-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathconnect four.py
123 lines (94 loc) · 3.47 KB
/
connect four.py
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
import os
import time
import numpy as np
TERMX, TERMY = os.get_terminal_size()
ANIMATION_DELAY = .1
def print_lines(*lines):
"""Print `lines` centered in terminal.
"""
for line in lines:
print(line.center(TERMX))
class ConnectFour:
"""
ConnectFour! The first player to connect four checkers in a row wins!
"""
def __init__(self, height=6, width=7):
self._to_bin = 2 ** np.arange(height * (width + 2), dtype=object)
self.height, self.width = height, min(width, 35)
self.labels = "1234567890abcdefghijklmnoprstuvwxyz"[:self.width]
self.board = np.zeros((self.height, self.width + 2), dtype=int)
@property
def current_player(self):
return np.count_nonzero(self.board) % 2 + 1
def print_board(self):
"""Print our current board state.
"""
os.system('cls' if os.name == 'nt' else 'clear')
header = f"╷{'╷'.join(self.labels)}╷"
gutter = (f"│{'│'.join(' ●○'[value] for value in row[:-2])}│" for row in self.board[::-1])
footer = f"╰{'─┴' * (self.width - 1)}─╯"
print("\n" * ((TERMY - self.height - 5) // 2)) # Vertical Buffer
print_lines(header, *gutter, footer)
def is_valid_move(self, move: str) -> bool:
"""Return True if move is valid.
"""
if move == 'q':
return True
if not (len(move) == 1 and move in self.labels):
print_lines("Please input a valid column!")
return False
# Check that a move is possible in given column.
column = self.labels.find(move)
if np.count_nonzero(self.board[:, column]) < self.height:
return True
print_lines("No moves possible in that column!")
return False
def get_valid_move(self) -> str:
player = "●○"[self.current_player - 1]
while True:
print_lines(f"{player}'s move, enter column or 'q' to quit:\n")
move = input("".center(TERMX // 2)).lower()
if self.is_valid_move(move):
return move
def play_move(self, column: int):
"""Drop a checker into a column.
"""
player = self.current_player
final_row = np.count_nonzero(self.board[:, column])
board = self.board
for row in range(self.height - 1, final_row, -1):
board[row, column] = player
self.print_board()
board[row, column] = 0
time.sleep(ANIMATION_DELAY)
board[final_row, column] = player
def is_winner(self, player):
"""Return True if a player has won.
"""
key = (self.board == player).flatten() @ self._to_bin
w = self.width + 2
return any(
key
& key >> offset
& key >> 2 * offset
& key >> 3 * offset
for offset in (1, w - 1, w, w + 1)
)
def run(self):
for _ in range(self.width * self.height):
player = self.current_player
self.print_board()
move = self.get_valid_move()
if move == "q":
break
column = self.labels.find(move)
self.play_move(column)
if self.is_winner(player):
self.print_board()
print_lines(f"{'●○'[player - 1]} wins!")
break
else:
self.print_board()
print_lines("It's a draw!")
if __name__ == "__main__":
ConnectFour().run()