Skip to content
renanss4Public

About

Connect Four in Python, playable in the terminal or a Pygame window, against a friend or a CPU opponent powered by minimax with alpha-beta pruning.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

82 Commits

Folders and files

Repository files navigation

Connect Four

CI Python License: MIT

The classic Connect Four game in Python, playable in the terminal or in a Pygame window, against a friend or against a CPU opponent that uses the minimax algorithm.

Game mode menu Player wins
Menu to choose between 1 player (easy, medium or hard) and 2 players Player 1 wins with four red pieces in a row, highlighted
CPU wins on a diagonal Draw
The CPU wins with four yellow pieces on a diagonal A full board with no winner

Features

  • Two interfaces: a text version for the terminal and a graphical version with Pygame.
  • CPU opponent with three difficulty levels, the hardest one using minimax search with alpha-beta pruning.
  • Pygame interface with a game mode menu, falling piece animation, mouse and keyboard controls, and the winning line highlighted.
  • Game rules separated from the interfaces: both the terminal and the Pygame window use the same Judge and Board classes.
  • Automated tests for the rules, the CPU and both interfaces, run on every push by GitHub Actions on Python 3.10 to 3.14.

Getting Started

You need Python 3.10 or newer.

git clone https://github.com/renanss4/Connect4.git
cd Connect4

python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate

pip install -e ".[gui,dev]"

The extras are optional: gui installs Pygame, which is only needed for the graphical version, and dev installs the testing and linting tools. The terminal version has no dependencies, so pip install -e . is enough to play it.

On Ubuntu and other Linux distributions you may need to use python3 instead of python.

How to Play

Two players take turns dropping pieces into one of the seven columns. The piece falls to the lowest empty row. The first player to line up four pieces in a row, horizontally, vertically or diagonally, wins. If the board fills up first, the game is a draw.

In the terminal

python -m connect4        # or simply: connect4

Choose 1 or 2 players (and the CPU difficulty for 1 player), then type the number of the column for each move. Press Ctrl+C to quit at any time.

In the Pygame window

python -m connect4.gui    # or simply: connect4-gui
Action Mouse Keyboard
Choose a game mode Click an option 1 to 4
Aim at a column Move the mouse ← →
Drop the piece Click Enter, Space or ↓, or 1 to 7 for that column
Restart the game R
Back to the menu (or quit, from the menu) Esc

CPU Difficulty Levels

Level Strategy
Easy Plays a random column.
Medium Wins if it can, otherwise blocks your winning move, otherwise plays at random.
Hard Looks 6 moves ahead with the minimax algorithm.

How the hard level thinks: for each possible move, it simulates every reply of the opponent, every answer to that reply, and so on, 6 moves deep. It assumes that both players always choose their best option: the CPU picks the move with the highest score and the opponent the one with the lowest. Positions where nobody has won yet are scored by a heuristic that rewards pieces in the center column and lines of 2 or 3 pieces that can still become 4.

Alpha-beta pruning skips branches that cannot change the decision. It chooses the same move as a plain minimax, about 50 times faster in this project's tests. Trying the center columns first makes the pruning even more effective.

The implementation is in connect4/cpu.py.

Project Structure

connect4/
├── board.py      # Board: the grid, dropping pieces, finding four in a row
├── player.py     # Player: a human player's name and symbol
├── cpu.py        # CpuPlayer and Difficulty: the three CPU strategies
├── judge.py      # Judge: turns, moves, winner and draw (no input or output)
├── cli.py        # Terminal interface
├── gui.py        # Pygame interface
└── __main__.py   # Makes `python -m connect4` start the terminal version
tests/            # pytest test suite

Architecture

Class diagram

classDiagram
    direction LR

    class Board {
        +int rows
        +int columns
        +cell(row, column) str
        +is_valid_move(column) bool
        +valid_columns() list
        +landing_row(column) int
        +drop_piece(column, symbol) int
        +remove_piece(column)
        +copy() Board
        +lines() tuple
        +winning_line(symbol) list
        +has_winner(symbol) bool
        +is_full() bool
    }

    class Player {
        +str name
        +str symbol
        +bool is_human
    }

    class CpuPlayer {
        +int SEARCH_DEPTH
        +Difficulty difficulty
        +choose_column(board) int
        -_minimax(board, depth, alpha, beta, maximizing) float
        -_evaluate(board) int
    }

    class Difficulty {
        <<enumeration>>
        EASY
        MEDIUM
        HARD
    }

    class Judge {
        +Board board
        +Player current_player
        +Player winner
        +bool is_draw
        +bool is_over
        +play(column) int
        +reset()
    }

    class cli {
        <<module>>
        +main()
        +play_game(judge)
        +render(board) str
        +ask_int(prompt, valid) int
    }

    class App {
        +Judge judge
        +run()
        +handle_event(event)
        +update(dt)
        +draw()
    }

    Player <|-- CpuPlayer
    CpuPlayer --> Difficulty
    Judge *-- Board
    Judge o-- "2" Player
    cli ..> Judge : uses
    App --> Judge : uses
    CpuPlayer ..> Board : simulates moves on a copy
Loading

One turn against the CPU

The interfaces never change the board directly: every move goes through Judge.play(), which enforces the rules. This is why the terminal and the Pygame window share the same flow.

sequenceDiagram
    actor Human
    participant UI as Interface (cli or App)
    participant Judge
    participant Board
    participant CPU as CpuPlayer

    Human->>UI: chooses a column
    UI->>Judge: play(column)
    Judge->>Board: drop_piece(column, symbol)
    Judge->>Board: has_winner(symbol)
    Board-->>Judge: False
    Judge->>Board: is_full()
    Board-->>Judge: False
    Note over Judge: no winner and no draw: the turn passes to the CPU

    UI->>Judge: current_player
    Judge-->>UI: CpuPlayer
    UI->>CPU: choose_column(board)
    CPU->>Board: copy()
    Note over CPU: minimax simulates moves on the copy
    CPU-->>UI: column
    UI->>Judge: play(column)
    Note over UI,Judge: repeats until judge.is_over
Loading

Running Tests

pytest                    # run the test suite
pytest -v                 # one line per test
ruff check .              # lint
ruff format .             # format the code

The Pygame tests run without opening a window. If Pygame is not installed, they are skipped.

Contributing

Contributions are welcome! If you find a bug or have an idea, open an issue or a pull request:

  1. Fork the repository and clone your fork.
  2. Create a branch: git checkout -b my-contribution.
  3. Make your changes and check that pytest and ruff check . pass.
  4. Commit, push to your fork and open a pull request.

GitHub Actions runs the tests and the linter on every pull request.

Future Improvements

  • Stronger CPU: a transposition table to avoid evaluating the same position twice, and iterative deepening to search as deep as a time limit allows.
  • More variety from the CPU: choose randomly between equally good moves, so it does not always play the same game.
  • Sound effects when a piece lands and when the game ends.
  • Score board across games, alternating who starts.

License

This project is licensed under the MIT License, which means you have complete freedom to use it as you prefer. Enjoy playing and contributing!

About

Connect Four in Python, playable in the terminal or a Pygame window, against a friend or a CPU opponent powered by minimax with alpha-beta pruning.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages