| from arena.board_view import to_svg |
| from typing import List |
|
|
| RED = 1 |
| YELLOW = -1 |
| EMPTY = 0 |
| show = {EMPTY: "⚪️", RED: "🔴", YELLOW: "🟡"} |
| pieces = {EMPTY: "", RED: "red", YELLOW: "yellow"} |
| simple = {EMPTY: "_", RED: "R", YELLOW: "Y"} |
| cols = "ABCDEFG" |
|
|
|
|
| class Board: |
| """ |
| A class to represent a Four-in-the-row Board |
| """ |
|
|
| def __init__(self): |
| """ |
| Initialize this instance, starting with empty cells, RED to play |
| The latest x,y is used to track the most recent move, so it animates on the display |
| """ |
| self.cells = [[0 for _ in range(7)] for _ in range(6)] |
| self.player = RED |
| self.winner = EMPTY |
| self.draw = False |
| self.forfeit = False |
| self.latest_x, self.latest_y = -1, -1 |
|
|
| def __repr__(self): |
| """ |
| A visual representation |
| """ |
| result = "" |
| for y in range(6): |
| for x in range(7): |
| result += show[self.cells[5 - y][x]] |
| result += "\n" |
| result += "\n" + self.message() |
| return result |
|
|
| def message(self): |
| """ |
| A summary of the status |
| """ |
| if self.winner and self.forfeit: |
| return f"{show[self.winner]} wins after an illegal move by {show[-1*self.winner]}\n" |
| elif self.winner: |
| return f"{show[self.winner]} wins\n" |
| elif self.draw: |
| return "The game is a draw\n" |
| else: |
| return f"{show[self.player]} to play\n" |
|
|
| def html(self): |
| """ |
| Return an HTML representation |
| """ |
| result = '<div style="text-align: center;font-size:24px">' |
| result += self.__repr__().replace("\n", "<br/>") |
| result += "</div>" |
| return result |
|
|
| def svg(self): |
| """ |
| Return an SVG representation |
| """ |
| return to_svg(self) |
|
|
| def json(self): |
| """ |
| Return a json representation |
| """ |
| result = "{\n" |
| result += ' "Column names": ["A", "B", "C", "D", "E", "F", "G"],\n' |
| for y in range(6): |
| result += f' "Row {6-y}": [' |
| for x in range(7): |
| result += f'"{pieces[self.cells[5-y][x]]}", ' |
| result = result[:-2] + "],\n" |
| result = result[:-2] + "\n}" |
| return result |
|
|
| def alternative(self): |
| """ |
| An alternative representation, used in prompting so that the LLM sees this 2 ways |
| """ |
| result = " A B C D E F G\n" |
| for y in range(6): |
| for x in range(7): |
| result += " " + simple[self.cells[5 - y][x]] |
| result += "\n" |
| return result |
|
|
| def height(self, x: int) -> int: |
| """ |
| Return the height of the given column |
| """ |
| height = 0 |
| while height < 6 and self.cells[height][x] != EMPTY: |
| height += 1 |
| return height |
|
|
| def legal_moves(self) -> List[str]: |
| """ |
| Return the names of columns that are not full |
| """ |
| return [cols[x] for x in range(7) if self.height(x) < 6] |
|
|
| def illegal_moves(self) -> List[str]: |
| """ |
| Return the names of columns that are full |
| """ |
| return [cols[x] for x in range(7) if self.height(x) == 6] |
|
|
| def winning_line(self, x: int, y: int, dx: int, dy: int) -> int: |
| """ |
| Return RED or YELLOW if this cell is the start of a 4 in the row going in the direction dx, dy |
| Or EMPTY if not |
| """ |
| color = self.cells[y][x] |
| for pointer in range(1, 4): |
| xp = x + dx * pointer |
| yp = y + dy * pointer |
| if not (0 <= xp <= 6 and 0 <= yp <= 5) or self.cells[yp][xp] != color: |
| return EMPTY |
| return color |
|
|
| def winning_cell(self, x: int, y: int) -> int: |
| """ |
| Return RED or YELLOW if this cell is the start of a 4 in the row |
| Or EMPTY if not |
| For performance reasons, only look in 4 of the possible 8 directions, |
| (because this test will run on both sides of the 4-in-a-row) |
| """ |
| for dx, dy in ((0, 1), (1, 1), (1, 0), (1, -1)): |
| if winner := self.winning_line(x, y, dx, dy): |
| return winner |
| return EMPTY |
|
|
| def wins(self) -> int: |
| """ |
| Return RED or YELLOW if there is a 4-in-a-row of that color on the board |
| Or EMPTY if not |
| """ |
| for y in range(6): |
| for x in range(7): |
| if winner := self.winning_cell(x, y): |
| return winner |
| return EMPTY |
|
|
| def move(self, x: int): |
| """ |
| Make a move in the given column |
| """ |
| y = self.height(x) |
| self.cells[y][x] = self.player |
| self.latest_x, self.latest_y = x, y |
| if winner := self.wins(): |
| self.winner = winner |
| elif not self.legal_moves: |
| self.draw = True |
| else: |
| self.player = -1 * self.player |
| return self |
|
|
| def is_active(self) -> bool: |
| """ |
| Return true if the game has not yet ended |
| """ |
| return not self.winner and not self.draw |
|
|