The object representing the state of the game
The object representing a move in the game
Extra data used in evaluation not suitable for storing in the gamestate
Root to start the
Control the behaviour of the negamax search
Search options.
Protected
expiredTracks whether a searched has expired due to time
Protected
expireTime the search will expire. 0 disables expiry
Protected
fullFlags that full depth has not been reached when set to false
Protected
presortEnables presort of children internally
Protected
outcomesNumber of leaf or depth = 0 reached during current call
Protected
nodeFlag set if node limit has been reached
Callback to evaluate the value of gamestate attached to node
Original root when tree was created
Active root used for the creation and searching of tree. Set to root in constructor.
Number of nodes in tree
Number of leaf nodes in tree
Protected
activeMaximum depth of current search
Callback to get the moves for a gamestate attached to a Node.
Callback to create a child of a parent node using a move
Protected
evalSearches the tree up to a certain depth. Returns after reaching depth, or early if tree is complete.
The result of the search
Override the depth parameter set in opts
Protected
negamaxImplements the negamax algorithm up to a given depth of search.
Recursively calls itself until depth
is 0
or LEAF node is reached and evaluated.
Node values are then passed back up the tree according to the NodeAim of the parent nodes.
The colour
arguments alternates between 1
and -1
, as the node aim alternates between MAX and MIN.
This changes the sign of the value applied to node depending on the aim of the parent.
Selection of the best child can then be done based on the maximum node value.
Returns early if timeout occurs
false
if time expired during search, true
if search should continue
Node to evaluate or search children
Depth to search from this node
minimum guaranteed score
Best guaranteed path
maximum guaranteed score from minimising player
Best guaranteed path for minimising player
Protected
assignAssigns value to node using eval function. Tracks outcomes count and fulldepth
true
if node is a leaf
Protected
sortProtected
negamax_Same as negamax but optimised for best performing options.
Runs when optimal = true
Runs as:
true
true
false
Protected
evalSearches the tree repeatedly, incrementing the depth each time. Returns after reaching target depth, or early if tree is complete or time exceeded.
The result of the search
Search the tree according to the options specified by the options (opts).
Result from the tree search
Called during deepening search between each depth. Does nothing until overidden
Protected
checkProtected
checkProtected
getReturns either the array of node children or a generator which may create children on the fly Also presorts children if enabled
An iterable for going through children of node
Node to return an iterable of children
Protected
randomSelects from the nodes children which all have an equal best value (and depth) Uniform random weighting Node.child must already represent best child
randomly selected best child with same value as best
Parent to select best child from
Protected
randomProtected
routeProtected
optimalFunction to return the list of moves starting from the root that are the 'optimal' path.
Must be called after evaluating the tree.
A list of moves from the active root Node that represent the optimal sequence through the game.
Protected
removeProtected
createProtected
createCreates the full game tree starting from activeRoot.
Uses the GetMoves and CreateChildNode callbacks.
Protected
childGenerated using TypeDoc
For deterministic zero-sum 2 player games with alternating turns and full game knowledge. Can be configured to do depth based, time based and deepening searches, with a number of tuning options.
For an example implementation, see the mancala game and required callback function and the usage.
For configuring the search options, see the NegamaxOpts page.
Usage
To search the game tree from a game state involves creating a root Node, creating the Negamax tree with the root, attaching callbacks and then evaluating.
Setting up
The 3rd argument
0
is the move used to get to this node but the value does not matter as it is the root node. The 4th is the data, not used here, so set to 0.The 5th argument must be either MAX or MIN, depending on whether the player taking the next turn is trying to minimise or maximise the score.
Evaluating / searching
Search options must be configured as required, then the evaluate function can be called. See NegamaxOpts for details on the options.
The result is of type SearchResult and the best move and value can be acquired from it, as well as other information about the search process.
Implementing callbacks
In the following implementations, the (made-up) concrete types of the 3 generic types are:
Gamestate
number
number
(always 0, unused)GetMoves
The GetMoves callback returns a reference to a list of moves. It may have to generate this list if not done during the CreateChildNode callback.
The moves list and contained moves are never modified by the search, and must not be modified in any of the other callbacks.
CreateChildNode
The CreateChildNode callback as a few impnodeortant responsibilites:
Passing a NodeAim argument to the child node constructor is not required and will be ignored by the negamax algorithm. The actual NodeAim is dervied internally from the root NodeAim.
The return type (Node<Gamestate, number, number>) is implied by the function type annotation.
If using the Node.data property, the CreateChildNode callback is also responsible for passing that data to the child node, with whatever modification/copying of data is required.
EvaluateNode
This callback receives a Node and evaluates its relative value based on the gamestate and any extra data included on the node.
This is highly game dependant, but may look something like this:
This function should not modify the node.
depthCallback
This callback is optional and a little different to the other 3. It is called on every depth of search when using the DEEPENING or TIME based search methods.
It passes the full tree (Negamax class instance) and the SearchResult for that depth as arguments. It is most useful for printing out the current state of the search during the iterative deepening process
Doing any long processes in this function will hold up the search, and it cannot update the DOM before the search has finished.
Advanced usage
Removing the GetMoves callback
The GetMoves callback can be omitted if the root node is initialised with an array of moves and if the CreateChildNode callback assigns a list of moves to the created child.
The may be useful if the game state already has a list of moves in the process of creating the child node, either from playing the previous move or by checking for an end condition.
Do not do this if extra computation is required to create a list of moves.
Removing the EvaluateNode callback
The EvaluateNode callback can be removed if the node value is directly assigned during the CreateChildNode callback. This requires first creating the node, then setting the value property value.
This should only be done if the evaluation is very quick e.g the difference between 2 scores on the gamestate.
Using the Node.data property
The data property is attached to the root Node and can then be passed through the tree via the CreateChildNode callback.
It is most useful for containing evaluation specific information that does not belong on the gamestate. An example is constant evaluation options that can be passed to the evaluation function. Otherwise these would have to be global variables.
It also does not have to be constant and could contain variables to help speed up the evaulation for complex games, by using information from previous turns.
The example below shows how a contant "options" object could be used: