@slugbugblue/trax 0.22.0 → 1.0.0
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/CHANGELOG.md +15 -16
- package/README.md +1 -1
- package/package.json +9 -8
- package/src/engine.js +129 -57
- package/src/types.d.ts +17 -2
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @slugbugblue/trax
|
|
2
2
|
|
|
3
|
+
## 1.0.0 - 2025-11-04
|
|
4
|
+
|
|
5
|
+
v1 release:
|
|
6
|
+
|
|
7
|
+
- Update dev dependencies to latest versions
|
|
8
|
+
- Fix xo complaints
|
|
9
|
+
- No other code changes
|
|
10
|
+
|
|
11
|
+
## 0.23.0 - 2023-03-28
|
|
12
|
+
|
|
13
|
+
Release candidate
|
|
14
|
+
|
|
15
|
+
Finalize work with jsdoc/typescript
|
|
16
|
+
|
|
3
17
|
## 0.22.0 - 2023-03-25
|
|
4
18
|
|
|
5
19
|
- Pull point.js into its own package: `@slugbugblue/point`
|
|
@@ -12,7 +26,6 @@
|
|
|
12
26
|
## 0.21.0 - 2023-03-12
|
|
13
27
|
|
|
14
28
|
- Update the analysis engine to be more lazy
|
|
15
|
-
|
|
16
29
|
- The LRU cache is more likely to have a hit because it no longer separates
|
|
17
30
|
validated from unvalidated analyses
|
|
18
31
|
- Suggestions are much faster because it now skips validations for low-scoring
|
|
@@ -28,7 +41,7 @@
|
|
|
28
41
|
- Drop support for node 14; node v16.15 is our minimum supported version
|
|
29
42
|
- More puzzles from Martin M. S. Pedersen -- bringing the total to 100+
|
|
30
43
|
- Enhance the docker configuration and document it in the README
|
|
31
|
-
- Refine faulty threat detection and add test for
|
|
44
|
+
- Refine faulty threat detection and add test for one case it was failing
|
|
32
45
|
|
|
33
46
|
## 0.19.0 - 2023-02-06
|
|
34
47
|
|
|
@@ -37,23 +50,19 @@
|
|
|
37
50
|
## 0.18.0 - 2023-02-05
|
|
38
51
|
|
|
39
52
|
- Analysis improvements:
|
|
40
|
-
|
|
41
53
|
- Add `count.b(level)` and `count.w(level)` methods to an analysis to quickly
|
|
42
54
|
count the number of threats at a given level
|
|
43
55
|
- Remove faulty threats from an analysis
|
|
44
56
|
- Add additional threats for more puzzle solving mojo
|
|
45
57
|
|
|
46
58
|
- Development environment enhancements:
|
|
47
|
-
|
|
48
59
|
- Added an initial benchmarking framework with puzzle benchmarks so far
|
|
49
60
|
- Added husky pre-commit hook to ensure npm test is run before a commit
|
|
50
61
|
|
|
51
62
|
- CLI improvements:
|
|
52
|
-
|
|
53
63
|
- Puzzle information is now stored as notes, to make it searchable
|
|
54
64
|
|
|
55
65
|
- More puzzles:
|
|
56
|
-
|
|
57
66
|
- Martin M. S. Pedersen contributed eleven new puzzles
|
|
58
67
|
|
|
59
68
|
## 0.17.0 - 2022-12-29
|
|
@@ -63,7 +72,6 @@
|
|
|
63
72
|
## 0.16.0 - 2022-12-27
|
|
64
73
|
|
|
65
74
|
- Added a `notes` command to the CLI to add comments to a game
|
|
66
|
-
|
|
67
75
|
- all comments are included in the output generated by the `export` command
|
|
68
76
|
- the latest comment can be used as a text filter by the `list` command
|
|
69
77
|
- the latest comment is displayed by the `view` and `list` commands
|
|
@@ -73,17 +81,14 @@
|
|
|
73
81
|
## 0.15.0 - 2022-12-14
|
|
74
82
|
|
|
75
83
|
- Added shared `puzzles` commands to the CLI:
|
|
76
|
-
|
|
77
84
|
- `trax ls puzzles`: see all the puzzles
|
|
78
85
|
- `trax new puzzle`: start a new puzzle
|
|
79
86
|
|
|
80
87
|
- Puzzle improvements:
|
|
81
|
-
|
|
82
88
|
- Adjust puzzle category levels for harder puzzles
|
|
83
89
|
- Add multiple hints when the puzzle can be solved multiple ways
|
|
84
90
|
|
|
85
91
|
- Analysis improvements:
|
|
86
|
-
|
|
87
92
|
- Analysis suggestions now "solve" all tough puzzles
|
|
88
93
|
- Reworked threats to be more precise
|
|
89
94
|
- Reworked scoring to add more nuance
|
|
@@ -91,7 +96,6 @@
|
|
|
91
96
|
## 0.14.0 - 2022-12-05
|
|
92
97
|
|
|
93
98
|
- Added `puzzles` to the CLI:
|
|
94
|
-
|
|
95
99
|
- `trax puzzles ls`: see all the puzzles
|
|
96
100
|
- `trax puzzle new`: start a new puzzle
|
|
97
101
|
|
|
@@ -102,13 +106,11 @@
|
|
|
102
106
|
## 0.13.0 - 2022-11-25
|
|
103
107
|
|
|
104
108
|
- Add move suggestions to `analyst.js` for CLI and bot use
|
|
105
|
-
|
|
106
109
|
- use `suggest(trax)` to get a suggestion for the current position
|
|
107
110
|
- added the `suggest` CLI command for quick access to a random suggestion
|
|
108
111
|
|
|
109
112
|
- Use font glyphs for text bubbles in `tty.js` only if one of the following
|
|
110
113
|
environment variables are defined and non-empty:
|
|
111
|
-
|
|
112
114
|
- `NERDFONT`, `POWERLINE`, `P9K_TTY`, `P9K_SSH`
|
|
113
115
|
|
|
114
116
|
- Begin work on documenting the javascript files using jsdoc, with the initial
|
|
@@ -125,7 +127,6 @@
|
|
|
125
127
|
`play()`. `playMove()` is no longer available.
|
|
126
128
|
- Add a new function `playMoves()` to submit multiple moves at once
|
|
127
129
|
- Initial work on `analyst.js`, with documentation and a start on tests
|
|
128
|
-
|
|
129
130
|
- use `analyze(trax)` to perform an analysis
|
|
130
131
|
- added the `analyze` CLI command to see an analysis
|
|
131
132
|
|
|
@@ -185,7 +186,6 @@ variables from snake_case to camelCase.
|
|
|
185
186
|
- The name and signature for provisional move handling, which was broken, has
|
|
186
187
|
been changed. It is now called `provisional_moves()` and takes three
|
|
187
188
|
arguments:
|
|
188
|
-
|
|
189
189
|
- `from`: the normalized position code of the board before the move
|
|
190
190
|
- `to`: the normalized position code of the board after the move
|
|
191
191
|
- `via`: the move notation to go from `from` to `to`
|
|
@@ -221,7 +221,6 @@ More convenience:
|
|
|
221
221
|
Initial release:
|
|
222
222
|
|
|
223
223
|
- engine.js
|
|
224
|
-
|
|
225
224
|
- create a new Trax game with `trax = new Trax()`
|
|
226
225
|
- play one move at a time with `trax.play('@0/')`
|
|
227
226
|
- ... etc
|
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Trax][gnutrax] for over two decades, and has a lot of expertise in this area.
|
|
|
71
71
|
|
|
72
72
|
## License
|
|
73
73
|
|
|
74
|
-
Copyright 2019-
|
|
74
|
+
Copyright 2019-2025 Chad Transtrum
|
|
75
75
|
|
|
76
76
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
77
77
|
the files in this project except in compliance with the License. You may obtain
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slugbugblue/trax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Trax game engine",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"trax",
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
},
|
|
20
20
|
"repository": "gitlab:slugbugblue/trax",
|
|
21
21
|
"scripts": {
|
|
22
|
-
"genversion": "genversion --
|
|
22
|
+
"genversion": "genversion --esm src/version.js",
|
|
23
23
|
"git-add": "git add src/version.js",
|
|
24
24
|
"test": "xo && c8 ava",
|
|
25
|
-
"prepare": "husky
|
|
25
|
+
"prepare": "husky",
|
|
26
26
|
"preversion": "npm test",
|
|
27
27
|
"version": "npm run genversion ; npm run git-add",
|
|
28
28
|
"postversion": "git push && git push --tags && npm publish"
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"@slugbugblue/point": "^1.0.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"ava": "^
|
|
36
|
-
"c8": "^
|
|
35
|
+
"ava": "^6.4.1",
|
|
36
|
+
"c8": "^10.1.3",
|
|
37
37
|
"genversion": "^3.0.2",
|
|
38
|
-
"husky": "^
|
|
39
|
-
"prettier": "^
|
|
40
|
-
"xo": "
|
|
38
|
+
"husky": "^9.1.7",
|
|
39
|
+
"prettier": "^3.6.2",
|
|
40
|
+
"xo": "^1.2.3"
|
|
41
41
|
},
|
|
42
42
|
"type": "module",
|
|
43
43
|
"engines": {
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"xo": {
|
|
62
62
|
"prettier": true,
|
|
63
|
+
"space": true,
|
|
63
64
|
"rules": {
|
|
64
65
|
"curly": [
|
|
65
66
|
"error",
|
package/src/engine.js
CHANGED
|
@@ -1,20 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* You may obtain a copy of the License at
|
|
6
|
-
*
|
|
7
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
*
|
|
9
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
* See the License for the specific language governing permissions and
|
|
13
|
-
* limitations under the License.
|
|
1
|
+
/** Trax engine.
|
|
2
|
+
* @copyright 2019-2022
|
|
3
|
+
* @author Chad Transtrum <chad@transtrum.net>
|
|
4
|
+
* @license Apache-2.0
|
|
14
5
|
*/
|
|
15
6
|
|
|
16
|
-
// Trax internals. Woot.
|
|
17
|
-
|
|
18
7
|
import { Point } from '@slugbugblue/point'
|
|
19
8
|
|
|
20
9
|
// Fun trax helper constants
|
|
@@ -36,7 +25,9 @@ const notationRegex = /^([@a-z]+)(\d+)([/\\+])$/i
|
|
|
36
25
|
* */
|
|
37
26
|
const tileTypes = ['a', 'b', 'c', 'd', 'e', 'f']
|
|
38
27
|
|
|
39
|
-
/** Slashes
|
|
28
|
+
/** Slashes indicate the direction the tile is placed. A forward slash or a
|
|
29
|
+
* backslash indicate the tile is a "curve" and the plus sign indicates the
|
|
30
|
+
* tile is a "straight".
|
|
40
31
|
* @readonly
|
|
41
32
|
* @type {Record<ValidTiles, Slash>}
|
|
42
33
|
*/
|
|
@@ -119,50 +110,42 @@ const addBlank = (code) => {
|
|
|
119
110
|
* @returns {number} - the actual length of the row
|
|
120
111
|
*/
|
|
121
112
|
const codeRowLength = (row) =>
|
|
122
|
-
row.
|
|
113
|
+
row.replaceAll(/\d/g, (n) => '.'.repeat(Number(n) + 1)).length
|
|
123
114
|
|
|
124
115
|
// This is where the magic happens
|
|
125
116
|
/** A digital representation of a Trax game. */
|
|
126
117
|
export class Trax {
|
|
127
118
|
// Static class properties
|
|
128
|
-
|
|
129
119
|
/** @readonly @type {Record<TraxVariant, string>} */
|
|
130
120
|
static names = {
|
|
131
121
|
trax: 'Trax',
|
|
132
122
|
traxloop: 'Loop Trax',
|
|
133
123
|
trax8: '8x8 Trax',
|
|
134
124
|
}
|
|
135
|
-
|
|
136
125
|
/** @readonly */
|
|
137
126
|
static variants = new Set(Object.keys(this.names)) // Known variants
|
|
138
|
-
|
|
139
127
|
// Static class methods
|
|
140
|
-
|
|
141
128
|
/** Create an x,y Point object with special functions.
|
|
142
129
|
* @arg {number|PointLike} x - either the x value, or an object with x,y keys
|
|
143
130
|
* @arg {number} [y] - if x is a number, y must be provided as well
|
|
144
131
|
* @returns {Point} a new Point object
|
|
145
132
|
*/
|
|
146
133
|
static point = (x, y) => new Point(x, y)
|
|
147
|
-
|
|
148
134
|
/** Given a player number, get the color.
|
|
149
135
|
* @arg {number} playerNumber - the player number, 1 or 2
|
|
150
136
|
* @returns {Color} the color of that player, w or b
|
|
151
137
|
*/
|
|
152
|
-
static colorOf = (playerNumber) => ({ 1: 'w', 2: 'b' }[playerNumber]
|
|
153
|
-
|
|
138
|
+
static colorOf = (playerNumber) => ({ 1: 'w', 2: 'b' })[playerNumber]
|
|
154
139
|
/** Given a color, get the player number.
|
|
155
140
|
* @arg {string} color - the color, w or b
|
|
156
141
|
* @returns {number} the player number, 1 or 2
|
|
157
142
|
*/
|
|
158
|
-
static playerNumber = (color) => ({ b: 2, w: 1 }[color]
|
|
159
|
-
|
|
143
|
+
static playerNumber = (color) => ({ b: 2, w: 1 })[color]
|
|
160
144
|
/** Given a color, get the other color.
|
|
161
145
|
* @arg {string} color - the color, w or b
|
|
162
146
|
* @returns {Color} the other color, b or w
|
|
163
147
|
*/
|
|
164
|
-
static other = (color) => ({ b: 'w', w: 'b' }[color]
|
|
165
|
-
|
|
148
|
+
static other = (color) => ({ b: 'w', w: 'b' })[color]
|
|
166
149
|
/** Encode a numeric column number into the Trax notation column letter.
|
|
167
150
|
* @arg {number} col - the colum number, with 0 just to the left of the tiles
|
|
168
151
|
* @returns {string} the encoded column letter
|
|
@@ -177,7 +160,6 @@ export class Trax {
|
|
|
177
160
|
|
|
178
161
|
return n || '@'
|
|
179
162
|
}
|
|
180
|
-
|
|
181
163
|
/** Decode a Trax notation column letter back to a number.
|
|
182
164
|
* @arg {string} col - the Trax column letter
|
|
183
165
|
* @returns {number} the column number
|
|
@@ -197,10 +179,10 @@ export class Trax {
|
|
|
197
179
|
// Trax class constructor
|
|
198
180
|
/** Create a new Trax game
|
|
199
181
|
* @arg {TraxVariant} [rules='trax'] - the variant to play
|
|
200
|
-
* @arg {string|string[]} [moves=
|
|
182
|
+
* @arg {string|string[]} [moves=''] - the initial moves to pre-play
|
|
201
183
|
* @arg {string} [id='trax'] - an id used to differentiate tiles from multiple games
|
|
202
184
|
*/
|
|
203
|
-
constructor(rules = 'trax', moves =
|
|
185
|
+
constructor(rules = 'trax', moves = '', id = 'trax') {
|
|
204
186
|
if (!Trax.variants.has(rules)) rules = 'trax'
|
|
205
187
|
this.id = String(id)
|
|
206
188
|
this.rules = rules
|
|
@@ -311,6 +293,7 @@ export class Trax {
|
|
|
311
293
|
*/
|
|
312
294
|
addTile(type, loc) {
|
|
313
295
|
const id = this.tileId(loc)
|
|
296
|
+
/** @type {Tile} */
|
|
314
297
|
const tile = { type, loc, id, move: this.move, seq: this.count }
|
|
315
298
|
if (type === 'x') {
|
|
316
299
|
this.invalid = true // Invalid tile is being played
|
|
@@ -334,7 +317,7 @@ export class Trax {
|
|
|
334
317
|
}
|
|
335
318
|
|
|
336
319
|
/** Is this tile valid?
|
|
337
|
-
* @type {(type: string, loc: Point) => type is
|
|
320
|
+
* @type {(type: string, loc: Point) => type is ValidTiles}
|
|
338
321
|
*/
|
|
339
322
|
validTile(type, loc) {
|
|
340
323
|
if (this.count === 0 && loc.x === 0 && loc.y === 0) {
|
|
@@ -377,6 +360,10 @@ export class Trax {
|
|
|
377
360
|
return this.right - this.left + 1
|
|
378
361
|
}
|
|
379
362
|
|
|
363
|
+
/** Determine if a location is valid to play in.
|
|
364
|
+
* @arg {Point} loc - the location to check
|
|
365
|
+
* @returns {boolean}
|
|
366
|
+
*/
|
|
380
367
|
validLocation(loc) {
|
|
381
368
|
if (this.over) return false
|
|
382
369
|
if (this.tileAt(loc)) return false
|
|
@@ -398,26 +385,31 @@ export class Trax {
|
|
|
398
385
|
return false
|
|
399
386
|
}
|
|
400
387
|
|
|
388
|
+
/** Find all possible locations to play in. Note that these are not
|
|
389
|
+
* necessarily valid locations, just empty ones that border existing tiles.
|
|
390
|
+
*/
|
|
401
391
|
possibleLocations() {
|
|
402
392
|
if (this.count === 0) return [zero]
|
|
403
393
|
/** @type Record<TileId, Point> */
|
|
404
394
|
const possibles = {}
|
|
405
395
|
|
|
406
396
|
for (const tile of Object.values(this.tiles)) {
|
|
407
|
-
tile.loc.around
|
|
397
|
+
for (const loc of tile.loc.around) {
|
|
408
398
|
const id = this.tileId(loc)
|
|
409
399
|
if (!(id in this.tiles)) possibles[id] = loc
|
|
410
|
-
|
|
411
|
-
})
|
|
400
|
+
}
|
|
412
401
|
}
|
|
413
402
|
|
|
414
|
-
// Get rid of all the real tiles and return all the maybes
|
|
415
403
|
return Object.values(possibles)
|
|
416
404
|
}
|
|
417
405
|
|
|
406
|
+
/** Find all possible moves as a list of notations,
|
|
407
|
+
* ie: ['@1+', '@1/', '@1\\', ...]
|
|
408
|
+
*
|
|
409
|
+
* Note that moves are not guaranteed to be valid.
|
|
410
|
+
*/
|
|
418
411
|
possibleMoves() {
|
|
419
|
-
|
|
420
|
-
// as notations, ie ['@1+', '@1/', '@1\\', ...]
|
|
412
|
+
/** @type {string[]} */
|
|
421
413
|
const possibles = []
|
|
422
414
|
if (this.over) return possibles // Shortcut if the game is over
|
|
423
415
|
for (const loc of this.possibleLocations()) {
|
|
@@ -430,9 +422,14 @@ export class Trax {
|
|
|
430
422
|
return possibles
|
|
431
423
|
}
|
|
432
424
|
|
|
425
|
+
/** Called after a move was just played, to determine the forced moves.
|
|
426
|
+
* @arg {Point} loc - the location just played at
|
|
427
|
+
* @returns {Tile[]} a list of tiles that should be added as part of the move
|
|
428
|
+
*/
|
|
433
429
|
forcedMoves(loc) {
|
|
434
|
-
|
|
430
|
+
/** @type {Tile[]} */
|
|
435
431
|
const forced = []
|
|
432
|
+
/** @type {Record<string, boolean>} */
|
|
436
433
|
const invalids = {}
|
|
437
434
|
let check = loc.around
|
|
438
435
|
while (check.length > 0) {
|
|
@@ -455,16 +452,26 @@ export class Trax {
|
|
|
455
452
|
return forced
|
|
456
453
|
}
|
|
457
454
|
|
|
455
|
+
/** Determine the notation for a move. Note that this must be determined
|
|
456
|
+
* BEFORE the move is placed on the board.
|
|
457
|
+
* @arg {ValidTiles} type - the tile placed on the board
|
|
458
|
+
* @arg {Point} loc - the location the tile is placed
|
|
459
|
+
* @returns {string} the notation of the move
|
|
460
|
+
*/
|
|
458
461
|
notate(type, loc) {
|
|
459
|
-
// Notation depends on the position of the board BEFORE the move
|
|
460
462
|
let notation = Trax.encodeCol(loc.x - this.left + 1)
|
|
461
463
|
notation += String(loc.y - this.top + 1)
|
|
462
464
|
notation += slash[type]
|
|
463
465
|
return notation
|
|
464
466
|
}
|
|
465
467
|
|
|
468
|
+
/** Turn a move notation into a tile type and location.
|
|
469
|
+
* @arg {string} notation - a notation for a single move to be played at
|
|
470
|
+
* the current board position
|
|
471
|
+
* @returns {RawMove} the tile type and location of the move
|
|
472
|
+
*/
|
|
466
473
|
decodeNotation(notation) {
|
|
467
|
-
|
|
474
|
+
/** @type {RawMove} */
|
|
468
475
|
const bad = { type: 'x', loc: zero }
|
|
469
476
|
const match = notation.match(notationRegex)
|
|
470
477
|
if (match === null) return bad
|
|
@@ -477,6 +484,14 @@ export class Trax {
|
|
|
477
484
|
return bad
|
|
478
485
|
}
|
|
479
486
|
|
|
487
|
+
/** Follow a color from one location through one or more tiles to the other
|
|
488
|
+
* end of the color line.
|
|
489
|
+
* @arg {Color} color - the color to follow
|
|
490
|
+
* @arg {Point} loc - the location to start
|
|
491
|
+
* @arg {string} from - the edge of the tile to start from
|
|
492
|
+
* @returns {LineEnd} the ending location and a list of the tile ids the path
|
|
493
|
+
* takes to get there.
|
|
494
|
+
*/
|
|
480
495
|
follow(color, loc, from) {
|
|
481
496
|
const path = []
|
|
482
497
|
let id = this.tileId(loc)
|
|
@@ -504,7 +519,13 @@ export class Trax {
|
|
|
504
519
|
return { loc, path }
|
|
505
520
|
}
|
|
506
521
|
|
|
522
|
+
/** Given a tile and a color, follow the line for that color to each end.
|
|
523
|
+
* @arg {Color} color - the color of ends of interest
|
|
524
|
+
* @arg { Point} loc - the location of the tile of interest
|
|
525
|
+
* @returns {LineEnd[]} a list of two items, each one end of the line
|
|
526
|
+
*/
|
|
507
527
|
findEnds(color, loc) {
|
|
528
|
+
/** @type {LineEnd[]} */
|
|
508
529
|
const ends = []
|
|
509
530
|
const tile = this.tileAt(loc)
|
|
510
531
|
if (upColor(tile) === color) ends.push(this.follow(color, loc, 'top'))
|
|
@@ -514,6 +535,11 @@ export class Trax {
|
|
|
514
535
|
return ends
|
|
515
536
|
}
|
|
516
537
|
|
|
538
|
+
/** Determine if a given line ends the game.
|
|
539
|
+
* @arg {Point} locA - one end of the line
|
|
540
|
+
* @arg {Point} locB - the other end of the line
|
|
541
|
+
* @returns {boolean} - true if this line wins the game
|
|
542
|
+
*/
|
|
517
543
|
lineWin(locA, locB) {
|
|
518
544
|
if (this.rules === 'traxloop') return false
|
|
519
545
|
if (this.width > 7 && locA.distX(locB) > this.width) return true
|
|
@@ -521,6 +547,9 @@ export class Trax {
|
|
|
521
547
|
return false
|
|
522
548
|
}
|
|
523
549
|
|
|
550
|
+
/** Determine if the game has ended.
|
|
551
|
+
* @arg {Tile[]} tiles - a list of the tiles placed during the last move.
|
|
552
|
+
*/
|
|
524
553
|
checkWin(tiles) {
|
|
525
554
|
/** @type {Color|false} */
|
|
526
555
|
let winner = false
|
|
@@ -529,13 +558,13 @@ export class Trax {
|
|
|
529
558
|
if (winner) continue // Skip the second player if the first has won
|
|
530
559
|
for (const tile of tiles) {
|
|
531
560
|
if (winner) continue // Skip the remaining tiles once we find a win
|
|
532
|
-
const
|
|
533
|
-
if (
|
|
561
|
+
const [end1, end2] = this.findEnds(color, tile.loc)
|
|
562
|
+
if (end1.loc.eq(end2.loc)) {
|
|
534
563
|
winner = color // Loop win
|
|
535
|
-
this.path =
|
|
536
|
-
} else if (this.lineWin(
|
|
564
|
+
this.path = end1.path // Both paths are essentially the same
|
|
565
|
+
} else if (this.lineWin(end1.loc, end2.loc)) {
|
|
537
566
|
winner = color // Line win, paths need to be combined
|
|
538
|
-
this.path = [...
|
|
567
|
+
this.path = [...end1.path.reverse(), ...end2.path.slice(1)]
|
|
539
568
|
}
|
|
540
569
|
}
|
|
541
570
|
}
|
|
@@ -576,6 +605,11 @@ export class Trax {
|
|
|
576
605
|
return this.dropTile(notation)
|
|
577
606
|
}
|
|
578
607
|
|
|
608
|
+
/** Play one or more moves.
|
|
609
|
+
* @arg {string|string[]} moves - the list of moves, provided either as a
|
|
610
|
+
* space-separated string of notations, or as a list of notations. Move
|
|
611
|
+
* numbers are optional, but if provided will be checked for accuracy.
|
|
612
|
+
*/
|
|
579
613
|
playMoves(moves) {
|
|
580
614
|
// Play one or more moves, from a string or a list of moves
|
|
581
615
|
// If move numbers are included, ensure they are accurate
|
|
@@ -583,12 +617,12 @@ export class Trax {
|
|
|
583
617
|
moves = moves.join(' ')
|
|
584
618
|
}
|
|
585
619
|
|
|
586
|
-
moves = moves.
|
|
620
|
+
moves = moves.replaceAll('\n', ' ').split(/\s+/)
|
|
587
621
|
|
|
588
622
|
let moveNumber = 0
|
|
589
623
|
for (const move of moves) {
|
|
590
624
|
if (moveNumberRegex.test(move)) {
|
|
591
|
-
moveNumber = Number(move.
|
|
625
|
+
moveNumber = Number(move.replaceAll(/\D/g, ''))
|
|
592
626
|
}
|
|
593
627
|
|
|
594
628
|
if (notationRegex.test(move)) {
|
|
@@ -603,6 +637,9 @@ export class Trax {
|
|
|
603
637
|
}
|
|
604
638
|
}
|
|
605
639
|
|
|
640
|
+
/** Add the current move notation to the notation string
|
|
641
|
+
* @arg {string} notation - the notation of the current move
|
|
642
|
+
*/
|
|
606
643
|
updateNotation(notation) {
|
|
607
644
|
this.move++
|
|
608
645
|
let note = this.move + '. ' + notation
|
|
@@ -617,13 +654,13 @@ export class Trax {
|
|
|
617
654
|
/** An array of the moves made in the game. */
|
|
618
655
|
get moves() {
|
|
619
656
|
return this.notation
|
|
620
|
-
.
|
|
657
|
+
.replaceAll('\n', ' ')
|
|
621
658
|
.split(' ')
|
|
622
659
|
.filter((n) => Boolean(n) && !n.endsWith('.'))
|
|
623
660
|
}
|
|
624
661
|
|
|
625
662
|
/** Drop a tile onto the board. This is a lower level call. Use play() if possible instead.
|
|
626
|
-
* @arg {string|
|
|
663
|
+
* @arg {string|ValidTiles} type - a special move, a tile type, or a notation
|
|
627
664
|
* @arg {Point} [loc] - a location if type is a tile type
|
|
628
665
|
* @arg {string|boolean} [tentative] - if truthy, the move will not be saved
|
|
629
666
|
* @returns {TileDrop} an object representing the results of the drop
|
|
@@ -673,6 +710,11 @@ export class Trax {
|
|
|
673
710
|
return { dropped, notation, valid }
|
|
674
711
|
}
|
|
675
712
|
|
|
713
|
+
/** Symmetry helper. Rotates a move around the board in case we are trying to
|
|
714
|
+
* play a symmetrical rather than an exact move.
|
|
715
|
+
* @arg {string} move - the notation of the move to be rotated
|
|
716
|
+
* @returns {string[]} the four rotations of this move
|
|
717
|
+
*/
|
|
676
718
|
moveRotations(move) {
|
|
677
719
|
// Symmetry helper, rotate a move around the board
|
|
678
720
|
const match = move.match(notationRegex)
|
|
@@ -695,15 +737,24 @@ export class Trax {
|
|
|
695
737
|
return [...moves]
|
|
696
738
|
}
|
|
697
739
|
|
|
740
|
+
/** Play a provisional move if it is valid.
|
|
741
|
+
* @arg {string} from - the normalized encoding of the starting position
|
|
742
|
+
* @arg {string} to - the normalized encoding of the ending position
|
|
743
|
+
* @arg {string} via - the move to be used to transition
|
|
744
|
+
* @returns {false|string} if the provisional move is invalid: false; if the
|
|
745
|
+
* provisional move will never be valid for any future moves:
|
|
746
|
+
* 'delete-provisional'; if the provisional move is valid, the correct
|
|
747
|
+
* notation, which may be symmetrically adjusted as needed
|
|
748
|
+
*/
|
|
698
749
|
provisionalMove(from, to, via) {
|
|
699
750
|
// Test a provisional move against the current state
|
|
700
|
-
|
|
751
|
+
const lines = from.slice(1).split(':')
|
|
701
752
|
let cols = 0
|
|
702
|
-
for (const
|
|
703
|
-
cols = Math.max(cols, codeRowLength(
|
|
753
|
+
for (const line of lines) {
|
|
754
|
+
cols = Math.max(cols, codeRowLength(line))
|
|
704
755
|
}
|
|
705
756
|
|
|
706
|
-
rows =
|
|
757
|
+
const rows = lines.length
|
|
707
758
|
if (
|
|
708
759
|
(this.width > rows && this.height > cols) ||
|
|
709
760
|
(this.width > cols && this.height > rows)
|
|
@@ -726,15 +777,31 @@ export class Trax {
|
|
|
726
777
|
return false
|
|
727
778
|
}
|
|
728
779
|
|
|
780
|
+
/** Get an encoded representation of the current position, useful for drawing
|
|
781
|
+
* the board without having to do much analysis.
|
|
782
|
+
* @returns {string} the current position code
|
|
783
|
+
*/
|
|
729
784
|
get icon() {
|
|
730
785
|
return this.positionCode()
|
|
731
786
|
}
|
|
732
787
|
|
|
788
|
+
/** Get an encoded representation of the current position, with a set of
|
|
789
|
+
* tiles highlighted differently, useful for showing the effects of a move.
|
|
790
|
+
* @arg {TileDrop} drops - the drops of the most recent play
|
|
791
|
+
* @returns {string} the current position code, with drops highlighted
|
|
792
|
+
*/
|
|
733
793
|
dropsIcon(drops) {
|
|
734
794
|
return this.positionCode(undefined, undefined, undefined, drops)
|
|
735
795
|
}
|
|
736
796
|
|
|
737
|
-
|
|
797
|
+
/** Symmetry helper, draw the board from different angles.
|
|
798
|
+
* @arg {boolean} [rightToLeft] - reverse order horizontally
|
|
799
|
+
* @arg {boolean} [bottomToTop] - reverse order vertically
|
|
800
|
+
* @arg {boolean} [rotate] - rotate the tiles by 90 degrees
|
|
801
|
+
* @arg {TileDrop} [drops] - the drops of the most recent play, if you want
|
|
802
|
+
* them highlighted
|
|
803
|
+
* @returns {string} an encoding of the position
|
|
804
|
+
*/
|
|
738
805
|
positionCode(rightToLeft, bottomToTop, rotate, drops) {
|
|
739
806
|
const startX = rightToLeft ? this.right : this.left
|
|
740
807
|
const startY = bottomToTop ? this.bottom : this.top
|
|
@@ -799,9 +866,11 @@ export class Trax {
|
|
|
799
866
|
return code.join(':')
|
|
800
867
|
}
|
|
801
868
|
|
|
869
|
+
/** Trax has the potential for symmetry, so this gives us the ability to
|
|
870
|
+
* examine horizontal, vertical, and rotational symmetry for a color.
|
|
871
|
+
* @returns {string} a position code that matches all symmetrical positions
|
|
872
|
+
*/
|
|
802
873
|
normalize() {
|
|
803
|
-
// Trax has the potential for symmetry, so this gives us the ability to
|
|
804
|
-
// examine horizontal, vertical, and rotational symmetry for a color
|
|
805
874
|
let norm = 'z'
|
|
806
875
|
const bools = [true, false]
|
|
807
876
|
for (const rightToLeft of bools) {
|
|
@@ -816,6 +885,9 @@ export class Trax {
|
|
|
816
885
|
return (this.color || 't').toUpperCase() + norm
|
|
817
886
|
}
|
|
818
887
|
|
|
888
|
+
/** Get the normalized code for this position. All symmetrical positions will
|
|
889
|
+
* result in the same normalized code.
|
|
890
|
+
*/
|
|
819
891
|
get normalized() {
|
|
820
892
|
return this.normalize()
|
|
821
893
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -7,6 +7,21 @@
|
|
|
7
7
|
/** Color is a single character to represent white or black. */
|
|
8
8
|
type Color = 'w' | 'b'
|
|
9
9
|
|
|
10
|
+
/** One end of a line and the tiles taken to get there. */
|
|
11
|
+
type LineEnd = {
|
|
12
|
+
loc: Point
|
|
13
|
+
path: TileId[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A notation of a move. */
|
|
17
|
+
type Notation = string
|
|
18
|
+
|
|
19
|
+
/** A tile type and a location determine a raw move. */
|
|
20
|
+
type RawMove = {
|
|
21
|
+
type: TileType
|
|
22
|
+
loc: Point
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
/** Treat the save state as an opaque object,
|
|
11
26
|
* produced by save() and fed into restore().
|
|
12
27
|
*/
|
|
@@ -19,7 +34,7 @@ type SaveState = {
|
|
|
19
34
|
right: number
|
|
20
35
|
top: number
|
|
21
36
|
bottom: number
|
|
22
|
-
notation:
|
|
37
|
+
notation: Notation
|
|
23
38
|
tiles: string
|
|
24
39
|
path: string
|
|
25
40
|
invalid: boolean
|
|
@@ -42,7 +57,7 @@ type Tile = {
|
|
|
42
57
|
/** When a tile is dropped, this object represents the results. */
|
|
43
58
|
type TileDrop = {
|
|
44
59
|
dropped: Tile[]
|
|
45
|
-
notation:
|
|
60
|
+
notation: Notation
|
|
46
61
|
valid: boolean
|
|
47
62
|
}
|
|
48
63
|
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '0.
|
|
2
|
+
export const version = '1.0.0'
|