refactor: go all in on Element abstraction to prepare for multiplayer support
parent
0a0a7d8d49
commit
32f8b66761
|
@ -0,0 +1,50 @@
|
|||
pub const Card = @import("./Element/Card.zig");
|
||||
pub const HBox = @import("./Element/HBox.zig");
|
||||
pub const Link = @import("./Element/Link.zig");
|
||||
pub const Page = @import("./Element/Page.zig");
|
||||
pub const Pile = @import("./Element/Pile.zig");
|
||||
pub const Text = @import("./Element/Text.zig");
|
||||
pub const View = @import("./Element/View.zig");
|
||||
|
||||
pointer: ?*anyopaque,
|
||||
interface: *const Interface,
|
||||
|
||||
pub const Element = @This();
|
||||
|
||||
pub const Error = error{
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const Interface = struct {
|
||||
destroy: *const fn (?*anyopaque) void,
|
||||
minimum_size: *const fn (?*anyopaque, RenderResources) [2]f32,
|
||||
render: *const fn (?*anyopaque, *seizer.Canvas, RenderResources, [2]f32, [2]f32) void,
|
||||
get_actions: *const fn (?*anyopaque, *std.ArrayList(Action), RenderResources, [2]f32, [2]f32) Error!void,
|
||||
};
|
||||
|
||||
pub const RenderResources = struct {
|
||||
hovered: ?Element.Command,
|
||||
deck: DeckSprites,
|
||||
font: seizer.Canvas.Font,
|
||||
};
|
||||
|
||||
pub const Action = struct {
|
||||
center: [2]f32,
|
||||
command: Command,
|
||||
};
|
||||
|
||||
pub const Command = union(enum) {
|
||||
goto_reference: protocol.Reference,
|
||||
callback: struct {
|
||||
pointer: ?*anyopaque,
|
||||
function: *const fn (?*anyopaque) void,
|
||||
},
|
||||
};
|
||||
|
||||
const DeckSprites = assets.DeckSprites;
|
||||
|
||||
const protocol = @import("./protocol.zig");
|
||||
const assets = @import("./assets.zig");
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,101 @@
|
|||
allocator: std.mem.Allocator,
|
||||
visual: Visual,
|
||||
command: ?[]const u8,
|
||||
marked: bool,
|
||||
|
||||
const Visual = union(enum) {
|
||||
back,
|
||||
card: assets.Card,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, options: struct { visual: Visual, command: ?[]const u8, marked: ?bool = null }) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.visual = options.visual,
|
||||
.command = options.command,
|
||||
.marked = options.marked orelse false,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
_ = this;
|
||||
|
||||
return .{
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])),
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const mark_offset = if (this.marked)
|
||||
[2]f32{
|
||||
0,
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * -0.4,
|
||||
}
|
||||
else
|
||||
[2]f32{ 0, 0 };
|
||||
|
||||
switch (this.visual) {
|
||||
.back => render_resources.deck.tilesheet.renderTile(
|
||||
canvas,
|
||||
render_resources.deck.back,
|
||||
.{ min[0] + mark_offset[0], min[1] + mark_offset[1] },
|
||||
.{},
|
||||
),
|
||||
.card => |card| render_resources.deck.tilesheet.renderTile(
|
||||
canvas,
|
||||
render_resources.deck.getTileForCard(card),
|
||||
.{ @floor(min[0] + mark_offset[0]), @floor(min[1] + mark_offset[1]) },
|
||||
.{},
|
||||
),
|
||||
}
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
canvas.rect(
|
||||
.{ min[0] + mark_offset[0], min[1] + mark_offset[1] },
|
||||
.{ max[0] - min[0], max[1] - min[1] },
|
||||
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
_ = render_resources;
|
||||
|
||||
if (this.command) |command| {
|
||||
try actions.append(.{
|
||||
.center = [2]f32{
|
||||
(min[0] + max[0]) / 2,
|
||||
(min[1] + max[1]) / 2,
|
||||
},
|
||||
.command = command,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,130 @@
|
|||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Element),
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn addElement(this: *@This(), child_element: Element) !void {
|
||||
try this.children.append(this.allocator, child_element);
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_destroy(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
for (this.children.items) |child| {
|
||||
child.interface.destroy(child.pointer);
|
||||
}
|
||||
this.children.deinit(this.allocator);
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
var minimum_size = [2]f32{ 0, 0 };
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
minimum_size = .{
|
||||
minimum_size[0] + child_size[0],
|
||||
@max(minimum_size[1], child_size[1]),
|
||||
};
|
||||
}
|
||||
|
||||
return minimum_size;
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const num_spaces = if (empty_space > 0) this.children.items.len + 1 else this.children.items.len - 1;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt(num_spaces));
|
||||
|
||||
var x: f32 = min[0] + @max(space_around, 0);
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
child.interface.render(child.pointer, canvas, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt((this.children.items.len + 1)));
|
||||
|
||||
var x: f32 = min[0] + space_around;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
try child.interface.get_actions(child.pointer, actions, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,76 @@
|
|||
allocator: std.mem.Allocator,
|
||||
child: Element,
|
||||
reference: protocol.Reference,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, child: Element, reference: protocol.Reference) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.child = child,
|
||||
.reference = reference,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_destroy(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
this.child.interface.destroy(this.child.pointer);
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
return this.child.interface.minimum_size(this.child.pointer, render_resources);
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
// render child element
|
||||
this.child.interface.render(this.child.pointer, canvas, render_resources, min, max);
|
||||
|
||||
// render selection indicator on top if this link is selected
|
||||
if (render_resources.hovered != null and std.meta.eql(render_resources.hovered.?, Element.Command{ .goto_reference = this.reference })) {
|
||||
canvas.rect(
|
||||
.{ min[0], min[1] },
|
||||
.{ max[0] - min[0], max[1] - min[1] },
|
||||
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
_ = render_resources;
|
||||
|
||||
try actions.append(.{
|
||||
.center = [2]f32{
|
||||
(min[0] + max[0]) / 2,
|
||||
(min[1] + max[1]) / 2,
|
||||
},
|
||||
.command = .{ .goto_reference = this.reference },
|
||||
});
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const protocol = @import("../protocol.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,150 @@
|
|||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Child),
|
||||
|
||||
pub const Child = struct {
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
/// both the parent and child element. This defines where on the parent that pin
|
||||
/// passes through.
|
||||
///
|
||||
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
|
||||
/// the numbers are negative.
|
||||
anchor_in_parent: [2]f32,
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
/// both the parent and child element. This defines where on the child that pin
|
||||
/// passes through.
|
||||
///
|
||||
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
|
||||
/// the numbers are negative.
|
||||
anchor_in_child: [2]f32,
|
||||
element: Element,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn addElement(this: *@This(), anchor_in_parent: [2]f32, anchor_in_child: [2]f32, child_element: Element) !void {
|
||||
try this.children.append(this.allocator, Child{
|
||||
.anchor_in_parent = anchor_in_parent,
|
||||
.anchor_in_child = anchor_in_child,
|
||||
.element = child_element,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_destroy(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
for (this.children.items) |child| {
|
||||
child.element.interface.destroy(child.element.pointer);
|
||||
}
|
||||
this.children.deinit(this.allocator);
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
var minimum_size = [2]f32{ 0, 0 };
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
minimum_size = .{
|
||||
@max(minimum_size[0], child_size[0]),
|
||||
@max(minimum_size[1], child_size[1]),
|
||||
};
|
||||
}
|
||||
|
||||
return minimum_size;
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
|
||||
const child_min = [2]f32{
|
||||
@max(min[0], pos_in_parent[0] - pos_in_child[0]),
|
||||
@max(min[1], pos_in_parent[1] - pos_in_child[1]),
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
@min(max[0], pos_in_parent[0] + (child_size[0] - pos_in_child[0])),
|
||||
@min(max[1], pos_in_parent[1] + (child_size[1] - pos_in_child[1])),
|
||||
};
|
||||
|
||||
child.element.interface.render(child.element.pointer, canvas, render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
|
||||
const child_min = [2]f32{
|
||||
pos_in_parent[0] - pos_in_child[0],
|
||||
pos_in_parent[1] - pos_in_child[1],
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
pos_in_parent[0] + (child_size[0] - pos_in_child[0]),
|
||||
pos_in_parent[1] + (child_size[1] - pos_in_child[1]),
|
||||
};
|
||||
|
||||
try child.element.interface.get_actions(child.element.pointer, actions, render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,93 @@
|
|||
allocator: std.mem.Allocator,
|
||||
cards: []Card,
|
||||
hidden: bool = false,
|
||||
command: ?[]const u8 = null,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, cards: []const Card) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
const cards_owned = try allocator.dupe(Card, cards);
|
||||
errdefer allocator.free(cards_owned);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.cards = cards_owned,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return .{
|
||||
@floatFromInt(render_resources.deck.tilesheet.tile_size[0]),
|
||||
@as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) + @as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) * @as(f32, @floatFromInt(this.cards.len)) / 52.0 * 0.25,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const start_y: f32 = max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1]));
|
||||
|
||||
for (this.cards, 0..) |card, i| {
|
||||
const oy = -@as(f32, @floatFromInt(i)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
|
||||
if (this.hidden) {
|
||||
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.back, .{
|
||||
min[0],
|
||||
start_y + oy,
|
||||
}, .{});
|
||||
} else {
|
||||
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.getTileForCard(card), .{
|
||||
min[0],
|
||||
start_y + oy,
|
||||
}, .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
const oy = -@as(f32, @floatFromInt(this.cards.len)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
|
||||
canvas.rect(
|
||||
.{ min[0], start_y + oy },
|
||||
.{ @floatFromInt(render_resources.deck.tilesheet.tile_size[0]), @floatFromInt(render_resources.deck.tilesheet.tile_size[1]) },
|
||||
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.command) |command| {
|
||||
const center = [2]f32{
|
||||
min[0] + @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])) / 2,
|
||||
max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * (2.0 - @as(f32, @floatFromInt(this.cards.len)) / (52.0 * 4)),
|
||||
};
|
||||
|
||||
try actions.append(.{
|
||||
.center = center,
|
||||
.command = command,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const Card = assets.Card;
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,62 @@
|
|||
allocator: std.mem.Allocator,
|
||||
text: []const u8,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, text: []const u8) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
const text_owned = try allocator.dupe(u8, text);
|
||||
errdefer allocator.free(text_owned);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.text = text_owned,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_destroy(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
this.allocator.free(this.text);
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
return render_resources.font.textSize(this.text, 1);
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
_ = max;
|
||||
_ = render_resources;
|
||||
|
||||
_ = canvas.writeText(min, this.text, .{});
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
_ = pointer;
|
||||
_ = actions;
|
||||
_ = render_resources;
|
||||
_ = min;
|
||||
_ = max;
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,179 @@
|
|||
allocator: std.mem.Allocator,
|
||||
reference: protocol.Reference,
|
||||
response: ?protocol.Response,
|
||||
|
||||
link_actions_in_view: std.AutoHashMapUnmanaged(*LinkProxy, void) = .{},
|
||||
|
||||
const View = @This();
|
||||
|
||||
const LinkProxy = struct {
|
||||
view: *View,
|
||||
reference: protocol.Reference,
|
||||
|
||||
fn callback(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
this.view.setReference(this.reference);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, reference: protocol.Reference) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.reference = reference,
|
||||
.response = null,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn setReference(this: *@This(), reference: protocol.Reference) void {
|
||||
if (this.response) |response| {
|
||||
response.arena.deinit();
|
||||
this.response = null;
|
||||
}
|
||||
|
||||
this.reference = reference;
|
||||
|
||||
var action_proxies_iter = this.link_actions_in_view.keyIterator();
|
||||
while (action_proxies_iter.next()) |link_proxy| {
|
||||
this.allocator.destroy(link_proxy.*);
|
||||
}
|
||||
this.link_actions_in_view.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.destroy = &element_destroy,
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const LOADING_TEXT = "Loading...";
|
||||
|
||||
pub fn element_destroy(pointer: ?*anyopaque) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
if (this.response) |response| {
|
||||
switch (response.body) {
|
||||
.element => |root| root.interface.destroy(root.pointer),
|
||||
}
|
||||
response.arena.deinit();
|
||||
}
|
||||
|
||||
var action_proxies_iter = this.link_actions_in_view.keyIterator();
|
||||
while (action_proxies_iter.next()) |link_proxy| {
|
||||
this.allocator.destroy(link_proxy.*);
|
||||
}
|
||||
this.link_actions_in_view.deinit(this.allocator);
|
||||
|
||||
switch (this.reference) {
|
||||
.handler => |handler| if (handler.interface.deinit) |deinit| deinit(handler.pointer),
|
||||
}
|
||||
|
||||
this.allocator.destroy(this);
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
if (this.response) |response| {
|
||||
switch (response.body) {
|
||||
.element => |root| return root.interface.minimum_size(root.pointer, render_resources),
|
||||
}
|
||||
} else {
|
||||
return render_resources.font.textSize(LOADING_TEXT, 1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.response == null) {
|
||||
this.response = this.reference.handler.interface.handle(this.reference.handler.pointer, .{
|
||||
.allocator = this.allocator,
|
||||
}) catch response_error: {
|
||||
std.log.warn("Could not get response", .{});
|
||||
break :response_error null;
|
||||
};
|
||||
}
|
||||
|
||||
if (this.response) |response| {
|
||||
var child_render_resources = render_resources;
|
||||
|
||||
if (render_resources.hovered) |hovered| {
|
||||
switch (hovered) {
|
||||
.callback => |cb| {
|
||||
if (this.link_actions_in_view.getKey(@ptrCast(@alignCast(cb.pointer)))) |link_proxy| {
|
||||
child_render_resources.hovered = .{ .goto_reference = link_proxy.reference };
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
|
||||
switch (response.body) {
|
||||
.element => |root| root.interface.render(root.pointer, canvas, child_render_resources, min, max),
|
||||
}
|
||||
} else {
|
||||
_ = canvas.writeText(min, LOADING_TEXT, .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
// clean up previous link actions
|
||||
var action_proxies_iter = this.link_actions_in_view.keyIterator();
|
||||
while (action_proxies_iter.next()) |link_proxy| {
|
||||
this.allocator.destroy(link_proxy.*);
|
||||
}
|
||||
this.link_actions_in_view.clearRetainingCapacity();
|
||||
|
||||
var children_actions = std.ArrayList(Element.Action).init(this.allocator);
|
||||
defer children_actions.deinit();
|
||||
|
||||
if (this.response) |response| {
|
||||
switch (response.body) {
|
||||
.element => |root| {
|
||||
try root.interface.get_actions(root.pointer, &children_actions, render_resources, min, max);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
for (children_actions.items) |child_action| {
|
||||
switch (child_action.command) {
|
||||
.goto_reference => |ref| {
|
||||
const link_proxy_action = try this.allocator.create(LinkProxy);
|
||||
try this.link_actions_in_view.put(this.allocator, link_proxy_action, {});
|
||||
link_proxy_action.* = .{
|
||||
.view = this,
|
||||
.reference = ref,
|
||||
};
|
||||
try actions.append(.{
|
||||
.center = child_action.center,
|
||||
.command = .{
|
||||
.callback = .{
|
||||
.pointer = link_proxy_action,
|
||||
.function = &LinkProxy.callback,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
.callback => try actions.append(child_action),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Element = @import("../Element.zig");
|
||||
const protocol = @import("../protocol.zig");
|
||||
const assets = @import("../assets.zig");
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
|
@ -0,0 +1,76 @@
|
|||
pub const main_menu = Handler{
|
||||
.pointer = null,
|
||||
.interface = &.{
|
||||
.handle = &main_menu_handler,
|
||||
.deinit = null,
|
||||
},
|
||||
};
|
||||
|
||||
fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
var join_multiplayer_text = try Element.Text.create(arena.allocator(), "Join Multiplayer Game");
|
||||
var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = join_multiplayer_game });
|
||||
|
||||
var host_multiplayer_text = try Element.Text.create(arena.allocator(), "Host Multiplayer Game");
|
||||
var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = host_multiplayer_game });
|
||||
|
||||
var play_game_hbox = try Element.HBox.create(arena.allocator());
|
||||
try play_game_hbox.addElement(join_multiplayer_link.element());
|
||||
try play_game_hbox.addElement(host_multiplayer_link.element());
|
||||
|
||||
var page = try Element.Page.create(arena.allocator());
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, play_game_hbox.element());
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
pub const join_multiplayer_game = Handler{
|
||||
.pointer = null,
|
||||
.interface = &.{
|
||||
.handle = &join_multiplayer_game_handler,
|
||||
.deinit = null,
|
||||
},
|
||||
};
|
||||
|
||||
fn join_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
var text = try Element.Text.create(arena.allocator(), "Joining Multiplayer Game");
|
||||
|
||||
var page = try Element.Page.create(arena.allocator());
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
pub const host_multiplayer_game = Handler{
|
||||
.pointer = null,
|
||||
.interface = &.{
|
||||
.handle = &host_multiplayer_game_handler,
|
||||
.deinit = null,
|
||||
},
|
||||
};
|
||||
|
||||
fn host_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
|
||||
var arena = std.heap.ArenaAllocator.init(request.allocator);
|
||||
errdefer arena.deinit();
|
||||
|
||||
var text = try Element.Text.create(arena.allocator(), "Hosting Multiplayer Game");
|
||||
|
||||
var page = try Element.Page.create(arena.allocator());
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
|
||||
return Response{ .arena = arena, .body = .{ .element = page.element() } };
|
||||
}
|
||||
|
||||
const Handler = protocol.Handler;
|
||||
const Request = protocol.Request;
|
||||
const Response = protocol.Response;
|
||||
|
||||
const protocol = @import("./protocol.zig");
|
||||
const Element = @import("./Element.zig");
|
||||
|
||||
const std = @import("std");
|
792
src/main.zig
792
src/main.zig
|
@ -1,36 +1,8 @@
|
|||
var gl_binding: gl.Binding = undefined;
|
||||
|
||||
const HandlerError = error{OutOfMemory};
|
||||
const Handler = *const fn (std.mem.Allocator, Request) HandlerError!Response;
|
||||
|
||||
const Request = struct {
|
||||
game_state: GameState,
|
||||
command: ?[]const u8,
|
||||
};
|
||||
const Response = union(enum) {
|
||||
page: Element,
|
||||
transition: struct {
|
||||
game_state: GameState,
|
||||
history_type: HistoryType,
|
||||
reset_selection: bool = false,
|
||||
},
|
||||
|
||||
pub const HistoryType = enum {
|
||||
/// This game state should act as the start of history, and all previous history should
|
||||
/// be removed.
|
||||
start,
|
||||
/// This game state shouldn't be added to the history.
|
||||
transient,
|
||||
/// This game state should be added to the history, as a point in time that can be reverted
|
||||
/// to.
|
||||
important,
|
||||
};
|
||||
};
|
||||
|
||||
const GameState = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
prng: std.rand.DefaultPrng,
|
||||
handler: Handler,
|
||||
draw_pile: std.ArrayListUnmanaged(Card),
|
||||
discard_pile: std.ArrayListUnmanaged(Card),
|
||||
hands: []std.ArrayListUnmanaged(Card),
|
||||
|
@ -69,7 +41,6 @@ const GameState = struct {
|
|||
return @This(){
|
||||
.allocator = allocator,
|
||||
.prng = prng,
|
||||
.handler = &drawCardHandler,
|
||||
.draw_pile = draw_pile,
|
||||
.discard_pile = discard_pile,
|
||||
.hands = hands,
|
||||
|
@ -139,6 +110,12 @@ pub fn main() !void {
|
|||
}
|
||||
defer seizer.backend.glfw.c.glfwTerminate();
|
||||
|
||||
// Initialize enet
|
||||
if (c.enet_initialize() != 0) {
|
||||
return error.EnetInitialize;
|
||||
}
|
||||
defer c.enet_deinitialize();
|
||||
|
||||
// update joystick to gamepad mappings
|
||||
update_gamepad_mappings_file: {
|
||||
const sdl_controller_config_filepath = std.process.getEnvVarOwned(gpa.allocator(), "SDL_GAMECONTROLLERCONFIG_FILE") catch break :update_gamepad_mappings_file;
|
||||
|
@ -206,26 +183,15 @@ pub fn main() !void {
|
|||
var canvas = try seizer.Canvas.init(gpa.allocator(), .{});
|
||||
defer canvas.deinit(gpa.allocator());
|
||||
|
||||
// game state
|
||||
var history = std.ArrayList(GameState).init(gpa.allocator());
|
||||
defer {
|
||||
for (history.items) |*state| {
|
||||
state.deinit();
|
||||
}
|
||||
history.deinit();
|
||||
}
|
||||
try history.append(try GameState.init(gpa.allocator(), std.crypto.random.int(u64), 1));
|
||||
var history_type = Response.HistoryType.start;
|
||||
// get main screen
|
||||
const main_view = try Element.View.create(gpa.allocator(), .{ .handler = LocalUI.main_menu });
|
||||
defer main_view.element().interface.destroy(main_view.element().pointer);
|
||||
|
||||
var root_element: ?Element = null;
|
||||
var response_arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer response_arena.deinit();
|
||||
|
||||
var actions = std.ArrayList(Action).init(gpa.allocator());
|
||||
var actions = std.ArrayList(Element.Action).init(gpa.allocator());
|
||||
defer actions.deinit();
|
||||
|
||||
// TODO: Restore hovered_action when undoing
|
||||
var hovered_action: ?Action = null;
|
||||
var hovered_action: ?Element.Action = null;
|
||||
|
||||
if (seizer.backend.glfw.c.glfwJoystickIsGamepad(seizer.backend.glfw.c.GLFW_JOYSTICK_1) != 0) {
|
||||
std.log.info("detected gamepad = \"{?s}\" {?s}", .{ seizer.backend.glfw.c.glfwGetGamepadName(seizer.backend.glfw.c.GLFW_JOYSTICK_1), seizer.backend.glfw.c.glfwGetJoystickGUID(seizer.backend.glfw.c.GLFW_JOYSTICK_1) });
|
||||
|
@ -310,26 +276,21 @@ pub fn main() !void {
|
|||
prev_controller_input_state = controller_input_state;
|
||||
}
|
||||
|
||||
var request_command: ?[]const u8 = null;
|
||||
defer if (request_command) |command| gpa.allocator().free(command);
|
||||
|
||||
if (hovered_action) |hovered| {
|
||||
if (input_state.action) {
|
||||
request_command = try gpa.allocator().dupe(u8, hovered.command);
|
||||
root_element = null;
|
||||
switch (hovered.command) {
|
||||
.goto_reference => {
|
||||
std.debug.panic("Got `.goto_reference` action at top-level; expected `main_view` to proxy them all into LinkProxy callbacks.", .{});
|
||||
},
|
||||
.callback => |cb| {
|
||||
cb.function(cb.pointer);
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if (actions.items.len > 0) {
|
||||
hovered_action = actions.items[0];
|
||||
}
|
||||
|
||||
if (input_state.undo and history.items.len > 1) {
|
||||
var discarded_state = history.pop();
|
||||
history_type = .important;
|
||||
discarded_state.deinit();
|
||||
root_element = null;
|
||||
hovered_action = null;
|
||||
}
|
||||
|
||||
if (hovered_action) |hovered| {
|
||||
var direction = [2]f32{ 0, 0 };
|
||||
if (input_state.left) {
|
||||
|
@ -348,12 +309,12 @@ pub fn main() !void {
|
|||
var new_distance: ?f32 = null;
|
||||
var new_action = hovered;
|
||||
for (actions.items) |action| {
|
||||
if (std.mem.eql(u8, action.command, hovered.command)) {
|
||||
if (std.meta.eql(action.command, hovered.command)) {
|
||||
continue;
|
||||
}
|
||||
if (distanceToAction(hovered.center, direction, action.center)) |distance| {
|
||||
if (new_distance == null or (new_distance != null and distance < new_distance.?)) {
|
||||
new_action = Action{
|
||||
new_action = Element.Action{
|
||||
.center = .{
|
||||
if (input_state.left or input_state.right) action.center[0] else hovered.center[0],
|
||||
if (input_state.up or input_state.down) action.center[1] else hovered.center[1],
|
||||
|
@ -368,46 +329,6 @@ pub fn main() !void {
|
|||
hovered_action = new_action;
|
||||
}
|
||||
|
||||
while (root_element == null) {
|
||||
_ = response_arena.reset(.retain_capacity);
|
||||
const current_state = history.items[history.items.len - 1];
|
||||
const response = try current_state.handler(response_arena.allocator(), Request{
|
||||
.game_state = current_state,
|
||||
.command = request_command,
|
||||
});
|
||||
|
||||
switch (response) {
|
||||
.page => |page_root_element| root_element = page_root_element,
|
||||
.transition => |transition| {
|
||||
if (transition.reset_selection) {
|
||||
hovered_action = null;
|
||||
}
|
||||
const new_game_state = try transition.game_state.clone(gpa.allocator());
|
||||
if (history_type == .transient) {
|
||||
var transient_state = history.pop();
|
||||
transient_state.deinit();
|
||||
}
|
||||
switch (transition.history_type) {
|
||||
.start => {
|
||||
for (history.items) |*state| {
|
||||
state.deinit();
|
||||
}
|
||||
history.clearRetainingCapacity();
|
||||
try history.append(new_game_state);
|
||||
},
|
||||
.important => try history.append(new_game_state),
|
||||
.transient => {
|
||||
try history.append(new_game_state);
|
||||
},
|
||||
}
|
||||
history_type = transition.history_type;
|
||||
|
||||
if (request_command) |command| gpa.allocator().free(command);
|
||||
request_command = null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
gl.clearColor(0.7, 0.5, 0.5, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
|
@ -448,9 +369,8 @@ pub fn main() !void {
|
|||
else => unreachable,
|
||||
};
|
||||
|
||||
if (root_element) |root| {
|
||||
root.interface.render(
|
||||
root.pointer,
|
||||
main_view.element().interface.render(
|
||||
main_view.element().pointer,
|
||||
&canvas,
|
||||
.{
|
||||
.hovered = if (hovered_action) |ha| ha.command else null,
|
||||
|
@ -463,16 +383,16 @@ pub fn main() !void {
|
|||
@floatFromInt(window_size[1]),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_ = canvas.printText(.{ 0, 0 }, "#actions = {}", .{actions.items.len}, .{});
|
||||
|
||||
canvas.end();
|
||||
|
||||
seizer.backend.glfw.c.glfwSwapBuffers(window);
|
||||
|
||||
if (root_element) |root| {
|
||||
actions.clearRetainingCapacity();
|
||||
try root.interface.get_actions(
|
||||
root.pointer,
|
||||
try main_view.element().interface.get_actions(
|
||||
main_view.element().pointer,
|
||||
&actions,
|
||||
.{
|
||||
.hovered = if (hovered_action) |ha| ha.command else null,
|
||||
|
@ -486,7 +406,6 @@ pub fn main() !void {
|
|||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn distanceToAction(origin: [2]f32, direction: [2]f32, other_pos: [2]f32) ?f32 {
|
||||
|
@ -507,538 +426,8 @@ fn distanceToAction(origin: [2]f32, direction: [2]f32, other_pos: [2]f32) ?f32 {
|
|||
}
|
||||
}
|
||||
|
||||
const Action = struct {
|
||||
center: [2]f32,
|
||||
/// A string representing what should occur if this action is taken
|
||||
command: []const u8,
|
||||
};
|
||||
|
||||
const RenderResources = struct {
|
||||
hovered: ?[]const u8,
|
||||
deck: DeckSprites,
|
||||
font: seizer.Canvas.Font,
|
||||
};
|
||||
|
||||
pub const Element = struct {
|
||||
pointer: ?*anyopaque,
|
||||
interface: *const Interface,
|
||||
|
||||
pub const Error = error{
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const Interface = struct {
|
||||
minimum_size: *const fn (?*anyopaque, RenderResources) [2]f32,
|
||||
render: *const fn (?*anyopaque, *seizer.Canvas, RenderResources, [2]f32, [2]f32) void,
|
||||
get_actions: *const fn (?*anyopaque, *std.ArrayList(Action), RenderResources, [2]f32, [2]f32) Error!void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Page = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Child),
|
||||
|
||||
pub const Child = struct {
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
/// both the parent and child element. This defines where on the parent that pin
|
||||
/// passes through.
|
||||
///
|
||||
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
|
||||
/// the numbers are negative.
|
||||
anchor_in_parent: [2]f32,
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
/// both the parent and child element. This defines where on the child that pin
|
||||
/// passes through.
|
||||
///
|
||||
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
|
||||
/// the numbers are negative.
|
||||
anchor_in_child: [2]f32,
|
||||
element: Element,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn addElement(this: *@This(), anchor_in_parent: [2]f32, anchor_in_child: [2]f32, child_element: Element) !void {
|
||||
try this.children.append(this.allocator, Child{
|
||||
.anchor_in_parent = anchor_in_parent,
|
||||
.anchor_in_child = anchor_in_child,
|
||||
.element = child_element,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
var minimum_size = [2]f32{ 0, 0 };
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
minimum_size = .{
|
||||
@max(minimum_size[0], child_size[0]),
|
||||
@max(minimum_size[1], child_size[1]),
|
||||
};
|
||||
}
|
||||
|
||||
return minimum_size;
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
|
||||
const child_min = [2]f32{
|
||||
@max(min[0], pos_in_parent[0] - pos_in_child[0]),
|
||||
@max(min[1], pos_in_parent[1] - pos_in_child[1]),
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
@min(max[0], pos_in_parent[0] + (child_size[0] - pos_in_child[0])),
|
||||
@min(max[1], pos_in_parent[1] + (child_size[1] - pos_in_child[1])),
|
||||
};
|
||||
|
||||
child.element.interface.render(child.element.pointer, canvas, render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
|
||||
const child_min = [2]f32{
|
||||
pos_in_parent[0] - pos_in_child[0],
|
||||
pos_in_parent[1] - pos_in_child[1],
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
pos_in_parent[0] + (child_size[0] - pos_in_child[0]),
|
||||
pos_in_parent[1] + (child_size[1] - pos_in_child[1]),
|
||||
};
|
||||
|
||||
try child.element.interface.get_actions(child.element.pointer, actions, render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const HBox = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Element),
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn addElement(this: *@This(), child_element: Element) !void {
|
||||
try this.children.append(this.allocator, child_element);
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
var minimum_size = [2]f32{ 0, 0 };
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
minimum_size = .{
|
||||
minimum_size[0] + child_size[0],
|
||||
@max(minimum_size[1], child_size[1]),
|
||||
};
|
||||
}
|
||||
|
||||
return minimum_size;
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const num_spaces = if (empty_space > 0) this.children.items.len + 1 else this.children.items.len - 1;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt(num_spaces));
|
||||
|
||||
var x: f32 = min[0] + @max(space_around, 0);
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
child.interface.render(child.pointer, canvas, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt((this.children.items.len + 1)));
|
||||
|
||||
var x: f32 = min[0] + space_around;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
try child.interface.get_actions(child.pointer, actions, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Pile = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
cards: []Card,
|
||||
hidden: bool = false,
|
||||
command: ?[]const u8 = null,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, cards: []const Card) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
const cards_owned = try allocator.dupe(Card, cards);
|
||||
errdefer allocator.free(cards_owned);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.cards = cards_owned,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
return .{
|
||||
@floatFromInt(render_resources.deck.tilesheet.tile_size[0]),
|
||||
@as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) + @as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) * @as(f32, @floatFromInt(this.cards.len)) / 52.0 * 0.25,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const start_y: f32 = max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1]));
|
||||
|
||||
for (this.cards, 0..) |card, i| {
|
||||
const oy = -@as(f32, @floatFromInt(i)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
|
||||
if (this.hidden) {
|
||||
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.back, .{
|
||||
min[0],
|
||||
start_y + oy,
|
||||
}, .{});
|
||||
} else {
|
||||
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.getTileForCard(card), .{
|
||||
min[0],
|
||||
start_y + oy,
|
||||
}, .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
const oy = -@as(f32, @floatFromInt(this.cards.len)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
|
||||
canvas.rect(
|
||||
.{ min[0], start_y + oy },
|
||||
.{ @floatFromInt(render_resources.deck.tilesheet.tile_size[0]), @floatFromInt(render_resources.deck.tilesheet.tile_size[1]) },
|
||||
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
if (this.command) |command| {
|
||||
const center = [2]f32{
|
||||
min[0] + @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])) / 2,
|
||||
max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * (2.0 - @as(f32, @floatFromInt(this.cards.len)) / (52.0 * 4)),
|
||||
};
|
||||
|
||||
try actions.append(.{
|
||||
.center = center,
|
||||
.command = command,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const CardElement = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
visual: Visual,
|
||||
command: ?[]const u8,
|
||||
marked: bool,
|
||||
|
||||
const Visual = union(enum) {
|
||||
back,
|
||||
card: Card,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, options: struct { visual: Visual, command: ?[]const u8, marked: ?bool = null }) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.visual = options.visual,
|
||||
.command = options.command,
|
||||
.marked = options.marked orelse false,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
_ = this;
|
||||
|
||||
return .{
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])),
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
const mark_offset = if (this.marked)
|
||||
[2]f32{
|
||||
0,
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * -0.4,
|
||||
}
|
||||
else
|
||||
[2]f32{ 0, 0 };
|
||||
|
||||
switch (this.visual) {
|
||||
.back => render_resources.deck.tilesheet.renderTile(
|
||||
canvas,
|
||||
render_resources.deck.back,
|
||||
.{ min[0] + mark_offset[0], min[1] + mark_offset[1] },
|
||||
.{},
|
||||
),
|
||||
.card => |card| render_resources.deck.tilesheet.renderTile(
|
||||
canvas,
|
||||
render_resources.deck.getTileForCard(card),
|
||||
.{ @floor(min[0] + mark_offset[0]), @floor(min[1] + mark_offset[1]) },
|
||||
.{},
|
||||
),
|
||||
}
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
canvas.rect(
|
||||
.{ min[0] + mark_offset[0], min[1] + mark_offset[1] },
|
||||
.{ max[0] - min[0], max[1] - min[1] },
|
||||
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
_ = render_resources;
|
||||
|
||||
if (this.command) |command| {
|
||||
try actions.append(.{
|
||||
.center = [2]f32{
|
||||
(min[0] + max[0]) / 2,
|
||||
(min[1] + max[1]) / 2,
|
||||
},
|
||||
.command = command,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const TextElement = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
text: []const u8,
|
||||
command: ?[]const u8,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, options: struct { text: []const u8, command: ?[]const u8 }) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.text = options.text,
|
||||
.command = options.command,
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn element(this: *@This()) Element {
|
||||
return Element{
|
||||
.pointer = this,
|
||||
.interface = &Element.Interface{
|
||||
.minimum_size = &element_minimum_size,
|
||||
.render = &element_render,
|
||||
.get_actions = &element_get_actions,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: RenderResources) [2]f32 {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
return render_resources.font.textSize(this.text, 1);
|
||||
}
|
||||
|
||||
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: RenderResources, min: [2]f32, max: [2]f32) void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
_ = max;
|
||||
|
||||
const text_size = canvas.writeText(min, this.text, .{
|
||||
.color = if (this.command != null) .{ 0xFF, 0xFF, 0xFF, 0xFF } else .{ 0xAA, 0xAA, 0xAA, 0xFF },
|
||||
});
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
canvas.rect(
|
||||
.{ min[0], min[1] },
|
||||
text_size,
|
||||
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
|
||||
const this: *@This() = @ptrCast(@alignCast(pointer));
|
||||
|
||||
_ = render_resources;
|
||||
|
||||
if (this.command) |command| {
|
||||
try actions.append(.{
|
||||
.center = [2]f32{
|
||||
(min[0] + max[0]) / 2,
|
||||
(min[1] + max[1]) / 2,
|
||||
},
|
||||
.command = command,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Handler at the start of a turn, while the player is drawing a card.
|
||||
fn drawCardHandler(arena: std.mem.Allocator, request: Request) HandlerError!Response {
|
||||
fn drawCardHandler(arena: std.mem.Allocator, request: Request) Handler.Error!Response {
|
||||
if (request.command) |command| {
|
||||
if (std.mem.eql(u8, command, "draw draw_pile")) {
|
||||
var new_game_state = try request.game_state.clone(arena);
|
||||
|
@ -1059,34 +448,34 @@ fn drawCardHandler(arena: std.mem.Allocator, request: Request) HandlerError!Resp
|
|||
}
|
||||
}
|
||||
|
||||
var draw_pile = try Pile.create(arena, request.game_state.draw_pile.items);
|
||||
var draw_pile = try Element.Pile.create(arena, request.game_state.draw_pile.items);
|
||||
draw_pile.hidden = true;
|
||||
draw_pile.command = "draw draw_pile";
|
||||
|
||||
var discard_pile = try Pile.create(arena, request.game_state.discard_pile.items);
|
||||
var discard_pile = try Element.Pile.create(arena, request.game_state.discard_pile.items);
|
||||
discard_pile.command = "draw discard_pile";
|
||||
|
||||
var hand = try HBox.create(arena);
|
||||
var hand = try Element.HBox.create(arena);
|
||||
for (request.game_state.hands[0].items) |card| {
|
||||
var card_element = try CardElement.create(arena, .{
|
||||
var card_element = try Element.CardElement.create(arena, .{
|
||||
.visual = .{ .card = card },
|
||||
.command = null,
|
||||
});
|
||||
try hand.addElement(card_element.element());
|
||||
}
|
||||
|
||||
var draw_discard_hbox = try HBox.create(arena);
|
||||
var draw_discard_hbox = try Element.HBox.create(arena);
|
||||
try draw_discard_hbox.addElement(draw_pile.element());
|
||||
try draw_discard_hbox.addElement(discard_pile.element());
|
||||
|
||||
var page = try Page.create(arena);
|
||||
var page = try Element.Page.create(arena);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, draw_discard_hbox.element());
|
||||
try page.addElement(.{ 0.5, 1 }, .{ 0.5, 1 }, hand.element());
|
||||
|
||||
return Response{ .page = page.element() };
|
||||
}
|
||||
|
||||
fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Response {
|
||||
fn playerTurnHandler(arena: std.mem.Allocator, request: Request) Handler.Error!Response {
|
||||
if (request.command) |command| handle_command: {
|
||||
if (std.mem.startsWith(u8, command, "mark ")) {
|
||||
var iter = std.mem.tokenizeScalar(u8, command, ' ');
|
||||
|
@ -1141,7 +530,7 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
|
|||
}
|
||||
}
|
||||
|
||||
var new_meld_text = try TextElement.create(arena, .{
|
||||
var new_meld_text = try Element.Text.create(arena, .{
|
||||
.text = "New Meld",
|
||||
.command = null,
|
||||
});
|
||||
|
@ -1149,20 +538,20 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
|
|||
new_meld_text.command = "new-meld";
|
||||
}
|
||||
|
||||
var melds_hbox = try HBox.create(arena);
|
||||
var melds_hbox = try Element.HBox.create(arena);
|
||||
try melds_hbox.addElement(new_meld_text.element());
|
||||
|
||||
var draw_pile = try Pile.create(arena, request.game_state.draw_pile.items);
|
||||
var draw_pile = try Element.Pile.create(arena, request.game_state.draw_pile.items);
|
||||
draw_pile.hidden = true;
|
||||
|
||||
var discard_pile = try Pile.create(arena, request.game_state.discard_pile.items);
|
||||
var discard_pile = try Element.Pile.create(arena, request.game_state.discard_pile.items);
|
||||
if (request.game_state.marked_cards.count() == 1) {
|
||||
discard_pile.command = "discard";
|
||||
}
|
||||
|
||||
var hand = try HBox.create(arena);
|
||||
var hand = try Element.HBox.create(arena);
|
||||
for (request.game_state.hands[0].items) |card| {
|
||||
var card_element = try CardElement.create(arena, .{
|
||||
var card_element = try Element.Card.create(arena, .{
|
||||
.visual = .{ .card = card },
|
||||
.command = try std.fmt.allocPrint(arena, "mark {} of {s}", .{ card.rank, @tagName(card.suit) }),
|
||||
.marked = request.game_state.marked_cards.contains(card),
|
||||
|
@ -1170,11 +559,11 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
|
|||
try hand.addElement(card_element.element());
|
||||
}
|
||||
|
||||
var draw_discard_hbox = try HBox.create(arena);
|
||||
var draw_discard_hbox = try Element.HBox.create(arena);
|
||||
try draw_discard_hbox.addElement(draw_pile.element());
|
||||
try draw_discard_hbox.addElement(discard_pile.element());
|
||||
|
||||
var page = try Page.create(arena);
|
||||
var page = try Element.Page.create(arena);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, draw_discard_hbox.element());
|
||||
try page.addElement(.{ 0.5, 1 }, .{ 0.5, 1 }, hand.element());
|
||||
try page.addElement(.{ 0.5, 0 }, .{ 0.5, 0 }, melds_hbox.element());
|
||||
|
@ -1182,7 +571,7 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
|
|||
return Response{ .page = page.element() };
|
||||
}
|
||||
|
||||
fn endOfTurnConfirmHandler(arena: std.mem.Allocator, request: Request) HandlerError!Response {
|
||||
fn endOfTurnConfirmHandler(arena: std.mem.Allocator, request: Request) Handler.Error!Response {
|
||||
if (request.command) |command| {
|
||||
if (std.mem.eql(u8, command, "end_turn")) {
|
||||
var new_game_state = try request.game_state.clone(arena);
|
||||
|
@ -1195,16 +584,16 @@ fn endOfTurnConfirmHandler(arena: std.mem.Allocator, request: Request) HandlerEr
|
|||
}
|
||||
}
|
||||
|
||||
var melds_hbox = try HBox.create(arena);
|
||||
var melds_hbox = try Element.HBox.create(arena);
|
||||
|
||||
var draw_pile = try Pile.create(arena, request.game_state.draw_pile.items);
|
||||
var draw_pile = try Element.Pile.create(arena, request.game_state.draw_pile.items);
|
||||
draw_pile.hidden = true;
|
||||
|
||||
var discard_pile = try Pile.create(arena, request.game_state.discard_pile.items);
|
||||
var discard_pile = try Element.Pile.create(arena, request.game_state.discard_pile.items);
|
||||
|
||||
var hand = try HBox.create(arena);
|
||||
var hand = try Element.HBox.create(arena);
|
||||
for (request.game_state.hands[0].items) |card| {
|
||||
var card_element = try CardElement.create(arena, .{
|
||||
var card_element = try Element.Card.create(arena, .{
|
||||
.visual = .{ .card = card },
|
||||
.command = null,
|
||||
.marked = false,
|
||||
|
@ -1212,16 +601,16 @@ fn endOfTurnConfirmHandler(arena: std.mem.Allocator, request: Request) HandlerEr
|
|||
try hand.addElement(card_element.element());
|
||||
}
|
||||
|
||||
var draw_discard_hbox = try HBox.create(arena);
|
||||
var draw_discard_hbox = try Element.HBox.create(arena);
|
||||
try draw_discard_hbox.addElement(draw_pile.element());
|
||||
try draw_discard_hbox.addElement(discard_pile.element());
|
||||
|
||||
var end_turn_text = try TextElement.create(arena, .{
|
||||
var end_turn_text = try Element.TextElement.create(arena, .{
|
||||
.text = "End Turn",
|
||||
.command = "end_turn",
|
||||
});
|
||||
|
||||
var page = try Page.create(arena);
|
||||
var page = try Element.Page.create(arena);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, draw_discard_hbox.element());
|
||||
try page.addElement(.{ 0.5, 1 }, .{ 0.5, 1 }, hand.element());
|
||||
try page.addElement(.{ 0.5, 0 }, .{ 0.5, 0 }, melds_hbox.element());
|
||||
|
@ -1230,6 +619,75 @@ fn endOfTurnConfirmHandler(arena: std.mem.Allocator, request: Request) HandlerEr
|
|||
return Response{ .page = page.element() };
|
||||
}
|
||||
|
||||
fn mainMenuHandler(arena: std.mem.Allocator, request: Request) Handler.Error!Response {
|
||||
if (request.command) |command| {
|
||||
if (std.mem.eql(u8, command, "join-multiplayer-game")) {
|
||||
var new_game_state = try request.game_state.clone(arena);
|
||||
new_game_state.handler = &joinMultiplayerGameHandler;
|
||||
|
||||
return Response{ .transition = .{
|
||||
.game_state = new_game_state,
|
||||
.history_type = .start,
|
||||
} };
|
||||
} else if (std.mem.eql(u8, command, "host-multiplayer-game")) {
|
||||
var new_game_state = try request.game_state.clone(arena);
|
||||
new_game_state.handler = &hostMultiplayerGameHandler;
|
||||
|
||||
return Response{ .transition = .{
|
||||
.game_state = new_game_state,
|
||||
.history_type = .start,
|
||||
} };
|
||||
}
|
||||
}
|
||||
|
||||
var join_multiplayer_text = try Element.Textcreate(arena, .{
|
||||
.text = "Join Multiplayer Game",
|
||||
.command = "join-multiplayer-game",
|
||||
});
|
||||
|
||||
var host_multiplayer_text = try Element.Text.create(arena, .{
|
||||
.text = "Host Multiplayer Game",
|
||||
.command = "host-multiplayer-game",
|
||||
});
|
||||
|
||||
var play_game_hbox = try Element.HBox.create(arena);
|
||||
try play_game_hbox.addElement(join_multiplayer_text.element());
|
||||
try play_game_hbox.addElement(host_multiplayer_text.element());
|
||||
|
||||
var page = try Element.Page.create(arena);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, play_game_hbox.element());
|
||||
|
||||
return Response{ .page = page.element() };
|
||||
}
|
||||
|
||||
fn hostMultiplayerGameHandler(arena: std.mem.Allocator, request: Request) Handler.Error!Response {
|
||||
_ = request;
|
||||
|
||||
var text = try Element.Text.create(arena, .{
|
||||
.text = "Hosting Multiplayer Game",
|
||||
.command = null,
|
||||
});
|
||||
|
||||
var page = try Element.Page.create(arena);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
|
||||
return Response{ .page = page.element() };
|
||||
}
|
||||
|
||||
fn joinMultiplayerGameHandler(arena: std.mem.Allocator, request: Request) Handler.Error!Response {
|
||||
_ = request;
|
||||
|
||||
var text = try Element.Text.create(arena, .{
|
||||
.text = "Joining Multiplayer Game",
|
||||
.command = null,
|
||||
});
|
||||
|
||||
var page = try Element.Page.create(arena);
|
||||
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
|
||||
|
||||
return Response{ .page = page.element() };
|
||||
}
|
||||
|
||||
fn isValidRummyMeld(cards: []const Card) bool {
|
||||
std.debug.assert(std.sort.isSorted(Card, cards, {}, rummyHandSort));
|
||||
if (cards.len < 3) {
|
||||
|
@ -1313,9 +771,17 @@ test {
|
|||
_ = @import("./assets.zig");
|
||||
}
|
||||
|
||||
const Handler = protocol.Handler;
|
||||
const Request = protocol.Request;
|
||||
const Response = protocol.Response;
|
||||
const DeckSprites = assets.DeckSprites;
|
||||
const Card = assets.Card;
|
||||
|
||||
const LocalUI = @import("./LocalUI.zig");
|
||||
const Element = @import("./Element.zig");
|
||||
|
||||
const protocol = @import("./protocol.zig");
|
||||
const c = @import("./c.zig");
|
||||
const assets = @import("./assets.zig");
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
pub const Handler = struct {
|
||||
pointer: ?*anyopaque,
|
||||
interface: *const Interface,
|
||||
|
||||
pub const Error = error{OutOfMemory};
|
||||
pub const Fn = *const fn (Request) Error!Response;
|
||||
pub const Interface = struct {
|
||||
handle: *const fn (?*anyopaque, Request) Error!Response,
|
||||
deinit: ?*const fn (?*anyopaque) void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Reference = union(enum) {
|
||||
handler: Handler,
|
||||
};
|
||||
|
||||
pub const Request = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
body: ?[]const u8 = null,
|
||||
};
|
||||
pub const Response = struct {
|
||||
arena: std.heap.ArenaAllocator,
|
||||
body: Body,
|
||||
|
||||
const Body = union(enum) {
|
||||
element: Element,
|
||||
};
|
||||
};
|
||||
|
||||
const Element = @import("./Element.zig");
|
||||
const std = @import("std");
|
Loading…
Reference in New Issue