Four in a Row

Tags: large, boardgame, game, twoplayer

In this classic tile-dropping board game for two players, you must try to get four of your tiles in a row horizontally, vertically, or diagonally, while preventing your opponent from doing the same. This program is similar to Connect Four.

four_in_a_row.py
  1"""Four in a Row, by Al Sweigart al@inventwithpython.com
  2A tile-dropping game to get four in a row, similar to Connect Four.
  3This code is available at https://nostarch.com/big-book-small-python-programming
  4Tags: large, game, board game, two-player"""
  5
  6import sys
  7
  8# Constants used for displaying the board:
  9EMPTY_SPACE = '.'  # A period is easier to count than a space.
 10PLAYER_X = 'X'
 11PLAYER_O = 'O'
 12
 13# Note: Update displayBoard() & COLUMN_LABELS if BOARD_WIDTH is changed.
 14BOARD_WIDTH = 7
 15BOARD_HEIGHT = 6
 16COLUMN_LABELS = ('1', '2', '3', '4', '5', '6', '7')
 17assert len(COLUMN_LABELS) == BOARD_WIDTH
 18
 19
 20def main():
 21    print("""Four in a Row, by Al Sweigart al@inventwithpython.com
 22
 23Two players take turns dropping tiles into one of seven columns, trying
 24to make four in a row horizontally, vertically, or diagonally.
 25""")
 26
 27    # Set up a new game:
 28    gameBoard = getNewBoard()
 29    playerTurn = PLAYER_X
 30
 31    while True:  # Run a player's turn.
 32        # Display the board and get player's move:
 33        displayBoard(gameBoard)
 34        playerMove = askForPlayerMove(playerTurn, gameBoard)
 35        gameBoard[playerMove] = playerTurn
 36
 37        # Check for a win or tie:
 38        if isWinner(playerTurn, gameBoard):
 39            displayBoard(gameBoard)  # Display the board one last time.
 40            print('Player ' + playerTurn + ' has won!')
 41            sys.exit()
 42        elif isFull(gameBoard):
 43            displayBoard(gameBoard)  # Display the board one last time.
 44            print('There is a tie!')
 45            sys.exit()
 46
 47        # Switch turns to other player:
 48        if playerTurn == PLAYER_X:
 49            playerTurn = PLAYER_O
 50        elif playerTurn == PLAYER_O:
 51            playerTurn = PLAYER_X
 52
 53
 54def getNewBoard():
 55    """Returns a dictionary that represents a Four in a Row board.
 56
 57    The keys are (columnIndex, rowIndex) tuples of two integers, and the
 58    values are one of the 'X', 'O' or '.' (empty space) strings."""
 59    board = {}
 60    for columnIndex in range(BOARD_WIDTH):
 61        for rowIndex in range(BOARD_HEIGHT):
 62            board[(columnIndex, rowIndex)] = EMPTY_SPACE
 63    return board
 64
 65
 66def displayBoard(board):
 67    """Display the board and its tiles on the screen."""
 68
 69    '''Prepare a list to pass to the format() string method for the
 70    board template. The list holds all of the board's tiles (and empty
 71    spaces) going left to right, top to bottom:'''
 72    tileChars = []
 73    for rowIndex in range(BOARD_HEIGHT):
 74        for columnIndex in range(BOARD_WIDTH):
 75            tileChars.append(board[(columnIndex, rowIndex)])
 76
 77    # Display the board:
 78    print("""
 79     1234567
 80    +-------+
 81    |{}{}{}{}{}{}{}|
 82    |{}{}{}{}{}{}{}|
 83    |{}{}{}{}{}{}{}|
 84    |{}{}{}{}{}{}{}|
 85    |{}{}{}{}{}{}{}|
 86    |{}{}{}{}{}{}{}|
 87    +-------+""".format(*tileChars))
 88
 89
 90def askForPlayerMove(playerTile, board):
 91    """Let a player select a column on the board to drop a tile into.
 92
 93    Returns a tuple of the (column, row) that the tile falls into."""
 94    while True:  # Keep asking player until they enter a valid move.
 95        print('Player {}, enter a column or QUIT:'.format(playerTile))
 96        response = input('> ').upper().strip()
 97
 98        if response == 'QUIT':
 99            print('Thanks for playing!')
100            sys.exit()
101
102        if response not in COLUMN_LABELS:
103            print('Enter a number from 1 to {}.'.format(BOARD_WIDTH))
104            continue  # Ask player again for their move.
105
106        columnIndex = int(response) - 1  # -1 for 0-based the index.
107
108        # If the column is full, ask for a move again:
109        if board[(columnIndex, 0)] != EMPTY_SPACE:
110            print('That column is full, select another one.')
111            continue  # Ask player again for their move.
112
113        # Starting from the bottom, find the first empty space.
114        for rowIndex in range(BOARD_HEIGHT - 1, -1, -1):
115            if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
116                return (columnIndex, rowIndex)
117
118
119def isFull(board):
120    """Returns True if the `board` has no empty spaces, otherwise
121    returns False."""
122    for rowIndex in range(BOARD_HEIGHT):
123        for columnIndex in range(BOARD_WIDTH):
124            if board[(columnIndex, rowIndex)] == EMPTY_SPACE:
125                return False  # Found an empty space, so return False.
126    return True  # All spaces are full.
127
128
129def isWinner(playerTile, board):
130    """Returns True if `playerTile` has four tiles in a row on `board`,
131    otherwise returns False."""
132
133    # Go through the entire board, checking for four-in-a-row:
134    for columnIndex in range(BOARD_WIDTH - 3):
135        for rowIndex in range(BOARD_HEIGHT):
136            # Check for horizontal four-in-a-row going right:
137            tile1 = board[(columnIndex, rowIndex)]
138            tile2 = board[(columnIndex + 1, rowIndex)]
139            tile3 = board[(columnIndex + 2, rowIndex)]
140            tile4 = board[(columnIndex + 3, rowIndex)]
141            if tile1 == tile2 == tile3 == tile4 == playerTile:
142                return True
143
144    for columnIndex in range(BOARD_WIDTH):
145        for rowIndex in range(BOARD_HEIGHT - 3):
146            # Check for vertical four-in-a-row going down:
147            tile1 = board[(columnIndex, rowIndex)]
148            tile2 = board[(columnIndex, rowIndex + 1)]
149            tile3 = board[(columnIndex, rowIndex + 2)]
150            tile4 = board[(columnIndex, rowIndex + 3)]
151            if tile1 == tile2 == tile3 == tile4 == playerTile:
152                return True
153
154    for columnIndex in range(BOARD_WIDTH - 3):
155        for rowIndex in range(BOARD_HEIGHT - 3):
156            # Check for four-in-a-row going right-down diagonal:
157            tile1 = board[(columnIndex, rowIndex)]
158            tile2 = board[(columnIndex + 1, rowIndex + 1)]
159            tile3 = board[(columnIndex + 2, rowIndex + 2)]
160            tile4 = board[(columnIndex + 3, rowIndex + 3)]
161            if tile1 == tile2 == tile3 == tile4 == playerTile:
162                return True
163
164            # Check for four-in-a-row going left-down diagonal:
165            tile1 = board[(columnIndex + 3, rowIndex)]
166            tile2 = board[(columnIndex + 2, rowIndex + 1)]
167            tile3 = board[(columnIndex + 1, rowIndex + 2)]
168            tile4 = board[(columnIndex, rowIndex + 3)]
169            if tile1 == tile2 == tile3 == tile4 == playerTile:
170                return True
171    return False
172
173
174# If the program is run (instead of imported), run the game:
175if __name__ == '__main__':
176    main()

https://inventwithpython.com/bigbookpython/project30.html