Skip to content

Commit 053469a

Browse files
committed
feat: apply some changes
1 parent b894929 commit 053469a

File tree

7 files changed

+187
-158
lines changed

7 files changed

+187
-158
lines changed

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
venv/
2+
.vscode/
3+
dist/
4+
__pycache__/
5+
.mypy_cache/
6+
.pytest_cache/
7+
*.egg-info/

src/connect4/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .core import Connect4
2+
from .errors import *

src/connect4/__main__.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from connect4 import Connect4
2+
3+
power4 = Connect4()
4+
while True:
5+
print(''.join(map(str, range(7))))
6+
print(power4.strboard(empty="."))
7+
play = input("Player {} turn: ".format(power4.get_turn()))
8+
result = power4.play(int(play))
9+
10+
if result:
11+
print(power4.strboard(empty="."))
12+
print("gg u win")
13+
break

src/connect4/core.py

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from enum import Enum
5+
from typing import Any, Literal, Sequence, TypeAlias, cast
6+
7+
from connect4.errors import ColumnFull, GameOver, InvalidColumn
8+
9+
Token: TypeAlias = str
10+
11+
12+
class Players(Enum):
13+
ONE = 1
14+
TWO = 2
15+
16+
17+
class Connect4:
18+
def __init__(self, dimensions: Sequence[int] = (7, 6)):
19+
self._dimensions: tuple[int, int] = (dimensions[0], dimensions[1])
20+
self._idim: tuple[int, int] = (self._dimensions[0] - 1, self._dimensions[1] - 1)
21+
self.plays_history: list[tuple[int, int]] = []
22+
self._win_points: set[tuple[int, int]] = set()
23+
24+
self._board: list[list[Literal[0, 1, 2]]] = self.create_empty_board(self._dimensions)
25+
self.turn: Players = Players.ONE
26+
27+
self._dim = self._dimensions
28+
29+
@property
30+
def dimensions(self) -> tuple[int, int]:
31+
return self._dimensions
32+
33+
@property
34+
def yxboard(self) -> list[list[Literal[0, 1, 2]]]:
35+
return [[row[i] for row in self._board] for i in range(self._dim[1])[::-1]]
36+
37+
@property
38+
def board(self):
39+
return self._board
40+
41+
@staticmethod
42+
def create_empty_board(dimensions: Sequence[int]) -> list[list[Any]]:
43+
return [[0] * dimensions[0] for _ in range(dimensions[1])]
44+
45+
def strboard(self, player1: Token = "x", player2: Token = "o", win_player1: Token = "X", win_player2: Token = "O", empty: Token = " ") -> str:
46+
board: list[list[int]] = cast(list[list[int]], self.board.copy())
47+
for row, column in self._win_points:
48+
board[row][column] = 3 if self._board[row][column] == Players.ONE.value else 4
49+
return ''.join(''.join((empty, player1, player2, win_player1, win_player2)[case] for case in row) + '\n' for row in self.board)[:-1]
50+
51+
def get_turn(self) -> Literal[1, 2]:
52+
return self.turn.value
53+
54+
def play(self, column: int):
55+
if self._win_points:
56+
raise GameOver(self.get_turn())
57+
58+
if column < 0 or column >= self._dim[0]:
59+
raise InvalidColumn(column)
60+
61+
for i, row in enumerate(self._board[::-1]):
62+
if row[column] == 0:
63+
row[column] = self.get_turn()
64+
break
65+
else:
66+
raise ColumnFull(column)
67+
68+
self.plays_history.append((column, self._idim[1] - i))
69+
self.get_win(column, self._idim[1] - i)
70+
71+
if self.turn == Players.ONE:
72+
self.turn = Players.TWO
73+
else:
74+
self.turn = Players.ONE
75+
76+
return self._win_points
77+
78+
def get_win(self, icolumn, irow):
79+
target = self.get_turn()
80+
81+
@dataclass
82+
class T:
83+
positions: set
84+
before: bool
85+
after: bool
86+
87+
def __bool__(self) -> bool:
88+
return self.before or self.after
89+
90+
lines: dict[str, T] = {
91+
'horizontal': T({(irow, icolumn)}, True, True),
92+
'vertical': T({(irow, icolumn)}, False, True),
93+
'diagonal1': T({(irow, icolumn)}, True, True),
94+
'diagonal2': T({(irow, icolumn)}, True, True)
95+
}
96+
97+
for k in range(1, max(self._dim[0], self._dim[1])):
98+
line = lines['horizontal']
99+
if line.before and (i := icolumn - k) >= 0 and self._board[irow][i] == target:
100+
line.positions.add((irow, i))
101+
else:
102+
line.before = False
103+
104+
if line.after and (i := icolumn + k) < self._dim[0] and self._board[irow][i] == target:
105+
line.positions.add((irow, i))
106+
else:
107+
line.after = False
108+
109+
# before (top) is not needed for vertical victory.
110+
line = lines['vertical']
111+
if line.after and (i := irow + k) < self._dim[1] and self._board[i][icolumn] == target:
112+
line.positions.add((i, icolumn))
113+
else:
114+
line.after = False
115+
116+
line = lines['diagonal1']
117+
if line.before and (i := icolumn - k) >= 0 and (j := irow - k) >= 0 and self._board[j][i] == target:
118+
line.positions.add((j, i))
119+
else:
120+
line.before = False
121+
122+
if line.after and (i := icolumn + k) < self._dim[0] and (j := irow + k) < self._dim[1] and self._board[j][i] == target:
123+
line.positions.add((j, i))
124+
else:
125+
line.after = False
126+
127+
line = lines['diagonal2']
128+
if line.before and (i := icolumn - k) >= 0 and (j := irow + k) < self._dim[1] and self._board[j][i] == target:
129+
line.positions.add((j, i))
130+
else:
131+
line.before = False
132+
133+
if line.after and (i := icolumn + k) < self._dim[0] and (j := irow - k) >= 0 and self._board[j][i] == target:
134+
line.positions.add((j, i))
135+
else:
136+
line.after = False
137+
138+
if not any(check for check in lines.values()):
139+
break
140+
141+
for line in lines.values():
142+
if len(line.positions) >= 4:
143+
self._win_points = line.positions
144+
145+
def show_history(self):
146+
board = self.create_empty_board(self._dim)
147+
for i, (column, row) in enumerate(self.plays_history):
148+
board[row][column] = i + 1
149+
150+
return board

src/connect4/errors.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class GameOver(Exception):
2+
def __init__(self, winner):
3+
super().__init__(f"Game is over, winner is player {winner}")
4+
self.winner = winner
5+
6+
7+
class ColumnFull(Exception):
8+
def __init__(self, column_index):
9+
super().__init__(f"The column {column_index} is full")
10+
self.column_index = column_index
11+
12+
13+
class InvalidColumn(Exception):
14+
def __init__(self, column_index) -> None:
15+
super().__init__(f"The column {column_index} is out of range.")
16+
self.column_index = column_index

src/connect4/py.typed

Whitespace-only changes.

src/main.py

-158
This file was deleted.

0 commit comments

Comments
 (0)