Build a Connect Four game with HTMX and Django in 8 minutes 🟡🔴

Updated: Fri 03 May 2024

We'll build a simple Connect4 game with Django and HTMX - fast 🏎️

By the end, you'll have built a multiplayer game using HTMX, using neat server-side logic and storing all results in your database. HTMX is a great way to use javascript without writing javascript.

Here's how your final product will look 🟡🔴: Everything is rendered server-side, and the state is kept server-side.

Let's start 👨‍🚀

Setup your Django app

pip install --upgrade django

django-admin startproject core .
python manage.py startapp sim
  • Add our app sim to the INSTALLED_APPS in settings.py:
# settings.py
INSTALLED_APPS = [
    'sim',
    ...
]

Add your game model with logic

  • Add the following code to sim/models.py:
from django.db import models


def default_board():
    return [[0 for _ in range(6)] for _ in range(7)]


class Game(models.Model):
    board = models.JSONField(default=default_board)
    active_player = models.IntegerField(default=1)
    winner = models.IntegerField(default=0)

    def drop_piece(self, column) -> None:
        for row in range(5, -1, -1):
            if self.board[column][row] == 0:
                self.board[column][row] = self.active_player
                break

    def check_winner(self) -> bool:
        # Check horizontal
        # I.e.,
        #
        #  1 1 1 1
        #
        for row in range(6):
            for col in range(4):
                if self.board[col][row] == self.active_player and
                        self.board[col + 1][row] == self.active_player and
                        self.board[col + 2][row] == self.active_player and
                        self.board[col + 3][row] == self.active_player:
                    self.winner = self.active_player
                    return True

        # Check vertical
        # I.e.,
        # 1
        # 1
        # 1
        # 1
        for col in range(7):
            for row in range(3):
                if self.board[col][row] == self.active_player and
                        self.board[col][row + 1] == self.active_player and
                        self.board[col][row + 2] == self.active_player and
                        self.board[col][row + 3] == self.active_player:
                    self.winner = self.active_player
                    return True

        # Check positive diagonal
        # I.e.,
        #    1
        #   1
        #  1
        # 1
        for col in range(4):
            for row in range(3):
                if self.board[col][row] == self.active_player and
                        self.board[col + 1][row + 1] == self.active_player and
                        self.board[col + 2][row + 2] == self.active_player and
                        self.board[col + 3][row + 3] == self.active_player:
                    self.winner = self.active_player
                    return True

        # Check negative diagonal
        # I.e.,
        # 1
        #  1
        #   1
        #    1
        for col in range(4):
            for row in range(5, 2, -1):
                if self.board[col][row] == self.active_player and
                        self.board[col + 1][row - 1] == self.active_player and
                        self.board[col + 2][row - 2] == self.active_player and
                        self.board[col + 3][row - 3] == self.active_player:
                    self.winner = self.active_player
                    return True

        # No winner, switch player
        self.active_player = 1 if self.active_player == 2 else 2
        return False

  • Make migrations and migrate the database:
python manage.py makemigrations
python manage.py migrate

Add your game views (sim/views.py)

from django.shortcuts import render, redirect, reverse
from django.views.decorators.http import require_http_methods
from .models import Game
from django.http import HttpResponse


@require_http_methods(["GET", "POST"])
def index(request):
    game, _ = Game.objects.get_or_create(id=1)  # Ensure only one game is active

    if request.method == 'POST':
        column = int(request.POST.get('column'))
        game.drop_piece(column)
        if game.check_winner():
            game.save()
            return render(request, 'index.html', context={'game': game})
        game.save()

    context = {
            'columns': range(7),
            'rows': range(6),
            'game': game
    }
    return render(request, 'index.html', context=context)


def reset(request):
    game, _ = Game.objects.get_or_create(id=1)
    game.board = [[0 for _ in range(6)] for _ in range(7)]
    game.active_player = 1
    game.winner = 0
    game.save()
    return redirect('index')

Add your HTML templates for the game

  • Create templates directory in the sim app

  • Create the game board by creating templates/index.html containing:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Connect Four</title>
    <script src="https://cdn.jsdelivr.net/npm/htmx.org"></script>
    <style>
        .container {
            max-width: 70%;
            margin: auto;
        }

        .container header {
            text-align: center;
            margin-bottom: 20px;
        }

        .token-container {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 10px;
        }
        .board {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 10px;
            margin: auto; /* centers the board horizontally */
            min-height: 500px;
        }
        .row {
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }
        .cell {
            display: flex;
            justify-content: center;
            align-items: center;
        }
        .reset-container {
            padding-top: 20px;
            display: flex;
            justify-content: center;
        }
    </style>
</head>
<body>
<main class="container">
    <header>
        <h1>Connect Four 🟡🔴</h1>
    </header>

    {% if not game.winner %}
        <div>
            <p>It's player {{ game.active_player }}'s turn</p>
        </div>
        <form method="post" class="token-container" hx-boost="true">
            {% csrf_token %}
            {% for column in columns  %}
                <button type="submit" name="column" value="{{ column }}">Column {{ forloop.counter }}</button>
            {% endfor %}
        </form>
    {% endif %}

    <section id="board-wrapper">
        {% if game.winner %}
            <h1>We have a winner 🎉</h1>
            <h2>Player {{ game.winner }} is the winner!</h2>
            <a href="{% url 'reset' %}">Play again?</a>
        {% else %}
            <div class="board" id="board">
            {% for row in game.board %}
                <div class="row">
                    {% for cell in row %}
                        <span class="cell">
                    {% if cell == 1 %}
                        🔴
                    {% elif cell == 2 %}
                        🟡
                    {% else %}
                        ⬜️
                    {%  endif %}
                    </span>
                    {% endfor %}
                </div>
            {% endfor %}
        {% endif %}
        </div>
    </section>

    <div class="reset-container">
        <a href="{% url 'reset' %}">Reset Game</a>
    </div>
</main>
</body>
</html>

Update your urls

  • Update core/url.py to include our app:
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('sim.urls')),
]

  • Create a file at sim/urls.py containing:
from django.urls import path
from sim import views


urlpatterns = [
    path('', views.index, name='index'),
    path('reset/', views.reset, name='reset'),
]

Run your server to play your game 🟡🔴

python manage.py runserver

Complete ✅

Congrats. You've built a Connect Four game with Django and HTMX.

Now you might want to add more features like:

If you want to build another HTMX and Django app, check out: Create a quiz app with HTMX and Django in 8 mins ☑️

Build your Django frontend even faster?

I want to release high-quality products as soon as possible. Probably like you, I want to make my Django product ideas become reality as soon as possible.

That's why I built Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes. Photon Designer outputs neat, clean

Let's get visual.

Do you want to create beautiful django frontends effortlessly?
Click below to book your spot on our early access mailing list (as well as early adopter prices).
Copied link to clipboard 📋