Four in a Row Game in Python
Intermediate · Games · about 90 minutes to build · 110 lines
Two players take turns dropping X and O pieces into a 6 by 7 grid, and the first to line up four across, down or diagonally wins.
The program
Press Run and answer its questions in the box that appears under the code.
Open it in the full editor to save it, share it and keep building.
Sample run
What the program printed, and what was typed (in yellow).
=== Four in a Row === Drop pieces into columns 1 to 7. Four in a line in any direction wins. Player X, choose a column (1-7): 4 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . X . . .| Player O, choose a column (1-7): 4 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . O . . .| |. . . X . . .| Player X, choose a column (1-7): 3 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . O . . .| |. . X X . . .| Player O, choose a column (1-7): 8 Please type a number from 1 to 7. Player O, choose a column (1-7): 3 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . O O . . .| |. . X X . . .| Player X, choose a column (1-7): 5 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . O O . . .| |. . X X X . .| Player O, choose a column (1-7): 6 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . O O . . .| |. . X X X O .| Player X, choose a column (1-7): 2 1 2 3 4 5 6 7 |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . . . . . .| |. . O O . . .| |. X X X X O .| Four in a row. Player X wins. Score: X 1, O 0 Play again? (y/n) n Thanks for playing.
How it is built
1. The grid
The board is a list of six rows, each a list of seven cells, so board[row][col] is one cell and row 0 is the top. DIRECTIONS holds one step for each kind of line.
ROWS = 6 COLS = 7 EMPTY = "." PIECES = ("X", "O") # The four lines to check, as (row step, column step): across, down and both diagonals. DIRECTIONS = [(0, 1), (1, 0), (1, 1), (1, -1)]
2. Choosing a column
A column is full when its top cell is taken, so board[0][col] is the only cell the check needs.
def ask_column(board, piece): """Ask until the player picks a column that is not full; return its index.""" while True: text = input(f"Player {piece}, choose a column (1-{COLS}): ").strip() if not text.isdecimal() or not 1 <= int(text) <= COLS: print(f" Please type a number from 1 to {COLS}.") elif board[0][int(text) - 1] != EMPTY: print(" That column is full. Pick another one.") else: return int(text) - 1
3. Dropping a piece
The loop starts at the bottom row and climbs until it finds an empty cell, the way a real piece falls.
def drop_piece(board, col, piece): """Put the piece in the lowest empty cell of the column and return its row.""" for row in range(ROWS - 1, -1, -1): if board[row][col] == EMPTY: board[row][col] = piece return row return None # the column was full (ask_column prevents this)
4. Counting in one direction
From the new piece, the function keeps stepping by (d_row, d_col) while it stays on the board and finds the same piece. A step of (1, -1) walks down and to the left.
def count_direction(board, row, col, d_row, d_col): """Count matching pieces next to (row, col), stepping by (d_row, d_col).""" piece = board[row][col] count = 0 row, col = row + d_row, col + d_col while 0 <= row < ROWS and 0 <= col < COLS and board[row][col] == piece: count += 1 row, col = row + d_row, col + d_col return count
5. Four in a line
Only the newest piece can make a new line, so the check starts there. For each direction it counts matching pieces ahead and behind and adds one for the piece itself.
def is_winning_move(board, row, col): """Return True if the piece at (row, col) is part of four or more in a line.""" for d_row, d_col in DIRECTIONS: # Count both ways along the line, plus the new piece itself. ahead = count_direction(board, row, col, d_row, d_col) behind = count_direction(board, row, col, -d_row, -d_col) if 1 + ahead + behind >= 4: return True return False
6. Taking turns
PIECES[turn % 2] gives X on even turns and O on odd ones. After 42 moves every cell is full and the game is a draw.
def play_game(): """Play one game and return the winning piece, or None for a draw.""" board = new_board() for turn in range(ROWS * COLS): piece = PIECES[turn % 2] col = ask_column(board, piece) row = drop_piece(board, col, piece) show_board(board) if is_winning_move(board, row, col): return piece return None
Make it bigger
- Add a computer player that wins when it can, blocks when it must and otherwise prefers the middle column.
- Mark the winning four by printing them in lowercase.
- Let the players type their names and keep the score under those names.
- Change
ROWSandCOLSto try a bigger board, and check that nothing else in the code needs to change.
What it practises
- lists of lists
- nested loops
- direction steps
- functions
- input validation