@slugbugblue/trax 0.7.0 → 0.10.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.
@@ -0,0 +1,129 @@
1
+ /* Copyright 2022 Chad Transtrum
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
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.
14
+ */
15
+
16
+ // CLI play/try commands
17
+
18
+ const moveRx = /^\d+\.?$/
19
+ const notationRx = /^[@a-z]+\d+[bps/\\+]$/i
20
+
21
+ export const playCmd = {
22
+ name: 'play',
23
+ alt: ['move', 'mv'],
24
+ args: '<move> [...]',
25
+ comp: '<play>',
26
+ desc: 'play a move',
27
+ rx: [moveRx, notationRx],
28
+ help: [
29
+ 'Enter the move in standard Trax notation. The final character of the',
30
+ 'notation can be replaced with a letter for ease of entering at the',
31
+ 'command line. Use <s> for "/", <b> for "\\", and <p> for "+".',
32
+ 'Multiple moves can be submitted at once. Move numbers are optional,',
33
+ 'but if included, they will be checked for accuracy.',
34
+ ],
35
+ }
36
+
37
+ export const tryCmd = {
38
+ name: 'try',
39
+ alt: 'see',
40
+ args: '<move>',
41
+ comp: '<play>',
42
+ desc: 'see the effects of a move',
43
+ help: [
44
+ 'View a move without committing to it. This is useful for seeing the',
45
+ 'effects of forced tiles.',
46
+ ],
47
+ }
48
+
49
+ const fixNotation = (move) => {
50
+ if (move.endsWith('b')) return move.slice(0, -1) + '\\'
51
+ if (move.endsWith('s')) return move.slice(0, -1) + '/'
52
+ if (move.endsWith('p')) return move.slice(0, -1) + '+'
53
+ return move
54
+ }
55
+
56
+ playCmd.fn = (CLI, ...moves) => {
57
+ if (moves.length === 0 || !moves[0]) {
58
+ CLI.error('You must provide a move.')
59
+ return CLI.do('help', 'play')
60
+ }
61
+
62
+ if (!CLI.GAME?.id) {
63
+ CLI.do('new')
64
+ }
65
+
66
+ if (CLI.TRAX.over) {
67
+ return CLI.error('The game is over.')
68
+ }
69
+
70
+ const start = CLI.TRAX.move
71
+ let checkMove
72
+
73
+ for (let move of moves) {
74
+ if (notationRx.test(move)) {
75
+ const moveNumber = CLI.TRAX.move
76
+ move = fixNotation(move)
77
+ if (checkMove) {
78
+ CLI.TRAX.playMove(checkMove, move)
79
+ } else {
80
+ CLI.TRAX.play(move)
81
+ }
82
+
83
+ if (CLI.TRAX.move === moveNumber) {
84
+ CLI.error(
85
+ 'Move "' +
86
+ (checkMove ? checkMove + '. ' : '') +
87
+ move +
88
+ '" is invalid.',
89
+ )
90
+ }
91
+ } else if (moveRx.test(move)) {
92
+ checkMove = Number(move)
93
+ } else if (move) {
94
+ CLI.error('Move "' + move + '" is not in the correct notation.')
95
+ }
96
+ }
97
+
98
+ if (CLI.TRAX.move !== start) {
99
+ CLI.updateGameData()
100
+ CLI.do('view')
101
+ }
102
+ }
103
+
104
+ tryCmd.fn = (CLI, move) => {
105
+ if (move.length === 0) {
106
+ CLI.error('You must provide a move.')
107
+ return CLI.do('help', 'try')
108
+ }
109
+
110
+ if (!CLI.TRAX) {
111
+ CLI.do('new')
112
+ }
113
+
114
+ if (CLI.TRAX.over) {
115
+ return CLI.error('The game is over.')
116
+ }
117
+
118
+ if (notationRx.test(move)) {
119
+ move = fixNotation(move)
120
+ const play = CLI.TRAX.dropTile(move, undefined, 'tentative')
121
+ if (play.valid || play.dropped.length > 0) {
122
+ CLI.display(CLI.TRAX, CLI.GAME.players, move)
123
+ } else {
124
+ CLI.error('Move is invalid.')
125
+ }
126
+ } else {
127
+ CLI.error('Move is invalid.')
128
+ }
129
+ }
@@ -0,0 +1,47 @@
1
+ /* Copyright 2022 Chad Transtrum
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
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.
14
+ */
15
+
16
+ // CLI select command
17
+
18
+ export const selectCmd = {
19
+ name: 'select',
20
+ alt: ['choose', '#'],
21
+ args: '#id [command]',
22
+ comp: '<id> <cmd>',
23
+ desc: 'select a different game',
24
+ rx: /^#\d+$/,
25
+ help: [
26
+ 'Make another game the default game of future commands. If another',
27
+ 'command is included, that command will be run immediately.',
28
+ ],
29
+ }
30
+
31
+ selectCmd.fn = (CLI, id, ...cmds) => {
32
+ if (!id) {
33
+ CLI.error('Missing id.')
34
+ return CLI.do('help', 'select')
35
+ }
36
+
37
+ if (id.startsWith('#')) id = id.slice(1)
38
+ CLI.load(id)
39
+ if (String(CLI.GAME?.id) === id) {
40
+ CLI.out(CLI.color(`Game #${id} selected.`))
41
+ if (cmds.length > 0) {
42
+ CLI.doNext(cmds.join(' '))
43
+ }
44
+ } else {
45
+ CLI.error('Invalid id. Type "list" to see available games.')
46
+ }
47
+ }
@@ -0,0 +1,40 @@
1
+ /* Copyright 2022 Chad Transtrum
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
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.
14
+ */
15
+
16
+ // CLI undo command
17
+
18
+ export const undoCmd = {
19
+ name: 'undo',
20
+ args: '[n]',
21
+ comp: '<move>',
22
+ desc: 'undo the last move in a game',
23
+ help: [
24
+ 'Backtrack one or more moves in a game. Type "undo" to take back the last',
25
+ 'move, or "undo 2" to undo the last two moves.',
26
+ ],
27
+ }
28
+
29
+ undoCmd.fn = (CLI, n) => {
30
+ n = Math.max(1, Math.floor(Number(n) || 1)) // Get an integer 1 or higher
31
+
32
+ const { moves } = CLI.TRAX
33
+ const undo = moves.slice(0, 0 - n)
34
+ if (moves.length === undo.length) {
35
+ CLI.error('Unable to undo.')
36
+ } else {
37
+ CLI.setGame(undo)
38
+ CLI.do('view')
39
+ }
40
+ }
@@ -0,0 +1,53 @@
1
+ /* Copyright 2022 Chad Transtrum
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
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.
14
+ */
15
+
16
+ // CLI view command
17
+
18
+ export const viewCmd = {
19
+ name: 'view',
20
+ alt: ['show', 'display'],
21
+ args: '[move number]',
22
+ comp: '<move>',
23
+ desc: 'show the game board',
24
+ help: [
25
+ 'Show the current game. Include a move number to see the board as it was at',
26
+ 'that move.',
27
+ ],
28
+ }
29
+
30
+ viewCmd.fn = (CLI, move) => {
31
+ if (!CLI.GAME?.id) {
32
+ return CLI.error('No active game. Type "new" to start a game.')
33
+ }
34
+
35
+ const trax = CLI.TRAX
36
+
37
+ let show = Number.POSITIVE_INFINITY
38
+ if (move && Number(move) >= 0 && Number(move) <= trax.moves.length) {
39
+ show = Number(move)
40
+ }
41
+
42
+ const players = CLI.GAME.players || ['white', 'black']
43
+
44
+ CLI.out(
45
+ CLI.color('#' + CLI.ID + ' ') +
46
+ CLI.name(trax.rules) +
47
+ ' ' +
48
+ CLI.bubble('w' + (trax.turn === 1 ? 'h' : ''), players[0]) +
49
+ CLI.color(' vs ') +
50
+ CLI.bubble('b' + (trax.turn === 2 ? 'h' : ''), players[1]),
51
+ )
52
+ CLI.display(trax, players, false, show)
53
+ }
@@ -24,7 +24,7 @@ import { Point } from './point.js'
24
24
  // Fun trax helper constants
25
25
 
26
26
  const zero = new Point(0, 0)
27
- const notationRegex = /^ *([@a-z]+) *(\d+) *([/\\+]) *$/i
27
+ const notationRegex = /^([@a-z]+)(\d+)([/\\+])$/i
28
28
 
29
29
  const slash = { a: '\\', b: '+', c: '/', d: '/', e: '+', f: '\\' }
30
30
 
@@ -72,7 +72,13 @@ const codeRowLength = (row) =>
72
72
  // This is where the magic happens
73
73
  export const Trax = class {
74
74
  // Static class properties
75
- static variants = new Set(['trax', 'traxloop', 'trax8']) // Known variants
75
+ static names = {
76
+ trax: 'Trax',
77
+ traxloop: 'Loop Trax',
78
+ trax8: '8x8 Trax',
79
+ }
80
+
81
+ static variants = new Set(Object.keys(this.names)) // Known variants
76
82
 
77
83
  // Static class methods
78
84
  static point = (x, y) => new Point(x, y)
@@ -122,13 +128,9 @@ export const Trax = class {
122
128
  // Moves might be a notation string
123
129
  if (moves) {
124
130
  if (typeof moves === 'string') moves = moves.split(/\s+/)
125
- for (let move of moves) {
126
- if (typeof move === 'string') {
127
- move = { move: this.move, notation: move }
128
- }
129
-
130
- if (notationRegex.test(move.notation)) {
131
- this.playMove(move.move, move.notation)
131
+ for (const move of moves) {
132
+ if (notationRegex.test(move)) {
133
+ this.dropTile(move)
132
134
  }
133
135
  }
134
136
  }
@@ -170,6 +172,11 @@ export const Trax = class {
170
172
  }
171
173
  }
172
174
 
175
+ get name() {
176
+ // Get the name of this variant
177
+ return Trax.names[this.rules]
178
+ }
179
+
173
180
  get count() {
174
181
  // Get the number of tiles currently in play
175
182
  return Object.keys(this.tiles).length
@@ -433,9 +440,9 @@ export const Trax = class {
433
440
  return this.dropTile(notation)
434
441
  }
435
442
 
436
- playMove(currentMoveNumber, nextMoveNotation) {
437
- // Generic API call for dropTile
438
- if (currentMoveNumber === this.move) return this.dropTile(nextMoveNotation)
443
+ playMove(moveNumber, notation) {
444
+ // Generic API call for playing a move with move number safety
445
+ if (moveNumber === this.move + 1) return this.dropTile(notation)
439
446
  return { valid: false }
440
447
  }
441
448
 
@@ -482,7 +489,7 @@ export const Trax = class {
482
489
  loc = decoded.loc
483
490
  }
484
491
 
485
- if (this.validLocation(loc) && this.validTile(type, loc)) {
492
+ if (loc && this.validLocation(loc) && this.validTile(type, loc)) {
486
493
  valid = true
487
494
  const saved = this.save() // Just in case we need to roll back
488
495
  notation = this.notate(type, loc) // Determine notation BEFORE we play the move
@@ -23,6 +23,8 @@ export const Point = class {
23
23
  #x = 0
24
24
  #y = 0
25
25
 
26
+ static dirs = ['up', 'down', 'left', 'right']
27
+
26
28
  constructor(x, y) {
27
29
  if (is(x, 'obj') && is(x.x, 'num') && is(x.y, 'num')) {
28
30
  // You can call this with new Point(point) or new Point({x,y})
@@ -76,6 +78,7 @@ export const Point = class {
76
78
  dir(direction) {
77
79
  const error = 'direction must be one of: up, down, left, right, u, d, l, r'
78
80
  if (!is(direction, 'str')) throw new SyntaxError(error)
81
+ direction = direction.toLowerCase()
79
82
  if (direction[0] === 'u') return this.up
80
83
  if (direction[0] === 'd') return this.down
81
84
  if (direction[0] === 'l') return this.left
@@ -99,6 +102,15 @@ export const Point = class {
99
102
  return this.#x === that.x && this.#y === that.y
100
103
  }
101
104
 
105
+ in(topLeft, bottomRight) {
106
+ return (
107
+ this.x >= topLeft.x &&
108
+ this.x <= bottomRight.x &&
109
+ this.y >= topLeft.y &&
110
+ this.y <= bottomRight.y
111
+ )
112
+ }
113
+
102
114
  // Play nicely with the rest of the world
103
115
 
104
116
  toString() {
@@ -22,13 +22,17 @@ import { Trax } from './engine.js'
22
22
  const esc = '\u001B[' // Start of console color escape code
23
23
  const off = esc + 'm' // Color reset code
24
24
 
25
- // display a message with optional color
25
+ // Colorize a string
26
+ export const color = (contents = '', fgcolor = '', bgcolor = '') => {
27
+ const c = []
28
+ if (fgcolor) c.push('38;5;' + fgcolor)
29
+ if (bgcolor) c.push('48;5;' + bgcolor)
30
+ return esc + c.join(';') + 'm' + contents
31
+ }
32
+
33
+ // Display a message with optional color
26
34
  export const outty = (message, fgcolor, bgcolor) => {
27
- let ansi = fgcolor || bgcolor ? esc : ''
28
- if (fgcolor) ansi += '38;5;' + fgcolor
29
- if (bgcolor) ansi += '48;5;' + bgcolor
30
- if (ansi) ansi += 'm'
31
- console.log(ansi + message + off)
35
+ console.log(color(message, fgcolor, bgcolor) + off)
32
36
  }
33
37
 
34
38
  let set = 'trax' // Which color set should we draw with?
@@ -44,6 +48,28 @@ const colorSets = {
44
48
  tentative: { b: 235, B: 18, e: 19, t: 244, w: 252, W: 217 },
45
49
  }
46
50
 
51
+ export const name = (rules) => {
52
+ if (Trax.variants.has(rules)) {
53
+ return color(Trax.names[rules], colorSets[rules].t)
54
+ }
55
+
56
+ return rules
57
+ }
58
+
59
+ export const bubble = (clr, text) => {
60
+ const colors = {
61
+ w: { fg: 236, bg: 153 },
62
+ b: { fg: 250, bg: 18 },
63
+ wd: { fg: 236, bg: 248 },
64
+ bd: { fg: 248, bg: 236 },
65
+ wh: { fg: 63, bg: 123 },
66
+ bh: { fg: 220, bg: 20 },
67
+ }
68
+
69
+ const c = colors[clr]
70
+ return color('', c.bg) + color(text, c.fg, c.bg) + off + color('', c.bg)
71
+ }
72
+
47
73
  const ln = 'm\u2584' // Lower half block -- with the leading m to terminate ansi color escape
48
74
  const up = 'm\u2588' // Full block
49
75
  const lt = 'm\u2599' // Lower half and left half block
@@ -201,29 +227,76 @@ export const traxGame = (game = 'trax', moves = '') => {
201
227
  return trax
202
228
  }
203
229
 
204
- export const displayGame = (game = 'trax', moves = '', tentative = false) => {
205
- const trax = traxGame(game, moves)
230
+ const printNotation = (trax, tentative, show = Number.POSITIVE_INFINITY) => {
231
+ let b = false
232
+ let n = 1
233
+ let out = ''
234
+ let length = 0
235
+ const t = tentative ? trax.moves.length - 1 : show
236
+ for (const move of trax.moves) {
237
+ const c = (b ? 'b' : 'w') + (t < n ? 'd' : show === n ? 'h' : '')
238
+ const notation = String(n) + ' ' + move
239
+ if (length + notation.length + 3 > process.stdout.columns) {
240
+ out += '\n'
241
+ length = 0
242
+ }
243
+
244
+ out += bubble(c, notation) + ' '
245
+ length += notation.length + 3
246
+ b = !b
247
+ n++
248
+ }
249
+
250
+ outty(out)
251
+ }
252
+
253
+ export const display = (
254
+ game,
255
+ players,
256
+ tentative = false,
257
+ showMove = Number.POSITIVE_INFINITY,
258
+ ) => {
259
+ const trax = traxGame(game)
260
+ const saved = trax.save()
261
+ const colors = ['white', 'black']
262
+ if (!Array.isArray(players)) players = colors
206
263
  let { icon } = trax
207
264
  if (tentative) {
208
265
  const drops = trax.dropTile(tentative)
209
- if (drops.valid) {
266
+ if (drops.valid || drops.dropped.length > 0) {
210
267
  icon = trax.dropsIcon(drops)
211
268
  } else {
212
269
  tentative = false
213
270
  }
271
+ } else if (showMove <= trax.moves.length) {
272
+ icon = trax.dropsIcon({
273
+ dropped: Object.values(trax.tiles).filter((t) => t.move > showMove),
274
+ })
214
275
  }
215
276
 
216
- console.info(trax.notation)
277
+ printNotation(trax, tentative, showMove)
217
278
  printIcon(icon, trax.rules)
218
- if (tentative) console.info('--- tentative position ---')
219
- const name = { 1: 'white', 2: 'black' }[trax.turn]
279
+ const nameColor = { 1: 'w', 2: 'b' }[trax.turn]
280
+ if (tentative) {
281
+ const otherColor = { w: 'bd', b: 'wd' }[nameColor] || 'wd'
282
+ outty('---' + bubble(otherColor, 'tentative position') + off + '---')
283
+ }
284
+
285
+ const name = players[trax.turn - 1] || colors[trax.turn - 1]
220
286
  if (trax.over) {
221
287
  if (trax.turn) {
222
- console.info(name + ' wins')
288
+ outty(bubble(nameColor + 'h', name) + off + ' wins')
223
289
  } else {
224
- console.info('game end' + (tentative ? 's' : 'ed') + ' in a draw')
290
+ outty('game end' + (tentative ? 's' : 'ed') + ' in a draw')
225
291
  }
226
292
  } else if (trax.turn) {
227
- console.info((tentative ? 'leaving ' : '') + name + ' to play')
293
+ outty(
294
+ (tentative ? 'leaving ' : '') +
295
+ bubble(nameColor, name) +
296
+ off +
297
+ ' to play',
298
+ )
228
299
  }
300
+
301
+ if (tentative) trax.restore(saved)
229
302
  }
package/src/version.js ADDED
@@ -0,0 +1,2 @@
1
+ // Generated by genversion.
2
+ export const version = '0.10.0'