Build a Connect Four game with HTMX and Django in 8 minutes ๐ก๐ด
Published: April 11, 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 djangodjango-admin startproject core .python manage.py startapp sim
- Add our app sim to the
INSTALLED_APPS
in settings.py:
# settings.pyINSTALLED_APPS = ['sim',...]
Add your game model with logic
- Add the following code to
sim/models.py
:
from django.db import modelsdef 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_playerbreakdef 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 andself.board[col + 1][row] == self.active_player andself.board[col + 2][row] == self.active_player andself.board[col + 3][row] == self.active_player:self.winner = self.active_playerreturn True# Check vertical# I.e.,# 1# 1# 1# 1for col in range(7):for row in range(3):if self.board[col][row] == self.active_player andself.board[col][row + 1] == self.active_player andself.board[col][row + 2] == self.active_player andself.board[col][row + 3] == self.active_player:self.winner = self.active_playerreturn True# Check positive diagonal# I.e.,# 1# 1# 1# 1for col in range(4):for row in range(3):if self.board[col][row] == self.active_player andself.board[col + 1][row + 1] == self.active_player andself.board[col + 2][row + 2] == self.active_player andself.board[col + 3][row + 3] == self.active_player:self.winner = self.active_playerreturn True# Check negative diagonal# I.e.,# 1# 1# 1# 1for col in range(4):for row in range(5, 2, -1):if self.board[col][row] == self.active_player andself.board[col + 1][row - 1] == self.active_player andself.board[col + 2][row - 2] == self.active_player andself.board[col + 3][row - 3] == self.active_player:self.winner = self.active_playerreturn True# No winner, switch playerself.active_player = 1 if self.active_player == 2 else 2return False
- Make migrations and migrate the database:
python manage.py makemigrationspython manage.py migrate
Add your game views ( sim/views.py
)
from django.shortcuts import render, redirect, reversefrom django.views.decorators.http import require_http_methodsfrom .models import Gamefrom 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 activeif 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 = 1game.winner = 0game.save()return redirect('index')
Add your HTML templates for the game
-
Create
templates
directory in thesim
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 adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('', include('sim.urls')),]
- Create a file at
sim/urls.py
containing:
from django.urls import pathfrom sim import viewsurlpatterns = [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:
- Deploying the app
- Real-time multiplayer (You could use the techniques from The simplest way to build an instant messaging app with Django ๐ฎ)
If you want to build another HTMX and Django app, check out: Create a quiz app with HTMX and Django in 8 mins โ๏ธ