public void AddStatesForMovesForCar(BoardState state, byte car, int xVec, int yVec, List<BoardState> seenStates, List<BoardState> newStates) { // multiply vector by an increasing number to test all move options until the move is blocked. // e.g. move left 1, move left 2, move left 3, etc int mul = 1; var direction = (xVec != 0) ? (xVec > 0 ? BoardState.Direction.Right : BoardState.Direction.Left) : (yVec > 0 ? BoardState.Direction.Down : BoardState.Direction.Up); while (true) { // reset here but only initalised after validations have been performed BoardState newState = null; for (int y = 0; y < state.Height; y++) { for (int x = 0; x < state.Width; x++) { var curOfs = y * state.Width + x; if (state.Board[curOfs] != car) { continue; } var nx = x + (xVec * mul); var ny = y + (yVec * mul); var newOfs = ny * state.Width + nx; // invalid if the move has caused the piece to move off-board if ((nx < 0) || (ny < 0) || (nx >= state.Width) || (ny >= state.Height)) return; // if there's a matched piece to the left or right, we can't move up or down if ((((x > 0) && (state.Board[curOfs - 1] == car)) || ((x < state.Width - 1) && (state.Board[curOfs + 1] == car))) && (yVec != 0)) return; // if there's a matched piece up or down, we can't move to the left or right if ((((y > 0) && (state.Board[curOfs - state.Width] == car)) || ((y < state.Height - 1) && (state.Board[curOfs + state.Width] == car))) && (xVec != 0)) return; // invalid if the new position is not either empty or another of the same piece if ((state.Board[newOfs] != BoardState.CELL_EMPTY) && (state.Board[newOfs] != car)) return; // initialise after checks have been performed if (newState == null) { newState = new BoardState(state); } if (newState.Board[curOfs] != BoardState.CELL_ACTIVE) { newState.Board[curOfs] = BoardState.CELL_EMPTY; } newState.Board[newOfs] = BoardState.CELL_ACTIVE; } } // will crash with newState = null if the car character was not matched for (int i = 0; i < state.Board.Length; i++) { if (newState.Board[i] == BoardState.CELL_ACTIVE) { newState.Board[i] = car; } } // if this state has ever been seen before, continuing with this tree is pointless // scan backwards because it's very likely that a state not far back is the same rather than one at the beginning for (int i = seenStates.Count() - 1; i >= 0; i--) { if (newState.Equals(seenStates[i])) { return; } } // store the narration newState.Narration = GetNarration(car, mul, direction, newState.CarMap); seenStates.Add(newState); newStates.Add(newState); mul++; } }
public string GetNarration(byte car, int distance, BoardState.Direction direction, Dictionary<byte, char> carMap) { var directionStr = ""; switch (direction) { case BoardState.Direction.Up: directionStr = "↑"; break; case BoardState.Direction.Down: directionStr = "↓"; break; case BoardState.Direction.Left: directionStr = "←"; break; case BoardState.Direction.Right: directionStr = "→"; break; } return string.Format( "{0} {1} {2}", carMap[car], directionStr, distance); }
public List<BoardState> AddStates(BoardState initialState, List<BoardState> seenStates) { var newStates = new List<BoardState>(); // 1-based so we skip empty for (byte car = 1; car <= initialState.CarCount; car++) { this.AddStatesForMovesForCar(initialState, car, 1, 0, seenStates, newStates); this.AddStatesForMovesForCar(initialState, car, -1, 0, seenStates, newStates); this.AddStatesForMovesForCar(initialState, car, 0, 1, seenStates, newStates); this.AddStatesForMovesForCar(initialState, car, 0, -1, seenStates, newStates); } return newStates; }
public BoardState Solve(BoardState initialState, int targetX, int targetY) { int maxDepth = 1; int i = 0; int targetOfs = targetY * initialState.Width + targetX; while (true) { Queue<BoardState> stack = new Queue<BoardState>(); stack.Enqueue(initialState); List<BoardState> seenStates = new List<BoardState>(); seenStates.Add(initialState); i = 0; Console.WriteLine("Searching at depth " + maxDepth + "."); while (stack.Count() > 0) { var state = stack.Dequeue(); if (state.Board[targetOfs] == BoardState.CELL_REDCAR) { return state; } if (state.Depth <= maxDepth) { foreach (var newState in this.AddStates(state, seenStates)) { stack.Enqueue(newState); i++; } } } if (maxDepth > 0) { Console.WriteLine(" Evaluated " + i + " valid leaves."); } if (i == 0) { return null; } maxDepth++; } }
public void RenderBoardToConsole(BoardState board) { Console.WriteLine(); Console.WriteLine(board.Narration); for (int i = 0; i < board.Board.Length; i++) { if (i % board.Width == 0) { Console.WriteLine(); } if (board.Board[i] == 0) { Console.Write(" "); } else { Console.ForegroundColor = this.GetConsoleColorForIndex(board.Board[i]); Console.Write("X"); } } Console.ForegroundColor = ConsoleColor.Gray; Console.WriteLine(); }
public BoardState SetBoardFromText(string[] lines, char redCarChar = 'X', char emptyChar = '.') { var cx = 0; foreach (var line in lines) { if (line.Length > cx) cx = line.Length; } var board = new byte[lines.Length, cx]; Dictionary<char, byte> map = new Dictionary<char, byte>(); map.Add(emptyChar, BoardState.CELL_EMPTY); map.Add(redCarChar, BoardState.CELL_REDCAR); int y = 0; foreach (var line in lines) { int x = 0; foreach (var cell in line) { if (!map.ContainsKey(cell)) { // find a console colour byte colour = 1; while (map.ContainsValue(colour)) { colour++; } if (colour > 0xf) { throw new Exception("Too many cars to render."); } map.Add(cell, colour); } board[y, x] = map[cell]; x++; } y++; } var boardState = new BoardState(board); boardState.CarMap = map.ToDictionary(item => item.Value, item => item.Key); return boardState; }
public BoardState(BoardState parent) { this.Parent = parent; this.CarMap = parent.CarMap; this.Width = parent.Width; this.Height = parent.Height; this.CarCount = parent.CarCount; this.Depth = parent.Depth + 1; this.Board = new byte[parent.Board.Length]; Buffer.BlockCopy(parent.Board, 0, this.Board, 0, parent.Board.Length); }