Compare commits

...

3 Commits

1 changed files with 130 additions and 3 deletions

View File

@ -23,7 +23,7 @@ const GameState = struct {
discard_pile: std.ArrayListUnmanaged(Card),
hands: []std.ArrayListUnmanaged(Card),
marked_cards: std.AutoHashMapUnmanaged(Card, void),
marked_cards: std.AutoArrayHashMapUnmanaged(Card, void),
pub fn init(allocator: std.mem.Allocator, seed: u64, num_players: usize) !@This() {
var draw_pile = std.ArrayListUnmanaged(Card).fromOwnedSlice(try makeStandardDeck(allocator));
@ -132,8 +132,12 @@ pub fn main() !void {
seizer.backend.glfw.c.glfwWindowHint(seizer.backend.glfw.c.GLFW_CONTEXT_VERSION_MAJOR, 3);
seizer.backend.glfw.c.glfwWindowHint(seizer.backend.glfw.c.GLFW_CONTEXT_VERSION_MINOR, 0);
const primary_monitor = seizer.backend.glfw.c.glfwGetPrimaryMonitor();
var workarea_size: [2]c_int = undefined;
seizer.backend.glfw.c.glfwGetMonitorWorkarea(primary_monitor, null, null, &workarea_size[0], &workarea_size[1]);
// Open window
const window = seizer.backend.glfw.c.glfwCreateWindow(720, 720, "Rummy", null, null) orelse return error.GlfwCreateWindow;
const window = seizer.backend.glfw.c.glfwCreateWindow(workarea_size[0], workarea_size[1], "Rummy", null, null) orelse return error.GlfwCreateWindow;
errdefer seizer.backend.glfw.c.glfwDestroyWindow(window);
seizer.backend.glfw.c.glfwMakeContextCurrent(window);
@ -323,6 +327,7 @@ pub fn main() !void {
.{
.hovered = if (hovered_action) |ha| ha.command else null,
.deck = deck_sprites,
.font = canvas.font,
},
.{ 0, 0 },
.{
@ -344,6 +349,7 @@ pub fn main() !void {
.{
.hovered = if (hovered_action) |ha| ha.command else null,
.deck = deck_sprites,
.font = canvas.font,
},
.{ 0, 0 },
.{
@ -382,6 +388,7 @@ const Action = struct {
const RenderResources = struct {
hovered: ?[]const u8,
deck: DeckSprites,
font: seizer.Canvas.Font,
};
pub const Element = struct {
@ -835,6 +842,71 @@ pub const CardElement = struct {
}
};
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, .{});
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 {
if (request.command) |command| {
@ -900,10 +972,11 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
var new_game_state = try request.game_state.clone(arena);
if (new_game_state.marked_cards.contains(card)) {
_ = new_game_state.marked_cards.remove(card);
_ = new_game_state.marked_cards.swapRemove(card);
} else {
try new_game_state.marked_cards.put(arena, card, {});
}
new_game_state.marked_cards.sort(ArrayHashMapRummyHandSort{ .keys = new_game_state.marked_cards.keys() });
return Response{ .transition = .{
.game_state = new_game_state,
@ -912,6 +985,17 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
}
}
var new_meld_text = try TextElement.create(arena, .{
.text = "New Meld",
.command = null,
});
if (isValidRummyMeld(request.game_state.marked_cards.keys())) {
new_meld_text.command = "new-meld";
}
var melds_hbox = try HBox.create(arena);
try melds_hbox.addElement(new_meld_text.element());
var draw_pile = try Pile.create(arena, request.game_state.draw_pile.items);
draw_pile.hidden = true;
@ -934,10 +1018,53 @@ fn playerTurnHandler(arena: std.mem.Allocator, request: Request) HandlerError!Re
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());
try page.addElement(.{ 0.5, 0 }, .{ 0.5, 0 }, melds_hbox.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) {
return false;
}
const expected_rank = cards[0].rank;
var is_valid_set = true;
for (cards) |card| {
if (card.rank != expected_rank) {
is_valid_set = false;
break;
}
}
const expected_suit = cards[0].suit;
var is_valid_run = true;
for (cards[0 .. cards.len - 1], cards[1..]) |prev_card, card| {
if (card.suit != expected_suit or card.rank != (prev_card.rank + 1)) {
is_valid_run = false;
break;
}
}
return is_valid_set or is_valid_run;
}
pub fn rummyHandSort(_: void, lhs: Card, rhs: Card) bool {
if (lhs.rank < rhs.rank) return true;
if (lhs.rank > rhs.rank) return false;
return @intFromEnum(lhs.suit) < @intFromEnum(rhs.suit);
}
const ArrayHashMapRummyHandSort = struct {
keys: []const Card,
pub fn lessThan(this: @This(), lhs_index: usize, rhs_index: usize) bool {
return rummyHandSort({}, this.keys[lhs_index], this.keys[rhs_index]);
}
};
fn glfw_framebuffer_size_callback(window: ?*seizer.backend.glfw.c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
_ = window;
gl.viewport(