{"id":20885718,"url":"https://github.com/sabakihq/immutable-gametree","last_synced_at":"2025-06-30T19:33:56.398Z","repository":{"id":55007216,"uuid":"140971505","full_name":"SabakiHQ/immutable-gametree","owner":"SabakiHQ","description":"An immutable game tree data type.","archived":false,"fork":false,"pushed_at":"2021-07-09T01:01:37.000Z","size":178,"stargazers_count":23,"open_issues_count":0,"forks_count":10,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-26T18:08:34.236Z","etag":null,"topics":["baduk","board-game","data-type","go","immutable","sgf","tree","weiqi"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SabakiHQ.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"custom":"https://www.paypal.me/yishn/5"}},"created_at":"2018-07-14T19:10:47.000Z","updated_at":"2025-04-03T20:17:42.000Z","dependencies_parsed_at":"2022-08-14T08:50:52.748Z","dependency_job_id":null,"html_url":"https://github.com/SabakiHQ/immutable-gametree","commit_stats":null,"previous_names":["sabakihq/gametree"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/SabakiHQ/immutable-gametree","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SabakiHQ%2Fimmutable-gametree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SabakiHQ%2Fimmutable-gametree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SabakiHQ%2Fimmutable-gametree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SabakiHQ%2Fimmutable-gametree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SabakiHQ","download_url":"https://codeload.github.com/SabakiHQ/immutable-gametree/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SabakiHQ%2Fimmutable-gametree/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262838695,"owners_count":23372575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["baduk","board-game","data-type","go","immutable","sgf","tree","weiqi"],"created_at":"2024-11-18T08:14:17.890Z","updated_at":"2025-06-30T19:33:56.313Z","avatar_url":"https://github.com/SabakiHQ.png","language":"JavaScript","funding_links":["https://www.paypal.me/yishn/5"],"categories":[],"sub_categories":[],"readme":"# @sabaki/immutable-gametree [![Build Status](https://github.com/SabakiHQ/immutable-gametree/workflows/CI/badge.svg?branch=master)](https://github.com/SabakiHQ/immutable-gametree/actions)\n\nAn immutable game tree data type.\n\n## Installation\n\nUse npm to install:\n\n```\n$ npm install @sabaki/immutable-gametree\n```\n\n## Usage\n\n```js\nconst GameTree = require('@sabaki/immutable-gametree')\n\nlet tree = new GameTree()\n\nlet newTree = tree.mutate(draft =\u003e {\n  let id1 = draft.appendNode(draft.root.id, {B: ['dd']})\n  let id2 = draft.appendNode(id1, {W: ['dq']})\n\n  draft.addToProperty(id2, 'W', 'qd')\n})\n\nconsole.log(newTree !== tree)\n// =\u003e true\nconsole.log(tree.root.children.length)\n// =\u003e 0\nconsole.log(newTree.root.children.length)\n// =\u003e 1\nconsole.log(newTree.root.children[0].children[0].data.W)\n// =\u003e ['dq', 'qd']\n```\n\n## API\n\n### Node Object\n\nA node is represented by an object of the following form:\n\n```js\n{\n  id: \u003cPrimitive\u003e,\n  data: {\n    [property: \u003cString\u003e]: \u003cArray\u003cPrimitive\u003e\u003e\n  },\n  parentId: \u003cPrimitive\u003e | null,\n  children: \u003cArray\u003cNodeObject\u003e\u003e\n}\n```\n\n### Currents Object\n\nA node can have a distinguished child. You can specify the distinguished\nchildren of nodes with an object of the following form:\n\n```js\n{\n  [id: \u003cPrimitive\u003e]: \u003cPrimitive\u003e\n}\n```\n\nEvery value is the id of the distinguished child of the node with its key as id.\nIf the currents object doesn't specify a distinguished child for a node, the\ndefault will be the first child index-wise.\n\n---\n\n### `class GameTree`\n\n#### `new GameTree([options])`\n\n- `options` `\u003cObject\u003e` _(optional)_\n  - `getId` `\u003cFunction\u003e` _(optional)_\n  - `merger` `\u003cFunction\u003e` _(optional)_\n  - `root` [`\u003cNodeObject\u003e`](#node-object) _(optional)_\n\n#### `tree.getId`\n\n`\u003cFunction\u003e` - The `getId` function will be called to get an id for each\nappended node. It should return a primitive value which is unique for each call.\nDefaults to a simple counter.\n\nThis property will be inherited across mutations.\n\n#### `tree.merger`\n\n`\u003cFunction\u003e` - When appending a new node during mutations, you can instruct your\n`GameTree` to automatically merge your new data into an existing node when\ndesired. The merger function has the following signature:\n\n```js\n(node: \u003cNodeObject\u003e, data: \u003cObject\u003e) -\u003e \u003cObject\u003e | null\n```\n\nwhere `node` is a merge candidate and `data` the data to be appended. Return\n`null` if you do not want `data` to be merged into the existing `node`. Return\nan object (representing the merged data) if you want `node` to get that data\ninstead (and no new nodes are going to be appended).\n\nThis property will be inherited across mutations.\n\n#### `tree.root`\n\n[`\u003cNodeObject\u003e`](#node-object) - The root node.\n\n#### `tree.get(id)`\n\n- `id` `\u003cPrimitive\u003e`\n\nSearches the whole tree for a node with the specified id and returns a\n[node object](#node-object). If a node with the specified id doesn't exist, it\nwill return `null`. Each instance of `GameTree` will maintain a cache.\n\nPlease refrain from mutating the returned object to ensure immutability.\n\n#### `*tree.getSequence(id)`\n\nA generator function that yields [node objects](#node-objects), starting with\nthe node of the given `id` and continuing with its children until we reach a\ndescendant which has multiple or no children.\n\n#### `tree.mutate(mutator)`\n\n- `mutator` `\u003cFunction\u003e`\n\nThe `mutator` will be called with a [`Draft`](#class-draft) class. In the\n`mutator` function you will apply all your changes to the draft. Returns a new\n`GameTree` instance with the changes you applied to the draft, without changing\nthe original `GameTree` instance.\n\nWe use structural sharing to make mutations fairly efficient.\n\n#### `tree.navigate(id, step, currents)`\n\n- `id` `\u003cPrimitive\u003e`\n- `step` `\u003cInteger\u003e`\n- `currents` [`\u003cCurrentsObject\u003e`](#currents-object)\n\nStarts at the node with the given `id`, takes the specified `step` forward or\nbackward with respect to `currents`, and returns the node at the new position.\n\n#### `*tree.listNodes()`\n\nA generator function that yields all the nodes as [node objects](#node-object)\nof the game tree.\n\n#### `*tree.listNodesHorizontally(startId, step)`\n\n- `startId` `\u003cPrimitive\u003e`\n- `step` `\u003cInteger\u003e` - `1` or `-1`\n\nA generator function that yields the nodes as [node objects](#node-object) of\nthe game tree by walking horizontally along the game tree (left if `step` is\n`-1`, otherwise right) starting at the node with id `startId`.\n\n#### `*tree.listNodesVertically(startId, step, currents)`\n\n- `startId` `\u003cPrimitive\u003e`\n- `step` `\u003cInteger\u003e` - `1` or `-1`\n- `currents` [`\u003cCurrentsObject\u003e`](#currents-object)\n\nA generator function that yields the nodes as [node objects](#node-object) of\nthe game tree by walking vertically along given `currents` (up if `step` is\n`-1`, otherwise down) starting at the node with id `startId`.\n\n#### `*tree.listCurrentNodes(currents)`\n\n- `currents` [`\u003cCurrentsObject\u003e`](#currents-object)\n\nEquivalent to `tree.listNodesVertically(tree.root.id, 1, currents)`.\n\n#### `*tree.listMainNodes()`\n\nEquivalent to `tree.listCurrentNodes({})`.\n\n#### `tree.getLevel(id)`\n\n- `id` `\u003cPrimitive\u003e`\n\nReturns an integer denoting the level of the node with the given `id`. If node\ndoesn't exist, it will return `null`.\n\n#### `*tree.getSection(level)`\n\n- `level` `\u003cInteger\u003e`\n\nA generator function that yields all nodes of the given `level`.\n\n#### `tree.getCurrentHeight(currents)`\n\n- `currents` [`\u003cCurrentsObject\u003e`](#currents-object)\n\nEquivalent to `[...tree.listCurrentNodes(currents)].length`.\n\n#### `tree.getHeight()`\n\nCalculates and returns the height of the tree as an integer. This value will be\ncached across mutations when possible.\n\n#### `tree.getHash()`\n\nCalculates and returns a hash of the whole tree as a string. This value will\nbe cached.\n\n#### `tree.getStructureHash()`\n\nCalculates and returns a hash of the tree structure as a string. This value will\nbe cached across mutations when possible.\n\n#### `tree.onCurrentLine(id, currents)`\n\n- `id` `\u003cPrimitive\u003e`\n- `currents` [`\u003cCurrentsObject\u003e`](#currents-object)\n\nReturns whether the node with the given `id` is the root node or a distinguished\ndescendant of the root node with respect to `currents`.\n\n#### `tree.onMainLine(id)`\n\n- `id` `\u003cPrimitive\u003e`\n\nEquivalent to `tree.onCurrentLine(id, {})`.\n\n#### `tree.toJSON()`\n\nReturns `tree.root`.\n\n---\n\n### `class Draft`\n\n#### `draft.root`\n\nSee [tree.root](#treeroot).\n\n#### `draft.get(id)`\n\n- `id` `\u003cPrimitive\u003e`\n\nSee [tree.get(id)](#treegetid).\n\n#### `draft.appendNode(parentId, data[, options])`\n\n- `parentId` `\u003cPrimitive\u003e`\n- `data` `\u003cObject\u003e`\n- `options` `\u003cObject\u003e` _(optional)_\n  - `disableMerging` `\u003cBoolean\u003e` - Default: `false`\n\nAppends a new node with the given `data` to the node with id `parentId`. Returns\n`null` if operation has failed, otherwise the id of the new node. If\n`disableMerging` is set to `true`, automatic merging via\n[`tree.merger`](#treemerger) will be disabled.\n\n#### `draft.UNSAFE_appendNodeWithId(parentId, id, data[, options])`\n\n- `parentId` `\u003cPrimitive\u003e`\n- `id` `\u003cPrimitive\u003e`\n- `data` `\u003cObject\u003e`\n- `options` `\u003cObject\u003e` _(optional)_ - See\n  [`draft.appendNode`](#draftappendnodeparentid-data-options)\n\nAppends a new node with the given `id` and `data` to the node with id\n`parentId`. Returns `false` if operation has failed, otherwise `true`.\n\nMake sure the `id` provided does not already exist in the tree and that the\n`getId` function will never return `id`. We won't do any checks for you.\n\n#### `draft.removeNode(id)`\n\n- `id` `\u003cPrimitive\u003e`\n\nRemoves the node with given `id`. Throws an error if specified `id` represents\nthe root node. Returns `false` if operation has failed, otherwise `true`.\n\n#### `draft.shiftNode(id, direction)`\n\n- `id` `\u003cPrimitive\u003e`\n- `direction` `\u003cString\u003e` - One of `'left'`, `'right'`, `'main'`\n\nChanges the position of the node with the given `id` in the children array of\nits parent node. If `direction` is `'main'`, the node will be shifted to the\nfirst position. Returns `null` if operation has failed, otherwise the new index.\n\n#### `draft.makeRoot(id)`\n\n- `id` `\u003cPrimitive\u003e`\n\nMakes the node with the given `id` the root node of the mutated tree. Returns\n`false` if operation has failed, otherwise `true`.\n\n#### `draft.addToProperty(id, property, value)`\n\n- `id` `\u003cPrimitive\u003e`\n- `property` `\u003cString\u003e`\n- `value` `\u003cPrimitive\u003e`\n\nAdds the given `value` to the specified `property` of the node with the given\n`id`. Ignores duplicate values. If data doesn't include the given `property`, it\nwill add it. Returns `false` if operation has failed, otherwise `true`.\n\n#### `draft.removeFromProperty(id, property, value)`\n\n- `id` `\u003cPrimitive\u003e`\n- `property` `\u003cString\u003e`\n- `value` `\u003cPrimitive\u003e`\n\nRemoves the given `value` from the specified `property` of the node with the\ngiven `id`. If property list gets empty, the property key will be removed from\ndata. Returns `false` if operation has failed, otherwise `true`.\n\n#### `draft.updateProperty(id, property, values)`\n\n- `id` `\u003cPrimitive\u003e`\n- `property` `\u003cString\u003e`\n- `values` `\u003cArray\u003cPrimitive\u003e\u003e`\n\nSets the specified `property` of the node with the given `id` as `values`.\nRefrain from mutating `values` to ensure immutability. Returns `false` if\noperation has failed, otherwise `true`.\n\n#### `draft.removeProperty(id, property)`\n\n- `id` `\u003cPrimitive\u003e`\n- `property` `\u003cString\u003e`\n\nRemoves the specified `property` from the node. Returns `false` if operation has\nfailed, otherwise `true`.\n\n## Related\n\n- [crdt-gametree](https://github.com/SabakiHQ/crdt-gametree) - An immutable,\n  conflict-free replicated game tree data type.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsabakihq%2Fimmutable-gametree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsabakihq%2Fimmutable-gametree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsabakihq%2Fimmutable-gametree/lists"}