jetzig/demo/src/app/views/websockets.zig
Bob Farrell d3b3ae63cf WIP
2025-04-22 19:42:20 +01:00

223 lines
6.4 KiB
Zig

const std = @import("std");
const jetzig = @import("jetzig");
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}
pub fn get(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn post(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.created);
}
pub fn put(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn patch(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub fn delete(id: []const u8, request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
_ = id;
return request.render(.ok);
}
pub const Channel = struct {
pub fn open(channel: jetzig.channels.Channel) !void {
if (channel.get("cells") == null) try initGame(channel);
try channel.sync();
}
pub fn receive(message: jetzig.channels.Message) !void {
const value = try message.value();
if (value.getT(.boolean, "reset") == true) {
try resetGame(message.channel);
try message.channel.sync();
return;
}
const cell: usize = if (value.getT(.integer, "cell")) |integer|
@intCast(integer)
else
return;
const cells = message.channel.getT(.array, "cells") orelse return;
const items = cells.items();
var grid: [9]Game.State = undefined;
for (0..9) |id| {
if (items[id].* != .null) {
grid[id] = if (items[id].eql("player")) .player else .cpu;
} else {
grid[id] = .empty;
}
}
var game = Game{ .grid = grid };
game.evaluate();
if (game.winner != null) {
try message.channel.publish(.{ .err = "Game is already over." });
return;
}
if (game.movePlayer(cell)) {
items[cell] = message.data.string("player");
if (game.winner == null) {
items[game.moveCpu()] = message.data.string("cpu");
}
if (game.winner) |winner| {
try message.channel.put("winner", @tagName(winner));
var results = message.channel.getT(.object, "results") orelse return;
const count = results.getT(.integer, @tagName(winner)) orelse return;
try results.put(@tagName(winner), count + 1);
}
}
try message.channel.sync();
}
fn resetGame(channel: jetzig.channels.Channel) !void {
try channel.put("winner", null);
var cells = try channel.put("cells", .array);
for (0..9) |_| try cells.append(null);
}
fn initGame(channel: jetzig.channels.Channel) !void {
var results = try channel.put("results", .object);
try results.put("cpu", 0);
try results.put("player", 0);
try results.put("ties", 0);
try resetGame(channel);
}
};
const Game = struct {
grid: [9]State,
winner: ?State = null,
pub const State = enum { empty, player, cpu, tie };
pub fn movePlayer(game: *Game, cell: usize) bool {
if (cell >= game.grid.len) return false;
if (game.grid[cell] != .empty) return false;
game.grid[cell] = .player;
game.evaluate();
return true;
}
pub fn moveCpu(game: *Game) usize {
std.debug.assert(game.winner == null);
var available: [9]usize = undefined;
var available_len: usize = 0;
for (game.grid, 0..) |cell, cell_index| {
if (cell == .empty) {
available[available_len] = cell_index;
available_len += 1;
}
}
std.debug.assert(available_len > 0);
const choice = available[std.crypto.random.intRangeAtMost(usize, 0, available_len - 1)];
game.grid[choice] = .cpu;
game.evaluate();
return choice;
}
fn evaluate(game: *Game) void {
var full = true;
for (game.grid) |cell| {
if (cell == .empty) full = false;
}
if (full) {
game.winner = .tie;
return;
}
const patterns = [_][3]usize{
.{ 0, 1, 2 },
.{ 3, 4, 5 },
.{ 6, 7, 8 },
.{ 0, 3, 6 },
.{ 1, 4, 7 },
.{ 2, 5, 8 },
.{ 0, 4, 8 },
.{ 2, 4, 6 },
};
for (patterns) |pattern| {
var cpu_winner = true;
var player_winner = true;
for (pattern) |cell_index| {
if (game.grid[cell_index] != .cpu) cpu_winner = false;
if (game.grid[cell_index] != .player) player_winner = false;
}
std.debug.assert(!(cpu_winner and player_winner));
if (cpu_winner) game.winner = .cpu;
if (player_winner) game.winner = .player;
}
}
};
test "index" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.GET, "/websockets", .{});
try response.expectStatus(.ok);
}
test "get" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.GET, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}
test "post" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.POST, "/websockets", .{});
try response.expectStatus(.created);
}
test "put" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.PUT, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}
test "patch" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.PATCH, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}
test "delete" {
var app = try jetzig.testing.app(std.testing.allocator, @import("routes"));
defer app.deinit();
const response = try app.request(.DELETE, "/websockets/example-id", .{});
try response.expectStatus(.ok);
}