Compare commits
3 Commits
2dde105a58
...
197756e5c3
Author | SHA1 | Date |
---|---|---|
LeRoyce Pearson | 197756e5c3 | |
LeRoyce Pearson | 6f42b35e14 | |
LeRoyce Pearson | f0e6bc4e0d |
133
src/main.zig
133
src/main.zig
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue