Actions & Reducers¶
The Redux State¶
You’ll see a state
parameter a lot. This is the Redux state, which
stores all the game state, as described in Architecture. It is
an Immutable.js Map with fields nodes
, globals
, board
,
toolbox
, and goal
.
Mutable vs Immutable Expressions¶
Two “types” of expressions exist in Reduct-Redux. Most of the time,
you will work with immutable expressions, which are Immutable.js Map
data structures, and are manipulated through methods like get
,
set
, and withMutations
. However, it’s a lot easier to build up
an AST as plain old JavaScript objects during parsing. Thus, there is
a conversion step needed, and some semantics functions have to work
with both types. (This should be mostly transparent to you.)
action.startLevel()
handles the conversion.
Working with Immutable.js¶
We can’t describe all of Immutable.js here, so please read the documentation for that library. However, the key things are:
Use
get
to get a field. Children are stored indirectly, so you’ll need a reference to the overall state to get access to children.// expr is a plain old JavaScript object console.log(expr.id); console.log(expr.child.value); // expr is an Immutable.js Map stored in Redux // state is the Redux store's state console.log(expr.get("id")); console.log(state.getIn([ "nodes", expr.get("id"), "value" ])); // getIn is a convenience function; here the use roughly // translates to state["nodes"][expr["id"]]["value"] if these // were all plain old JavaScript objects
Learn to use temporary variables and helpers like
getIn
.Use
set
to change a field. This does not modify the original object, instead you get a new copy of the object! Thus, if you’re modifying a nested object, you also have to re-set the field on the parent object.// Hypothetical example where node ID 5 has its value incremented const oldNode = nodes.get(5); nodes.set(5, oldNode.set("value", oldNode.get("value") + 1));
If you’re modifying a lot of fields on the same object, check out the
withMutations
method.
Expression Fields¶
To work with expressions, you’ll need to know what fields they have.
Every expression always has an id
field, as well as a field for
each of the fields and subexpressions defined in the semantics (see
Defining Expressions). Additionally, if the expression is a
child of another, it should have a parent
and parentField
. The
former is the ID of the parent expression, and the latter is the name
of the field in which this expression is stored on the parent.
Remember, fields storing child expressions always have numeric IDs, not objects, as values.
Holes are simply expressions of type "missing"
. This assumption is
unfortunately hard-coded (see Areas of Improvement). When a hole is
filled with an expression, the original hole is placed in a field
whose name is the original field name with __hole
. For example, if
the argument
field is a hole and is filled in, then the reducer
will add an argument__hole
field that contains the ID of the
original hole expression. (This is because the reducer doesn’t know
how to generate new expressions on the fly; consequently, you can’t
make a hole where there wasn’t one before, either.)
Notches are stored in fields named notch<n>
where <n>
is the
index of the notch. The expression stored in the notch will have its
parent
and parentField
set as normal.
Actions¶
import * as action from "./reducer/action";
import * as undo from "./reducer/undo";
-
action.
startLevel
(stage, goal, board, toolbox, globals)¶ Redux action to start a new level.
Takes trees of normal AST nodes and flattens them into immutable nodes, suitable to store in Redux. Also runs the semantics module’s postParse hook, if defined, and creates the initial views for these expressions.
Flattened trees are doubly-linked: children know their parent, and which parent field they are stored in.
Arguments: - stage (basestage.BaseStage) –
- goal (Array) – List of (mutable) expressions for the goal.
- board (Array) – List of (mutable) expressions for the board.
- toolbox (Array) – List of (mutable) expressions for the toolbox.
- globals (Object) – Map of (mutable) expressions for globals.
-
action.
victory
()¶ We’ve won the level.
Clear the board/goal, which has the side effect of stopping them from drawing anymore.
-
action.
smallStep
(nodeId, newNodeIds, newNodes)¶ Node
nodeId
took a small step to producenewNode
which containsnewNodes
as nested nodes. All of these are immutable nodes.
-
action.
betaReduce
(topNodeId, argNodeId, newNodeIds, addedNodes)¶ Node
topNodeId
was applied toargNodeId
to producenewNodeIds
which containaddedNodes
as nested nodes.A beta-reduction can produce multiple result nodes due to replicators.
-
action.
detach
(nodeId)¶ Detach the given node from its parent.
-
action.
fillHole
(holeId, childId)¶ Replace the given hole by the given expression.
-
action.
attachNotch
(parentId, notchIdx, childId, childNotchIdx)¶ Attach the child to the given parent through the given notches
-
action.
define
(name, id)¶ Define the given name as the given node ID.
-
action.
useToolbox
(nodeId, clonedNodeId, addedNodes)¶ Take the given node out of the toolbox.
-
action.
raise
(nodeId)¶ Raise the given node to the top.
This is a visual concern, but the stage draws nodes in the order they are in the store, so this changes the z-index. We could make the board an immutable.Set and store the draw-order elsewhere, but we would have to synchronize it with any changes to the store. I figured it was easier to just break separation in this case.
-
action.
unfold
(nodeId, newNodeId, addedNodes)¶ Unfold the definition of
nodeId
, producingnewNodeId
(and addingaddedNodes
to the store).
-
action.
unfade
(source, nodeId, newNodeId, addedNodes)¶ Replace a node with its unfaded variant temporarily.
-
action.
fade
(source, unfadedId, fadedId)¶ Replace an unfaded node with its faded variant.
-
undo.
undo
()¶ Undo the last action.
-
undo.
redo
()¶ Redo the last undone action.
-
undo.
undoable
(options)¶ Given a reducer, return a reducer that supports undo/redo.
Arguments: - options (Object) –
- options.restoreExtraState (function) – After an undo or redo, given the previous and new state, restore any state that lives outside of Redux. (For instance, positions of expressions on the board.)
- options.actionFilter (function) – Given an action, if true, then simply modify the state and do not add the previous state to the undo stack.
- options.extraState (function) – Given the previous and new state, record any state that lives outside of Redux.
Reducers¶
import * as reducer from "./reducer/reducer";
-
reducer.
nextId
()¶ Returns the next unique ID. Used to assign IDs to nodes and views.
-
reducer.
reduct
(restorePos)¶ The core reducer for Reduct-Redux. Interprets actions and generates the new state. Needs a semantics module and the views, in order to record positions of objects on the board for undo/redo.
Arguments: - restorePos (function) – a function that transforms the position of a view after undo/redo. Useful to adjust positions so that things stay within bounds (e.g. if the game has resized since the view’s position was recorded).