schachnovelle 1.1.0 → 1.1.2
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/README.md +2 -4
- package/package.json +5 -1
- package/ui/Board.js +19 -17
- package/ui/Game.js +9 -8
- package/ui/WebPrinter.js +51 -0
- package/ui/index.js +27 -18
- package/ui/plainChalk.js +19 -0
- package/ui/web.js +53 -0
- package/ui/webPrompt.js +59 -0
- package/web.js +5 -0
package/README.md
CHANGED
|
@@ -4,8 +4,8 @@ _»Um Gottes Willen! Nicht!«_
|
|
|
4
4
|
[](https://gitlab.com/manegame/schachnovelle)
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
_[manusnijhoff.nl](https://manusnijhoff.nl)_,
|
|
7
8
|
_[Read the book](https://en.wikipedia.org/wiki/The_Royal_Game)_, _[Play me on lichess](https://lichess.org/@/manegame)_,
|
|
8
|
-
_[Check out my website](https://manusnijhoff.nl)_
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
## Installation
|
|
@@ -23,13 +23,11 @@ or npm
|
|
|
23
23
|
...And play!
|
|
24
24
|
|
|
25
25
|
## Features
|
|
26
|
-
There is no check detection yet. Play with your head! For now...
|
|
27
|
-
|
|
28
26
|
You can play against yourself or a friend on the same keyboard.
|
|
29
27
|
|
|
30
28
|
## Roadmap
|
|
31
29
|
|
|
32
|
-
I'
|
|
30
|
+
I'd love to connect this to the [lichess](https://lichess.org/) API, so you can play others from the comfort of the command line!
|
|
33
31
|
|
|
34
32
|
## Changelog
|
|
35
33
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "schachnovelle",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Chess to play on the command line",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.js",
|
|
8
|
+
"./web": "./web.js"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
11
|
"play": "node index.js",
|
|
8
12
|
"serve": "node server/index.js"
|
package/ui/Board.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
const Printer = require('./Printer')
|
|
1
|
+
const plainChalk = require('./plainChalk')
|
|
4
2
|
const Utils = require('./Utils')
|
|
5
3
|
|
|
6
4
|
const files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
|
|
@@ -20,7 +18,11 @@ class Board {
|
|
|
20
18
|
this.ranks = ranks
|
|
21
19
|
}
|
|
22
20
|
|
|
23
|
-
this.
|
|
21
|
+
this.chalk = options.chalk || plainChalk
|
|
22
|
+
if (!options.printer) {
|
|
23
|
+
throw new Error('Board requires a printer instance via options.printer')
|
|
24
|
+
}
|
|
25
|
+
this.printer = options.printer
|
|
24
26
|
this.utils = new Utils()
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -45,9 +47,9 @@ class Board {
|
|
|
45
47
|
})
|
|
46
48
|
if (this.options.mode === 'color') {
|
|
47
49
|
if (activeWhitePiece) {
|
|
48
|
-
return chalk` {whiteBright ${activeWhitePiece.piece}} `
|
|
50
|
+
return this.chalk` {whiteBright ${activeWhitePiece.piece}} `
|
|
49
51
|
} else if (activeBlackPiece) {
|
|
50
|
-
return chalk` {black ${activeBlackPiece.piece}} `
|
|
52
|
+
return this.chalk` {black ${activeBlackPiece.piece}} `
|
|
51
53
|
}
|
|
52
54
|
return ` `
|
|
53
55
|
} else if (this.options.mode === 'bare') {
|
|
@@ -56,15 +58,15 @@ class Board {
|
|
|
56
58
|
*/
|
|
57
59
|
if (activeWhitePiece) {
|
|
58
60
|
if (activeWhitePiece.type === 'King' && this.white.inCheck) {
|
|
59
|
-
return chalk`{red ${activeWhitePiece.piece}} `
|
|
61
|
+
return this.chalk`{red ${activeWhitePiece.piece}} `
|
|
60
62
|
} else {
|
|
61
|
-
return chalk`{black ${activeWhitePiece.piece}} `
|
|
63
|
+
return this.chalk`{black ${activeWhitePiece.piece}} `
|
|
62
64
|
}
|
|
63
65
|
} else if (activeBlackPiece) {
|
|
64
66
|
if (activeBlackPiece.type === 'King' && this.black.inCheck) {
|
|
65
|
-
return chalk`{red ${activeBlackPiece.piece}} `
|
|
67
|
+
return this.chalk`{red ${activeBlackPiece.piece}} `
|
|
66
68
|
} else {
|
|
67
|
-
return chalk`{black ${activeBlackPiece.piece}} `
|
|
69
|
+
return this.chalk`{black ${activeBlackPiece.piece}} `
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
72
|
/**
|
|
@@ -74,9 +76,9 @@ class Board {
|
|
|
74
76
|
const rankParity = this.ranks.indexOf(rank) % 2 === 0 // the rank is odd
|
|
75
77
|
const fileParity = this.files.indexOf(file) % 2 === 0 // the file is odd
|
|
76
78
|
if (rankParity && fileParity || !rankParity && !fileParity) {
|
|
77
|
-
return chalk`{white .} `
|
|
79
|
+
return this.chalk`{white .} `
|
|
78
80
|
} else {
|
|
79
|
-
return chalk`{black .} `
|
|
81
|
+
return this.chalk`{black .} `
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -174,16 +176,16 @@ class Board {
|
|
|
174
176
|
// if rank is even, start with white
|
|
175
177
|
if (rank % 2 === 0) {
|
|
176
178
|
if (file % 2 === 0) {
|
|
177
|
-
renderRank += chalk.bgKeyword('darkgrey')(this.getPiece(this.files[file], this.ranks[rank]))
|
|
179
|
+
renderRank += this.chalk.bgKeyword('darkgrey')(this.getPiece(this.files[file], this.ranks[rank]))
|
|
178
180
|
} else {
|
|
179
|
-
renderRank += chalk.bgBlackBright(this.getPiece(this.files[file], this.ranks[rank]))
|
|
181
|
+
renderRank += this.chalk.bgBlackBright(this.getPiece(this.files[file], this.ranks[rank]))
|
|
180
182
|
}
|
|
181
183
|
renderRank = this.printer.addSpace('x', renderRank)
|
|
182
184
|
} else {
|
|
183
185
|
if (file % 2 === 0) {
|
|
184
|
-
renderRank += chalk.bgBlackBright(this.getPiece(this.files[file], this.ranks[rank]))
|
|
186
|
+
renderRank += this.chalk.bgBlackBright(this.getPiece(this.files[file], this.ranks[rank]))
|
|
185
187
|
} else {
|
|
186
|
-
renderRank += chalk.bgKeyword('darkgrey')(this.getPiece(this.files[file], this.ranks[rank]))
|
|
188
|
+
renderRank += this.chalk.bgKeyword('darkgrey')(this.getPiece(this.files[file], this.ranks[rank]))
|
|
187
189
|
}
|
|
188
190
|
renderRank = this.printer.addSpace('x', renderRank)
|
|
189
191
|
}
|
|
@@ -208,4 +210,4 @@ class Board {
|
|
|
208
210
|
}
|
|
209
211
|
}
|
|
210
212
|
|
|
211
|
-
module.exports = Board
|
|
213
|
+
module.exports = Board
|
package/ui/Game.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
const Side = require("./Side")
|
|
3
3
|
const Board = require("./Board")
|
|
4
|
-
const Printer = require("./Printer")
|
|
5
4
|
const Utils = require("./Utils")
|
|
6
5
|
|
|
7
|
-
const Enquirer = require("enquirer")
|
|
8
|
-
|
|
9
6
|
class Game {
|
|
10
7
|
constructor(options) {
|
|
8
|
+
if (!options || !options.enquirer || typeof options.enquirer.prompt !== 'function') {
|
|
9
|
+
throw new Error('Game requires an enquirer-like instance via options.enquirer')
|
|
10
|
+
}
|
|
11
|
+
|
|
11
12
|
this.white = new Side("white")
|
|
12
13
|
this.black = new Side("black")
|
|
13
14
|
this.playingAs = options.playingAs
|
|
@@ -20,14 +21,14 @@ class Game {
|
|
|
20
21
|
|
|
21
22
|
// Initialize the board
|
|
22
23
|
this.board = new Board(this.white, this.black, {
|
|
23
|
-
mode: "pieces", // possibly add text mode
|
|
24
|
+
mode: options.mode || "pieces", // possibly add text mode
|
|
24
25
|
orientation: this.playingAs,
|
|
25
26
|
spacing: options.spacing,
|
|
26
|
-
|
|
27
|
+
printer: options.printer,
|
|
28
|
+
chalk: options.chalk
|
|
27
29
|
})
|
|
28
30
|
|
|
29
|
-
this.enquirer =
|
|
30
|
-
this.printer = new Printer(options.spacing)
|
|
31
|
+
this.enquirer = options.enquirer
|
|
31
32
|
this.utils = new Utils()
|
|
32
33
|
}
|
|
33
34
|
|
package/ui/WebPrinter.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class WebPrinter {
|
|
2
|
+
constructor (spacing, io) {
|
|
3
|
+
this.spacing = spacing
|
|
4
|
+
this.io = io
|
|
5
|
+
this.tab = ' '
|
|
6
|
+
this.lineCount = 0
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
clearView () {
|
|
10
|
+
if (this.io && this.io.clear) {
|
|
11
|
+
this.io.clear()
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
clearForward () {}
|
|
16
|
+
|
|
17
|
+
printLine (value) {
|
|
18
|
+
if (this.io && this.io.write) {
|
|
19
|
+
this.io.write(value + '\n')
|
|
20
|
+
this.lineCount++
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
print (value) {
|
|
25
|
+
if (this.io && this.io.write) {
|
|
26
|
+
this.io.write(value)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addSpace (direction, string) {
|
|
31
|
+
if (direction === 'x') {
|
|
32
|
+
for (let i = 0; i < this.spacing; i++) {
|
|
33
|
+
string += ' '
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
for (let i = 0; i < this.spacing; i++) {
|
|
37
|
+
this.printLine('')
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
addSpaceBefore (string) {
|
|
44
|
+
for (let i = 0; i < this.spacing; i++) {
|
|
45
|
+
string = ' ' + string
|
|
46
|
+
}
|
|
47
|
+
return string
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = WebPrinter
|
package/ui/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
const spacing = 1
|
|
2
|
+
const Enquirer = require('enquirer')
|
|
2
3
|
const { Select } = require('enquirer')
|
|
4
|
+
const chalk = require('chalk')
|
|
3
5
|
|
|
4
6
|
const { spawn } = require('child_process')
|
|
5
7
|
|
|
@@ -7,6 +9,7 @@ const Game = require('./Game')
|
|
|
7
9
|
const Printer = require('./Printer')
|
|
8
10
|
|
|
9
11
|
const printer = new Printer(spacing)
|
|
12
|
+
const enquirer = new Enquirer()
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Welcome to Schachnovelle
|
|
@@ -19,6 +22,9 @@ exports.welcomeMessage = () => {
|
|
|
19
22
|
printer.addSpace('y')
|
|
20
23
|
printer.printLine(printer.addSpaceBefore(' >>Um Gottes Willen! Nicht!<<'))
|
|
21
24
|
printer.addSpace('y')
|
|
25
|
+
printer.printLine(printer.addSpaceBefore(' Public Service Announcement '))
|
|
26
|
+
printer.printLine(printer.addSpaceBefore(' I run best on a white terminal '))
|
|
27
|
+
printer.addSpace('y')
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
//
|
|
@@ -72,7 +78,10 @@ exports.localGame = () => {
|
|
|
72
78
|
const game = new Game({
|
|
73
79
|
playingAs: choices.color,
|
|
74
80
|
spacing,
|
|
75
|
-
mode: 'bare'
|
|
81
|
+
mode: 'bare',
|
|
82
|
+
enquirer,
|
|
83
|
+
printer,
|
|
84
|
+
chalk
|
|
76
85
|
})
|
|
77
86
|
|
|
78
87
|
printer.clearView()
|
|
@@ -91,25 +100,25 @@ exports.init = () => {
|
|
|
91
100
|
// Don't be shy, say hello to that person!
|
|
92
101
|
this.welcomeMessage()
|
|
93
102
|
|
|
103
|
+
this.localGame()
|
|
94
104
|
|
|
95
|
-
const what = new Select({
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
})
|
|
105
|
+
// const what = new Select({
|
|
106
|
+
// name: 'todo',
|
|
107
|
+
// message: 'How do you want to play?',
|
|
108
|
+
// choices: ['local', 'on lichess (beta)']
|
|
109
|
+
// })
|
|
100
110
|
|
|
101
|
-
what.run()
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
// what.run()
|
|
112
|
+
// .then((answer) => {
|
|
113
|
+
// printer.addSpace('y')
|
|
104
114
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
this.spawnServer()
|
|
115
|
+
// if (answer === 'local') {
|
|
116
|
+
// } else {
|
|
117
|
+
// this.spawnServer()
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
// printer.addSpace('y')
|
|
120
|
+
// printer.printLine(printer.addSpaceBefore(' Log in with Lichess to begin'))
|
|
121
|
+
// printer.addSpace('y')
|
|
122
|
+
// }
|
|
123
|
+
// })
|
|
115
124
|
}
|
package/ui/plainChalk.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const stripChalkMarkup = (value) => {
|
|
2
|
+
return value.replace(/\{[^}\s]*\s/g, '').replace(/\}/g, '')
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const plainChalk = (strings, ...values) => {
|
|
6
|
+
let output = ''
|
|
7
|
+
for (let i = 0; i < strings.length; i++) {
|
|
8
|
+
output += strings[i]
|
|
9
|
+
if (i < values.length) {
|
|
10
|
+
output += values[i]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return stripChalkMarkup(output)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
plainChalk.bgKeyword = () => (value) => stripChalkMarkup(value)
|
|
17
|
+
plainChalk.bgBlackBright = (value) => stripChalkMarkup(value)
|
|
18
|
+
|
|
19
|
+
module.exports = plainChalk
|
package/ui/web.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const Game = require('./Game')
|
|
2
|
+
const WebPrinter = require('./WebPrinter')
|
|
3
|
+
const createWebPrompt = require('./webPrompt')
|
|
4
|
+
const plainChalk = require('./plainChalk')
|
|
5
|
+
|
|
6
|
+
const spacing = 1
|
|
7
|
+
|
|
8
|
+
const welcomeMessage = (printer) => {
|
|
9
|
+
printer.addSpace('y')
|
|
10
|
+
printer.printLine(printer.addSpaceBefore('S . C . V . E'))
|
|
11
|
+
printer.printLine(printer.addSpaceBefore(' C A H O E L '))
|
|
12
|
+
printer.printLine(printer.addSpaceBefore('. H . N . L .'))
|
|
13
|
+
printer.addSpace('y')
|
|
14
|
+
printer.printLine(printer.addSpaceBefore(' >>Um Gottes Willen! Nicht!<<'))
|
|
15
|
+
printer.addSpace('y')
|
|
16
|
+
printer.printLine(printer.addSpaceBefore(' Public Service Announcement '))
|
|
17
|
+
printer.printLine(printer.addSpaceBefore(' I run best on a white terminal '))
|
|
18
|
+
printer.addSpace('y')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const createWebGame = (io, options = {}) => {
|
|
22
|
+
const prompt = createWebPrompt(io)
|
|
23
|
+
const printer = new WebPrinter(options.spacing || spacing, io)
|
|
24
|
+
|
|
25
|
+
welcomeMessage(printer)
|
|
26
|
+
|
|
27
|
+
prompt.prompt([{
|
|
28
|
+
type: 'select',
|
|
29
|
+
name: 'color',
|
|
30
|
+
message: 'Play as',
|
|
31
|
+
choices: ['white', 'black']
|
|
32
|
+
}]).then((answer) => {
|
|
33
|
+
const game = new Game({
|
|
34
|
+
playingAs: answer.color,
|
|
35
|
+
spacing: options.spacing || spacing,
|
|
36
|
+
mode: 'bare',
|
|
37
|
+
enquirer: prompt,
|
|
38
|
+
printer,
|
|
39
|
+
chalk: plainChalk
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
printer.clearView()
|
|
43
|
+
game.init()
|
|
44
|
+
}).catch((error) => {
|
|
45
|
+
if (io && io.write) {
|
|
46
|
+
io.write(`Error starting game: ${error?.message || error}\n`)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = {
|
|
52
|
+
createWebGame
|
|
53
|
+
}
|
package/ui/webPrompt.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const createWebPrompt = (io) => {
|
|
2
|
+
let pendingResolve = null
|
|
3
|
+
|
|
4
|
+
if (io && io.onLine) {
|
|
5
|
+
io.onLine((line) => {
|
|
6
|
+
if (pendingResolve) {
|
|
7
|
+
const resolve = pendingResolve
|
|
8
|
+
pendingResolve = null
|
|
9
|
+
resolve(line)
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ask = (question) => {
|
|
15
|
+
const choices = question.choices || []
|
|
16
|
+
io.write(`${question.message}\n`)
|
|
17
|
+
choices.forEach((choice, index) => {
|
|
18
|
+
io.write(` ${index + 1}) ${choice}\n`)
|
|
19
|
+
})
|
|
20
|
+
io.write('> ')
|
|
21
|
+
|
|
22
|
+
return new Promise((resolve) => {
|
|
23
|
+
const resolveChoice = (line) => {
|
|
24
|
+
const trimmed = `${line}`.trim()
|
|
25
|
+
let selection = choices.find(choice => choice === trimmed)
|
|
26
|
+
if (!selection) {
|
|
27
|
+
const index = Number(trimmed)
|
|
28
|
+
if (index >= 1 && index <= choices.length) {
|
|
29
|
+
selection = choices[index - 1]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!selection) {
|
|
34
|
+
io.write('Invalid choice, try again.\n')
|
|
35
|
+
io.write('> ')
|
|
36
|
+
pendingResolve = resolveChoice
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const result = question.result ? question.result(selection) : selection
|
|
41
|
+
resolve({ [question.name]: result })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
pendingResolve = resolveChoice
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
prompt: async (questions) => {
|
|
50
|
+
if (!Array.isArray(questions) || questions.length === 0) {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return ask(questions[0])
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = createWebPrompt
|