Skip to content

Instantly share code, notes, and snippets.

@Spebby
Created December 4, 2022 23:14
Show Gist options
  • Save Spebby/cdc4837560d414b6068f073464c08b3e to your computer and use it in GitHub Desktop.
Save Spebby/cdc4837560d414b6068f073464c08b3e to your computer and use it in GitHub Desktop.
Simulates a game of connect four in the terminal.
#!/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