absfuyu 3.1.1__py3-none-any.whl → 3.3.3__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.

Files changed (61) hide show
  1. absfuyu/__init__.py +3 -10
  2. absfuyu/__main__.py +5 -250
  3. absfuyu/cli/__init__.py +51 -0
  4. absfuyu/cli/color.py +24 -0
  5. absfuyu/cli/config_group.py +56 -0
  6. absfuyu/cli/do_group.py +76 -0
  7. absfuyu/cli/game_group.py +109 -0
  8. absfuyu/config/__init__.py +117 -100
  9. absfuyu/config/config.json +0 -7
  10. absfuyu/core.py +5 -66
  11. absfuyu/everything.py +7 -9
  12. absfuyu/extensions/beautiful.py +30 -23
  13. absfuyu/extensions/dev/__init__.py +11 -8
  14. absfuyu/extensions/dev/password_hash.py +4 -2
  15. absfuyu/extensions/dev/passwordlib.py +7 -5
  16. absfuyu/extensions/dev/project_starter.py +4 -2
  17. absfuyu/extensions/dev/shutdownizer.py +148 -0
  18. absfuyu/extensions/extra/__init__.py +1 -2
  19. absfuyu/extensions/extra/data_analysis.py +182 -107
  20. absfuyu/fun/WGS.py +50 -26
  21. absfuyu/fun/__init__.py +6 -7
  22. absfuyu/fun/tarot.py +1 -1
  23. absfuyu/game/__init__.py +75 -81
  24. absfuyu/game/game_stat.py +36 -0
  25. absfuyu/game/sudoku.py +41 -48
  26. absfuyu/game/tictactoe.py +303 -548
  27. absfuyu/game/wordle.py +56 -47
  28. absfuyu/general/__init__.py +17 -7
  29. absfuyu/general/content.py +16 -15
  30. absfuyu/general/data_extension.py +282 -90
  31. absfuyu/general/generator.py +67 -67
  32. absfuyu/general/human.py +74 -78
  33. absfuyu/logger.py +94 -68
  34. absfuyu/pkg_data/__init__.py +29 -25
  35. absfuyu/py.typed +0 -0
  36. absfuyu/sort.py +61 -47
  37. absfuyu/tools/__init__.py +0 -1
  38. absfuyu/tools/converter.py +80 -62
  39. absfuyu/tools/keygen.py +62 -67
  40. absfuyu/tools/obfuscator.py +57 -53
  41. absfuyu/tools/stats.py +24 -24
  42. absfuyu/tools/web.py +10 -9
  43. absfuyu/util/__init__.py +71 -33
  44. absfuyu/util/api.py +53 -43
  45. absfuyu/util/json_method.py +25 -27
  46. absfuyu/util/lunar.py +20 -24
  47. absfuyu/util/path.py +362 -241
  48. absfuyu/util/performance.py +217 -135
  49. absfuyu/util/pkl.py +8 -8
  50. absfuyu/util/zipped.py +17 -19
  51. absfuyu/version.py +160 -147
  52. absfuyu-3.3.3.dist-info/METADATA +124 -0
  53. absfuyu-3.3.3.dist-info/RECORD +59 -0
  54. {absfuyu-3.1.1.dist-info → absfuyu-3.3.3.dist-info}/WHEEL +1 -2
  55. {absfuyu-3.1.1.dist-info → absfuyu-3.3.3.dist-info}/entry_points.txt +1 -0
  56. {absfuyu-3.1.1.dist-info → absfuyu-3.3.3.dist-info/licenses}/LICENSE +1 -1
  57. absfuyu/extensions/dev/pkglib.py +0 -98
  58. absfuyu/game/tictactoe2.py +0 -318
  59. absfuyu-3.1.1.dist-info/METADATA +0 -215
  60. absfuyu-3.1.1.dist-info/RECORD +0 -55
  61. absfuyu-3.1.1.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: 1.0.3
6
- Date update: 24/11/2023 (mm/dd/yyyy)
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
- # Library
11
- ##############################################################
12
- import random as __random
13
- import time as __time
14
- from typing import Optional as __Optional, List as __List
15
-
16
- from absfuyu import core as __core
17
- from absfuyu.game.tictactoe2 import GameMode
18
-
19
-
20
- # Tic Tac Toe
21
- ##############################################################
22
-
23
- # GAME SETTING
24
- X = "X"
25
- O = "O"
26
- BLANK = " "
27
- POS_SPLIT = ","
28
- END_BREAK = "END"
29
- __C = __core.Color
30
-
31
-
32
- # FUNCTIONS
33
- def __test_case_3x3(num: int = 0):
34
- case = {
35
- 1: [[X,X,X],
36
- [BLANK,BLANK,BLANK],
37
- [BLANK,BLANK,BLANK]],
38
- 2: [[BLANK,BLANK,BLANK],
39
- [X,X,X],
40
- [BLANK,BLANK,BLANK]],
41
- 3: [[BLANK,BLANK,BLANK],
42
- [BLANK,BLANK,BLANK],
43
- [X,X,X]],
44
- 4: [[X,BLANK,BLANK],
45
- [X,BLANK,BLANK],
46
- [X,BLANK,BLANK]],
47
- 5: [[BLANK,X,BLANK],
48
- [BLANK,X,BLANK],
49
- [BLANK,X,BLANK]],
50
- 6: [[BLANK,BLANK,X],
51
- [BLANK,BLANK,X],
52
- [BLANK,BLANK,X]],
53
- 7: [[X,BLANK,BLANK],
54
- [BLANK,X,BLANK],
55
- [BLANK,BLANK,X]],
56
- 8: [[BLANK,BLANK,X],
57
- [BLANK,X,BLANK],
58
- [X,BLANK,BLANK]],
59
- }
60
- try:
61
- import numpy as np
62
- for k, v in case.items():
63
- case[k] = np.array(v)
64
- except:
65
- pass
66
-
67
- if num < 1:
68
- return case
69
- return case[num]
70
-
71
-
72
-
73
- def __gen_board(row_size: int, col_size: int, content: str = BLANK):
74
- """
75
- Generate board game (with or without `numpy`)
76
-
77
- Parameter:
78
- ---
79
- row_size : int
80
- Number of rows
81
-
82
- col_size : int
83
- Number of columns
84
-
85
- content : str
86
- What should be filled inside the board
87
-
88
- Return:
89
- ---
90
- Game board
91
- """
92
-
93
- try:
94
- import numpy as np
95
- except:
96
- np = None
97
-
98
- if np is None:
99
- board = [[BLANK for _ in range(row_size)] for _ in range(col_size)]
100
- else:
101
- board = np.full((row_size, col_size), content)
102
- return board
103
-
104
- def __check_state(table):
105
- """
106
- Check game winning state
107
-
108
- Parameter:
109
- ---
110
- table : numpy.ndarray | list[list[str]]
111
- Game board
112
-
113
- Return:
114
- ---
115
- X | O | BLANK
116
- """
117
-
118
- # Data
119
- nrow, ncol = len(table), len(table[0])
120
-
121
- # Check rows
122
- for row in range(nrow):
123
- if len(set(table[row])) == 1:
124
- # return list(set(table[row]))[0]
125
- key = list(set(table[row]))[0]
126
- return {"key": key, "location": "row", "pos": row} # modified
127
-
128
- # Check cols
129
- for col in range(ncol):
130
- temp = [table[row][col] for row in range(nrow)]
131
- if len(set(temp)) == 1:
132
- # return list(set(temp))[0]
133
- key = list(set(temp))[0]
134
- return {"key": key, "location": "col", "pos": col} # modified
135
-
136
- # Check diagonal
137
- diag1 = [table[i][i] for i in range(len(table))]
138
- if len(set(diag1)) == 1:
139
- # return list(set(diag1))[0]
140
- key = list(set(diag1))[0]
141
- return {"key": key, "location": "diag", "pos": 1} # modified
142
-
143
- diag2 = [table[i][len(table)-i-1] for i in range(len(table))]
144
- if len(set(diag2)) == 1:
145
- # return list(set(diag2))[0]
146
- key = list(set(diag2))[0]
147
- return {"key": key, "location": "diag", "pos": 2} # modified
148
-
149
- # Else
150
- # return BLANK
151
- return {"key": BLANK}
152
-
153
- def __print_board(table):
154
- """
155
- Print Tic Tac Toe board
156
-
157
- Parameter:
158
- ---
159
- table : numpy.ndarray | list[list[str]]
160
- Game board
161
- """
162
- nrow, ncol = len(table), len(table[0])
163
- length = len(table)
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][i] = detail['key']
199
- else:
166
+ board[i][loc_line] = detail.key
167
+ elif loc.startswith("row"):
200
168
  for i in range(len(board)):
201
- board[i][len(board)-i-1] = detail['key']
202
-
203
- # Output
204
- return board
205
-
206
- def __is_blank(table, pos: __List[int]):
207
- """Check if current slot is filled"""
208
- return table[pos[0]][pos[1]] == BLANK
209
-
210
- def __convert_bot_output(pos: __List[int]):
211
- """
212
- Turn to real pos by:
213
- - +1 to row and col
214
- - convert into str
215
- """
216
- for i in range(len(pos)):
217
- pos[i] = str(int(pos[i])+1)
218
- return pos
219
-
220
- def __generate_surround_move(table, pos: __List[int]):
221
- """
222
- Generate all surrounding move of a position
223
- """
224
- surround_move = [
225
- [pos[0]-1, pos[1]], # Up pos
226
- [pos[0]+1, pos[1]], # Down pos
227
- [pos[0], pos[1]-1], # Left pos
228
- [pos[0], pos[1]+1], # Right pos
229
- [pos[0]-1, pos[1]-1], # Up left pos
230
- [pos[0]-1, pos[1]+1], # Up right pos
231
- [pos[0]+1, pos[1]-1], # Down left pos
232
- [pos[0]+1, pos[1]+1], # Down right pos
233
- ]
234
- surround_move = {
235
- "U": [pos[0]-1, pos[1]], # Up pos
236
- "D": [pos[0]+1, pos[1]], # Down pos
237
- "L": [pos[0], pos[1]-1], # Left pos
238
- "R": [pos[0], pos[1]+1], # Right pos
239
- "UL": [pos[0]-1, pos[1]-1], # Up left pos
240
- "UR": [pos[0]-1, pos[1]+1], # Up right pos
241
- "DL": [pos[0]+1, pos[1]-1], # Down left pos
242
- "DR": [pos[0]+1, pos[1]+1], # Down right pos
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
- show_stats: bool = False,
406
- ):
407
- """
408
- Tic Tac Toe
409
-
410
- Parameters
411
- ----------
412
- size : int
413
- board size
414
-
415
- mode : str
416
- "1v1": Player vs player
417
- "1v0": Player vs BOT
418
- "0v0": BOT vs BOT
419
-
420
- smarter_bot : bool
421
- W.I.P
422
- New bot's behaviour
423
-
424
- board_game : True | False | None
425
- True: draw board
426
- False: print array
427
- None: no board or array
428
-
429
- bot_time : float
430
- time sleep between each bot move
431
- [Default: 0]
432
-
433
- show_stats : bool
434
- Print current game stats
435
- [Default: False]
436
-
437
- Returns
438
- ------
439
- dict
440
- Game stats
441
- """
442
-
443
- # Init game
444
- board = __gen_board(size, size)
445
- filled = 0
446
- current_player = X
447
- # state = __check_state(board)
448
- state = __check_state(board)["key"]
449
- BOT = False
450
- BOT2 = False
451
-
452
- # Welcome message
453
- if board_game is not None:
454
- print(f"""\
455
- {__C['GREEN']}Welcome to Tic Tac Toe!
456
-
457
- {__C['YELLOW']}Rules: Match lines vertically, horizontally or diagonally
458
- {__C['YELLOW']}{X} goes first, then {O}
459
- {__C['RED']}Type '{END_BREAK}' to end the game{__C['reset']}""")
460
- else:
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 = __generate_random_move(board)
496
- str_move = POS_SPLIT.join(move)
497
- move = str_move
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
- move = input(f"Place {__C['BLUE']}{current_player}{__C['reset']} at {__C['BLUE']}<row{POS_SPLIT}col>:{__C['reset']} ")
501
-
502
- if move.upper() == END_BREAK: # Failsafe
503
- print(f"{__C['RED']}Game ended{__C['reset']}")
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}")