forked from trekhleb/javascript-algorithms
-
Notifications
You must be signed in to change notification settings - Fork 3
/
knightTour.js
112 lines (95 loc) · 3.21 KB
/
knightTour.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* @param {number[][]} chessboard
* @param {number[]} position
* @return {number[][]}
*/
function getPossibleMoves(chessboard, position) {
// Generate all knight moves (even those that go beyond the board).
const possibleMoves = [
[position[0] - 1, position[1] - 2],
[position[0] - 2, position[1] - 1],
[position[0] + 1, position[1] - 2],
[position[0] + 2, position[1] - 1],
[position[0] - 2, position[1] + 1],
[position[0] - 1, position[1] + 2],
[position[0] + 1, position[1] + 2],
[position[0] + 2, position[1] + 1],
];
// Filter out all moves that go beyond the board.
return possibleMoves.filter((move) => {
const boardSize = chessboard.length;
return move[0] >= 0 && move[1] >= 0 && move[0] < boardSize && move[1] < boardSize;
});
}
/**
* @param {number[][]} chessboard
* @param {number[]} move
* @return {boolean}
*/
function isMoveAllowed(chessboard, move) {
return chessboard[move[0]][move[1]] !== 1;
}
/**
* @param {number[][]} chessboard
* @param {number[][]} moves
* @return {boolean}
*/
function isBoardCompletelyVisited(chessboard, moves) {
const totalPossibleMovesCount = chessboard.length ** 2;
const existingMovesCount = moves.length;
return totalPossibleMovesCount === existingMovesCount;
}
/**
* @param {number[][]} chessboard
* @param {number[][]} moves
* @return {boolean}
*/
function knightTourRecursive(chessboard, moves) {
const currentChessboard = chessboard;
// If board has been completely visited then we've found a solution.
if (isBoardCompletelyVisited(currentChessboard, moves)) {
return true;
}
// Get next possible knight moves.
const lastMove = moves[moves.length - 1];
const possibleMoves = getPossibleMoves(currentChessboard, lastMove);
// Try to do next possible moves.
for (let moveIndex = 0; moveIndex < possibleMoves.length; moveIndex += 1) {
const currentMove = possibleMoves[moveIndex];
// Check if current move is allowed. We aren't allowed to go to
// the same cells twice.
if (isMoveAllowed(currentChessboard, currentMove)) {
// Actually do the move.
moves.push(currentMove);
currentChessboard[currentMove[0]][currentMove[1]] = 1;
// If further moves starting from current are successful then
// return true meaning the solution is found.
if (knightTourRecursive(currentChessboard, moves)) {
return true;
}
// BACKTRACKING.
// If current move was unsuccessful then step back and try to do another move.
moves.pop();
currentChessboard[currentMove[0]][currentMove[1]] = 0;
}
}
// Return false if we haven't found solution.
return false;
}
/**
* @param {number} chessboardSize
* @return {number[][]}
*/
export default function knightTour(chessboardSize) {
// Init chessboard.
const chessboard = Array(chessboardSize).fill(null).map(() => Array(chessboardSize).fill(0));
// Init moves array.
const moves = [];
// Do first move and place the knight to the 0x0 cell.
const firstMove = [0, 0];
moves.push(firstMove);
chessboard[firstMove[0]][firstMove[0]] = 1;
// Recursively try to do the next move.
const solutionWasFound = knightTourRecursive(chessboard, moves);
return solutionWasFound ? moves : [];
}