Created
December 4, 2022 23:14
-
-
Save Spebby/cdc4837560d414b6068f073464c08b3e to your computer and use it in GitHub Desktop.
Simulates a game of connect four in the terminal.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Module connect_four contains the ConnectFour class, instances of which model Connect 4 games. | |
(Seven columns, six rows.) | |
""" | |
class ConnectFour: | |
""" | |
Models a game of Connect Four. Players can be given colors, but are still represented as 1 and 2 | |
on the board. Player 1 has the first turn. Turns are taken by calling the drop() method. | |
""" | |
def __current_player__(self): | |
""" | |
Returns the current player. | |
>>> g.current_player() | |
1 | |
>>> g.drop(0) | |
>>> g.current_player() | |
2 | |
""" | |
return self.current_player | |
def __init__(self, p1="red", p2="black"): | |
""" | |
Creates a Connect Four game with the disc colors for players 1 and 2 specified by | |
arguments p1 and p2, respectively. Turns alternate, with player 1 getting the first turn. | |
Specified colours can be either strings or hex codes. Interacts with the GUI class. | |
Color names are specified in `/srv/datasets/rgb.txt` | |
>>> g = ConnectFour() | |
""" | |
self.p1 = p1 | |
self.p2 = p2 | |
self.columns = [ | |
[None, None, None, None, None, None], | |
[None, None, None, None, None, None], | |
[None, None, None, None, None, None], | |
[None, None, None, None, None, None], | |
[None, None, None, None, None, None], | |
[None, None, None, None, None, None], | |
[None, None, None, None, None, None], | |
] | |
self.current_player = 1 | |
def __bool__(self): | |
""" | |
A Connect Four game is considered "True" if still in play. | |
A game is considered ongoing if there is no winner and possible moves remain. | |
|_|_|_|_|_|_|_| | |
|_|_|_|_|_|_|_| | |
|1|_|_|_|_|_|_| | |
|1|2|_|_|_|_|_| | |
|1|2|_|_|_|_|_| | |
|1|2|_|_|_|_|_| | |
>>> g.winner() | |
'yellow' | |
""" | |
# check if there are possible moves | |
if self.winner() is not None: | |
return False | |
# check if there are any empty spaces | |
for col in self.columns: | |
if None in col: | |
return True | |
return False | |
def __str__(self): | |
""" | |
Returns a string representation of the game board, formatted as in the following: | |
|_|_|_|_|_|_|_| | |
|_|_|_|_|_|_|_| | |
|_|_|_|_|_|_|_| | |
|_|_|_|_|_|_|_| | |
|_|_|_|_|_|_|_| | |
|_|_|_|_|_|_|_| | |
""" | |
output = "" | |
for row in self.board(): | |
for col in row: | |
if col is None: | |
output += "|_" | |
else: | |
output += f"|{col}" | |
output += "|\n" | |
return output[:-1] | |
def board(self): | |
""" | |
Returns a tuple comprised of 6 subtuples each representing a row of the board. | |
Each subtuple shall consist of either the int value `1`or `2` to represent a | |
location claimed by player 1 or 2, or the value `None` for a location that has | |
not been claimed by either player. | |
""" | |
output = [[], [], [], [], [], []] | |
columns = self.columns | |
# organise output | |
for row in range(6): | |
for col in columns: | |
index = columns.index(col) | |
output[row].append(columns[index][row]) | |
# make the content of the output a tuple | |
for row in output: | |
output[output.index(row)] = tuple(row) | |
return tuple(reversed(output)) | |
def drop(self, col: int, ignoreInvalid=False): | |
""" | |
Takes a turn by dropping a token into the given column (0–6) for the current player. | |
Raises a ValueError if the turn is invalid. (full column, invalid index, game ended) | |
>>> g.board()[5][0] # bottom row, first column | |
1 | |
""" | |
if col < -6 or col > 6: | |
raise ValueError("Invalid column index") | |
if self is False: | |
raise ValueError("Game has ended") | |
column = self.columns[col] | |
if column[5] is not None: | |
if ignoreInvalid: | |
return | |
raise ValueError("Column is full") | |
i = column.index(None) | |
column[i] = self.current_player | |
self.current_player = 2 if self.current_player == 1 else 1 | |
def dropList(self, drops: list): | |
""" | |
<b> USED FOR TESTING ONLY </b> | |
Takes a list of column indices and drops a token into each column in order. | |
Raises a ValueError if the turn is invalid. (full column, invalid index, game ended) | |
>>> g.dropList([0, 1, 2, 3, 4, 5, 6]) | |
""" | |
# ignores value errors :D | |
for col in drops: | |
self.drop(col, ignoreInvalid=True) | |
def player_names(self): | |
""" | |
Returns a dict of the current players, mapped with their colours. | |
>>> g = ConnectFour(p1='yellow', p2='blue') | |
>>> g.player_names() | |
{1: 'yellow', 2: 'blue'} | |
""" | |
return {1: self.p1, 2: self.p2} | |
def winner(self): | |
""" | |
Returns the winner of the game, or None if the game has not ended | |
or has ended without a winner. | |
>>> g = ConnectFour(p1='yellow', p2='blue') | |
>>> flag = True | |
>>> while g: | |
... g.drop(0 if flag else 1) | |
... print(g.winner()) | |
... flag = not flag | |
None(x6) | |
yellow | |
""" | |
rows = self.board() | |
columns = self.columns | |
# horizontal & vertical checks | |
winLine(rows) | |
winLine(columns) | |
# positive diagonal win | |
for col in range(len(columns) - 3): | |
for row in range(len(columns[col]) - 3): | |
if ( | |
columns[col][row] | |
== columns[col + 1][row + 1] | |
== columns[col + 2][row + 2] | |
== columns[col + 3][row + 3] | |
and columns[col][row] is not None | |
): | |
return self.player_names()[int(columns[col][row])] | |
# negative diagonal win | |
for col in range(len(columns) - 3): | |
for row in range(len(columns[col]) - 3): | |
if ( | |
columns[col][row + 3] | |
== columns[col + 1][row + 2] | |
== columns[col + 2][row + 1] | |
== columns[col + 3][row] | |
and columns[col][row + 3] is not None | |
): | |
return self.player_names()[int(columns[col][row + 3])] | |
return None | |
def winLine(self, input: list): | |
for line in input: | |
for i in range(len(line) - 3): | |
if ( | |
line[i] == line[i + 1] == line[i + 2] == line[i + 3] | |
and line[i] is not None | |
): | |
return self.player_names()[int(line[i])] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment