@slugbugblue/trax 0.1.0 → 0.4.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 CHANGED
@@ -36,7 +36,7 @@ rules:
36
36
  - unix
37
37
  max-len:
38
38
  - warn
39
- - code: 119
39
+ - code: 99
40
40
  tabWidth: 2
41
41
  no-alert: error
42
42
  no-caller: error
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # @slugbugblue/trax
2
+
3
+ ## 0.3.0
4
+
5
+ Display a tentative move on the screen
6
+
7
+ * Add `trax.tentative_icon()` function
8
+ * `tty.display()` now accepts a tentative move
9
+
10
+ ## 0.2.0
11
+
12
+ Breaking changes:
13
+
14
+ * Converted from CommonJS to ESM
15
+ * `trax.normalized` now prepends turn information to the string
16
+
17
+ More convenience:
18
+
19
+ * `trax.color`: the color of the player whose turn it is
20
+ * `Trax.color_of[num]`: go from player number to color
21
+ * `Trax.player_num[color]`: or from color to player number
22
+ * `Trax.other[color]`: easily switch from 'w' to 'b' and back
23
+
24
+ ## 0.1.0
25
+
26
+ Initial release:
27
+
28
+ * engine.js
29
+ * create a new Trax game with `trax = new Trax()`
30
+ * play one move at a time with `trax.play('@0/')`
31
+ * ... etc
32
+
33
+ * tty.js
34
+ * display a game with `tty.display(trax)`
package/README.md CHANGED
@@ -26,11 +26,21 @@ The main Trax engine is provided as `engine.js`,
26
26
  which can be used as follows:
27
27
 
28
28
  ```javascript
29
- const Trax = require('@slugbugblue/trax')
29
+ import Trax from '@slugbugblue/trax'
30
30
 
31
+ // default constructor gives you a Trax game
31
32
  let trax = new Trax()
32
-
33
33
  trax.play('@0/')
34
+
35
+ // but you can also select Loop Trax
36
+ let loops = new Trax('traxloop')
37
+
38
+ // or 8x8 Trax
39
+ let tiny = new Trax('trax8')
40
+
41
+ // and the constructor lets you start with an initial set of moves
42
+ let puzzle = new Trax('trax', '@0/ a0\\ @2/')
43
+ puzzle.play('@2\\')
34
44
  ```
35
45
 
36
46
  Explore the file `engine.js` for more information
@@ -48,7 +58,7 @@ until I get around to documenting the functionality.
48
58
 
49
59
  This project is built by [Chad Transtrum][ctrans]
50
60
  for [slugbugblue.com][sbb].
51
- Issues can be opened on the
61
+ Issues can be opened on the
52
62
  [gitlab project page][repo].
53
63
 
54
64
  ## Contributing
@@ -66,18 +76,21 @@ and for his generosity in allowing me to
66
76
  add the game to [GoldToken.com][goldtoken].
67
77
 
68
78
  Thanks to his widow Colleen Foley-Smith
69
- for extending that generosity
70
- to allow me to include Trax on [slugbugblue.com][sbb].
79
+ for extending that courtesy
80
+ to allow me to include Trax on [slugbugblue.com][sbb]
81
+ as well.
71
82
 
72
83
  I would also like to extend a huge heartfelt appreciation
73
84
  to the magnanimous [Donald G. Bailey][dgb],
74
85
  who, through his excellent [book][traxbook],
75
- taught me the basics (and more!) of Trax
76
- and was always ready to share his insights
86
+ taught me the basics (and more!) of Trax.
87
+ I found him always ready to share his insights
77
88
  on the mechanics of the game,
78
- various approaches to encoding its complexities,
79
- and for his undeserved kindness
80
- in indulging my many questions.
89
+ as well as various approaches to encoding its complexities.
90
+ His undeserved kindness
91
+ and infinite patience
92
+ in indulging my many questions
93
+ can never be repaid.
81
94
 
82
95
  ## License
83
96
 
@@ -98,7 +111,7 @@ limitations under the License.
98
111
 
99
112
  ## Trax rules copyright
100
113
 
101
- In the United States, game mechanics may not be copyrighted,
114
+ In the United States, game mechanics may not be copyrighted;
102
115
  however, the specific wording of game rules does fall under copyright law.
103
116
 
104
117
  The official rules for Trax are found at http://traxgame.com/about_rules.php.
package/engine.js CHANGED
@@ -1,11 +1,11 @@
1
- /* Copyright 2021 Chad Transtrum
2
- *
1
+ /* Copyright 2019-2022 Chad Transtrum
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.
5
5
  * You may obtain a copy of the License at
6
- *
6
+ *
7
7
  * http://www.apache.org/licenses/LICENSE-2.0
8
- *
8
+ *
9
9
  * Unless required by applicable law or agreed to in writing, software
10
10
  * distributed under the License is distributed on an "AS IS" BASIS,
11
11
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -19,6 +19,7 @@
19
19
  // and then sorted alphabetically and given a single letter name, so 'bbww' becomes 'a'
20
20
  // which gives us six different tile names: a-f
21
21
 
22
+ const variants = ['trax', 'traxloop', 'trax8'] // known variants
22
23
 
23
24
  // fun trax helper constants
24
25
 
@@ -33,7 +34,9 @@ const symmetry = { // convert tile to different views
33
34
 
34
35
  const notation_regex = /^ *([@a-z]+) *(\d+) *([/\\+]) *$/i
35
36
 
36
- const variants = ['trax', 'traxloop', 'trax8'] // known variants
37
+ const color_of = { 1: 'w', 2: 'b' }
38
+ const player_num = { b: 2, w: 1 }
39
+ const other = { b: 'w', w: 'b' }
37
40
 
38
41
  // fun trax helper functions
39
42
 
@@ -84,16 +87,17 @@ const Trax = class {
84
87
  this.notation = ''
85
88
  this.tiles = {}
86
89
  this.path = []
87
- this.count = 0
88
90
  this.invalid = false
89
91
  // moves might be a notation string
90
- if (typeof moves === 'string') moves = moves.split(' ')
91
- moves.forEach(move => {
92
- if (typeof move === 'string') {
93
- move = { move: this.move, notation: move }
94
- }
95
- this.play_move(move.move, move.notation)
96
- })
92
+ if (moves) {
93
+ if (typeof moves === 'string') moves = moves.split(' ')
94
+ moves.forEach(move => {
95
+ if (typeof move === 'string') {
96
+ move = { move: this.move, notation: move }
97
+ }
98
+ this.play_move(move.move, move.notation)
99
+ })
100
+ }
97
101
  }
98
102
 
99
103
  save () { // in order to evaluate potential moves, we have to modify the state
@@ -108,7 +112,6 @@ const Trax = class {
108
112
  notation: this.notation,
109
113
  tiles: JSON.stringify(this.tiles),
110
114
  path: JSON.stringify(this.path),
111
- count: this.count,
112
115
  invalid: this.invalid,
113
116
  }
114
117
  }
@@ -124,10 +127,17 @@ const Trax = class {
124
127
  this.notation = saved.notation
125
128
  this.tiles = JSON.parse(saved.tiles)
126
129
  this.path = JSON.parse(saved.path)
127
- this.count = saved.count
128
130
  this.invalid = saved.invalid
129
131
  }
130
132
 
133
+ get count () { // get the number of tiles currently in play
134
+ return Object.keys(this.tiles).length
135
+ }
136
+
137
+ get color () { // get the color of the current player (w or b)
138
+ return color_of[this.turn]
139
+ }
140
+
131
141
  get game_over () { // returns true if the game is over
132
142
  return this.over
133
143
  }
@@ -147,7 +157,6 @@ const Trax = class {
147
157
  this.invalid = true // invalid tile is being played
148
158
  } else {
149
159
  this.tiles[id] = tile
150
- this.count++
151
160
  this.left = Math.min(this.left, x)
152
161
  this.right = Math.max(this.right, x)
153
162
  this.top = Math.min(this.top, y)
@@ -194,17 +203,27 @@ const Trax = class {
194
203
  if (this.width > 7 && (x < this.left || x > this.right)) return false
195
204
  if (this.height > 7 && (y < this.top || y > this.bottom)) return false
196
205
  }
197
- return this.tile_at(x-1, y) || this.tile_at(x+1, y) || this.tile_at(x, y-1) || this.tile_at(x, y+1)
206
+ return Boolean(
207
+ this.tile_at(x-1, y) || this.tile_at(x+1, y) ||
208
+ this.tile_at(x, y-1) || this.tile_at(x, y+1)
209
+ )
198
210
  }
199
211
 
200
212
  possible_locations () {
201
- let possibles = []
202
- for (let x = this.left - 1 ; x <= this.right + 1 ; x++) {
203
- for (let y = this.top -1 ; y <= this.bottom + 1 ; y ++) {
204
- if (this.valid_location(x, y)) possibles.push({x, y})
205
- }
213
+ let locs = { '0x0': { x:0, y:0 } } // if no tiles are placed, we can play a starting tile
214
+ const maybe = (x, y) => { // add a loc if a tile isn't already there
215
+ let loc = x + 'x' + y
216
+ locs[loc] = locs[loc] || { x, y }
206
217
  }
207
- return possibles
218
+ for (let tile of Object.values(this.tiles)) {
219
+ locs[tile.x + 'x' + tile.y] = 'tile' // add an existing tile
220
+ maybe(tile.x - 1, tile.y) // and four maybe tiles around it
221
+ maybe(tile.x + 1, tile.y) // which will either be replaced by real tiles
222
+ maybe(tile.x, tile.y - 1) // or will be potentially valid locations
223
+ maybe(tile.x, tile.y + 1)
224
+ }
225
+ // get rid of all the real tiles and return all the maybes
226
+ return Object.values(locs).filter(l => l !== 'tile')
208
227
  }
209
228
 
210
229
  possible_moves () { // returns a list of every currently possible move
@@ -293,13 +312,12 @@ const Trax = class {
293
312
 
294
313
  check_win (tiles) {
295
314
  let winner = false
296
- let colors = this.turn === 1 ? ['w', 'b'] : ['b', 'w'] // check win for current player first
315
+ let colors = [ this.color, other[this.color] ] // check win for current player first
297
316
  colors.forEach(color => {
298
317
  if (winner) return // skip the second player if the first has won
299
318
  tiles.forEach(tile => {
300
319
  if (winner) return // skip the remaining tiles once we find a win
301
320
  let ends = this.find_ends(color, tile.x, tile.y)
302
- if (ends.length !== 2) return // this should never happen, but if it does, don't die
303
321
  if (ends[0].x === ends[1].x && ends[0].y === ends[1].y) {
304
322
  winner = color // loop win
305
323
  this.path = ends[0].path // both paths are essentially the same
@@ -314,12 +332,14 @@ const Trax = class {
314
332
  })
315
333
  if (winner) {
316
334
  this.over = true
317
- this.turn = winner === 'w' ? 1 : 2
335
+ this.turn = player_num[winner]
318
336
  } else {
319
337
  // 8x8 Trax can end in a tie once there are no valid moves left
320
- if (this.rules === 'trax8' && this.possible_moves().length < 1) {
321
- this.over = true
322
- this.turn = 0 // tie
338
+ if (this.rules === 'trax8' && this.width > 7 && this.height > 7) {
339
+ if (this.possible_moves().length < 1) {
340
+ this.over = true
341
+ this.turn = 0 // tie
342
+ }
323
343
  }
324
344
  }
325
345
  }
@@ -371,56 +391,66 @@ const Trax = class {
371
391
  this.update_notation(notation)
372
392
  dropped.push(this.add_tile(type, x, y)) // play the tile
373
393
  dropped = dropped.concat(this.forced_moves(x, y)) // play all forced moves
374
- if (!tentative) this.check_win(dropped, tentative)
375
394
  if (this.invalid || tentative) {
376
- valid = false
395
+ if (this.invalid) valid = false
377
396
  this.restore(saved) // abort! abort!
378
- } else if (!this.over) {
379
- this.turn = this.turn === 1 ? 2 : 1
397
+ } else {
398
+ this.check_win(dropped)
399
+ if (!this.over) this.turn = this.turn === 1 ? 2 : 1
380
400
  }
381
401
  }
382
402
  }
383
403
  return { dropped, notation, valid }
384
404
  }
385
405
 
386
- rotate_move (move) { // symmetry helper, rotate a move around the board
406
+ move_rotations (move) { // symmetry helper, rotate a move around the board
387
407
  let match = move.match(notation_regex)
388
- if (match === null) return move
389
- let x = this.width + 1 - Number(match[2])
390
- let y = decode_col(match[1])
391
- let s = { '/': '\\', '\\': '/', '+': '+'}[match[3]]
392
- return encode_col(x) + String(y) + s
393
- }
394
-
395
- provisional_notation (provisional) { // test a provisional move against the current state
396
- let prov = provisional.split(';') // return false if the provisional move does not match
397
- if (prov.length !== 4) return 'delete-provisional' // <-- if the provisional move is bad
398
- let notation = false // otherwise, return the notation required to make the move
399
- let rows = prov[1].split(':')
408
+ if (match === null) return []
409
+ let x = decode_col(match[1])
410
+ let y = Number(match[2])
411
+ let s = match[3]
412
+ let slashes = s === '+' ? [s] : ['/', '\\']
413
+ let moves = new Set()
414
+ // this isn't super precise, but it limits the search space ... maybe #TODO?
415
+ for (let slash of slashes) {
416
+ moves.add(encode_col(x) + String(y) + slash)
417
+ moves.add(encode_col(y) + String(x) + slash)
418
+ moves.add(encode_col(this.height - x + 1) + String(this.width - y + 1) + slash)
419
+ moves.add(encode_col(this.width - y + 1) + String(this.height -x + 1) + slash)
420
+ }
421
+ return [...moves]
422
+ }
423
+
424
+ provisional_move (from, to, via) { // test a provisional move against the current state
425
+ let rows = from.slice(1).split(':')
400
426
  let cols = rows.reduce((acc, cur) => Math.max(acc, code_row_len(cur)), 0)
401
427
  rows = rows.length
402
428
  if (this.width > rows && this.height > cols || this.width > cols && this.height > rows) {
403
429
  return 'delete-provisional' // current board is bigger than the provisional move
404
430
  }
405
- if (prov[1] === this.normalized) {
406
- let move = prov[2]
431
+ if (from === this.normalized) {
407
432
  let saved = this.save() // save this position
408
- for (let i = 0 ; i < 4 && !notation; i++) { // try all four orientations because symmetry
409
- this.drop_tile(move)
410
- if (prov[3] === this.normalized) notation = move // this move matched!
411
- this.restore(saved) // put the board back how it was
412
- move = this.rotate_move(move) // rotate to the next orientation
433
+ for (let move of this.move_rotations(via)) { // try all symmetrical moves
434
+ let drop = this.drop_tile(move)
435
+ if (drop.valid) {
436
+ if (to === this.normalized) return move // this move matched!
437
+ this.restore(saved) // put the board back how it was
438
+ }
413
439
  }
414
440
  }
415
- return notation
441
+ return false
416
442
  }
417
443
 
418
444
  get icon () {
419
445
  return this.position_code()
420
446
  }
421
447
 
448
+ tentative_icon (drops) {
449
+ return this.position_code(undefined, undefined, undefined, undefined, drops)
450
+ }
451
+
422
452
  // symmetry helper
423
- position_code (right_to_left, bottom_to_top, rotate, swap_colors) {
453
+ position_code (right_to_left, bottom_to_top, rotate, swap_colors, drops) {
424
454
  const start_x = right_to_left ? this.right : this.left
425
455
  const start_y = bottom_to_top ? this.bottom : this.top
426
456
  const inc_x = right_to_left ? -1 : 1
@@ -439,6 +469,9 @@ const Trax = class {
439
469
  if (this.path.includes(this.tile_id(x, y))) {
440
470
  tile = String.fromCharCode(tile.charCodeAt(0) + this.turn * 6)
441
471
  }
472
+ if (drops && drops.dropped) {
473
+ if (drops.dropped.filter(t => t.x === x && t.y === y).length) tile = tile.toUpperCase()
474
+ }
442
475
  line += tile
443
476
  } else {
444
477
  line = add_blank(line)
@@ -481,7 +514,7 @@ const Trax = class {
481
514
  })
482
515
  })
483
516
  })
484
- return norm
517
+ return (this.color || 't').toUpperCase() + norm
485
518
  }
486
519
 
487
520
  get normalized () {
@@ -490,7 +523,13 @@ const Trax = class {
490
523
  }
491
524
  }
492
525
 
493
- Trax.column = (col) => encode_col(col)
526
+ // add class methods
527
+ Trax.column = encode_col
528
+
529
+ // and class constants
494
530
  Trax.variants = variants
531
+ Trax.color_of = color_of
532
+ Trax.player_num = player_num
533
+ Trax.other = other
495
534
 
496
- module.exports = Trax
535
+ export default Trax
package/jsconfig.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "module": "commonjs",
3
+ "module": "es2020",
4
4
  "target": "es2020"
5
5
  },
6
6
  "checkJs": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slugbugblue/trax",
3
- "version": "0.1.0",
3
+ "version": "0.4.0",
4
4
  "description": "Trax game engine and friends",
5
5
  "keywords": [
6
6
  "trax",
@@ -11,18 +11,20 @@
11
11
  "bugs": "https://gitlab.com/slugbugblue/trax/issues",
12
12
  "license": "Apache-2.0",
13
13
  "author": "Chad Transtrum <chad@transtrum.net>",
14
- "main": "engine.js",
14
+ "exports": "./engine.js",
15
15
  "repository": "gitlab:slugbugblue/trax",
16
16
  "scripts": {
17
- "test": "tape tests.js"
17
+ "test": "c8 tap --no-coverage",
18
+ "preversion": "npm test",
19
+ "postversion": "git push && git push --tags"
18
20
  },
19
21
  "devDependencies": {
20
- "eslint": "8.x",
21
- "tape": "5.x",
22
- "typescript": "^4.5.4",
23
- "typescript-language-server": "^0.8.1"
22
+ "c8": "^7.11.0",
23
+ "eslint": "^8.7.0",
24
+ "tap": "^15.1.6"
24
25
  },
26
+ "type": "module",
25
27
  "engines": {
26
- "node": ">=16.0.0"
28
+ "node": "^14.14.0 || >=16.0.0"
27
29
  }
28
30
  }
package/tty.js CHANGED
@@ -1,11 +1,11 @@
1
1
  /* Copyright 2021 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.
5
5
  * You may obtain a copy of the License at
6
- *
6
+ *
7
7
  * http://www.apache.org/licenses/LICENSE-2.0
8
- *
8
+ *
9
9
  * Unless required by applicable law or agreed to in writing, software
10
10
  * distributed under the License is distributed on an "AS IS" BASIS,
11
11
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -13,17 +13,12 @@
13
13
  * limitations under the License.
14
14
  */
15
15
 
16
- // Console functions for Trax
16
+ // Ever wanted to draw Trax boards on the console? I have.
17
17
 
18
- /* eslint-disable no-console */
19
-
20
- const Trax = require('./engine')
21
-
22
- // fix Javascript's broken typeof
23
- const TypeOf = o => ({}).toString.call(o).slice(8,-1)
18
+ import Trax from './engine.js'
24
19
 
25
20
  const esc = '\x1b[' // start of console color escape code
26
- const off = esc + 'm'
21
+ const off = esc + 'm' // color reset code
27
22
 
28
23
  // display a message with optional color
29
24
  const out = (message, fgcolor, bgcolor) => {
@@ -34,34 +29,44 @@ const out = (message, fgcolor, bgcolor) => {
34
29
  console.log(ansi + message + off)
35
30
  }
36
31
 
32
+ let set = 'trax' // which color set should we draw with?
37
33
 
38
34
  // a little magic to display the game in progress
39
35
  const color_sets = { // Colors for terminal sequence: <Esc>[38;5;<color_num>m
36
+ // b, B, w, W: color of line for each player, capital for a winning line
37
+ // e: empty (ie, board) color, t: tile color
40
38
  trax: { b: 238, B: 57, e: 19, t: 74, w: 255, W: 184 },
41
39
  trax8: { b: 236, B: 21, e: 58, t: 137, w: 254, W: 117 },
42
40
  traxloop: { b: 235, B: 20, e: 17, t: 61, w: 253, W: 222 },
41
+ tentative: { b: 235, B: 18, e: 19, t: 244, w: 252, W: 217 },
43
42
  }
44
43
 
44
+ const ln = 'm\u2584' // lower half block -- with the leading m to terminate ansi color escape
45
+ const up = 'm\u2588' // full block
46
+ const lt = 'm\u2599' // lower half and left half block
47
+ const rt = 'm\u259f' // lower half and right half block
48
+ const fg = n => '38;5;' + String(set[n]) // foreground color_num
49
+ const bg = n => '48;5;' + String(set[n]) // background color_num
50
+ const sp = () => esc + bg('e') + 'm ' // lower space
51
+ // tiles are displayed on two lines; the top half of the first line is empty
52
+ // and the second half is composed of three characters, up color surrounded by tile color
53
+ const uptty = clr => esc + fg('t') + ';' + bg('e') + ln
54
+ + esc + fg(clr) + ln + esc + fg('t') + ln + ' '
55
+ // the top half of the second line has both horizontal lines with a center transition
56
+ // 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) => esc + fg(clr) + (char === up ? '' : ';' + bg(oppclr)) + char
59
+ // list of icon_char => tile line colors for easy reference in order up, right, down, left
60
+ const tiles = { a: 'bbww', b: 'bwbw', c: 'bwwb', d: 'wbbw', e: 'wbwb', f: 'wwbb' }
61
+
45
62
  // return the ANSI codes to display a 3x1.5 block tile, one line at a time
46
- const tile_tty = (set, tile, top) => { // top: true for first line, false for second
47
- const ln = 'm\u2584' // lower half block -- with the leading m to terminate a terminal color escape sequence
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
54
- // tiles are displayed on two lines; the top half of the first line is empty
55
- // and the second half is composed of three characters, up color surrounded by tile color
56
- const uptty = clr => esc + fg('t') + ';' + bg('e') + ln + esc + fg(clr) + ln + esc + fg('t') + ln + ' '
57
- // the top half of the second line has both horizontal lines with a center transition
58
- // while the bottom half is the tile background color surrounding the down color
59
- const horiztty = clr => esc + fg('t') + ';' + bg(clr) + ln
60
- const dntty = (clr, oppclr, char) => esc + fg(clr) + (char === up ? '' : ';' + bg(oppclr)) + char
61
- // list of icon_char => tile line colors for easy reference in order up, right, down, left
62
- const tiles = { a: 'bbww', b: 'bwbw', c: 'bwwb', d: 'wbbw', e: 'wbwb', f: 'wwbb' }
63
+ const tile_tty = (tile, top) => { // top: true for first line, false for second
63
64
  // quick access to tile color and its opposite in upper case
64
- const colors = { b: 'b', w: 'w', B: 'w', W: 'b' }
65
+ let colors = { b: 'b', w: 'w', B: 'w', W: 'b' }
66
+ let save = set
67
+ let tentative = tile !== tile.toLowerCase()
68
+ if (tentative) set = color_sets.tentative
69
+ tile = tile.toLowerCase()
65
70
  if (!(tile in tiles)) { // winning paths are represented by adding 6 to the tile char code
66
71
  tile = String.fromCharCode(tile.charCodeAt(0) - 6)
67
72
  if (tile in tiles) {
@@ -73,19 +78,30 @@ const tile_tty = (set, tile, top) => { // top: true for first line, false for se
73
78
  colors.W = 'B'
74
79
  }
75
80
  }
81
+ let val = false
76
82
  if (tile in tiles) {
77
83
  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
84
+ if (top) {
85
+ val = uptty(colors[tile[0]])
86
+ } else {
87
+ let center_char = tile[1] === tile[2] ? rt : tile[2] === tile[3] ? lt : up
88
+ let centertty = dntty(colors[tile[2]], colors[tile[2].toUpperCase()], center_char)
89
+ val = horiztty(colors[tile[3]]) + centertty + horiztty(colors[tile[1]]) + sp()
90
+ }
82
91
  }
83
- if (top) return '??? '
84
- return '\'\'\' '
92
+ set = save
93
+ return val || (top ? '??? ' : '\'\'\' ')
94
+ }
95
+
96
+ const merge_tentative = () => {
97
+ color_sets.tentative.e = set.e // copy empty color to tentative set
98
+ color_sets.tentative.W = set.W
99
+ color_sets.tentative.B = set.B
85
100
  }
86
101
 
87
102
  const print_icon = (icon, set_name) => {
88
- let set = color_sets[set_name] || color_sets.trax
103
+ set = color_sets[set_name] || color_sets.trax
104
+ merge_tentative(set)
89
105
  const on = esc + '38;5;' + String(set.t) + ';48;5;' + String(set.e) + 'm'
90
106
  let o1 = ' '
91
107
  let o2 = '1 '
@@ -110,8 +126,8 @@ const print_icon = (icon, set_name) => {
110
126
  o2 += ' '.repeat(empty * 4)
111
127
  line_width += empty
112
128
  } else {
113
- o1 += tile_tty(set, c, 'top')
114
- o2 += tile_tty(set, c)
129
+ o1 += tile_tty(c, 'top')
130
+ o2 += tile_tty(c)
115
131
  line_width++
116
132
  }
117
133
  }
@@ -144,7 +160,7 @@ const trax_game = (game='trax', moves) => {
144
160
  if (game instanceof Trax) {
145
161
  trax = game
146
162
  if (moves) {
147
- if (TypeOf(moves) === 'String') moves = moves.split(' ')
163
+ if (typeof moves === 'string') moves = moves.split(' ')
148
164
  moves.forEach(move => trax.drop_tile(move))
149
165
  }
150
166
  } else {
@@ -153,23 +169,33 @@ const trax_game = (game='trax', moves) => {
153
169
  return trax
154
170
  }
155
171
 
156
- const display_game = (game='trax', moves='') => {
172
+ const display_game = (game='trax', moves='', tentative) => {
157
173
  let trax = trax_game(game, moves)
174
+ let icon = trax.icon
175
+ if (tentative) {
176
+ let drops = trax.drop_tile(tentative)
177
+ if (drops.valid) {
178
+ icon = trax.tentative_icon(drops)
179
+ } else {
180
+ tentative = false
181
+ }
182
+ }
158
183
  console.info(trax.notation)
159
- print_icon(trax.icon, trax.rules)
160
- let name = {0: 'nobody', 1: 'white', 2: 'black'}[trax.turn]
184
+ print_icon(icon, trax.rules)
185
+ if (tentative) console.info('--- tentative position ---')
186
+ let name = {1: 'white', 2: 'black'}[trax.turn]
161
187
  if (trax.over) {
162
188
  if (trax.turn) {
163
189
  console.info(name + ' wins')
164
190
  } else {
165
- console.info('game ended in a draw')
191
+ console.info('game end' + (tentative ? 's' : 'ed') + ' in a draw')
166
192
  }
167
193
  } else if (trax.turn) {
168
- console.info(name +' to play')
194
+ console.info((tentative ? 'leaving ' : '') + name +' to play')
169
195
  }
170
196
  }
171
197
 
172
- module.exports = {
198
+ export default {
173
199
  display: display_game,
174
200
  game: trax_game,
175
201
  out,