First simulator solve. Still a lot of bugs

This commit is contained in:
Matthias Portzel 2024-02-22 21:13:42 -05:00
parent 65b11083b2
commit 80b7e884cd
7 changed files with 164 additions and 37 deletions

13
README
View File

@ -5,8 +5,17 @@ This creates two main files:
* `zig-out/bin/micromouse` which can be run on the host computer in simulation
# Overview / Assumptions
We're using the same coordinate space as the simulator (mms). This is "math coords", with 0, 0 in the lower left and positive y up.
We're using the same coordinate space as the simulator (mms). This is "math coords", with 0, 0 in the lower left and positive y up. North is up
# Code notes
I'm using usize as my default int type so that I can index into arrays with it, I don't know if there's a better option.
TODO: test with non-square. I did my best but I don't know
TODO: test with non-square mazes. I did my best but I don't know
Use std.log.debug. microzig. yeah
# Simulation
Download the mms simulator from https://github.com/mackorone/mms/releases.
(I installed this to my Applications folder.)
Pop open a new Mouse in the simulator by clicking the plus next to the "Mouse" selector.
Point it at this directory. Set it up with `/usr/local/bin/zig build` as the build command and `./zig-out/bin/micromouse` as the run command.

View File

@ -1,6 +1,13 @@
const std = @import("std");
const assert = std.debug.assert;
// TODO: extract this into robot or main or something
fn print (comptime fmt: []const u8, args: anytype) void {
if (@import("builtin").os.tag != .freestanding) {
std.debug.print(fmt, args);
}
}
const map = @import("map.zig");
const Point = map.Point;
const Cardinal = map.Cardinal;
@ -19,11 +26,14 @@ const Node = struct {
// I could clean this up with like a loop and a helper function
// North
// std.log.debug("North: y + 1 < height {}; not wall: {}", .{self.p.y + 1 < map.height, !isWall(self.p, .north)});
// std.log.debug("isWall(self.p '{}', .north): {}", .{self.p, isWall(self.p, .north)});
if (self.p.y + 1 < map.height and !isWall(self.p, .north)) {
// std.log.debug("Adding node", .{});
nodes.append(&mazeNodes[self.p.y + 1][self.p.x]) catch unreachable;
}
// South
if (self.p.y - 1 >= 0 and !isWall(self.p, .south)) {
if (self.p.y > 0 and !isWall(self.p, .south)) {
nodes.append(&mazeNodes[self.p.y - 1][self.p.x]) catch unreachable;
}
// East
@ -31,7 +41,7 @@ const Node = struct {
nodes.append(&mazeNodes[self.p.y][self.p.x + 1]) catch unreachable;
}
// West
if (self.p.x - 1 >= 0 and !isWall(self.p, .west)) {
if (self.p.x > 0 and !isWall(self.p, .west)) {
nodes.append(&mazeNodes[self.p.y][self.p.x - 1]) catch unreachable;
}
@ -103,11 +113,12 @@ var walls = init: {
// };
fn isEdge(p: Point, direction: Cardinal) bool {
// std.log.debug("is Edge called with point: {} direction: {}\n", .{p, direction});
return (
(p.x == 0 and direction == .west)
or (p.y == 0 and direction == .north)
or (p.y == 0 and direction == .south)
or (p.x == map.width - 1 and direction == .east)
or (p.y == map.height - 1 and direction == .south)
or (p.y == map.height - 1 and direction == .north)
);
}
@ -129,6 +140,7 @@ fn getWall(p: Point, direction: Cardinal) *?bool {
fn isWall(p: Point, direction: Cardinal) bool {
// Boundaries are definitely there
if (isEdge(p, direction)) {
// std.log.debug("forcing true return from isWall for point {} and direction {}", .{p, direction});
return true;
}
@ -141,7 +153,18 @@ fn isWall(p: Point, direction: Cardinal) bool {
pub fn setWall(p: Point, direction: Cardinal, value: bool) void {
// We should maybe have a better way of handling this if it actually happens to the robot
// If that direction is an outer wall, assert that we saw a wall
assert(!(isEdge(p, direction) and !value));
// TODO: cleanup
if (isEdge(p, direction)) {
// std.log.debug("Hello? {} {} {} {}", .{p, direction, isEdge(p, direction), value});
assert(value);
// print("Hello? {} {} {} {}", .{p, direction, isEdge(p, direction), value});
// std.debug.panic("Outside of maze is ");
}
// assert(!(isEdge(p, direction) and !value));
if (isEdge(p, direction)) {
return;
}
const wall = getWall(p, direction);
// wall.* is a ?bool and value is a bool, will this error?
@ -206,16 +229,27 @@ fn hasPoint(l: []const Point, needle: Point) bool {
// Takes a list of destination points, and a mouse
// Blocks until the mouse is in one of the destination points
pub fn floodFill(mouse: *Mouse, dest: []const Point) void {
mouse.updateWalls();
// Calculate optimistic distances from the center
recalcScores(dest);
for (dest) |p| {
const goalNode = &mazeNodes[p.y][p.x];
std.log.debug("goal nodes: {}\n", .{ goalNode });
}
// While we aren't at dest,
while (!hasPoint(dest[0..], mouse.position)) {
const currentSquare = &mazeNodes[mouse.position.y][mouse.position.x];
// 1. get the square adjacent to the mouse which has the lowest score
var minAdjacent: ?*Node = null;
for (currentSquare.getAdjacentNodes().slice()) |n| {
std.log.debug("currentSquare {} score: {}", .{ currentSquare.p, currentSquare.score });
const adjacentNodes = currentSquare.getAdjacentNodes().slice();
std.log.debug("adjacentNodes {}", .{adjacentNodes.len});
for (adjacentNodes) |n| {
std.log.debug("Node at ({}, {}) has score {}", .{ n.p.x, n.p.y, n.score });
assert(@TypeOf(n) == *Node);
if (minAdjacent == null or n.score < minAdjacent.?.score) {
minAdjacent = n;

View File

@ -7,7 +7,7 @@ const map = @import("map.zig");
// TODO: Refactor, move the Mouse declaration into algorithm
const mouse = @import("mouse.zig");
pub fn main() !noreturn {
pub fn main() !void {
var m = mouse.initialize();
algo.floodFill(&m, map.goals[0..]);
@ -15,4 +15,8 @@ pub fn main() !noreturn {
algo.floodFill(&m, starts[0..]);
m.stall();
// if (!mouse.is_robot) {
// @import("simulator.zig").moveForward();
// }
}

View File

@ -25,8 +25,8 @@ pub const TurningDirection = enum {
};
// While we're here, we might as well generalize to a non-square maze
pub const width = 10;
pub const height = 10;
pub const width = 16;
pub const height = 16;
pub const numTiles = width * width;
@ -45,12 +45,14 @@ pub const numTiles = width * width;
pub const goals = [4]Point{
Point { .x = width / 2 , .y = height / 2 },
Point { .x = width / 2 , .y = height / 2 + 1},
Point { .x = width / 2 + 1 , .y = height / 2 },
Point { .x = width / 2 + 1 , .y = height / 2 + 1}
Point { .x = width / 2 , .y = height / 2 - 1},
Point { .x = width / 2 - 1 , .y = height / 2 },
Point { .x = width / 2 - 1 , .y = height / 2 - 1}
};
// This should be compile-time accessible
// @compileLog(goals);
comptime {
// @compileLog(goals);
}
pub fn isGoal (p: Point) bool {
for (goals) |goalPoint| {

View File

@ -1,6 +1,8 @@
const assert = @import("std").debug.assert;
const abs = @import("std").math.absCast; // builtin in 0.12 please
const intDist = @import("util.zig").intDist;
const map = @import("map.zig");
const Cardinal = map.Cardinal;
const Point = map.Point;
@ -62,18 +64,21 @@ pub const Mouse = struct {
}
pub fn moveAdjacent (self: *Mouse, p: Point) void {
// @abs is coming in Zig 0.12. PLEASE
assert(abs(self.position.x - p.x) + abs(self.position.y - p.y) == 1);
// |self.position.x - p.x| + |self.position.y - p.y| == 1
assert(intDist(self.position.x, p.x) + intDist(self.position.y, p.y) == 1);
// assert(@max(self.position.x, p.x) - @min() ==);
// assert(abs(@as(isize, self.position.x) - @as(isize, p.x)) + abs(@as(isize, self.position.y) - @as(isize, p.y)) == 1);
// The direction we need to move to get to this point
var direction: Cardinal = undefined;
// - 1 has a chance to underflow if it's 0.
if (p.x + 1 == self.position.x) {
direction = .west;
}else if (p.x - 1 == self.position.x) {
direction = .east;
}else if (p.y + 1 == self.position.y) {
direction = .south;
}else if (p.y - 1 == self.position.y) {
}else if (p.x > 0 and p.x - 1 == self.position.x) {
direction = .east;
}else if (p.y > 0 and p.y - 1 == self.position.y) {
direction = .north;
}
@ -81,6 +86,9 @@ pub const Mouse = struct {
// I haven't decided if this is going to be implicit 1 square or what
robot.moveForward();
// Update our position
self.position = p;
}
// These functions return null if that direction is the back (where we don't have a sensor)
@ -88,32 +96,32 @@ pub const Mouse = struct {
fn readNorth(self: Mouse) ?bool {
switch (self.facing) {
.north => return robot.readFront(),
.east => return robot.readRight(),
.east => return robot.readLeft(),
.south => return null,
.west => return robot.readLeft()
.west => return robot.readRight()
}
}
fn readEast(self: Mouse) ?bool {
switch (self.facing) {
.north => return robot.readLeft(),
.north => return robot.readRight(),
.east => return robot.readFront(),
.south => return robot.readRight(),
.south => return robot.readLeft(),
.west => return null
}
}
fn readSouth(self: Mouse) ?bool {
switch (self.facing) {
.north => return null,
.east => return robot.readLeft(),
.east => return robot.readRight(),
.south => return robot.readFront(),
.west => return robot.readRight()
.west => return robot.readLeft()
}
}
fn readWest(self: Mouse) ?bool {
switch (self.facing) {
.north => return robot.readRight(),
.north => return robot.readLeft(),
.east => return null,
.south => return robot.readLeft(),
.south => return robot.readRight(),
.west => return robot.readFront()
}
}

View File

@ -1,38 +1,102 @@
const std = @import("std");
const assert = std.debug.assert;
const map = @import("map.zig");
const stdin = std.io.getStdIn().reader();
const stdout = std.io.getStdOut().writer();
// Since this only runs on my computer, we can do whatever we want here
// const allocator =
pub fn setup () void {
}
// Sim docs
// https://github.com/mackorone/mms?tab=readme-ov-file#example
fn waitForAck () void {
var input = std.BoundedArray(u8, 128).init(0) catch unreachable;
_ = stdin.streamUntilDelimiter(input.writer(), '\n', input.capacity()) catch unreachable;
assert(std.mem.eql(u8, input.slice(), "ack"));
}
pub fn moveForward() void {
// TODO
// print "moveForward" to std out
_ = stdout.write("moveForward\n") catch unreachable;
// wait for "ack" on std in
waitForAck();
// var input = std.BoundedArray(u8, 128).init(0) catch unreachable;
// const w = input.writer();
// // _ = w.write("hi") catch unreachable;
// // std.debug.print("Reading stdin {}\n", .{ input.len });
// std.log.debug("Reading std in {}", .{input.capacity()});
// _ = stdin.streamUntilDelimiter(w, '\n', input.capacity()) catch unreachable;
// std.log.debug("Read {}", .{ input });
// std.log.debug("{s}", .{ input.slice()[0..input.len] });
// const got: []u8 = input.slice();
// std.log.debug("{s}", .{ got });
}
pub fn turn90(direction: map.TurningDirection) void {
// TODO
_ = direction;
_ = stdout.write(switch (direction) {
.right => "turnRight\n",
.left => "turnLeft\n"
}) catch unreachable;
// wait for "ack" on std in
waitForAck();
}
pub fn turn180() void {
// TODO
turn90(.left);
turn90(.left);
}
pub fn stall() noreturn {
std.debug.print("Program has completed...", .{});
while (true) {
std.time.sleep(500_000000);
std.debug.print(".", .{});
}
}
// TODO: Style all of these
pub fn readRight() bool {
return true;
_ = stdout.write("wallRight\n") catch unreachable;
var input = std.BoundedArray(u8, 128).init(0) catch unreachable;
_ = stdin.streamUntilDelimiter(input.writer(), '\n', input.capacity()) catch unreachable;
return std.mem.eql(u8, input.slice(), "true");
}
pub fn readLeft() bool {
return true;
_ = stdout.write("wallLeft\n") catch unreachable;
var input = std.BoundedArray(u8, 128).init(0) catch unreachable;
_ = stdin.streamUntilDelimiter(input.writer(), '\n', input.capacity()) catch unreachable;
return std.mem.eql(u8, input.slice(), "true");
}
pub fn readFront() bool {
return true;
_ = stdout.write("wallFront\n") catch unreachable;
var input = std.BoundedArray(u8, 128).init(0) catch unreachable;
_ = stdin.streamUntilDelimiter(input.writer(), '\n', input.capacity()) catch unreachable;
return std.mem.eql(u8, input.slice(), "true");
}
pub fn readBack() bool {
return true;
// What?
// stdout.write("wallBack\n");
// var input = std.BoundedArray(u8, 128).init(0) catch unreachable;
// _ = stdin.streamUntilDelimiter(input.writer(), '\n', input.capacity()) catch unreachable;
// return std.mem.eql(u8, input.slice(), "true");
std.debug.panic();
}

6
src/util.zig Normal file
View File

@ -0,0 +1,6 @@
// Returns the distance between two integers
// Ideally, would take any integer type and
// return the same type but unsigned, since this is always positive or 0
pub fn intDist (int1: usize, int2: usize) usize {
return @max(int1, int2) - @min(int1, int2);
}