From b38d143605a3d763d8fbab9749eb93b679468674 Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Tue, 28 Nov 2023 23:44:47 +0000 Subject: [PATCH 1/2] Add support for 'reach' (4, 8, 24), crashing for 24... --- zig/main.zig | 121 +++++++++++++++++++-------------- zig/zig_minesolver/__init__.py | 3 +- 2 files changed, 71 insertions(+), 53 deletions(-) diff --git a/zig/main.zig b/zig/main.zig index 3a85436..5362dda 100644 --- a/zig/main.zig +++ b/zig/main.zig @@ -216,6 +216,7 @@ const Args = struct { input_file: File, mines: MinesInfo, per_cell: u8 = 1, + reach: u8 = 8, debug: bool = false, quiet: bool = false, }; @@ -419,53 +420,50 @@ fn Grid(comptime T: type) type { } /// Stores the result in the provided buffer. - pub fn getNeighbours(g: Self, x: usize, y: usize, buf: *[8]Entry) []const Entry { - var x_min: usize = x; - var x_max: usize = x; - var y_min: usize = y; - var y_max: usize = y; - if (x > 0) x_min -= 1; - if (x < g.xSize() - 1) x_max += 1; - if (y > 0) y_min -= 1; - if (y < g.ySize() - 1) y_max += 1; - - var i: usize = x_min; + pub fn getNeighbours( + g: Self, + x: usize, + y: usize, + reach: u8, + buf: []Entry, + ) []const Entry { + assert(buf.len >= reach); var nbr_idx: usize = 0; - while (i <= x_max) : (i += 1) { - var j: usize = y_min; - while (j <= y_max) : (j += 1) { - if (i == x and j == y) continue; - buf[nbr_idx] = .{ .x = i, .y = j, .value = g.getCell(i, j) }; + if (reach == 4) { + if (x > 0) { + buf[nbr_idx] = .{ .x = x - 1, .y = y, .value = g.getCell(x - 1, y) }; nbr_idx += 1; } - } - return buf[0..nbr_idx]; - } - - /// Allocates the slice - memory owned by the caller. - pub fn getNeighboursAlloc(g: Self, x: usize, y: usize) error{OutOfMemory}![]const Entry { - var x_min: usize = x; - var x_max: usize = x; - var y_min: usize = y; - var y_max: usize = y; - if (x > 0) x_min -= 1; - if (x < g.xSize() - 1) x_max += 1; - if (y > 0) y_min -= 1; - if (y < g.ySize() - 1) y_max += 1; - const num_nbrs = (x_max - x_min + 1) * (y_max - y_min + 1) - 1; - var nbrs = ArrayList(Entry).init(allocator); - try nbrs.ensureTotalCapacity(num_nbrs); - var i: usize = x_min; - while (i <= x_max) : (i += 1) { - var j: usize = y_min; - while (j <= y_max) : (j += 1) { - if (i == x and j == y) continue; - nbrs.appendAssumeCapacity( - Entry{ .x = i, .y = j, .value = g.getCell(i, j) }, - ); + if (y > 0) { + buf[nbr_idx] = .{ .x = x, .y = y - 1, .value = g.getCell(x, y - 1) }; + nbr_idx += 1; + } + if (x < g.xSize() - 1) { + buf[nbr_idx] = .{ .x = x + 1, .y = y, .value = g.getCell(x + 1, y) }; + nbr_idx += 1; + } + if (y < g.ySize() - 1) { + buf[nbr_idx] = .{ .x = x, .y = y + 1, .value = g.getCell(x, y + 1) }; + nbr_idx += 1; + } + } else { + assert(reach == 8 or reach == 24); + const step: usize = if (reach == 8) 1 else 2; + const x_min: usize = if (x >= step) x - step else 0; + const x_max: usize = if (x < g.xSize() - step) x + step else g.xSize() - 1; + const y_min: usize = if (y >= step) y - step else 0; + const y_max: usize = if (y < g.ySize() - step) y + step else g.ySize() - 1; + var i: usize = x_min; + while (i <= x_max) : (i += 1) { + var j: usize = y_min; + while (j <= y_max) : (j += 1) { + if (i == x and j == y) continue; + buf[nbr_idx] = .{ .x = i, .y = j, .value = g.getCell(i, j) }; + nbr_idx += 1; + } } } - return nbrs.toOwnedSlice(); + return buf[0..nbr_idx]; } }; } @@ -823,6 +821,7 @@ const Solver = struct { board: Board, mines: MinesInfo, per_cell: u8, + reach: u8, computed_state: ComputedState = .{}, const ComputedState = struct { @@ -847,11 +846,12 @@ const Solver = struct { /// The board is still owned by the caller and should be independently /// deinitialised. - pub fn init(board: Board, mines_info: MinesInfo, per_cell: u8) Self { + pub fn init(board: Board, mines_info: MinesInfo, per_cell: u8, reach: u8) Self { return Self{ .board = board, .mines = mines_info, .per_cell = per_cell, + .reach = reach, }; } @@ -1047,7 +1047,9 @@ const Solver = struct { // - An array of numbers (where elements contain the cell index, the // number value and a list of group indices) - var nbrs_buf: [8]Grid(CellContents).Entry = undefined; + // Use array of size 24 since this is the largest supported reach value. + assert(self.reach <= 24); + var nbrs_buf: [24]Grid(CellContents).Entry = undefined; // Find the numbers (we want these to be in order). var numbers = ArrayList(ComputedState.Number).init(allocator); @@ -1064,6 +1066,7 @@ const Solver = struct { const num_nbrs = self.board.grid.getNeighbours( entry.x, entry.y, + self.reach, &nbrs_buf, ); for (num_nbrs) |num_nbr_entry| { @@ -1094,14 +1097,19 @@ const Solver = struct { var outer_group = ArrayList(usize).init(allocator); defer outer_group.deinit(); - var num_nbr_idxs_buf: [8]usize = undefined; + var num_nbr_idxs_buf: [24]usize = undefined; var cell_idx: usize = 0; while (cell_idx < self.board.xSize() * self.board.ySize()) : (cell_idx += 1) { const entry = self.board.grid.getEntryAtIdx(cell_idx); if (entry.value != .Unclicked) continue; // Create a slice of indices of number neighbours. - const nbrs = self.board.grid.getNeighbours(entry.x, entry.y, &nbrs_buf); + const nbrs = self.board.grid.getNeighbours( + entry.x, + entry.y, + self.reach, + &nbrs_buf, + ); var num_nbr_idxs_idx: usize = 0; for (nbrs) |nbr| { if (nbr.value == .Number) { @@ -1507,6 +1515,7 @@ fn parseArgs() !Args { clap.parseParam("-m, --mines Number of mines") catch unreachable, clap.parseParam("-d, --infinite-density Density of mines on infinite board") catch unreachable, clap.parseParam("-p, --per-cell Max number of mines per cell") catch unreachable, + clap.parseParam("-r, --reach How many neighbour cells the numbers reach") catch unreachable, clap.parseParam("-v, --verbose Output debug info and logging to stderr") catch unreachable, clap.parseParam("-q, --quiet Emit less output to stderr") catch unreachable, }; @@ -1579,6 +1588,14 @@ fn parseArgs() !Args { args.per_cell = per_cell; } + if (parse_result.args.reach) |reach| { + if (!(reach == 4 or reach == 8 or reach == 24)) { + try stderr.writeAll("Expected reach value to be one of 4, 8 or 24\n"); + return error.InvalidArgument; + } + args.reach = reach; + } + if (parse_result.args.verbose != 0) { args.debug = true; } @@ -1619,12 +1636,12 @@ pub fn main() !u8 { const verbosity = if (args.debug) "verbose" else if (args.quiet) "quiet" else "default"; switch (args.mines) { .Num => |num_mines| std.log.debug( - "Parsed args: mines={d}, per_cell={d}, verbosity={s}", - .{ num_mines, args.per_cell, verbosity }, + "Parsed args: mines={d}, per_cell={d}, reach={d}, verbosity={s}", + .{ num_mines, args.per_cell, args.reach, verbosity }, ), .Density => |density| std.log.debug( - "Parsed args: density={d}, per_cell={d}, verbosity={s}", - .{ density, args.per_cell, verbosity }, + "Parsed args: density={d}, per_cell={d}, reach={d}, verbosity={s}", + .{ density, args.per_cell, args.reach, verbosity }, ), } @@ -1655,7 +1672,7 @@ pub fn main() !u8 { defer board.deinit(); try stdout.print("Board:\n{s}\n", .{try board.toStr()}); - var solver = Solver.init(board, args.mines, args.per_cell); + var solver = Solver.init(board, args.mines, args.per_cell, args.reach); defer solver.deinit(); if (args.debug) { @@ -1820,7 +1837,7 @@ test "Solver: invalid board" { ); defer board.deinit(); - var solver = Solver.init(board, .{ .Num = 1 }, 1); + var solver = Solver.init(board, .{ .Num = 1 }, 1, 4); defer solver.deinit(); try std.testing.expectError(error.InvalidMatrixEquations, solver.solve()); diff --git a/zig/zig_minesolver/__init__.py b/zig/zig_minesolver/__init__.py index fe42277..0f0cc1c 100644 --- a/zig/zig_minesolver/__init__.py +++ b/zig/zig_minesolver/__init__.py @@ -14,6 +14,7 @@ def get_board_probs( mines: Optional[int] = None, density: Optional[float] = None, per_cell: int = 1, + reach: int = 8, ) -> List[List[float]]: """ Get probabilities for a minesweeper board. @@ -30,7 +31,7 @@ def get_board_probs( Either a number of mines or a density of mines must be given. """ - cmd = [str(THIS_DIR / "zig-main"), "--per-cell", str(per_cell)] + cmd = [str(THIS_DIR / "zig-main"), "--per-cell", str(per_cell), "--reach", str(reach)] if mines is not None: cmd += ["--mines", str(mines)] if density is not None: From 70741750feaab1d1bfb67e91ffed171ea380480c Mon Sep 17 00:00:00 2001 From: Lewis Gaul Date: Wed, 29 Nov 2023 01:23:16 +0000 Subject: [PATCH 2/2] Fix crash from including empty outer group --- zig/example4-reach24.txt | 6 ++++++ zig/main.zig | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 zig/example4-reach24.txt diff --git a/zig/example4-reach24.txt b/zig/example4-reach24.txt new file mode 100644 index 0000000..c996dab --- /dev/null +++ b/zig/example4-reach24.txt @@ -0,0 +1,6 @@ +# # # # # # +2 3 3 3 2 # +2 3 3 3 2 # +. . . 1 1 # +. . . 1 1 # +. . . 1 1 # diff --git a/zig/main.zig b/zig/main.zig index 5362dda..ac1c65c 100644 --- a/zig/main.zig +++ b/zig/main.zig @@ -1153,16 +1153,17 @@ const Solver = struct { self.computed_state.numbers = try numbers.toOwnedSlice(); + const include_outer_group = (self.mines == .Num and outer_group.items.len > 0); const groups_slice = try allocator.alloc( []const usize, - groups.items.len + @as(usize, if (self.mines == .Num) 1 else 0), + groups.items.len + @as(usize, if (include_outer_group) 1 else 0), ); errdefer allocator.free(groups_slice); for (groups.items, 0..) |*grp, i| { groups_slice[i] = try grp.toOwnedSlice(); } // This makes the outer group always come at the end. - if (self.mines == .Num) + if (include_outer_group) groups_slice[groups_slice.len - 1] = try outer_group.toOwnedSlice(); return groups_slice; @@ -1319,6 +1320,9 @@ const Solver = struct { var log_combs: f64 = 0; for (cfg, 0..) |m_i, i| { const g_size = groups[i].len; + // TODO: Is this a bug?!! + // I think we should be adding up the actual combinations, + // not the log of the combinations... log_combs += logCombs(g_size, m_i, self.per_cell); var k: u16 = 1; while (k <= m_i) : (k += 1) { // Divide by m_i!