From 916639fa3d27e9264a3b93f3c9253d5c3c2501e7 Mon Sep 17 00:00:00 2001 From: mayaekd <59815270+mayaekd@users.noreply.github.com> Date: Sat, 20 Jun 2020 00:13:20 -0700 Subject: [PATCH 1/4] tests, small comments and modifications, and start of new strategy - Created test_C4Game.py to test some things in C4Game.py. Currently passes all tests. - Added comments - Added (player) names to parameters for creating a C4Game - Fixed some small typos - Idea for new strategy is started in C4Game.allPossibleWins. - allPossibleWins is not yet implemented but there is a large comment describing what it should do and some pseudocode - The idea comes from the observation that there really aren't that many distinct lines of 4 (or however many) that you can complete to win. The idea is to keep a set of all the possible lines of 4 (or however many) that could be made, and every time during play that one of those possibilities is lost, remove it from the set. Trying to win can be based on trying to move toward completing the lines still in the set. --- C4Game.py | 191 +++++++++++++++++++++++++++++++++++++++++++++---- C4Node.py | 18 ++--- test_C4Game.py | 92 ++++++++++++++++++++++++ 3 files changed, 278 insertions(+), 23 deletions(-) create mode 100644 test_C4Game.py 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..b07338e --- /dev/null +++ b/test_C4Game.py @@ -0,0 +1,92 @@ +"""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() + From ed88ba85910a6c3fd498174901dde789528dae48 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 28 Jun 2020 18:30:24 -0700 Subject: [PATCH 2/4] fixing whitespace format here --- .gitignore | 3 +++ test_C4Game.py | 40 ++++++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 .gitignore 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/test_C4Game.py b/test_C4Game.py index b07338e..326409e 100644 --- a/test_C4Game.py +++ b/test_C4Game.py @@ -50,28 +50,36 @@ def test_play(self): ['-','-','-']] self.assertEqual(game.board, turn0Exp) game.play(1) - turn1Exp = [[ b,'-','-'], - ['-','-','-'], - ['-','-','-'], - ['-','-','-']] + turn1Exp = [ + [b, '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'] + ] self.assertEqual(game.board, turn1Exp) game.play(1) - turn2Exp = [[ b,'-','-'], - [ r,'-','-'], - ['-','-','-'], - ['-','-','-']] + turn2Exp = [ + [ b,'-','-'], + [ r,'-','-'], + ['-','-','-'], + ['-','-','-'] + ] self.assertEqual(game.board, turn2Exp) game.play(2) - turn3Exp = [[ b, b,'-'], - [ r,'-','-'], - ['-','-','-'], - ['-','-','-']] + turn3Exp = [ + [b, b, '-'], + [r, '-', '-'], + ['-', '-', '-'], + ['-', '-', '-'] + ] self.assertEqual(game.board, turn3Exp) game.play(3) - turn4Exp = [[ b, b, r], - [ r,'-','-'], - ['-','-','-'], - ['-','-','-']] + turn4Exp = [ + [ b, b, r], + [ r,'-','-'], + ['-','-','-'], + ['-','-','-'] + ] self.assertEqual(game.board, turn4Exp) game.play(2) turn5Exp = [[ b, b, r], From 71740da5f5953d3006a4eddcc6f303afda9c0aa1 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 28 Jun 2020 18:32:50 -0700 Subject: [PATCH 3/4] missed a whitespace change --- test_C4Game.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test_C4Game.py b/test_C4Game.py index 326409e..7c2b376 100644 --- a/test_C4Game.py +++ b/test_C4Game.py @@ -44,10 +44,12 @@ def test_play(self): game = C4Game(2, 3, 4, 2, 0, "Maya", "Joey") b = colored('O', 'blue') r = colored('O', 'red') - turn0Exp = [['-','-','-'], - ['-','-','-'], - ['-','-','-'], - ['-','-','-']] + turn0Exp = [ + ['-','-','-'], + ['-','-','-'], + ['-','-','-'], + ['-','-','-'] + ] self.assertEqual(game.board, turn0Exp) game.play(1) turn1Exp = [ From 2f5b5ddb1f57e3bfda7355209966a115d3eb8cf6 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 28 Jun 2020 18:34:06 -0700 Subject: [PATCH 4/4] missed 2 more witespaces... --- test_C4Game.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/test_C4Game.py b/test_C4Game.py index 7c2b376..067e002 100644 --- a/test_C4Game.py +++ b/test_C4Game.py @@ -84,16 +84,20 @@ def test_play(self): ] self.assertEqual(game.board, turn4Exp) game.play(2) - turn5Exp = [[ b, b, r], - [ r, b,'-'], - ['-','-','-'], - ['-','-','-']] + turn5Exp = [ + [ b, b, r], + [ r, b,'-'], + ['-','-','-'], + ['-','-','-'] + ] self.assertEqual(game.board, turn5Exp) game.play(1) - turn6Exp = [[ b, b, r], - [ r, b,'-'], - [ r,'-','-'], - ['-','-','-']] + turn6Exp = [ + [b, b, r], + [r, b, '-'], + [r, '-', '-'], + ['-', '-', '-'] + ] self.assertEqual(game.board, turn6Exp)