-
Notifications
You must be signed in to change notification settings - Fork 1
The Defs Module
The Defs module contains all the Constants, Custom Errors and Values repeating throughout the program. It contains two python files and one directory containing three Enums.
The enum directory contains three Enum
classes, for Colors, GameModes and MoveType.
Colors.py
class Color(Enum):
WHITE = 1
BLACK = -1
EMPTY = 0
GameModes.py
class GameModes(Enum):
PLAYER_VS_PLAYER = 22
PLAYER_VS_AI = 21
AI_VS_PLAYER = 12
AI_VS_AI = 11
MoveType.py
class MoveType(Enum):
NORMAL = 2
CASTLE = 3
EN_PASSANT = 4
PROMOTION = 5
NONE = -2
There are broadly two reasons for using Enum
instead of Strings
for representing these values.
-
The first one comes from PEP 435.
The properties of an enumeration are useful for defining an immutable, related set of constant values that may or may not have a semantic meaning.
-
The second reason, which is more specific for my use case, is by creating a pre-defined set of constant values, there will be a lesser chance for typos and case-sensitive comparisons.
The reason for using Integer
values for Enum
and not String
takes its roots to this answer on StackOverflow.
- String comparison: You have to compare each of the digits one by one. This is 10 comparisons, since the integers only differ by the last digit.
- Integer comparison: You can do this in one comparison, saving 9 comparisons as compared to the String comparison.
(considering a 10 character String and 10 digit Integer)
Even though the above quote is specific to C++, broadly it applies to every programming language, including Python.
The program as a whole is dependent on project-specific objects and data types, so Python is not able to detect project-specific errors. An example of this would be:
piece.set_color("Gray")
Python would never know if the above code has an error, because neither it checks for data types, not does it know that there exists no Gray color in Chess. So, this would remain unnoticed until appropriate checks are performed in error prone areas. Whenever an unknown object/data type gets passed as a parameter in a method, the checks catch that and show custom errors specific to the situation. An example of one of those checks is in the fen_to_board()
method in the GameState
class.
if ('K' not in fen_list[0]) or ('k' not in fen_list[0]):
raise ChessErrors.NoKingError(Constants.no_king_error)
If the provided FEN string does not contain either the White or the Black King, NoKingError
is raised with the appropriate message.
All the custom errors inherit from the ChessErrors
class.
class ChessErrors(Exception):
"""Base class for exceptions in this module."""
pass
The program accesses some constant values repeatedly, so instead of declaring them everytime when needed, they are declared in a Constants.py
file and are accessed wherever necessary. This also helps in customization and testing with certain specific values fairly easy, as the values have to be edited only once, and once saved, they get applied project-wide.
The Constants.py
file can also be addressed as Game Settings when you have a proper knowledge on how to change the values without disturbing the program execution.
The first line of the Constants.py
file contains the FEN string for the initial board.
initial_board = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
This FEN string is rendered to a playable board by the fen_to_board()
method in the GameState
class.
The next block of the Constants.py
file looks like this:
WIDTH = HEIGHT = 720
DIMENSION = 8
SQ_SIZE = HEIGHT // DIMENSION
MAX_FPS = 60
IMAGES = {}
PIECE_VALUES = {'K': 20000, 'Q': 900, 'R': 500, 'B': 330, 'N': 320, 'P': 100}
CHECKMATE = 100000
STALEMATE = -100000
DEPTH = 2
GAME_MODE = GameModes.AI_VS_AI
Here the WIDTH
, HEIGHT
, DIMENSION
, SQ_SIZE
(square size), MAX_FPS
(maximum frames rendered per second), IMAGES
(images for the pieces) and GAME_MODE
are defined, which can be treated as customizations for the board. A word of caution - The variables PIECE_VALUES
, CHECKMATE
, STALEMATE
and DEPTH
are used in the functioning of the AI, and editing them can lead to disastrous moves by the AI, or heavy usage of your computer's processing power and memory.
The next block of code looks like this:
CASTLES = {
((7, 4), (7, 6)),
((7, 4), (7, 2)),
((0, 4), (0, 6)),
((0, 4), (0, 2))
}
EN_PASSANTS = {
((3, 0), (2, 1)),
((3, 1), (2, 0)),
((3, 1), (2, 2)),
((3, 2), (2, 1)),
((3, 2), (2, 3)),
((3, 3), (2, 2)),
((3, 3), (2, 4)),
((3, 4), (2, 3)),
((3, 4), (2, 5)),
((3, 5), (2, 4)),
((3, 5), (2, 6)),
((3, 6), (2, 5)),
((3, 6), (2, 7)),
((3, 7), (2, 6)),
((4, 0), (5, 1)),
((4, 1), (5, 0)),
((4, 1), (5, 2)),
((4, 2), (5, 1)),
((4, 2), (5, 3)),
((4, 3), (5, 2)),
((4, 3), (5, 4)),
((4, 4), (5, 3)),
((4, 4), (5, 5)),
((4, 5), (5, 4)),
((4, 5), (5, 6)),
((4, 6), (5, 5)),
((4, 6), (5, 7)),
((4, 7), (5, 6))
}
These contain all the possible castles and en-passants, so that the program does not have to generate them everytime they are needed. They are stored in the set
data type to optimize the searching of a possible pair in constant ($O(1)$) time. These are also not supposed to be edited, as changing these values can lead to malfunctioning of the game engine.
The next block of code looks like this:
colors = [(238, 216, 192), (171, 122, 101)]
highlight_colors = [(207, 172, 106), (197, 173, 96)]
legal_move_colors = [(221, 89, 89), (197, 68, 79)]
These variables store the list of colors used by the program in the format [COLOR USED IN WHITE SQUARES, COLOR USED IN BLACK SQUARES]
, where each entry in the list is a tuple containing RGB values of the color in the sequence (R, G, B)
. If you want to customize the colors in the program, you can edit these values.
-
colors
variable contains the colors used by the board in itsidle
state, or when the user has triggered no event. -
highlight_colors
variable contains the colors which are used in highlighting the square selected by the user containing a piece. -
legal_move_colors
variable contains the colors used in highlighting thelegal_moves
of a piece on the board.
The next few blocks of code contains the list of colors applied to every square once the engine has been initialized, and a backup of the same, and the error messages displayed for every kind of error.
The next block of code contains two methods.
def to_matrix(lst: list, number: int) -> list:
return [lst[i:i + number] for i in range(0, len(lst), number)]
def get_color_alpha(color: Color) -> str:
return "w" if color == Color.WHITE else "b"
The to_matrix()
function converts the 1D board array, generated by converting the FEN string to a playable board array, to a 2D board array which can easily be incorporated in the pygame window. The get_color_alpha()
method is called when the program wants to import the required icons for the pieces. Since the Color
values are Integer
Enum
values, they cannot be directly used to represent the file name for icons. So they are first converted to the characteristic letter for the specific color, and then are used to represent file names. It gets called in the first few starting lines of the main.py
file.