A simple Node.js application
The project implements a simple board game.
The game consists of a board and a robot.
The board is a n×n
grid, the bottom left corner is the 0x0 point.
The robot accepts commands to pivot (turn in place) to right and left, and move; it can be placed anywhere on the board.
The robot rejects moving outside the bounds of the board.
The interface to the game is textual, in what we call a gamescript, it context-free, case-sensitive and line delimited, the following ABNF-ish describes the overall script:
game-script = PLACE|MOVE|TURN|REPORT;
PLACE = "PLACE" POSITION ORIENTATION NL;
POSITION = 1*DIGIT 1*DIGIT ; two section of at least one digit
ORIENTATION = ("NORTH" | "SOUTH" | "EAST" | "WEST") NL;
MOVE = "MOVE" NL;
TURN = LEFT|RIGHT NL;
LEFT = "LEFT";
REPORT = "REPORT"
NL = LF ; newline is the linefeed (\n) not carrage return (\r)
The application is written in ES6 with flow-type and uses babel to compile to ES5. The code is linted with eslint
using an extended airbnb/javascript
configurations with slight modifications.
ES6 provides us with advance programming features and flow-type brings type safety to JavaScript and along with eslint
it elemints a huge class of bugs and errors at compile time.
The application is broken into non-interdependent functional components that provide the overall functionality required. The packages are further split into more fine grain ES6 modules.
Why?
Composability, for example, if one wants to port this game to browser, they can simply import the engine and parser without caring or depending to the cli interface.
Testability, breaking the application into simple components allows us more through unit testing.
Each components' main packages are put under an eponym directory as index.js
, with other packages under the same directory as package_name.js
.
The main three components are as following:
parser defines an AST and parser for gamescript
Engine contains the domain logic of the game, it implements a single board and robot system with a simple interface to issue commands to the robot; Engine is responsible for enforcing bound checks on move and other validation.
The cli module provides the interface for executing gamescripts from a file and an interactive mode to play the game by typing commands directly in an interactive shell.
As the name board
was taken on npmjs the commandline interface is called roboboard
, following is the help command output:
$ roboboard help
Commands:
help [command...] Provides help for a given command.
run <file> runs the provide gamescript
interactive runs the game engine in interactive mode
The run method is pretty simpl, you call roboboard run path/to/game/script
and it will run and print the output:
For example,
running roboboard run test/gamescript_2.txt
will yield:
Warning: Ignoring line 1. No robot placed
Info: Executing Instruction: PLACE 1,2,EAST: current state: 1, 2, EAST
Warning: Ignoring line 3. Robot already placed
Info: Executing Instruction: MOVE: current state: 2, 2, EAST
Info: Executing Instruction: LEFT: current state: 2, 2, NORTH
Info: Executing Instruction: MOVE: current state: 2, 3, NORTH
Info: Executing Instruction: MOVE: current state: 2, 4, NORTH
Info: Executing Instruction: MOVE: current state: 2, 5, NORTH
Warning: Ignoring line 9. Illegal move: out of bounds
Info: 2, 5, NORTH
The interactive mode accepts a slightly different syntax for convenience, you can see the details by running the help
command after entering interactive mode:
$ roboboard interactive
> help
Commands:
help [command...] Provides help for a given command.
exit Exits application.
place <x> <y> <o> runs the provide gamescript
move making a move
left turning left
right turning right
report prints the current state of the board
(^C again to quit)
>
Issuing commands works like this:
> place 1 2 east
Info: Command Executed
> move
Info: Command Executed
> left
Info: Command Executed
> move
Info: Command Executed
> right
Info: Command Executed
> move
Info: Command Executed
> re
3, 3, EAST
> place 0 0 north
Warning: Command Rejected: Robot already placed
> move
Info: Command Executed
> move
Info: Command Executed
> move
Warning: Command Rejected: Illegal move: out of bounds
> move
Warning: Command Rejected: Illegal move: out of bounds
> right
Info: Command Executed
> move
Info: Command Executed
> move
Info: Command Executed
> move
Info: Command Executed
> re
5, 0, SOUTH
> move
Warning: Command Rejected: Illegal move: out of bounds
Warning: the interactive mode is highly addictive.
To build the project, simply clone this repository and run yarn install
followed by yarn build
, you may use npm if you fancy.
You can install the roboboard game directly from npm registery by running yarn global add roboboard
or npm install -g roboboard
, if you don't have yarn installed.
Alternatively, you can install your local build by running npm install -g .
from the project directory after build.
We use jest
as a test runner and assertion library, it provides a much better out of the box experience and very little overhead for running tests.
Tests are collocated with packages as $package_name.test.js
, the tests gets compiled along the code and written in a way that they can be excuted under build
as well as src
, this provides as a mechanism to test the compiled case in case the compiled code creates any bugs.
To run tests execute yarn run test
and to test the compiled code run yarn run test-build
, of course, after running yarn run build
.
For other commands please see the package.json scripts section.
Add tests for CLI
MIT.