diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index b1163f3..603ef17 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 @@ -22,7 +22,7 @@ jobs: run: pip3 install -r requirements.txt - name: Run mypy - run: mypy src/* tests/* + run: mypy src/*.py tests/*.py - name: Run tests run: pytest -v diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 0000000..cd00630 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +disallow_untyped_calls = True +disallow_untyped_defs = True diff --git a/requirements.txt b/requirements.txt index c3b5761..e98a6c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -click==8.1.3 -colorama==0.4.6 -mypy==1.1.1 -mypy-extensions==1.0.0 -pygame==2.1.3 -pytest==7.2.1 -types-colorama==0.4.15.7 \ No newline at end of file +click>=8.1.7 +colorama>=0.4.6 +mypy>=1.7.1 +mypy-extensions>=1.0.0 +pygame>=2.5.2 +pytest>=7.4.3 +types-colorama>=0.4.15.12 diff --git a/src/base.py b/src/base.py new file mode 100644 index 0000000..e111642 --- /dev/null +++ b/src/base.py @@ -0,0 +1,187 @@ +""" +Base class for Connect-M +""" +from abc import ABC, abstractmethod +from enum import Enum +from typing import Optional + +PieceColor = Enum("PieceColor", ["RED", "YELLOW"]) +""" +Enum type for representing piece colors. +""" + + +class ConnectMBase(ABC): + """ + Class for representing a Connect-M board + """ + + # + # PRIVATE ATTRIBUTES + # + + # Number of rows and columns + _nrows: int + _ncols: int + + # Number of contiguous pieces needed to win + _m: int + + + # + # PUBLIC METHODS + # + + def __init__(self, nrows: int, ncols: int, m: int): + """ + Constructor + + Args: + nrows (int): Number of rows + ncols (int): Number of columns + m (int): Number of contiguous pieces needed to win + """ + if nrows < m: + raise ValueError(f"Number of rows ({nrows}) must be at least M ({m}") + + if ncols < m: + raise ValueError(f"Number of columns ({ncols}) must be at least M ({m}") + + self._nrows = nrows + self._ncols = ncols + self._m = m + + @abstractmethod + def __str__(self) -> str: + """ Returns a string representation of the board """ + raise NotImplementedError + + @abstractmethod + def can_drop(self, col: int) -> bool: + """ Checks if a piece can be dropped into a column + + Args: + col (int): Column index + + Raises: + ValueError: if col is not a valid column index + + Returns: + bool: True if a piece can be dropped in the + specified column. False otherwise. + + """ + raise NotImplementedError + + @abstractmethod + def drop_wins(self, col: int, color: PieceColor) -> bool: + """ Checks whether dropping a piece in this + column will result in a win. + + Args: + col: Column index + color: Color of the piece to drop + + Raises: + ValueError: if col is not a valid column index, + or if the column is already full. + + Returns: + bool: True if dropping a piece of the given + color would result in a win; False otherwise. + + """ + raise NotImplementedError + + @abstractmethod + def drop(self, col: int, color: PieceColor) -> None: + """ Drops a piece in a column + + Args: + col: Column index + color: Color of the piece to drop + + Raises: + ValueError: if col is not a valid column index, + or if the column is already full. + + Returns: None + + """ + + # After dropping the piece, we would use _winner_at + # to check whether adding that piece results in a + # winning row/column/diagonal. If there is a winner + # we update the _winner attribute. + raise NotImplementedError + + @abstractmethod + def reset(self) -> None: + """ Resets the board (removes all pieces) + + Args: None + + Returns: None + + """ + raise NotImplementedError + + @property + @abstractmethod + def done(self) -> bool: + """ Checks whether the game is done + + A game can be done either because there is a winner, + or because no more pieces can be dropped + + Args: None + + Returns: + bool: True if the game is done. False otherwise. + + """ + raise NotImplementedError + + @property + @abstractmethod + def winner(self) -> Optional[PieceColor]: + """ Returns the winner (if any) in the board + + Returns: + Optional[PieceColor]: If there is a winner, + return its color. Otherwise, return None. + + """ + # Only needs to return the value of _winner + # (does not check for a winner in every cell + # of the board) + raise NotImplementedError + + @property + def num_cols(self) -> int: + """ Returns the number of columns in the board""" + return self._ncols + + @property + def num_rows(self) -> int: + """ Returns the number of rows in the board""" + return self._nrows + + @property + def m(self) -> int: + """ Returns the number of contiguous pieces + needed to win""" + return self._m + + @property + @abstractmethod + def grid(self) -> list[list[Optional[PieceColor]]]: + """ Returns the board as a list of list of PieceColors + + Returns: + list[list[PieceColor]]: A list of lists with the same + dimensions as the board. In each row, the values + in the list will be None (no piece), PieceColor.RED + (red piece), or PieceColor.YELLOW (yellow piece) + """ + raise NotImplementedError diff --git a/src/bot.py b/src/bot.py index 3c96bdd..8bda396 100644 --- a/src/bot.py +++ b/src/bot.py @@ -4,11 +4,11 @@ (and command for running simulations with bots) """ import random -from typing import Union import click -from connectm import ConnectMBase, ConnectM, PieceColor +from base import ConnectMBase, PieceColor +from connectm import ConnectM # @@ -29,7 +29,7 @@ def __init__(self, connectm: ConnectMBase, color: PieceColor, """ Constructor Args: - board: Board the bot will play on + connectm: The Connect-M board color: Bot's color opponent_color: Opponent's color """ @@ -70,7 +70,7 @@ def __init__(self, connectm: ConnectMBase, color: PieceColor, """ Constructor Args: - board: Board the bot will play on + connectm: The Connect-M board the bot will play in color: Bot's color opponent_color: Opponent's color """ @@ -140,7 +140,7 @@ class BotPlayer: """ name: str - bot: Union[RandomBot, SmartBot] + bot: RandomBot | SmartBot color: PieceColor wins: int @@ -150,7 +150,7 @@ def __init__(self, name: str, connectm: ConnectMBase, color: PieceColor, Args: name: Name of the bot - board: Board to play on + connectm: The Connect-M board the bot will play in color: Bot's color opponent_color: Opponent's color """ @@ -164,11 +164,11 @@ def __init__(self, name: str, connectm: ConnectMBase, color: PieceColor, self.wins = 0 -def simulate(connectm: ConnectMBase, n: int, bots) -> None: +def simulate(connectm: ConnectMBase, n: int, bots: dict[PieceColor, BotPlayer]) -> None: """ Simulates multiple games between two bots Args: - board: The board to play on + connectm: The Connect-M board the bot will play in n: The number of matches to play bots: Dictionary mapping piece colors to BotPlayer objects (the bots what will @@ -210,7 +210,7 @@ def simulate(connectm: ConnectMBase, n: int, bots) -> None: @click.option('--player2', type=click.Choice(['random', 'smart'], case_sensitive=False), default="random") -def cmd(num_games, player1, player2): +def cmd(num_games: int, player1: str, player2: str) -> None: board = ConnectM(nrows=6, ncols=7, m=4) bot1 = BotPlayer(player1, board, PieceColor.YELLOW, PieceColor.RED) diff --git a/src/connectm.py b/src/connectm.py index 41de06a..d094868 100644 --- a/src/connectm.py +++ b/src/connectm.py @@ -1,191 +1,11 @@ """ -Classes for Connect-M (a generalized form of Connect Four) +Implementation of Connect-M (a generalized form of Connect Four) """ import copy -from abc import ABC, abstractmethod -from enum import Enum -from typing import Optional, List, Union +from typing import Optional -PieceColor = Enum("PieceColor", ["RED", "YELLOW"]) -""" -Enum type for representing piece colors. -""" - -class ConnectMBase(ABC): - """ - Class for representing a Connect-M board - """ - - # - # PRIVATE ATTRIBUTES - # - - # Number of rows and columns - _nrows: int - _ncols: int - - # Number of contiguous pieces needed to win - _m: int - - - # - # PUBLIC METHODS - # - - def __init__(self, nrows: int, ncols: int, m: int): - """ - Constructor - - Args: - nrows (int): Number of rows - ncols (int): Number of columns - m (int): Number of contiguous pieces needed to win - """ - if nrows < m: - raise ValueError(f"Number of rows ({nrows}) must be at least M ({m}") - - if ncols < m: - raise ValueError(f"Number of columns ({ncols}) must be at least M ({m}") - - self._nrows = nrows - self._ncols = ncols - self._m = m - - @abstractmethod - def __str__(self) -> str: - """ Returns a string representation of the board """ - raise NotImplementedError - - @abstractmethod - def can_drop(self, col: int) -> bool: - """ Checks if a piece can be dropped into a column - - Args: - col (int): Column index - - Raises: - ValueError: if col is not a valid column index - - Returns: - bool: True if a piece can be dropped in the - specified column. False otherwise. - - """ - raise NotImplementedError - - @abstractmethod - def drop_wins(self, col: int, color: PieceColor) -> bool: - """ Checks whether dropping a piece in this - column will result in a win. - - Args: - col: Column index - color: Color of the piece to drop - - Raises: - ValueError: if col is not a valid column index, - or if the column is already full. - - Returns: - bool: True if dropping a piece of the given - color would result in a win; False otherwise. - - """ - raise NotImplementedError - - @abstractmethod - def drop(self, col: int, color: PieceColor) -> None: - """ Drops a piece in a column - - Args: - col: Column index - color: Color of the piece to drop - - Raises: - ValueError: if col is not a valid column index, - or if the column is already full. - - Returns: None - - """ - - # After dropping the piece, we would use _winner_at - # to check whether adding that piece results in a - # winning row/column/diagonal. If there is a winner - # we update the _winner attribute. - raise NotImplementedError - - @abstractmethod - def reset(self) -> None: - """ Resets the board (removes all pieces) - - Args: None - - Returns: None - - """ - raise NotImplementedError - - @property - @abstractmethod - def done(self) -> bool: - """ Checks whether the game is done - - A game can be done either because there is a winner, - or because no more pieces can be dropped - - Args: None - - Returns: - bool: True if the game is done. False otherwise. - - """ - raise NotImplementedError - - @property - @abstractmethod - def winner(self) -> Optional[PieceColor]: - """ Returns the winner (if any) in the board - - Returns: - Optional[PieceColor]: If there is a winner, - return its color. Otherwise, return None. - - """ - # Only needs to return the value of _winner - # (does not check for a winner in every cell - # of the board) - raise NotImplementedError - - @property - def num_cols(self) -> int: - """ Returns the number of columns in the board""" - return self._ncols - - @property - def num_rows(self) -> int: - """ Returns the number of rows in the board""" - return self._nrows - - @property - def m(self) -> int: - """ Returns the number of contiguous pieces - needed to win""" - return self._m - - @property - @abstractmethod - def grid(self) -> List[List[Optional[PieceColor]]]: - """ Returns the board as a list of list of PieceColors - - Returns: - list[list[PieceColor]]: A list of lists with the same - dimensions as the board. In each row, the values - in the list will be None (no piece), PieceColor.RED - (red piece), or PieceColor.YELLOW (yellow piece) - """ - raise NotImplementedError +from base import ConnectMBase, PieceColor class ConnectM(ConnectMBase): @@ -198,7 +18,7 @@ class ConnectM(ConnectMBase): # # The board itself - _board: List[List[Optional[PieceColor]]] + _board: list[list[Optional[PieceColor]]] # The winner (if any) on the board _winner: Optional[PieceColor] @@ -361,7 +181,7 @@ def winner(self) -> Optional[PieceColor]: return self._winner @property - def grid(self) -> List[List[Optional[PieceColor]]]: + def grid(self) -> list[list[Optional[PieceColor]]]: """ Returns the board as a list of list of PieceColors Not suitable for JSON serialization, but can be useful diff --git a/src/mocks.py b/src/fakes.py similarity index 85% rename from src/mocks.py rename to src/fakes.py index 5512376..378b785 100644 --- a/src/mocks.py +++ b/src/fakes.py @@ -1,9 +1,9 @@ """ -Stub and mock implementations of the ConnectMBase class +Fake implementations of the ConnectMBase class """ -from connectm import ConnectMBase, PieceColor -from typing import Optional, List +from base import ConnectMBase, PieceColor +from typing import Optional from copy import deepcopy @@ -12,7 +12,7 @@ class ConnectMStub(ConnectMBase): Stub implementation of the ConnectMBase class """ - _board: List[List[Optional[PieceColor]]] + _board: list[list[Optional[PieceColor]]] def __init__(self, nrows: int, ncols: int, m: int): super().__init__(nrows, ncols, m) @@ -42,13 +42,13 @@ def winner(self) -> Optional[PieceColor]: return None @property - def grid(self) -> List[List[Optional[PieceColor]]]: + def grid(self) -> list[list[Optional[PieceColor]]]: return deepcopy(self._board) -class ConnectMMock(ConnectMBase): +class ConnectMFake(ConnectMBase): """ - Mock implementation of the ConnectMBase class + Fake implementation of the ConnectMBase class Expected behaviours: - Stores the full board internally, but we only ever @@ -59,7 +59,7 @@ class ConnectMMock(ConnectMBase): otherwise, Yellow wins. """ - _board: List[List[Optional[PieceColor]]] + _board: list[list[Optional[PieceColor]]] _nummoves: int def __init__(self, nrows: int, ncols: int, m: int): @@ -115,13 +115,13 @@ def winner(self) -> Optional[PieceColor]: return None @property - def grid(self) -> List[List[Optional[PieceColor]]]: + def grid(self) -> list[list[Optional[PieceColor]]]: return deepcopy(self._board) -class ConnectMBotMock(ConnectMBase): +class ConnectMBotFake(ConnectMBase): """ - Mock implementation of the ConnectMBase class, + Fake implementation of the ConnectMBase class, specifically for testing the bots. Since the bots only care about whether a drop is @@ -130,7 +130,7 @@ class ConnectMBotMock(ConnectMBase): and get_num_cols (and stub out the remaining methods). - The mock will use two lists: one to specify + This fake will use two lists: one to specify whether a piece can be dropped in a given column, and another to specify whether a drop in a column will result in a win for a player. can_drop @@ -139,8 +139,8 @@ class ConnectMBotMock(ConnectMBase): """ - _can_drop: List[bool] - _drop_wins: List[Optional[PieceColor]] + _can_drop: list[bool] + _drop_wins: list[Optional[PieceColor]] def __init__(self, nrows: int, ncols: int, m: int): super().__init__(nrows, ncols, m) @@ -177,5 +177,5 @@ def winner(self) -> Optional[PieceColor]: return None @property - def grid(self) -> List[List[Optional[PieceColor]]]: + def grid(self) -> list[list[Optional[PieceColor]]]: return [] diff --git a/src/gui.py b/src/gui.py index 38fc022..cfde2f8 100644 --- a/src/gui.py +++ b/src/gui.py @@ -4,19 +4,20 @@ import os import sys -from typing import Union, Dict +from typing import Union os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" import pygame import click from connectm import ConnectMBase, ConnectM, PieceColor -from mocks import ConnectMStub, ConnectMMock +from fakes import ConnectMStub, ConnectMFake from bot import RandomBot, SmartBot DEFAULT_SIDE = 75 SMALL_SIDE = 40 + class GUIPlayer: """ Simple class to store information about a GUI player @@ -100,7 +101,7 @@ def draw_board(surface: pygame.surface.Surface, connectm: ConnectMBase) -> None: center=center, radius=radius) -def play_connect_4(connectm: ConnectMBase, players: Dict[PieceColor, GUIPlayer], +def play_connect_4(connectm: ConnectMBase, players: dict[PieceColor, GUIPlayer], bot_delay: float) -> None: """ Plays a game of Connect Four on a Pygame window @@ -201,7 +202,7 @@ def play_connect_4(connectm: ConnectMBase, players: Dict[PieceColor, GUIPlayer], @click.option('--cols', type=click.INT, default=7) @click.option('--m', type=click.INT, default=4) @click.option('--mode', - type=click.Choice(['real', 'stub', 'mock'], case_sensitive=False), + type=click.Choice(['real', 'stub', 'fake'], case_sensitive=False), default="real") @click.option('--player1', type=click.Choice(['human', 'random-bot', 'smart-bot'], case_sensitive=False), @@ -210,20 +211,22 @@ def play_connect_4(connectm: ConnectMBase, players: Dict[PieceColor, GUIPlayer], type=click.Choice(['human', 'random-bot', 'smart-bot'], case_sensitive=False), default="human") @click.option('--bot-delay', type=click.FLOAT, default=0.5) -def cmd(rows, cols, m, mode, player1, player2, bot_delay): +def cmd(rows: int, cols: int, m: int, mode: str, player1: str, player2: str, bot_delay: float) -> None: + connectm: ConnectMBase if mode == "real": connectm = ConnectM(rows, cols, m) elif mode == "stub": connectm = ConnectMStub(rows, cols, m) - elif mode == "mock": - connectm = ConnectMMock(rows, cols, m) + elif mode == "fake": + connectm = ConnectMFake(rows, cols, m) - player1 = GUIPlayer(1, player1, connectm, PieceColor.YELLOW, PieceColor.RED) - player2 = GUIPlayer(2, player2, connectm, PieceColor.RED, PieceColor.YELLOW) + gui_player1 = GUIPlayer(1, player1, connectm, PieceColor.YELLOW, PieceColor.RED) + gui_player2 = GUIPlayer(2, player2, connectm, PieceColor.RED, PieceColor.YELLOW) - players = {PieceColor.YELLOW: player1, PieceColor.RED: player2} + players = {PieceColor.YELLOW: gui_player1, PieceColor.RED: gui_player2} play_connect_4(connectm, players, bot_delay) + if __name__ == "__main__": cmd() diff --git a/src/mini-tui.py b/src/mini-tui.py index 445adbf..15903db 100644 --- a/src/mini-tui.py +++ b/src/mini-tui.py @@ -1,7 +1,7 @@ import sys from connectm import ConnectMBase, ConnectM, PieceColor -from mocks import ConnectMStub, ConnectMMock +from fakes import ConnectMStub, ConnectMFake def play_connect_4(connectm: ConnectMBase) -> None: # The starting player is yellow @@ -45,8 +45,8 @@ def play_connect_4(connectm: ConnectMBase) -> None: if len(sys.argv) == 2 and sys.argv[1] == "stub": game = ConnectMStub(nrows=6, ncols=7, m=4) - elif len(sys.argv) == 2 and sys.argv[1] == "mock": - game = ConnectMMock(nrows=6, ncols=7, m=4) + elif len(sys.argv) == 2 and sys.argv[1] == "fake": + game = ConnectMFake(nrows=6, ncols=7, m=4) else: game = ConnectM(nrows=6, ncols=7, m=4) diff --git a/src/tui.py b/src/tui.py index ace1dc6..db94883 100644 --- a/src/tui.py +++ b/src/tui.py @@ -3,13 +3,13 @@ """ import sys import time -from typing import Union, Dict, Optional, List +from typing import Optional import click from colorama import Fore, Style from connectm import ConnectMBase, ConnectM, PieceColor -from mocks import ConnectMMock, ConnectMStub +from fakes import ConnectMStub, ConnectMFake from bot import RandomBot, SmartBot @@ -22,19 +22,19 @@ class TUIPlayer: """ name: str - bot: Union[None, RandomBot, SmartBot] - connectm: ConnectMMock + bot: None | RandomBot | SmartBot + connectm: ConnectMBase color: PieceColor bot_delay: float - def __init__(self, n: int, player_type: str, connectm: ConnectMMock, + def __init__(self, n: int, player_type: str, connectm: ConnectMBase, color: PieceColor, opponent_color: PieceColor, bot_delay: float): """ Constructor Args: n: The player's number (1 or 2) player_type: "human", "random-bot", or "smart-bot" - board: The Connect-M board + connectm: The Connect-M board color: The player's color opponent_color: The opponent's color bot_delay: When playing as a bot, an artificial delay @@ -83,7 +83,7 @@ def get_move(self) -> int: continue -def print_board(grid: List[List[Optional[PieceColor]]]) -> None: +def print_board(grid: list[list[Optional[PieceColor]]]) -> None: """ Prints the board to the screen Args: @@ -117,7 +117,7 @@ def print_board(grid: List[List[Optional[PieceColor]]]) -> None: print(Fore.BLUE + "└" + ("─┴" * (ncols-1)) + "─┘" + Style.RESET_ALL) -def play_connect_4(connectm: ConnectMBase, players: Dict[PieceColor, TUIPlayer]) -> None: +def play_connect_4(connectm: ConnectMBase, players: dict[PieceColor, TUIPlayer]) -> None: """ Plays a game of Connect Four on the terminal Args: @@ -168,7 +168,7 @@ def play_connect_4(connectm: ConnectMBase, players: Dict[PieceColor, TUIPlayer]) @click.option('--cols', type=click.INT, default=7) @click.option('--m', type=click.INT, default=4) @click.option('--mode', - type=click.Choice(['real', 'stub', 'mock'], case_sensitive=False), + type=click.Choice(['real', 'stub', 'fake'], case_sensitive=False), default="real") @click.option('--player1', type=click.Choice(['human', 'random-bot', 'smart-bot'], case_sensitive=False), @@ -177,20 +177,21 @@ def play_connect_4(connectm: ConnectMBase, players: Dict[PieceColor, TUIPlayer]) type=click.Choice(['human', 'random-bot', 'smart-bot'], case_sensitive=False), default="human") @click.option('--bot-delay', type=click.FLOAT, default=0.5) -def cmd(rows, cols, m, mode, player1, player2, bot_delay): +def cmd(rows: int, cols: int, m: int, mode: str, player1: str, player2: str, bot_delay: float) -> None: + connectm: ConnectMBase if mode == "real": - board = ConnectM(rows, cols, m) + connectm = ConnectM(rows, cols, m) elif mode == "stub": - board = ConnectMStub(rows, cols, m) - elif mode == "mock": - board = ConnectMMock(rows, cols, m) + connectm = ConnectMStub(rows, cols, m) + elif mode == "fake": + connectm = ConnectMFake(rows, cols, m) - player1 = TUIPlayer(1, player1, board, PieceColor.YELLOW, PieceColor.RED, bot_delay) - player2 = TUIPlayer(2, player2, board, PieceColor.RED, PieceColor.YELLOW, bot_delay) + tui_player1 = TUIPlayer(1, player1, connectm, PieceColor.YELLOW, PieceColor.RED, bot_delay) + tui_player2 = TUIPlayer(2, player2, connectm, PieceColor.RED, PieceColor.YELLOW, bot_delay) - players = {PieceColor.YELLOW: player1, PieceColor.RED: player2} + players = {PieceColor.YELLOW: tui_player1, PieceColor.RED: tui_player2} - play_connect_4(board, players) + play_connect_4(connectm, players) if __name__ == "__main__": diff --git a/tests/test_bot.py b/tests/test_bot.py index 8d37fb5..fa61731 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -2,16 +2,16 @@ from bot import RandomBot, SmartBot from connectm import PieceColor -from mocks import ConnectMBotMock +from fakes import ConnectMBotFake -def test_random_1(): +def test_random_1() -> None: """ Checks that the random bot returns a valid column number (when pieces can be dropped in any column) """ - board = ConnectMBotMock(6, 7, 4) + board = ConnectMBotFake(6, 7, 4) bot = RandomBot(board, PieceColor.YELLOW, PieceColor.RED) col = bot.suggest_move() @@ -19,12 +19,12 @@ def test_random_1(): assert 0 <= col < 7 -def test_random_2(): +def test_random_2() -> None: """ Checks that, if pieces can't be dropped in certain columns, we don't get back any of those columns. """ - board = ConnectMBotMock(6, 7, 4) + board = ConnectMBotFake(6, 7, 4) bot = RandomBot(board, PieceColor.YELLOW, PieceColor.RED) board._can_drop = [True, True, False, True, False, True, True] @@ -37,12 +37,12 @@ def test_random_2(): assert col in (0, 1, 3, 5, 6) -def test_random_3(): +def test_random_3() -> None: """ Checks that, if pieces can only be dropped in a single column, we only get back that column """ - board = ConnectMBotMock(6, 7, 4) + board = ConnectMBotFake(6, 7, 4) bot = RandomBot(board, PieceColor.YELLOW, PieceColor.RED) board._can_drop = [False, False, False, True, False, False, False] @@ -55,12 +55,12 @@ def test_random_3(): assert col == 3 -def test_smart_1(): +def test_smart_1() -> None: """ Checks that, if there is a winning move for the bot's color, it will take it. """ - board = ConnectMBotMock(6, 7, 4) + board = ConnectMBotFake(6, 7, 4) bot = SmartBot(board, PieceColor.YELLOW, PieceColor.RED) board._drop_wins = [None, None, PieceColor.YELLOW, @@ -71,15 +71,15 @@ def test_smart_1(): assert col == 2 -def test_smart_2(): +def test_smart_2() -> None: """ Checks that, if there is no winning move, but there is a blocking move, it will take it. """ - board = ConnectMBotMock(6, 7, 4) + board = ConnectMBotFake(6, 7, 4) bot = SmartBot(board, PieceColor.YELLOW, PieceColor.RED) - bot._drop_wins = [None, None, None, None, PieceColor.RED, + board._drop_wins = [None, None, None, None, PieceColor.RED, None, PieceColor.RED] col = bot.suggest_move() @@ -87,13 +87,13 @@ def test_smart_2(): assert col in (4, 6) -def test_smart_3(): +def test_smart_3() -> None: """ Checks that, if there is neither a winning move nor a blocking move, it returns columns you can drop pieces into. """ - board = ConnectMBotMock(6, 7, 4) + board = ConnectMBotFake(6, 7, 4) bot = SmartBot(board, PieceColor.YELLOW, PieceColor.RED) board._can_drop = [True, True, False, True, False, True, True] diff --git a/tests/test_connectm.py b/tests/test_connectm.py index d8bfde7..4db5ac4 100644 --- a/tests/test_connectm.py +++ b/tests/test_connectm.py @@ -1,8 +1,7 @@ -from typing import Dict, Tuple from connectm import ConnectM, PieceColor def validate_grid(connectm: ConnectM, - pieces: Dict[Tuple[int, int], PieceColor]): + pieces: dict[tuple[int, int], PieceColor]) -> None: """ Helper function that validates whether a ConnectM object has pieces in the expected positions. @@ -58,7 +57,7 @@ def sample_board() -> ConnectM: return connectm -def test_create_1(): +def test_create_1() -> None: """ Tests creating a 6x7 board """ @@ -67,7 +66,7 @@ def test_create_1(): assert not connectm.done assert connectm.winner is None -def test_create_2(): +def test_create_2() -> None: """ Tests creating a 20x20 board """ @@ -76,7 +75,7 @@ def test_create_2(): assert not connectm.done assert connectm.winner is None -def test_can_drop_1(): +def test_can_drop_1() -> None: """ Tests that we can drop a piece in every column of an empty board @@ -86,7 +85,7 @@ def test_can_drop_1(): for i in range(7): assert connectm.can_drop(i) -def test_can_drop_2(): +def test_can_drop_2() -> None: """ Tests that we can drop a piece in every column of the sample board (except column 4, which is full) @@ -98,7 +97,7 @@ def test_can_drop_2(): assert not connectm.can_drop(4) -def test_drop_wins_1(): +def test_drop_wins_1() -> None: """ Tests that dropping a piece in any of the columns in an empty board does not result in a win. @@ -109,7 +108,7 @@ def test_drop_wins_1(): assert not connectm.drop_wins(i, PieceColor.RED) assert not connectm.drop_wins(i, PieceColor.YELLOW) -def test_drop_wins_2(): +def test_drop_wins_2() -> None: """ Tests that dropping a piece in any of the columns in an empty board does not result in a win, except dropping @@ -124,7 +123,7 @@ def test_drop_wins_2(): assert connectm.drop_wins(2, PieceColor.RED) assert not connectm.drop_wins(2, PieceColor.YELLOW) -def test_drop_1(): +def test_drop_1() -> None: """ Tests that we can correctly drop a piece """ @@ -134,7 +133,7 @@ def test_drop_1(): validate_grid(connectm, {(5,0): PieceColor.YELLOW}) -def test_drop_2(): +def test_drop_2() -> None: """ Tests that we can correctly drop two pieces (in two separate columns) @@ -147,7 +146,7 @@ def test_drop_2(): validate_grid(connectm, {(5,0): PieceColor.YELLOW, (5,1): PieceColor.RED}) -def test_drop_3(): +def test_drop_3() -> None: """ Tests that we can correctly drop two pieces (in the same column) @@ -160,7 +159,7 @@ def test_drop_3(): validate_grid(connectm, {(5,0): PieceColor.YELLOW, (4,0): PieceColor.RED}) -def test_reset(): +def test_reset() -> None: """ Tests that we can correctly reset the board (starting from the sample board) @@ -169,7 +168,7 @@ def test_reset(): connectm.reset() validate_grid(connectm, {}) -def test_win(): +def test_win() -> None: """ Tests that dropping a red piece in column 2 results in a win.