From 56f1eb564c8bb22751a587193de50bee800f5ba3 Mon Sep 17 00:00:00 2001 From: finn Date: Thu, 29 Jun 2023 12:45:50 -0400 Subject: [PATCH] At this point I think everything except for amazons-works can be deleted, but I'm hanging onto my mess for a bit before doing the dishes later, just in case. --- .../__pycache__/board.cpython-310.pyc | Bin 0 -> 3924 bytes .../__pycache__/view.cpython-310.pyc | Bin 0 -> 1031 bytes amazons-works/board.py | 159 ++++++++++ amazons-works/game.py | 36 +++ amazons-works/player.py | 0 amazons-works/test/__init__.py | 0 .../test/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 147 bytes .../__pycache__/test_board.cpython-310.pyc | Bin 0 -> 1126 bytes amazons-works/test/test_board.py | 39 +++ amazons-works/view.py | 17 ++ amazons/__pycache__/board.cpython-310.pyc | Bin 3178 -> 3596 bytes amazons/__pycache__/view.cpython-310.pyc | Bin 0 -> 989 bytes amazons/board.py | 93 ++++-- amazons/board0.py | 159 ++++++++++ amazons/board1.py | 158 ++++++++++ amazons/game.py | 39 ++- amazons/test/test_board.py | 6 +- amazons/view.py | 18 +- amazonsplay/__pycache__/board.cpython-310.pyc | Bin 0 -> 3924 bytes amazonsplay/__pycache__/view.cpython-310.pyc | Bin 0 -> 1031 bytes amazonsplay/amazons_old.py | 283 ++++++++++++++++++ amazonsplay/board.py | 208 +++++++++++++ amazonsplay/board0.py | 159 ++++++++++ amazonsplay/board1.py | 158 ++++++++++ amazonsplay/game.py | 40 +++ amazonsplay/player.py | 0 amazonsplay/test/__init__.py | 0 .../test/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 147 bytes .../__pycache__/test_board.cpython-310.pyc | Bin 0 -> 1126 bytes amazonsplay/test/test_board.py | 39 +++ amazonsplay/view.py | 19 ++ 31 files changed, 1598 insertions(+), 32 deletions(-) create mode 100644 amazons-works/__pycache__/board.cpython-310.pyc create mode 100644 amazons-works/__pycache__/view.cpython-310.pyc create mode 100644 amazons-works/board.py create mode 100644 amazons-works/game.py create mode 100644 amazons-works/player.py create mode 100644 amazons-works/test/__init__.py create mode 100644 amazons-works/test/__pycache__/__init__.cpython-310.pyc create mode 100644 amazons-works/test/__pycache__/test_board.cpython-310.pyc create mode 100644 amazons-works/test/test_board.py create mode 100644 amazons-works/view.py create mode 100644 amazons/__pycache__/view.cpython-310.pyc create mode 100644 amazons/board0.py create mode 100644 amazons/board1.py create mode 100644 amazonsplay/__pycache__/board.cpython-310.pyc create mode 100644 amazonsplay/__pycache__/view.cpython-310.pyc create mode 100644 amazonsplay/amazons_old.py create mode 100644 amazonsplay/board.py create mode 100644 amazonsplay/board0.py create mode 100644 amazonsplay/board1.py create mode 100644 amazonsplay/game.py create mode 100644 amazonsplay/player.py create mode 100644 amazonsplay/test/__init__.py create mode 100644 amazonsplay/test/__pycache__/__init__.cpython-310.pyc create mode 100644 amazonsplay/test/__pycache__/test_board.cpython-310.pyc create mode 100644 amazonsplay/test/test_board.py create mode 100644 amazonsplay/view.py diff --git a/amazons-works/__pycache__/board.cpython-310.pyc b/amazons-works/__pycache__/board.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07bc5f36612496b4bfc6a41996628e78f6dcd90d GIT binary patch literal 3924 zcma)9-D_LP6`z^=p{ozuitIRXlHG{?tlHgp34s*jHA^?7lv1}efrYTkh^c z&0Ht3xPqk#w6BFipBC)C<+Y^`ZU2J)3j=*v`jn@>Z%ff*bc9JZPw!6_m*v7efJL*NFeyRuE z=w$nVM7cFMF+pfqhau@slPs(LXw^!3YdUp7WDm=%$T!gFK*PV|pu$qeQW|k_0e8RU zzQQ7=$_u3B+JRSe;43=Xb|<5%N&{r-Vi#RbxkPNsk`i^TxM*0pZ+N3Ch1?hPwQvDq zM8#g<85$WugmbC|)zTUBoIU3E*cWSKkqgw7v6BN1UkY_S=hD5)#(a2bxRME4C!HH-#fWSBYv_ApKlgJEoqjhLr*7`#-k~^yg@)IM>)`8TUJi?0f%ZAe$-&)yRl}L) z#+)W6unz})4Htp~jB2xH^W$dAS8Ld!NFk;ir^mfm!Nm%_`q5wzr_wk&Q>{{=UzcoR z$}=8@KT{NM3_nrp#Aykn8yLHdqOXD!8g_Yu*S$cj3h4S@bd9f8nA)V0)+o^2Ban(Zu6m)f0REMODI;{7Qs0o-Ohhi6Rm3si3r zj@>QRfo)`otRqXX&eypoJ&$1)p>m7eWe(eA7;`6M*inpuX8@kwlqdRS^x3J%y|eR^ zCVll=%vbC@EWa>s`|3Bjcj^L5C9JDrU1`cUcTT;$L`cC*4Ku)4af{TGD`P(uL<{Nl z-taxFTAg2&JHXP~1WT6*ODc}qN6|qSjx{`vRIFe92cM#lZQXMe^i|SH(w*l0(MqrX zByRQx1h$>#sQ#nuecepb=0X2Bl^P>8I}-N>*$H|#`)NCFMx7{0KWJu0adU2aGtoPv ziak6@J3GyXqv}i&y)%XG6-Z&qNg5U>m>|)i>lbpGicD0DPy>H3 z#Q}6XZOXH8Qzb#d;vQz@D9J*N^pYJx5)(vHT5noLJ7t7>DEbZvq5Teby@1#8bJ5!n z8)$9viYW3A4LSwZ0H)$OWia7x+CIFylfX2b>996mE(z zejo5Aa5-mu4A3KZ+pxYx(1p8f5o}!qqm8jQ1tr16CV*1Tix8BT4zV9W=)>VVSmm7| zn8OKvEdAH<8`dRwT|8%4&$&$jMZj7`IME5=ikI_b9(8U`r1TzX`vy}X2be1hiY4%i*9Wmo%&O%s%-N<%DXrcfl zY$~CYJDZ_aF+)j(OMf;)tzxD$4y2m{9Tdob1V+I{`mpU!WeMY0mHWFa^D>BE3yWTq^oKkY)S*k{#liB;6OmudF}bL&}D(yos*fAYIMyv2>DNIEr#0 zJ##42Y6@%}80w4Cn1eAOaK?WvhA@M@aEmLtD4x*b)7`;SyWK=*?S5~t z_b1{=>^Iry?)km{r&zakyZv_5)q7}6d9*}fJ93b@qX^r|zF5DH)*n$cJrYRqn_~3# z|8dxYyAV6lfem~=MqM5GJ~uJ;7Zgp8vcUPkY1l_4 z9+Z=R7}G>`;d--Za1T4ruLnVyeiN~}sBvA248>)?bZKUBL^Y?EnqKJsL)d6`6ZJ9J zFH8L<#{P<;ZGsVZiYM?|VWnA?-EXbXpQkkH#bIbFAp)vAM*I#4NAcqK_l1;ZU!5HUt3i)zYok) S{W>Nl*ezt-vz!R(4fbDlFiwpC literal 0 HcmV?d00001 diff --git a/amazons-works/__pycache__/view.cpython-310.pyc b/amazons-works/__pycache__/view.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..658887bfd573815d49f837f15e3c97b6c8fba6ab GIT binary patch literal 1031 zcmZvbJ#P~+7{_hjOD;_U9T0`h8#)9%wJ=r)sZ$5C6bUIpik!142lo=$PEk%}3SR>W zHdt8rHeQ+X6*}=ecL}t*IC@_Ecl`6qJ+4NhJp%3budnjVBjgWuZZ-y;mtf`zI7uYU z$!m-(rv;m^!kc)MNKg7-$i$a{3?T-xcTVDH4Kqkg9eo@#7n-H9s&IZO3YFr>woyoM zl~XXo!C6@4Bh!19O17e(VYPDtxwp+@f9zOkotNgu1!=B?j+yphriK$VD^7nB-G}`8 zD0yEMDtVWcW%BCH=|yIhNOCcorb0`Th(gp=Y38|DCYPD|bU0t?9?V0S3Bg5lNQbP^ zQrzgaVIF`roBjbk-1Lf4jIik@!iJmZTY5!4Fc~}~Xe)X^WOx8a`lt4Gs?koh->Mrn zTv9^;!VWg7T{fy+HmbMTgAE)0yMv8tmyK$djp}XoaKi?52OHHc8`UlwRWQ8O&7F*p z{muIhj0f#)-)YCNkm9@JdhqmrbDmvfb)R#=XFTPyX3x3CuX|1T8si#kbB%>nvuC_! z$6!ai9-i4phGXF<&FctqyU^uf?7LvDGptTywF`_DR=N8Z%1%XUvr9E?r%cmIDtH^g ztb#&pYPEIv8ub7iCT1U8#3CBex0@fQ&FDA|^*tDLtTIj2xAl*;+l=2J;ychtbJ0mr T$wjVS;JHi$E+YFhV#Dw+>)^sa literal 0 HcmV?d00001 diff --git a/amazons-works/board.py b/amazons-works/board.py new file mode 100644 index 0000000..99e00b6 --- /dev/null +++ b/amazons-works/board.py @@ -0,0 +1,159 @@ +class Board: + # First we need to initialize our object with a 'state.' + def __init__(self,): + # While at the moment, we have a fixed initial state, in the future + # we may want players to be able to choose where the amazons start, + # or perhaps include an option for a random start. + # In order to build in this future flexibility, we will use another + # function to determine go get the initial state whih we can edit later. + self.state = self.get_initial_state() + # Note also that these functions are of the class, and so need to be + # prefixed with a 'self.' so that our script will know we mean the + # function that is defined within the our class structure. + + def get_initial_state(self): + # For now we will use a 4x4 board and place the amazons in the middle + # four squares, diagonlally from eachother for symmetry. + # Note that because the board is symettrical, one of the players must + # have an advantage... but is it player 1 or player 2? + return [[0,0,0,0],[0,2,3,0],[0,3,2,0],[0,0,0,0]] + + def get_active_player_code(self): + # It's going to be useful to know whose turn it is at any given time. + # Luckily, because a player burns away a square each turn, we can tell + # whose turn it is by counting how many open squares are left! They act + # like a kind of timer, coutning down to the end of the game. + # On turn 1 (initial state), it is player 1's turn and there are 12 + # open spaces at the start of the turn... so if the number of open + # spaces is even, then it's player 1's turn, and if odd then player 2's. + free_spaces = 0 + for row in self.state: #remember the state is a list of four 'rows'... + for box in row: + if box == 0: + free_spaces += 1 + # The logic above only worked because we had a 4x4 board, but if we had + # a 5x5 board, then we would start with 21 open spaces, so the logic is + # reversed with player 1 being odd and player 2 even, so... + if len(self.state[0])%2 == 0: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (2) # means it's player 1's turn. + else: + return (3) # And an odd number makes it player 2's. + else: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (3) # means it's player 2's turn. + else: + return (2) # And an odd number makes it player 1's. + + def get_active_amazons_positions(self): + code = self.get_active_player_code() #First of all, whose turn is it? + positions=[] # This will contain the (x,y) for each of the two amazons. + for x, row in enumerate(self.state): # 'enumerate' takes a list like: + # ['a', 'b', 'c'] and outputs [(0,'a'), (1,'b'), (2,'c')] + # So in this case 'y' refers to these numbers (the enumeration) + # which correspond to the column we are interested in, and 'row' + # refers to the row we are interested in. + for y, box in enumerate(row): # This time we hone in on the x coordinate + # of each entry in the row. + if box == code: # If the actual value at that (x,y) matches whose + # turn it is, i.e. they have an amazon there... + positions.append((x, y)) # We add that (x,y) pair to the list. + return positions + + def get_reachable_squares(self, origin): + directions = [[-1,1],[0,1],[1,1],[-1,0],[1,0],[-1,-1],[0,-1],[1,-1]] + # From each square on the board, there are eight directions amazons can + # move in or shoot in. These represent those eight directions. For example, + # (1,1) refers to going to the right once and up once and (-1,-1) means + # left and down respectively. + reachables = [] + for direction in directions: # For each of the 8 directions... + move_option = [origin[0], origin[1]] # center ourselves on the amazon. + hit_something = False # We will make this false if we find an obstacle. + while hit_something == False: # Until we hit something.... + # move in the specified direction: + move_option[0] += direction[0] + move_option[1] += direction[1] + if self.in_bounds(move_option): + if self.is_empty(move_option): + # if we are still on the board and if the square is empty... + addition = move_option.copy() + reachables.append(addition) # add it to the list. + else: + hit_something = True + else: # If we hit the edge of the board or an obstacle... + hit_something = True + return reachables + + def possible_moves(self): + # Note that a "move" consists of both moving an amazon and shooting. + # This means that a move has three values: chosen amazon's starting position, + # the amazon's new position, and the position of the burned square. + move_options=[] + amazons = self.get_active_amazons_positions() # Find the amazons. + for amazon in amazons: + amazon_move_options = self.get_reachable_squares(amazon) + # And where they can go... + # Before we burn anything, we need to empty the square we moved from + # so we can burn it or squares past it if we want to: + self.state[amazon[0]][amazon[1]] = 0 + for move_option in amazon_move_options: + #For each move, we see also what squares we can burn: + burn_options = self.get_reachable_squares(move_option) + for burn_option in burn_options: + # Now that we have an amazon, each square it can go to, and + # each square it can burn from each move, we have a (potentially large) + # list of triples. Let's add them to the list and move to the + # next amazon. + move_options.append((amazon, move_option, burn_option)) + # Let's not forget to put the amazon back in its square. + # Also, since we emptied a square, we have to reverse the logic: + if self.get_active_player_code() == 2: + self.state[amazon[0]][amazon[1]] = 3 + else: + self.state[amazon[0]][amazon[1]] = 2 + return move_options + + def get_successor_state(self, move_and_burn): + # We need to update the board based on the move, so let's grab it: + new_state = self.state.copy() + # These are the (x,y) coordinates of the chosen amazon's start. + ai, aj = move_and_burn[0][0], move_and_burn[0][1] + # These are the (x,y) coordinates of the chosen amazon's move. + mi, mj = move_and_burn[1][0], move_and_burn[1][1] + # These are the (x,y) corrdinates of the chosen burn square. + bi, bj = move_and_burn[2][0], move_and_burn[2][1] + new_state[ai][aj] = 0 # The amazon's start square is emptied. + if self.get_active_player_code() == 2: # The move square is filled. + new_state[mi][mj] = 3 + else: + new_state[mi][mj] = 2 + new_state[bi][bj] = 1 # The burn square is burned. + return (new_state) + + def get_possible_successor_states(self): + # This just uses the other function and applies is to every possible move. + return [self.get_successor_state(move) for move in self.possible_moves()] + + def is_empty(self, square): + # Here's the function referenced in "get_reachable_squares." + # Recall that the input is the (x,y) position of an amazon. + x, y = square # We isolate the x and y. + # Don't forget that while we say (x,y), the indices are refernced as [y][x] + # since the rows are above and below eachother (y) and columns adjacent (x): + if (x in range(len(self.state[0]))) and (y in range(len(self.state[0]))): + if self.state[x][y] == 0: + return True + else: + return False + else: + return False + + def in_bounds(self, square): + # Here's the other function refernced in "get_reachable_squares." + x, y = square + # We need to make sure all (x,y) values are between 0 and the length of a row. + if (x < 0) or (y < 0) or (x > len(self.state[0])) or (y > len(self.state[0])): + return False + else: + return True diff --git a/amazons-works/game.py b/amazons-works/game.py new file mode 100644 index 0000000..9a1e1cd --- /dev/null +++ b/amazons-works/game.py @@ -0,0 +1,36 @@ +from board import * +from view import * + +board = Board() + +translator = { + 'a4':[0,0], 'a3':[1,0], 'a2':[2,0], 'a1':[3,0], + 'b4':[0,1], 'b3':[1,1], 'b2':[2,1], 'b1':[3,1], + 'c4':[0,2], 'c3':[1,2], 'c2':[2,2], 'c1':[3,2], + 'd4':[0,3], 'd3':[1,3], 'd2':[2,3], 'd1':[3,3] +} + +def turn(): + global board + move_chosen = False + while move_chosen == False: + tui(board) + print("Valid coordinates look like: \'b2\' or \'c4\'") + amazon = translator[(input("The starting coordinates of the amazon of your choice: "))] + move = translator[(input("The coordinates of the square you'd like to move to: "))] + burn = translator[(input("The coordinates of the square you'd like to set aflame: "))] + choice = ((amazon[0],amazon[1]),move,burn) + print(board.possible_moves()) + if choice in board.possible_moves(): + board.state = board.get_successor_state(choice) + move_chosen = True + else: + print(str(choice)+" is not a valid move.") + + +def play(): + while len(board.possible_moves())>0: + turn() + return ("Player "+str(get_active_player_code()-1)+" loses!") + +play() diff --git a/amazons-works/player.py b/amazons-works/player.py new file mode 100644 index 0000000..e69de29 diff --git a/amazons-works/test/__init__.py b/amazons-works/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amazons-works/test/__pycache__/__init__.cpython-310.pyc b/amazons-works/test/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a71fe263e04ab7164d6a48a91b6b1c46bd75345 GIT binary patch literal 147 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 len(self.state[0])) or (y > len(self.state[0])): + return False + else: + return True + +board = Board() + +print(Board.possible_moves(board)) diff --git a/amazons/game.py b/amazons/game.py index 2a674a4..c71fecc 100644 --- a/amazons/game.py +++ b/amazons/game.py @@ -1,3 +1,38 @@ -import board +from board import * +from view import * -possible_moves() +board = Board() + +translator = { + 'a4':[0,0], 'a3':[1,0], 'a2':[2,0], 'a1':[3,0], + 'b4':[0,1], 'b3':[1,1], 'b2':[2,1], 'b1':[3,1], + 'c4':[0,2], 'c3':[1,2], 'c2':[2,2], 'c1':[3,2], + 'd4':[0,3], 'd3':[1,3], 'd2':[2,3], 'd1':[3,3] +} + +def turn(): + global board + move_chosen = False + while move_chosen == False: + tui(board) + print("Valid coordinates look like: \'b2\' or \'c4\'") + amazon = translator[(input("The starting coordinates of the amazon of your choice: "))] + move = translator[(input("The coordinates of the square you'd like to move to: "))] + burn = translator[(input("The coordinates of the square you'd like to set aflame: "))] + choice = ((amazon[0],amazon[1]),move,burn) + print(board.possible_moves()) + if choice in board.possible_moves(): + print("This is what I'm sending: "+str(choice)) + board.state = board.get_successor_state(choice) + print("board = "+str(board.state)) + move_chosen = True + else: + print(str(choice)+" is not a valid move.") + + +def play(): + while len(board.possible_moves())>0: + turn() + return ("Player "+str(get_active_player_code()-1)+" loses!") + +play() diff --git a/amazons/test/test_board.py b/amazons/test/test_board.py index d49b589..e9fe3c7 100644 --- a/amazons/test/test_board.py +++ b/amazons/test/test_board.py @@ -1,7 +1,7 @@ from unittest import TestCase from board import Board -class TestBoard(TestCase): +class TestBoard(TestCase):. def setUp(self): self.board = Board() self.tiny = Board() @@ -23,10 +23,14 @@ class TestBoard(TestCase): self.assertTrue(self.board.in_bounds(square)) def test_get_initial_state(self): + #unecessary? def test_get_active_player_code(self): + #unecessary? def test_get_active_amazons_positions(self): + + def test_possible_moves(self): diff --git a/amazons/view.py b/amazons/view.py index 6af282f..16862ce 100644 --- a/amazons/view.py +++ b/amazons/view.py @@ -1 +1,17 @@ -import board +from board import * +from os import system, name + +def clear(): + if name == 'nt': + _ = system('cls') + else: + _ = system('clear') + +def tui(board): #This displays the board state to the user + #clear() + print(" a b c d") + print("4 "+str(board.state[0][0])+" "+str(board.state[0][1])+" "+str(board.state[0][2])+" "+str(board.state[0][3])) + print("3 "+str(board.state[1][0])+" "+str(board.state[1][1])+" "+str(board.state[1][2])+" "+str(board.state[1][3])) + print("2 "+str(board.state[2][0])+" "+str(board.state[2][1])+" "+str(board.state[2][2])+" "+str(board.state[2][3])) + print("1 "+str(board.state[3][0])+" "+str(board.state[3][1])+" "+str(board.state[3][2])+" "+str(board.state[3][3])) + print("It's player "+str(board.get_active_player_code()-1)+"'s turn.") diff --git a/amazonsplay/__pycache__/board.cpython-310.pyc b/amazonsplay/__pycache__/board.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07bc5f36612496b4bfc6a41996628e78f6dcd90d GIT binary patch literal 3924 zcma)9-D_LP6`z^=p{ozuitIRXlHG{?tlHgp34s*jHA^?7lv1}efrYTkh^c z&0Ht3xPqk#w6BFipBC)C<+Y^`ZU2J)3j=*v`jn@>Z%ff*bc9JZPw!6_m*v7efJL*NFeyRuE z=w$nVM7cFMF+pfqhau@slPs(LXw^!3YdUp7WDm=%$T!gFK*PV|pu$qeQW|k_0e8RU zzQQ7=$_u3B+JRSe;43=Xb|<5%N&{r-Vi#RbxkPNsk`i^TxM*0pZ+N3Ch1?hPwQvDq zM8#g<85$WugmbC|)zTUBoIU3E*cWSKkqgw7v6BN1UkY_S=hD5)#(a2bxRME4C!HH-#fWSBYv_ApKlgJEoqjhLr*7`#-k~^yg@)IM>)`8TUJi?0f%ZAe$-&)yRl}L) z#+)W6unz})4Htp~jB2xH^W$dAS8Ld!NFk;ir^mfm!Nm%_`q5wzr_wk&Q>{{=UzcoR z$}=8@KT{NM3_nrp#Aykn8yLHdqOXD!8g_Yu*S$cj3h4S@bd9f8nA)V0)+o^2Ban(Zu6m)f0REMODI;{7Qs0o-Ohhi6Rm3si3r zj@>QRfo)`otRqXX&eypoJ&$1)p>m7eWe(eA7;`6M*inpuX8@kwlqdRS^x3J%y|eR^ zCVll=%vbC@EWa>s`|3Bjcj^L5C9JDrU1`cUcTT;$L`cC*4Ku)4af{TGD`P(uL<{Nl z-taxFTAg2&JHXP~1WT6*ODc}qN6|qSjx{`vRIFe92cM#lZQXMe^i|SH(w*l0(MqrX zByRQx1h$>#sQ#nuecepb=0X2Bl^P>8I}-N>*$H|#`)NCFMx7{0KWJu0adU2aGtoPv ziak6@J3GyXqv}i&y)%XG6-Z&qNg5U>m>|)i>lbpGicD0DPy>H3 z#Q}6XZOXH8Qzb#d;vQz@D9J*N^pYJx5)(vHT5noLJ7t7>DEbZvq5Teby@1#8bJ5!n z8)$9viYW3A4LSwZ0H)$OWia7x+CIFylfX2b>996mE(z zejo5Aa5-mu4A3KZ+pxYx(1p8f5o}!qqm8jQ1tr16CV*1Tix8BT4zV9W=)>VVSmm7| zn8OKvEdAH<8`dRwT|8%4&$&$jMZj7`IME5=ikI_b9(8U`r1TzX`vy}X2be1hiY4%i*9Wmo%&O%s%-N<%DXrcfl zY$~CYJDZ_aF+)j(OMf;)tzxD$4y2m{9Tdob1V+I{`mpU!WeMY0mHWFa^D>BE3yWTq^oKkY)S*k{#liB;6OmudF}bL&}D(yos*fAYIMyv2>DNIEr#0 zJ##42Y6@%}80w4Cn1eAOaK?WvhA@M@aEmLtD4x*b)7`;SyWK=*?S5~t z_b1{=>^Iry?)km{r&zakyZv_5)q7}6d9*}fJ93b@qX^r|zF5DH)*n$cJrYRqn_~3# z|8dxYyAV6lfem~=MqM5GJ~uJ;7Zgp8vcUPkY1l_4 z9+Z=R7}G>`;d--Za1T4ruLnVyeiN~}sBvA248>)?bZKUBL^Y?EnqKJsL)d6`6ZJ9J zFH8L<#{P<;ZGsVZiYM?|VWnA?-EXbXpQkkH#bIbFAp)vAM*I#4NAcqK_l1;ZU!5HUt3i)zYok) S{W>Nl*ezt-vz!R(4fbDlFiwpC literal 0 HcmV?d00001 diff --git a/amazonsplay/__pycache__/view.cpython-310.pyc b/amazonsplay/__pycache__/view.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..658887bfd573815d49f837f15e3c97b6c8fba6ab GIT binary patch literal 1031 zcmZvbJ#P~+7{_hjOD;_U9T0`h8#)9%wJ=r)sZ$5C6bUIpik!142lo=$PEk%}3SR>W zHdt8rHeQ+X6*}=ecL}t*IC@_Ecl`6qJ+4NhJp%3budnjVBjgWuZZ-y;mtf`zI7uYU z$!m-(rv;m^!kc)MNKg7-$i$a{3?T-xcTVDH4Kqkg9eo@#7n-H9s&IZO3YFr>woyoM zl~XXo!C6@4Bh!19O17e(VYPDtxwp+@f9zOkotNgu1!=B?j+yphriK$VD^7nB-G}`8 zD0yEMDtVWcW%BCH=|yIhNOCcorb0`Th(gp=Y38|DCYPD|bU0t?9?V0S3Bg5lNQbP^ zQrzgaVIF`roBjbk-1Lf4jIik@!iJmZTY5!4Fc~}~Xe)X^WOx8a`lt4Gs?koh->Mrn zTv9^;!VWg7T{fy+HmbMTgAE)0yMv8tmyK$djp}XoaKi?52OHHc8`UlwRWQ8O&7F*p z{muIhj0f#)-)YCNkm9@JdhqmrbDmvfb)R#=XFTPyX3x3CuX|1T8si#kbB%>nvuC_! z$6!ai9-i4phGXF<&FctqyU^uf?7LvDGptTywF`_DR=N8Z%1%XUvr9E?r%cmIDtH^g ztb#&pYPEIv8ub7iCT1U8#3CBex0@fQ&FDA|^*tDLtTIj2xAl*;+l=2J;ychtbJ0mr T$wjVS;JHi$E+YFhV#Dw+>)^sa literal 0 HcmV?d00001 diff --git a/amazonsplay/amazons_old.py b/amazonsplay/amazons_old.py new file mode 100644 index 0000000..9cb1f6b --- /dev/null +++ b/amazonsplay/amazons_old.py @@ -0,0 +1,283 @@ +from random import * +from os import system, name + +def clear(): + if name == 'nt': + _ = system('cls') + else: + _ = system('clear') + +class Square: + def __init__(self, location, name): + self.location = location #where the square is as an [x,y] pair + self.x = location[0] + self.y = location[1] + self.state = "empty" #behind the scenes data + self.display = " " #what the user will see + self.name = name #There's gotta be a better way! + +#The names of the squares are according to chess convention, +#but the coordinates are according to usual matrix notation. +a1 = Square([0,3], "a1") +a2 = Square([0,2], "a2") +a3 = Square([0,1], "a3") +a4 = Square([0,0], "a4") +b1 = Square([1,3], "b1") +b2 = Square([1,2], "b2") +b3 = Square([1,1], "b3") +b4 = Square([1,0], "b4") +c1 = Square([2,3], "c1") +c2 = Square([2,2], "c2") +c3 = Square([2,1], "c3") +c4 = Square([2,0], "c4") +d1 = Square([3,3], "d1") +d2 = Square([3,2], "d2") +d3 = Square([3,1], "d3") +d4 = Square([3,0], "d4") + +board=[a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4,d1,d2,d3,d4] + +setup_seed = sample(board, 4) #We pick four squares for initial amazon placement + +setup_seed[0].state = "ba1" #black amazon 1 +setup_seed[1].state = "ba2" #black amazon 2 +setup_seed[2].state = "wa1" #white amazon 1 +setup_seed[3].state = "wa2" #white amazon 2 + +turn = "w" #White moves first. +turn_counter = 1 + +def display(): #This interprets the states of the squares for display + global turn, turn_counter, board + for box in board: + if box.state == "empty": + box.display = " " + if box.state == "ba1": + box.display = "B" + if box.state == "ba2": + box.display = "B" + if box.state == "wa1": + box.display = "W" + if box.state == "wa2": + box.display = "W" + if box.state == "fire": + box.display = "X" + +def gui(): #This displays the board state to the user + global turn, turn_counter, board + clear() + display() + print(" a b c d") + print("4 "+a4.display+" "+b4.display+" "+c4.display+" "+d4.display) + print("3 "+a3.display+" "+b3.display+" "+c3.display+" "+d3.display) + print("2 "+a2.display+" "+b2.display+" "+c2.display+" "+d2.display) + print("1 "+a1.display+" "+b1.display+" "+c1.display+" "+d1.display) + if turn_counter%2 == 0: + print("It's black's turn.") + else: + print("It's white's turn.") + +def choice_translate(box): #Takes the string inputs of the user and outputs corresponding square + global turn, turn_counter, board + if box == "a1": + return a1 + if box == "a2": + return a2 + if box == "a3": + return a3 + if box == "a4": + return a4 + if box == "b1": + return b1 + if box == "b2": + return b2 + if box == "b3": + return b3 + if box == "b4": + return b4 + if box == "c1": + return c1 + if box == "c2": + return c2 + if box == "c3": + return c3 + if box == "c4": + return c4 + if box == "d1": + return d1 + if box == "d2": + return d2 + if box == "d3": + return d3 + if box == "d4": + return d4 + else: + return False + +def valid_move(start, end): #Checks to see if a mvoe is valid (for both amazons and arrows) + global turn, turn_counter, board + + test_set=[] #This will be the set of squares between the start and end of the move. + #For reasons unknown, the test set always finds each eligible square twice. How to fix? + + #There are three reasons to immediately reject a move: + if end.state != "empty": #If the ending square isn't empty + return False + if start == end: #If the ending square is the starting square + return False + #If the start and end aren't in the same row, column, or diagonal. + if (start.x != end.x) and (start.y != end.y) and (abs(start.x - end.x) != abs(start.y - end.y)): + return False + + #If they are in the same column... + if start.x == end.x: + if start.y > end.y: #If we are going down.... + for box in board: #...find the squares... + if box.x == start.x: #...in that column... + for i in range(1, abs(start.y-end.y)): #... between the start and end... + if box.y == start.y-i: + test_set.append(box) #... and add it to the list. + if start.y < end.y: #If we are going up... etc... + for box in board: + if box.x == start.x: + for i in range(1, abs(start.y-end.y)): + if box.y == start.y+i: + test_set.append(box) + + #If they are in the same row... + if start.y == end.y: + if start.x > end.x: #going right.... + for box in board: + if box.y == start.y: + for i in range(1, abs(start.x-end.x)): + if box.x == start.x-i: + test_set.append(box) + if start.x < end.x: #going left... + for box in board: + if box.y == start.y: + for i in range(1, abs(start.x-end.x)): + if box.x == start.x+i: + test_set.append(box) + + #If they are on the same diagonal + if abs(start.x - end.x) == abs(start.y - end.y): + if (start.x < end.x) and (start.y < end.y): #going up and right + for box in board: + for i in range(1, abs(start.x-end.x)): + if ((box.x == start.x+i) and (box.y == start.y+i)): + test_set.append(box) + if (start.x > end.x) and (start.y > end.y): #going down and left + for box in board: + for i in range(1, abs(start.x-end.x)): + if ((box.x == start.x-i) and (box.y == start.y-i)): + test_set.append(box) + if (start.x > end.x) and (start.y < end.y): #going up and left + for box in board: + for i in range(1, abs(start.x-end.x)): + if ((box.x == start.x-i) and (box.y == start.y+i)): + test_set.append(box) + if (start.x < end.x) and (start.y > end.y): #going down and right + for box in board: + for i in range(1, abs(start.x-end.x)): + if ((box.x == start.x+i) and (box.y == start.y-i)): + test_set.append(box) + + for test in test_set: #now make sure all of the squares we've selected are indeed empty + if test.state != "empty": + return False + return True + +def possible_moves(amazon): #outputs a list of squares that can be reached by an amazon + moves=[] + for box in board: + if valid_move(amazon, box): + moves.append(box) + return(moves) + +def play(): + global turn, turn_counter, board + gui() + + #We check which amazons can legally move + movable_amazons=[] + for box in board: + if box.state == turn+"a1": + if len(possible_moves(box)) != 0: + movable_amazons.append(box.name) + if box.state == turn+"a2": + if len(possible_moves(box)) != 0: + movable_amazons.append(box.name) + + #If none can move, then the game is over! + if len(movable_amazons)==0: + if turn_counter%2 == 0: + input("White wins!") + exit(0) + if turn_counter%2 == 1: + input("Black wins!") + exit(0) + + #Otherwise, the player chooses an amazon to move + if len(movable_amazons) == 2: #If both can move they have a choice + amazon_choice = input("Choose a square containing one of your amazons, either "+movable_amazons[0]+" or "+movable_amazons[1]+": ") + while amazon_choice not in movable_amazons: + amazon_choice = input("That's not one of the options. Try again: ") + if len(movable_amazons) == 1: #If only one can move, they have no choice + amazon_choice = movable_amazons[0] + gui() + + #The player chooses a square to move to + print("You must move the amazon on "+amazon_choice+". Choose the square you want to move to.") + print("Which of the following would you like to move to?") + amazon_choice = choice_translate(amazon_choice) + choices = [] #We have a list to display to the user... + for option in possible_moves(amazon_choice): #...consisting of their possible moves. + choices.append(option.name) + move_choice = choice_translate(input(choices)) + + #The following three while statements only catch exemptions if made in this order. + #So if the user first inputs a square correctly, but it's not a possible move, + #and then they enter in gibberish, it wigs out. How to fix? + while move_choice == False: #choice_translate outputs False when the input isn't a square name + move_choice = choice_translate(input("That's not a valid move. Try again: ")) + while move_choice.name not in choices: + move_choice = choice_translate(input("That's not an option. Try again: ")) + while valid_move(amazon_choice, move_choice) == False: + move_choice = choice_translate(input("That amazon can't move there. Try again: ")) + + #If all goes well, we make the move by changing the states of the squares. + if valid_move(amazon_choice, move_choice): + move_choice.state = amazon_choice.state + amazon_choice.state = "empty" + gui() + + #The player chooses a square to shoot + choices = [] + for option in possible_moves(move_choice): + choices.append(option.name) + print("Choose the square you want to set aflame: ") + burn_choice = choice_translate(input(choices)) + + #These three statements have the same problem as those above. + while burn_choice == False: + burn_choice = choice_translate(input("That's not the name of any square. Try again: ")) + while burn_choice.state != "empty": + burn_choice = choice_translate(input("That's square isn't empty. Try again: ")) + while valid_move(move_choice, burn_choice) == False: + burn_choice = choice_translate(input("You can't shoot there. Try again: ")) + burn_choice.state = "fire" + + #Bookkeepping for turn taking + turn_counter+=1 + if turn_counter%2 == 0: + turn = "b" + if turn_counter%2 == 1: + turn = "w" + +while True: + play() + + +#Next Steps: + #have students change the abilities of the amazons, size of the board, etc. + #Add a class to record game state!?!?!? diff --git a/amazonsplay/board.py b/amazonsplay/board.py new file mode 100644 index 0000000..daa9922 --- /dev/null +++ b/amazonsplay/board.py @@ -0,0 +1,208 @@ +class Board: + # First we need to initialize our object with a 'state.' + def __init__(self,): + # While at the moment, we have a fixed initial state, in the future + # we may want players to be able to choose where the amazons start, + # or perhaps include an option for a random start. + # In order to build in this future flexibility, we will use another + # function to determine go get the initial state whih we can edit later. + self.state = self.get_initial_state() + # Note also that these functions are of the class, and so need to be + # prefixed with a 'self.' so that our script will know we mean the + # function that is defined within the our class structure. + + def get_initial_state(self): + # For now we will use a 4x4 board and place the amazons in the middle + # four squares, diagonlally from eachother for symmetry. + # Note that because the board is symettrical, one of the players must + # have an advantage... but is it player 1 or player 2? + return [[0,0,0,0],[0,2,3,0],[0,3,2,0],[0,0,0,0]] + + def get_active_player_code(self): + print("-----------") + print("get_active_player_code") + # It's going to be useful to know whose turn it is at any given time. + # Luckily, because a player burns away a square each turn, we can tell + # whose turn it is by counting how many open squares are left! They act + # like a kind of timer, coutning down to the end of the game. + # On turn 1 (initial state), it is player 1's turn and there are 12 + # open spaces at the start of the turn... so if the number of open + # spaces is even, then it's player 1's turn, and if odd then player 2's. + free_spaces = 0 + for row in self.state: #remember the state is a list of four 'rows'... + for box in row: + if box == 0: + free_spaces += 1 + # The logic above only worked because we had a 4x4 board, but if we had + # a 5x5 board, then we would start with 21 open spaces, so the logic is + # reversed with player 1 being odd and player 2 even, so... + if len(self.state[0])%2 == 0: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (2) # means it's player 1's turn. + else: + return (3) # And an odd number makes it player 2's. + else: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (3) # means it's player 2's turn. + else: + return (2) # And an odd number makes it player 1's. + + def get_active_amazons_positions(self): + print("-----------") + print("get_active_amazons_positions") + code = self.get_active_player_code() #First of all, whose turn is it? + positions=[] # This will contain the (x,y) for each of the two amazons. + for x, row in enumerate(self.state): # 'enumerate' takes a list like: + # ['a', 'b', 'c'] and outputs [(0,'a'), (1,'b'), (2,'c')] + # So in this case 'y' refers to these numbers (the enumeration) + # which correspond to the column we are interested in, and 'row' + # refers to the row we are interested in. + for y, box in enumerate(row): # This time we hone in on the x coordinate + # of each entry in the row. + if box == code: # If the actual value at that (x,y) matches whose + # turn it is, i.e. they have an amazon there... + positions.append((x, y)) # We add that (x,y) pair to the list. + print("positions = "+str(positions)) + return positions + + def get_reachable_squares(self, origin): + print("-----------") + print("get_reachable_squares") + directions = [[-1,1],[0,1],[1,1],[-1,0],[1,0],[-1,-1],[0,-1],[1,-1]] + # From each square on the board, there are eight directions amazons can + # move in or shoot in. These represent those eight directions. For example, + # (1,1) refers to going to the right once and up once and (-1,-1) means + # left and down respectively. + reachables = [] + for direction in directions: # For each of the 8 directions... + print("origin: "+str(origin)) + move_option = [origin[0], origin[1]] # center ourselves on the amazon. + hit_something = False # We will make this false if we find an obstacle. + while hit_something == False: # Until we hit something.... + # move in the specified direction: + move_option[0] += direction[0] + move_option[1] += direction[1] + print("move option: "+str(move_option)) + print("It's in bounds: "+str(self.in_bounds(move_option))) + if self.in_bounds(move_option): + print("It's empty: "+str(self.is_empty(move_option))) + if self.is_empty(move_option): + # if we are still on the board and if the square is empty... + addition = move_option.copy() + print("once again, the move option is:"+str(addition)) + reachables.append(addition) # add it to the list. + print("reaching: "+str(reachables)) + else: + hit_something = True + else: # If we hit the edge of the board or an obstacle... + hit_something = True + print("reachables:"+str(reachables)) + return reachables + + +# def get_reachable_squares(self, origin): +# directions = [[-1,1],[0,1],[1,1],[-1,0],[1,0],[-1,-1],[0,-1],[1,-1]] + # From each square on the board, there are eight directions amazons can + # move in or shoot in. These represent those eight directions. For example, + # (1,1) refers to going to the right once and up once and (-1,-1) means + # left and down respectively. +# reachables = [] +# for direction in directions: # For each of the 8 directions... +# move_option = [origin[0], origin[1]] # center ourselves on the amazon. +# hit_something = False # We will make this false if we find an obstacle. +# while hit_something == False: # Until we hit something.... +# # move in the specified direction: +# move_option[0] += direction[0] +# move_option[1] += direction[1] +# if self.in_bounds(move_option): +# if self.is_empty(move_option): +# # if we are still on the board and if the square is empty... +# addition = move_option.copy() +# reachables.append(addition) # add it to the list. +# else: +# hit_something = True +# else: # If we hit the edge of the board or an obstacle... +# hit_something = True +# return reachables + + + + def possible_moves(self): + print("-----------") + print("possible_moves") + # Note that a "move" consists of both moving an amazon and shooting. + # This means that a move has three values: chosen amazon's starting position, + # the amazon's new position, and the position of the burned square. + move_options=[] + amazons = self.get_active_amazons_positions() # Find the amazons. + for amazon in amazons: + amazon_move_options = self.get_reachable_squares(amazon) + # And where they can go... + # Before we burn anything, we need to empty the square we moved from + # so we can burn it or squares past it if we want to: + self.state[amazon[0]][amazon[1]] = 0 + for move_option in amazon_move_options: + #For each move, we see also what squares we can burn: + burn_options = self.get_reachable_squares(move_option) + for burn_option in burn_options: + # Now that we have an amazon, each square it can go to, and + # each square it can burn from each move, we have a (potentially large) + # list of triples. Let's add them to the list and move to the + # next amazon. + move_options.append((amazon, move_option, burn_option)) + # Let's not forget to put the amazon back in its square. + # Also, since we emptied a square, we have to reverse the logic: + if self.get_active_player_code() == 2: + self.state[amazon[0]][amazon[1]] = 3 + else: + self.state[amazon[0]][amazon[1]] = 2 + return move_options + + def get_successor_state(self, move_and_burn): + print("-----------") + print("get_successor_state") + # We need to update the board based on the move, so let's grab it: + new_state = self.state.copy() + # These are the (x,y) coordinates of the chosen amazon's start. + print("move_and_burn: "+str(move_and_burn)) + ai, aj = move_and_burn[0][0], move_and_burn[0][1] + # These are the (x,y) coordinates of the chosen amazon's move. + mi, mj = move_and_burn[1][0], move_and_burn[1][1] + # These are the (x,y) corrdinates of the chosen burn square. + bi, bj = move_and_burn[2][0], move_and_burn[2][1] + new_state[ai][aj] = 0 # The amazon's start square is emptied. + if self.get_active_player_code() == 2: # The move square is filled. + new_state[mi][mj] = 3 + else: + new_state[mi][mj] = 2 + new_state[bi][bj] = 1 # The burn square is burned. + return (new_state) + + def get_possible_successor_states(self): + print("-----------") + print("get_possible_successor_states") + # This just uses the other function and applies is to every possible move. + return [self.get_successor_state(move) for move in self.possible_moves()] + + def is_empty(self, square): + # Here's the function referenced in "get_reachable_squares." + # Recall that the input is the (x,y) position of an amazon. + x, y = square # We isolate the x and y. + # Don't forget that while we say (x,y), the indices are refernced as [y][x] + # since the rows are above and below eachother (y) and columns adjacent (x): + if (x in range(len(self.state[0]))) and (y in range(len(self.state[0]))): + if self.state[x][y] == 0: + return True + else: + return False + else: + return False + + def in_bounds(self, square): + # Here's the other function refernced in "get_reachable_squares." + x, y = square + # We need to make sure all (x,y) values are between 0 and the length of a row. + if (x < 0) or (y < 0) or (x > len(self.state[0])) or (y > len(self.state[0])): + return False + else: + return True diff --git a/amazonsplay/board0.py b/amazonsplay/board0.py new file mode 100644 index 0000000..b5e47e5 --- /dev/null +++ b/amazonsplay/board0.py @@ -0,0 +1,159 @@ +class Board: + # First we need to initialize our object with a 'state.' + def __init__(self,): + # While at the moment, we have a fixed initial state, in the future + # we may want players to be able to choose where the amazons start, + # or perhaps include an option for a random start. + # In order to build in this future flexibility, we will use another + # function to determine go get the initial state whih we can edit later. + self.state = self.get_initial_state() + # Note also that these functions are of the class, and so need to be + # prefixed with a 'self.' so that our script will know we mean the + # function that is defined within the our class structure. + + def get_initial_state(self): + # For now we will use a 4x4 board and place the amazons in the middle + # four squares, diagonlally from eachother for symmetry. + # Note that because the board is symettrical, one of the players must + # have an advantage... but is it player 1 or player 2? + return [[0,0,0,0],[0,2,3,0],[0,3,2,0],[0,0,0,0]] + + def get_active_player_code(self): + # It's going to be useful to know whose turn it is at any given time. + # Luckily, because a player burns away a square each turn, we can tell + # whose turn it is by counting how many open squares are left! They act + # like a kind of timer, coutning down to the end of the game. + # On turn 1 (initial state), it is player 1's turn and there are 12 + # open spaces at the start of the turn... so if the number of open + # spaces is even, then it's player 1's turn, and if odd then player 2's. + free_spaces = 0 + for row in self.state: #remember the state is a list of four 'rows'... + for box in row: + if box == 0: + free_spaces += 1 + # The logic above only worked because we had a 4x4 board, but if we had + # a 5x5 board, then we would start with 21 open spaces, so the logic is + # reversed with player 1 being odd and player 2 even, so... + if len(self.state[0])%2 == 0: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (2) # means it's player 1's turn. + else: + return (3) # And an odd number makes it player 2's. + else: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (3) # means it's player 2's turn. + else: + return (2) # And an odd number makes it player 1's. + + def get_active_amazons_positions(self): + code = self.get_active_player_code() #First of all, whose turn is it? + positions=[] # This will contain the (x,y) for each of the two amazons. + for y, row in enumerate(self.state): # 'enumerate' takes a list like: + # ['a', 'b', 'c'] and outputs [(0,'a'), (1,'b'), (2,'c')] + # So in this case 'y' refers to these numbers (the enumeration) + # which correspond to the column we are interested in, and 'row' + # refers to the row we are interested in. + for x, box in enumerate(row): # This time we hone in on the x coordinate + # of each entry in the row. + if box == code: # If the actual value at that (x,y) matches whose + # turn it is, i.e. they have an amazon there... + positions.append((x, y)) # We add that (x,y) pair to the list. + return positions + + def get_reachable_squares(self, origin): + directions = [[-1,1],[0,1],[1,1],[-1,0],[1,0],[-1,-1],[0,-1],[1,-1]] + # From each square on the board, there are eight directions amazons can + # move in or shoot in. These represent those eight directions. For example, + # (1,1) refers to going to the right once and up once and (-1,-1) means + # left and down respectively. + reachables = [] + print('amazon= '+str(origin)) + for direction in directions: # For each of the 8 directions... + move_option = [origin[0], origin[1]] # center ourselves on the amazon. + hit_something = False # We will make this false if we find an obstacle. + while hit_something == False: # Until we hit something.... + # move in the specified direction... + move_option[0] += direction[0] + move_option[1] += direction[1] + print('----------------') + print('move_option = '+str(move_option)) + # move in the specified direction... + print('in bounds = '+str(self.in_bounds(move_option))) + print('is empty = '+str(self.is_empty(move_option))) + if self.in_bounds(move_option): + if self.is_empty(move_option): + # if we are still on the board and if the square is empty... + reachables.append(move_option) # add it to the list. + else: # If we hit the edge of the board or an obstacle... + hit_something = True + print('I hit something = '+str(hit_something)) + print('ok found em') + return reachables + + def possible_moves(self): + # Note that a "move" consists of both moving an amazon and shooting. + # This means that a move has three values: chosen amazon's starting position, + # the amazon's new position, and the position of the burned square. + move_options=[] + amazons = self.get_active_amazons_positions() # Find the amazons. + print('amazons are '+str(amazons)) + for amazon in amazons: + print('we are on '+str(amazon)) + amazon_move_options = self.get_reachable_squares(amazon) + # And where they can go... + print('move options are: '+str(amazon_move_options)) + for move_option in amazon_move_options: + #For each move, we see also what squares we can burn: + burn_options = self.get_reachable_squares(move_option) + for burn_option in burn_options: + # Now that we have an amazon, each square it can go to, and + # each square it can burn from each move, we have a (potentially large) + # list of triples. Let's add them to the list and move to the + # next amazon. + move_options.append((amazon, move_option, burn_option)) + print('one down') + return move_options + + def get_successor_state(self, move_and_burn): + # We need to update the board based on the move, so let's grab it: + new_state = self.state.copy() + # These are the (x,y) coordinates of the chosen amazon's start. + ai, aj = [move_and_burn[0][0]], [move_and_burn[0][1]] + # These are the (x,y) coordinates of the chosen amazon's move. + mi, mj = [move_and_burn[1][0]], [move_and_burn[1][1]] + # These are the (x,y) corrdinates of the chosen burn square. + bi, bj = [move_and_burn[2][0]], [move_and_burn[2][1]] + new_state[aj][ai] = 0 # The amazon's start square is emptied. + new_state[mj][mi] = self.get_active_player_code() # The move square is filled. + new_state[bj][bi] = 1 # The burn square is burned. + return (new_state) + + def get_possible_successor_states(self): + # This just uses the other function and applies is to every possible move. + return [self.get_successor_state(move) for move in self.possible_moves()] + + def is_empty(self, square): + # Here's the function referenced in "get_reachable_squares." + # Recall that the input is the (x,y) position of an amazon. + x, y = square # We isolate the x and y. + # Don't forget that while we say (x,y), the indices are refernced as [y][x] + # since the rows are above and below eachother (y) and columns adjacent (x): + print("the square is empty: "+str(self.state[y][x])) + if self.state[y][x] == 0: + return True + else: + return False + + def in_bounds(self, square): + # Here's the other function refernced in "get_reachable_squares." + print('square = '+str(square)) + x, y = square + # We need to make sure all (x,y) values are between 0 and the length of a row. + if (x < 0) or (y < 0) or (x > len(self.state[0])) or (y > len(self.state[0])): + return False + else: + return True + +board = Board() + +print(Board.possible_moves(board)) diff --git a/amazonsplay/board1.py b/amazonsplay/board1.py new file mode 100644 index 0000000..3499238 --- /dev/null +++ b/amazonsplay/board1.py @@ -0,0 +1,158 @@ +class Board: + # First we need to initialize our object with a 'state.' + def __init__(self,): + # While at the moment, we have a fixed initial state, in the future + # we may want players to be able to choose where the amazons start, + # or perhaps include an option for a random start. + # In order to build in this future flexibility, we will use another + # function to determine go get the initial state whih we can edit later. + self.state = self.get_initial_state() + # Note also that these functions are of the class, and so need to be + # prefixed with a 'self.' so that our script will know we mean the + # function that is defined within the our class structure. + + def get_initial_state(self): + # For now we will use a 4x4 board and place the amazons in the middle + # four squares, diagonlally from eachother for symmetry. + # Note that because the board is symettrical, one of the players must + # have an advantage... but is it player 1 or player 2? + return [[0,0,0,0],[0,2,3,0],[0,3,2,0],[0,0,0,0]] + + def get_active_player_code(self): + # It's going to be useful to know whose turn it is at any given time. + # Luckily, because a player burns away a square each turn, we can tell + # whose turn it is by counting how many open squares are left! They act + # like a kind of timer, coutning down to the end of the game. + # On turn 1 (initial state), it is player 1's turn and there are 12 + # open spaces at the start of the turn... so if the number of open + # spaces is even, then it's player 1's turn, and if odd then player 2's. + free_spaces = 0 + for row in self.state: #remember the state is a list of four 'rows'... + for box in row: + if box == 0: + free_spaces += 1 + # The logic above only worked because we had a 4x4 board, but if we had + # a 5x5 board, then we would start with 21 open spaces, so the logic is + # reversed with player 1 being odd and player 2 even, so... + if len(self.state[0])%2 == 0: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (2) # means it's player 1's turn. + else: + return (3) # And an odd number makes it player 2's. + else: # If the length of the rows is even... + if free_spaces%2 == 0: # then an even number of free spaces... + return (3) # means it's player 2's turn. + else: + return (2) # And an odd number makes it player 1's. + + def get_active_amazons_positions(self): + code = self.get_active_player_code() #First of all, whose turn is it? + positions=[] # This will contain the (x,y) for each of the two amazons. + for y, row in enumerate(self.state): # 'enumerate' takes a list like: + # ['a', 'b', 'c'] and outputs [(0,'a'), (1,'b'), (2,'c')] + # So in this case 'y' refers to these numbers (the enumeration) + # which correspond to the column we are interested in, and 'row' + # refers to the row we are interested in. + for x, box in enumerate(row): # This time we hone in on the x coordinate + # of each entry in the row. + if box == code: # If the actual value at that (x,y) matches whose + # turn it is, i.e. they have an amazon there... + positions.append((x, y)) # We add that (x,y) pair to the list. + return positions + + def get_reachable_squares(self, origin): + directions = [[-1,1],[0,1],[1,1],[-1,0],[1,0],[-1,-1],[0,-1],[1,-1]] + # From each square on the board, there are eight directions amazons can + # move in or shoot in. These represent those eight directions. For example, + # (1,1) refers to going to the right once and up once and (-1,-1) means + # left and down respectively. + reachables = [] + for direction in directions: # For each of the 8 directions... + print("origin: "+str(origin)) + move_option = [origin[0], origin[1]] # center ourselves on the amazon. + hit_something = False # We will make this false if we find an obstacle. + while hit_something == False: # Until we hit something.... + # move in the specified direction: + move_option[0] += direction[0] + move_option[1] += direction[1] + print("move option: "+str(move_option)) + print("It's in bounds: "+str(self.in_bounds(move_option))) + if self.in_bounds(move_option): + print("It's empty: "+str(self.is_empty(move_option))) + if self.is_empty(move_option): + # if we are still on the board and if the square is empty... + addition = move_option.copy() + print("once again, the move option is:"+str(addition)) + reachables.append(addition) # add it to the list. + print("reaching: "+str(reachables)) + else: + hit_something = True + else: # If we hit the edge of the board or an obstacle... + hit_something = True + print("reachables:"+str(reachables)) + return reachables + + def possible_moves(self): + # Note that a "move" consists of both moving an amazon and shooting. + # This means that a move has three values: chosen amazon's starting position, + # the amazon's new position, and the position of the burned square. + move_options=[] + amazons = self.get_active_amazons_positions() # Find the amazons. + for amazon in amazons: + amazon_move_options = self.get_reachable_squares(amazon) + # And where they can go... + for move_option in amazon_move_options: + #For each move, we see also what squares we can burn: + burn_options = self.get_reachable_squares(move_option) + for burn_option in burn_options: + # Now that we have an amazon, each square it can go to, and + # each square it can burn from each move, we have a (potentially large) + # list of triples. Let's add them to the list and move to the + # next amazon. + move_options.append((amazon, move_option, burn_option)) + return move_options + + def get_successor_state(self, move_and_burn): + # We need to update the board based on the move, so let's grab it: + new_state = self.state.copy() + # These are the (x,y) coordinates of the chosen amazon's start. + ai, aj = [move_and_burn[0][0]], [move_and_burn[0][1]] + # These are the (x,y) coordinates of the chosen amazon's move. + mi, mj = [move_and_burn[1][0]], [move_and_burn[1][1]] + # These are the (x,y) corrdinates of the chosen burn square. + bi, bj = [move_and_burn[2][0]], [move_and_burn[2][1]] + new_state[aj][ai] = 0 # The amazon's start square is emptied. + new_state[mj][mi] = self.get_active_player_code() # The move square is filled. + new_state[bj][bi] = 1 # The burn square is burned. + return (new_state) + + def get_possible_successor_states(self): + # This just uses the other function and applies is to every possible move. + return [self.get_successor_state(move) for move in self.possible_moves()] + + def is_empty(self, square): + # Here's the function referenced in "get_reachable_squares." + # Recall that the input is the (x,y) position of an amazon. + x, y = square # We isolate the x and y. + # Don't forget that while we say (x,y), the indices are refernced as [y][x] + # since the rows are above and below eachother (y) and columns adjacent (x): + if (x in range(len(self.state[0]))) and (y in range(len(self.state[0]))): + if self.state[y][x] == 0: + return True + else: + return False + else: + return False + + def in_bounds(self, square): + # Here's the other function refernced in "get_reachable_squares." + x, y = square + # We need to make sure all (x,y) values are between 0 and the length of a row. + if (x < 0) or (y < 0) or (x > len(self.state[0])) or (y > len(self.state[0])): + return False + else: + return True + +board = Board() + +print(Board.possible_moves(board)) diff --git a/amazonsplay/game.py b/amazonsplay/game.py new file mode 100644 index 0000000..fb311f1 --- /dev/null +++ b/amazonsplay/game.py @@ -0,0 +1,40 @@ +from board import * +from view import * + +board = Board() + +translator = { + 'a4':[0,0], 'a3':[1,0], 'a2':[2,0], 'a1':[3,0], + 'b4':[0,1], 'b3':[1,1], 'b2':[2,1], 'b1':[3,1], + 'c4':[0,2], 'c3':[1,2], 'c2':[2,2], 'c1':[3,2], + 'd4':[0,3], 'd3':[1,3], 'd2':[2,3], 'd1':[3,3] +} + +def turn(): + print("-----------") + print("turn") + global board + move_chosen = False + while move_chosen == False: + tui(board) + print("Valid coordinates look like: \'b2\' or \'c4\'") + amazon = translator[(input("The starting coordinates of the amazon of your choice: "))] + move = translator[(input("The coordinates of the square you'd like to move to: "))] + burn = translator[(input("The coordinates of the square you'd like to set aflame: "))] + choice = ((amazon[0],amazon[1]),move,burn) + print(board.possible_moves()) + if choice in board.possible_moves(): + print("This is what I'm sending: "+str(choice)) + board.state = board.get_successor_state(choice) + print("board = "+str(board.state)) + move_chosen = True + else: + print(str(choice)+" is not a valid move.") + + +def play(): + while len(board.possible_moves())>0: + turn() + return ("Player "+str(get_active_player_code()-1)+" loses!") + +play() diff --git a/amazonsplay/player.py b/amazonsplay/player.py new file mode 100644 index 0000000..e69de29 diff --git a/amazonsplay/test/__init__.py b/amazonsplay/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/amazonsplay/test/__pycache__/__init__.cpython-310.pyc b/amazonsplay/test/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a71fe263e04ab7164d6a48a91b6b1c46bd75345 GIT binary patch literal 147 zcmd1j<>g`kf(Y5B6cGIwL?8o3AjbiSi&=m~3PUi1CZpdhSe2hwtY4B^T%sQzpP83g5+AQuPAOKrR#^MMBm#c9$)i-PYa+5hWMW z2Wh$WRd@tnIpr0&gc)y@rjfAZFXJ80=b3LN<=$S4;rjjfrz$vOf9SBe0zACLZJ#12 zrXXiy7y^*Zb3PVB0qh-9LIvkc1sWD&7^v_giyKRfU@^GpP}{1XSdDi0s!WU;k!N$^ z%k?Y!XlOPB#fDsg;^%BAYHngx4LpZx2iYUnB-)w|Hn_+Zxcj*66NHK_*o;ZI{`Ise zS6qpdr+mgvxD<*ZWiVJ$pdp4OWfTX7KDh9Rl6FCr6+c{PtEAFb-~y}j<11#`c;)Z2 z-q&)hd&gN(^gev_K|UgHz(u)u+=CNo)flf(W`W zy}K^%!23trB~o8}iCr{i?1EK5rJRTbD7fU*A1sLvmUJ-=+-_p6Hr1Q6Ns>E}>%#1y z!!!wK0(==Y?BUTppj7KDrOl>VW>$_$WAvz^>z4GvJoj(%{C~i$WO3fGbZLz{#(9kNBt z4L$4uZMxtAZGVg4Mya<+j)IX$u9+36#H3KxwQ3m;%|zpL?2=a;B{&;tSt{4CD6i4m zT6VlNYZCdrG~X<#Z0{iiJXl2{5*_S(2ZGh~(UzuLfrGf=A}Nbxtfh1