I used a fixed point type for the X coordinate in part 2. I doubt this is how you’re supposed to solve it, but it worked for me.
Heh, I wondered if that was possible. Very cool solution.
I didn’t see part 2, so only about part 1:
while I don’t think I would do this my actual code, since I was having fun, I decided to play with reduce operations for the GPS calculation. I had a vector of vector of map icons, so I ended up learning I need to double Reduce (one for each dimension):
function Calculate_GPS(Current : Map_Coordinate) return GPS_Value is
(100 * (GPS_Value(Current.Y)-1) + GPS_Value(Current.X)-1);
function Calculate_GPS(Map : Warehouse_Map) return GPS_Value is
([for Y in 1 .. Map.Last_Index =>
[for X in 1 .. Map(Y).Last_Index =>
(if Map(Y)(X) = Box then
Calculate_GPS(Map_Coordinate'(X,Y))
else
0)
]'Reduce("+",0)
]'Reduce("+",0));
My general algorithm was to look at the next location and if it was a box, then keep looking that direction until I hit a wall or a space. If it was a wall, I couldn’t do anything, but if I hit a space, then I could shift everything one space:
procedure Push_Up(Current : Map_Coordinate; Map : in out Warehouse_Map)
with Pre => Map(Current.Y)(Current.X) = Box
and Map(Current.Y+1)(Current.X) = Robot;
procedure Push_Up(Current : Map_Coordinate; Map : in out Warehouse_Map) is
Last : constant Y_Axis := Current.Y - 1;
First : constant Y_Axis := 1;
begin
for Y in reverse First .. Last loop
case Map(Y)(Current.X) is
when Box => null; -- skip
when Wall => return;
when Robot => raise Constraint_Error with "Multiple robots in map";
when Space =>
Map(Y)(Current.X) := Box;
Map(Current.Y)(Current.X) := Robot;
Map(Current.Y+1)(Current.X) := Space;
return;
end case;
end loop;
raise Constraint_Error with "Map not bounded by wall";
end Push_Up;
-- Similar procedures for the other 3 directions
procedure Move(Command : Command_Icon; Current : in out Map_Coordinate; Map : in out Warehouse_Map)
with Pre => Map(Current.Y)(Current.X) = Robot,
Post => Map(Current.Y)(Current.X) = Robot
and Current.X in Current.X'Old-1 .. Current.X'Old+1
and Current.Y in Current.Y'Old-1 .. Current.Y'Old+1;
procedure Move(Command : Command_Icon; Current : in out Map_Coordinate; Map : in out Warehouse_Map) is
Next : constant Map_Coordinate :=
(case Command is
when Up => (X => Current.X, Y => Current.Y-1),
when Down => (X => Current.X, Y => Current.Y+1),
when Left => (X => Current.X-1, Y => Current.Y ),
when Right => (X => Current.X+1, Y => Current.Y ));
begin
case Map(Next.Y)(Next.X) is
when Robot => raise Constraint_Error with "Multiple robots found";
when Wall => null;
when Space =>
Map(Next.Y)(Next.X) := Robot;
Map(Current.Y)(Current.X) := Space;
Current := Next;
when Box =>
case Command is
when Up => Push_Up(Next, Map);
when Down => Push_Down(Next, Map);
when Left => Push_Left(Next, Map);
when Right => Push_Right(Next, Map);
end case;
if Map(Next.Y)(Next.X) = Robot then
Current := Next;
end if;
end case;
end Move;
If I read you right, you missed an optimization: instead of moving each box, just move the first one in a row after the last one. The boxes are otherwise indistinguishable.
I thought of it immediately because I’m always anxious that the second part will repeat the first part, with one of its twists being to crank the volume up several orders of magnitude. Reducing the number of boxes to move can make life a little easier.
As it happens, that was not merely useless in Part 2, the twist essentially forces you not to optimize that way. Oh, well.
I probably worded it poorly (I’m not super good at explaining). I did the following
when Space =>
Map(Y)(Current.X) := Box;
Map(Current.Y)(Current.X) := Robot;
Map(Current.Y+1)(Current.X) := Space;
return;
Here I set the empty space after the boxes to a box and then set the original first box to a robot, then set the old robot to a space. I didn’t move all the boxes. Hopefully that explains what I did better? Sorry for the poor wording.
Ah, I get it now. I think I just didn’t pay enough attention to the difference between Map(Y)
and Map(Current.Y)
.