initial version of seizer-solitaire
commit
b199c7d52e
|
@ -0,0 +1,2 @@
|
||||||
|
zig-out/
|
||||||
|
zig-cache/
|
|
@ -0,0 +1,56 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// Although this function looks imperative, note that its job is to
|
||||||
|
// declaratively construct a build graph that will be executed by an external
|
||||||
|
// runner.
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
// Standard target options allows the person running `zig build` to choose
|
||||||
|
// what target to build for. Here we do not override the defaults, which
|
||||||
|
// means any target is allowed, and the default is native. Other options
|
||||||
|
// for restricting supported target set are available.
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
|
||||||
|
// Standard optimization options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||||
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const seizer = b.dependency("seizer", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "seizer-solitaire",
|
||||||
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe.root_module.addImport("seizer", seizer.module("seizer"));
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
|
||||||
|
const exe_unit_tests = b.addTest(.{
|
||||||
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
exe_unit_tests.root_module.addImport("seizer", seizer.module("seizer"));
|
||||||
|
|
||||||
|
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
|
||||||
|
|
||||||
|
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||||
|
// the `zig build --help` menu, providing a way for the user to request
|
||||||
|
// running the unit tests.
|
||||||
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
test_step.dependOn(&run_exe_unit_tests.step);
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
.{
|
||||||
|
.name = "seizer-solitaire",
|
||||||
|
// This is a [Semantic Version](https://semver.org/).
|
||||||
|
// In a future version of Zig it will be used for package deduplication.
|
||||||
|
.version = "0.0.0",
|
||||||
|
|
||||||
|
// This field is optional.
|
||||||
|
// This is currently advisory only; Zig does not yet do anything
|
||||||
|
// with this value.
|
||||||
|
//.minimum_zig_version = "0.11.0",
|
||||||
|
|
||||||
|
// This field is optional.
|
||||||
|
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||||
|
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||||
|
// Once all dependencies are fetched, `zig build` no longer requires
|
||||||
|
// internet connectivity.
|
||||||
|
.dependencies = .{
|
||||||
|
.seizer = .{
|
||||||
|
.url = "https://github.com/leroycep/seizer/archive/ec651ce18e423a8fdda3b1a3369d9121c45d96f0.tar.gz",
|
||||||
|
.hash = "122028487862f2462ba02191adbc6676b621b37fa05e0962f0a5990d4079ed34140e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
// This makes *all* files, recursively, included in this package. It is generally
|
||||||
|
// better to explicitly list the files and directories instead, to insure that
|
||||||
|
// fetching from tarballs, file system paths, and version control all result
|
||||||
|
// in the same contents hash.
|
||||||
|
"",
|
||||||
|
// For example...
|
||||||
|
//"build.zig",
|
||||||
|
//"build.zig.zon",
|
||||||
|
//"src",
|
||||||
|
//"LICENSE",
|
||||||
|
//"README.md",
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,394 @@
|
||||||
|
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 = .{ 13, 12 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const seizer = @import("seizer");
|
||||||
|
const gl = seizer.gl;
|
||||||
|
const std = @import("std");
|
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,549 @@
|
||||||
|
pub const main = seizer.main;
|
||||||
|
|
||||||
|
var gpa: std.mem.Allocator = undefined;
|
||||||
|
var prng: std.rand.DefaultPrng = undefined;
|
||||||
|
var canvas: seizer.Canvas = undefined;
|
||||||
|
var card_tilemap: ?DeckSprites = null;
|
||||||
|
|
||||||
|
var draw_pile: std.ArrayListUnmanaged(Card) = .{};
|
||||||
|
var draw_pile_exhausted = false;
|
||||||
|
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 last_hovered_stack: usize = 0;
|
||||||
|
var last_hovered_foundation: usize = 0;
|
||||||
|
var hovered_deck: ?*std.ArrayListUnmanaged(Card) = null;
|
||||||
|
var hovered_card: usize = 0;
|
||||||
|
|
||||||
|
var selected_deck: ?*std.ArrayListUnmanaged(Card) = null;
|
||||||
|
var selected_card: usize = 0;
|
||||||
|
|
||||||
|
pub fn init(context: *seizer.Context) !void {
|
||||||
|
gpa = context.gpa;
|
||||||
|
prng = std.rand.DefaultPrng.init(@bitCast(std.time.timestamp()));
|
||||||
|
|
||||||
|
const window = try context.createWindow(.{
|
||||||
|
.title = "Seizer Solitaire",
|
||||||
|
.on_render = render,
|
||||||
|
.on_destroy = destroy,
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas = try seizer.Canvas.init(context.gpa, .{});
|
||||||
|
errdefer canvas.deinit();
|
||||||
|
|
||||||
|
card_tilemap = try assets.loadSolitaireCards(context.gpa);
|
||||||
|
|
||||||
|
draw_pile = std.ArrayListUnmanaged(Card).fromOwnedSlice(try makeStandardDeck(gpa));
|
||||||
|
prng.random().shuffle(Card, draw_pile.items);
|
||||||
|
|
||||||
|
for (&stacks, 0..) |*stack, i| {
|
||||||
|
for (0..i + 1) |_| {
|
||||||
|
const drawn_card = draw_pile.pop();
|
||||||
|
try stack.append(gpa, drawn_card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update joystick to gamepad mappings
|
||||||
|
update_gamepad_mappings_file: {
|
||||||
|
const sdl_controller_config_filepath = std.process.getEnvVarOwned(gpa, "SDL_GAMECONTROLLERCONFIG_FILE") catch break :update_gamepad_mappings_file;
|
||||||
|
defer gpa.free(sdl_controller_config_filepath);
|
||||||
|
|
||||||
|
std.log.debug("Loading gamepad mappings from file: \"{}\"", .{std.zig.fmtEscapes(sdl_controller_config_filepath)});
|
||||||
|
|
||||||
|
const controller_config_data = std.fs.cwd().readFileAllocOptions(
|
||||||
|
gpa,
|
||||||
|
sdl_controller_config_filepath,
|
||||||
|
512 * 1024 * 1024,
|
||||||
|
null,
|
||||||
|
@alignOf(u8),
|
||||||
|
0,
|
||||||
|
) catch break :update_gamepad_mappings_file;
|
||||||
|
defer gpa.free(controller_config_data);
|
||||||
|
|
||||||
|
if (!seizer.backend.glfw.Joystick.updateGamepadMappings(controller_config_data)) {
|
||||||
|
std.log.warn("Failed to update gamepad mappings from file", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
update_gamepad_mappings: {
|
||||||
|
const sdl_controller_config = std.process.getEnvVarOwned(gpa, "SDL_GAMECONTROLLERCONFIG") catch break :update_gamepad_mappings;
|
||||||
|
defer gpa.free(sdl_controller_config);
|
||||||
|
|
||||||
|
std.log.debug("Loading gamepad mappings from environment variable", .{});
|
||||||
|
|
||||||
|
const sdl_controller_configz = gpa.dupeZ(u8, sdl_controller_config) catch break :update_gamepad_mappings;
|
||||||
|
|
||||||
|
if (!seizer.backend.glfw.Joystick.updateGamepadMappings(sdl_controller_configz)) {
|
||||||
|
std.log.warn("Failed to update gamepad mappings from environment variable", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.glfw_window.setKeyCallback(key_input_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(window: *seizer.Window) void {
|
||||||
|
_ = window;
|
||||||
|
draw_pile.deinit(gpa);
|
||||||
|
drawn_cards.deinit(gpa);
|
||||||
|
for (&stacks) |*stack| {
|
||||||
|
stack.deinit(gpa);
|
||||||
|
}
|
||||||
|
for (&foundations) |*foundation| {
|
||||||
|
foundation.deinit(gpa);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (card_tilemap) |*ct| {
|
||||||
|
ct.deinit(gpa);
|
||||||
|
card_tilemap = null;
|
||||||
|
}
|
||||||
|
canvas.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(window: *seizer.Window) !void {
|
||||||
|
for (1..@intFromEnum(seizer.backend.glfw.Joystick.Id.last)) |i| {
|
||||||
|
const joystick = seizer.backend.glfw.Joystick{ .jid = @enumFromInt(i) };
|
||||||
|
if (joystick.getGamepadState()) |gamepad| {
|
||||||
|
if (gamepad.getButton(.a) == .press) doSelectOrPlace();
|
||||||
|
if (gamepad.getButton(.dpad_left) == .press) moveLeft();
|
||||||
|
if (gamepad.getButton(.dpad_right) == .press) moveRight();
|
||||||
|
if (gamepad.getButton(.dpad_up) == .press) moveUp();
|
||||||
|
if (gamepad.getButton(.dpad_down) == .press) moveDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.clearColor(0.2, 0.4, 0.2, 1.0);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
canvas.begin(.{
|
||||||
|
.window_size = window.getSize(),
|
||||||
|
.framebuffer_size = window.getFramebufferSize(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const scale: u32 = if (canvas.window_size[1] <= 400) 1 else 2;
|
||||||
|
const scalef: f32 = @floatFromInt(scale);
|
||||||
|
const tile_size = [2]u32{
|
||||||
|
card_tilemap.?.tilesheet.tile_size[0] * scale,
|
||||||
|
card_tilemap.?.tilesheet.tile_size[1] * scale,
|
||||||
|
};
|
||||||
|
const margin = card_tilemap.?.margin * scale;
|
||||||
|
const hand_offset = [2]u32{
|
||||||
|
card_tilemap.?.hand_offset[0] * scale,
|
||||||
|
card_tilemap.?.hand_offset[1] * scale,
|
||||||
|
};
|
||||||
|
const tile_sizef = [2]f32{ @floatFromInt(tile_size[0]), @floatFromInt(tile_size[1]) };
|
||||||
|
|
||||||
|
for (&stacks, 0..) |*stack, i| {
|
||||||
|
const deck_is_selected = selected_deck != null and selected_deck.? == stack;
|
||||||
|
const deck_is_hovered = hovered_deck != null and hovered_deck.? == stack;
|
||||||
|
const deck_color = if (deck_is_selected)
|
||||||
|
[4]u8{ 0xFF, 0xA0, 0xA0, 0xFF }
|
||||||
|
else if (deck_is_hovered)
|
||||||
|
[4]u8{ 0xA0, 0xFF, 0xA0, 0xFF }
|
||||||
|
else
|
||||||
|
[4]u8{ 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
var pos = [2]f32{
|
||||||
|
@floatFromInt(margin + ((tile_size[0] + margin) * (i + 1))),
|
||||||
|
@floatFromInt(margin + margin + tile_size[1]),
|
||||||
|
};
|
||||||
|
canvas.rect(pos, tile_sizef, .{ .color = deck_color });
|
||||||
|
|
||||||
|
for (stack.items, 0..) |card, j| {
|
||||||
|
const is_selected = deck_is_selected and j >= selected_card;
|
||||||
|
const is_hovered = deck_is_hovered and j >= hovered_card;
|
||||||
|
const color = if (is_selected)
|
||||||
|
[4]u8{ 0xFF, 0xA0, 0xA0, 0xFF }
|
||||||
|
else if (is_hovered)
|
||||||
|
[4]u8{ 0xA0, 0xFF, 0xA0, 0xFF }
|
||||||
|
else
|
||||||
|
[4]u8{ 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
const tile_id = card_tilemap.?.getTileForCard(card);
|
||||||
|
card_tilemap.?.tilesheet.renderTile(&canvas, tile_id, pos, .{
|
||||||
|
.size = tile_sizef,
|
||||||
|
.color = color,
|
||||||
|
});
|
||||||
|
pos[1] += @floatFromInt(hand_offset[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (&foundations, 0..) |*foundation, i| {
|
||||||
|
const is_hovered = hovered_deck != null and hovered_deck.? == foundation;
|
||||||
|
const color = if (is_hovered) [4]u8{ 0xA0, 0xFF, 0xA0, 0xFF } else [4]u8{ 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
const pos = [2]f32{
|
||||||
|
@floatFromInt(margin),
|
||||||
|
@floatFromInt(margin + ((margin + tile_size[1]) * (i + 1))),
|
||||||
|
};
|
||||||
|
canvas.rect(pos, tile_sizef, .{ .color = color });
|
||||||
|
|
||||||
|
for (foundation.items) |card| {
|
||||||
|
const tile_id = card_tilemap.?.getTileForCard(card);
|
||||||
|
card_tilemap.?.tilesheet.renderTile(&canvas, tile_id, pos, .{
|
||||||
|
.size = tile_sizef,
|
||||||
|
.color = color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const deck_is_selected = selected_deck != null and selected_deck.? == &drawn_cards;
|
||||||
|
const deck_is_hovered = hovered_deck != null and hovered_deck.? == &drawn_cards;
|
||||||
|
|
||||||
|
var pos = [2]f32{
|
||||||
|
@floatFromInt(margin + margin + tile_size[0]),
|
||||||
|
@floatFromInt(margin),
|
||||||
|
};
|
||||||
|
for (drawn_cards.items, 0..) |card, j| {
|
||||||
|
const is_selected = deck_is_selected and j >= selected_card;
|
||||||
|
const is_hovered = deck_is_hovered and j >= hovered_card;
|
||||||
|
const color = if (is_selected)
|
||||||
|
[4]u8{ 0xFF, 0xA0, 0xA0, 0xFF }
|
||||||
|
else if (is_hovered)
|
||||||
|
[4]u8{ 0xA0, 0xFF, 0xA0, 0xFF }
|
||||||
|
else
|
||||||
|
[4]u8{ 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
const tile_id = card_tilemap.?.getTileForCard(card);
|
||||||
|
card_tilemap.?.tilesheet.renderTile(&canvas, tile_id, pos, .{
|
||||||
|
.size = tile_sizef,
|
||||||
|
.color = color,
|
||||||
|
});
|
||||||
|
pos[0] += @floatFromInt(hand_offset[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const is_hovered = hovered_deck != null and hovered_deck.? == &draw_pile;
|
||||||
|
const color = if (is_hovered) [4]u8{ 0xA0, 0xFF, 0xA0, 0xFF } else [4]u8{ 0xFF, 0xFF, 0xFF, 0xFF };
|
||||||
|
|
||||||
|
var pos = [2]f32{ @floatFromInt(margin), @floatFromInt(margin) };
|
||||||
|
for (draw_pile.items) |card| {
|
||||||
|
const tile_id = if (!draw_pile_exhausted) card_tilemap.?.back else card_tilemap.?.getTileForCard(card);
|
||||||
|
card_tilemap.?.tilesheet.renderTile(&canvas, tile_id, pos, .{
|
||||||
|
.size = tile_sizef,
|
||||||
|
.color = color,
|
||||||
|
});
|
||||||
|
pos[1] -= 0.25 * scalef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var text_writer = canvas.textWriter(.{});
|
||||||
|
for (1..@intFromEnum(seizer.backend.glfw.Joystick.Id.last)) |i| {
|
||||||
|
const joystick = seizer.backend.glfw.Joystick{ .jid = @enumFromInt(i) };
|
||||||
|
if (joystick.getName()) |name| {
|
||||||
|
text_writer.writer().print("connected_controller[{}] = {s}\n", .{ i, name }) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 key_input_callback(
|
||||||
|
window: seizer.backend.glfw.Window,
|
||||||
|
key: seizer.backend.glfw.Key,
|
||||||
|
scancode: i32,
|
||||||
|
action: seizer.backend.glfw.Action,
|
||||||
|
mods: seizer.backend.glfw.Mods,
|
||||||
|
) void {
|
||||||
|
_ = window;
|
||||||
|
_ = scancode;
|
||||||
|
_ = mods;
|
||||||
|
switch (action) {
|
||||||
|
.press => switch (key) {
|
||||||
|
.z => doSelectOrPlace(),
|
||||||
|
.left => moveLeft(),
|
||||||
|
.right => moveRight(),
|
||||||
|
.down => moveDown(),
|
||||||
|
.up => moveUp(),
|
||||||
|
else => {},
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doSelectOrPlace() void {
|
||||||
|
if (selected_deck == null) {
|
||||||
|
if (hovered_deck == &draw_pile and !draw_pile_exhausted) {
|
||||||
|
drawn_cards.ensureUnusedCapacity(gpa, 3) catch return;
|
||||||
|
for (0..3) |_| {
|
||||||
|
if (draw_pile.popOrNull()) |card| drawn_cards.appendAssumeCapacity(card);
|
||||||
|
}
|
||||||
|
if (draw_pile.items.len == 0) {
|
||||||
|
draw_pile_exhausted = true;
|
||||||
|
}
|
||||||
|
} else if (hovered_deck == &draw_pile and draw_pile_exhausted) {
|
||||||
|
selected_deck = hovered_deck;
|
||||||
|
selected_card = hovered_card;
|
||||||
|
} else if (hovered_deck == &drawn_cards) {
|
||||||
|
selected_deck = hovered_deck;
|
||||||
|
selected_card = hovered_card;
|
||||||
|
} else {
|
||||||
|
for (stacks[0..]) |*stack| {
|
||||||
|
if (hovered_deck != stack) continue;
|
||||||
|
selected_deck = hovered_deck;
|
||||||
|
selected_card = hovered_card;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hovered_deck == selected_deck) {
|
||||||
|
const selected_substack = selected_deck.?.items[selected_card..];
|
||||||
|
|
||||||
|
// try to move all selected cards into the foundations
|
||||||
|
move_cards_to_foundations: for (0..selected_substack.len) |_| {
|
||||||
|
for (foundations[0..]) |*foundation| {
|
||||||
|
if (hovered_deck == foundation) break :move_cards_to_foundations;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (foundations[0..]) |*foundation| {
|
||||||
|
const empty = foundation.items.len == 0;
|
||||||
|
const ace = selected_deck.?.items[selected_card].rank == 1;
|
||||||
|
const suit_matches = !empty and foundation.items[foundation.items.len - 1].suit == selected_deck.?.items[selected_deck.?.items.len - 1].suit;
|
||||||
|
const is_next_rank = !empty and foundation.items[foundation.items.len - 1].rank + 1 == selected_deck.?.items[selected_deck.?.items.len - 1].rank;
|
||||||
|
if ((empty and ace) or (suit_matches and is_next_rank)) {
|
||||||
|
foundation.ensureUnusedCapacity(gpa, 1) catch continue;
|
||||||
|
foundation.appendAssumeCapacity(selected_deck.?.pop());
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break :move_cards_to_foundations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selected_deck = null;
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
|
||||||
|
const hovered = hovered_deck orelse break :move_from_selected_to_hovered;
|
||||||
|
|
||||||
|
if (hovered.items.len == 0) {
|
||||||
|
for (stacks[0..]) |*stack| {
|
||||||
|
if (hovered != stack) continue;
|
||||||
|
hovered.ensureUnusedCapacity(gpa, selected_substack.len) catch break :move_from_selected_to_hovered;
|
||||||
|
hovered.appendSliceAssumeCapacity(selected_substack);
|
||||||
|
selected.shrinkRetainingCapacity(selected.items.len - selected_substack.len);
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
break :move_from_selected_to_hovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected_substack.len == 1) {
|
||||||
|
if (draw_pile_exhausted and hovered == &draw_pile and draw_pile.items.len == 0) {
|
||||||
|
hovered.ensureUnusedCapacity(gpa, 1) catch break :move_from_selected_to_hovered;
|
||||||
|
hovered.appendAssumeCapacity(selected_deck.?.pop());
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
break :move_from_selected_to_hovered;
|
||||||
|
} else for (foundations[0..]) |*foundation| {
|
||||||
|
if (foundation != selected) continue;
|
||||||
|
const empty = foundation.items.len == 0;
|
||||||
|
const ace = selected_deck.?.items[selected_card].rank == 1;
|
||||||
|
const suit_matches = !empty and foundation.items[foundation.items.len - 1].suit == selected_deck.?.items[selected_deck.?.items.len - 1].suit;
|
||||||
|
const is_next_rank = !empty and foundation.items[foundation.items.len - 1].rank + 1 == selected_deck.?.items[selected_deck.?.items.len - 1].rank;
|
||||||
|
if ((empty and ace) or (suit_matches and is_next_rank)) {
|
||||||
|
foundation.ensureUnusedCapacity(gpa, 1) catch continue;
|
||||||
|
foundation.appendAssumeCapacity(selected_deck.?.pop());
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
break :move_from_selected_to_hovered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break :move_from_selected_to_hovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hovered.items[hovered.items.len - 1].rank - 1 != selected_substack[0].rank or
|
||||||
|
hovered.items[hovered.items.len - 1].suit == selected_substack[0].suit)
|
||||||
|
{
|
||||||
|
break :move_from_selected_to_hovered;
|
||||||
|
}
|
||||||
|
hovered.ensureUnusedCapacity(gpa, selected_substack.len) catch break :move_from_selected_to_hovered;
|
||||||
|
hovered.appendSliceAssumeCapacity(selected_substack);
|
||||||
|
selected.shrinkRetainingCapacity(selected.items.len - selected_substack.len);
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
}
|
||||||
|
selected_deck = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveLeft() void {
|
||||||
|
if (hovered_deck == null) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else if (hovered_deck == &draw_pile) {
|
||||||
|
hovered_deck = &drawn_cards;
|
||||||
|
hovered_card = drawn_cards.items.len -| 1;
|
||||||
|
} else if (hovered_deck == &drawn_cards) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else if (hovered_deck == &stacks[0]) {
|
||||||
|
hovered_deck = &foundations[last_hovered_foundation];
|
||||||
|
} else {
|
||||||
|
for (stacks[1..], 1..) |*stack, i| {
|
||||||
|
if (hovered_deck == stack) {
|
||||||
|
hovered_deck = &stacks[i - 1];
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
last_hovered_stack = i - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (&foundations) |*foundation| {
|
||||||
|
if (hovered_deck == foundation) {
|
||||||
|
hovered_deck = &stacks[stacks.len - 1];
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveRight() void {
|
||||||
|
if (hovered_deck == null) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else if (hovered_deck == &draw_pile) {
|
||||||
|
hovered_deck = &drawn_cards;
|
||||||
|
hovered_card = drawn_cards.items.len -| 1;
|
||||||
|
} else if (hovered_deck == &drawn_cards) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else if (hovered_deck == &stacks[stacks.len - 1]) {
|
||||||
|
hovered_deck = &foundations[last_hovered_foundation];
|
||||||
|
} else {
|
||||||
|
for (stacks[0 .. stacks.len - 1], 0..) |*stack, i| {
|
||||||
|
if (hovered_deck == stack) {
|
||||||
|
hovered_deck = &stacks[i + 1];
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
last_hovered_stack = i + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (&foundations) |*foundation| {
|
||||||
|
if (hovered_deck == foundation) {
|
||||||
|
hovered_deck = &stacks[0];
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveUp() void {
|
||||||
|
if (hovered_deck == null) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else if (hovered_deck == &draw_pile) {
|
||||||
|
hovered_deck = &foundations[foundations.len - 1];
|
||||||
|
last_hovered_foundation = foundations.len - 1;
|
||||||
|
} else if (hovered_deck == &drawn_cards) {
|
||||||
|
hovered_deck = &stacks[last_hovered_stack];
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
} else if (hovered_deck == &foundations[0]) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else {
|
||||||
|
for (stacks[0..]) |*stack| {
|
||||||
|
if (hovered_deck != stack) continue;
|
||||||
|
const top_of_stack = indexOfTopOfStack(stack.items);
|
||||||
|
if (hovered_card > top_of_stack) {
|
||||||
|
hovered_card -= 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (drawn_cards.items.len > 0) {
|
||||||
|
hovered_deck = &drawn_cards;
|
||||||
|
hovered_card = drawn_cards.items.len -| 1;
|
||||||
|
} else {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (foundations[1..], 1..) |*foundation, i| {
|
||||||
|
if (hovered_deck == foundation) {
|
||||||
|
hovered_deck = &foundations[i - 1];
|
||||||
|
last_hovered_foundation = i - 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moveDown() void {
|
||||||
|
if (hovered_deck == null) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else if (hovered_deck == &draw_pile) {
|
||||||
|
hovered_deck = &foundations[0];
|
||||||
|
last_hovered_foundation = 0;
|
||||||
|
} else if (hovered_deck == &drawn_cards) {
|
||||||
|
hovered_deck = &stacks[last_hovered_stack];
|
||||||
|
hovered_card = indexOfTopOfStack(hovered_deck.?.items);
|
||||||
|
} else if (hovered_deck == &foundations[foundations.len - 1]) {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
} else {
|
||||||
|
for (stacks[0..]) |*stack| {
|
||||||
|
if (hovered_deck != stack) continue;
|
||||||
|
if (hovered_card < stack.items.len -| 1) {
|
||||||
|
hovered_card += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawn_cards.items.len > 0) {
|
||||||
|
hovered_deck = &drawn_cards;
|
||||||
|
hovered_card = drawn_cards.items.len -| 1;
|
||||||
|
} else {
|
||||||
|
hovered_deck = &draw_pile;
|
||||||
|
hovered_card = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (foundations[0 .. foundations.len - 1], 0..) |*foundation, i| {
|
||||||
|
if (hovered_deck == foundation) {
|
||||||
|
hovered_deck = &foundations[i + 1];
|
||||||
|
last_hovered_foundation = i + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn indexOfTopOfStack(cards: []Card) usize {
|
||||||
|
if (cards.len < 2) return 0;
|
||||||
|
|
||||||
|
var index = cards.len - 1;
|
||||||
|
while (index > 0) : (index -= 1) {
|
||||||
|
const prev_card = cards[index];
|
||||||
|
const card = cards[index - 1];
|
||||||
|
if (card.suit.color() == prev_card.suit.color()) break;
|
||||||
|
if (card.rank != prev_card.rank + 1) break;
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DeckSprites = assets.DeckSprites;
|
||||||
|
const Card = assets.Card;
|
||||||
|
|
||||||
|
const assets = @import("./assets.zig");
|
||||||
|
const seizer = @import("seizer");
|
||||||
|
const gl = seizer.gl;
|
||||||
|
const ecs = seizer.flecs;
|
||||||
|
const std = @import("std");
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
Loading…
Reference in New Issue