Four in a Row
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()