395 lines
12 KiB
Zig
395 lines
12 KiB
Zig
pub const Suit = enum(u2) {
|
|
clubs = 0b00,
|
|
spades = 0b01,
|
|
hearts = 0b10,
|
|
diamonds = 0b11,
|
|
|
|
pub fn color(this: @This()) u1 {
|
|
return @intCast((@intFromEnum(this) & 0b10) >> 1);
|
|
}
|
|
};
|
|
|
|
pub const Card = packed struct(u6) {
|
|
suit: Suit,
|
|
rank: Rank,
|
|
|
|
pub const 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 uvCoordinatesFromTileId(this: @This(), tile_id: u32) seizer.geometry.AABB(f32) {
|
|
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],
|
|
};
|
|
return 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],
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn renderTile(this: @This(), canvas: *seizer.Canvas, tile_id: u32, pos: [2]f32, options: struct {
|
|
size: ?[2]f32 = null,
|
|
color: [4]u8 = [4]u8{ 0xFF, 0xFF, 0xFF, 0xFF },
|
|
}) void {
|
|
const uv = this.uvCoordinatesFromTileId(tile_id);
|
|
|
|
canvas.rect(pos, options.size orelse [2]f32{ @floatFromInt(this.tile_size[0]), @floatFromInt(this.tile_size[1]) }, .{
|
|
.texture = this.texture.glTexture,
|
|
.uv = uv,
|
|
.color = options.color,
|
|
});
|
|
}
|
|
};
|
|
|
|
test "tilesheet math is exact" {
|
|
const tilesheet = TileSheet{
|
|
.texture = .{
|
|
.glTexture = undefined,
|
|
.size = .{ 494, 329 },
|
|
},
|
|
.tile_offset = .{ 6, 2 },
|
|
.tile_size = .{ 20, 29 },
|
|
.tile_stride = .{ 33, 33 },
|
|
};
|
|
|
|
try std.testing.expectEqualDeep(seizer.geometry.AABB(f32){
|
|
.min = .{
|
|
468.0 / 494.0,
|
|
35.0 / 329.0,
|
|
},
|
|
.max = .{
|
|
(468.0 + 20.0) / 494.0,
|
|
(35.0 + 29.0) / 329.0,
|
|
},
|
|
}, tilesheet.uvCoordinatesFromTileId(29));
|
|
}
|
|
|
|
/// 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,
|
|
margin: u32,
|
|
hand_offset: [2]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,
|
|
};
|
|
}
|
|
|
|
pub fn loadSolitaireCards(gpa: std.mem.Allocator) !DeckSprites {
|
|
var tilesheet = try TileSheet.init(.{
|
|
.allocator = gpa,
|
|
.image_file_contents = @embedFile("./solitaire-cards.png"),
|
|
.tile_offset = .{ 0, 0 },
|
|
.tile_size = .{ 32, 40 },
|
|
.tile_stride = .{ 32, 40 },
|
|
.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,
|
|
.margin = 4,
|
|
.hand_offset = .{ 10, 12 },
|
|
};
|
|
}
|
|
|
|
const seizer = @import("seizer");
|
|
const gl = seizer.gl;
|
|
const std = @import("std");
|