"""Four in a Row, by Al Sweigart al@inventwithpython.com
A tile-dropping game to get four in a row, similar to Connect Four.
This code is available at https://nostarch.com/big-book-small-python-programming
Tags: large, game, board game, two-player"""
import sys
# Constants used for displaying the board:
EMPTY_SPACE = '.' # A period is easier to count than a space.
PLAYER_X = 'X'
PLAYER_O = 'O'
# Note: Update displayBoard() & COLUMN_LABELS if BOARD_WIDTH is changed.
BOARD_WIDTH = 7
BOARD_HEIGHT = 6
COLUMN_LABELS = ('1', '2', '3', '4', '5', '6', '7')
assert len(COLUMN_LABELS) == BOARD_WIDTH
def main():
print("""Four in a Row, by Al Sweigart al@inventwithpython.com
Two players take turns dropping tiles into one of seven columns, trying
to make four in a row horizontally, vertically, or diagonally.
""")
# Set up a new game:
gameBoard = getNewBoard()
playerTurn = PLAYER_X
while True: # Run a player's turn.
# Display the board and get player's move:
displayBoard(gameBoard)
playerMove = askForPlayerMove(playerTurn, gameBoard)
gameBoard[playerMove] = playerTurn
# Check for a win or tie:
if isWinner(playerTurn, gameBoard):
displayBoard(gameBoard) # Display the board one last time.
print('Player ' + playerTurn + ' has won!')
sys.exit()
elif isFull(gameBoard):
displayBoard(gameBoard) # Display the board one last time.
print('There is a tie!')
sys.exit()
# Switch turns to other player:
if playerTurn == PLAYER_X:
playerTurn = PLAYER_O
elif playerTurn == PLAYER_O:
playerTurn = PLAYER_X
def getNewBoard():
"""Returns a dictionary that represents a Four in a Row board.
The keys are (columnIndex, rowIndex) tuples of two integers, and the
values are one of the 'X', 'O' or '.' (empty space) strings."""
board = {}
for columnIndex in range(BOARD_WIDTH):
for rowIndex in range(BOARD_HEIGHT):
board[(columnIndex, rowIndex)] = EMPTY_SPACE
return board
def displayBoard(board):
"""Display the board and its tiles on the screen."""
'''Prepare a list to pass to the format() string method for the
board template. The list holds all of the board's tiles (and empty
spaces) going left to right, top to bottom:'''
tileChars = []
for rowIndex in range(BOARD_HEIGHT):
for columnIndex in range(BOARD_WIDTH):
tileChars.append(board[(columnIndex, rowIndex)])
# Display the board:
print("""
1234567
+-------+
|{}{}{}{}{}{}{}|
|{}{}{}{}{}{}{}|
|{}{}{}{}{}{}{}|
|{}{}{}{}{}{}{}|
|{}{}{}{}{}{}{}|
|{}{}{}{}{}{}{}|
+-------+""".format(*tileChars))
def askForPlayerMove(playerTile, board):
"""Let a player select a column on the board to drop a tile into.
Returns a tuple of the (column, row) that the tile falls into."""
while True: # Keep asking player until they enter a valid move.
print('Player {}, enter a column or QUIT:'.format(playerTile))
response = input('> ').upper().strip()
if response == 'QUIT':
print('Thanks for playing!')
sys.exit()
if response not in COLUMN_LABELS:
print('Enter a number from 1 to {}.'.format(BOARD_WIDTH))
continue # Ask player again for their move.
columnIndex = int(response) - 1 # -1 for 0-based the index.
# If the column is full, ask for a move again:
if board[(columnIndex, 0)] != EMPTY_SPACE:
print('That column is full, select another one.')
continue # Ask player again for their move.
# Starting from the bottom, find the first empty space.
for rowIndex in range(BOARD_HEIGHT - 1, -1, -1):
if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
return (columnIndex, rowIndex)
def isFull(board):
"""Returns True if the `board` has no empty spaces, otherwise
returns False."""
for rowIndex in range(BOARD_HEIGHT):
for columnIndex in range(BOARD_WIDTH):
if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
return False # Found an empty space, so return False.
return True # All spaces are full.
def isWinner(playerTile, board):
"""Returns True if `playerTile` has four tiles in a row on `board`,
otherwise returns False."""
# Go through the entire board, checking for four-in-a-row:
for columnIndex in range(BOARD_WIDTH - 3):
for rowIndex in range(BOARD_HEIGHT):
# Check for horizontal four-in-a-row going right:
tile1 = board[(columnIndex, rowIndex)]
tile2 = board[(columnIndex + 1, rowIndex)]
tile3 = board[(columnIndex + 2, rowIndex)]
tile4 = board[(columnIndex + 3, rowIndex)]
if tile1 == tile2 == tile3 == tile4 == playerTile:
return True
for columnIndex in range(BOARD_WIDTH):
for rowIndex in range(BOARD_HEIGHT - 3):
# Check for vertical four-in-a-row going down:
tile1 = board[(columnIndex, rowIndex)]
tile2 = board[(columnIndex, rowIndex + 1)]
tile3 = board[(columnIndex, rowIndex + 2)]
tile4 = board[(columnIndex, rowIndex + 3)]
if tile1 == tile2 == tile3 == tile4 == playerTile:
return True
for columnIndex in range(BOARD_WIDTH - 3):
for rowIndex in range(BOARD_HEIGHT - 3):
# Check for four-in-a-row going right-down diagonal:
tile1 = board[(columnIndex, rowIndex)]
tile2 = board[(columnIndex + 1, rowIndex + 1)]
tile3 = board[(columnIndex + 2, rowIndex + 2)]
tile4 = board[(columnIndex + 3, rowIndex + 3)]
if tile1 == tile2 == tile3 == tile4 == playerTile:
return True
# Check for four-in-a-row going left-down diagonal:
tile1 = board[(columnIndex + 3, rowIndex)]
tile2 = board[(columnIndex + 2, rowIndex + 1)]
tile3 = board[(columnIndex + 1, rowIndex + 2)]
tile4 = board[(columnIndex, rowIndex + 3)]
if tile1 == tile2 == tile3 == tile4 == playerTile:
return True
return False
# If the program is run (instead of imported), run the game:
if __name__ == '__main__':
main()