@slugbugblue/trax 0.2.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 +51 -0
- package/README.md +29 -13
- package/docs/engine.md +64 -0
- package/engine.js +390 -267
- package/package.json +17 -6
- package/tty.js +130 -73
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,18 +14,29 @@
|
|
|
14
14
|
"exports": "./engine.js",
|
|
15
15
|
"repository": "gitlab:slugbugblue/trax",
|
|
16
16
|
"scripts": {
|
|
17
|
-
"test": "
|
|
17
|
+
"test": "xo && c8 ava",
|
|
18
18
|
"preversion": "npm test",
|
|
19
19
|
"postversion": "git push && git push --tags"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
22
|
+
"ava": "^4.0.1",
|
|
23
|
+
"c8": "^7.11.0",
|
|
24
|
+
"prettier": "^2.5.1",
|
|
25
|
+
"xo": "^0.47.0"
|
|
26
26
|
},
|
|
27
27
|
"type": "module",
|
|
28
28
|
"engines": {
|
|
29
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
|
|
30
41
|
}
|
|
31
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,148 +31,203 @@ 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 },
|
|
39
42
|
trax8: { b: 236, B: 21, e: 58, t: 137, w: 254, W: 117 },
|
|
40
43
|
traxloop: { b: 235, B: 20, e: 17, t: 61, w: 253, W: 222 },
|
|
44
|
+
tentative: { b: 235, B: 18, e: 19, t: 244, w: 252, W: 217 },
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
const ln = 'm\u2584'
|
|
44
|
-
const up = 'm\u2588'
|
|
45
|
-
const lt = 'm\u2599'
|
|
46
|
-
const rt = 'm\
|
|
47
|
-
const fg = n => '38;5;' + String(set[n])
|
|
48
|
-
const bg = n => '48;5;' + String(set[n])
|
|
49
|
-
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
|
|
50
54
|
// tiles are displayed on two lines; the top half of the first line is empty
|
|
51
55
|
// and the second half is composed of three characters, up color surrounded by tile color
|
|
52
|
-
const uptty = clr =>
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
55
70
|
// while the bottom half is the tile background color surrounding the down color
|
|
56
|
-
const horiztty = clr => esc + fg('t') + ';' + bg(clr) + ln
|
|
57
|
-
const dntty = (clr, oppclr, char) =>
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
}
|
|
60
83
|
|
|
61
|
-
//
|
|
62
|
-
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
|
|
63
87
|
// quick access to tile color and its opposite in upper case
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
|
92
|
+
tile = tile.toLowerCase()
|
|
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)
|
|
67
96
|
if (tile in tiles) {
|
|
68
97
|
colors.w = 'W'
|
|
69
98
|
colors.B = 'W'
|
|
70
|
-
} else {
|
|
71
|
-
|
|
99
|
+
} else {
|
|
100
|
+
// And another six for a black win
|
|
101
|
+
tile = String.fromCodePoint(tile.codePointAt(0) - 6)
|
|
72
102
|
colors.b = 'B'
|
|
73
103
|
colors.W = 'B'
|
|
74
104
|
}
|
|
75
105
|
}
|
|
106
|
+
|
|
107
|
+
let value = false
|
|
76
108
|
if (tile in tiles) {
|
|
77
109
|
tile = tiles[tile]
|
|
78
|
-
if (top)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
110
|
+
if (top) {
|
|
111
|
+
value = uptty(colors[tile[0]])
|
|
112
|
+
} else {
|
|
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()
|
|
122
|
+
}
|
|
82
123
|
}
|
|
83
|
-
|
|
84
|
-
|
|
124
|
+
|
|
125
|
+
set = save
|
|
126
|
+
return value || (top ? '??? ' : "''' ")
|
|
85
127
|
}
|
|
86
128
|
|
|
87
|
-
const
|
|
88
|
-
|
|
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
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const printIcon = (icon, setName) => {
|
|
136
|
+
set = colorSets[setName] || colorSets.trax
|
|
137
|
+
mergeTentative(set)
|
|
89
138
|
const on = esc + '38;5;' + String(set.t) + ';48;5;' + String(set.e) + 'm'
|
|
90
139
|
let o1 = ' '
|
|
91
140
|
let o2 = '1 '
|
|
92
141
|
let row = 1
|
|
93
142
|
let width = 1
|
|
94
|
-
|
|
95
|
-
let
|
|
96
|
-
while (icon.length) {
|
|
97
|
-
|
|
143
|
+
const info = []
|
|
144
|
+
let lineWidth = 0
|
|
145
|
+
while (icon.length > 0) {
|
|
146
|
+
const c = icon[0]
|
|
98
147
|
icon = icon.slice(1)
|
|
99
148
|
if (c === ':') {
|
|
100
|
-
info.push({o: o1, w:
|
|
101
|
-
info.push({o: o2, w: line_width})
|
|
149
|
+
info.push({ o: o1, w: lineWidth }, { o: o2, w: lineWidth })
|
|
102
150
|
row++
|
|
103
|
-
width = Math.max(width,
|
|
104
|
-
|
|
151
|
+
width = Math.max(width, lineWidth)
|
|
152
|
+
lineWidth = 0
|
|
105
153
|
o1 = ' '
|
|
106
154
|
o2 = String(row).padEnd(3)
|
|
107
155
|
} else if (c >= '0' && c <= '9') {
|
|
108
|
-
|
|
156
|
+
const empty = Number(c) + 1
|
|
109
157
|
o1 += ' '.repeat(empty * 4)
|
|
110
158
|
o2 += ' '.repeat(empty * 4)
|
|
111
|
-
|
|
159
|
+
lineWidth += empty
|
|
112
160
|
} else {
|
|
113
|
-
o1 +=
|
|
114
|
-
o2 +=
|
|
115
|
-
|
|
161
|
+
o1 += tileTty(c, 'top')
|
|
162
|
+
o2 += tileTty(c)
|
|
163
|
+
lineWidth++
|
|
116
164
|
}
|
|
117
165
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
info.push({o: o2, w:
|
|
121
|
-
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)
|
|
122
170
|
}
|
|
123
|
-
|
|
124
|
-
|
|
171
|
+
|
|
172
|
+
const columns = process.stdout.columns || 0
|
|
173
|
+
const rows = process.stdout.rows || 0
|
|
125
174
|
if (width * 4 < columns - 4 && info.length < rows - 2) {
|
|
126
|
-
let
|
|
175
|
+
let colOut = ' '
|
|
127
176
|
for (let col = 1; col <= width; col++) {
|
|
128
|
-
|
|
177
|
+
colOut += ' ' + Trax.column(col).padEnd(3)
|
|
129
178
|
}
|
|
130
|
-
|
|
131
|
-
|
|
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)
|
|
132
183
|
console.info(on + ''.padEnd((width + 1) * 4) + off)
|
|
133
184
|
} else {
|
|
134
185
|
console.warn('Console is too small to display this game')
|
|
135
186
|
}
|
|
136
187
|
}
|
|
137
188
|
|
|
138
|
-
//
|
|
189
|
+
// Flexible way to get a Trax game object
|
|
139
190
|
// game = existing Trax instance, or a string indicating the variant name
|
|
140
191
|
// moves = optional list or string of moves to be made on the game
|
|
141
192
|
// returns a Trax instance
|
|
142
|
-
const
|
|
193
|
+
export const traxGame = (game = 'trax', moves = '') => {
|
|
143
194
|
let trax
|
|
144
195
|
if (game instanceof Trax) {
|
|
145
196
|
trax = game
|
|
146
197
|
if (moves) {
|
|
147
198
|
if (typeof moves === 'string') moves = moves.split(' ')
|
|
148
|
-
|
|
199
|
+
for (const move of moves) trax.dropTile(move)
|
|
149
200
|
}
|
|
150
201
|
} else {
|
|
151
202
|
trax = new Trax(game, moves, 'cli')
|
|
152
203
|
}
|
|
204
|
+
|
|
153
205
|
return trax
|
|
154
206
|
}
|
|
155
207
|
|
|
156
|
-
const
|
|
157
|
-
|
|
208
|
+
export const displayGame = (game = 'trax', moves = '', tentative = false) => {
|
|
209
|
+
const trax = traxGame(game, moves)
|
|
210
|
+
let { icon } = trax
|
|
211
|
+
if (tentative) {
|
|
212
|
+
const drops = trax.dropTile(tentative)
|
|
213
|
+
if (drops.valid) {
|
|
214
|
+
icon = trax.tentativeIcon(drops)
|
|
215
|
+
} else {
|
|
216
|
+
tentative = false
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
158
220
|
console.info(trax.notation)
|
|
159
|
-
|
|
160
|
-
|
|
221
|
+
printIcon(icon, trax.rules)
|
|
222
|
+
if (tentative) console.info('--- tentative position ---')
|
|
223
|
+
const name = { 1: 'white', 2: 'black' }[trax.turn]
|
|
161
224
|
if (trax.over) {
|
|
162
225
|
if (trax.turn) {
|
|
163
226
|
console.info(name + ' wins')
|
|
164
227
|
} else {
|
|
165
|
-
console.info('game
|
|
228
|
+
console.info('game end' + (tentative ? 's' : 'ed') + ' in a draw')
|
|
166
229
|
}
|
|
167
230
|
} else if (trax.turn) {
|
|
168
|
-
console.info(name +' to play')
|
|
231
|
+
console.info((tentative ? 'leaving ' : '') + name + ' to play')
|
|
169
232
|
}
|
|
170
233
|
}
|
|
171
|
-
|
|
172
|
-
export default {
|
|
173
|
-
display: display_game,
|
|
174
|
-
game: trax_game,
|
|
175
|
-
out,
|
|
176
|
-
}
|