@slugbugblue/trax 0.12.0 → 0.14.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 +25 -0
- package/README.md +45 -7
- package/docs/analyst.md +44 -2
- package/package.json +6 -7
- package/src/analyst.js +97 -12
- package/src/cli.js +114 -31
- package/src/cmds/analyze.js +2 -2
- package/src/cmds/import-export.js +33 -3
- package/src/cmds/list.js +2 -5
- package/src/cmds/new.js +6 -4
- package/src/cmds/play-try.js +54 -7
- package/src/cmds/puzzles.js +152 -0
- package/src/cmds/suggest.js +52 -0
- package/src/cmds/view.js +1 -1
- package/src/engine.js +167 -29
- package/src/point.js +54 -12
- package/src/puzzles.js +808 -0
- package/src/threats.js +39 -35
- package/src/tty.js +30 -15
- package/src/types.d.ts +105 -0
- package/src/version.js +1 -1
package/src/threats.js
CHANGED
|
@@ -1,23 +1,12 @@
|
|
|
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
|
+
/** Threats database: hand-coded threats
|
|
2
|
+
* @copyright 2022
|
|
3
|
+
* @author Chad Transtrum <chad@transtrum.net>
|
|
4
|
+
* @license Apache-2.0
|
|
14
5
|
*/
|
|
15
6
|
|
|
16
|
-
// Threats database: hand-coded threats
|
|
17
|
-
|
|
18
7
|
const REGEX = {
|
|
19
|
-
a: '[am]', // White that pairs with the next white
|
|
20
|
-
c: '[cp]', // White that forms a connectable pair with next white
|
|
8
|
+
a: '[am]', // White that pairs with the next white (m=hollow)
|
|
9
|
+
c: '[cp]', // White that forms a connectable pair with next white (p=hollow)
|
|
21
10
|
l: '[lmp]', // White in a hollow
|
|
22
11
|
w: '[aclmpw]', // Any white
|
|
23
12
|
'!': '(?:b[acw])*?', // Ripple black/white
|
|
@@ -26,14 +15,16 @@ const REGEX = {
|
|
|
26
15
|
'?': 'b?', // Zero or one black piece
|
|
27
16
|
}
|
|
28
17
|
|
|
29
|
-
class
|
|
18
|
+
class Threat {
|
|
30
19
|
#depth = 0
|
|
31
20
|
#pattern = ''
|
|
32
21
|
#rx = null
|
|
22
|
+
#reforms = false
|
|
33
23
|
|
|
34
|
-
constructor(depth, pattern) {
|
|
24
|
+
constructor(depth, pattern, reforms = false) {
|
|
35
25
|
this.#depth = depth
|
|
36
26
|
this.#pattern = pattern
|
|
27
|
+
this.#reforms = reforms
|
|
37
28
|
|
|
38
29
|
let rx = ''
|
|
39
30
|
for (const char of pattern) {
|
|
@@ -55,20 +46,26 @@ class CustomInspectableThreat {
|
|
|
55
46
|
return this.#rx
|
|
56
47
|
}
|
|
57
48
|
|
|
49
|
+
get value() {
|
|
50
|
+
return this.#reforms ? 2 : 1
|
|
51
|
+
}
|
|
52
|
+
|
|
58
53
|
// To make life at the REPL a little easier
|
|
59
54
|
[Symbol.for('nodejs.util.inspect.custom')](_, out) {
|
|
60
55
|
const style = out.stylize
|
|
61
|
-
|
|
56
|
+
const display = [
|
|
62
57
|
'[' + style('Threat', 'special') + ']',
|
|
63
58
|
'{',
|
|
64
59
|
style(this.#pattern, 'string') + ':',
|
|
65
60
|
style(this.#depth, 'number'),
|
|
61
|
+
this.#reforms ? '!' : '',
|
|
66
62
|
'}',
|
|
67
|
-
]
|
|
63
|
+
]
|
|
64
|
+
return display.filter((d) => d !== '').join(' ')
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
class
|
|
68
|
+
class ThreatsDB {
|
|
72
69
|
#db = {}
|
|
73
70
|
#depth = 0
|
|
74
71
|
|
|
@@ -78,9 +75,10 @@ class CustomInspectableThreatsDB {
|
|
|
78
75
|
}
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
add(depth, pattern) {
|
|
78
|
+
add(depth, pattern, reforms = false) {
|
|
82
79
|
this.#depth = Math.max(depth, this.#depth)
|
|
83
|
-
this.#db
|
|
80
|
+
if (pattern in this.#db) throw new Error('Duplicate pattern:' + pattern)
|
|
81
|
+
this.#db[pattern] = new Threat(depth, pattern, reforms)
|
|
84
82
|
return this.#db[pattern]
|
|
85
83
|
}
|
|
86
84
|
|
|
@@ -113,8 +111,8 @@ class CustomInspectableThreatsDB {
|
|
|
113
111
|
}
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
const pre = true
|
|
117
|
-
const post = true
|
|
114
|
+
const pre = true // This pattern can appear first in an L threat
|
|
115
|
+
const post = true // This pattern can come second in an L threat
|
|
118
116
|
const corners = {
|
|
119
117
|
arw: { pre, post }, // Simple corner
|
|
120
118
|
abrw: { post }, // Wide-first corner
|
|
@@ -125,7 +123,7 @@ const corners = {
|
|
|
125
123
|
cbw: {}, // Wide connectable pair
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
export const threats = new
|
|
126
|
+
export const threats = new ThreatsDB(Object.keys(corners))
|
|
129
127
|
|
|
130
128
|
// Attacks
|
|
131
129
|
const attacks = [
|
|
@@ -134,15 +132,19 @@ const attacks = [
|
|
|
134
132
|
attacks.map((a) => threats.add(1, a))
|
|
135
133
|
|
|
136
134
|
// Re-forming attacks
|
|
137
|
-
const
|
|
135
|
+
const reformAttacks = [
|
|
138
136
|
'rm?lr', // Cave/wide cave attack
|
|
139
|
-
'rp?lr',
|
|
140
137
|
'rbm?lbr', // Deep cave/deep wide cave attack (impossibly rare?)
|
|
141
|
-
'rbp?lbr',
|
|
142
138
|
'ralmrw', // Attack at top of cave
|
|
143
139
|
'arlmwr', // Reversed
|
|
144
140
|
]
|
|
145
|
-
|
|
141
|
+
reformAttacks.map((ra) => threats.add(1, ra, true))
|
|
142
|
+
|
|
143
|
+
const reformThreats = [
|
|
144
|
+
'rp?lr', // Cave/wide cave threat
|
|
145
|
+
'rbp?lbr', // Deep cave/deept wide cave attach (impossibly rare?)
|
|
146
|
+
]
|
|
147
|
+
reformThreats.map((rt) => threats.add(2, rt))
|
|
146
148
|
|
|
147
149
|
// Create multi-stage threats
|
|
148
150
|
for (const one of Object.keys(corners)) {
|
|
@@ -151,14 +153,16 @@ for (const one of Object.keys(corners)) {
|
|
|
151
153
|
const [before, after] = [corners[one].post, corners[two].pre]
|
|
152
154
|
if (before || after) {
|
|
153
155
|
const start = one.replace('w', 'l')
|
|
154
|
-
|
|
156
|
+
const pre = before ? '?' : ''
|
|
157
|
+
const post = after ? '?' : ''
|
|
158
|
+
threats.add(2, start + pre + '¡' + two) // Make an L
|
|
159
|
+
threats.add(3, start + pre + 'rb¡' + two) // Make a three-stage threat
|
|
155
160
|
const end = two.replace('a', 'm').replace('c', 'p')
|
|
156
|
-
threats.add(2, one +
|
|
161
|
+
threats.add(2, one + post + '!' + end) // L
|
|
162
|
+
threats.add(3, one + post + '!br' + end) // Three-stage
|
|
157
163
|
}
|
|
158
164
|
|
|
159
165
|
// Edge threats
|
|
160
166
|
threats.add(3, one + '!b*b¡' + two)
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
|
-
|
|
164
|
-
// Create edge threats
|
package/src/tty.js
CHANGED
|
@@ -17,12 +17,21 @@
|
|
|
17
17
|
|
|
18
18
|
import process from 'node:process'
|
|
19
19
|
|
|
20
|
-
import { Trax } from '
|
|
20
|
+
import { Trax } from '@slugbugblue/trax'
|
|
21
21
|
|
|
22
22
|
const esc = '\u001B[' // Start of console color escape code
|
|
23
23
|
const off = esc + 'm' // Color reset code
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const ENV = process.env
|
|
25
|
+
const NERD = ENV.NERDFONT
|
|
26
|
+
const POWER = ENV.POWERLINE || ENV.P9K_TTY || ENV.P9K_SSH
|
|
27
|
+
const leftBubble = NERD ? '' : POWER ? '' : '▐'
|
|
28
|
+
const rightBubble = NERD ? '' : POWER ? '' : '▌'
|
|
29
|
+
|
|
30
|
+
/** Colorize a string.
|
|
31
|
+
* @arg {string} contents
|
|
32
|
+
* @arg {string|number} [fgcolor]
|
|
33
|
+
* @arg {string|number} [bgcolor]
|
|
34
|
+
*/
|
|
26
35
|
export const color = (contents = '', fgcolor = '', bgcolor = '') => {
|
|
27
36
|
const c = []
|
|
28
37
|
if (fgcolor) c.push('38;5;' + fgcolor)
|
|
@@ -35,9 +44,7 @@ export const outty = (message, fgcolor, bgcolor) => {
|
|
|
35
44
|
console.log(color(message, fgcolor, bgcolor) + off)
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// a little magic to display the game in progress
|
|
47
|
+
// A little magic to display the game in progress
|
|
41
48
|
const colorSets = {
|
|
42
49
|
// Colors for terminal sequence: <Esc>[38;5;<color_num>m
|
|
43
50
|
// b, B, w, W: color of line for each player, capital for a winning line
|
|
@@ -48,6 +55,8 @@ const colorSets = {
|
|
|
48
55
|
tentative: { b: 235, B: 18, e: 19, t: 244, w: 252, W: 217 },
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
let set = colorSets.trax // Which color set should we draw with?
|
|
59
|
+
|
|
51
60
|
export const name = (rules) => {
|
|
52
61
|
if (Trax.variants.has(rules)) {
|
|
53
62
|
return color(Trax.names[rules], colorSets[rules].t)
|
|
@@ -67,7 +76,12 @@ export const bubble = (clr, text) => {
|
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
const c = colors[clr]
|
|
70
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
color(leftBubble, c.bg) +
|
|
81
|
+
color(text, c.fg, c.bg) +
|
|
82
|
+
off +
|
|
83
|
+
color(rightBubble, c.bg)
|
|
84
|
+
)
|
|
71
85
|
}
|
|
72
86
|
|
|
73
87
|
const ln = 'm\u2584' // Lower half block -- with the leading m to terminate ansi color escape
|
|
@@ -124,7 +138,7 @@ const tileTty = (tile, top) => {
|
|
|
124
138
|
}
|
|
125
139
|
}
|
|
126
140
|
|
|
127
|
-
let value =
|
|
141
|
+
let value = ''
|
|
128
142
|
if (tile in tiles) {
|
|
129
143
|
tile = tiles[tile]
|
|
130
144
|
if (top) {
|
|
@@ -154,7 +168,7 @@ const mergeTentative = () => {
|
|
|
154
168
|
|
|
155
169
|
const printIcon = (icon, setName) => {
|
|
156
170
|
set = colorSets[setName] || colorSets.trax
|
|
157
|
-
mergeTentative(
|
|
171
|
+
mergeTentative()
|
|
158
172
|
const on = esc + '38;5;' + String(set.t) + ';48;5;' + String(set.e) + 'm'
|
|
159
173
|
let o1 = ' '
|
|
160
174
|
let o2 = '1 '
|
|
@@ -208,10 +222,11 @@ const printIcon = (icon, setName) => {
|
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
224
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
225
|
+
/** Flexible way to get a Trax game object.
|
|
226
|
+
* @arg {Trax|TraxVariant} [game] - existing Trax instance or a string indicating the variant name
|
|
227
|
+
* @arg {string|string[]} [moves] - optional list or string of moves to be made on the game
|
|
228
|
+
* @returns {Trax} a Trax instance
|
|
229
|
+
*/
|
|
215
230
|
export const traxGame = (game = 'trax', moves = '') => {
|
|
216
231
|
let trax
|
|
217
232
|
if (game instanceof Trax) {
|
|
@@ -253,7 +268,7 @@ const printNotation = (trax, tentative, show = Number.POSITIVE_INFINITY) => {
|
|
|
253
268
|
export const display = (
|
|
254
269
|
game,
|
|
255
270
|
players,
|
|
256
|
-
tentative =
|
|
271
|
+
tentative = '',
|
|
257
272
|
showMove = Number.POSITIVE_INFINITY,
|
|
258
273
|
) => {
|
|
259
274
|
const trax = traxGame(game)
|
|
@@ -266,7 +281,7 @@ export const display = (
|
|
|
266
281
|
if (drops.valid || drops.dropped.length > 0) {
|
|
267
282
|
icon = trax.dropsIcon(drops)
|
|
268
283
|
} else {
|
|
269
|
-
tentative =
|
|
284
|
+
tentative = ''
|
|
270
285
|
}
|
|
271
286
|
} else if (showMove <= trax.moves.length) {
|
|
272
287
|
icon = trax.dropsIcon({
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/** Type information.
|
|
2
|
+
* @author Chad Transtrum <chad@transtrum.net>
|
|
3
|
+
* @license Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Color is a single character to represent white or black. */
|
|
7
|
+
type Color = 'w' | 'b'
|
|
8
|
+
|
|
9
|
+
/** Colorize is a fancy function for colorizing text. */
|
|
10
|
+
type Colorize = (text: string, def?: string | number) => string
|
|
11
|
+
|
|
12
|
+
/** Colorer is a little too fancy. Hence the gnarly typescript. */
|
|
13
|
+
type Colorer = {
|
|
14
|
+
(text: string, def?: string | number): string
|
|
15
|
+
black: Colorize
|
|
16
|
+
command: Colorize
|
|
17
|
+
default: Colorize
|
|
18
|
+
error: Colorize
|
|
19
|
+
fatal: Colorize
|
|
20
|
+
help: Colorize
|
|
21
|
+
id: Colorize
|
|
22
|
+
variable: Colorize
|
|
23
|
+
optional: Colorize
|
|
24
|
+
short: Colorize
|
|
25
|
+
white: Colorize
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A point-like object has numerical x and y properties. */
|
|
29
|
+
type PointLike = {
|
|
30
|
+
x: number
|
|
31
|
+
y: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A puzzle. */
|
|
35
|
+
type Puzzle = {
|
|
36
|
+
id: string
|
|
37
|
+
src: string
|
|
38
|
+
game: TraxVariant
|
|
39
|
+
notation: string
|
|
40
|
+
icon: string
|
|
41
|
+
level: number
|
|
42
|
+
max: number
|
|
43
|
+
player: number
|
|
44
|
+
title?: string
|
|
45
|
+
desc?: string
|
|
46
|
+
hint?: string
|
|
47
|
+
hints?: string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
type PuzzleSource = {
|
|
51
|
+
name: string
|
|
52
|
+
copyright?: string
|
|
53
|
+
url?: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Treat the save state as an opaque object,
|
|
57
|
+
* produced by save() and fed into restore().
|
|
58
|
+
*/
|
|
59
|
+
type SaveState = {
|
|
60
|
+
id: string
|
|
61
|
+
move: number
|
|
62
|
+
turn: number
|
|
63
|
+
over: boolean
|
|
64
|
+
left: number
|
|
65
|
+
right: number
|
|
66
|
+
top: number
|
|
67
|
+
bottom: number
|
|
68
|
+
notation: string
|
|
69
|
+
tiles: string
|
|
70
|
+
path: string
|
|
71
|
+
invalid: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Representations of the different ways a tile can curve.
|
|
75
|
+
* Matches the symbols used in Trax notation.
|
|
76
|
+
*/
|
|
77
|
+
type Slash = '/' | '\\' | '+'
|
|
78
|
+
|
|
79
|
+
/** A single tile on the board. */
|
|
80
|
+
type Tile = {
|
|
81
|
+
id: TileId
|
|
82
|
+
loc: Point
|
|
83
|
+
type: TileType
|
|
84
|
+
move: number
|
|
85
|
+
seq: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** When a tile is dropped, this object represents the results. */
|
|
89
|
+
type TileDrop = {
|
|
90
|
+
dropped: Tile[]
|
|
91
|
+
notation: string
|
|
92
|
+
valid: boolean
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A TileId is just a string. */
|
|
96
|
+
type TileId = string
|
|
97
|
+
|
|
98
|
+
/** Invalid tiles are represented by 'x'. */
|
|
99
|
+
type TileType = ValidTiles | 'x'
|
|
100
|
+
|
|
101
|
+
/** All of the variants supported by the engine. */
|
|
102
|
+
type TraxVariant = 'trax' | 'traxloop' | 'trax8'
|
|
103
|
+
|
|
104
|
+
/** Tile types are represented by one of the following single characters. */
|
|
105
|
+
type ValidTiles = 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '0.
|
|
2
|
+
export const version = '0.14.0'
|