|
RulesManager is a utility class that has the responsability of storing
and managing a set of rules. Rules are identified by their name,
meaning that there cannot be two rules with the same names. However, no
check is done on the function content:
const func = (cell, _) => cell;
const rule1 = new Rule("foo", "", func);
const rule2 = new Rule("bar", "", func);
const rule3 = new Rule("foo", "", func);
const manager = new RulesManager();
manager.add(rule1); // OK
manager.add(rule2); // OK because "foo" != "bar"
manager.add(rule3); // Error("Rule 'foo' is already registered')
Also, the manager internally use a JavaScript Map object, meaning the
order returned when getting all registered rules is not promised to be
the same has the order of registration. This also means that the
performance and time complexity of the object is dependant on the
JavaScript engine (the ECMA Script specification only imposes a
datastructure where operations have a complexity better than O(n)).
I believe it is a non-issue as the manager should not keep a lot of
rules anyway.
The reason for the existance of the RulesManager class is to simplify
the facade that is the World class.
Signed-off-by: Nicolas Paul <n@nc0.fr>
|
|
The Board class is an abstraction over any 2D shaped board to allow the
use of any two dimensional shapes as a grid for the Life2 simulation.
This patch creates and exposes two types: the Board class, and a Grid
typedef.
The Board class is expected to be inherited by more concrete shaped
boards, such as classic "Rectangular" boards, or more complex "Triangle"
ones. Boards act as their own abstraction over their shape, with the
only requirement of implementing the small Board API defined by the
inherited class.
The Grid type serves as a encoded representation of the Board shape
using JavaScript Arrays and our defined Life2.Cell enumeration:
import {Cell} from "@life2/game";
/**
* @typedef {!Array<!Array<!Cell>>}
*/
let Grid;
The Grid structure can be though as a protocol.
The two combined can be represented as the following:
BOARD CODE
########## const grid = [[3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
###a.b.### [3, 3, 3, 1, 0, 2, 0, 3, 3, 3],
#.aab..ba# -> [3, 0, 1, 1, 2, 0, 0, 2, 1, 3],
###bb..### [3, 3, 3, 2, 2, 0, 0, 3, 3, 3],
########## [3, 3, 3, 3, 3, 3, 3, 3, 3, 3]];
Assuming:
* . = 0 = Cell.EMPTY
* a = 1 = Cell.TEAM_A
* b = 2 = Cell.TEAM_B
* # = 3 = Cell.BARRIER
The Grid representation could be obtained by calling the getGrid()
method, which can then be used by some user interface:
1. The simulator interface (our @life2/simulator)
2. A text format to share the grid state offline (our @life2/l2sf).
Signed-off-by: Nicolas Paul <n@nc0.fr>
|
|
Rules a simple mathematical fuctions that determine the next state of
the cells they are applied to. By their nature, rules either return a
new state, or do not apply.
Our implementation of rules allow modularity, where a rules either
return a state (Cell type) to notice a change, or nothing (the null
object) to notice no change.
The merit of such an implementation is that the last choice is
ultimately done by the board, which increases the flexibility of
downstream users.
For instance, a standard implementation of the game could determine the
final new state of a cell by reducing all the rules output on the given
cell and choosing the most occuring one:
const cell = ...;
const neighbors = board.getNeighbors(cell);
const states = rules.map(r => r.execute(cell, neighbords))
.filter(s => s != null);
const empty = states.filter(s => s === Cell.EMPTY)
.length;
const teamA = states.filter(s => s === Cell.TEAM_A)
.length;
const teamB = states.filter(s => s === Cell.TEAM_B)
.length;
const newState = Math.max(empty, teamA, teamB);
boad.changeCell(cell, newState);
Note that this is some fake code, the final API may not look like this.
Signed-off-by: Nicolas Paul <n@nc0.fr>
|