# Four Trucks Puzzle

## Spoilers

### Solution using A*

This doesnâ€™t take particularly long to exhaust all possibilities after it finds a solution so a depth first search would have been fine.

``````using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace Trucks
{
class Program
{

enum TruckGoal
{
Present,
House,
Flag
}

// Co-ordinate system
// Bottom left = (0,0)

// Co-ordinates of the present, house and flag
// for each truck
public static int[,] TruckGoalX = new int[4, 3]
{
{3, 0, 3},{0, 3, 2},{3, 0, 1},{0, 3, 0}
};

public static int[,] TruckGoalY = new int[4, 3]
{
{0, 3, 3},{2, 1, 3},{2, 1, 3},{0, 3, 3}
};

// Describes a possible move from a given initial state
class Move
{
public State InitialState { get; set; }

// Which direction does each truck move on this turn
public int[] DeltaX { get; } = new int[4];
public int[] DeltaY { get; } = new int[4];

// Which junctions and roads were used on this turn
public bool[] JunctionUsed { get; } = new bool[16];
public bool[] VerticalRoadUsed { get; } = new bool[12];
public bool[] HorizontalRoadUsed { get; } = new bool[12];

// Which junction and road did each truck use on this turn
public int[] TruckUsedJunction { get; } = new int[4];
public int[] TruckUsedVerticalRoad { get; } = new int[4];
public int[] TruckUsedHorizontalRoad { get; } = new int[4];

public Move(State initialState)
{
InitialState = initialState;
}

public Move(Move copyFrom)
{
InitialState = copyFrom.InitialState;

Array.Copy(copyFrom.DeltaX, DeltaX, 4);
Array.Copy(copyFrom.DeltaY, DeltaY, 4);
Array.Copy(copyFrom.JunctionUsed, JunctionUsed, 16);
Array.Copy(copyFrom.VerticalRoadUsed, VerticalRoadUsed, 12);
Array.Copy(copyFrom.HorizontalRoadUsed, HorizontalRoadUsed, 12);
Array.Copy(copyFrom.TruckUsedHorizontalRoad, TruckUsedHorizontalRoad, 4);
Array.Copy(copyFrom.TruckUsedVerticalRoad, TruckUsedVerticalRoad, 4);
}
}

// Defines a particular state (or node on the A* graph)
class State : IComparable<State>
{

public State PreviousState = null;

// The score of this state for use in the A* priority function
private int _score;

public State() { }

// Build a state from a particular move
public State(Move m)
{

PreviousState = m.InitialState;

Distance = m.InitialState.Distance + 1;

Array.Copy(m.InitialState.TruckGoal, TruckGoal, 4);
Array.Copy(m.InitialState.TruckHorizontalRoadTraversed, TruckHorizontalRoadTraversed, 48);
Array.Copy(m.InitialState.TruckVerticalRoadTraversed, TruckVerticalRoadTraversed, 48);

int finishedAllGoals = 0;

for (int t = 0; t < 4; t++)
{
var truckX = m.InitialState.TruckX[t] + m.DeltaX[t];
var truckY = m.InitialState.TruckY[t] + m.DeltaY[t];
var truckGoal = TruckGoal[t];
var truckGoalX = TruckGoalX[t, (int)truckGoal];
var truckGoalY = TruckGoalY[t, (int)truckGoal];

TruckX[t] = truckX;
TruckY[t] = truckY;

var horizontalRoad = m.TruckUsedHorizontalRoad[t];
if (horizontalRoad != -1)
TruckHorizontalRoadTraversed[t, horizontalRoad] = true;

var verticalRoad = m.TruckUsedVerticalRoad[t];
if (verticalRoad != -1)
TruckVerticalRoadTraversed[t, verticalRoad] = true;

// If this move brings us to the next goal then advance
// the truck on to the next goal.
if (truckX == truckGoalX
&& truckY == truckGoalY)
{

var newGoal = (int)TruckGoal[t] + 1;

if (newGoal == 3)
{
// Count how many trucks have finished all goals
finishedAllGoals++;
}
else
{
TruckGoal[t] = (TruckGoal)newGoal;
}
}

}

// If all four trucks are at the end then print the route
if (finishedAllGoals == 4)
{
var printState = this;

var stack = new Stack<State>();

do
{
stack.Push(printState);
printState = printState.PreviousState;
} while (printState != null);

int i = 0;
Console.WriteLine("STEP Yellow  Blue  Green   Red");
while (stack.TryPop(out var state))
{
Console.Write(\$"{i,3}. ({state.TruckX[0]}, {state.TruckY[0]}) ");
Console.Write(\$"({state.TruckX[1]}, {state.TruckY[1]}) ");
Console.Write(\$"({state.TruckX[2]}, {state.TruckY[2]}) ");

Console.WriteLine(\$"({state.TruckX[3]}, {state.TruckY[3]}) ");
i++;
}
Console.WriteLine();

}
}

// How far each truck has travelled so far
public int Distance { get; set; }

// Where each truck currently is
public int[] TruckX { get; } = new int[4] { 0, 1, 2, 3 };
public int[] TruckY { get; } = new int[4] { 0, 0, 0, 0 };

// A map of where each truck was for each turn up to this state
public bool[,] TruckHorizontalRoadTraversed { get; } = new bool[4,12];
public bool[,] TruckVerticalRoadTraversed { get; } = new bool[4,12];

// How many goals has each truck completed
TruckGoal[] TruckGoal { get; } = new TruckGoal[4];

public int CompareTo([AllowNull] State other)
{
if (other._score > _score) return -1;
if (other._score < _score) return 1;
return 0;
}

public int CalculateScore()
{
// Calculate score for use in A* priority queue
_score = Heuristic() + Distance;
return _score;
}

// Return all the valid moves that can be made from this state
public IEnumerable<Move> ValidMoves()
{
return ValidMoves(0, new Move(this));
}

private IEnumerable<Move> ValidMoves(int truck, Move move)
{

if (truck == 4)
{
yield return move;
yield break;
}

var y = TruckY[truck];
var x = TruckX[truck];

// UP
if (y < 3)
{
var destJunction = x + (y + 1) * 4;
var verticalRoad = (y) * 4 + x;
if (!TruckVerticalRoadTraversed[truck, verticalRoad] // This truck has not already used this road
&& !move.VerticalRoadUsed[verticalRoad] // AND another truck is not using this road this turn
&& !move.JunctionUsed[destJunction] // AND another truck is not using this junction this turn
&& !(x == 1 && y == 1) // AND we're not using any of the blocked routes
&& !(x == 3 && y == 1))
{
var newMove = new Move(move);
newMove.VerticalRoadUsed[verticalRoad] = true;
newMove.JunctionUsed[destJunction] = true;
newMove.DeltaX[truck] = 0;
newMove.DeltaY[truck] = 1;
newMove.TruckUsedHorizontalRoad[truck] = -1;
newMove.TruckUsedVerticalRoad[truck] = verticalRoad;

foreach (var r in ValidMoves(truck + 1, newMove))
yield return r;
}
}

// DOWN
if (y > 0)
{
var destJunction = x + (y - 1) * 4;
var verticalRoad = (y - 1) * 4 + x;
if (!TruckVerticalRoadTraversed[truck, verticalRoad]
&& !move.VerticalRoadUsed[verticalRoad]
&& !move.JunctionUsed[destJunction]
&& !(x == 1 && y == 2)
&& !(x == 2 && y == 3))
{
var newMove = new Move(move);
newMove.VerticalRoadUsed[verticalRoad] = true;
newMove.JunctionUsed[destJunction] = true;
newMove.DeltaX[truck] = 0;
newMove.DeltaY[truck] = -1;
newMove.TruckUsedHorizontalRoad[truck] = -1;
newMove.TruckUsedVerticalRoad[truck] = verticalRoad;

foreach (var r in ValidMoves(truck + 1, newMove))
yield return r;
}
}

// LEFT
if (x > 0)
{
var destJunction = (x - 1) + y * 4;
var horizontalRoad = y * 3 + (x - 1);
if (!TruckHorizontalRoadTraversed[truck, horizontalRoad]
&& !move.HorizontalRoadUsed[horizontalRoad]
&& !move.JunctionUsed[destJunction]
&& !(x == 1 && y == 1)
&& !(x == 3 && y == 1)
&& !(x == 3 && y == 2))
{
var newMove = new Move(move);
newMove.HorizontalRoadUsed[horizontalRoad] = true;
newMove.JunctionUsed[destJunction] = true;
newMove.DeltaX[truck] = -1;
newMove.DeltaY[truck] = 0;
newMove.TruckUsedHorizontalRoad[truck] = horizontalRoad;
newMove.TruckUsedVerticalRoad[truck] = -1;

foreach (var r in ValidMoves(truck + 1, newMove))
yield return r;
}
}

// RIGHT
if (x < 3)
{
var destJunction = (x + 1) + y * 4;
var horizontalRoad = y * 3 + (x );
if (!TruckHorizontalRoadTraversed[truck, horizontalRoad]
&& !move.HorizontalRoadUsed[horizontalRoad]
&& !move.JunctionUsed[destJunction])
{
var newMove = new Move(move);
newMove.HorizontalRoadUsed[horizontalRoad] = true;
newMove.JunctionUsed[destJunction] = true;
newMove.DeltaX[truck] = 1;
newMove.DeltaY[truck] = 0;
newMove.TruckUsedHorizontalRoad[truck] = horizontalRoad;
newMove.TruckUsedVerticalRoad[truck] = -1;

foreach (var r in ValidMoves(truck + 1, newMove))
yield return r;
}
}

}

// Calculates the A* heuristic function
private int Heuristic()
{

int maxDistance = 0;

for (int truck = 0; truck < 4; truck++)
{

// For each truck, calculate the euclidean distance from
// the current location to each remaining goal in turn
var currentGoal = TruckGoal[truck];

var truckX = TruckX[truck];
var truckY = TruckY[truck];

var distance = 0;

for (int i = (int)currentGoal; i < 3; i++)
{
var goalX = TruckGoalX[truck, i];
var goalY = TruckGoalY[truck, i];

distance += Math.Abs(truckX - goalX) + Math.Abs(truckY - goalY);

truckX = goalX;
truckY = goalY;
}

if (distance > maxDistance)
maxDistance = distance;

}

// return the worst case for any truck
return maxDistance;

}

}

static void Main(string[] args)
{

var priorityQueue = new PriorityQueue<State>();

// Add the initial state to the queue
var initial = new State();
initial.CalculateScore();
priorityQueue.Enqueue(initial);

while (priorityQueue.Count() > 0)
{

// Take the lowest score state off the queue
var state = priorityQueue.Dequeue();

// Push every valid move from this state onto the queue
foreach (var m in state.ValidMoves())
{
var newState = new State(m);
newState.CalculateScore();
priorityQueue.Enqueue(newState);
}

}
}
}

public class PriorityQueue<T> where T : IComparable<T>
{
private List<T> data;

public PriorityQueue()
{
this.data = new List<T>();
}

public void Enqueue(T item)
{
data.Add(item);
int ci = data.Count - 1; // child index; start at end
while (ci > 0)
{
int pi = (ci - 1) / 2; // parent index
if (data[ci].CompareTo(data[pi]) >= 0)
break; // child item is larger than (or equal) parent so we're done
T tmp = data[ci];
data[ci] = data[pi];
data[pi] = tmp;
ci = pi;
}
}

public T Dequeue()
{
// assumes pq is not empty; up to calling code
int li = data.Count - 1; // last index (before removal)
T frontItem = data[0];   // fetch the front
data[0] = data[li];
data.RemoveAt(li);

--li; // last index (after removal)
int pi = 0; // parent index. start at front of pq
while (true)
{
int ci = pi * 2 + 1; // left child index of parent
if (ci > li)
break;  // no children so done
int rc = ci + 1;     // right child
if (rc <= li && data[rc].CompareTo(data[ci]) < 0) // if there is a rc (ci + 1), and it is smaller than left child, use the rc instead
ci = rc;
if (data[pi].CompareTo(data[ci]) <= 0)
break; // parent is smaller than (or equal to) smallest child so done
T tmp = data[pi];
data[pi] = data[ci];
data[ci] = tmp; // swap parent and child
pi = ci;
}
return frontItem;
}

public T Peek()
{
T frontItem = data[0];
return frontItem;
}

public int Count()
{
return data.Count;
}

public override string ToString()
{
string s = "";
for (int i = 0; i < data.Count; ++i)
s += data[i].ToString() + " ";
s += "count = " + data.Count;
return s;
}

public bool IsConsistent()
{
// is the heap property true for all data?
if (data.Count == 0)
return true;
int li = data.Count - 1; // last index
for (int pi = 0; pi < data.Count; ++pi)
{ // each parent index
int lci = 2 * pi + 1; // left child index
int rci = 2 * pi + 2; // right child index

if (lci <= li && data[pi].CompareTo(data[lci]) > 0)
return false; // if lc exists and it's greater than parent then bad.
if (rci <= li && data[pi].CompareTo(data[rci]) > 0)
return false; // check the right child too.
}
return true; // passed all checks
}
// IsConsistent
}

}``````

#### Output

``````STEP Yellow  Blue  Green   Red
0. (0, 0) (1, 0) (2, 0) (3, 0)
1. (0, 1) (0, 0) (2, 1) (2, 0)
2. (1, 1) (0, 1) (2, 2) (1, 0)
3. (2, 1) (0, 2) (2, 3) (0, 0)
4. (3, 1) (1, 2) (3, 3) (0, 1)
5. (3, 0) (2, 2) (3, 2) (1, 1)
6. (2, 0) (3, 2) (3, 1) (2, 1)
7. (2, 1) (3, 1) (3, 0) (2, 2)
8. (2, 2) (3, 0) (2, 0) (3, 2)
9. (1, 2) (2, 0) (1, 0) (3, 3)
10. (0, 2) (1, 0) (0, 0) (2, 3)
11. (0, 3) (1, 1) (0, 1) (1, 3)
12. (1, 3) (2, 1) (0, 2) (1, 2)
13. (2, 3) (2, 2) (0, 3) (0, 2)
14. (3, 3) (2, 3) (1, 3) (0, 3)``````