diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d8afd0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +.idea/vcs.xml +.idea/workspace.xml diff --git a/C4Game.py b/C4Game.py index ed7b949..7569221 100644 --- a/C4Game.py +++ b/C4Game.py @@ -2,40 +2,49 @@ from termcolor import colored class C4Game: + + # List of shapes (characters) and colors that players will be assigned colorMap = [ - colored('O', 'blue'), # Gray - colored('O', 'red'), # Red + colored('O', 'blue'), + colored('O', 'red'), ] - def __init__(self, players=1, boardWidth=7, boardHeight=8, winLength=4, turn=0): - self.players = players + def __init__(self, players=1, boardWidth=7, boardHeight=8, winLength=4, turn=0, *names): + self.players = players # Number of players if players > len(self.colorMap): raise Exception("Too many players, not enough colors") - self.playerArray = [Player(i) for i in range(players)] + if len(names) != players: + raise Exception(f"{len(names)} player names provided, but {players} players") + self.playerArray = [Player(i, names[i]) for i in range(players)] self.boardWidth = boardWidth self.boardHeight = boardHeight self.winLength = winLength + if winLength > max(boardWidth, boardHeight): + print(f"winLength {winLength} is too large for {boardWidth}x{boardHeight} board. No one will be able to win.", sys.stderr) self.board = self.createBoard() - self.turn = turn + self.turn = turn # Whose turn it is, as an integer; takes values 0,...,players - 1 - # - # def __repr__(self): - # return f"C4Node({self.player}, {self.state}, {repr(nodearray)})" - # - # def __str__(self): - # return f"Player: {self.player}, State: {self.state}" + + def __repr__(self): + return f"C4Game(players = {self.players}, baordWidth = {self.boardWidth}, boardHeight = {self.boardHeight}, winLength = {self.winLength}, turn = {self.turn}, names = {self.names}" + + def __str__(self): + return repr(self) def createBoard(self): + # List of lists of blank spots ('-') that will be filled in with players' pieces return [['-' for i in range(self.boardWidth)] for j in range(self.boardHeight)] def printBoard(self): + # Prints the board in its current state for row in range(self.boardHeight): for col in range(self.boardWidth): print(self.board[self.boardHeight - 1 - row][col], end=" ") - print(" ") + print("") - def play(self, column: int): # Columns are 1 through boardWidth - if(column > self.boardWidth or column == 0): + def play(self, column: int): + """column is the column that was chosen for dropping a piece into. Can take values 1,...,boardWidth. Board will be modified and the turn will be changed.""" + if (column > self.boardWidth or column == 0): print(f"Column must be between 1 and {self.boardWidth}", sys.stderr) return if self.board[self.boardHeight-1][column-1] != '-': @@ -47,6 +56,158 @@ def play(self, column: int): # Columns are 1 through boardWidth break self.turn = (self.turn + 1) % self.players + def allPossibleWins(self): + """A set of all possible ways of winning, given the board dimensions. Each way of winning is itself represented as a frozenset (of size winLength) of tuples, where each tuple is the coordinates of one of the board spots included in the winning line. + + For example, if we have a board of size 3x3, and winLength = 2, then the possible ways of winning are: + + _______________________ + + horizontal lines: + + * * * + * * * + O O * + + * * * + * * * + * O O + + * * * + O O * + * * * + + * * * + * O O + * * * + + O O * + * * * + * * * + + * O O + * * * + * * * + + which will be represented by the frozensets + + {(0,0), (1,0)} + {(1,0), (2,0)} + {(0,1), (1,1)} + {(1,1), (2,1)} + {(0,2), (1,2)} + {(1,2), (2,2)} + + _______________________ + + vertical lines: + + * * * + O * * + O * * + + O * * + O * * + * * * + + * * * + * O * + * O * + + * O * + * O * + * * * + + * * * + * * O + * * O + + * * O + * * O + * * * + + which will be represented by the frozensets + + {(0,0), (0,1)} + {(0,1), (0,2)} + {(1,0), (1,1)} + {(1,1), (1,2)} + {(2,0), (2,1)} + {(2,1), (2,2)} + + _______________________ + + diagonal up lines: + + * * * + * O * + O * * + + * * * + * * O + * O * + + * O * + O * * + * * * + + * * O + * O * + * * * + + which will be represented by the frozensets + + {(0,0), (1,1)} + {(1,0), (2,1)} + {(0,1), (1,2)} + {(1,1), (2,2)} + + _______________________ + + diagonal down lines: + + * * * + O * * + * O * + + * * * + * O * + * * O + + O * * + * O * + * * * + + * O * + * * O + * * * + + which will be represented by the frozensets + + {(0,1), (1,0)} + {(1,1), (2,0)} + {(0,2), (1,1)} + {(1,2), (2,1)} + + _______________________ + + """ + horizontalWins = {} + # for each horizontalWinTuple: (must figure these out based on board size) + # horizontalWins.add(horizontalWinTuple) + verticalWins = {} + # for each verticalWinTuple: (must figure these out based on board size) + # verticalWins.add(verticalWinTuple) + diagonalUpWins = {} + # for each diagonalUpWinTuple: (must figure these out based on board size) + # diagonalUpWins.add(diagonalUpWinTuple) + diagonalDownWins = {} + # for each diagonalDownWinTuple: (must figure these out based on board size) + # diagonalDownWins.add(diagonalDownWinTuple) + allWins = {} + allWins = allWins.union(horizontalWins, verticalWins, diagonalUpWins, diagonalDownWins) + return allWins + class Player: diff --git a/C4Node.py b/C4Node.py index 00e5d32..dab47c0 100644 --- a/C4Node.py +++ b/C4Node.py @@ -1,16 +1,18 @@ -numberofslots = 7 +numberOfSlots = 7 class C4Node: + """A C4Node is a node in the tree of all possible game states. It keeps track of the state (one of the player has won, or no one has won yet), and which player's turn yielded that node. It also keeps track of all the nodes below it. + """ - def __init__(self, player: str, n = numberofslots, state="", nodearray = []): + def __init__(self, player: str, n = numberOfSlots, state="", nodeArray = []): self.player = player self.state = state - self.nodearray = nodearray - if self.nodearray == []: - self.nodearray = [None for i in range(n)] + self.nodeArray = nodeArray + if self.nodeArray == []: + self.nodeArray = [None for i in range(n)] def __repr__(self): - return f"C4Node({self.player}, {self.state}, {repr(nodearray)})" + return f"C4Node({self.player}, {self.state}, {repr(nodeArray)})" def __str__(self): return f"Player: {self.player}, State: {self.state}" @@ -22,9 +24,9 @@ def __str__(self): print('test') root = C4Node('red') root.state = 'red wins' - root.nodearray[2] = C4Node('black') + root.nodeArray[2] = C4Node('black') - print(root.nodearray[2]) + print(root.nodeArray[2]) diff --git a/test_C4Game.py b/test_C4Game.py new file mode 100644 index 0000000..067e002 --- /dev/null +++ b/test_C4Game.py @@ -0,0 +1,106 @@ +"""Unit tests for C4Game.py""" + +import unittest +from C4Game import * + +class TestC4Game(unittest.TestCase): + + def test_init(self): + """Tests for C4Game.__init__ (therefore includes some testing of C4Game.createBoard and Player class""" + game = C4Game(2, 3, 2, 1, 0, "Maya", "Joey") + # Tests basic attributes + self.assertEqual(game.players, 2) + self.assertEqual(game.boardWidth, 3) + self.assertEqual(game.boardHeight, 2) + self.assertEqual(game.winLength, 1) + self.assertEqual(game.turn, 0) + self.assertEqual(len(game.playerArray), game.players) + # Tests that playerArray comes out as expected + player0Exp = Player(0, "Maya") + player1Exp = Player(1, "Joey") + self.assertEqual(game.playerArray[0].name, player0Exp.name) + self.assertEqual(game.playerArray[0].color, player0Exp.color) + self.assertEqual(game.playerArray[1].name, player1Exp.name) + self.assertEqual(game.playerArray[1].color, player1Exp.color) + # Tests that board comes out as expected + boardExp = [['-','-','-'],['-','-','-']] + self.assertEqual(game.board, boardExp) + + def test_play_turns(self): + """Tests that C4Game.play advances turns properly""" + game = C4Game(2, 3, 4, 2, 0, "Joey", "Maya") + self.assertEqual(game.turn, 0) + game.play(1) + self.assertEqual(game.turn, 1) + game.play(2) + self.assertEqual(game.turn, 0) + game.play(1) + self.assertEqual(game.turn, 1) + game.play(3) + self.assertEqual(game.turn, 0) + + def test_play(self): + """Tests that C4Game.play changes the board properly""" + game = C4Game(2, 3, 4, 2, 0, "Maya", "Joey") + b = colored('O', 'blue') + r = colored('O', 'red') + turn0Exp = [ + ['-','-','-'], + ['-','-','-'], + ['-','-','-'], + ['-','-','-'] + ] + self.assertEqual(game.board, turn0Exp) + game.play(1) + turn1Exp = [ + [b, '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'] + ] + self.assertEqual(game.board, turn1Exp) + game.play(1) + turn2Exp = [ + [ b,'-','-'], + [ r,'-','-'], + ['-','-','-'], + ['-','-','-'] + ] + self.assertEqual(game.board, turn2Exp) + game.play(2) + turn3Exp = [ + [b, b, '-'], + [r, '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'] + ] + self.assertEqual(game.board, turn3Exp) + game.play(3) + turn4Exp = [ + [ b, b, r], + [ r,'-','-'], + ['-','-','-'], + ['-','-','-'] + ] + self.assertEqual(game.board, turn4Exp) + game.play(2) + turn5Exp = [ + [ b, b, r], + [ r, b,'-'], + ['-','-','-'], + ['-','-','-'] + ] + self.assertEqual(game.board, turn5Exp) + game.play(1) + turn6Exp = [ + [b, b, r], + [r, b, '-'], + [r, '-', '-'], + ['-', '-', '-'] + ] + self.assertEqual(game.board, turn6Exp) + + +if __name__ == "__main__": + unittest.main() +