@slugbugblue/trax-cli 0.11.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/.dockerignore +7 -0
- package/.rgignore +2 -0
- package/CHANGELOG.md +80 -0
- package/CONTRIBUTING.md +102 -0
- package/Dockerfile +14 -0
- package/LICENSE +201 -0
- package/README.md +193 -0
- package/package.json +71 -0
- package/src/cli.js +732 -0
- package/src/cmds/analyze.js +112 -0
- package/src/cmds/delete.js +71 -0
- package/src/cmds/help.js +81 -0
- package/src/cmds/import-export.js +394 -0
- package/src/cmds/list.js +177 -0
- package/src/cmds/new.js +136 -0
- package/src/cmds/notes.js +37 -0
- package/src/cmds/play-try.js +175 -0
- package/src/cmds/puzzles.js +188 -0
- package/src/cmds/select.js +47 -0
- package/src/cmds/suggest.js +55 -0
- package/src/cmds/undo.js +40 -0
- package/src/cmds/view.js +65 -0
- package/src/types.d.ts +105 -0
- package/src/utils.js +22 -0
- package/src/version.js +2 -0
package/src/cmds/list.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
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 list command
|
|
17
|
+
|
|
18
|
+
import process from 'node:process'
|
|
19
|
+
|
|
20
|
+
import { Trax } from '@slugbugblue/trax'
|
|
21
|
+
|
|
22
|
+
export const listCmd = {
|
|
23
|
+
name: 'list',
|
|
24
|
+
alt: 'ls',
|
|
25
|
+
args: '[sort|filter...] ["reverse"]',
|
|
26
|
+
desc: 'list all games',
|
|
27
|
+
comp:
|
|
28
|
+
'id|number game|rules|name player1|p1 player2|p2 move|status turn' +
|
|
29
|
+
' active|over trax|8x8|loop reverse',
|
|
30
|
+
help: [
|
|
31
|
+
'Valid sort options are:',
|
|
32
|
+
' "id": sort by game id',
|
|
33
|
+
' "game": sort by game type',
|
|
34
|
+
' "player1" or "player2": sort by player name',
|
|
35
|
+
' "moves": sort by the number of moves',
|
|
36
|
+
'Available filters:',
|
|
37
|
+
' #id: match a game or a certain number of moves',
|
|
38
|
+
' "active" or "over": whether a game is in progress or not',
|
|
39
|
+
' "loop" or "8x8" or "trax": filter by game type',
|
|
40
|
+
" <other text>: match a player's name or the latest game note",
|
|
41
|
+
'Add the "reverse" option to reverse the sort order.',
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const alphaSort = (a, b) => {
|
|
46
|
+
a = String(a).toLowerCase()
|
|
47
|
+
b = String(b).toLowerCase()
|
|
48
|
+
if (a < b) return -1
|
|
49
|
+
if (b < a) return 1
|
|
50
|
+
return 0
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sortBy = (sort) => {
|
|
54
|
+
if (!sort) return 'id'
|
|
55
|
+
sort = sort.toLowerCase()
|
|
56
|
+
if (sort === '#') return 'sid'
|
|
57
|
+
if ('ids'.startsWith(sort)) return 'sid'
|
|
58
|
+
if ('games'.startsWith(sort)) return 'rules'
|
|
59
|
+
if ('rules'.startsWith(sort)) return 'rules'
|
|
60
|
+
if ('names'.startsWith(sort)) return 'rules'
|
|
61
|
+
if ('numbers'.startsWith(sort)) return 'sid'
|
|
62
|
+
if ('moves'.startsWith(sort)) return 'smoves'
|
|
63
|
+
if ('status'.startsWith(sort)) return 'smoves'
|
|
64
|
+
if ('turn'.startsWith(sort)) return 'sturn'
|
|
65
|
+
if ('player'.startsWith(sort) && sort.endsWith('1')) return 'p1'
|
|
66
|
+
if ('player'.startsWith(sort) && sort.endsWith('2')) return 'p2'
|
|
67
|
+
if ('reverse'.startsWith(sort)) return 'reverse'
|
|
68
|
+
return sort
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const listSort = (list, filters) => {
|
|
72
|
+
if (list.length === 0) return list
|
|
73
|
+
let reverse = false
|
|
74
|
+
for (const sort of filters) {
|
|
75
|
+
const by = sortBy(sort)
|
|
76
|
+
if (by === 'reverse') {
|
|
77
|
+
reverse = true
|
|
78
|
+
} else if (list[0]?.[by]) {
|
|
79
|
+
list.sort((a, b) => alphaSort(a[by], b[by]))
|
|
80
|
+
} else if (by === 'trax') {
|
|
81
|
+
list = list.filter((g) => g.rules === by)
|
|
82
|
+
} else if (['over', 'active'].includes(by)) {
|
|
83
|
+
list = list.filter((g) => (by === 'over' ? g.over : !g.over))
|
|
84
|
+
} else {
|
|
85
|
+
list = list.filter((g) => {
|
|
86
|
+
return (
|
|
87
|
+
g.id.endsWith(by) ||
|
|
88
|
+
Trax.names[g.rules].toLowerCase().includes(by) ||
|
|
89
|
+
g.p1.toLowerCase().includes(by) ||
|
|
90
|
+
g.p2.toLowerCase().includes(by) ||
|
|
91
|
+
g.moves.startsWith(by + ' ') ||
|
|
92
|
+
g.note.includes(by)
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (reverse) list.reverse()
|
|
99
|
+
|
|
100
|
+
return list
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Find how many columns are left over after all the other columns arefilled.
|
|
104
|
+
* @arg {Record<string, number>} sizes
|
|
105
|
+
* @returns {number} number of columns left over
|
|
106
|
+
*/
|
|
107
|
+
const leftover = (sizes) => {
|
|
108
|
+
let left = process.stdout.columns
|
|
109
|
+
for (const size of Object.values(sizes)) {
|
|
110
|
+
left -= size
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return left
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
listCmd.fn = (CLI, ...filters) => {
|
|
117
|
+
if (Object.keys(CLI.GAMES).length === 0) {
|
|
118
|
+
return CLI.error('No games. Type "new" to start a new game.')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const puzzle = filters.find((f) => f.length > 1 && 'puzzles'.startsWith(f))
|
|
122
|
+
if (puzzle) return CLI.do('puzzles', ...filters.filter((f) => f !== puzzle))
|
|
123
|
+
|
|
124
|
+
let list = []
|
|
125
|
+
const size = {
|
|
126
|
+
spacing: 13, // Amount of space taken up by fixed-length and between columns
|
|
127
|
+
id: 1,
|
|
128
|
+
name: 1,
|
|
129
|
+
moves: 1,
|
|
130
|
+
p1: 1,
|
|
131
|
+
p2: 1,
|
|
132
|
+
}
|
|
133
|
+
let noteSize = Number.POSITIVE_INFINITY
|
|
134
|
+
|
|
135
|
+
for (const game of Object.values(CLI.GAMES)) {
|
|
136
|
+
const moves = game.moves.length === 0 ? 0 : game.moves.split(' ').length
|
|
137
|
+
list.push({
|
|
138
|
+
sid: String(game.id).padStart(9, '0'),
|
|
139
|
+
id: (game.id === CLI.GAME.id ? '* #' : '#') + String(game.id),
|
|
140
|
+
name: game.name?.length || 0,
|
|
141
|
+
rules: game.rules,
|
|
142
|
+
moves: game.over ? 'game over' : CLI.plural(moves, 'move'),
|
|
143
|
+
smoves: game.over ? '9999' : String(moves).padStart(9, '0'),
|
|
144
|
+
sturn: String(game.turn),
|
|
145
|
+
p1: game.players?.[0] || 'white',
|
|
146
|
+
p2: game.players?.[1] || 'black',
|
|
147
|
+
turn: game.turn,
|
|
148
|
+
over: game.over,
|
|
149
|
+
note: game.notes?.[game.notes.length - 1]?.note || '',
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
list = listSort(list, filters)
|
|
154
|
+
|
|
155
|
+
for (const game of list) {
|
|
156
|
+
size.id = Math.max(size.id, game.id.length)
|
|
157
|
+
size.name = Math.max(size.name, game.name)
|
|
158
|
+
size.moves = Math.max(size.moves, game.moves.length)
|
|
159
|
+
size.p1 = Math.max(size.p1, game.p1.length)
|
|
160
|
+
size.p2 = Math.max(size.p2, game.p2.length)
|
|
161
|
+
noteSize = Math.min(noteSize, leftover(size))
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
for (const game of list) {
|
|
165
|
+
CLI.out(
|
|
166
|
+
CLI.color(game.id.padStart(size.id) + ' ') +
|
|
167
|
+
Trax.names[game.rules] +
|
|
168
|
+
' '.repeat(size.name - game.name + 1) +
|
|
169
|
+
CLI.bubble(game.turn === 1 ? 'wh' : 'w', game.p1) +
|
|
170
|
+
CLI.color('vs '.padStart(size.p1 - game.p1.length + 4)) +
|
|
171
|
+
CLI.bubble(game.turn === 2 ? 'bh' : 'b', game.p2) +
|
|
172
|
+
CLI.color(' '.padStart(size.p2 - game.p2.length + 1)) +
|
|
173
|
+
CLI.color(game.moves.padEnd(size.moves)) +
|
|
174
|
+
CLI.color.help(' ' + game.note.slice(0, noteSize)),
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
}
|
package/src/cmds/new.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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 new command
|
|
17
|
+
|
|
18
|
+
export const newCmd = {
|
|
19
|
+
name: 'new',
|
|
20
|
+
alt: 'start',
|
|
21
|
+
args: '[variant] [[p1 name] "vs" [p2 name]]',
|
|
22
|
+
comp: 'trax|loop|8x8 vs',
|
|
23
|
+
desc: 'start a new game',
|
|
24
|
+
help: [
|
|
25
|
+
'Valid variants are "trax", "loop", and "8x8". Defaults to "trax" if not',
|
|
26
|
+
'specified. Use the "vs" keyword to use specific names for the players.',
|
|
27
|
+
],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Okay, yes, this is silly
|
|
31
|
+
const pairs = [
|
|
32
|
+
['Abbott', 'Costello'],
|
|
33
|
+
['Adam', 'Eve'],
|
|
34
|
+
['Anne Boleyn', 'Henry VIII'],
|
|
35
|
+
['Ash', 'Pikachu'],
|
|
36
|
+
['Barbie', 'Ken'],
|
|
37
|
+
['Batman', 'Robin'],
|
|
38
|
+
['Bert', 'Ernie'],
|
|
39
|
+
['Bonnie', 'Clyde'],
|
|
40
|
+
['Calvin', 'Hobbes'],
|
|
41
|
+
['Chip', 'Dale'],
|
|
42
|
+
['Donald', 'Daisy'],
|
|
43
|
+
['Fred', 'Barney'],
|
|
44
|
+
['Fred', 'George'],
|
|
45
|
+
['Frodo', 'Samwise'],
|
|
46
|
+
['Han', 'Chewie'],
|
|
47
|
+
['Harry', 'Ron'],
|
|
48
|
+
['Holmes', 'Watson'],
|
|
49
|
+
['Jekyll', 'Hyde'],
|
|
50
|
+
['Jim', 'Pam'],
|
|
51
|
+
['Joe', 'Volcano'],
|
|
52
|
+
['John', 'Yoko'],
|
|
53
|
+
['King Kong', 'Godzilla'],
|
|
54
|
+
['Lewis', 'Clark'],
|
|
55
|
+
['Lilo', 'Stitch'],
|
|
56
|
+
['Luke', 'Leia'],
|
|
57
|
+
['Marge', 'Homer'],
|
|
58
|
+
['Mario', 'Luigi'],
|
|
59
|
+
['Marlin', 'Dory'],
|
|
60
|
+
['Mary-Kate', 'Ashley'],
|
|
61
|
+
['Mickey', 'Minnie'],
|
|
62
|
+
['Miss Piggy', 'Kermit'],
|
|
63
|
+
['Mork', 'Mindy'],
|
|
64
|
+
['Obi-Wan', 'Anakin'],
|
|
65
|
+
['Pan', 'Hook'],
|
|
66
|
+
['Phineas', 'Ferb'],
|
|
67
|
+
['player 1', 'player 2'],
|
|
68
|
+
['p1', 'p2'],
|
|
69
|
+
['R2-D2', 'C-3PO'],
|
|
70
|
+
['Ren', 'Stimpy'],
|
|
71
|
+
['Rick', 'Morty'],
|
|
72
|
+
['Romeo', 'Juliet'],
|
|
73
|
+
['Shaggy', 'Scooby'],
|
|
74
|
+
['Shrek', 'Fiona'],
|
|
75
|
+
['Simon', 'Garfunkel'],
|
|
76
|
+
['Snoopy', 'Woodstock'],
|
|
77
|
+
['Sonny', 'Cher'],
|
|
78
|
+
['Spongebob', 'Patrick'],
|
|
79
|
+
['Tarzan', 'Jane'],
|
|
80
|
+
['Thelma', 'Louise'],
|
|
81
|
+
['Thing 1', 'Thing 2'],
|
|
82
|
+
['Tom', 'Huckleberry'],
|
|
83
|
+
['Tom', 'Jerry'],
|
|
84
|
+
['Tweety', 'Sylvester'],
|
|
85
|
+
['Waldorf', 'Statler'],
|
|
86
|
+
['Wallace', 'Gromit'],
|
|
87
|
+
['white', 'black'],
|
|
88
|
+
['Woody', 'Buzz'],
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
const pairUp = () => {
|
|
92
|
+
const duo = pairs[Math.floor(Math.random() * pairs.length)]
|
|
93
|
+
if (duo[0].toLowerCase() !== duo[0] && Math.random() < 0.5) duo.reverse()
|
|
94
|
+
return duo
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
newCmd.fn = (CLI, variant, ...names) => {
|
|
98
|
+
let rules = ''
|
|
99
|
+
if (variant) {
|
|
100
|
+
variant = variant.toLowerCase()
|
|
101
|
+
if (variant.length > 1 && 'puzzles'.startsWith(variant)) {
|
|
102
|
+
return CLI.do('puzzles', 'new', ...names)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (['8x8', '8', 'trax8x8', '8trax', '8x8trax'].includes(variant)) {
|
|
106
|
+
rules = 'trax8'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (['loop', 'l', 'looptrax'].includes(variant)) rules = 'traxloop'
|
|
110
|
+
if (variant === 't') rules = 'trax'
|
|
111
|
+
if (['trax', 'traxloop', 'trax8'].includes(variant)) rules = variant
|
|
112
|
+
|
|
113
|
+
if (!rules) {
|
|
114
|
+
if (names.includes('vs') || names.includes('VS')) {
|
|
115
|
+
// We don't have a variant, we have player names
|
|
116
|
+
names.unshift(variant)
|
|
117
|
+
} else {
|
|
118
|
+
CLI.error('Unknown Trax variant.')
|
|
119
|
+
return CLI.do('help', 'new')
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const players = pairUp()
|
|
125
|
+
if (names.length > 0) {
|
|
126
|
+
let vs = names.indexOf('vs')
|
|
127
|
+
if (vs === -1) vs = names.indexOf('VS')
|
|
128
|
+
if (vs >= 0) {
|
|
129
|
+
players[0] = names.slice(0, vs).join(' ') || 'white'
|
|
130
|
+
players[1] = names.slice(vs + 1).join(' ') || 'black'
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const id = CLI.newGame(rules, players, '')
|
|
135
|
+
CLI.out(CLI.color('Started new game #' + id))
|
|
136
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Notes CLI command.
|
|
2
|
+
* @copyright 2022
|
|
3
|
+
* @author Chad Transtrum <chad@transtrum.net>
|
|
4
|
+
* @license Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const notesCmd = {
|
|
8
|
+
name: 'notes',
|
|
9
|
+
args: '[#id] <text>',
|
|
10
|
+
desc: 'add a note to a game',
|
|
11
|
+
help: [
|
|
12
|
+
'Add a short note to a game that will be shown when the game is',
|
|
13
|
+
'displayed, listed, or exported.',
|
|
14
|
+
],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
notesCmd.fn = (CLI, id, ...note) => {
|
|
18
|
+
const current = String(CLI.GAME.id)
|
|
19
|
+
if (!id) id = current
|
|
20
|
+
if (!/^#?\d+$/.test(id)) {
|
|
21
|
+
note.unshift(id)
|
|
22
|
+
id = current
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (id.startsWith('#')) id = id.slice(1)
|
|
26
|
+
|
|
27
|
+
const game = CLI.GAMES[id]
|
|
28
|
+
|
|
29
|
+
if (!game) return CLI.error('Game not found.')
|
|
30
|
+
|
|
31
|
+
const notes = game.notes || []
|
|
32
|
+
const move = game.moves.length > 0 ? game.moves.split(' ').length : 0
|
|
33
|
+
|
|
34
|
+
notes.push({ move, note: note.join(' ') })
|
|
35
|
+
game.notes = notes
|
|
36
|
+
CLI.save()
|
|
37
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
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.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// CLI play/try commands
|
|
17
|
+
|
|
18
|
+
import { suggest } from '@slugbugblue/trax-analyst'
|
|
19
|
+
import { timeString } from '@slugbugblue/trax-cli/utils'
|
|
20
|
+
|
|
21
|
+
const moveRx = /^\d+\.?$/
|
|
22
|
+
const notationRx = /^[@a-z]+\d+[bps/\\+]$/i
|
|
23
|
+
|
|
24
|
+
export const playCmd = {
|
|
25
|
+
name: 'play',
|
|
26
|
+
alt: ['move', 'mv'],
|
|
27
|
+
args: '<move> [...]',
|
|
28
|
+
comp: '<play>',
|
|
29
|
+
desc: 'play a move',
|
|
30
|
+
rx: [moveRx, notationRx],
|
|
31
|
+
help: [
|
|
32
|
+
'Enter the move in standard Trax notation. The final character of the',
|
|
33
|
+
'notation can be replaced with a letter for ease of entering at the',
|
|
34
|
+
'command line. Use <s> for "/", <b> for "\\", and <p> for "+".',
|
|
35
|
+
'Multiple moves can be submitted at once. Move numbers are optional,',
|
|
36
|
+
'but if included, they will be checked for accuracy.',
|
|
37
|
+
],
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const tryCmd = {
|
|
41
|
+
name: 'try',
|
|
42
|
+
alt: 'see',
|
|
43
|
+
args: '<move>',
|
|
44
|
+
comp: '<play>',
|
|
45
|
+
desc: 'see the effects of a move',
|
|
46
|
+
help: [
|
|
47
|
+
'View a move without committing to it. This is useful for seeing the',
|
|
48
|
+
'effects of forced tiles.',
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bot = (CLI) => {
|
|
53
|
+
const game = CLI.GAME
|
|
54
|
+
const trax = CLI.TRAX
|
|
55
|
+
const players = game.players || ['a', 'b']
|
|
56
|
+
const name = players[trax.turn - 1]
|
|
57
|
+
if (trax.over && game.puzzle) {
|
|
58
|
+
if (trax.move <= game.max && name !== 'puzzlebot') {
|
|
59
|
+
CLI.puzzleSolved(game.puzzle)
|
|
60
|
+
return 'win'
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return 'lose'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!trax.over && /bot\b/i.test(name)) {
|
|
67
|
+
if (game.puzzle && trax.move >= game.max) {
|
|
68
|
+
trax.play('puzzled')
|
|
69
|
+
return 'lose'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
CLI.do('view')
|
|
73
|
+
CLI.out(CLI.bubble(trax.color + 'd', `${name} thinking...`))
|
|
74
|
+
const start = Date.now()
|
|
75
|
+
const move = suggest(trax)?.pick?.move
|
|
76
|
+
const ms = Date.now() - start
|
|
77
|
+
if (move) {
|
|
78
|
+
CLI.out(
|
|
79
|
+
CLI.bubble(trax.color + 'd', `${name} chooses ${move}`) +
|
|
80
|
+
CLI.color.black(' in ' + timeString(ms)),
|
|
81
|
+
)
|
|
82
|
+
trax.play(move)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (game.puzzle) return ' and win by move ' + String(game.max)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return ''
|
|
89
|
+
}
|
|
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
|
+
}
|