-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgame.py
More file actions
236 lines (193 loc) · 8.47 KB
/
game.py
File metadata and controls
236 lines (193 loc) · 8.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
"""Beta-Go-Zero: AI for playing Go built with python
Author:
Henry "TJ" Chen
Original project by:
Henry "TJ" Chen, Dmitrii Vlasov, Ming Yau (Oscar) Lam, Duain Chhabra
Version: 1.3
Module Description
==================
This module contains a python class that represents an entire game of go.
It also contains additional runners which can be used to easily test our
work.
See README file for instructions, project details, and the relevant copyright and usage information
"""
from board import Board
from typing import Optional
class Game:
"""A class representing the state of a game of Go.
Instance Attributes:
- board: representation of the current state of the board
- current_player: who's turn is it, either "Black" or "White
- moves: a list that represents the sequence of moves played so far in the game
- board_size: the size of the board. Note that the board is always a square
- black_captured: the amount of stones captured BY BLACK so far
- white_captured: the amount of stones captured BY WHITE so far
Representation Invariants:
- self.current_player in {'Black','White'}
- self.board_size > 0
- self.black_captured>=0
- self.white_captured>=0
"""
board: Board
current_player: str
# note that the tuple is in form (move number starting from 1, x, y)
# x and y defined such that origin is centered in the top left corner
moves: list[tuple[int, int, int]]
board_size: int
black_captured: int
white_captured: int
def __init__(self, active_board: Optional[Board] = None, player_turn: str = "Black",
move_sequence: Optional[list[tuple[int, int, int]]] = None, size: int = 9) -> None:
"""
Initialise a new Go game - defaults to a 9x9 empty board
Note: board sizes are typically 9x9, 13x13, or 19x19 (19x19 is by far the most common of them all)
Preconditions:
- moves on the active board match those of the given move sequence
- all((0 <= move[1] <= size) and (0 <= move[2] <= size) for move in move_sequence)
- size >= 9
- player_turn in {'Black','White'}
"""
if active_board is None:
self.board = Board(size=size) # initialise a new board with the give size
else:
self.board = active_board
self.current_player = player_turn
if move_sequence is None:
self.moves = []
else:
self.moves = move_sequence
self.board_size = size
self.white_captured = 0
self.black_captured = 0
def play_move(self, x: int, y: int) -> bool:
"""Plays the given move on the board
Given the location of a new move, mutates the board and game.
Returns whether updating was sucessful or not
NOTE: recall that the x and y defined with the origin centered in the top left corner
Preconditions:
- 0 <= x < self.board.size
- 0 <= y < self.board.size
"""
stone = self.board.get_stone(x, y)
if stone.color == "Neither":
self.board.add_stone(x, y, self.current_player)
# moves start at 1 and increase by one each time
new_move = (len(self.moves) + 1, x, y)
self.moves.append(new_move)
for adjacent in stone.neighbours.values():
if adjacent is stone:
pass
elif adjacent.check_is_dead(set()):
color = adjacent.color
# remember that the attribute keeps track of amount captured BY player
if color == "White":
self.black_captured += self.board.capture_stones(adjacent)
else:
self.white_captured += self.board.capture_stones(adjacent)
# update current player attribute
self.current_player = "White" if self.current_player == "Black" else "Black"
return True
else:
return False
def add_sequence(self, moves_sequence: list[tuple[int, int]]) -> None:
"""Function for testing the ouputting of a final board state
Given a move sequence, it adds each move to the board
Preconditions:
- every move in move_seqeunce is a valid move
"""
for move in moves_sequence:
x, y = move
self.play_move(x, y)
print(f"Playing {self.current_player}'s move at ({x}, {y})")
def played_moves(self) -> list[tuple[int, int]]:
"""Return a list of the moves that have been played so far in the game"""
new_moves = []
for _, x, y in self.moves:
new_moves.append((x, y))
return new_moves
def available_moves(self) -> list[tuple[int, int]]:
"""Return a list of the moves that are available to be played
Uses the check valid moves function under the board class
"""
available_moves = []
for x in range(self.board.size):
for y in range(self.board.size):
# notice that it uses the board to check valid moves
if self.board.is_valid_move(x, y, self.current_player):
available_moves.append((x, y))
return available_moves
def is_game_over(self) -> bool:
"""Checks if the game is over if both players have passed their turns it returns True,
otherwise False add a check for if the board is full and check its fucntionality
"""
if self.played_moves()[-1] == (-1, -1) and len(self.moves) >= 2 and self.played_moves()[-1] == (-1, -1):
return True
else:
return False
def last_turn_num(self) -> int:
"""Returns the turn number of the last move played"""
return len(self.moves)
def pass_turn(self) -> None:
"""Allows a player to pass their turn."""
self.current_player = "White" if self.current_player == "Black" else "Black"
self.moves.append((self.last_turn_num() + 1, -1, -1))
def game_end(self, max_moves: int) -> bool:
"""
Checks if the game has ended
Preconditions:
- max_moves > 0
"""
if len(self.moves) == max_moves:
return True
elif not self.available_moves():
return True
else:
return False
def overall_score(self, technique: str = "dfs") -> tuple[float, float]:
"""
Returns the overall score of the game
Preconditions:
- technique is a valid technique
"""
total_white, total_black = 2.5, 0
territory_score = self.board.calculate_score(technique)
total_black += len(territory_score[0]) + self.black_captured
total_white += len(territory_score[1]) + self.white_captured
return total_white, total_black
def iswinner(self, player: str) -> bool:
"""Returns True if the given player is the winner of the game, False otherwise
Preconditions:
- player in {'White', 'Black'}
"""
if self.overall_score()[0] > self.overall_score()[1]:
return player == "White"
else:
return player == "Black"
def get_move_sequence(self) -> list[tuple[int, int, int]]:
"""Returns the move sequence of the game"""
return self.moves
def get_move_info(self, x: int, y: int) -> tuple[int, str]:
"""Returns the turn number and player color based on the given coordinates
"""
for turn, move_x, move_y in self.moves:
if move_x == x and move_y == y:
player_color = "Black" if turn % 2 == 1 else "White"
return turn, player_color
return -1, "Neither"
################################################################################
# functions for testing purposes
################################################################################
def add_move_using_color(self, color: str, x: int, y: int) -> None:
"""Adds a move to the game using the given color and coordinates
For TESTING - DO NOT USE as it does not properly mutate the game/board
Preconditions:
- color in {'White', 'Black'}
"""
self.board.add_stone(x, y, color)
# if __name__ == "__main__":
# game = Game()
# moves = [(0, 0), (1, 1), (0, 1), (1, 0), (0, 2), (2, 2)]
# game.add_sequence(moves)
# from pygame_go import draw_board
# draw_board(game.board, open_in_browser=True)
# import python_ta