absfuyu 3.2.0__py3-none-any.whl → 3.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +3 -10
- absfuyu/__main__.py +5 -250
- absfuyu/cli/__init__.py +51 -0
- absfuyu/cli/color.py +24 -0
- absfuyu/cli/config_group.py +56 -0
- absfuyu/cli/do_group.py +98 -0
- absfuyu/cli/game_group.py +109 -0
- absfuyu/config/__init__.py +55 -94
- absfuyu/config/config.json +0 -7
- absfuyu/core.py +5 -66
- absfuyu/everything.py +7 -9
- absfuyu/extensions/beautiful.py +30 -23
- absfuyu/extensions/dev/__init__.py +11 -8
- absfuyu/extensions/dev/password_hash.py +4 -2
- absfuyu/extensions/dev/passwordlib.py +7 -5
- absfuyu/extensions/dev/project_starter.py +4 -2
- absfuyu/extensions/dev/shutdownizer.py +148 -0
- absfuyu/extensions/extra/__init__.py +1 -2
- absfuyu/extensions/extra/data_analysis.py +110 -58
- absfuyu/fun/WGS.py +50 -26
- absfuyu/fun/__init__.py +6 -7
- absfuyu/fun/tarot.py +1 -1
- absfuyu/game/__init__.py +75 -81
- absfuyu/game/game_stat.py +36 -0
- absfuyu/game/sudoku.py +41 -48
- absfuyu/game/tictactoe.py +303 -548
- absfuyu/game/wordle.py +56 -47
- absfuyu/general/__init__.py +17 -7
- absfuyu/general/content.py +16 -15
- absfuyu/general/data_extension.py +314 -109
- absfuyu/general/generator.py +67 -67
- absfuyu/general/human.py +148 -78
- absfuyu/logger.py +94 -68
- absfuyu/pkg_data/__init__.py +29 -25
- absfuyu/py.typed +0 -0
- absfuyu/sort.py +61 -47
- absfuyu/tools/__init__.py +0 -1
- absfuyu/tools/converter.py +80 -62
- absfuyu/tools/keygen.py +62 -67
- absfuyu/tools/obfuscator.py +57 -53
- absfuyu/tools/stats.py +24 -24
- absfuyu/tools/web.py +10 -9
- absfuyu/util/__init__.py +38 -40
- absfuyu/util/api.py +53 -43
- absfuyu/util/json_method.py +25 -27
- absfuyu/util/lunar.py +20 -24
- absfuyu/util/path.py +362 -241
- absfuyu/util/performance.py +36 -98
- absfuyu/util/pkl.py +8 -8
- absfuyu/util/zipped.py +17 -19
- absfuyu/version.py +137 -148
- absfuyu-3.4.0.dist-info/METADATA +124 -0
- absfuyu-3.4.0.dist-info/RECORD +59 -0
- {absfuyu-3.2.0.dist-info → absfuyu-3.4.0.dist-info}/WHEEL +1 -2
- {absfuyu-3.2.0.dist-info → absfuyu-3.4.0.dist-info}/entry_points.txt +1 -0
- {absfuyu-3.2.0.dist-info → absfuyu-3.4.0.dist-info/licenses}/LICENSE +1 -1
- absfuyu/extensions/dev/pkglib.py +0 -98
- absfuyu/game/tictactoe2.py +0 -318
- absfuyu-3.2.0.dist-info/METADATA +0 -216
- absfuyu-3.2.0.dist-info/RECORD +0 -55
- absfuyu-3.2.0.dist-info/top_level.txt +0 -1
absfuyu/game/tictactoe.py
CHANGED
|
@@ -2,560 +2,315 @@
|
|
|
2
2
|
Game: Tic Tac Toe
|
|
3
3
|
-----------------
|
|
4
4
|
|
|
5
|
-
Version:
|
|
6
|
-
Date
|
|
5
|
+
Version: 2.0.3
|
|
6
|
+
Date updated: 05/04/2024 (mm/dd/yyyy)
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
+
__all__ = ["TicTacToe", "GameMode"]
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
import
|
|
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
|
-
# return
|
|
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
|
-
print(f"{'+---'*length}+")
|
|
165
|
-
for row in range(nrow):
|
|
166
|
-
for col in range(ncol):
|
|
167
|
-
print(f"| {table[row][col]} ", end="")
|
|
168
|
-
print(f"|\n{'+---'*length}+")
|
|
169
|
-
|
|
170
|
-
def __win_hightlight(table):
|
|
171
|
-
"""
|
|
172
|
-
Hight light the win by removing other placed key
|
|
173
|
-
|
|
174
|
-
Parameter:
|
|
175
|
-
---
|
|
176
|
-
table : numpy.ndarray | list[list[str]]
|
|
177
|
-
Game board
|
|
178
|
-
"""
|
|
179
|
-
|
|
180
|
-
# Get detailed information
|
|
181
|
-
detail = __check_state(table)
|
|
182
|
-
loc = detail["location"]
|
|
183
|
-
loc_line = detail["pos"]
|
|
184
|
-
|
|
185
|
-
# Make new board
|
|
186
|
-
board = __gen_board(len(table), len(table[0]))
|
|
187
|
-
|
|
188
|
-
# Fill in the hightlighted content
|
|
189
|
-
if loc.startswith("col"):
|
|
190
|
-
for i in range(len(board)):
|
|
191
|
-
board[i][loc_line] = detail['key']
|
|
192
|
-
elif loc.startswith("row"):
|
|
193
|
-
for i in range(len(board)):
|
|
194
|
-
board[loc_line][i] = detail['key']
|
|
195
|
-
else:
|
|
196
|
-
if loc_line == 1:
|
|
11
|
+
|
|
12
|
+
import random
|
|
13
|
+
import time
|
|
14
|
+
from typing import List, Literal, NamedTuple
|
|
15
|
+
|
|
16
|
+
from absfuyu.core import CLITextColor
|
|
17
|
+
|
|
18
|
+
BoardGame = List[List[str]]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Pos(NamedTuple):
|
|
22
|
+
"""Position"""
|
|
23
|
+
|
|
24
|
+
row: int
|
|
25
|
+
col: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class GameMode:
|
|
29
|
+
ONE_V_ONE = "1v1"
|
|
30
|
+
ONE_V_BOT = "1v0"
|
|
31
|
+
BOT_V_BOT = "0v0"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class GameStateResult(NamedTuple):
|
|
35
|
+
key: str
|
|
36
|
+
location: Literal["col", "row", "diag", "blank"]
|
|
37
|
+
pos: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TicTacToe:
|
|
41
|
+
"""Tic Tac Toe game"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
game_size: int = 3,
|
|
46
|
+
*,
|
|
47
|
+
x: str = "X",
|
|
48
|
+
o: str = "O",
|
|
49
|
+
blank: str = " ",
|
|
50
|
+
position_split_symbol: str = ",",
|
|
51
|
+
end_break_word: str = "END",
|
|
52
|
+
welcome_message: bool = True,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
:param game_size: Board size (Default: 3x3)
|
|
56
|
+
:param x: X symbol
|
|
57
|
+
:param o: O symbol
|
|
58
|
+
:param blank: Blank symbol
|
|
59
|
+
:param position_split_symbol: Position split symbol
|
|
60
|
+
:param end_break_word: End break word
|
|
61
|
+
:param welcome_message: Show welcome message (Default: `True`)
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# Board size
|
|
65
|
+
self.row_size = game_size
|
|
66
|
+
self.col_size = game_size
|
|
67
|
+
|
|
68
|
+
# Game setting
|
|
69
|
+
self.X = x
|
|
70
|
+
self.O = o # noqa: E741
|
|
71
|
+
self.BLANK = blank
|
|
72
|
+
self.POS_SPLIT = position_split_symbol
|
|
73
|
+
self.END_BREAK = end_break_word
|
|
74
|
+
self.welcome_message = welcome_message
|
|
75
|
+
|
|
76
|
+
# Init board
|
|
77
|
+
self.board = self._gen_board()
|
|
78
|
+
|
|
79
|
+
def __str__(self) -> str:
|
|
80
|
+
return f"{self.__class__.__name__}(game_size={self.row_size})"
|
|
81
|
+
|
|
82
|
+
def __repr__(self) -> str:
|
|
83
|
+
return self.__str__()
|
|
84
|
+
|
|
85
|
+
# Game
|
|
86
|
+
def _gen_board(self) -> BoardGame:
|
|
87
|
+
"""
|
|
88
|
+
Generate board game
|
|
89
|
+
"""
|
|
90
|
+
board = [
|
|
91
|
+
[self.BLANK for _ in range(self.row_size)] for _ in range(self.col_size)
|
|
92
|
+
]
|
|
93
|
+
return board
|
|
94
|
+
|
|
95
|
+
def _check_state(self) -> GameStateResult:
|
|
96
|
+
"""
|
|
97
|
+
Check game winning state
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
dict[str, str | int]
|
|
102
|
+
``X`` | ``O`` | ``BLANK``
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
# Check rows
|
|
106
|
+
for row in range(self.row_size):
|
|
107
|
+
if len(set(self.board[row])) == 1:
|
|
108
|
+
key = list(set(self.board[row]))[0]
|
|
109
|
+
return GameStateResult(key, "row", row)
|
|
110
|
+
# return {"key": key, "location": "row", "pos": row} # modified
|
|
111
|
+
|
|
112
|
+
# Check cols
|
|
113
|
+
for col in range(self.col_size):
|
|
114
|
+
temp = [self.board[row][col] for row in range(self.row_size)]
|
|
115
|
+
if len(set(temp)) == 1:
|
|
116
|
+
key = list(set(temp))[0]
|
|
117
|
+
return GameStateResult(key, "col", col)
|
|
118
|
+
# return {"key": key, "location": "col", "pos": col} # modified
|
|
119
|
+
|
|
120
|
+
# Check diagonal
|
|
121
|
+
diag1 = [self.board[i][i] for i in range(len(self.board))]
|
|
122
|
+
if len(set(diag1)) == 1:
|
|
123
|
+
key = list(set(diag1))[0]
|
|
124
|
+
return GameStateResult(key, "diag", 1)
|
|
125
|
+
# return {"key": key, "location": "diag", "pos": 1} # modified
|
|
126
|
+
|
|
127
|
+
diag2 = [self.board[i][len(self.board) - i - 1] for i in range(len(self.board))]
|
|
128
|
+
if len(set(diag2)) == 1:
|
|
129
|
+
key = list(set(diag2))[0]
|
|
130
|
+
return GameStateResult(key, "diag", 2)
|
|
131
|
+
# return {"key": key, "location": "diag", "pos": 2} # modified
|
|
132
|
+
|
|
133
|
+
# Else
|
|
134
|
+
return GameStateResult(self.BLANK, "blank", 0)
|
|
135
|
+
# return {"key": self.BLANK}
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _print_board(board: BoardGame) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Print Tic Tac Toe board
|
|
141
|
+
"""
|
|
142
|
+
nrow, ncol = len(board), len(board[0])
|
|
143
|
+
length = len(board)
|
|
144
|
+
print(f"{'+---'*length}+")
|
|
145
|
+
for row in range(nrow):
|
|
146
|
+
for col in range(ncol):
|
|
147
|
+
print(f"| {board[row][col]} ", end="")
|
|
148
|
+
print(f"|\n{'+---'*length}+")
|
|
149
|
+
|
|
150
|
+
def _win_hightlight(self) -> BoardGame:
|
|
151
|
+
"""
|
|
152
|
+
Hight light the win by removing other placed key
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
# Get detailed information
|
|
156
|
+
detail = self._check_state()
|
|
157
|
+
loc = detail.location
|
|
158
|
+
loc_line = detail.pos
|
|
159
|
+
|
|
160
|
+
# Make new board
|
|
161
|
+
board = self._gen_board()
|
|
162
|
+
|
|
163
|
+
# Fill in the hightlighted content
|
|
164
|
+
if loc.startswith("col"):
|
|
197
165
|
for i in range(len(board)):
|
|
198
|
-
board[i][
|
|
199
|
-
|
|
166
|
+
board[i][loc_line] = detail.key
|
|
167
|
+
elif loc.startswith("row"):
|
|
200
168
|
for i in range(len(board)):
|
|
201
|
-
board[
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
# Remove value outside of board
|
|
245
|
-
# output = []
|
|
246
|
-
# for x in surround_move:
|
|
247
|
-
# condition = [
|
|
248
|
-
# x[0] < 0 or x[1] < 0,
|
|
249
|
-
# x[0] >= len(table) or x[1] >= len(table)
|
|
250
|
-
# ]
|
|
251
|
-
# if any(condition):
|
|
252
|
-
# continue
|
|
253
|
-
# output.append(x)
|
|
254
|
-
# return output
|
|
255
|
-
for k, v in surround_move.items():
|
|
256
|
-
condition = [
|
|
257
|
-
v[0] < 0 or v[1] < 0,
|
|
258
|
-
v[0] >= len(table) or v[1] >= len(table)
|
|
259
|
-
]
|
|
260
|
-
if any(condition):
|
|
261
|
-
surround_move.pop(k)
|
|
262
|
-
continue
|
|
263
|
-
# output.append(x)
|
|
264
|
-
return surround_move
|
|
265
|
-
|
|
266
|
-
def __generate_random_move(table):
|
|
267
|
-
"""
|
|
268
|
-
Generate a random move from board game
|
|
269
|
-
"""
|
|
270
|
-
while True:
|
|
271
|
-
output = [
|
|
272
|
-
__random.randint(0, len(table)-1),
|
|
273
|
-
__random.randint(0, len(table)-1)
|
|
274
|
-
]
|
|
275
|
-
if __is_blank(table, output):
|
|
276
|
-
break
|
|
277
|
-
return __convert_bot_output(output)
|
|
278
|
-
|
|
279
|
-
def __bot_move(table, pos: __List[int] = None, debug: bool = False):
|
|
280
|
-
"""
|
|
281
|
-
Calculate position to place for BOT
|
|
282
|
-
|
|
283
|
-
Parameters:
|
|
284
|
-
---
|
|
285
|
-
table : numpy.ndarray | list[list[str]]
|
|
286
|
-
Game board
|
|
287
|
-
|
|
288
|
-
pos : list[int]
|
|
289
|
-
previous move
|
|
290
|
-
"""
|
|
291
|
-
|
|
292
|
-
if debug:
|
|
293
|
-
print("Init Data:")
|
|
294
|
-
print(table)
|
|
295
|
-
print(pos)
|
|
296
|
-
# print(opponent)
|
|
297
|
-
|
|
298
|
-
if pos is None:
|
|
299
|
-
return __generate_random_move(table)
|
|
300
|
-
|
|
301
|
-
# Smart move: position that around previous game move
|
|
302
|
-
valid_move = __generate_surround_move(table, pos)
|
|
303
|
-
valid_move = list(valid_move.values())
|
|
304
|
-
if debug:
|
|
305
|
-
print("Smart move:")
|
|
306
|
-
print(valid_move)
|
|
307
|
-
|
|
308
|
-
# Filtered smart move: take only the placable position
|
|
309
|
-
filtered_move = []
|
|
310
|
-
for x in valid_move:
|
|
311
|
-
if __is_blank(table, x):
|
|
312
|
-
filtered_move.append(x)
|
|
313
|
-
# else:
|
|
314
|
-
# opponent = table[pos[0], pos[1]]
|
|
315
|
-
# if table[x[0], x[1]] == opponent:
|
|
316
|
-
# temp = __generate_surround_move(table, x)
|
|
317
|
-
# for v in temp:
|
|
318
|
-
# if __is_blank(table, v):
|
|
319
|
-
# filtered_move.append(v)
|
|
320
|
-
# print(temp)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if debug:
|
|
324
|
-
print("Filtered move:")
|
|
325
|
-
print(filtered_move)
|
|
326
|
-
|
|
327
|
-
if not filtered_move:
|
|
328
|
-
# If invalid then return a random move
|
|
329
|
-
return __generate_random_move(table)
|
|
330
|
-
|
|
331
|
-
# else
|
|
332
|
-
while True:
|
|
333
|
-
rand_move = __random.choice(filtered_move)
|
|
334
|
-
if any([
|
|
335
|
-
rand_move[0] < 0,
|
|
336
|
-
rand_move[0] >= len(table),
|
|
337
|
-
rand_move[1] < 0,
|
|
338
|
-
rand_move[1] >= len(table)
|
|
339
|
-
]):
|
|
340
|
-
continue
|
|
341
|
-
|
|
342
|
-
if __is_blank(table, rand_move):
|
|
343
|
-
rand_move = __convert_bot_output(rand_move)
|
|
344
|
-
if debug:
|
|
345
|
-
print("Chosen smart move:")
|
|
346
|
-
print(rand_move)
|
|
347
|
-
break
|
|
348
|
-
|
|
349
|
-
# print(rand_move)
|
|
350
|
-
return rand_move
|
|
351
|
-
|
|
352
|
-
def bot_ttt(table, pos: __List[int] = None):
|
|
353
|
-
"""Smart bot i guess"""
|
|
354
|
-
free_slots = []
|
|
355
|
-
for i in range(len(table)):
|
|
356
|
-
for j in range(len(table[0])):
|
|
357
|
-
if table[i][j] == BLANK:
|
|
358
|
-
free_slots.append([i, j])
|
|
359
|
-
if not free_slots:
|
|
360
|
-
return None
|
|
361
|
-
|
|
362
|
-
if pos is not None:
|
|
363
|
-
sur = __generate_surround_move(table, pos)
|
|
364
|
-
print(sur)
|
|
365
|
-
|
|
366
|
-
counter = []
|
|
367
|
-
for k, v in sur.items():
|
|
368
|
-
if table[v[0]][v[1]] == table[pos[0]][pos[1]]:
|
|
369
|
-
try:
|
|
370
|
-
if k == "R": counter.append(sur["L"])
|
|
371
|
-
except: pass
|
|
372
|
-
try:
|
|
373
|
-
if k == "L": counter.append(sur["R"])
|
|
374
|
-
except: pass
|
|
375
|
-
try:
|
|
376
|
-
if k == "U": counter.append(sur["D"])
|
|
377
|
-
except: pass
|
|
378
|
-
try:
|
|
379
|
-
if k == "D": counter.append(sur["U"])
|
|
380
|
-
except: pass
|
|
381
|
-
try:
|
|
382
|
-
if k == "UL": counter.append(sur["DR"])
|
|
383
|
-
except: pass
|
|
384
|
-
try:
|
|
385
|
-
if k == "UR": counter.append(sur["DL"])
|
|
386
|
-
except: pass
|
|
387
|
-
try:
|
|
388
|
-
if k == "DL": counter.append(sur["UR"])
|
|
389
|
-
except: pass
|
|
390
|
-
try:
|
|
391
|
-
if k == "DR": counter.append(sur["UL"])
|
|
392
|
-
except: pass
|
|
393
|
-
|
|
394
|
-
if not counter:
|
|
395
|
-
pass
|
|
396
|
-
|
|
397
|
-
return free_slots
|
|
398
|
-
|
|
399
|
-
def game_tictactoe(
|
|
400
|
-
size: int = 3,
|
|
401
|
-
mode: str = GameMode.ONE_V_BOT,
|
|
402
|
-
smarter_bot: bool = False,
|
|
403
|
-
board_game: __Optional[bool] = True,
|
|
169
|
+
board[loc_line][i] = detail.key
|
|
170
|
+
else:
|
|
171
|
+
if loc_line == 1:
|
|
172
|
+
for i in range(len(board)):
|
|
173
|
+
board[i][i] = detail.key
|
|
174
|
+
else:
|
|
175
|
+
for i in range(len(board)):
|
|
176
|
+
board[i][len(board) - i - 1] = detail.key
|
|
177
|
+
|
|
178
|
+
# Output
|
|
179
|
+
return board
|
|
180
|
+
|
|
181
|
+
def _is_blank(self, pos: Pos) -> bool:
|
|
182
|
+
"""Check if current pos is filled"""
|
|
183
|
+
return self.board[pos.row][pos.col] == self.BLANK
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def _convert_bot_output(pos: Pos) -> Pos:
|
|
187
|
+
"""
|
|
188
|
+
Turn to real pos by:
|
|
189
|
+
|
|
190
|
+
- +1 to ``row`` and ``col``
|
|
191
|
+
- convert into ``str``
|
|
192
|
+
"""
|
|
193
|
+
return Pos(pos.row + 1, pos.col + 1)
|
|
194
|
+
|
|
195
|
+
def _generate_random_move(self) -> Pos:
|
|
196
|
+
"""
|
|
197
|
+
Generate a random move from board game
|
|
198
|
+
"""
|
|
199
|
+
while True:
|
|
200
|
+
output = Pos(
|
|
201
|
+
random.randint(0, len(self.board) - 1),
|
|
202
|
+
random.randint(0, len(self.board) - 1),
|
|
203
|
+
)
|
|
204
|
+
if self._is_blank(output):
|
|
205
|
+
break
|
|
206
|
+
return self._convert_bot_output(output)
|
|
207
|
+
|
|
208
|
+
def play(
|
|
209
|
+
self,
|
|
210
|
+
game_mode: str = GameMode.ONE_V_BOT,
|
|
211
|
+
*,
|
|
404
212
|
bot_time: float = 0,
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
"
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
{
|
|
460
|
-
|
|
461
|
-
print("Tic Tac Toe")
|
|
462
|
-
|
|
463
|
-
# Check gamemode
|
|
464
|
-
game_mode = [
|
|
465
|
-
"1v1", # Player vs player
|
|
466
|
-
"1v0", # Player vs BOT
|
|
467
|
-
"0v0" # BOT vs BOT
|
|
468
|
-
]
|
|
469
|
-
if mode not in game_mode:
|
|
470
|
-
mode = game_mode[1] # Force vs BOT
|
|
471
|
-
if mode.startswith("1v0"):
|
|
472
|
-
BOT = True
|
|
473
|
-
if mode.startswith("0v0"):
|
|
474
|
-
BOT = True
|
|
475
|
-
BOT2 = True
|
|
476
|
-
|
|
477
|
-
# Game
|
|
478
|
-
if board_game:
|
|
479
|
-
__print_board(board)
|
|
480
|
-
elif board_game is None:
|
|
481
|
-
pass
|
|
482
|
-
else:
|
|
483
|
-
print(board)
|
|
484
|
-
|
|
485
|
-
place_pos = None
|
|
486
|
-
while state == BLANK and filled < size**2:
|
|
487
|
-
if board_game is not None:
|
|
488
|
-
print(f"{__C['BLUE']}{current_player}'s turn:{__C['reset']}")
|
|
489
|
-
|
|
490
|
-
try: # Error handling
|
|
491
|
-
if (BOT and current_player == O) or BOT2:
|
|
492
|
-
if smarter_bot:
|
|
493
|
-
move = __bot_move(board, place_pos)
|
|
213
|
+
) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Play a game of Tic Tac Toe
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
game_mode : str
|
|
220
|
+
Game mode
|
|
221
|
+
|
|
222
|
+
bot_time : float
|
|
223
|
+
Time sleep between each bot move (Default: ``0``)
|
|
224
|
+
"""
|
|
225
|
+
# Init game
|
|
226
|
+
filled = 0
|
|
227
|
+
current_player = self.X
|
|
228
|
+
state = self._check_state().key
|
|
229
|
+
BOT = False
|
|
230
|
+
BOT2 = False
|
|
231
|
+
|
|
232
|
+
# Welcome message
|
|
233
|
+
if self.welcome_message:
|
|
234
|
+
print(
|
|
235
|
+
f"""\
|
|
236
|
+
{CLITextColor.GREEN}Welcome to Tic Tac Toe!
|
|
237
|
+
|
|
238
|
+
{CLITextColor.YELLOW}Rules: Match lines vertically, horizontally or diagonally
|
|
239
|
+
{CLITextColor.YELLOW}{self.X} goes first, then {self.O}
|
|
240
|
+
{CLITextColor.RED}Type '{self.END_BREAK}' to end the game{CLITextColor.RESET}"""
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
# Check gamemode
|
|
244
|
+
_game_mode = [
|
|
245
|
+
"1v1", # Player vs player
|
|
246
|
+
"1v0", # Player vs BOT
|
|
247
|
+
"0v0", # BOT vs BOT
|
|
248
|
+
]
|
|
249
|
+
if game_mode not in _game_mode:
|
|
250
|
+
game_mode = _game_mode[1] # Force vs BOT
|
|
251
|
+
if game_mode.startswith(GameMode.ONE_V_BOT):
|
|
252
|
+
BOT = True
|
|
253
|
+
if game_mode.startswith(GameMode.BOT_V_BOT):
|
|
254
|
+
BOT = True
|
|
255
|
+
BOT2 = True
|
|
256
|
+
|
|
257
|
+
# Game
|
|
258
|
+
self._print_board(self.board)
|
|
259
|
+
|
|
260
|
+
place_pos = None
|
|
261
|
+
while state == self.BLANK and filled < self.row_size**2:
|
|
262
|
+
print(f"{CLITextColor.BLUE}{current_player}'s turn:{CLITextColor.RESET}")
|
|
263
|
+
|
|
264
|
+
try: # Error handling
|
|
265
|
+
if (BOT and current_player == self.O) or BOT2:
|
|
266
|
+
move = self._generate_random_move()
|
|
267
|
+
str_move = f"{move.row}{self.POS_SPLIT}{move.col}"
|
|
268
|
+
move = str_move # type: ignore
|
|
494
269
|
else:
|
|
495
|
-
move =
|
|
496
|
-
|
|
497
|
-
|
|
270
|
+
move = input( # type: ignore
|
|
271
|
+
f"Place {CLITextColor.BLUE}{current_player}{CLITextColor.RESET} at {CLITextColor.BLUE}<row{self.POS_SPLIT}col>:{CLITextColor.RESET} "
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
if move.upper() == self.END_BREAK: # type: ignore # Failsafe
|
|
275
|
+
print(f"{CLITextColor.RED}Game ended{CLITextColor.RESET}")
|
|
276
|
+
break
|
|
277
|
+
|
|
278
|
+
move = move.split(self.POS_SPLIT) # type: ignore
|
|
279
|
+
row = int(move[0])
|
|
280
|
+
col = int(move[1])
|
|
281
|
+
place_pos = Pos(row - 1, col - 1)
|
|
282
|
+
|
|
283
|
+
if self._is_blank(place_pos):
|
|
284
|
+
self.board[place_pos.row][place_pos.col] = current_player
|
|
285
|
+
filled += 1
|
|
286
|
+
|
|
287
|
+
else: # User and BOT error
|
|
288
|
+
print(
|
|
289
|
+
f"{CLITextColor.RED}Invalid move, please try again{CLITextColor.RESET}"
|
|
290
|
+
)
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
except Exception: # User error
|
|
294
|
+
print(
|
|
295
|
+
f"{CLITextColor.RED}Invalid move, please try again{CLITextColor.RESET}"
|
|
296
|
+
)
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
state = self._check_state().key
|
|
300
|
+
self._print_board(self.board)
|
|
301
|
+
|
|
302
|
+
if state != self.BLANK:
|
|
303
|
+
print(f"{CLITextColor.GREEN}{state} WON!{CLITextColor.RESET}")
|
|
304
|
+
self._print_board(self._win_hightlight())
|
|
498
305
|
|
|
306
|
+
# Change turn
|
|
307
|
+
if BOT2: # BOT delay
|
|
308
|
+
time.sleep(bot_time)
|
|
309
|
+
|
|
310
|
+
if current_player == self.X:
|
|
311
|
+
current_player = self.O
|
|
499
312
|
else:
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
break
|
|
505
|
-
|
|
506
|
-
move = move.split(POS_SPLIT)
|
|
507
|
-
row = int(move[0])
|
|
508
|
-
col = int(move[1])
|
|
509
|
-
place_pos = [row-1, col-1]
|
|
510
|
-
|
|
511
|
-
if __is_blank(board, place_pos):
|
|
512
|
-
board[place_pos[0]][place_pos[1]] = current_player
|
|
513
|
-
filled += 1
|
|
514
|
-
|
|
515
|
-
else: # User and BOT error
|
|
516
|
-
if board_game is not None:
|
|
517
|
-
print(f"{__C['RED']}Invalid move, please try again{__C['reset']}")
|
|
518
|
-
continue
|
|
519
|
-
|
|
520
|
-
except: # User error
|
|
521
|
-
if board_game is not None:
|
|
522
|
-
print(f"{__C['RED']}Invalid move, please try again{__C['reset']}")
|
|
523
|
-
continue
|
|
524
|
-
|
|
525
|
-
state = __check_state(board)["key"]
|
|
526
|
-
if board_game:
|
|
527
|
-
__print_board(board)
|
|
528
|
-
elif board_game is None:
|
|
529
|
-
pass
|
|
530
|
-
else:
|
|
531
|
-
print(board)
|
|
532
|
-
|
|
533
|
-
# state = __check_state(board)
|
|
534
|
-
if state != BLANK:
|
|
535
|
-
print(f"{__C['GREEN']}{state} WON!{__C['reset']}")
|
|
536
|
-
if board_game:
|
|
537
|
-
__print_board(__win_hightlight(board))
|
|
538
|
-
break
|
|
539
|
-
|
|
540
|
-
# Change turn
|
|
541
|
-
if BOT2: # BOT delay
|
|
542
|
-
__time.sleep(bot_time)
|
|
543
|
-
|
|
544
|
-
if current_player == X:
|
|
545
|
-
current_player = O
|
|
546
|
-
else:
|
|
547
|
-
current_player = X
|
|
548
|
-
|
|
549
|
-
if state == BLANK and filled == size**2:
|
|
550
|
-
print(f"{__C['YELLOW']}Draw Match!{__C['reset']}")
|
|
551
|
-
|
|
552
|
-
# Game stats
|
|
553
|
-
game_stats = {
|
|
554
|
-
"Total move": filled,
|
|
555
|
-
"Win by": None if state==BLANK else state,
|
|
556
|
-
}
|
|
557
|
-
if show_stats:
|
|
558
|
-
print(f"{__C['BLUE']}GAME STATS:{__C['reset']}")
|
|
559
|
-
for k, v in game_stats.items():
|
|
560
|
-
print(f"{__C['YELLOW']}{k}: {v}{__C['reset']}")
|
|
561
|
-
return game_stats
|
|
313
|
+
current_player = self.X
|
|
314
|
+
|
|
315
|
+
if state == self.BLANK and filled == self.row_size**2:
|
|
316
|
+
print(f"{CLITextColor.YELLOW}Draw Match!{CLITextColor.RESET}")
|