feat: activating and undoing actions

dev
LeRoyce Pearson 2024-02-26 21:55:51 -07:00
parent 1134f0ee08
commit c5526ba229
1 changed files with 128 additions and 19 deletions

View File

@ -1,8 +1,24 @@
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,
can_undo: bool,
},
};
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),
@ -39,6 +55,7 @@ const GameState = struct {
return @This(){
.allocator = allocator,
.prng = prng,
.handler = &drawCardHandler,
.draw_pile = draw_pile,
.discard_pile = discard_pile,
.hands = hands,
@ -53,6 +70,22 @@ const GameState = struct {
}
this.allocator.free(this.hands);
}
pub fn clone(this: @This(), allocator: std.mem.Allocator) !@This() {
const hands = try allocator.alloc(std.ArrayListUnmanaged(Card), this.hands.len);
for (hands, this.hands) |*hand_clone, hand| {
hand_clone.* = try hand.clone(allocator);
}
return @This(){
.allocator = allocator,
.prng = this.prng,
.handler = this.handler,
.draw_pile = try this.draw_pile.clone(allocator),
.discard_pile = try this.discard_pile.clone(allocator),
.hands = hands,
};
}
};
pub fn makeStandardDeck(allocator: std.mem.Allocator) ![]Card {
@ -123,18 +156,23 @@ pub fn main() !void {
defer canvas.deinit(gpa.allocator());
// game state
var game_state = try GameState.init(gpa.allocator(), std.crypto.random.int(u64), 1);
defer game_state.deinit();
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 root_element: ?Element = null;
var response_arena = std.heap.ArenaAllocator.init(gpa.allocator());
defer response_arena.deinit();
const handler = &drawCardHandler;
var actions = std.ArrayList(Action).init(gpa.allocator());
defer actions.deinit();
// TODO: Restore hovered_action when undoing
var hovered_action: ?Action = null;
while (seizer.backend.glfw.c.glfwWindowShouldClose(window) != seizer.backend.glfw.c.GLFW_TRUE) {
@ -143,13 +181,31 @@ pub fn main() !void {
.right = false,
.up = false,
.down = false,
.action = false,
.undo = false,
};
seizer.backend.glfw.c.glfwPollEvents();
if (hovered_action == null and actions.items.len > 0) {
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;
hovered_action = null;
}
} 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();
discarded_state.deinit();
root_element = null;
hovered_action = null;
}
if (hovered_action) |hovered| {
var direction = [2]f32{ 0, 0 };
if (input_state.left) {
@ -188,14 +244,25 @@ pub fn main() !void {
hovered_action = new_action;
}
if (root_element == null) {
while (root_element == null) {
_ = response_arena.reset(.retain_capacity);
const response = try handler(response_arena.allocator(), Request{
.game_state = game_state,
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.can_undo) {
for (history.items) |*state| {
state.deinit();
}
history.clearRetainingCapacity();
}
try history.append(try transition.game_state.clone(gpa.allocator()));
},
}
}
@ -219,6 +286,8 @@ pub fn main() !void {
},
});
_ = canvas.printText(.{ 0, 0 }, "History len: {}", .{history.items.len}, .{});
switch (framebuffer_size[1]) {
0...300 => if (card_tilemap_small == null) {
card_tilemap_small = try assets.loadSmallCards(gpa.allocator());
@ -307,16 +376,6 @@ const RenderResources = struct {
deck: DeckSprites,
};
const HandlerError = error{OutOfMemory};
const Handler = *const fn (std.mem.Allocator, Request) HandlerError!Response;
const Request = struct {
game_state: GameState,
};
const Response = union(enum) {
page: Element,
};
pub const Element = struct {
pointer: ?*anyopaque,
interface: *const Interface,
@ -760,6 +819,26 @@ pub const CardElement = struct {
/// Handler at the start of a turn, while the player is drawing a card.
fn drawCardHandler(arena: std.mem.Allocator, request: Request) HandlerError!Response {
if (request.command) |command| {
if (std.mem.eql(u8, command, "draw draw_pile")) {
var new_game_state = try request.game_state.clone(arena);
try new_game_state.hands[0].append(arena, new_game_state.draw_pile.pop());
new_game_state.handler = &playerTurnHandler;
return Response{ .transition = .{
.game_state = new_game_state,
.can_undo = false,
} };
} else if (std.mem.eql(u8, command, "draw discard_pile")) {
var new_game_state = try request.game_state.clone(arena);
try new_game_state.hands[0].append(arena, new_game_state.discard_pile.pop());
new_game_state.handler = &playerTurnHandler;
return Response{ .transition = .{
.game_state = new_game_state,
.can_undo = true,
} };
}
}
var draw_pile = try Pile.create(arena, request.game_state.draw_pile.items);
draw_pile.hidden = true;
draw_pile.command = "draw draw_pile";
@ -771,7 +850,33 @@ fn drawCardHandler(arena: std.mem.Allocator, request: Request) HandlerError!Resp
for (request.game_state.hands[0].items) |card| {
var card_element = try CardElement.create(arena, .{
.visual = .{ .card = card },
.command = try std.fmt.allocPrint(arena, "mark {} of {s}", .{ card.rank, @tagName(card.suit) }),
.command = null,
});
try hand.addElement(card_element.element());
}
var draw_discard_hbox = try 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);
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 {
var draw_pile = try 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 hand = try HBox.create(arena);
for (request.game_state.hands[0].items) |card| {
var card_element = try CardElement.create(arena, .{
.visual = .{ .card = card },
.command = "mark",
});
try hand.addElement(card_element.element());
}
@ -802,6 +907,8 @@ const InputState = struct {
right: bool,
up: bool,
down: bool,
action: bool,
undo: bool,
};
fn glfw_key_callback(window: ?*seizer.backend.glfw.c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void {
@ -815,6 +922,8 @@ fn glfw_key_callback(window: ?*seizer.backend.glfw.c.GLFWwindow, key: c_int, sca
seizer.backend.glfw.c.GLFW_KEY_RIGHT => input_state.right = true,
seizer.backend.glfw.c.GLFW_KEY_UP => input_state.up = true,
seizer.backend.glfw.c.GLFW_KEY_DOWN => input_state.down = true,
seizer.backend.glfw.c.GLFW_KEY_Z => input_state.action = true,
seizer.backend.glfw.c.GLFW_KEY_BACKSPACE => input_state.undo = true,
else => {},
}
}