Sudokudon’t
A new project?
Yes. This is the first devlog for a simple (not so simple) project. Let’s start with the basics.
Sudoku
Everybody knows Sudoku: a big 9×9 grid where rows, columns, and internal 3×3 squares don’t contain repeated numbers. When using a Sudoku app, there is so much we take for granted, from generating the Sudoku to displaying an incomplete version for us to fill.
Generating a Sudoku grid is not trivial. Fortunately, there are many posts and tutorials explaining how to do it.
But I’ll ignore them all.
For now, I’ll use a generic algorithm that creates a 9×9 grid from a seed. It does not follow Sudoku’s rules yet; I’ll take care of generating a valid grid later.
The main focus of this phase is allowing users to create rooms. Each new room receives a completed Sudoku, and the players receive an incomplete version to solve.
1 function generateSudoku(): Sudoku {
2 const codePoints = this.seed
3 .split('')
4 .map((char) => char.charCodeAt(0))
5
6 const grid = Array.from(
7 { length: 9 },
8 () => new Array<number>(9).fill(0),
9 )
10
11 let index = 0
12 for (let row = 0; row < 9; row++) {
13 for (let column = 0; column < 9; column++) {
14 grid[row][column] = (codePoints[index] % 9) + 1
15 index = (index + 1) % codePoints.length
16 }
17 }
18
19 this.grid = grid
20 return this
21 }
The generator parameter controls whether the incomplete grid is truly random or predictable. I’m using seedrandom, which provides exactly that predictable randomness.
Server
I’m using NestJS, of course. I already have experience with the framework, and I don’t want to spend time relearning the basics of another backend framework. Sadly, this means I’m creating a game server with Node.js. Performance lovers are allowed to scream now.
Authentication
To speed up development, I reused an authentication implementation from a previous Firebase project. I already regret that decision: it feels like overkill when all I need is a username/password flow with JWT tokens.
Room management
At first, I stored every room in memory. Constantly losing those rooms eventually forced me to persist them with Firebase.
Rooms are updated frequently, which will quickly become a problem because of Firebase’s quota limits and, especially, latency. I’ll probably switch to a non-relational database later, but that is not necessary during early development.
Socket.IO
Actions such as joining, leaving, starting or ending a game, and making a move are executed through WebSockets.
Fortunately, Socket.IO has built-in rooms, which means I can emit events to a specific room. Joining is managed by the server, so it should be straightforward to control who can and cannot make a move during a game.
@UsePipes(new ValidationPipe())
@UseGuards(WsAuthGuard)
@SubscribeMessage('joinRoom')
async function joinRoom(
@MessageBody() joinRoomDto: JoinRoomDto,
@GetSession() session,
@ConnectedSocket() client: Socket,
) {
const room = await this.roomsService.joinRoom(session, joinRoomDto)
client.join(`room:${room.code}`)
this.server.to(`room:${room.code}`).emit('userJoined', room)
return room
}
Next steps
There is still a lot to do, but for now I’ll focus on:
- Creating a proof of concept for web, Android, and iOS with Tamagui.
- Implementing a valid Sudoku-generation algorithm and a strategy for digging holes in the grid displayed to the client.
- Implementing matchmaking. Rooms are fun, but I’d also like players to tap one button and be matched with someone at a similar experience level.
