@slugbugblue/trax-cli 0.12.1 → 0.13.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/src/cmds/new.js CHANGED
@@ -1,19 +1,10 @@
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.
1
+ /** CLI new command
2
+ * @copyright 2022-2026
3
+ * @author Chad Transtrum <chad@transtrum.net>
4
+ * @license Apache-2.0
14
5
  */
15
6
 
16
- // CLI new command
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
17
8
 
18
9
  export const newCmd = {
19
10
  name: 'new',
@@ -25,6 +16,51 @@ export const newCmd = {
25
16
  'Valid variants are "trax", "loop", and "8x8". Defaults to "trax" if not',
26
17
  'specified. Use the "vs" keyword to use specific names for the players.',
27
18
  ],
19
+ /** @param {CLIContext} CLI @param {string} [variant] @param {...string} names */
20
+ fn(CLI, variant, ...names) {
21
+ let rules = ''
22
+ if (variant) {
23
+ variant = variant.toLowerCase()
24
+ if (variant.length > 1 && 'puzzles'.startsWith(variant)) {
25
+ return CLI.do('puzzles', 'new', ...names)
26
+ }
27
+
28
+ if (['8x8', '8', 'trax8x8', '8trax', '8x8trax'].includes(variant)) {
29
+ rules = 'trax8'
30
+ }
31
+
32
+ if (['loop', 'l', 'looptrax'].includes(variant)) rules = 'traxloop'
33
+ if (variant === 't') rules = 'trax'
34
+ if (['trax', 'traxloop', 'trax8'].includes(variant)) rules = variant
35
+
36
+ if (!rules) {
37
+ if (names.includes('vs') || names.includes('VS')) {
38
+ // We don't have a variant, we have player names
39
+ names.unshift(variant)
40
+ } else {
41
+ CLI.error('Unknown Trax variant.')
42
+ return CLI.do('help', 'new')
43
+ }
44
+ }
45
+ }
46
+
47
+ const players = pairUp()
48
+ if (names.length > 0) {
49
+ let vs = names.indexOf('vs')
50
+ if (vs === -1) vs = names.indexOf('VS')
51
+ if (vs >= 0) {
52
+ players[0] = names.slice(0, vs).join(' ') || 'white'
53
+ players[1] = names.slice(vs + 1).join(' ') || 'black'
54
+ }
55
+ }
56
+
57
+ const id = CLI.newGame(
58
+ /** @type {import('../cli.js').TraxVariant} */ (rules || 'trax'),
59
+ players,
60
+ '',
61
+ )
62
+ CLI.out(CLI.color('Started new game #' + id))
63
+ },
28
64
  }
29
65
 
30
66
  // Okay, yes, this is silly
@@ -103,44 +139,3 @@ const pairUp = () => {
103
139
  if (duo[0].toLowerCase() !== duo[0] && Math.random() < 0.5) duo.reverse()
104
140
  return duo
105
141
  }
106
-
107
- newCmd.fn = (CLI, variant, ...names) => {
108
- let rules = ''
109
- if (variant) {
110
- variant = variant.toLowerCase()
111
- if (variant.length > 1 && 'puzzles'.startsWith(variant)) {
112
- return CLI.do('puzzles', 'new', ...names)
113
- }
114
-
115
- if (['8x8', '8', 'trax8x8', '8trax', '8x8trax'].includes(variant)) {
116
- rules = 'trax8'
117
- }
118
-
119
- if (['loop', 'l', 'looptrax'].includes(variant)) rules = 'traxloop'
120
- if (variant === 't') rules = 'trax'
121
- if (['trax', 'traxloop', 'trax8'].includes(variant)) rules = variant
122
-
123
- if (!rules) {
124
- if (names.includes('vs') || names.includes('VS')) {
125
- // We don't have a variant, we have player names
126
- names.unshift(variant)
127
- } else {
128
- CLI.error('Unknown Trax variant.')
129
- return CLI.do('help', 'new')
130
- }
131
- }
132
- }
133
-
134
- const players = pairUp()
135
- if (names.length > 0) {
136
- let vs = names.indexOf('vs')
137
- if (vs === -1) vs = names.indexOf('VS')
138
- if (vs >= 0) {
139
- players[0] = names.slice(0, vs).join(' ') || 'white'
140
- players[1] = names.slice(vs + 1).join(' ') || 'black'
141
- }
142
- }
143
-
144
- const id = CLI.newGame(rules, players, '')
145
- CLI.out(CLI.color('Started new game #' + id))
146
- }
package/src/cmds/notes.js CHANGED
@@ -1,9 +1,11 @@
1
1
  /** Notes CLI command.
2
- * @copyright 2022
2
+ * @copyright 2022-2026
3
3
  * @author Chad Transtrum <chad@transtrum.net>
4
4
  * @license Apache-2.0
5
5
  */
6
6
 
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
8
+
7
9
  export const notesCmd = {
8
10
  name: 'notes',
9
11
  args: '[#id] <text>',
@@ -12,26 +14,26 @@ export const notesCmd = {
12
14
  'Add a short note to a game that will be shown when the game is',
13
15
  'displayed, listed, or exported.',
14
16
  ],
15
- }
16
-
17
- notesCmd.fn = (CLI, id, ...note) => {
18
- const current = String(CLI.GAME.id)
19
- id ||= current
20
- if (!/^#?\d+$/.test(id)) {
21
- note.unshift(id)
22
- id = current
23
- }
17
+ /** @param {CLIContext} CLI @param {string} [id] @param {...string} note */
18
+ fn(CLI, id, ...note) {
19
+ const current = String(CLI.GAME.id)
20
+ id ||= current
21
+ if (!/^#?\d+$/v.test(id)) {
22
+ note.unshift(id)
23
+ id = current
24
+ }
24
25
 
25
- if (id.startsWith('#')) id = id.slice(1)
26
+ if (id.startsWith('#')) id = id.slice(1)
26
27
 
27
- const game = CLI.GAMES[id]
28
+ const game = CLI.GAMES[id]
28
29
 
29
- if (!game) return CLI.error('Game not found.')
30
+ if (!game) return CLI.error('Game not found.')
30
31
 
31
- const notes = game.notes || []
32
- const move = game.moves.length > 0 ? game.moves.split(' ').length : 0
32
+ const notes = game.notes || []
33
+ const move = game.moves.length > 0 ? game.moves.split(' ').length : 0
33
34
 
34
- notes.push({ move, note: note.join(' ') })
35
- game.notes = notes
36
- CLI.save()
35
+ notes.push({ move, note: note.join(' ') })
36
+ game.notes = notes
37
+ CLI.save()
38
+ },
37
39
  }
@@ -1,25 +1,17 @@
1
- /* Copyright 2022-2023 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.
1
+ /** CLI play/try commands
2
+ * @copyright 2022-2026
3
+ * @author Chad Transtrum <chad@transtrum.net>
4
+ * @license Apache-2.0
14
5
  */
15
6
 
16
- // CLI play/try commands
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
17
8
 
18
9
  import { suggest } from '@slugbugblue/trax-analyst'
10
+ import { puzzles } from '@slugbugblue/trax-puzzles'
19
11
  import { timeString } from '@slugbugblue/trax-cli/utils'
20
12
 
21
- const moveRx = /^\d+\.?$/
22
- const notationRx = /^[@a-z]+\d+[bps/\\+]$/i
13
+ const moveRx = /^\d+\.?$/v
14
+ const notationRx = /^[@a-z]+\d+[bps\/\\+]$/iv
23
15
 
24
16
  export const playCmd = {
25
17
  name: 'play',
@@ -35,6 +27,65 @@ export const playCmd = {
35
27
  'Multiple moves can be submitted at once. Move numbers are optional,',
36
28
  'but if included, they will be checked for accuracy.',
37
29
  ],
30
+ /** @param {CLIContext} CLI @param {...string} moves */
31
+ fn(CLI, ...moves) {
32
+ if (moves.length === 0 || !moves[0]) {
33
+ CLI.error('You must provide a move.')
34
+ return CLI.do('help', 'play')
35
+ }
36
+
37
+ if (!CLI.GAME?.id) {
38
+ CLI.do('new')
39
+ }
40
+
41
+ const game = CLI.GAME
42
+ const trax = CLI.TRAX
43
+
44
+ if (trax.over) {
45
+ return CLI.error('The game is over.')
46
+ }
47
+
48
+ const start = trax.move
49
+ let checkMove
50
+
51
+ for (let move of moves) {
52
+ if (notationRx.test(move)) {
53
+ const moveNumber = trax.move
54
+ move = CLI.fixNotation(move)
55
+ if (checkMove) {
56
+ trax.play(checkMove, move)
57
+ } else {
58
+ trax.play(move)
59
+ }
60
+
61
+ if (trax.move === moveNumber) {
62
+ CLI.error(
63
+ 'Move "' +
64
+ (checkMove ? checkMove + '. ' : '') +
65
+ move +
66
+ '" is invalid.',
67
+ )
68
+ }
69
+ } else if (moveRx.test(move)) {
70
+ checkMove = Number(move)
71
+ } else if (move) {
72
+ CLI.error('Move "' + move + '" is not in the correct notation.')
73
+ }
74
+ }
75
+
76
+ if (trax.move !== start) {
77
+ const result = bot(CLI)
78
+ CLI.updateGameData()
79
+ CLI.do('view')
80
+ if (result === 'lose') {
81
+ CLI.error('Failed to complete puzzle ' + game.puzzle)
82
+ } else if (result === 'win') {
83
+ CLI.out(CLI.color.success('You completed puzzle ' + game.puzzle))
84
+ } else if (result) {
85
+ CLI.out(result)
86
+ }
87
+ }
88
+ },
38
89
  }
39
90
 
40
91
  export const tryCmd = {
@@ -47,15 +98,80 @@ export const tryCmd = {
47
98
  'View a move without committing to it. This is useful for seeing the',
48
99
  'effects of forced tiles.',
49
100
  ],
101
+ /** @param {CLIContext} CLI @param {string} [move] */
102
+ fn(CLI, move) {
103
+ if (!move || move.length === 0) {
104
+ CLI.error('You must provide a move.')
105
+ return CLI.do('help', 'try')
106
+ }
107
+
108
+ if (!CLI.TRAX) {
109
+ CLI.do('new')
110
+ }
111
+
112
+ if (CLI.TRAX.over) {
113
+ return CLI.error('The game is over.')
114
+ }
115
+
116
+ if (notationRx.test(move)) {
117
+ move = CLI.fixNotation(move)
118
+ const play = CLI.TRAX.dropTile(move, undefined, 'tentative')
119
+ if (play.valid || play.dropped.length > 0) {
120
+ CLI.display(CLI.TRAX, CLI.GAME.players, move)
121
+ } else {
122
+ CLI.error('Move is invalid.')
123
+ }
124
+ } else {
125
+ CLI.error('Move is invalid.')
126
+ }
127
+ },
128
+ }
129
+
130
+ /**
131
+ * Check if a concrete move matches a defense key token.
132
+ * Handles the catch-all wildcard (*), column wildcards (@**, A**, etc.),
133
+ * and row wildcards (*0*, *5*, etc.).
134
+ * @param {string} move
135
+ * @param {string} token
136
+ * @returns {boolean}
137
+ */
138
+ const matchesToken = (move, token) => {
139
+ if (token === '*') return true
140
+ if (!token.includes('*')) return token === move
141
+ const pattern =
142
+ '^' +
143
+ token
144
+ .replace('*', token.startsWith('*') ? '[@A-Za-z]+' : '[0-9]+')
145
+ .replace('*', String.raw`[+/\\]`) +
146
+ '$'
147
+ return new RegExp(pattern, 'v').test(move)
50
148
  }
51
149
 
150
+ /**
151
+ * Find the defense move for the current puzzle state, if one exists.
152
+ * @param {import('@slugbugblue/trax-puzzles').Defense} defense
153
+ * @param {string[]} moves
154
+ * @returns {string | undefined}
155
+ */
156
+ const getDefenseMove = (defense, moves) => {
157
+ const key = Object.keys(defense).find((k) => {
158
+ const tokens = k.split(' ')
159
+ return (
160
+ tokens.length === moves.length &&
161
+ tokens.every((token, i) => matchesToken(moves[i], token))
162
+ )
163
+ })
164
+ return key ? defense[key] : undefined
165
+ }
166
+
167
+ /** @param {CLIContext} CLI @returns {string} */
52
168
  const bot = (CLI) => {
53
169
  const game = CLI.GAME
54
170
  const trax = CLI.TRAX
55
171
  const players = game.players || ['a', 'b']
56
172
  const name = players[trax.turn - 1]
57
173
  if (trax.over && game.puzzle) {
58
- if (trax.move <= game.max && name !== 'puzzlebot') {
174
+ if (trax.move <= (game.max ?? 0) && name !== 'puzzlebot') {
59
175
  CLI.puzzleSolved(game.puzzle)
60
176
  return 'win'
61
177
  }
@@ -63,8 +179,8 @@ const bot = (CLI) => {
63
179
  return 'lose'
64
180
  }
65
181
 
66
- if (!trax.over && /bot\b/i.test(name)) {
67
- if (game.puzzle && trax.move >= game.max) {
182
+ if (!trax.over && /bot\b/iv.test(name)) {
183
+ if (game.puzzle && game.max !== undefined && trax.move >= game.max) {
68
184
  trax.play('puzzled')
69
185
  return 'lose'
70
186
  }
@@ -72,7 +188,18 @@ const bot = (CLI) => {
72
188
  CLI.do('view')
73
189
  CLI.out(CLI.bubble(trax.color + 'd', `${name} thinking...`))
74
190
  const start = Date.now()
75
- const move = suggest(trax)?.pick?.move
191
+
192
+ let move
193
+ if (game.puzzle) {
194
+ const puzzle = puzzles.find((p) => p.id === game.puzzle)
195
+ if (puzzle?.defense) {
196
+ const offset = puzzle.notation.split(' ').length
197
+ move = getDefenseMove(puzzle.defense, trax.moves.slice(offset))
198
+ }
199
+ }
200
+
201
+ move ??= suggest(trax)?.pick?.move
202
+
76
203
  const ms = Date.now() - start
77
204
  if (move) {
78
205
  CLI.out(
@@ -87,89 +214,3 @@ const bot = (CLI) => {
87
214
 
88
215
  return ''
89
216
  }
90
-
91
- playCmd.fn = (CLI, ...moves) => {
92
- if (moves.length === 0 || !moves[0]) {
93
- CLI.error('You must provide a move.')
94
- return CLI.do('help', 'play')
95
- }
96
-
97
- if (!CLI.GAME?.id) {
98
- CLI.do('new')
99
- }
100
-
101
- const game = CLI.GAME
102
- const trax = CLI.TRAX
103
-
104
- if (trax.over) {
105
- return CLI.error('The game is over.')
106
- }
107
-
108
- const start = trax.move
109
- let checkMove
110
-
111
- for (let move of moves) {
112
- if (notationRx.test(move)) {
113
- const moveNumber = trax.move
114
- move = CLI.fixNotation(move)
115
- if (checkMove) {
116
- trax.play(checkMove, move)
117
- } else {
118
- trax.play(move)
119
- }
120
-
121
- if (trax.move === moveNumber) {
122
- CLI.error(
123
- 'Move "' +
124
- (checkMove ? checkMove + '. ' : '') +
125
- move +
126
- '" is invalid.',
127
- )
128
- }
129
- } else if (moveRx.test(move)) {
130
- checkMove = Number(move)
131
- } else if (move) {
132
- CLI.error('Move "' + move + '" is not in the correct notation.')
133
- }
134
- }
135
-
136
- if (trax.move !== start) {
137
- const result = bot(CLI)
138
- CLI.updateGameData()
139
- CLI.do('view')
140
- if (result === 'lose') {
141
- CLI.error('Failed to complete puzzle ' + game.puzzle)
142
- } else if (result === 'win') {
143
- CLI.out(CLI.color.success('You completed puzzle ' + game.puzzle))
144
- } else if (result) {
145
- CLI.out(result)
146
- }
147
- }
148
- }
149
-
150
- tryCmd.fn = (CLI, move) => {
151
- if (!move || move.length === 0) {
152
- CLI.error('You must provide a move.')
153
- return CLI.do('help', 'try')
154
- }
155
-
156
- if (!CLI.TRAX) {
157
- CLI.do('new')
158
- }
159
-
160
- if (CLI.TRAX.over) {
161
- return CLI.error('The game is over.')
162
- }
163
-
164
- if (notationRx.test(move)) {
165
- move = CLI.fixNotation(move)
166
- const play = CLI.TRAX.dropTile(move, undefined, 'tentative')
167
- if (play.valid || play.dropped.length > 0) {
168
- CLI.display(CLI.TRAX, CLI.GAME.players, move)
169
- } else {
170
- CLI.error('Move is invalid.')
171
- }
172
- } else {
173
- CLI.error('Move is invalid.')
174
- }
175
- }
@@ -1,9 +1,11 @@
1
1
  /** @file Puzzles CLI
2
- * @copyright 2022-2023
2
+ * @copyright 2022-2026
3
3
  * @author Chad Transtrum <chad@transtrum.net>
4
4
  * @license Apache-2.0
5
5
  */
6
6
 
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
8
+
7
9
  import { Trax } from '@slugbugblue/trax'
8
10
  import { levels, puzzles, sources } from '@slugbugblue/trax-puzzles'
9
11
 
@@ -29,13 +31,50 @@ export const puzzlesCmd = {
29
31
  'Practice your skills with puzzles. Use "list" to see the puzzle',
30
32
  'categories, or "start" to play a puzzle.',
31
33
  ],
34
+ /** @param {CLIContext} CLI @param {string} [action] @param {string} [id] */
35
+ fn(CLI, action, id) {
36
+ action = action?.toLowerCase() ?? 'list'
37
+ if (startAliases.some((alias) => alias.startsWith(action))) {
38
+ if (!id) return startNextPuzzle(CLI)
39
+
40
+ const puzzle = puzzles.find((p) => p.id === id)
41
+ if (puzzle) {
42
+ return startPuzzle(CLI, puzzle)
43
+ }
44
+
45
+ if (validLevels.includes(id)) {
46
+ return startNextPuzzle(CLI, levels.indexOf(id))
47
+ }
48
+
49
+ const possibles = puzzles.filter((p) => p.id.includes(id))
50
+ if (possibles.length === 1) {
51
+ return startPuzzle(CLI, possibles[0])
52
+ }
53
+
54
+ return CLI.error('Could not find puzzle ' + id)
55
+ }
56
+
57
+ if (action) {
58
+ const x = id || action
59
+ if (validLevels.includes(x)) return listLevel(CLI, x)
60
+ if (listAliases.some((alias) => alias.startsWith(x))) {
61
+ return listPuzzles(CLI)
62
+ }
63
+
64
+ return showPuzzle(CLI, x)
65
+ }
66
+
67
+ listPuzzles(CLI)
68
+ },
32
69
  }
33
70
 
71
+ /** @param {string} [word] @returns {string} */
34
72
  const an = (word = '') => {
35
- const a = /^[aeiou]/.test(word) ? 'an' : 'a'
73
+ const a = /^[aeiou]/v.test(word) ? 'an' : 'a'
36
74
  return a + ' ' + word
37
75
  }
38
76
 
77
+ /** @param {CLIContext} CLI @param {string} id @param {boolean} [started] */
39
78
  const showPuzzle = (CLI, id, started = false) => {
40
79
  let puzzle = puzzles.find((p) => p.id === id)
41
80
  if (!puzzle) {
@@ -80,6 +119,7 @@ const showPuzzle = (CLI, id, started = false) => {
80
119
  )
81
120
  }
82
121
 
122
+ /** @param {CLIContext} CLI */
83
123
  const listPuzzles = (CLI) => {
84
124
  let size = 1
85
125
  const data = []
@@ -112,6 +152,7 @@ const listPuzzles = (CLI) => {
112
152
  }
113
153
  }
114
154
 
155
+ /** @param {CLIContext} CLI @param {string} levelName */
115
156
  const listLevel = (CLI, levelName) => {
116
157
  let size = 1
117
158
  const level = levels.indexOf(levelName)
@@ -134,55 +175,28 @@ const listLevel = (CLI, levelName) => {
134
175
  }
135
176
  }
136
177
 
178
+ /** @param {CLIContext} CLI @param {import('@slugbugblue/trax-puzzles').Puzzle} puzzle */
137
179
  const startPuzzle = (CLI, puzzle) => {
138
180
  const id = CLI.newPuzzle(puzzle)
139
181
  CLI.out(CLI.color(`Started puzzle ${puzzle.id} as game #${id}`))
140
182
  showPuzzle(CLI, puzzle.id, true)
141
183
  }
142
184
 
185
+ /** @param {CLIContext} CLI @param {number} [level] */
143
186
  const startNextPuzzle = (CLI, level) => {
144
187
  if (level === undefined) level = CLI.puzzleLevel
145
- const unsolved = puzzles.filter((p) => !CLI.puzzle(p.id).solved)
188
+ const unsolved = puzzles
189
+ .filter((p) => !CLI.puzzle(p.id).solved)
190
+ .toSorted(
191
+ (a, b) =>
192
+ a.level - b.level ||
193
+ a.notation.split(' ').length - b.notation.split(' ').length,
194
+ )
146
195
  if (unsolved.length === 0) {
147
- CLI.out(CLI.success('You have already completed all the puzzles.'))
196
+ CLI.out(CLI.color.success('You have already completed all the puzzles.'))
148
197
  return CLI.error('Specify a specific puzzle to try one again.')
149
198
  }
150
199
 
151
200
  const next = unsolved.find((p) => p.level >= level) || unsolved[0]
152
201
  startPuzzle(CLI, next)
153
202
  }
154
-
155
- puzzlesCmd.fn = (CLI, action, id) => {
156
- action = action?.toLowerCase() ?? 'list'
157
- if (startAliases.some((alias) => alias.startsWith(action))) {
158
- if (!id) return startNextPuzzle(CLI)
159
-
160
- const puzzle = puzzles.find((p) => p.id === id)
161
- if (puzzle) {
162
- return startPuzzle(CLI, puzzle)
163
- }
164
-
165
- if (validLevels.includes(id)) {
166
- return startNextPuzzle(CLI, levels.indexOf(id))
167
- }
168
-
169
- const possibles = puzzles.filter((p) => p.id.includes(id))
170
- if (possibles.length === 1) {
171
- return startPuzzle(CLI, possibles[0])
172
- }
173
-
174
- return CLI.error('Could not find puzzle ' + id)
175
- }
176
-
177
- if (action) {
178
- const x = id || action
179
- if (validLevels.includes(x)) return listLevel(CLI, x)
180
- if (listAliases.some((alias) => alias.startsWith(x))) {
181
- return listPuzzles(CLI)
182
- }
183
-
184
- return showPuzzle(CLI, x)
185
- }
186
-
187
- listPuzzles(CLI)
188
- }
@@ -1,19 +1,10 @@
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.
1
+ /** CLI select command
2
+ * @copyright 2022-2026
3
+ * @author Chad Transtrum <chad@transtrum.net>
4
+ * @license Apache-2.0
14
5
  */
15
6
 
16
- // CLI select command
7
+ /** @typedef {import('../cli.js').CLIContext} CLIContext */
17
8
 
18
9
  export const selectCmd = {
19
10
  name: 'select',
@@ -21,27 +12,27 @@ export const selectCmd = {
21
12
  args: '#id [command]',
22
13
  comp: '<id> <cmd>',
23
14
  desc: 'select a different game',
24
- rx: /^#\d+$/,
15
+ rx: /^#\d+$/v,
25
16
  help: [
26
17
  'Make another game the default game of future commands. If another',
27
18
  'command is included, that command will be run immediately.',
28
19
  ],
29
- }
30
-
31
- selectCmd.fn = (CLI, id, ...cmds) => {
32
- if (!id) {
33
- CLI.error('Missing id.')
34
- return CLI.do('help', 'select')
35
- }
20
+ /** @param {CLIContext} CLI @param {string} [id] @param {...string} cmds */
21
+ fn(CLI, id, ...cmds) {
22
+ if (!id) {
23
+ CLI.error('Missing id.')
24
+ return CLI.do('help', 'select')
25
+ }
36
26
 
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(' '))
27
+ if (id.startsWith('#')) id = id.slice(1)
28
+ CLI.load(id)
29
+ if (String(CLI.GAME?.id) === id) {
30
+ CLI.out(CLI.color(`Game #${id} selected.`))
31
+ if (cmds.length > 0) {
32
+ CLI.doNext(cmds.join(' '))
33
+ }
34
+ } else {
35
+ CLI.error('Invalid id. Type "list" to see available games.')
43
36
  }
44
- } else {
45
- CLI.error('Invalid id. Type "list" to see available games.')
46
- }
37
+ },
47
38
  }