@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slugbugblue/trax",
3
- "version": "0.2.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": "tape tests.js",
17
+ "test": "xo && c8 ava",
18
18
  "preversion": "npm test",
19
19
  "postversion": "git push && git push --tags"
20
20
  },
21
21
  "devDependencies": {
22
- "eslint": "8.x",
23
- "tape": "5.x",
24
- "typescript": "^4.5.4",
25
- "typescript-language-server": "^0.8.1"
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 = '\x1b[' // start of console color escape code
21
- const off = esc + 'm' // color reset code
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 out = (message, fgcolor, bgcolor) => {
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' // which color set should we draw with?
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 color_sets = { // Colors for terminal sequence: <Esc>[38;5;<color_num>m
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' // lower half block -- with the leading m to terminate ansi color escape
44
- const up = 'm\u2588' // full block
45
- const lt = 'm\u2599' // lower half and left half block
46
- const rt = 'm\u259f' // lower half and right half block
47
- const fg = n => '38;5;' + String(set[n]) // foreground color_num
48
- const bg = n => '48;5;' + String(set[n]) // background color_num
49
- const sp = () => esc + bg('e') + 'm ' // lower space
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 => esc + fg('t') + ';' + bg('e') + ln
53
- + esc + fg(clr) + ln + esc + fg('t') + ln + ' '
54
- // the top half of the second line has both horizontal lines with a center transition
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) => esc + fg(clr) + (char === up ? '' : ';' + bg(oppclr)) + char
58
- // list of icon_char => tile line colors for easy reference in order up, right, down, left
59
- const tiles = { a: 'bbww', b: 'bwbw', c: 'bwwb', d: 'wbbw', e: 'wbwb', f: 'wwbb' }
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
- // return the ANSI codes to display a 3x1.5 block tile, one line at a time
62
- const tile_tty = (tile, top) => { // top: true for first line, false for second
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
- let colors = { b: 'b', w: 'w', B: 'w', W: 'b' }
65
- if (!(tile in tiles)) { // winning paths are represented by adding 6 to the tile char code
66
- tile = String.fromCharCode(tile.charCodeAt(0) - 6)
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 { // and another six for a black win
71
- tile = String.fromCharCode(tile.charCodeAt(0) - 6)
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) return uptty(colors[tile[0]])
79
- let center_char = tile[1] === tile[2] ? rt : tile[2] === tile[3] ? lt : up
80
- let centertty = dntty(colors[tile[2]], colors[tile[2].toUpperCase()], center_char)
81
- return horiztty(colors[tile[3]]) + centertty + horiztty(colors[tile[1]]) + sp()
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
- if (top) return '??? '
84
- return '\'\'\' '
124
+
125
+ set = save
126
+ return value || (top ? '??? ' : "''' ")
85
127
  }
86
128
 
87
- const print_icon = (icon, set_name) => {
88
- set = color_sets[set_name] || color_sets.trax
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
- let info = []
95
- let line_width = 0
96
- while (icon.length) {
97
- let c = icon[0]
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: line_width})
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, line_width)
104
- line_width = 0
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
- let empty = Number(c) + 1
156
+ const empty = Number(c) + 1
109
157
  o1 += ' '.repeat(empty * 4)
110
158
  o2 += ' '.repeat(empty * 4)
111
- line_width += empty
159
+ lineWidth += empty
112
160
  } else {
113
- o1 += tile_tty(c, 'top')
114
- o2 += tile_tty(c)
115
- line_width++
161
+ o1 += tileTty(c, 'top')
162
+ o2 += tileTty(c)
163
+ lineWidth++
116
164
  }
117
165
  }
118
- if (o1.length) {
119
- info.push({o: o1, w: line_width})
120
- info.push({o: o2, w: line_width})
121
- width = Math.max(width, line_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
- let columns = process.stdout.columns || 0
124
- let rows = process.stdout.rows || 0
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 col_out = ' '
175
+ let colOut = ' '
127
176
  for (let col = 1; col <= width; col++) {
128
- col_out += ' ' + Trax.column(col).padEnd(3)
177
+ colOut += ' ' + Trax.column(col).padEnd(3)
129
178
  }
130
- console.info(on + col_out.padEnd((width + 1) * 4) + off)
131
- info.forEach(line => console.info(on + line.o + ''.padEnd((width - line.w) * 4) + ' ' + off))
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
- // flexible way to get a Trax game object
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 trax_game = (game='trax', moves) => {
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
- moves.forEach(move => trax.drop_tile(move))
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 display_game = (game='trax', moves='') => {
157
- let trax = trax_game(game, moves)
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
- print_icon(trax.icon, trax.rules)
160
- let name = {0: 'nobody', 1: 'white', 2: 'black'}[trax.turn]
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 ended in a draw')
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
- }