@slugbugblue/trax 0.13.0 → 0.15.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 +29 -0
- package/README.md +10 -6
- package/package.json +1 -1
- package/src/analyst.js +195 -77
- package/src/cli.js +55 -2
- package/src/cmds/analyze.js +2 -2
- package/src/cmds/import-export.js +32 -2
- package/src/cmds/list.js +4 -4
- package/src/cmds/new.js +4 -1
- package/src/cmds/play-try.js +54 -7
- package/src/cmds/puzzles.js +158 -0
- package/src/cmds/suggest.js +1 -1
- package/src/cmds/view.js +1 -1
- package/src/puzzles.js +53 -39
- package/src/threats.js +78 -40
- package/src/tty.js +1 -1
- package/src/types.d.ts +56 -0
- package/src/version.js +1 -1
package/src/threats.js
CHANGED
|
@@ -5,20 +5,26 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const REGEX = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
// Some of these are one for one:
|
|
9
|
+
// a: White that pairs with the next white
|
|
10
|
+
A: '[am]', // White maybe in a hollow that pairs with next white
|
|
11
|
+
// c: White that forms a connectable pair with next white
|
|
12
|
+
C: '[cp]', // White maybe in a hollow connectable pair with next white
|
|
13
|
+
l: '[lmp]', // Any White in a hollow
|
|
14
|
+
// m: "a" in a hollow
|
|
15
|
+
// p: "c" in a hollow)
|
|
16
|
+
w: '[acw]', // Any white except in hollows
|
|
17
|
+
W: '[aclmpw]', // Any white at all
|
|
12
18
|
'!': '(?:b[acw])*?', // Ripple black/white
|
|
13
19
|
'¡': '(?:[acw]b)*?', // Ripple white/black
|
|
14
20
|
'*': 'b*?', // Zero or more black pieces
|
|
15
21
|
'?': 'b?', // Zero or one black piece
|
|
16
22
|
}
|
|
17
23
|
|
|
18
|
-
class
|
|
24
|
+
export const Threat = class {
|
|
19
25
|
#depth = 0
|
|
20
26
|
#pattern = ''
|
|
21
|
-
#rx
|
|
27
|
+
#rx
|
|
22
28
|
#reforms = false
|
|
23
29
|
|
|
24
30
|
constructor(depth, pattern, reforms = false) {
|
|
@@ -31,7 +37,8 @@ class CustomInspectableThreat {
|
|
|
31
37
|
rx += REGEX[char] || char
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
// Create the regex with lookahead assertions to allow overlapping matches
|
|
41
|
+
this.#rx = new RegExp('(?=(' + rx + '))', 'g')
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
get depth() {
|
|
@@ -65,7 +72,7 @@ class CustomInspectableThreat {
|
|
|
65
72
|
}
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
class
|
|
75
|
+
export const ThreatsDB = class {
|
|
69
76
|
#db = {}
|
|
70
77
|
#depth = 0
|
|
71
78
|
|
|
@@ -75,9 +82,15 @@ class CustomInspectableThreatsDB {
|
|
|
75
82
|
}
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
compose(depth, pattern) {
|
|
86
|
+
pattern = pattern.replaceAll(/(?<=.)A/g, 'a').replaceAll(/W(?=.)/g, 'w')
|
|
87
|
+
return this.add(depth, pattern)
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
add(depth, pattern, reforms = false) {
|
|
79
91
|
this.#depth = Math.max(depth, this.#depth)
|
|
80
|
-
this.#db
|
|
92
|
+
if (pattern in this.#db) throw new Error('Duplicate pattern:' + pattern)
|
|
93
|
+
this.#db[pattern] = new Threat(depth, pattern, reforms)
|
|
81
94
|
return this.#db[pattern]
|
|
82
95
|
}
|
|
83
96
|
|
|
@@ -96,68 +109,93 @@ class CustomInspectableThreatsDB {
|
|
|
96
109
|
// To make life at the REPL a little easier
|
|
97
110
|
[Symbol.for('nodejs.util.inspect.custom')](_, out) {
|
|
98
111
|
const style = out.stylize
|
|
99
|
-
const inspection = '[' + style('Threats', 'special') + '
|
|
112
|
+
const inspection = '[' + style('Threats', 'special') + ':'
|
|
100
113
|
let total = 0
|
|
101
|
-
const keys = []
|
|
114
|
+
const keys = ['{']
|
|
102
115
|
|
|
103
116
|
for (let x = 0; x <= this.#depth; x++) {
|
|
104
117
|
const count = this.level(x).length
|
|
105
|
-
keys.push(style(x, 'string') + ':
|
|
118
|
+
keys.push(style(x, 'string') + ':' + style(count, 'number'))
|
|
106
119
|
total += count
|
|
107
120
|
}
|
|
108
121
|
|
|
109
|
-
return inspection +
|
|
122
|
+
return inspection + style(total, 'number') + '] ' + keys.join(' ') + ' }'
|
|
110
123
|
}
|
|
111
124
|
}
|
|
112
125
|
|
|
113
|
-
const pre = true
|
|
114
|
-
const post = true
|
|
126
|
+
const pre = true // This pattern can appear first in an L threat
|
|
127
|
+
const post = true // This pattern can come second in an L threat
|
|
115
128
|
const corners = {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
'ab?
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
ArW: { pre, post }, // Simple corner
|
|
130
|
+
AbrW: { post }, // Wide-first corner
|
|
131
|
+
ArbW: { pre }, // Wide-second corner
|
|
132
|
+
AbrbW: {}, // Wide corner
|
|
133
|
+
'ab?bW': { pre, post }, // Flat corner and wide flat corner
|
|
134
|
+
CW: { pre, post }, // Connectable pair
|
|
135
|
+
CbW: {}, // Wide connectable pair
|
|
123
136
|
}
|
|
124
137
|
|
|
125
|
-
export const threats = new
|
|
138
|
+
export const threats = new ThreatsDB(Object.keys(corners))
|
|
139
|
+
|
|
140
|
+
// Attacks
|
|
141
|
+
const attacks = [
|
|
142
|
+
'a?W', // Simple attack and wide attack
|
|
143
|
+
'm?W', // Simple and wide attack in a corner
|
|
144
|
+
]
|
|
145
|
+
attacks.map((a) => threats.add(1, a))
|
|
126
146
|
|
|
127
147
|
// Re-forming attacks
|
|
128
148
|
const reformAttacks = [
|
|
129
149
|
'rm?lr', // Cave/wide cave attack
|
|
130
150
|
'rbm?lbr', // Deep cave/deep wide cave attack (impossibly rare?)
|
|
131
|
-
'
|
|
151
|
+
'ralmrW', // Attack at top of cave
|
|
132
152
|
'arlmwr', // Reversed
|
|
133
153
|
]
|
|
134
154
|
reformAttacks.map((ra) => threats.add(1, ra, true))
|
|
135
155
|
|
|
136
156
|
const reformThreats = [
|
|
137
157
|
'rp?lr', // Cave/wide cave threat
|
|
138
|
-
'rbp?lbr', // Deep cave/
|
|
158
|
+
'rbp?lbr', // Deep cave/deep wide cave threat (impossibly rare?)
|
|
159
|
+
'rmbb???w', // Black corner with a long white loop potential
|
|
160
|
+
'abb???lr', // Reverse
|
|
139
161
|
]
|
|
140
162
|
reformThreats.map((rt) => threats.add(2, rt))
|
|
141
163
|
|
|
142
|
-
// Attacks
|
|
143
|
-
const attacks = [
|
|
144
|
-
'a?w', // Simple attack and wide attack
|
|
145
|
-
]
|
|
146
|
-
attacks.map((a) => threats.add(1, a))
|
|
147
|
-
|
|
148
164
|
// Create multi-stage threats
|
|
149
165
|
for (const one of Object.keys(corners)) {
|
|
166
|
+
const start = one.replace('W', 'l')
|
|
167
|
+
let end = one.replace(/[aA]/, 'm').replace('C', 'p')
|
|
168
|
+
const pre = corners[one].post ? '?' : ''
|
|
169
|
+
let post = corners[one].pre ? '?' : ''
|
|
170
|
+
threats.compose(3, start + pre + '¡rm?W') // Attacking 3-stage threat
|
|
171
|
+
threats.compose(3, 'a?lr!' + post + end) // Reverse
|
|
172
|
+
threats.compose(3, one + '¡ab*bwr') // Corner by reforming hollow potential
|
|
173
|
+
threats.compose(3, 'rab*bw!' + one) // Reverse
|
|
174
|
+
threats.compose(3, one + '!brm*bW') // Corner by hollow attack, defends to edge
|
|
175
|
+
threats.compose(3, one + '!bbrmW') // Same, but slightly different layout
|
|
176
|
+
threats.compose(3, 'ab*lrb¡' + one) // Reverse
|
|
177
|
+
threats.compose(3, 'alrbb¡' + one) // Reverse of different layout
|
|
178
|
+
|
|
150
179
|
for (const two of Object.keys(corners)) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
threats.
|
|
156
|
-
|
|
157
|
-
threats.add(2, one + (after ? '?!' : '!') + end)
|
|
180
|
+
end = two.replace(/[aA]/, 'm').replace('C', 'p')
|
|
181
|
+
post = corners[two].pre ? '?' : ''
|
|
182
|
+
if (pre) {
|
|
183
|
+
threats.compose(2, start + pre + '¡' + two) // Make an L
|
|
184
|
+
threats.compose(3, start + pre + 'rb¡' + two) // Make a three-stage threat
|
|
185
|
+
threats.compose(3, start + '?!rb¡' + two) // Attack to form an L threat
|
|
158
186
|
}
|
|
159
187
|
|
|
160
|
-
|
|
161
|
-
|
|
188
|
+
if (post) {
|
|
189
|
+
threats.compose(2, one + post + '!' + end) // L
|
|
190
|
+
threats.compose(3, one + post + '!br' + end) // Three-stage
|
|
191
|
+
threats.compose(3, one + '!br¡?' + end) // Attack to form an L threat
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
threats.compose(3, one + '!b*b¡' + two) // Edge threat
|
|
162
195
|
}
|
|
163
196
|
}
|
|
197
|
+
|
|
198
|
+
threats.add(3, 'ralbmrW') // Attack into a wide cave
|
|
199
|
+
threats.add(3, 'Arlbmwr') // Reversed
|
|
200
|
+
|
|
201
|
+
threats.add(4, 'ArlbmrW') // Wide cave ready for an attack
|
package/src/tty.js
CHANGED
|
@@ -22,7 +22,7 @@ import { Trax } from '@slugbugblue/trax'
|
|
|
22
22
|
const esc = '\u001B[' // Start of console color escape code
|
|
23
23
|
const off = esc + 'm' // Color reset code
|
|
24
24
|
const ENV = process.env
|
|
25
|
-
const NERD = ENV.NERDFONT
|
|
25
|
+
const NERD = ENV.NERDFONT || ENV.NERDFONTS
|
|
26
26
|
const POWER = ENV.POWERLINE || ENV.P9K_TTY || ENV.P9K_SSH
|
|
27
27
|
const leftBubble = NERD ? '' : POWER ? '' : '▐'
|
|
28
28
|
const rightBubble = NERD ? '' : POWER ? '' : '▌'
|
package/src/types.d.ts
CHANGED
|
@@ -25,12 +25,56 @@ type Colorer = {
|
|
|
25
25
|
white: Colorize
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** An object representing a space on the perimeter of a Trax position.
|
|
29
|
+
* x,y: the coordinates of the space
|
|
30
|
+
* c: the color present beside the space (w, b, l: both w and b, r: none)
|
|
31
|
+
* t: the type of possible moves: n-normal, x-none, c-cave, C-restricted cave
|
|
32
|
+
* b,w: if present, the number of the line to match with its other end
|
|
33
|
+
* pb, pw: if present, can pair with the next line in one turn
|
|
34
|
+
* xb, xw: the label of this space for each color
|
|
35
|
+
*/
|
|
36
|
+
type EdgeLocation = {
|
|
37
|
+
x: number
|
|
38
|
+
y: number
|
|
39
|
+
c: string
|
|
40
|
+
t: string
|
|
41
|
+
b?: number
|
|
42
|
+
w?: number
|
|
43
|
+
pb?: boolean
|
|
44
|
+
pw?: boolean
|
|
45
|
+
zb?: string
|
|
46
|
+
zw?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type EdgeObject = {
|
|
50
|
+
b: string
|
|
51
|
+
w: string
|
|
52
|
+
edge: RawEdge
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Object representing a concrete threat found in a position. */
|
|
56
|
+
type FoundThreat = { threat: string; match: string; at: number; value: number }
|
|
57
|
+
|
|
58
|
+
/** An object with keys representing each level, with arrays for each threat of
|
|
59
|
+
* that level. */
|
|
60
|
+
type FoundThreats = Record<string, FoundThreat[]>
|
|
61
|
+
|
|
62
|
+
/** All the threats found for both the white and black player. */
|
|
63
|
+
type FoundThreatsCollection = { b: ColorThreats; w: ColorThreats }
|
|
64
|
+
|
|
28
65
|
/** A point-like object has numerical x and y properties. */
|
|
29
66
|
type PointLike = {
|
|
30
67
|
x: number
|
|
31
68
|
y: number
|
|
32
69
|
}
|
|
33
70
|
|
|
71
|
+
/** An object used to track an analysis on a position. */
|
|
72
|
+
type PositionScore = {
|
|
73
|
+
move: string
|
|
74
|
+
score: number
|
|
75
|
+
analysis?: Analysis
|
|
76
|
+
}
|
|
77
|
+
|
|
34
78
|
/** A puzzle. */
|
|
35
79
|
type Puzzle = {
|
|
36
80
|
id: string
|
|
@@ -53,6 +97,10 @@ type PuzzleSource = {
|
|
|
53
97
|
url?: string
|
|
54
98
|
}
|
|
55
99
|
|
|
100
|
+
/** Array of objects representing the spaces surrounding the perimeter of a
|
|
101
|
+
* Trax position. */
|
|
102
|
+
type RawEdge = EdgeLocation[]
|
|
103
|
+
|
|
56
104
|
/** Treat the save state as an opaque object,
|
|
57
105
|
* produced by save() and fed into restore().
|
|
58
106
|
*/
|
|
@@ -76,6 +124,14 @@ type SaveState = {
|
|
|
76
124
|
*/
|
|
77
125
|
type Slash = '/' | '\\' | '+'
|
|
78
126
|
|
|
127
|
+
/** Threat definition. */
|
|
128
|
+
type Threat = {
|
|
129
|
+
depth: number
|
|
130
|
+
pattern: string
|
|
131
|
+
rx: RegExp
|
|
132
|
+
value: number
|
|
133
|
+
}
|
|
134
|
+
|
|
79
135
|
/** A single tile on the board. */
|
|
80
136
|
type Tile = {
|
|
81
137
|
id: TileId
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '0.
|
|
2
|
+
export const version = '0.15.0'
|