Main

Main file of Chess game entirely made in python with properly working game engine. Contains a lot of customization option for user. App allows using custom assets, fonts and colors. Comes with easy to use menu to change the properties and readable config file.

Libraries used:

  • customtkinter
  • threading
  • os
  • sys
  • platform
  • configparser
  • Pillow [PIL]
  • re [regex]
  • fontTools
  • subprocess
  • pywinstyles
  • built in libraries
  • sounddevice
  • soundfile

Link to full documentation: http://chessdocumentation.ct.ws/

  1"""Main file of Chess game entirely made in python with properly working game engine. Contains a lot of customization option for user.
  2App allows using custom assets, fonts and colors. Comes with easy to use menu to change the properties and readable config file.
  3
  4Libraries used: 
  5 - customtkinter
  6 - threading
  7 - os
  8 - sys
  9 - platform
 10 - configparser
 11 - Pillow [PIL]
 12 - re [regex]
 13 - fontTools
 14 - subprocess
 15 - pywinstyles
 16 - built in libraries
 17 - sounddevice
 18 - soundfile
 19
 20Link to full documentation: http://chessdocumentation.ct.ws/
 21"""
 22
 23import customtkinter as ctk
 24import os
 25import threading
 26from concurrent.futures import ThreadPoolExecutor
 27
 28from tools import resource_path, get_from_config
 29from properties import COLOR, SYSTEM
 30from menus import MovesRecord, Options
 31from board import Board
 32
 33class MainWindow(ctk.CTk):
 34    """Main class handling the app. Setting size, minimum size, font loading, icon setting,
 35    updating font, updating assets and restarting the game all happens here.
 36
 37    Args:
 38        ctk.CTk : Main app window of customtkinter library (master).
 39    """
 40    def __init__(self) -> None:
 41        """Constructor for the MainWindow class: 
 42             - sets title
 43             - sets geometry
 44             - sets minimum size of the window
 45             - loads font
 46             - Creates instances of the classes:
 47              - MoveRecord
 48              - Options
 49              - Board
 50             - loads theme of the app from the config: get_from_config
 51        """
 52        super().__init__(fg_color=COLOR.BACKGROUND)
 53        self.title('Chess')
 54        self.geometry(self.set_window_size())
 55        size: int = int(get_from_config('size'))
 56        self.minsize(((size + 2) * 10 + 40)+ 400, ((size + 2) * 10 + 40))
 57        self.load_font()
 58        self.moves_record: MovesRecord = MovesRecord(self)
 59        self.moves_record.pack(side=ctk.RIGHT, padx=10, pady=10, fill=ctk.Y)
 60        self.options: Options = Options(self, self.restart_game, self.update_assets, self.update_font, self.get_board)
 61        self.options.pack(side=ctk.LEFT, padx=10, pady=10, fill=ctk.Y)
 62        self.board: Board = Board(self, self.moves_record, size)
 63        self.theme: str = str(get_from_config('theme'))
 64        self.set_icon()
 65
 66    def load_font(self) -> None:
 67        """Function loads font independently on the users operating system.
 68        """
 69        font: str = str(get_from_config('font_file_name'))
 70        if SYSTEM == 'Windows':
 71            ctk.FontManager.windows_load_font(resource_path(os.path.join('fonts', font)))
 72        else:
 73            ctk.FontManager.load_font(resource_path(os.path.join('fonts', font)))
 74
 75    def set_icon(self) -> None:
 76        """Only for windows machines logo icon will be set due to lack of implementation for linux and mac.
 77        """
 78        if SYSTEM == 'Windows':
 79            self.iconbitmap(resource_path(os.path.join('assets', 'logo.ico')))
 80
 81    def set_window_size(self) -> str:
 82        """Calculates the size necessary to display all elements of the app on the screen.
 83
 84        Returns:
 85            str: f'{width}x{height]}' because customtkinter uses f'{width}x{height]}' to set the size of the window.
 86        """
 87        size: int = int(get_from_config('size'))
 88        size = (size+2) * 10 + 40
 89        center_pos: str = f'+{(self.winfo_screenwidth()-(int(size*1.5)))//2}+{(self.winfo_screenheight()-(int(size)*1.1))//2}'
 90        return f'{size + 300}x{size}{center_pos}'
 91
 92    def restart_game(self) -> None:
 93        """Helper function for game restart just by calling functions from Board and MoveRecord classes.
 94        """
 95        self.board.restart_game()
 96        self.moves_record.restart()
 97
 98    def update_assets(self) -> None:
 99        """Updates asset on the Board using thread to avoid window freezing.
100        """
101        for row in self.board.board:
102            for cell in row:
103                if cell.figure:
104                    threading.Thread(target=cell.figure.update_image).start()
105
106    def get_board(self) -> Board:
107        """Getter for board object.
108
109        Returns:
110            Board: Board object.
111        """
112        return self.board
113
114    def update_font(self, widget=None) -> None:
115        """Helper function updating the font during app runtime without freezing the window.
116
117        Args:
118            widget (Any, optional): Child of the widget. Defaults to None as master widget doesn't have any parents.
119        """
120        if widget is None:
121            widget = self
122            self.load_font()
123        def thread_task():
124            widgets = []
125            queue = [widget]
126            while queue:
127                current_widget = queue.pop()
128                widgets.append(current_widget)
129                queue.extend(current_widget.winfo_children())
130            with ThreadPoolExecutor() as executor:
131                for child in widgets:
132                    if isinstance(child, ctk.CTkLabel | ctk.CTkButton):
133                        size = child.cget('font').cget('size')
134                        executor.submit(self.__update_font_on_main_thread, child, size)
135        threading.Thread(target=thread_task, daemon=True).start()
136
137    def __update_font_on_main_thread(self, widget, size: int) -> None:
138        self.after(0, lambda: widget.configure(font=ctk.CTkFont(get_from_config('font_name'), size)))
139
140if __name__ == "__main__":
141    ctk.deactivate_automatic_dpi_awareness()
142    app = MainWindow()
143    app.mainloop()
class MainWindow(customtkinter.windows.ctk_tk.CTk):
 34class MainWindow(ctk.CTk):
 35    """Main class handling the app. Setting size, minimum size, font loading, icon setting,
 36    updating font, updating assets and restarting the game all happens here.
 37
 38    Args:
 39        ctk.CTk : Main app window of customtkinter library (master).
 40    """
 41    def __init__(self) -> None:
 42        """Constructor for the MainWindow class: 
 43             - sets title
 44             - sets geometry
 45             - sets minimum size of the window
 46             - loads font
 47             - Creates instances of the classes:
 48              - MoveRecord
 49              - Options
 50              - Board
 51             - loads theme of the app from the config: get_from_config
 52        """
 53        super().__init__(fg_color=COLOR.BACKGROUND)
 54        self.title('Chess')
 55        self.geometry(self.set_window_size())
 56        size: int = int(get_from_config('size'))
 57        self.minsize(((size + 2) * 10 + 40)+ 400, ((size + 2) * 10 + 40))
 58        self.load_font()
 59        self.moves_record: MovesRecord = MovesRecord(self)
 60        self.moves_record.pack(side=ctk.RIGHT, padx=10, pady=10, fill=ctk.Y)
 61        self.options: Options = Options(self, self.restart_game, self.update_assets, self.update_font, self.get_board)
 62        self.options.pack(side=ctk.LEFT, padx=10, pady=10, fill=ctk.Y)
 63        self.board: Board = Board(self, self.moves_record, size)
 64        self.theme: str = str(get_from_config('theme'))
 65        self.set_icon()
 66
 67    def load_font(self) -> None:
 68        """Function loads font independently on the users operating system.
 69        """
 70        font: str = str(get_from_config('font_file_name'))
 71        if SYSTEM == 'Windows':
 72            ctk.FontManager.windows_load_font(resource_path(os.path.join('fonts', font)))
 73        else:
 74            ctk.FontManager.load_font(resource_path(os.path.join('fonts', font)))
 75
 76    def set_icon(self) -> None:
 77        """Only for windows machines logo icon will be set due to lack of implementation for linux and mac.
 78        """
 79        if SYSTEM == 'Windows':
 80            self.iconbitmap(resource_path(os.path.join('assets', 'logo.ico')))
 81
 82    def set_window_size(self) -> str:
 83        """Calculates the size necessary to display all elements of the app on the screen.
 84
 85        Returns:
 86            str: f'{width}x{height]}' because customtkinter uses f'{width}x{height]}' to set the size of the window.
 87        """
 88        size: int = int(get_from_config('size'))
 89        size = (size+2) * 10 + 40
 90        center_pos: str = f'+{(self.winfo_screenwidth()-(int(size*1.5)))//2}+{(self.winfo_screenheight()-(int(size)*1.1))//2}'
 91        return f'{size + 300}x{size}{center_pos}'
 92
 93    def restart_game(self) -> None:
 94        """Helper function for game restart just by calling functions from Board and MoveRecord classes.
 95        """
 96        self.board.restart_game()
 97        self.moves_record.restart()
 98
 99    def update_assets(self) -> None:
100        """Updates asset on the Board using thread to avoid window freezing.
101        """
102        for row in self.board.board:
103            for cell in row:
104                if cell.figure:
105                    threading.Thread(target=cell.figure.update_image).start()
106
107    def get_board(self) -> Board:
108        """Getter for board object.
109
110        Returns:
111            Board: Board object.
112        """
113        return self.board
114
115    def update_font(self, widget=None) -> None:
116        """Helper function updating the font during app runtime without freezing the window.
117
118        Args:
119            widget (Any, optional): Child of the widget. Defaults to None as master widget doesn't have any parents.
120        """
121        if widget is None:
122            widget = self
123            self.load_font()
124        def thread_task():
125            widgets = []
126            queue = [widget]
127            while queue:
128                current_widget = queue.pop()
129                widgets.append(current_widget)
130                queue.extend(current_widget.winfo_children())
131            with ThreadPoolExecutor() as executor:
132                for child in widgets:
133                    if isinstance(child, ctk.CTkLabel | ctk.CTkButton):
134                        size = child.cget('font').cget('size')
135                        executor.submit(self.__update_font_on_main_thread, child, size)
136        threading.Thread(target=thread_task, daemon=True).start()
137
138    def __update_font_on_main_thread(self, widget, size: int) -> None:
139        self.after(0, lambda: widget.configure(font=ctk.CTkFont(get_from_config('font_name'), size)))

Main class handling the app. Setting size, minimum size, font loading, icon setting, updating font, updating assets and restarting the game all happens here.

Arguments:
  • ctk.CTk : Main app window of customtkinter library (master).
MainWindow()
41    def __init__(self) -> None:
42        """Constructor for the MainWindow class: 
43             - sets title
44             - sets geometry
45             - sets minimum size of the window
46             - loads font
47             - Creates instances of the classes:
48              - MoveRecord
49              - Options
50              - Board
51             - loads theme of the app from the config: get_from_config
52        """
53        super().__init__(fg_color=COLOR.BACKGROUND)
54        self.title('Chess')
55        self.geometry(self.set_window_size())
56        size: int = int(get_from_config('size'))
57        self.minsize(((size + 2) * 10 + 40)+ 400, ((size + 2) * 10 + 40))
58        self.load_font()
59        self.moves_record: MovesRecord = MovesRecord(self)
60        self.moves_record.pack(side=ctk.RIGHT, padx=10, pady=10, fill=ctk.Y)
61        self.options: Options = Options(self, self.restart_game, self.update_assets, self.update_font, self.get_board)
62        self.options.pack(side=ctk.LEFT, padx=10, pady=10, fill=ctk.Y)
63        self.board: Board = Board(self, self.moves_record, size)
64        self.theme: str = str(get_from_config('theme'))
65        self.set_icon()

Constructor for the MainWindow class:

  • sets title
  • sets geometry
  • sets minimum size of the window
  • loads font
  • Creates instances of the classes:
    • MoveRecord
    • Options
    • Board
  • loads theme of the app from the config: get_from_config
moves_record: menus.MovesRecord
options: menus.Options
board: board.Board
theme: str
def load_font(self) -> None:
67    def load_font(self) -> None:
68        """Function loads font independently on the users operating system.
69        """
70        font: str = str(get_from_config('font_file_name'))
71        if SYSTEM == 'Windows':
72            ctk.FontManager.windows_load_font(resource_path(os.path.join('fonts', font)))
73        else:
74            ctk.FontManager.load_font(resource_path(os.path.join('fonts', font)))

Function loads font independently on the users operating system.

def set_icon(self) -> None:
76    def set_icon(self) -> None:
77        """Only for windows machines logo icon will be set due to lack of implementation for linux and mac.
78        """
79        if SYSTEM == 'Windows':
80            self.iconbitmap(resource_path(os.path.join('assets', 'logo.ico')))

Only for windows machines logo icon will be set due to lack of implementation for linux and mac.

def set_window_size(self) -> str:
82    def set_window_size(self) -> str:
83        """Calculates the size necessary to display all elements of the app on the screen.
84
85        Returns:
86            str: f'{width}x{height]}' because customtkinter uses f'{width}x{height]}' to set the size of the window.
87        """
88        size: int = int(get_from_config('size'))
89        size = (size+2) * 10 + 40
90        center_pos: str = f'+{(self.winfo_screenwidth()-(int(size*1.5)))//2}+{(self.winfo_screenheight()-(int(size)*1.1))//2}'
91        return f'{size + 300}x{size}{center_pos}'

Calculates the size necessary to display all elements of the app on the screen.

Returns:

str: f'{width}x{height]}' because customtkinter uses f'{width}x{height]}' to set the size of the window.

def restart_game(self) -> None:
93    def restart_game(self) -> None:
94        """Helper function for game restart just by calling functions from Board and MoveRecord classes.
95        """
96        self.board.restart_game()
97        self.moves_record.restart()

Helper function for game restart just by calling functions from Board and MoveRecord classes.

def update_assets(self) -> None:
 99    def update_assets(self) -> None:
100        """Updates asset on the Board using thread to avoid window freezing.
101        """
102        for row in self.board.board:
103            for cell in row:
104                if cell.figure:
105                    threading.Thread(target=cell.figure.update_image).start()

Updates asset on the Board using thread to avoid window freezing.

def get_board(self) -> board.Board:
107    def get_board(self) -> Board:
108        """Getter for board object.
109
110        Returns:
111            Board: Board object.
112        """
113        return self.board

Getter for board object.

Returns:

Board: Board object.

def update_font(self, widget=None) -> None:
115    def update_font(self, widget=None) -> None:
116        """Helper function updating the font during app runtime without freezing the window.
117
118        Args:
119            widget (Any, optional): Child of the widget. Defaults to None as master widget doesn't have any parents.
120        """
121        if widget is None:
122            widget = self
123            self.load_font()
124        def thread_task():
125            widgets = []
126            queue = [widget]
127            while queue:
128                current_widget = queue.pop()
129                widgets.append(current_widget)
130                queue.extend(current_widget.winfo_children())
131            with ThreadPoolExecutor() as executor:
132                for child in widgets:
133                    if isinstance(child, ctk.CTkLabel | ctk.CTkButton):
134                        size = child.cget('font').cget('size')
135                        executor.submit(self.__update_font_on_main_thread, child, size)
136        threading.Thread(target=thread_task, daemon=True).start()

Helper function updating the font during app runtime without freezing the window.

Arguments:
  • widget (Any, optional): Child of the widget. Defaults to None as master widget doesn't have any parents.