Compare commits
11 Commits
15e1670ec3
...
c5526ba229
Author | SHA1 | Date |
---|---|---|
LeRoyce Pearson | c5526ba229 | |
LeRoyce Pearson | 1134f0ee08 | |
LeRoyce Pearson | 45bd514bd3 | |
LeRoyce Pearson | a7bc7e87e0 | |
LeRoyce Pearson | 91f896c5ef | |
LeRoyce Pearson | 54d04f4a2b | |
LeRoyce Pearson | d63a6d06fb | |
LeRoyce Pearson | 45bc09b612 | |
LeRoyce Pearson | 695c4d6280 | |
LeRoyce Pearson | 680f56a39d | |
LeRoyce Pearson | 755518bb5d |
|
@ -0,0 +1,301 @@
|
|||
pub const Suit = enum(u2) {
|
||||
clubs = 0b00,
|
||||
spades = 0b01,
|
||||
hearts = 0b10,
|
||||
diamonds = 0b11,
|
||||
|
||||
pub fn color(this: @This()) u1 {
|
||||
return (@intFromEnum(this) & 0b10) >> 1;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Card = packed struct(u6) {
|
||||
suit: Suit,
|
||||
rank: u4,
|
||||
};
|
||||
|
||||
/// A texture with a regular grid of sprites
|
||||
pub const TileSheet = struct {
|
||||
texture: seizer.Texture,
|
||||
tile_offset: [2]u32,
|
||||
tile_size: [2]u32,
|
||||
tile_stride: [2]u32,
|
||||
|
||||
pub const InitOptions = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
image_file_contents: []const u8,
|
||||
tile_offset: [2]u32,
|
||||
tile_size: [2]u32,
|
||||
tile_stride: [2]u32,
|
||||
texture_options: seizer.Texture.InitFromFileOptions = .{},
|
||||
};
|
||||
|
||||
pub fn init(options: InitOptions) !@This() {
|
||||
const texture = try seizer.Texture.initFromFileContents(
|
||||
options.allocator,
|
||||
options.image_file_contents,
|
||||
options.texture_options,
|
||||
);
|
||||
return @This(){
|
||||
.texture = texture,
|
||||
.tile_offset = options.tile_offset,
|
||||
.tile_size = options.tile_size,
|
||||
.tile_stride = options.tile_stride,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
this.texture.deinit();
|
||||
}
|
||||
|
||||
pub fn renderTile(this: @This(), canvas: *seizer.Canvas, tile_id: u32, pos: [2]f32, options: struct {
|
||||
size: ?[2]f32 = null,
|
||||
}) void {
|
||||
const texture_sizef = [2]f32{
|
||||
@floatFromInt(this.texture.size[0]),
|
||||
@floatFromInt(this.texture.size[1]),
|
||||
};
|
||||
const tile_offsetf = [2]f32{
|
||||
@floatFromInt(this.tile_offset[0]),
|
||||
@floatFromInt(this.tile_offset[1]),
|
||||
};
|
||||
const tile_stridef = [2]f32{
|
||||
@floatFromInt(this.tile_stride[0]),
|
||||
@floatFromInt(this.tile_stride[1]),
|
||||
};
|
||||
const tile_sizef = [2]f32{
|
||||
@floatFromInt(this.tile_size[0]),
|
||||
@floatFromInt(this.tile_size[1]),
|
||||
};
|
||||
// add a half to round up
|
||||
const size_in_tiles = [2]u32{
|
||||
(@as(u32, @intCast(this.texture.size[0])) + (this.tile_stride[0] / 2)) / this.tile_stride[0],
|
||||
(@as(u32, @intCast(this.texture.size[1])) + (this.tile_stride[1] / 2)) / this.tile_stride[1],
|
||||
};
|
||||
const pos_in_tiles = [2]u32{
|
||||
tile_id % size_in_tiles[0],
|
||||
tile_id / size_in_tiles[0],
|
||||
};
|
||||
const pos_in_tilesf = [2]f32{
|
||||
@floatFromInt(pos_in_tiles[0]),
|
||||
@floatFromInt(pos_in_tiles[1]),
|
||||
};
|
||||
|
||||
const pixel_pos = [2]f32{
|
||||
pos_in_tilesf[0] * tile_stridef[0] + tile_offsetf[0],
|
||||
pos_in_tilesf[1] * tile_stridef[1] + tile_offsetf[1],
|
||||
};
|
||||
const uv = seizer.geometry.AABB(f32){
|
||||
.min = .{
|
||||
pixel_pos[0] / texture_sizef[0],
|
||||
pixel_pos[1] / texture_sizef[1],
|
||||
},
|
||||
.max = .{
|
||||
(pixel_pos[0] + tile_sizef[0]) / texture_sizef[0],
|
||||
(pixel_pos[1] + tile_sizef[1]) / texture_sizef[1],
|
||||
},
|
||||
};
|
||||
|
||||
canvas.rect(pos, options.size orelse tile_sizef, .{
|
||||
.texture = this.texture.glTexture,
|
||||
.uv = uv,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/// A texture with a regular grid of sprites
|
||||
pub const DeckSprites = struct {
|
||||
tilesheet: TileSheet,
|
||||
/// Return the tile index for a given card,
|
||||
mapping: std.AutoHashMapUnmanaged(Card, u32),
|
||||
blank: u32,
|
||||
back: u32,
|
||||
|
||||
pub fn deinit(this: *@This(), gpa: std.mem.Allocator) void {
|
||||
this.tilesheet.deinit();
|
||||
this.mapping.deinit(gpa);
|
||||
}
|
||||
|
||||
pub fn getTileForCard(this: @This(), card: Card) u32 {
|
||||
return this.mapping.get(card) orelse 0;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn loadSmallCards(gpa: std.mem.Allocator) !DeckSprites {
|
||||
var tilesheet = try TileSheet.init(.{
|
||||
.allocator = gpa,
|
||||
.image_file_contents = @embedFile("./cardsSmall_tilemap.png"),
|
||||
.tile_offset = .{ 0, 0 },
|
||||
.tile_size = .{ 16, 16 },
|
||||
.tile_stride = .{ 17, 17 },
|
||||
.texture_options = .{
|
||||
.min_filter = .nearest,
|
||||
.mag_filter = .nearest,
|
||||
},
|
||||
});
|
||||
errdefer tilesheet.deinit();
|
||||
|
||||
var mapping = std.AutoHashMap(Card, u32).init(gpa);
|
||||
errdefer mapping.deinit();
|
||||
try mapping.ensureTotalCapacity(52);
|
||||
|
||||
const hearts_start_index: u32 = 0;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
|
||||
hearts_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const diamonds_start_index: u32 = 14;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
|
||||
diamonds_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const clubs_start_index: u32 = 28;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
|
||||
clubs_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const spades_start_index: u32 = 42;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
|
||||
spades_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
return DeckSprites{
|
||||
.tilesheet = tilesheet,
|
||||
.mapping = mapping.unmanaged,
|
||||
// TODO: add better graphic for blank card
|
||||
.blank = 59,
|
||||
.back = 59,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn loadMediumCards(gpa: std.mem.Allocator) !DeckSprites {
|
||||
var tilesheet = try TileSheet.init(.{
|
||||
.allocator = gpa,
|
||||
.image_file_contents = @embedFile("./cardsMedium_tilemap.png"),
|
||||
.tile_offset = .{ 6, 2 },
|
||||
.tile_size = .{ 20, 29 },
|
||||
.tile_stride = .{ 33, 33 },
|
||||
.texture_options = .{
|
||||
.min_filter = .nearest,
|
||||
.mag_filter = .nearest,
|
||||
},
|
||||
});
|
||||
errdefer tilesheet.deinit();
|
||||
|
||||
var mapping = std.AutoHashMap(Card, u32).init(gpa);
|
||||
errdefer mapping.deinit();
|
||||
try mapping.ensureTotalCapacity(52);
|
||||
|
||||
const hearts_start_index: u32 = 0;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
|
||||
hearts_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const diamonds_start_index: u32 = 15;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
|
||||
diamonds_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const clubs_start_index: u32 = 30;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
|
||||
clubs_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const spades_start_index: u32 = 45;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
|
||||
spades_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
return DeckSprites{
|
||||
.tilesheet = tilesheet,
|
||||
.mapping = mapping.unmanaged,
|
||||
.blank = 14,
|
||||
.back = 29,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn loadLargeCards(gpa: std.mem.Allocator) !DeckSprites {
|
||||
var tilesheet = try TileSheet.init(.{
|
||||
.allocator = gpa,
|
||||
.image_file_contents = @embedFile("./cardsLarge_tilemap.png"),
|
||||
.tile_offset = .{ 11, 2 },
|
||||
.tile_size = .{ 41, 60 },
|
||||
.tile_stride = .{ 65, 65 },
|
||||
.texture_options = .{
|
||||
.min_filter = .nearest,
|
||||
.mag_filter = .nearest,
|
||||
},
|
||||
});
|
||||
errdefer tilesheet.deinit();
|
||||
|
||||
var mapping = std.AutoHashMap(Card, u32).init(gpa);
|
||||
errdefer mapping.deinit();
|
||||
try mapping.ensureTotalCapacity(52);
|
||||
|
||||
const hearts_start_index: u32 = 0;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .hearts, .rank = @intCast(rank + 1) },
|
||||
hearts_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const diamonds_start_index: u32 = 14;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .diamonds, .rank = @intCast(rank + 1) },
|
||||
diamonds_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const clubs_start_index: u32 = 28;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .clubs, .rank = @intCast(rank + 1) },
|
||||
clubs_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
const spades_start_index: u32 = 42;
|
||||
for (0..13) |rank| {
|
||||
mapping.putAssumeCapacityNoClobber(
|
||||
Card{ .suit = .spades, .rank = @intCast(rank + 1) },
|
||||
spades_start_index + @as(u32, @intCast(rank)),
|
||||
);
|
||||
}
|
||||
|
||||
return DeckSprites{
|
||||
.tilesheet = tilesheet,
|
||||
.mapping = mapping.unmanaged,
|
||||
.blank = 13,
|
||||
.back = 27,
|
||||
};
|
||||
}
|
||||
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
926
src/main.zig
926
src/main.zig
|
@ -1,89 +1,110 @@
|
|||
var gl_binding: gl.Binding = undefined;
|
||||
|
||||
const Suit = enum(u2) {
|
||||
clubs = 0b00,
|
||||
spades = 0b01,
|
||||
hearts = 0b10,
|
||||
diamonds = 0b11,
|
||||
const HandlerError = error{OutOfMemory};
|
||||
const Handler = *const fn (std.mem.Allocator, Request) HandlerError!Response;
|
||||
|
||||
pub fn color(this: @This()) u1 {
|
||||
return (@intFromEnum(this) & 0b10) >> 1;
|
||||
}
|
||||
const Request = struct {
|
||||
game_state: GameState,
|
||||
command: ?[]const u8,
|
||||
};
|
||||
const Card = packed struct(u6) {
|
||||
suit: Suit,
|
||||
rank: u4,
|
||||
const Response = union(enum) {
|
||||
page: Element,
|
||||
transition: struct {
|
||||
game_state: GameState,
|
||||
can_undo: bool,
|
||||
},
|
||||
};
|
||||
|
||||
/// A texture with a regular grid of sprites
|
||||
const TileSheet = struct {
|
||||
texture: seizer.Texture,
|
||||
tile_size: [2]u32,
|
||||
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),
|
||||
|
||||
pub const InitOptions = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
image_file_contents: []const u8,
|
||||
tile_size: [2]u32,
|
||||
texture_options: seizer.Texture.InitFromFileOptions = .{},
|
||||
};
|
||||
pub fn init(allocator: std.mem.Allocator, seed: u64, num_players: usize) !@This() {
|
||||
var draw_pile = std.ArrayListUnmanaged(Card).fromOwnedSlice(try makeStandardDeck(allocator));
|
||||
errdefer draw_pile.deinit(allocator);
|
||||
|
||||
var prng = std.rand.DefaultPrng.init(seed);
|
||||
|
||||
prng.random().shuffle(Card, draw_pile.items);
|
||||
|
||||
const cards_per_player: usize = switch (num_players) {
|
||||
1, 2 => 7,
|
||||
3, 4 => 6,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const hands = try allocator.alloc(std.ArrayListUnmanaged(Card), num_players);
|
||||
errdefer allocator.free(hands);
|
||||
for (hands) |*hand| {
|
||||
hand.* = try std.ArrayListUnmanaged(Card).initCapacity(allocator, cards_per_player);
|
||||
for (0..cards_per_player) |_| {
|
||||
hand.appendAssumeCapacity(draw_pile.pop());
|
||||
}
|
||||
}
|
||||
|
||||
var discard_pile = try std.ArrayListUnmanaged(Card).initCapacity(allocator, 52);
|
||||
errdefer discard_pile.deinit(allocator);
|
||||
|
||||
// start game with one card in the discard pile
|
||||
discard_pile.appendAssumeCapacity(draw_pile.pop());
|
||||
|
||||
pub fn init(options: InitOptions) !@This() {
|
||||
const texture = try seizer.Texture.initFromFileContents(
|
||||
options.allocator,
|
||||
options.image_file_contents,
|
||||
options.texture_options,
|
||||
);
|
||||
return @This(){
|
||||
.texture = texture,
|
||||
.tile_size = options.tile_size,
|
||||
.allocator = allocator,
|
||||
.prng = prng,
|
||||
.handler = &drawCardHandler,
|
||||
.draw_pile = draw_pile,
|
||||
.discard_pile = discard_pile,
|
||||
.hands = hands,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(this: *@This()) void {
|
||||
this.texture.deinit();
|
||||
this.draw_pile.deinit(this.allocator);
|
||||
this.discard_pile.deinit(this.allocator);
|
||||
for (this.hands) |*hand| {
|
||||
hand.deinit(this.allocator);
|
||||
}
|
||||
this.allocator.free(this.hands);
|
||||
}
|
||||
|
||||
pub fn renderTile(this: @This(), canvas: *seizer.Canvas, tile_id: u32, pos: [2]f32, options: struct {
|
||||
size: ?[2]f32 = null,
|
||||
}) void {
|
||||
const texture_sizef = [2]f32{
|
||||
@floatFromInt(this.texture.size[0]),
|
||||
@floatFromInt(this.texture.size[1]),
|
||||
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,
|
||||
};
|
||||
const tile_sizef = [2]f32{
|
||||
@floatFromInt(this.tile_size[0]),
|
||||
@floatFromInt(this.tile_size[1]),
|
||||
};
|
||||
const size_in_tiles = [2]u32{
|
||||
@as(u32, @intCast(this.texture.size[0])) / this.tile_size[0],
|
||||
@as(u32, @intCast(this.texture.size[1])) / this.tile_size[1],
|
||||
};
|
||||
const pos_in_tiles = [2]u32{
|
||||
tile_id % size_in_tiles[0],
|
||||
tile_id / size_in_tiles[0],
|
||||
};
|
||||
const pos_in_tilesf = [2]f32{
|
||||
@floatFromInt(pos_in_tiles[0]),
|
||||
@floatFromInt(pos_in_tiles[1]),
|
||||
};
|
||||
const uv = seizer.geometry.AABB(f32){
|
||||
.min = .{
|
||||
(pos_in_tilesf[0] * tile_sizef[0]) / texture_sizef[0],
|
||||
(pos_in_tilesf[1] * tile_sizef[1]) / texture_sizef[1],
|
||||
},
|
||||
.max = .{
|
||||
((pos_in_tilesf[0] + 1) * tile_sizef[0]) / texture_sizef[0],
|
||||
((pos_in_tilesf[1] + 1) * tile_sizef[1]) / texture_sizef[1],
|
||||
},
|
||||
};
|
||||
_ = canvas.printText(.{ 0, 0 }, "uv = {}", .{uv}, .{});
|
||||
canvas.rect(pos, options.size orelse tile_sizef, .{
|
||||
.texture = this.texture.glTexture,
|
||||
.uv = uv,
|
||||
});
|
||||
}
|
||||
};
|
||||
// const cards_medium_spritesheet =
|
||||
|
||||
pub fn makeStandardDeck(allocator: std.mem.Allocator) ![]Card {
|
||||
var deck = try allocator.alloc(Card, 52);
|
||||
errdefer allocator.free(deck);
|
||||
|
||||
var next_index: usize = 0;
|
||||
for (0..4) |suit| {
|
||||
for (1..14) |rank| {
|
||||
deck[next_index] = Card{
|
||||
.suit = @enumFromInt(suit),
|
||||
.rank = @intCast(rank),
|
||||
};
|
||||
next_index += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return deck;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
|
@ -116,25 +137,135 @@ pub fn main() !void {
|
|||
gl.makeBindingCurrent(&gl_binding);
|
||||
|
||||
// Set up input callbacks
|
||||
var input_state: InputState = undefined;
|
||||
_ = seizer.backend.glfw.c.glfwSetWindowUserPointer(window, &input_state);
|
||||
|
||||
_ = seizer.backend.glfw.c.glfwSetKeyCallback(window, &glfw_key_callback);
|
||||
_ = seizer.backend.glfw.c.glfwSetFramebufferSizeCallback(window, &glfw_framebuffer_size_callback);
|
||||
|
||||
var card_tilemap = try TileSheet.init(.{
|
||||
.allocator = gpa.allocator(),
|
||||
.image_file_contents = @embedFile("./cardsMedium_tilemap.png"),
|
||||
.tile_size = .{ 32, 32 },
|
||||
.texture_options = .{
|
||||
.min_filter = .nearest,
|
||||
.mag_filter = .nearest,
|
||||
},
|
||||
});
|
||||
defer card_tilemap.deinit();
|
||||
var card_tilemap_small: ?DeckSprites = null;
|
||||
defer if (card_tilemap_small) |*tilemap| tilemap.deinit(gpa.allocator());
|
||||
|
||||
var card_tilemap_medium: ?DeckSprites = null;
|
||||
defer if (card_tilemap_medium) |*tilemap| tilemap.deinit(gpa.allocator());
|
||||
|
||||
var card_tilemap_large: ?DeckSprites = null;
|
||||
defer if (card_tilemap_large) |*tilemap| tilemap.deinit(gpa.allocator());
|
||||
|
||||
var canvas = try seizer.Canvas.init(gpa.allocator(), .{});
|
||||
defer canvas.deinit(gpa.allocator());
|
||||
|
||||
// game state
|
||||
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();
|
||||
|
||||
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) {
|
||||
input_state = .{
|
||||
.left = false,
|
||||
.right = false,
|
||||
.up = false,
|
||||
.down = false,
|
||||
.action = false,
|
||||
.undo = false,
|
||||
};
|
||||
seizer.backend.glfw.c.glfwPollEvents();
|
||||
|
||||
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) {
|
||||
direction[0] -= 1;
|
||||
}
|
||||
if (input_state.right) {
|
||||
direction[0] += 1;
|
||||
}
|
||||
if (input_state.up) {
|
||||
direction[1] -= 1;
|
||||
}
|
||||
if (input_state.down) {
|
||||
direction[1] += 1;
|
||||
}
|
||||
|
||||
var new_distance: ?f32 = null;
|
||||
var new_action = hovered;
|
||||
for (actions.items) |action| {
|
||||
if (std.mem.eql(u8, action.command, hovered.command)) {
|
||||
continue;
|
||||
}
|
||||
if (distanceToAction(hovered.center, direction, action.center)) |distance| {
|
||||
if (new_distance == null or (new_distance != null and distance < new_distance.?)) {
|
||||
new_action = Action{
|
||||
.center = .{
|
||||
if (input_state.left or input_state.right) action.center[0] else hovered.center[0],
|
||||
if (input_state.up or input_state.down) action.center[1] else hovered.center[1],
|
||||
},
|
||||
.command = action.command,
|
||||
};
|
||||
new_distance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hovered_action = new_action;
|
||||
}
|
||||
|
||||
while (root_element == null) {
|
||||
_ = response_arena.reset(.retain_capacity);
|
||||
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()));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
gl.clearColor(0.7, 0.5, 0.5, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
|
@ -154,19 +285,613 @@ pub fn main() !void {
|
|||
@floatFromInt(framebuffer_size[1]),
|
||||
},
|
||||
});
|
||||
card_tilemap.renderTile(&canvas, 1, .{ 10, 10 }, .{});
|
||||
// canvas.rect(.{ 10, 10 }, .{ @as(f32, @floatFromInt(card_tilemap.size[0])) / 15.0, @as(f32, @floatFromInt(card_tilemap.size[1])) / 10.0 }, .{
|
||||
// .texture = card_tilemap.glTexture,
|
||||
// .uv = .{ .min = .{ 0, 0 }, .max = .{ 1.0 / 15.0, 1.0 / 10.0 } },
|
||||
// });
|
||||
_ = canvas.writeText(.{ 50, 50 }, "Hello, world!", .{});
|
||||
_ = canvas.printText(.{ 50, 100 }, "window_size = {}, {}\nframebuffer_size = {}, {}", .{ window_size[0], window_size[1], framebuffer_size[0], framebuffer_size[1] }, .{});
|
||||
|
||||
_ = 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());
|
||||
},
|
||||
301...1000 => if (card_tilemap_medium == null) {
|
||||
card_tilemap_medium = try assets.loadMediumCards(gpa.allocator());
|
||||
},
|
||||
1001...std.math.maxInt(c_int) => if (card_tilemap_large == null) {
|
||||
card_tilemap_large = try assets.loadLargeCards(gpa.allocator());
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
const deck_sprites = switch (framebuffer_size[1]) {
|
||||
0...300 => card_tilemap_small.?,
|
||||
301...1000 => card_tilemap_medium.?,
|
||||
1001...std.math.maxInt(c_int) => card_tilemap_large.?,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
if (root_element) |root| {
|
||||
root.interface.render(
|
||||
root.pointer,
|
||||
&canvas,
|
||||
.{
|
||||
.hovered = if (hovered_action) |ha| ha.command else null,
|
||||
.deck = deck_sprites,
|
||||
},
|
||||
.{ 0, 0 },
|
||||
.{
|
||||
@floatFromInt(window_size[0]),
|
||||
@floatFromInt(window_size[1]),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
canvas.end();
|
||||
|
||||
seizer.backend.glfw.c.glfwSwapBuffers(window);
|
||||
|
||||
if (root_element) |root| {
|
||||
actions.clearRetainingCapacity();
|
||||
try root.interface.get_actions(
|
||||
root.pointer,
|
||||
&actions,
|
||||
.{
|
||||
.hovered = if (hovered_action) |ha| ha.command else null,
|
||||
.deck = deck_sprites,
|
||||
},
|
||||
.{ 0, 0 },
|
||||
.{
|
||||
@floatFromInt(window_size[0]),
|
||||
@floatFromInt(window_size[1]),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn distanceToAction(origin: [2]f32, direction: [2]f32, other_pos: [2]f32) ?f32 {
|
||||
const off_axis_dir = [2]f32{ direction[1], -direction[0] };
|
||||
const offset = [2]f32{
|
||||
other_pos[0] - origin[0],
|
||||
other_pos[1] - origin[1],
|
||||
};
|
||||
|
||||
// use dot product to check how in line with each direction the point of interest is
|
||||
const in_axis_distance = offset[0] * direction[0] + offset[1] * direction[1];
|
||||
const off_axis_distance = offset[0] * off_axis_dir[0] + offset[1] * off_axis_dir[1];
|
||||
|
||||
if (in_axis_distance > 0) {
|
||||
return in_axis_distance + off_axis_distance * off_axis_distance;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const Action = struct {
|
||||
center: [2]f32,
|
||||
/// A string representing what should occur if this action is taken
|
||||
command: []const u8,
|
||||
};
|
||||
|
||||
const RenderResources = struct {
|
||||
hovered: ?[]const u8,
|
||||
deck: DeckSprites,
|
||||
};
|
||||
|
||||
pub const Element = struct {
|
||||
pointer: ?*anyopaque,
|
||||
interface: *const Interface,
|
||||
|
||||
pub const Error = error{
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
pub const Interface = struct {
|
||||
minimum_size: *const fn (?*anyopaque, RenderResources) [2]f32,
|
||||
render: *const fn (?*anyopaque, *seizer.Canvas, RenderResources, [2]f32, [2]f32) void,
|
||||
get_actions: *const fn (?*anyopaque, *std.ArrayList(Action), RenderResources, [2]f32, [2]f32) Error!void,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Page = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Child),
|
||||
|
||||
pub const Child = struct {
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
/// both the parent and child element. This defines where on the parent that pin
|
||||
/// passes through.
|
||||
///
|
||||
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
|
||||
/// the numbers are negative.
|
||||
anchor_in_parent: [2]f32,
|
||||
/// Where is the child attached on the parent? Imagine it as a pin going through
|
||||
/// both the parent and child element. This defines where on the child that pin
|
||||
/// passes through.
|
||||
///
|
||||
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
|
||||
/// the numbers are negative.
|
||||
anchor_in_child: [2]f32,
|
||||
element: Element,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn addElement(this: *@This(), anchor_in_parent: [2]f32, anchor_in_child: [2]f32, child_element: Element) !void {
|
||||
try this.children.append(this.allocator, Child{
|
||||
.anchor_in_parent = anchor_in_parent,
|
||||
.anchor_in_child = anchor_in_child,
|
||||
.element = child_element,
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
var minimum_size = [2]f32{ 0, 0 };
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
minimum_size = .{
|
||||
@max(minimum_size[0], child_size[0]),
|
||||
@max(minimum_size[1], child_size[1]),
|
||||
};
|
||||
}
|
||||
|
||||
return minimum_size;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
|
||||
const child_min = [2]f32{
|
||||
@max(min[0], pos_in_parent[0] - pos_in_child[0]),
|
||||
@max(min[1], pos_in_parent[1] - pos_in_child[1]),
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
@min(max[0], pos_in_parent[0] + (child_size[0] - pos_in_child[0])),
|
||||
@min(max[1], pos_in_parent[1] + (child_size[1] - pos_in_child[1])),
|
||||
};
|
||||
|
||||
child.element.interface.render(child.element.pointer, canvas, render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
for (this.children.items) |child| {
|
||||
const pos_in_parent = [2]f32{
|
||||
child.anchor_in_parent[0] * parent_size[0],
|
||||
child.anchor_in_parent[1] * parent_size[1],
|
||||
};
|
||||
|
||||
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
|
||||
|
||||
const pos_in_child = [2]f32{
|
||||
child.anchor_in_child[0] * child_size[0],
|
||||
child.anchor_in_child[1] * child_size[1],
|
||||
};
|
||||
|
||||
const child_min = [2]f32{
|
||||
pos_in_parent[0] - pos_in_child[0],
|
||||
pos_in_parent[1] - pos_in_child[1],
|
||||
};
|
||||
const child_max = [2]f32{
|
||||
pos_in_parent[0] + (child_size[0] - pos_in_child[0]),
|
||||
pos_in_parent[1] + (child_size[1] - pos_in_child[1]),
|
||||
};
|
||||
|
||||
try child.element.interface.get_actions(child.element.pointer, actions, render_resources, child_min, child_max);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const HBox = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
children: std.ArrayListUnmanaged(Element),
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.children = .{},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn addElement(this: *@This(), child_element: Element) !void {
|
||||
try this.children.append(this.allocator, child_element);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
var minimum_size = [2]f32{ 0, 0 };
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
minimum_size = .{
|
||||
minimum_size[0] + child_size[0],
|
||||
@max(minimum_size[1], child_size[1]),
|
||||
};
|
||||
}
|
||||
|
||||
return minimum_size;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const num_spaces = if (empty_space > 0) this.children.items.len + 1 else this.children.items.len - 1;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt(num_spaces));
|
||||
|
||||
var x: f32 = min[0] + @max(space_around, 0);
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
child.interface.render(child.pointer, canvas, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
if (this.children.items.len == 0) return;
|
||||
|
||||
const parent_size = [2]f32{
|
||||
max[0] - min[0],
|
||||
max[1] - min[1],
|
||||
};
|
||||
|
||||
var filled_space: f32 = 0;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
filled_space += child_size[0];
|
||||
}
|
||||
|
||||
const empty_space = parent_size[0] - filled_space;
|
||||
|
||||
const space_around = empty_space / @as(f32, @floatFromInt((this.children.items.len + 1)));
|
||||
|
||||
var x: f32 = min[0] + space_around;
|
||||
for (this.children.items) |child| {
|
||||
const child_size = child.interface.minimum_size(child.pointer, render_resources);
|
||||
|
||||
const child_min = [2]f32{ x, min[1] };
|
||||
const child_max = [2]f32{ x + child_size[0], max[1] };
|
||||
|
||||
try child.interface.get_actions(child.pointer, actions, render_resources, child_min, child_max);
|
||||
|
||||
x += child_size[0] + space_around;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Pile = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
cards: []Card,
|
||||
hidden: bool = false,
|
||||
command: ?[]const u8 = null,
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, cards: []const Card) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
const cards_owned = try allocator.dupe(Card, cards);
|
||||
errdefer allocator.free(cards_owned);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.cards = cards_owned,
|
||||
};
|
||||
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 .{
|
||||
@floatFromInt(render_resources.deck.tilesheet.tile_size[0]),
|
||||
@as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) + @as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) * @as(f32, @floatFromInt(this.cards.len)) / 52.0 * 0.25,
|
||||
};
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
const start_y: f32 = max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1]));
|
||||
|
||||
for (this.cards, 0..) |card, i| {
|
||||
const oy = -@as(f32, @floatFromInt(i)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
|
||||
if (this.hidden) {
|
||||
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.back, .{
|
||||
min[0],
|
||||
start_y + oy,
|
||||
}, .{});
|
||||
} else {
|
||||
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.getTileForCard(card), .{
|
||||
min[0],
|
||||
start_y + oy,
|
||||
}, .{});
|
||||
}
|
||||
}
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
const oy = -@as(f32, @floatFromInt(this.cards.len)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
|
||||
canvas.rect(
|
||||
.{ min[0], start_y + oy },
|
||||
.{ @floatFromInt(render_resources.deck.tilesheet.tile_size[0]), @floatFromInt(render_resources.deck.tilesheet.tile_size[1]) },
|
||||
.{ .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));
|
||||
|
||||
if (this.command) |command| {
|
||||
const center = [2]f32{
|
||||
min[0] + @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])) / 2,
|
||||
max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * (2.0 - @as(f32, @floatFromInt(this.cards.len)) / (52.0 * 4)),
|
||||
};
|
||||
|
||||
try actions.append(.{
|
||||
.center = center,
|
||||
.command = command,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const CardElement = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
visual: Visual,
|
||||
command: ?[]const u8,
|
||||
|
||||
const Visual = union(enum) {
|
||||
back,
|
||||
card: Card,
|
||||
};
|
||||
|
||||
pub fn create(allocator: std.mem.Allocator, options: struct { visual: Visual, command: ?[]const u8 }) !*@This() {
|
||||
const this = try allocator.create(@This());
|
||||
errdefer allocator.destroy(this);
|
||||
|
||||
this.* = .{
|
||||
.allocator = allocator,
|
||||
.visual = options.visual,
|
||||
.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));
|
||||
_ = this;
|
||||
|
||||
return .{
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])),
|
||||
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[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));
|
||||
|
||||
switch (this.visual) {
|
||||
.back => render_resources.deck.tilesheet.renderTile(
|
||||
canvas,
|
||||
render_resources.deck.back,
|
||||
.{ min[0], min[1] },
|
||||
.{},
|
||||
),
|
||||
.card => |card| render_resources.deck.tilesheet.renderTile(
|
||||
canvas,
|
||||
render_resources.deck.getTileForCard(card),
|
||||
.{ min[0], min[1] },
|
||||
.{},
|
||||
),
|
||||
}
|
||||
|
||||
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
|
||||
canvas.rect(
|
||||
min,
|
||||
.{ max[0] - min[0], max[1] - min[1] },
|
||||
.{ .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| {
|
||||
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";
|
||||
|
||||
var discard_pile = try Pile.create(arena, request.game_state.discard_pile.items);
|
||||
discard_pile.command = "draw discard_pile";
|
||||
|
||||
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 = 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());
|
||||
}
|
||||
|
||||
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 glfw_framebuffer_size_callback(window: ?*seizer.backend.glfw.c.GLFWwindow, width: c_int, height: c_int) callconv(.C) void {
|
||||
_ = window;
|
||||
gl.viewport(
|
||||
|
@ -177,6 +902,37 @@ fn glfw_framebuffer_size_callback(window: ?*seizer.backend.glfw.c.GLFWwindow, wi
|
|||
);
|
||||
}
|
||||
|
||||
const InputState = struct {
|
||||
left: bool,
|
||||
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 {
|
||||
const input_state = @as(*InputState, @alignCast(@ptrCast(seizer.backend.glfw.c.glfwGetWindowUserPointer(window))));
|
||||
|
||||
_ = scancode;
|
||||
_ = mods;
|
||||
if (action == seizer.backend.glfw.c.GLFW_PRESS) {
|
||||
switch (key) {
|
||||
seizer.backend.glfw.c.GLFW_KEY_LEFT => input_state.left = true,
|
||||
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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const DeckSprites = assets.DeckSprites;
|
||||
const Card = assets.Card;
|
||||
|
||||
const assets = @import("./assets.zig");
|
||||
const seizer = @import("seizer");
|
||||
const gl = seizer.gl;
|
||||
const std = @import("std");
|
||||
|
|
Loading…
Reference in New Issue