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 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 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 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:
- 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 โ๏ธ