@slugbugblue/trax 0.4.0 → 0.5.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/{.eslintrc.yaml → .eslintrc.backup.yaml} +0 -0
- package/CHANGELOG.md +20 -3
- package/README.md +5 -2
- package/docs/engine.md +64 -0
- package/engine.js +367 -267
- package/package.json +16 -4
- package/tty.js +114 -83
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slugbugblue/trax",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Trax game engine and friends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"trax",
|
|
@@ -14,17 +14,29 @@
|
|
|
14
14
|
"exports": "./engine.js",
|
|
15
15
|
"repository": "gitlab:slugbugblue/trax",
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "c8
|
|
17
|
+
"test": "xo && c8 ava",
|
|
18
18
|
"preversion": "npm test",
|
|
19
19
|
"postversion": "git push && git push --tags"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
+
"ava": "^4.0.1",
|
|
22
23
|
"c8": "^7.11.0",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
24
|
+
"prettier": "^2.5.1",
|
|
25
|
+
"xo": "^0.47.0"
|
|
25
26
|
},
|
|
26
27
|
"type": "module",
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": "^14.14.0 || >=16.0.0"
|
|
30
|
+
},
|
|
31
|
+
"prettier": {
|
|
32
|
+
"bracketSpacing": true,
|
|
33
|
+
"proseWrap": "always",
|
|
34
|
+
"semi": false,
|
|
35
|
+
"singleQuote": true,
|
|
36
|
+
"trailingComma": "all",
|
|
37
|
+
"useTabs": false
|
|
38
|
+
},
|
|
39
|
+
"xo": {
|
|
40
|
+
"prettier": true
|
|
29
41
|
}
|
|
30
42
|
}
|
package/tty.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* Copyright 2021 Chad Transtrum
|
|
1
|
+
/* Copyright 2021-2022 Chad Transtrum
|
|
2
2
|
*
|
|
3
3
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License.
|
|
@@ -15,13 +15,15 @@
|
|
|
15
15
|
|
|
16
16
|
// Ever wanted to draw Trax boards on the console? I have.
|
|
17
17
|
|
|
18
|
+
import process from 'node:process'
|
|
19
|
+
|
|
18
20
|
import Trax from './engine.js'
|
|
19
21
|
|
|
20
|
-
const esc = '\
|
|
21
|
-
const off = esc + 'm'
|
|
22
|
+
const esc = '\u001B[' // Start of console color escape code
|
|
23
|
+
const off = esc + 'm' // Color reset code
|
|
22
24
|
|
|
23
25
|
// display a message with optional color
|
|
24
|
-
const
|
|
26
|
+
export const outty = (message, fgcolor, bgcolor) => {
|
|
25
27
|
let ansi = fgcolor || bgcolor ? esc : ''
|
|
26
28
|
if (fgcolor) ansi += '38;5;' + fgcolor
|
|
27
29
|
if (bgcolor) ansi += '48;5;' + bgcolor
|
|
@@ -29,10 +31,11 @@ const out = (message, fgcolor, bgcolor) => {
|
|
|
29
31
|
console.log(ansi + message + off)
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
let set = 'trax' //
|
|
34
|
+
let set = 'trax' // Which color set should we draw with?
|
|
33
35
|
|
|
34
36
|
// a little magic to display the game in progress
|
|
35
|
-
const
|
|
37
|
+
const colorSets = {
|
|
38
|
+
// Colors for terminal sequence: <Esc>[38;5;<color_num>m
|
|
36
39
|
// b, B, w, W: color of line for each player, capital for a winning line
|
|
37
40
|
// e: empty (ie, board) color, t: tile color
|
|
38
41
|
trax: { b: 238, B: 57, e: 19, t: 74, w: 255, W: 184 },
|
|
@@ -41,149 +44,183 @@ const color_sets = { // Colors for terminal sequence: <Esc>[38;5;<color_num>m
|
|
|
41
44
|
tentative: { b: 235, B: 18, e: 19, t: 244, w: 252, W: 217 },
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
const ln = 'm\u2584'
|
|
45
|
-
const up = 'm\u2588'
|
|
46
|
-
const lt = 'm\u2599'
|
|
47
|
-
const rt = 'm\
|
|
48
|
-
const fg = n => '38;5;' + String(set[n])
|
|
49
|
-
const bg = n => '48;5;' + String(set[n])
|
|
50
|
-
const sp = () => esc + bg('e') + 'm '
|
|
47
|
+
const ln = 'm\u2584' // Lower half block -- with the leading m to terminate ansi color escape
|
|
48
|
+
const up = 'm\u2588' // Full block
|
|
49
|
+
const lt = 'm\u2599' // Lower half and left half block
|
|
50
|
+
const rt = 'm\u259F' // Lower half and right half block
|
|
51
|
+
const fg = (n) => '38;5;' + String(set[n]) // Foreground color_num
|
|
52
|
+
const bg = (n) => '48;5;' + String(set[n]) // Background color_num
|
|
53
|
+
const sp = () => esc + bg('e') + 'm ' // Lower space
|
|
51
54
|
// tiles are displayed on two lines; the top half of the first line is empty
|
|
52
55
|
// and the second half is composed of three characters, up color surrounded by tile color
|
|
53
|
-
const uptty = clr =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const uptty = (clr) =>
|
|
57
|
+
esc +
|
|
58
|
+
fg('t') +
|
|
59
|
+
';' +
|
|
60
|
+
bg('e') +
|
|
61
|
+
ln +
|
|
62
|
+
esc +
|
|
63
|
+
fg(clr) +
|
|
64
|
+
ln +
|
|
65
|
+
esc +
|
|
66
|
+
fg('t') +
|
|
67
|
+
ln +
|
|
68
|
+
' '
|
|
69
|
+
// The top half of the second line has both horizontal lines with a center transition
|
|
56
70
|
// while the bottom half is the tile background color surrounding the down color
|
|
57
|
-
const horiztty = clr => esc + fg('t') + ';' + bg(clr) + ln
|
|
58
|
-
const dntty = (clr, oppclr, char) =>
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
const horiztty = (clr) => esc + fg('t') + ';' + bg(clr) + ln
|
|
72
|
+
const dntty = (clr, oppclr, char) =>
|
|
73
|
+
esc + fg(clr) + (char === up ? '' : ';' + bg(oppclr)) + char
|
|
74
|
+
// List of icon_char => tile line colors for easy reference in order up, right, down, left
|
|
75
|
+
const tiles = {
|
|
76
|
+
a: 'bbww',
|
|
77
|
+
b: 'bwbw',
|
|
78
|
+
c: 'bwwb',
|
|
79
|
+
d: 'wbbw',
|
|
80
|
+
e: 'wbwb',
|
|
81
|
+
f: 'wwbb',
|
|
82
|
+
}
|
|
61
83
|
|
|
62
|
-
//
|
|
63
|
-
const
|
|
84
|
+
// Return the ANSI codes to display a 3x1.5 block tile, one line at a time
|
|
85
|
+
const tileTty = (tile, top) => {
|
|
86
|
+
// Top: true for first line, false for second
|
|
64
87
|
// quick access to tile color and its opposite in upper case
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (tentative) set =
|
|
88
|
+
const colors = { b: 'b', w: 'w', B: 'w', W: 'b' }
|
|
89
|
+
const save = set
|
|
90
|
+
const tentative = tile !== tile.toLowerCase()
|
|
91
|
+
if (tentative) set = colorSets.tentative
|
|
69
92
|
tile = tile.toLowerCase()
|
|
70
|
-
if (!(tile in tiles)) {
|
|
71
|
-
|
|
93
|
+
if (!(tile in tiles)) {
|
|
94
|
+
// Winning paths are represented by adding 6 to the tile char code
|
|
95
|
+
tile = String.fromCodePoint(tile.codePointAt(0) - 6)
|
|
72
96
|
if (tile in tiles) {
|
|
73
97
|
colors.w = 'W'
|
|
74
98
|
colors.B = 'W'
|
|
75
|
-
} else {
|
|
76
|
-
|
|
99
|
+
} else {
|
|
100
|
+
// And another six for a black win
|
|
101
|
+
tile = String.fromCodePoint(tile.codePointAt(0) - 6)
|
|
77
102
|
colors.b = 'B'
|
|
78
103
|
colors.W = 'B'
|
|
79
104
|
}
|
|
80
105
|
}
|
|
81
|
-
|
|
106
|
+
|
|
107
|
+
let value = false
|
|
82
108
|
if (tile in tiles) {
|
|
83
109
|
tile = tiles[tile]
|
|
84
110
|
if (top) {
|
|
85
|
-
|
|
111
|
+
value = uptty(colors[tile[0]])
|
|
86
112
|
} else {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
const centerChar =
|
|
114
|
+
tile[1] === tile[2] ? rt : tile[2] === tile[3] ? lt : up
|
|
115
|
+
const centertty = dntty(
|
|
116
|
+
colors[tile[2]],
|
|
117
|
+
colors[tile[2].toUpperCase()],
|
|
118
|
+
centerChar,
|
|
119
|
+
)
|
|
120
|
+
value =
|
|
121
|
+
horiztty(colors[tile[3]]) + centertty + horiztty(colors[tile[1]]) + sp()
|
|
90
122
|
}
|
|
91
123
|
}
|
|
124
|
+
|
|
92
125
|
set = save
|
|
93
|
-
return
|
|
126
|
+
return value || (top ? '??? ' : "''' ")
|
|
94
127
|
}
|
|
95
128
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
const mergeTentative = () => {
|
|
130
|
+
colorSets.tentative.e = set.e // Copy empty color to tentative set
|
|
131
|
+
colorSets.tentative.W = set.W
|
|
132
|
+
colorSets.tentative.B = set.B
|
|
100
133
|
}
|
|
101
134
|
|
|
102
|
-
const
|
|
103
|
-
set =
|
|
104
|
-
|
|
135
|
+
const printIcon = (icon, setName) => {
|
|
136
|
+
set = colorSets[setName] || colorSets.trax
|
|
137
|
+
mergeTentative(set)
|
|
105
138
|
const on = esc + '38;5;' + String(set.t) + ';48;5;' + String(set.e) + 'm'
|
|
106
139
|
let o1 = ' '
|
|
107
140
|
let o2 = '1 '
|
|
108
141
|
let row = 1
|
|
109
142
|
let width = 1
|
|
110
|
-
|
|
111
|
-
let
|
|
112
|
-
while (icon.length) {
|
|
113
|
-
|
|
143
|
+
const info = []
|
|
144
|
+
let lineWidth = 0
|
|
145
|
+
while (icon.length > 0) {
|
|
146
|
+
const c = icon[0]
|
|
114
147
|
icon = icon.slice(1)
|
|
115
148
|
if (c === ':') {
|
|
116
|
-
info.push({o: o1, w:
|
|
117
|
-
info.push({o: o2, w: line_width})
|
|
149
|
+
info.push({ o: o1, w: lineWidth }, { o: o2, w: lineWidth })
|
|
118
150
|
row++
|
|
119
|
-
width = Math.max(width,
|
|
120
|
-
|
|
151
|
+
width = Math.max(width, lineWidth)
|
|
152
|
+
lineWidth = 0
|
|
121
153
|
o1 = ' '
|
|
122
154
|
o2 = String(row).padEnd(3)
|
|
123
155
|
} else if (c >= '0' && c <= '9') {
|
|
124
|
-
|
|
156
|
+
const empty = Number(c) + 1
|
|
125
157
|
o1 += ' '.repeat(empty * 4)
|
|
126
158
|
o2 += ' '.repeat(empty * 4)
|
|
127
|
-
|
|
159
|
+
lineWidth += empty
|
|
128
160
|
} else {
|
|
129
|
-
o1 +=
|
|
130
|
-
o2 +=
|
|
131
|
-
|
|
161
|
+
o1 += tileTty(c, 'top')
|
|
162
|
+
o2 += tileTty(c)
|
|
163
|
+
lineWidth++
|
|
132
164
|
}
|
|
133
165
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
info.push({o: o2, w:
|
|
137
|
-
width = Math.max(width,
|
|
166
|
+
|
|
167
|
+
if (o1.length > 0) {
|
|
168
|
+
info.push({ o: o1, w: lineWidth }, { o: o2, w: lineWidth })
|
|
169
|
+
width = Math.max(width, lineWidth)
|
|
138
170
|
}
|
|
139
|
-
|
|
140
|
-
|
|
171
|
+
|
|
172
|
+
const columns = process.stdout.columns || 0
|
|
173
|
+
const rows = process.stdout.rows || 0
|
|
141
174
|
if (width * 4 < columns - 4 && info.length < rows - 2) {
|
|
142
|
-
let
|
|
175
|
+
let colOut = ' '
|
|
143
176
|
for (let col = 1; col <= width; col++) {
|
|
144
|
-
|
|
177
|
+
colOut += ' ' + Trax.column(col).padEnd(3)
|
|
145
178
|
}
|
|
146
|
-
|
|
147
|
-
|
|
179
|
+
|
|
180
|
+
console.info(on + colOut.padEnd((width + 1) * 4) + off)
|
|
181
|
+
for (const line of info)
|
|
182
|
+
console.info(on + line.o + ''.padEnd((width - line.w) * 4) + ' ' + off)
|
|
148
183
|
console.info(on + ''.padEnd((width + 1) * 4) + off)
|
|
149
184
|
} else {
|
|
150
185
|
console.warn('Console is too small to display this game')
|
|
151
186
|
}
|
|
152
187
|
}
|
|
153
188
|
|
|
154
|
-
//
|
|
189
|
+
// Flexible way to get a Trax game object
|
|
155
190
|
// game = existing Trax instance, or a string indicating the variant name
|
|
156
191
|
// moves = optional list or string of moves to be made on the game
|
|
157
192
|
// returns a Trax instance
|
|
158
|
-
const
|
|
193
|
+
export const traxGame = (game = 'trax', moves = '') => {
|
|
159
194
|
let trax
|
|
160
195
|
if (game instanceof Trax) {
|
|
161
196
|
trax = game
|
|
162
197
|
if (moves) {
|
|
163
198
|
if (typeof moves === 'string') moves = moves.split(' ')
|
|
164
|
-
|
|
199
|
+
for (const move of moves) trax.dropTile(move)
|
|
165
200
|
}
|
|
166
201
|
} else {
|
|
167
202
|
trax = new Trax(game, moves, 'cli')
|
|
168
203
|
}
|
|
204
|
+
|
|
169
205
|
return trax
|
|
170
206
|
}
|
|
171
207
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
let icon = trax
|
|
208
|
+
export const displayGame = (game = 'trax', moves = '', tentative = false) => {
|
|
209
|
+
const trax = traxGame(game, moves)
|
|
210
|
+
let { icon } = trax
|
|
175
211
|
if (tentative) {
|
|
176
|
-
|
|
212
|
+
const drops = trax.dropTile(tentative)
|
|
177
213
|
if (drops.valid) {
|
|
178
|
-
icon = trax.
|
|
214
|
+
icon = trax.tentativeIcon(drops)
|
|
179
215
|
} else {
|
|
180
216
|
tentative = false
|
|
181
217
|
}
|
|
182
218
|
}
|
|
219
|
+
|
|
183
220
|
console.info(trax.notation)
|
|
184
|
-
|
|
221
|
+
printIcon(icon, trax.rules)
|
|
185
222
|
if (tentative) console.info('--- tentative position ---')
|
|
186
|
-
|
|
223
|
+
const name = { 1: 'white', 2: 'black' }[trax.turn]
|
|
187
224
|
if (trax.over) {
|
|
188
225
|
if (trax.turn) {
|
|
189
226
|
console.info(name + ' wins')
|
|
@@ -191,12 +228,6 @@ const display_game = (game='trax', moves='', tentative) => {
|
|
|
191
228
|
console.info('game end' + (tentative ? 's' : 'ed') + ' in a draw')
|
|
192
229
|
}
|
|
193
230
|
} else if (trax.turn) {
|
|
194
|
-
console.info((tentative ? 'leaving ' : '') + name +' to play')
|
|
231
|
+
console.info((tentative ? 'leaving ' : '') + name + ' to play')
|
|
195
232
|
}
|
|
196
233
|
}
|
|
197
|
-
|
|
198
|
-
export default {
|
|
199
|
-
display: display_game,
|
|
200
|
-
game: trax_game,
|
|
201
|
-
out,
|
|
202
|
-
}
|