From c5526ba229fac1d654d77207f2d34891e9162ebe Mon Sep 17 00:00:00 2001 From: geemili Date: Mon, 26 Feb 2024 21:55:51 -0700 Subject: [PATCH] feat: activating and undoing actions --- src/main.zig | 147 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 128 insertions(+), 19 deletions(-) diff --git a/src/main.zig b/src/main.zig index 5e3e7a6..bea3bb7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -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 => {}, } }