Skip to content

Commit

Permalink
Updates for Winter 2024
Browse files Browse the repository at this point in the history
- Moved ConnectMBase to base.py
- Replaced 'mock' with 'fake' throughout the code
- Updated/fixed some type annotations
- Other minor updates
  • Loading branch information
borjasotomayor committed Feb 1, 2024
1 parent 08550b9 commit 9217a85
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 276 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[mypy]
disallow_untyped_calls = True
disallow_untyped_defs = True
14 changes: 7 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -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
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
187 changes: 187 additions & 0 deletions src/base.py
Original file line number Diff line number Diff line change
@@ -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
18 changes: 9 additions & 9 deletions src/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


#
Expand All @@ -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
"""
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -140,7 +140,7 @@ class BotPlayer:
"""

name: str
bot: Union[RandomBot, SmartBot]
bot: RandomBot | SmartBot
color: PieceColor
wins: int

Expand All @@ -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
"""
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 9217a85

Please sign in to comment.