Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions zig/example4-reach24.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# # # # # #
2 3 3 3 2 #
2 3 3 3 2 #
. . . 1 1 #
. . . 1 1 #
. . . 1 1 #
129 changes: 75 additions & 54 deletions zig/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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];
}
};
}
Expand Down Expand Up @@ -823,6 +821,7 @@ const Solver = struct {
board: Board,
mines: MinesInfo,
per_cell: u8,
reach: u8,
computed_state: ComputedState = .{},

const ComputedState = struct {
Expand All @@ -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,
};
}

Expand Down Expand Up @@ -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);
Expand All @@ -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| {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1145,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;
Expand Down Expand Up @@ -1311,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!
Expand Down Expand Up @@ -1507,6 +1519,7 @@ fn parseArgs() !Args {
clap.parseParam("-m, --mines <NUM> Number of mines") catch unreachable,
clap.parseParam("-d, --infinite-density <VAL> Density of mines on infinite board") catch unreachable,
clap.parseParam("-p, --per-cell <NUM> Max number of mines per cell") catch unreachable,
clap.parseParam("-r, --reach <NUM> 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,
};
Expand Down Expand Up @@ -1579,6 +1592,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;
}
Expand Down Expand Up @@ -1619,12 +1640,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 },
),
}

Expand Down Expand Up @@ -1655,7 +1676,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) {
Expand Down Expand Up @@ -1820,7 +1841,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());
Expand Down
3 changes: 2 additions & 1 deletion zig/zig_minesolver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:
Expand Down