knightstour 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/index.js +6 -25
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
```
|
|
2
|
+
# KnightsTour
|
|
3
|
+
|
|
4
|
+
A flexible **Knight's Tour** game engine in JavaScript/TypeScript.
|
|
5
|
+
Supports single-player (Open/Closed tours) and multiplayer (Co-op/Versus) modes, with built-in move validation, undo, hints, and automated tour generation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install knightstour
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
or with Yarn:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add knightstour
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import KnightsTour from "knightstour";
|
|
27
|
+
|
|
28
|
+
// Create a new game
|
|
29
|
+
const game = new KnightsTour({
|
|
30
|
+
rows: 8,
|
|
31
|
+
cols: 8,
|
|
32
|
+
players: [
|
|
33
|
+
{ name: "Alice", color: "red", moveIndexes: [] },
|
|
34
|
+
{ name: "Bob", color: "blue", moveIndexes: [] }
|
|
35
|
+
],
|
|
36
|
+
gameMode: "Co-op", // Open, Closed, Versus, Co-op
|
|
37
|
+
disabledIndexes: [0, 7] // optional blocked cells
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Make a move
|
|
41
|
+
game.move(10);
|
|
42
|
+
|
|
43
|
+
// Undo last move
|
|
44
|
+
game.undo();
|
|
45
|
+
|
|
46
|
+
// Get the next available moves for current player
|
|
47
|
+
const nextMoves = game.getAvailableMovesFor(game.turnIndex);
|
|
48
|
+
console.log(nextMoves);
|
|
49
|
+
|
|
50
|
+
// Reset the game
|
|
51
|
+
game.reset();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### `new KnightsTour(config?: KnightsTourConfig)`
|
|
59
|
+
|
|
60
|
+
- `rows` – number of rows (default: 6)
|
|
61
|
+
- `cols` – number of columns (default: 5)
|
|
62
|
+
- `players` – array of player objects:
|
|
63
|
+
```ts
|
|
64
|
+
{
|
|
65
|
+
name: string;
|
|
66
|
+
color: string;
|
|
67
|
+
moveIndexes: number[];
|
|
68
|
+
CPU?: boolean; // optional
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
- `gameMode` – `"Open" | "Closed" | "Co-op" | "Versus"` (default depends on player count)
|
|
72
|
+
- `disabledIndexes` – array of blocked cell indices
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### Methods
|
|
77
|
+
|
|
78
|
+
- `move(index: number): boolean` – Move the current player to the given index
|
|
79
|
+
- `undo(): void` – Undo the last move
|
|
80
|
+
- `getAvailableMovesFor(playerIndex: number): number[]` – Get valid moves for a player
|
|
81
|
+
- `reset(): void` – Reset the game to initial configuration
|
|
82
|
+
- `updateBoard(autoMove?: boolean): void` – Refresh the board and calculate next moves
|
|
83
|
+
- `hint(): void` – Suggest and execute the best move for the current player
|
|
84
|
+
- `solve(increment?: number): void` – Attempt to solve the tour (single-player)
|
|
85
|
+
- `generate(increment?: number): void` – Attempt to generate a valid tour
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### Properties
|
|
90
|
+
|
|
91
|
+
- `rows`, `cols` – board dimensions
|
|
92
|
+
- `players` – current players with their moves
|
|
93
|
+
- `turnIndex` – index of current player
|
|
94
|
+
- `cells` – array of cell objects for rendering
|
|
95
|
+
- `state` – current game state (e.g., `"Game Over"`, `"Alice's Turn"`)
|
|
96
|
+
- `solved` – boolean indicating if a tour is completed
|
|
97
|
+
- `generated` – boolean indicating if a tour was generated
|
|
98
|
+
- `allMoveIndexes` – flattened move history of all players
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## TypeScript Support
|
|
103
|
+
|
|
104
|
+
Type definitions are included. Importing in TypeScript will give full IntelliSense support.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import KnightsTour from "knightstour";
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT © Puzzookie
|
|
115
|
+
```
|
package/index.js
CHANGED
|
@@ -45,14 +45,11 @@ class KnightsTour {
|
|
|
45
45
|
|
|
46
46
|
this.players = players.map(p => ({
|
|
47
47
|
...p,
|
|
48
|
-
// Ensures each player gets a fresh copy of their original moves
|
|
49
48
|
moveIndexes: [...p.moveIndexes]
|
|
50
49
|
}));
|
|
51
50
|
|
|
52
|
-
//need to fix allMoveIndexes
|
|
53
51
|
this.allMoveIndexes = [];
|
|
54
52
|
|
|
55
|
-
|
|
56
53
|
const maxMoves = Math.max(...this.players.map(p => p.moveIndexes.length));
|
|
57
54
|
for (let i = 0; i < maxMoves; i++) {
|
|
58
55
|
for(let j = 0; j < this.players.length; j++)
|
|
@@ -72,7 +69,6 @@ class KnightsTour {
|
|
|
72
69
|
|
|
73
70
|
this.cells = [];
|
|
74
71
|
|
|
75
|
-
// Set Game Mode logic
|
|
76
72
|
if (this.players.length === 1) {
|
|
77
73
|
this.gameMode = (gameMode === "Open" || gameMode === "Closed") ? gameMode : "Open";
|
|
78
74
|
} else {
|
|
@@ -100,28 +96,23 @@ class KnightsTour {
|
|
|
100
96
|
isDisabled: this.disabledIndexes.includes(index),
|
|
101
97
|
isHighlighted: false,
|
|
102
98
|
isClosed: false,
|
|
103
|
-
value: null
|
|
99
|
+
value: null
|
|
104
100
|
};
|
|
105
101
|
})
|
|
106
102
|
);
|
|
107
103
|
}
|
|
108
104
|
|
|
109
|
-
// Helper to get cell by index without repetitive math
|
|
110
105
|
getCell(index) {
|
|
111
106
|
const r = Math.floor(index / this.cols);
|
|
112
107
|
const c = index % this.cols;
|
|
113
108
|
return this.matrix[r] ? this.matrix[r][c] : null;
|
|
114
109
|
}
|
|
115
110
|
|
|
116
|
-
/**
|
|
117
|
-
* Core logic: Identifies where the current player can move.
|
|
118
|
-
*/
|
|
119
111
|
getAvailableMovesFor(playerIndex) {
|
|
120
112
|
const player = this.players[playerIndex];
|
|
121
113
|
const lastMove = player.moveIndexes[player.moveIndexes.length - 1];
|
|
122
114
|
|
|
123
115
|
if (lastMove === undefined) {
|
|
124
|
-
// First move: any non-disabled, non-visited cell
|
|
125
116
|
return this.matrix.flat()
|
|
126
117
|
.filter(cell => this.isAvailable(cell.index))
|
|
127
118
|
.map(cell => cell.index);
|
|
@@ -134,15 +125,12 @@ class KnightsTour {
|
|
|
134
125
|
|
|
135
126
|
const currentPlayer = this.players[this.turnIndex];
|
|
136
127
|
|
|
137
|
-
// Record move
|
|
138
128
|
currentPlayer.moveIndexes.push(index);
|
|
139
129
|
this.allMoveIndexes.push(this.turnIndex);
|
|
140
130
|
|
|
141
|
-
// Visually mark the cell with the move number
|
|
142
131
|
const cell = this.getCell(index);
|
|
143
132
|
cell.value = currentPlayer.moveIndexes.length;
|
|
144
133
|
|
|
145
|
-
// Switch turn and refresh board
|
|
146
134
|
this.turnIndex = (this.turnIndex + 1) % this.players.length;
|
|
147
135
|
this.updateBoard();
|
|
148
136
|
return true;
|
|
@@ -162,11 +150,10 @@ class KnightsTour {
|
|
|
162
150
|
|
|
163
151
|
this.generated = false;
|
|
164
152
|
this.solved = false;
|
|
165
|
-
|
|
153
|
+
|
|
166
154
|
const cell = this.getCell(lastMoveIndex);
|
|
167
155
|
if (cell) cell.value = null;
|
|
168
156
|
|
|
169
|
-
// Return turn to the player who was undone
|
|
170
157
|
this.turnIndex = lastPlayerIndex;
|
|
171
158
|
this.updateBoard(false);
|
|
172
159
|
|
|
@@ -182,7 +169,6 @@ class KnightsTour {
|
|
|
182
169
|
|
|
183
170
|
let nextMoves = this.getAvailableMovesFor(startIndex);
|
|
184
171
|
|
|
185
|
-
// 3. If current player is stuck, try skipping to next player (Co-op/Versus)
|
|
186
172
|
if (nextMoves.length === 0 && this.players.length > 1) {
|
|
187
173
|
const nextIdx = (startIndex + 1) % this.players.length;
|
|
188
174
|
const nextMoves = this.getAvailableMovesFor(nextIdx);
|
|
@@ -206,10 +192,8 @@ class KnightsTour {
|
|
|
206
192
|
}
|
|
207
193
|
this.clear();
|
|
208
194
|
|
|
209
|
-
// 2. Calculate next moves for the current player
|
|
210
195
|
let nextMoves = this.getNextMoves(this.turnIndex);
|
|
211
196
|
|
|
212
|
-
// 4. Update Board State
|
|
213
197
|
if (nextMoves.length === 0) {
|
|
214
198
|
this.state = "Game Over";
|
|
215
199
|
if (this.gameMode === "Open") {
|
|
@@ -353,7 +337,6 @@ class KnightsTour {
|
|
|
353
337
|
isClosed(lastMove = this.players[0].moveIndexes[this.players[0].moveIndexes.length - 1]) {
|
|
354
338
|
const firstMove = this.players[0].moveIndexes[0];
|
|
355
339
|
const knightMoves = this.getKnightMovesAt(firstMove, false);
|
|
356
|
-
//const lastMove = this.players[0].moveIndexes[this.players[0].moveIndexes.length - 1];
|
|
357
340
|
return knightMoves.includes(lastMove);
|
|
358
341
|
}
|
|
359
342
|
|
|
@@ -421,6 +404,10 @@ class KnightsTour {
|
|
|
421
404
|
{
|
|
422
405
|
nextCell = this.cells[Math.floor(Math.random() * this.cells.length)];
|
|
423
406
|
}
|
|
407
|
+
if(this.players[this.turnIndex].moveIndexes.length === 0)
|
|
408
|
+
{
|
|
409
|
+
nextCell = this.cells[Math.floor(Math.random() * this.cells.length)];
|
|
410
|
+
}
|
|
424
411
|
this.move(nextCell.index);
|
|
425
412
|
}
|
|
426
413
|
|
|
@@ -428,13 +415,11 @@ class KnightsTour {
|
|
|
428
415
|
{
|
|
429
416
|
if(this.gameMode === "Versus")
|
|
430
417
|
{
|
|
431
|
-
//console.log("Cannot solve in Versus mode");
|
|
432
418
|
return;
|
|
433
419
|
}
|
|
434
420
|
this.solving = true;
|
|
435
421
|
if(increment === 1000)
|
|
436
422
|
{
|
|
437
|
-
//console.log("No solution");
|
|
438
423
|
this.solving = false;
|
|
439
424
|
}
|
|
440
425
|
else
|
|
@@ -446,7 +431,6 @@ class KnightsTour {
|
|
|
446
431
|
}
|
|
447
432
|
else
|
|
448
433
|
{
|
|
449
|
-
//console.log("Solved in " + (increment + 1) + " attempt(s)");
|
|
450
434
|
this.solving = false;
|
|
451
435
|
}
|
|
452
436
|
}
|
|
@@ -457,9 +441,7 @@ class KnightsTour {
|
|
|
457
441
|
this.generating = true;
|
|
458
442
|
if(increment === 1000)
|
|
459
443
|
{
|
|
460
|
-
//console.log("No generation");
|
|
461
444
|
this.generating = false;
|
|
462
|
-
|
|
463
445
|
}
|
|
464
446
|
else
|
|
465
447
|
{
|
|
@@ -471,7 +453,6 @@ class KnightsTour {
|
|
|
471
453
|
}
|
|
472
454
|
else
|
|
473
455
|
{
|
|
474
|
-
//console.log("Generated in " + (increment + 1) + " attempt(s)");
|
|
475
456
|
this.generating = false;
|
|
476
457
|
}
|
|
477
458
|
}
|