Skip to content

Commit a1afd34

Browse files
committed
update docs
1 parent 3ebc705 commit a1afd34

File tree

2 files changed

+26
-8
lines changed

2 files changed

+26
-8
lines changed

README.md

+11-6
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,16 @@ This code is interesting for the following reasons :
9898

9999
- Its all written in Zig
100100
- It uses the excellent http.zig library https://github.com/karlseguin/http.zig
101-
- Note that the router is decorated inside the Game object ... the implications of this are that it is possible to create 'web components' using this
102-
zig/htmx approach that are all self contained, and can be imported into other projects, without having to know about connecting up routes. Interesting.
103-
- Uses SSE / Event Streams to keep it all realtime updates with pub/sub from multiple players
101+
- Its about as simple as doing the same thing in Go, there is really nothing too nasty required in the code.
102+
- The router, and all the HTML contents is part of the Game object ... the implications of this are that it is possible to create 'web components' using this
103+
zig/htmx approach that are all self contained, and can be imported into other projects, without having to know about connecting up routes, or pulling in content. Interesting.
104+
- Uses SSE / Event Streams to keep it all realtime updates with pub/sub from multiple players. Is simple, and it works.
105+
- Demonstrates how to do threading, thread conditions / signalling, and using mutexes to make object updates thread safe.
104106
- No websockets needed
105107
- There is pretty much NO JAVASCRIPT on the frontend. It uses HTMX https://htmx.org ... which is an alternative approach to web clients, where the frontend is pure hypertext, and everything is done on the backend
106108
- There is a tiny bit of JS, to update the session ID, but im still thinking up a way of getting rid of that as well.
107-
- Uses std.fmt.print() as the templating engine. I didnt know before, but you can used named tags inside the comptime format string, and use those to reference fields in the args param.
108-
Actually makes for a half decent templating tool.
109+
- Uses std.fmt.print() as the templating engine. I didnt know before, but you can use named tags inside the comptime format string, and use those to reference fields in the args param.
110+
Actually makes for a half decent templating tool, without having to find and import a templating lib.
109111

110112

111113
## HTMX thoughts
@@ -115,7 +117,10 @@ Yeah, its pretty cool, worth adding to your toolbelt. Not sure its really a 'rep
115117
What it is 100% excellent for though is for doing apps where there is a lot of shared state between users. (Like a turn based game !) In that case, doing everything on the server, and not having any state
116118
at all on the frontend to sync, is really ideal. Its a good fit to this sort of problem.
117119

120+
Its super robust. You can do horrible things like reboot the backend, or hard refresh the front end(s) ... and it maintains state without a hiccup, and without needing any exception handling code at all.
121+
Very nice. It would be a nightmare doing this in react.
122+
118123
There are some very complex things you could do with a turn-based-multiplayer-game type of model though (doesnt have to be a game even) And being able to do that without touching JS, or
119124
writing a tonne of code to synch state on the frontends is really nice.
120125

121-
Its also a reasonable model for doing shared state between GUI frontends, just using the HTTP/SSE protocol too.
126+
Its also a reasonable model for doing shared state between GUI frontends, just using the HTTP/SSE protocol too. Its just HTTP requests, so the networking is totally portable.

src/game.zig

+15-2
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ current_player: u8 = 0,
5656
prng: std.rand.Xoshiro256 = undefined,
5757
watcher: std.Thread = undefined,
5858

59+
/// init returns a new Game object
5960
pub fn init(grid_x: u8, grid_y: u8, players: u8, needed_to_win: u8, flipper: u8) !Self {
6061
if (players > 8) {
6162
return Errors.GameError.TooManyPlayers;
@@ -80,11 +81,13 @@ pub fn init(grid_x: u8, grid_y: u8, players: u8, needed_to_win: u8, flipper: u8)
8081
return s;
8182
}
8283

84+
/// startWatcher starts a thread to watch a given game
8385
pub fn startWatcher(self: *Self) !void {
8486
self.watcher = try std.Thread.spawn(.{}, Self.watcherThread, .{self});
8587
self.watcher.detach();
8688
}
8789

90+
/// watcherThread loops forever, updating the state based upon expiring clocks
8891
fn watcherThread(self: *Self) void {
8992
while (true) {
9093
self.game_mutex.lock();
@@ -107,6 +110,8 @@ fn watcherThread(self: *Self) void {
107110
}
108111
}
109112

113+
/// randPlayerMode will return a newly generated mode base of the % chance of things in the current game settings.
114+
/// use this to get a random mode for the next player's turn at the end of the turn.
110115
fn randPlayerMode(self: *Self) PlayerMode {
111116
const dice = (self.prng.random().int(u8) % (11)) * 10;
112117
if (dice < self.flipper_chance) {
@@ -118,6 +123,7 @@ fn randPlayerMode(self: *Self) PlayerMode {
118123
return .normal;
119124
}
120125

126+
/// addRoutes sets up the routes and handlers used in this game object
121127
pub fn addRoutes(self: *Self, router: anytype) void {
122128
_ = self;
123129
router.get("/events", Self.events); // SSE event stream of game state changes
@@ -140,6 +146,7 @@ fn signal(self: *Self, ev: Event) void {
140146
std.log.info("signal event {}", .{ev});
141147
}
142148

149+
/// reboot will reboot the server back to the vanilla state. Call this when everything has timed out, and we want to go right back to the original start
143150
fn reboot(self: *Self) void {
144151
self.game_mutex.lock();
145152
defer self.game_mutex.unlock();
@@ -153,6 +160,7 @@ fn reboot(self: *Self) void {
153160
self.signal(.init);
154161
}
155162

163+
/// restart will set the game back to the setup phase, using the current Game parameters
156164
fn restart(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
157165
_ = req;
158166
self.game_mutex.lock();
@@ -163,7 +171,7 @@ fn restart(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
163171
self.signal(.init);
164172
}
165173

166-
// clock() function emits an event of type clock with the current elapsed duration
174+
/// clock() function emits an event of type clock with the current number of remaining seconds until the next expiry time
167175
fn clock(self: *Self, stream: std.net.Stream) !void {
168176
self.game_mutex.lock();
169177
defer self.game_mutex.unlock();
@@ -183,13 +191,14 @@ fn getPlayer(self: *Self, req: *httpz.Request) u8 {
183191
return std.fmt.parseInt(u8, req.headers.get("x-player") orelse "", 10) catch @as(u8, 0);
184192
}
185193

194+
/// zeroWing handler to get the background image
186195
fn zeroWing(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
187196
_ = self;
188197
_ = req;
189198
res.body = @embedFile("images/zero-wing-gradient.jpg");
190199
}
191200

192-
// header() GET req returns the title header, depending on the game state
201+
/// header() GET req returns the title header, depending on the game state
193202
fn header(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
194203
const player = self.getPlayer(req);
195204
std.log.info("GET /header {} player {}", .{ self.state, player });
@@ -261,6 +270,7 @@ fn app(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
261270
}
262271
}
263272

273+
/// calcBoardClass is used to get the CSS class for the board, based on whether it's this players turn, and what mode they are in
264274
fn calcBoardClass(self: *Self, player: u8) []const u8 {
265275
if (player == self.current_player) {
266276
return switch (self.player_mode) {
@@ -272,6 +282,7 @@ fn calcBoardClass(self: *Self, player: u8) []const u8 {
272282
return "inactive-player";
273283
}
274284

285+
/// showBoard writes the current board to the HTTP response, based on the current player and what state they are in
275286
fn showBoard(self: *Self, player: u8, res: *httpz.Response) !void {
276287
const w = res.writer();
277288

@@ -518,6 +529,8 @@ fn setup(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
518529
}
519530
}
520531

532+
/// events GET handler is an SSE stream that emits events whenever the state changes
533+
/// or a clock event expires. Uses the Game.event_condition to synch with the outer threads
521534
fn events(self: *Self, req: *httpz.Request, res: *httpz.Response) !void {
522535
std.log.info("(event-source) GET /events {}", .{self.state});
523536
_ = req;

0 commit comments

Comments
 (0)