chessai.ui.text
1import sys 2import typing 3 4import chessai.core.gamestate 5import chessai.core.piece 6import chessai.core.ui 7 8DEFAULT_PADDING_SIZE: int = 1 9""" The default number of padding spaces between each piece and the square border. """ 10 11class TextUI(chessai.core.ui.UI): 12 """ 13 A simple UI that renders the game to a text stream. 14 This UI will be simple and generally meant for debugging. 15 """ 16 17 SYMBOL_SIZE: int = 2 18 """ 19 The space reserved to display the symbols. 20 Pieces represent themselves as unicode symbols, which often take two spaces. 21 """ 22 23 def __init__(self, 24 output_stream: typing.TextIO, 25 padding_size: int = DEFAULT_PADDING_SIZE, 26 **kwargs: typing.Any) -> None: 27 super().__init__(**kwargs) 28 29 self._output_stream: typing.TextIO = output_stream 30 """ The stream output will be sent to. """ 31 32 self._padding_size: int = padding_size 33 """ The number of padding spaces between each piece and the square border. """ 34 35 self._cell_width: int = self.SYMBOL_SIZE 36 """ The width of an individual cell, including the padding and symbol sizes. """ 37 38 self._file_labels: str | None = None 39 """ The file labels to be displayed at the bottom of the board. """ 40 41 self._horizontal_border: str | None = None 42 """ The horizontal border to be displayed between each rank. """ 43 44 def draw(self, 45 state: chessai.core.gamestate.GameState, 46 termination_reason: chessai.core.types.TerminationReason | None = None, 47 **kwargs: typing.Any, 48 ) -> None: 49 if (state.get_previous_action() == chessai.core.action.NoneAction()): 50 return 51 52 # Draw the top border. 53 self._output_stream.write(self._get_horizontal_border(state)) 54 55 prefix_padding_size = self._get_prefix_padding_size(state) 56 57 for rank in range((state.board.num_ranks - 1), -1, -1): 58 rank_str = str(rank + 1) 59 rank_padding_size = prefix_padding_size - len(rank_str) 60 rank_padding = ' ' * rank_padding_size 61 62 line = f"{rank_str}{rank_padding}{self._get_padding()}|" 63 64 for file in range(state.board.num_files): 65 piece_symbol = self._translate_piece(state.board.get(file, rank)) 66 line += f"{piece_symbol}|" 67 68 self._output_stream.write(line + "\n") 69 self._output_stream.write(self._get_horizontal_border(state)) 70 71 self._output_stream.write(self._get_file_labels(state)) 72 73 # Display the most recent action. 74 action = state.get_previous_action() 75 if (action is not None): 76 self._output_stream.write(f"Previous action: '{action}'.\n") 77 78 if (termination_reason is not None): 79 self._output_stream.write('Game Over!\n') 80 self._output_stream.write(f"Termination Reason: '{termination_reason}'.\n") 81 82 self._output_stream.write('\n') 83 self._output_stream.flush() 84 85 def _get_padding(self) -> str: 86 """ Get the padding for this UI. """ 87 88 return " " * self._padding_size 89 90 def _get_prefix_padding(self, state: chessai.core.gamestate.GameState) -> str: 91 """ 92 Get the initial padding for rows without a column numbering, 93 such as the horizontal border and the file labels. 94 """ 95 96 prefix_padding_size = self._get_prefix_padding_size(state) 97 98 prefix_padding = ' ' * prefix_padding_size 99 100 return f"{prefix_padding}{self._get_padding()}" 101 102 103 def _get_prefix_padding_size(self, state: chessai.core.gamestate.GameState) -> int: 104 """ Get the size of the prefix padding to account for the rank labels. """ 105 106 prefix_padding_size = 1 107 num_ranks = state.board.num_ranks 108 109 while (num_ranks >= 10): 110 prefix_padding_size += 1 111 num_ranks = num_ranks // 10 112 113 return prefix_padding_size 114 115 def _get_horizontal_border(self, state: chessai.core.gamestate.GameState) -> str: 116 """ Get the horizontal border for the UI. """ 117 118 if (self._horizontal_border is not None): 119 return self._horizontal_border 120 121 dash_segment = '-' * self._cell_width + '+' 122 123 horizontal_border = f"{self._get_prefix_padding(state)}+" + (dash_segment * state.board.num_files) + '\n' 124 125 # Cache the horizontal border for subsequent calls. 126 self._horizontal_border = horizontal_border 127 128 return self._horizontal_border 129 130 def _get_file_labels(self, state: chessai.core.gamestate.GameState) -> str: 131 """ Get the labels of the files for the bottom of the board. """ 132 133 if (self._file_labels is not None): 134 return self._file_labels 135 136 file_labels = self._get_prefix_padding(state) 137 for file in range(state.board.num_files): 138 # Use a coordinate to get the file name. 139 coordinate = chessai.core.coordinate.Coordinate(file, 0) 140 141 file_str = coordinate.uci(only_file = True) 142 file_labels += f"{self._get_padding()}{file_str}{self._get_padding()}" 143 144 file_labels += '\n' 145 146 # Cache the file labels for subsequent calls to the UI. 147 self._file_labels = file_labels 148 149 return file_labels 150 151 def _translate_piece(self, piece: chessai.core.piece.Piece | None) -> str: 152 """ 153 Convert a piece to a string. 154 This can be trivial (since a piece knows how to represent itself as a string), 155 but this allows children to implement special conversions. 156 """ 157 158 if (piece is None): 159 return ' ' * self.SYMBOL_SIZE 160 161 return piece.unicode_symbol().center(self.SYMBOL_SIZE) 162 163class StdioUI(TextUI): 164 """ 165 A convenience class for a TextUI using stdout. 166 """ 167 168 def __init__(self, **kwargs: typing.Any) -> None: 169 super().__init__(sys.stdout, **kwargs)
The default number of padding spaces between each piece and the square border.
12class TextUI(chessai.core.ui.UI): 13 """ 14 A simple UI that renders the game to a text stream. 15 This UI will be simple and generally meant for debugging. 16 """ 17 18 SYMBOL_SIZE: int = 2 19 """ 20 The space reserved to display the symbols. 21 Pieces represent themselves as unicode symbols, which often take two spaces. 22 """ 23 24 def __init__(self, 25 output_stream: typing.TextIO, 26 padding_size: int = DEFAULT_PADDING_SIZE, 27 **kwargs: typing.Any) -> None: 28 super().__init__(**kwargs) 29 30 self._output_stream: typing.TextIO = output_stream 31 """ The stream output will be sent to. """ 32 33 self._padding_size: int = padding_size 34 """ The number of padding spaces between each piece and the square border. """ 35 36 self._cell_width: int = self.SYMBOL_SIZE 37 """ The width of an individual cell, including the padding and symbol sizes. """ 38 39 self._file_labels: str | None = None 40 """ The file labels to be displayed at the bottom of the board. """ 41 42 self._horizontal_border: str | None = None 43 """ The horizontal border to be displayed between each rank. """ 44 45 def draw(self, 46 state: chessai.core.gamestate.GameState, 47 termination_reason: chessai.core.types.TerminationReason | None = None, 48 **kwargs: typing.Any, 49 ) -> None: 50 if (state.get_previous_action() == chessai.core.action.NoneAction()): 51 return 52 53 # Draw the top border. 54 self._output_stream.write(self._get_horizontal_border(state)) 55 56 prefix_padding_size = self._get_prefix_padding_size(state) 57 58 for rank in range((state.board.num_ranks - 1), -1, -1): 59 rank_str = str(rank + 1) 60 rank_padding_size = prefix_padding_size - len(rank_str) 61 rank_padding = ' ' * rank_padding_size 62 63 line = f"{rank_str}{rank_padding}{self._get_padding()}|" 64 65 for file in range(state.board.num_files): 66 piece_symbol = self._translate_piece(state.board.get(file, rank)) 67 line += f"{piece_symbol}|" 68 69 self._output_stream.write(line + "\n") 70 self._output_stream.write(self._get_horizontal_border(state)) 71 72 self._output_stream.write(self._get_file_labels(state)) 73 74 # Display the most recent action. 75 action = state.get_previous_action() 76 if (action is not None): 77 self._output_stream.write(f"Previous action: '{action}'.\n") 78 79 if (termination_reason is not None): 80 self._output_stream.write('Game Over!\n') 81 self._output_stream.write(f"Termination Reason: '{termination_reason}'.\n") 82 83 self._output_stream.write('\n') 84 self._output_stream.flush() 85 86 def _get_padding(self) -> str: 87 """ Get the padding for this UI. """ 88 89 return " " * self._padding_size 90 91 def _get_prefix_padding(self, state: chessai.core.gamestate.GameState) -> str: 92 """ 93 Get the initial padding for rows without a column numbering, 94 such as the horizontal border and the file labels. 95 """ 96 97 prefix_padding_size = self._get_prefix_padding_size(state) 98 99 prefix_padding = ' ' * prefix_padding_size 100 101 return f"{prefix_padding}{self._get_padding()}" 102 103 104 def _get_prefix_padding_size(self, state: chessai.core.gamestate.GameState) -> int: 105 """ Get the size of the prefix padding to account for the rank labels. """ 106 107 prefix_padding_size = 1 108 num_ranks = state.board.num_ranks 109 110 while (num_ranks >= 10): 111 prefix_padding_size += 1 112 num_ranks = num_ranks // 10 113 114 return prefix_padding_size 115 116 def _get_horizontal_border(self, state: chessai.core.gamestate.GameState) -> str: 117 """ Get the horizontal border for the UI. """ 118 119 if (self._horizontal_border is not None): 120 return self._horizontal_border 121 122 dash_segment = '-' * self._cell_width + '+' 123 124 horizontal_border = f"{self._get_prefix_padding(state)}+" + (dash_segment * state.board.num_files) + '\n' 125 126 # Cache the horizontal border for subsequent calls. 127 self._horizontal_border = horizontal_border 128 129 return self._horizontal_border 130 131 def _get_file_labels(self, state: chessai.core.gamestate.GameState) -> str: 132 """ Get the labels of the files for the bottom of the board. """ 133 134 if (self._file_labels is not None): 135 return self._file_labels 136 137 file_labels = self._get_prefix_padding(state) 138 for file in range(state.board.num_files): 139 # Use a coordinate to get the file name. 140 coordinate = chessai.core.coordinate.Coordinate(file, 0) 141 142 file_str = coordinate.uci(only_file = True) 143 file_labels += f"{self._get_padding()}{file_str}{self._get_padding()}" 144 145 file_labels += '\n' 146 147 # Cache the file labels for subsequent calls to the UI. 148 self._file_labels = file_labels 149 150 return file_labels 151 152 def _translate_piece(self, piece: chessai.core.piece.Piece | None) -> str: 153 """ 154 Convert a piece to a string. 155 This can be trivial (since a piece knows how to represent itself as a string), 156 but this allows children to implement special conversions. 157 """ 158 159 if (piece is None): 160 return ' ' * self.SYMBOL_SIZE 161 162 return piece.unicode_symbol().center(self.SYMBOL_SIZE)
A simple UI that renders the game to a text stream. This UI will be simple and generally meant for debugging.
24 def __init__(self, 25 output_stream: typing.TextIO, 26 padding_size: int = DEFAULT_PADDING_SIZE, 27 **kwargs: typing.Any) -> None: 28 super().__init__(**kwargs) 29 30 self._output_stream: typing.TextIO = output_stream 31 """ The stream output will be sent to. """ 32 33 self._padding_size: int = padding_size 34 """ The number of padding spaces between each piece and the square border. """ 35 36 self._cell_width: int = self.SYMBOL_SIZE 37 """ The width of an individual cell, including the padding and symbol sizes. """ 38 39 self._file_labels: str | None = None 40 """ The file labels to be displayed at the bottom of the board. """ 41 42 self._horizontal_border: str | None = None 43 """ The horizontal border to be displayed between each rank. """
The space reserved to display the symbols. Pieces represent themselves as unicode symbols, which often take two spaces.
45 def draw(self, 46 state: chessai.core.gamestate.GameState, 47 termination_reason: chessai.core.types.TerminationReason | None = None, 48 **kwargs: typing.Any, 49 ) -> None: 50 if (state.get_previous_action() == chessai.core.action.NoneAction()): 51 return 52 53 # Draw the top border. 54 self._output_stream.write(self._get_horizontal_border(state)) 55 56 prefix_padding_size = self._get_prefix_padding_size(state) 57 58 for rank in range((state.board.num_ranks - 1), -1, -1): 59 rank_str = str(rank + 1) 60 rank_padding_size = prefix_padding_size - len(rank_str) 61 rank_padding = ' ' * rank_padding_size 62 63 line = f"{rank_str}{rank_padding}{self._get_padding()}|" 64 65 for file in range(state.board.num_files): 66 piece_symbol = self._translate_piece(state.board.get(file, rank)) 67 line += f"{piece_symbol}|" 68 69 self._output_stream.write(line + "\n") 70 self._output_stream.write(self._get_horizontal_border(state)) 71 72 self._output_stream.write(self._get_file_labels(state)) 73 74 # Display the most recent action. 75 action = state.get_previous_action() 76 if (action is not None): 77 self._output_stream.write(f"Previous action: '{action}'.\n") 78 79 if (termination_reason is not None): 80 self._output_stream.write('Game Over!\n') 81 self._output_stream.write(f"Termination Reason: '{termination_reason}'.\n") 82 83 self._output_stream.write('\n') 84 self._output_stream.flush()
Visualize the state of the game to the UI. This is the typically the main override point for children. Note that how this method visualizes the game completely unrelated to how the draw_image() method works. draw() will render to whatever the specific UI for the child class is, while draw_image() specifically creates an image which will be used for animations. If the child UI is also image-based than it can leverage draw_image(), but there is no requirement to do that.
Inherited Members
164class StdioUI(TextUI): 165 """ 166 A convenience class for a TextUI using stdout. 167 """ 168 169 def __init__(self, **kwargs: typing.Any) -> None: 170 super().__init__(sys.stdout, **kwargs)
A convenience class for a TextUI using stdout.