summaryrefslogtreecommitdiff
path: root/life2/game/index.js
AgeCommit message (Collapse)Author
2024-03-18Add stats calculation within the game loopNicolas Paul
This patch changes the behavior of the calculation loop of a board (nextState) to keep track of the changes that happened during the round (number of cells that died or were born in a given team). These stats may be accessed by called the getStats() method on a World instance, which returns a !Stats object: const world = Life2.World(...); // ... const stats = world.getStats(); displayStats(stats); Close: https://github.com/nc0fr/life2/issues/8 Signed-off-by: Nicolas Paul <n@nc0.fr>
2024-03-08Add World library facadeNicolas Paul
The World class is a Facade pattern which manages the main logic of the Life2 simulation game: iterating over the board given a set of rules. The World class mainly represents a single simulation. Signed-off-by: Nicolas Paul <n@nc0.fr>
2024-03-08Add RulesManager helperNicolas Paul
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>
2024-03-08game: Add Board abstractionNicolas Paul
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>
2024-03-08game: Define game RulesNicolas Paul
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>
2024-03-08game: Define the Cell typeNicolas Paul
A cell is the smallest unit in the Life2 simulation game. It carries information about its aliveness (whether or not it is filled), and the team it is part of (TEAM_A or TEAM_B). The cell type is currently implemented as an enumeration of integers, merging the two states in one value. I believe integers are the fastest type JavaScript runtimes can manipulate, especially at this small scale (unsigned 8-bit integers). Given these properties, I believe using integers will avoid huge performance and memory consumption in the browser when dealing with boards of larger sizes (or even pseudo infinite sizes...). Signed-off-by: Nicolas Paul <n@nc0.fr>