feat: allow user to undo card movements

ci-dev
LeRoyce Pearson 2024-04-21 02:56:58 -06:00
parent 9de25c4129
commit 35067f824e
1 changed files with 115 additions and 0 deletions

View File

@ -12,6 +12,8 @@ var drawn_cards: std.ArrayListUnmanaged(Card) = .{};
var stacks: [7]std.ArrayListUnmanaged(Card) = [_]std.ArrayListUnmanaged(Card){.{}} ** 7;
var foundations: [4]std.ArrayListUnmanaged(Card) = [_]std.ArrayListUnmanaged(Card){.{}} ** 4;
var history: std.ArrayListUnmanaged(Snapshot) = .{};
var last_hovered_stack: usize = 0;
var last_hovered_foundation: usize = 0;
var hovered_deck: ?*std.ArrayListUnmanaged(Card) = null;
@ -85,6 +87,11 @@ pub fn init(context: *seizer.Context) !void {
.on_event = toggleMenu,
.default_bindings = &.{.start},
});
try context.addButtonInput(.{
.title = "undo",
.on_event = undo,
.default_bindings = &.{.x},
});
const app_data_dir_path = try std.fs.getAppDataDir(gpa, "seizer-solitaire");
defer gpa.free(app_data_dir_path);
@ -135,6 +142,13 @@ fn resetGame() !void {
win_triggered = false;
}
fn resetHistory() void {
for (history.items) |*snapshot| {
snapshot.deinit();
}
history.shrinkRetainingCapacity(0);
}
fn destroy(window: *seizer.Window) void {
_ = window;
draw_pile.deinit(gpa);
@ -161,6 +175,7 @@ fn render(window: *seizer.Window) !void {
std.mem.writeInt(u32, &win_count_buffer, win_count, .little);
try win_count_file.pwriteAll(&win_count_buffer, 0);
resetHistory();
win_triggered = true;
}
@ -410,6 +425,7 @@ pub fn doSelectOrPlace(pressed: bool) !void {
}
if (selected_deck == null) {
if (hovered_deck == &draw_pile and !draw_pile_exhausted) {
resetHistory();
drawn_cards.ensureUnusedCapacity(gpa, 3) catch return;
for (0..3) |_| {
if (draw_pile.popOrNull()) |card| drawn_cards.appendAssumeCapacity(card);
@ -432,9 +448,11 @@ pub fn doSelectOrPlace(pressed: bool) !void {
}
}
} else if (hovered_deck == selected_deck) {
const snapshot = try Snapshot.initFromCurrentGlobalState();
const selected_substack = selected_deck.?.items[selected_card..];
// try to move all selected cards into the foundations
var cards_moved = false;
move_cards_to_foundations: for (0..selected_substack.len) |_| {
for (foundations[0..]) |*foundation| {
if (hovered_deck == foundation) break :move_cards_to_foundations;
@ -449,14 +467,18 @@ pub fn doSelectOrPlace(pressed: bool) !void {
foundation.ensureUnusedCapacity(gpa, 1) catch continue;
foundation.appendAssumeCapacity(selected_deck.?.pop());
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
cards_moved = true;
break;
}
} else {
break :move_cards_to_foundations;
}
}
try history.append(gpa, snapshot);
selected_deck = null;
} else {
const snapshot = try Snapshot.initFromCurrentGlobalState();
if (selected_deck) |selected| move_from_selected_to_hovered: {
const selected_substack = selected.items[selected_card..];
if (selected_substack.len == 0) break :move_from_selected_to_hovered;
@ -470,6 +492,7 @@ pub fn doSelectOrPlace(pressed: bool) !void {
hovered.appendSliceAssumeCapacity(selected_substack);
selected.shrinkRetainingCapacity(selected.items.len - selected_substack.len);
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
try history.append(gpa, snapshot);
break :move_from_selected_to_hovered;
}
@ -478,6 +501,7 @@ pub fn doSelectOrPlace(pressed: bool) !void {
hovered.ensureUnusedCapacity(gpa, 1) catch break :move_from_selected_to_hovered;
hovered.appendAssumeCapacity(selected_deck.?.pop());
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
try history.append(gpa, snapshot);
break :move_from_selected_to_hovered;
} else for (foundations[0..]) |*foundation| {
if (foundation != selected) continue;
@ -489,6 +513,7 @@ pub fn doSelectOrPlace(pressed: bool) !void {
foundation.ensureUnusedCapacity(gpa, 1) catch continue;
foundation.appendAssumeCapacity(selected_deck.?.pop());
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
try history.append(gpa, snapshot);
break :move_from_selected_to_hovered;
}
}
@ -506,6 +531,7 @@ pub fn doSelectOrPlace(pressed: bool) !void {
hovered.appendSliceAssumeCapacity(selected_substack);
selected.shrinkRetainingCapacity(selected.items.len - selected_substack.len);
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
try history.append(gpa, snapshot);
}
selected_deck = null;
}
@ -666,6 +692,15 @@ pub fn moveDown(pressed: bool) !void {
}
}
pub fn undo(pressed: bool) !void {
if (!pressed) return;
if (history.popOrNull()) |snapshot_const| {
var snapshot = snapshot_const;
defer snapshot.deinit();
try snapshot.restore();
}
}
pub fn toggleMenu(pressed: bool) !void {
if (!pressed) return;
show_menu = !show_menu;
@ -684,6 +719,86 @@ pub fn indexOfTopOfStack(cards: []Card) usize {
return index;
}
const Snapshot = struct {
draw_pile: []Card,
draw_pile_exhausted: bool,
drawn_cards: []Card,
stacks: [7][]Card,
foundations: [4][]Card,
selected_deck: *std.ArrayListUnmanaged(Card),
selected_card: usize,
pub fn initFromCurrentGlobalState() !Snapshot {
const snapshot_selected_deck = selected_deck orelse return error.InvalidSelectionForSnapshot;
const snapshot_draw_pile = try gpa.dupe(Card, draw_pile.items);
errdefer gpa.free(snapshot_draw_pile);
const snapshot_drawn_cards = try gpa.dupe(Card, drawn_cards.items);
errdefer gpa.free(snapshot_drawn_cards);
var snapshot_stacks: [7][]Card = undefined;
for (&snapshot_stacks, &stacks) |*snapshot_stack, stack| {
snapshot_stack.* = try gpa.dupe(Card, stack.items);
}
var snapshot_foundations: [4][]Card = undefined;
for (&snapshot_foundations, &foundations) |*snapshot_foundation, foundation| {
snapshot_foundation.* = try gpa.dupe(Card, foundation.items);
}
return Snapshot{
.draw_pile = snapshot_draw_pile,
.draw_pile_exhausted = draw_pile_exhausted,
.drawn_cards = snapshot_drawn_cards,
.stacks = snapshot_stacks,
.foundations = snapshot_foundations,
.selected_deck = snapshot_selected_deck,
.selected_card = selected_card,
};
}
pub fn deinit(snapshot: *@This()) void {
gpa.free(snapshot.draw_pile);
gpa.free(snapshot.drawn_cards);
}
pub fn restore(snapshot: @This()) !void {
// ensure there is enough space for all the cards
try draw_pile.ensureTotalCapacity(gpa, snapshot.draw_pile.len);
try drawn_cards.ensureTotalCapacity(gpa, snapshot.drawn_cards.len);
for (&snapshot.stacks, &stacks) |snapshot_stack, *stack| {
try stack.ensureTotalCapacity(gpa, snapshot_stack.len);
}
for (&snapshot.foundations, &foundations) |snapshot_foundation, *foundation| {
try foundation.ensureTotalCapacity(gpa, snapshot_foundation.len);
}
// reset the state
draw_pile.shrinkRetainingCapacity(0);
draw_pile.appendSliceAssumeCapacity(snapshot.draw_pile);
draw_pile_exhausted = snapshot.draw_pile_exhausted;
drawn_cards.shrinkRetainingCapacity(0);
drawn_cards.appendSliceAssumeCapacity(snapshot.drawn_cards);
for (&snapshot.stacks, &stacks) |snapshot_stack, *stack| {
stack.shrinkRetainingCapacity(0);
stack.appendSliceAssumeCapacity(snapshot_stack);
}
for (&snapshot.foundations, &foundations) |snapshot_foundation, *foundation| {
foundation.shrinkRetainingCapacity(0);
foundation.appendSliceAssumeCapacity(snapshot_foundation);
}
selected_deck = null;
selected_card = 0;
hovered_deck = snapshot.selected_deck;
hovered_card = snapshot.selected_card;
}
};
const DeckSprites = assets.DeckSprites;
const Card = assets.Card;