File size: 5,126 Bytes
70830d6
683d749
70830d6
 
 
 
 
 
d2c1e33
70830d6
 
 
 
683d749
 
 
70830d6
 
683d749
 
 
 
70830d6
 
 
 
 
 
 
 
683d749
 
 
70830d6
 
 
 
 
 
 
 
 
683d749
 
 
70830d6
 
 
 
 
 
 
 
 
 
683d749
 
 
70830d6
 
 
 
 
 
683d749
 
 
70830d6
 
 
683d749
 
 
70830d6
 
 
 
 
 
 
 
 
 
 
683d749
 
 
d2c1e33
70830d6
 
d2c1e33
70830d6
 
 
683d749
 
 
 
70830d6
 
 
 
 
683d749
 
 
 
70830d6
 
683d749
 
 
 
70830d6
 
683d749
 
 
 
 
70830d6
 
 
 
 
 
 
 
683d749
 
 
 
 
 
 
70830d6
 
 
 
 
683d749
 
 
 
 
70830d6
 
 
 
 
 
683d749
 
 
 
70830d6
 
 
 
 
 
 
 
 
 
 
683d749
 
 
 
70830d6
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
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