Tools
Module with tools used all across the project. They are implemented in a way to be as reusable as possible.
1"""Module with tools used all across the project. They are implemented in a way to be as reusable as possible. 2""" 3 4import customtkinter as ctk 5from PIL import Image 6import configparser 7import sys 8import os 9from datetime import datetime 10import sounddevice 11from typing import Any 12import json 13from platform import system 14 15def resource_path(relative_path: str) -> str: 16 """Function obtaining the absolute path to desired relative path. 17 Ensures That pyinstaller executable will work properly. 18 19 Args: 20 relative_path (str): Relative or absolute path to resource. 21 22 Returns: 23 str: Absolute path to resource. 24 """ 25 try: 26 base_path = sys._MEIPASS2 # type: ignore 27 except Exception: 28 base_path = os.path.abspath(".") 29 return os.path.join(base_path, relative_path) 30 31def get_from_config(variable: str) -> str | int: 32 """Functions reading specific value from the config file. 33 34 Args: 35 variable (str): variable name from config file. 36 37 Returns: 38 str | int: Color, size or font name 39 """ 40 config = configparser.ConfigParser() 41 config.read(resource_path(os.path.join('assets', 'config.ini'))) 42 db_variable = config['database'][variable] 43 if variable == 'size': 44 return int(db_variable) 45 elif variable == 'font_name': 46 if system() == 'Linux': 47 db_variable = list(db_variable.split(' '))[0] 48 return db_variable 49 50def change_config(change_variable: str, value: str | int) -> None: 51 """Updates specific variable in config file. 52 53 Args: 54 change_variable (str): Variable name to change 55 value (str | int): Value to which the variable will be updated. 56 """ 57 config = configparser.ConfigParser() 58 config.read(resource_path(os.path.join('assets', 'config.ini'))) 59 if isinstance(value, int): 60 value = str(value) 61 config['database'][change_variable] = value 62 with open(resource_path(os.path.join('assets', 'config.ini')), 'w') as configfile: 63 config.write(configfile) 64 65def load_menu_image(option: str, resize: float = 1.5) -> ctk.CTkImage | None: 66 """Function loading images for menu. 67 68 Args: 69 option (str): Option image name. 70 resize (float, optional): Resize value [original_val // resize]. Defaults to 1.5. 71 72 Returns: 73 ctk.CTkImage | None: Image object. 74 """ 75 setting_icon_path = resource_path(os.path.join('assets', 'menu', f'{option}.png')) 76 try: 77 size = int(get_from_config('size')) // resize 78 setting_icon = Image.open(setting_icon_path).convert('RGBA') 79 return ctk.CTkImage(light_image=setting_icon, dark_image=setting_icon, size=(size, size)) 80 except (FileNotFoundError, FileExistsError) as e: 81 update_error_log(e) 82 return None 83 84def get_colors() -> dict: 85 """Function loading colors from config file. 86 87 Returns: 88 dict: Dictionary (later enum) of color name : color code. 89 """ 90 config = configparser.ConfigParser() 91 config.read(resource_path(os.path.join('assets', 'config.ini'))) 92 colors = dict(config['Colors']) 93 return colors 94 95def change_color(color_name: str, color_value: str) -> None: 96 """Function changing color value in config file. 97 98 Args: 99 color_name (str): Color name to change. 100 color_value (str): New color value. 101 """ 102 config = configparser.ConfigParser() 103 config.read(resource_path(os.path.join('assets', 'config.ini'))) 104 config['Colors'][color_name] = color_value 105 with open(resource_path(os.path.join('assets', 'config.ini')), 'w') as configfile: 106 config.write(configfile) 107 108def update_error_log(error: Exception) -> None: 109 """Appends new error to error log. 110 111 Args: 112 error (Exception): Error to append log file with. 113 """ 114 now: str = str(datetime.now()) 115 with open(resource_path('error.log'), 'a') as file: 116 file.write(f'[{now}]: Error occurred: {error} in {os.path.relpath(__file__)}\n') 117 118def play_sound(data: Any) -> None: 119 """Plays sound. 120 121 Args: 122 data (Any): Array like with raw sound data. 123 """ 124 try: 125 sounddevice.play(data) 126 except Exception as e: 127 update_error_log(e) 128 129def create_save_file(save_info: dict[tuple[int, int] | str, tuple[str, str, bool] | list[str]], current_turn: str, 130 white_moves: list[str], black_moves: list[str], game_over: bool, save_name: str | None=None) -> None: 131 """Creates save file in saves directory. Save is .json file with all positions, current turn information, previous notation and game over information. 132 133 Args: 134 save_info (dict[tuple[int, int] | str, tuple[str, str, bool] | list[str]]): Information to be saved in file. 135 current_turn (str): Information about color of the current player. 136 white_moves (list[str]): Notation from previous white moves. 137 black_moves (list[str]): Notation from previous black moves. 138 game_over (bool): Information about general state of the game. 139 save_name (str | None, optional): Name of the save file. Defaults to None. 140 """ 141 save_info_serialized = {f"{k[0]},{k[1]}": v for k, v in save_info.items()} 142 save_data = { 143 'current_turn': current_turn, 144 'board_state' : save_info_serialized, 145 'white_moves' : white_moves, 146 'black_moves' : black_moves, 147 'game_over' : game_over 148 } 149 if not save_name: 150 files: list[str] = [f for f in os.listdir(resource_path('saves')) if 'chess_game_' in f] 151 if files: 152 new_file: str = f'chess_game_{len(files)+1}.json' 153 else: 154 new_file = 'chess_game_1.json' 155 with open(resource_path(os.path.join('saves', new_file)), 'w') as file: 156 json.dump(save_data, file, indent=2) 157 else: 158 new_file = f'{save_name}.json' 159 with open(resource_path(os.path.join('saves', new_file)), 'w') as file: 160 json.dump(save_data, file, indent=2) 161 162def delete_save_file(file_name: str) -> bool: 163 """Removes save .json file from saves directory. 164 165 Args: 166 file_name (str): Name of the file to delete. 167 168 Returns: 169 bool: Returns True if file was removed successfully, False otherwise. 170 """ 171 file_path: str = resource_path(os.path.join('saves', file_name)) 172 if os.path.exists(file_path): 173 os.remove(file_path) 174 return True 175 else: 176 update_error_log(FileNotFoundError(f'Couldn\'t delete the file: {file_name}. File not found.')) 177 return False 178 179def get_save_info(file_name: str) -> dict: 180 """Gathers data from .json save file. 181 182 Args: 183 file_name (str): Name of the save to be loaded. 184 185 Returns: 186 dict: All needed information to load the game state. 187 """ 188 with open(resource_path(os.path.join('saves', file_name)), "r") as file: 189 data: dict = json.load(file) 190 return data
16def resource_path(relative_path: str) -> str: 17 """Function obtaining the absolute path to desired relative path. 18 Ensures That pyinstaller executable will work properly. 19 20 Args: 21 relative_path (str): Relative or absolute path to resource. 22 23 Returns: 24 str: Absolute path to resource. 25 """ 26 try: 27 base_path = sys._MEIPASS2 # type: ignore 28 except Exception: 29 base_path = os.path.abspath(".") 30 return os.path.join(base_path, relative_path)
Function obtaining the absolute path to desired relative path. Ensures That pyinstaller executable will work properly.
Arguments:
- relative_path (str): Relative or absolute path to resource.
Returns:
str: Absolute path to resource.
32def get_from_config(variable: str) -> str | int: 33 """Functions reading specific value from the config file. 34 35 Args: 36 variable (str): variable name from config file. 37 38 Returns: 39 str | int: Color, size or font name 40 """ 41 config = configparser.ConfigParser() 42 config.read(resource_path(os.path.join('assets', 'config.ini'))) 43 db_variable = config['database'][variable] 44 if variable == 'size': 45 return int(db_variable) 46 elif variable == 'font_name': 47 if system() == 'Linux': 48 db_variable = list(db_variable.split(' '))[0] 49 return db_variable
Functions reading specific value from the config file.
Arguments:
- variable (str): variable name from config file.
Returns:
str | int: Color, size or font name
51def change_config(change_variable: str, value: str | int) -> None: 52 """Updates specific variable in config file. 53 54 Args: 55 change_variable (str): Variable name to change 56 value (str | int): Value to which the variable will be updated. 57 """ 58 config = configparser.ConfigParser() 59 config.read(resource_path(os.path.join('assets', 'config.ini'))) 60 if isinstance(value, int): 61 value = str(value) 62 config['database'][change_variable] = value 63 with open(resource_path(os.path.join('assets', 'config.ini')), 'w') as configfile: 64 config.write(configfile)
Updates specific variable in config file.
Arguments:
- change_variable (str): Variable name to change
- value (str | int): Value to which the variable will be updated.
85def get_colors() -> dict: 86 """Function loading colors from config file. 87 88 Returns: 89 dict: Dictionary (later enum) of color name : color code. 90 """ 91 config = configparser.ConfigParser() 92 config.read(resource_path(os.path.join('assets', 'config.ini'))) 93 colors = dict(config['Colors']) 94 return colors
Function loading colors from config file.
Returns:
dict: Dictionary (later enum) of color name : color code.
96def change_color(color_name: str, color_value: str) -> None: 97 """Function changing color value in config file. 98 99 Args: 100 color_name (str): Color name to change. 101 color_value (str): New color value. 102 """ 103 config = configparser.ConfigParser() 104 config.read(resource_path(os.path.join('assets', 'config.ini'))) 105 config['Colors'][color_name] = color_value 106 with open(resource_path(os.path.join('assets', 'config.ini')), 'w') as configfile: 107 config.write(configfile)
Function changing color value in config file.
Arguments:
- color_name (str): Color name to change.
- color_value (str): New color value.
109def update_error_log(error: Exception) -> None: 110 """Appends new error to error log. 111 112 Args: 113 error (Exception): Error to append log file with. 114 """ 115 now: str = str(datetime.now()) 116 with open(resource_path('error.log'), 'a') as file: 117 file.write(f'[{now}]: Error occurred: {error} in {os.path.relpath(__file__)}\n')
Appends new error to error log.
Arguments:
- error (Exception): Error to append log file with.
119def play_sound(data: Any) -> None: 120 """Plays sound. 121 122 Args: 123 data (Any): Array like with raw sound data. 124 """ 125 try: 126 sounddevice.play(data) 127 except Exception as e: 128 update_error_log(e)
Plays sound.
Arguments:
- data (Any): Array like with raw sound data.
130def create_save_file(save_info: dict[tuple[int, int] | str, tuple[str, str, bool] | list[str]], current_turn: str, 131 white_moves: list[str], black_moves: list[str], game_over: bool, save_name: str | None=None) -> None: 132 """Creates save file in saves directory. Save is .json file with all positions, current turn information, previous notation and game over information. 133 134 Args: 135 save_info (dict[tuple[int, int] | str, tuple[str, str, bool] | list[str]]): Information to be saved in file. 136 current_turn (str): Information about color of the current player. 137 white_moves (list[str]): Notation from previous white moves. 138 black_moves (list[str]): Notation from previous black moves. 139 game_over (bool): Information about general state of the game. 140 save_name (str | None, optional): Name of the save file. Defaults to None. 141 """ 142 save_info_serialized = {f"{k[0]},{k[1]}": v for k, v in save_info.items()} 143 save_data = { 144 'current_turn': current_turn, 145 'board_state' : save_info_serialized, 146 'white_moves' : white_moves, 147 'black_moves' : black_moves, 148 'game_over' : game_over 149 } 150 if not save_name: 151 files: list[str] = [f for f in os.listdir(resource_path('saves')) if 'chess_game_' in f] 152 if files: 153 new_file: str = f'chess_game_{len(files)+1}.json' 154 else: 155 new_file = 'chess_game_1.json' 156 with open(resource_path(os.path.join('saves', new_file)), 'w') as file: 157 json.dump(save_data, file, indent=2) 158 else: 159 new_file = f'{save_name}.json' 160 with open(resource_path(os.path.join('saves', new_file)), 'w') as file: 161 json.dump(save_data, file, indent=2)
Creates save file in saves directory. Save is .json file with all positions, current turn information, previous notation and game over information.
Arguments:
- save_info (dict[tuple[int, int] | str, tuple[str, str, bool] | list[str]]): Information to be saved in file.
- current_turn (str): Information about color of the current player.
- white_moves (list[str]): Notation from previous white moves.
- black_moves (list[str]): Notation from previous black moves.
- game_over (bool): Information about general state of the game.
- save_name (str | None, optional): Name of the save file. Defaults to None.
163def delete_save_file(file_name: str) -> bool: 164 """Removes save .json file from saves directory. 165 166 Args: 167 file_name (str): Name of the file to delete. 168 169 Returns: 170 bool: Returns True if file was removed successfully, False otherwise. 171 """ 172 file_path: str = resource_path(os.path.join('saves', file_name)) 173 if os.path.exists(file_path): 174 os.remove(file_path) 175 return True 176 else: 177 update_error_log(FileNotFoundError(f'Couldn\'t delete the file: {file_name}. File not found.')) 178 return False
Removes save .json file from saves directory.
Arguments:
- file_name (str): Name of the file to delete.
Returns:
bool: Returns True if file was removed successfully, False otherwise.
180def get_save_info(file_name: str) -> dict: 181 """Gathers data from .json save file. 182 183 Args: 184 file_name (str): Name of the save to be loaded. 185 186 Returns: 187 dict: All needed information to load the game state. 188 """ 189 with open(resource_path(os.path.join('saves', file_name)), "r") as file: 190 data: dict = json.load(file) 191 return data
Gathers data from .json save file.
Arguments:
- file_name (str): Name of the save to be loaded.
Returns:
dict: All needed information to load the game state.