Skip to content

Commit 476c0ac

Browse files
committedMay 17, 2018
Add Knight's tour.
1 parent d2c6d14 commit 476c0ac

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed
 

‎README.md

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
* **Uncategorized**
8181
* [Tower of Hanoi](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/hanoi-tower)
8282
* [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens)
83+
* [Knight's Tour](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/knight-tour)
8384
* Union-Find
8485
* Maze
8586
* Sudoku
@@ -114,6 +115,7 @@
114115
* **Backtracking**
115116
* [Hamiltonian Cycle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/graph/hamiltonian-cycle) - Visit every vertex exactly once
116117
* [N-Queens Problem](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/n-queens)
118+
* [Knight's Tour](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/uncategorized/knight-tour)
117119
* **Branch & Bound**
118120

119121
## How to use this repository
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Knight's Tour
2+
3+
A **knight's tour** is a sequence of moves of a knight on a chessboard
4+
such that the knight visits every square only once. If the knight
5+
ends on a square that is one knight's move from the beginning
6+
square (so that it could tour the board again immediately,
7+
following the same path), the tour is **closed**, otherwise it
8+
is **open**.
9+
10+
The **knight's tour problem** is the mathematical problem of
11+
finding a knight's tour. Creating a program to find a knight's
12+
tour is a common problem given to computer science students.
13+
Variations of the knight's tour problem involve chessboards of
14+
different sizes than the usual `8×8`, as well as irregular
15+
(non-rectangular) boards.
16+
17+
The knight's tour problem is an instance of the more
18+
general **Hamiltonian path problem** in graph theory. The problem of finding
19+
a closed knight's tour is similarly an instance of the Hamiltonian
20+
cycle problem.
21+
22+
![Knight's Tour](https://upload.wikimedia.org/wikipedia/commons/d/da/Knight%27s_tour_anim_2.gif)
23+
24+
An open knight's tour of a chessboard.
25+
26+
![Knight's Tour](https://upload.wikimedia.org/wikipedia/commons/c/ca/Knights-Tour-Animation.gif)
27+
28+
An animation of an open knight's tour on a 5 by 5 board.
29+
30+
## References
31+
32+
- [Wikipedia](https://en.wikipedia.org/wiki/Knight%27s_tour)
33+
- [GeeksForGeeks](https://www.geeksforgeeks.org/backtracking-set-1-the-knights-tour-problem/)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import knightTour from '../knightTour';
2+
3+
describe('knightTour', () => {
4+
it('should not find solution on 3x3 board', () => {
5+
const moves = knightTour(3);
6+
7+
expect(moves.length).toBe(0);
8+
});
9+
10+
it('should find one solution to do knight tour on 5x5 board', () => {
11+
const moves = knightTour(5);
12+
13+
expect(moves.length).toBe(25);
14+
15+
expect(moves).toEqual([
16+
[0, 0],
17+
[1, 2],
18+
[2, 0],
19+
[0, 1],
20+
[1, 3],
21+
[3, 4],
22+
[2, 2],
23+
[4, 1],
24+
[3, 3],
25+
[1, 4],
26+
[0, 2],
27+
[1, 0],
28+
[3, 1],
29+
[4, 3],
30+
[2, 4],
31+
[0, 3],
32+
[1, 1],
33+
[3, 0],
34+
[4, 2],
35+
[2, 1],
36+
[4, 0],
37+
[3, 2],
38+
[4, 4],
39+
[2, 3],
40+
[0, 4],
41+
]);
42+
});
43+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/**
2+
* @param {number[][]} chessboard
3+
* @param {number[]} position
4+
* @return {number[][]}
5+
*/
6+
function getPossibleMoves(chessboard, position) {
7+
// Generate all knight moves (event those that goes beyond the board).
8+
const possibleMoves = [
9+
[position[0] - 1, position[1] - 2],
10+
[position[0] - 2, position[1] - 1],
11+
[position[0] + 1, position[1] - 2],
12+
[position[0] + 2, position[1] - 1],
13+
[position[0] - 2, position[1] + 1],
14+
[position[0] - 1, position[1] + 2],
15+
[position[0] + 1, position[1] + 2],
16+
[position[0] + 2, position[1] + 1],
17+
];
18+
19+
// Filter out all moves that go beyond the board.
20+
return possibleMoves.filter((move) => {
21+
const boardSize = chessboard.length;
22+
return move[0] >= 0 && move[1] >= 0 && move[0] < boardSize && move[1] < boardSize;
23+
});
24+
}
25+
26+
/**
27+
* @param {number[][]} chessboard
28+
* @param {number[]} move
29+
* @return {boolean}
30+
*/
31+
function isMoveAllowed(chessboard, move) {
32+
return chessboard[move[0]][move[1]] !== 1;
33+
}
34+
35+
/**
36+
* @param {number[][]} chessboard
37+
* @param {number[][]} moves
38+
* @return {boolean}
39+
*/
40+
function isBoardCompletelyVisited(chessboard, moves) {
41+
const totalPossibleMovesCount = chessboard.length ** 2;
42+
const existingMovesCount = moves.length;
43+
44+
return totalPossibleMovesCount === existingMovesCount;
45+
}
46+
47+
/**
48+
* @param {number[][]} chessboard
49+
* @param {number[][]} moves
50+
* @return {boolean}
51+
*/
52+
function knightTourRecursive(chessboard, moves) {
53+
const currentChessboard = chessboard;
54+
55+
// If board has been completely visited then we've found a solution.
56+
if (isBoardCompletelyVisited(currentChessboard, moves)) {
57+
return true;
58+
}
59+
60+
// Get next possible knight moves.
61+
const lastMove = moves[moves.length - 1];
62+
const possibleMoves = getPossibleMoves(currentChessboard, lastMove);
63+
64+
// Try to do next possible moves.
65+
for (let moveIndex = 0; moveIndex < possibleMoves.length; moveIndex += 1) {
66+
const currentMove = possibleMoves[moveIndex];
67+
68+
// Check if current move is allowed. We aren't allowed to go to
69+
// the same cells twice.
70+
if (isMoveAllowed(currentChessboard, currentMove)) {
71+
// Actually do the move.
72+
moves.push(currentMove);
73+
currentChessboard[currentMove[0]][currentMove[1]] = 1;
74+
75+
// If further moves starting from current are successful then
76+
// return true meaning the solution is found.
77+
if (knightTourRecursive(currentChessboard, moves)) {
78+
return true;
79+
}
80+
81+
// BACKTRACKING.
82+
// If current move was unsuccessful then step back and try to do another move.
83+
moves.pop();
84+
currentChessboard[currentMove[0]][currentMove[1]] = 0;
85+
}
86+
}
87+
88+
// Return false if we haven't found solution.
89+
return false;
90+
}
91+
92+
/**
93+
* @param {number} chessboardSize
94+
* @return {number[][]}
95+
*/
96+
export default function knightTour(chessboardSize) {
97+
// Init chessboard.
98+
const chessboard = Array(chessboardSize).fill(null).map(() => Array(chessboardSize).fill(0));
99+
100+
// Init moves array.
101+
const moves = [];
102+
103+
// Do first move and place the knight to the 0x0 cell.
104+
const firstMove = [0, 0];
105+
moves.push(firstMove);
106+
chessboard[firstMove[0]][firstMove[0]] = 1;
107+
108+
// Recursively try to do the next move.
109+
const solutionWasFound = knightTourRecursive(chessboard, moves);
110+
111+
return solutionWasFound ? moves : [];
112+
}

0 commit comments

Comments
 (0)
Please sign in to comment.