{"id":41675682,"url":"https://github.com/aprescott/tenuki","last_synced_at":"2026-01-24T18:12:03.257Z","repository":{"id":65515435,"uuid":"54915143","full_name":"aprescott/tenuki","owner":"aprescott","description":"Tenuki is a web-based go board and JavaScript library","archived":false,"fork":false,"pushed_at":"2022-12-19T05:45:58.000Z","size":795,"stargazers_count":122,"open_issues_count":10,"forks_count":26,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-10-26T06:38:47.994Z","etag":null,"topics":["baduk","board","board-game","game","go","html","igo","javascript","web","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/aprescott.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-03-28T18:44:13.000Z","updated_at":"2025-10-19T02:16:45.000Z","dependencies_parsed_at":"2023-01-29T20:45:55.379Z","dependency_job_id":null,"html_url":"https://github.com/aprescott/tenuki","commit_stats":null,"previous_names":["aprescott/tenuki.js"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/aprescott/tenuki","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aprescott%2Ftenuki","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aprescott%2Ftenuki/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aprescott%2Ftenuki/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aprescott%2Ftenuki/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aprescott","download_url":"https://codeload.github.com/aprescott/tenuki/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aprescott%2Ftenuki/sbom","scorecard":{"id":204281,"data":{"date":"2025-08-11","repo":{"name":"github.com/aprescott/tenuki","commit":"aeedb4cd39d73242e49490aea359118ea5a4df23"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.8,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 2/20 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v0.3.1 not signed: https://api.github.com/repos/aprescott/tenuki/releases/53955081","Warn: release artifact v0.3.1 does not have provenance: https://api.github.com/repos/aprescott/tenuki/releases/53955081"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 15 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T23:21:57.425Z","repository_id":65515435,"created_at":"2025-08-16T23:21:57.425Z","updated_at":"2025-08-16T23:21:57.425Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28733630,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T17:51:25.893Z","status":"ssl_error","status_checked_at":"2026-01-24T17:50:48.377Z","response_time":89,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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","board-game","game","go","html","igo","javascript","web","weiqi"],"created_at":"2026-01-24T18:12:03.160Z","updated_at":"2026-01-24T18:12:03.235Z","avatar_url":"https://github.com/aprescott.png","language":"JavaScript","readme":"[![Build Status](https://travis-ci.org/aprescott/tenuki.svg?branch=master)](https://travis-ci.org/aprescott/tenuki)\n\nTenuki is a web-based board and JavaScript library for the game of go/baduk/weiqi.\n\n_The API is still subject to change at any point. Treat it as beta software!_\n\nThere are two main pieces:\n\n1. **A JavaScript engine** representing the board, game, and rules.\n2. **A go board interface**.\n\nThe JavaScript engine is not dependent on the renderer and works stand-alone. You can use it by itself as part of a larger JavaScript application.\n\nThe go board interface is intended to be a robust, functional component that can be embedded in a web page. By using the JavaScript API you could then build your own custom controls for undo/pass/etc.\n\nThe game engine supports playing an entire game and has various features and configuration settings:\n\n  * Both Simple ko and superko rules.\n  * Handicap stones, with or without free placement.\n  * End-game functionality: dead stone marking and scoring.\n  * Different scoring rules: territory, area, equivalence (with pass stones).\n  * Komi.\n  * Seki detection for territory scoring rules.\n\nThe board UI also has its own features:\n\n  * A responsive UI for comfortably playing on touch devices and small screens.\n  * Automatic board resizing to fit the layout.\n  * Ko point markers.\n  * Optional coordinate markers for points A19 through T1.\n  * Optional fuzzy stone placement with collision movement animations.\n\n# Live examples\n\nFor live examples, see `examples/`, or view them on GitHub:\n\n* [`example_with_simple_controls.html`](https://aprescott.github.io/tenuki/examples/example_with_simple_controls.html) — Board with an example of simple custom controls and updating game info.\n* [`example_fuzzy_placement.html`](https://aprescott.github.io/tenuki/examples/example_fuzzy_placement.html) — Fuzzy stone placement.\n* [`example_with_simple_controls_and_gutter.html`](https://aprescott.github.io/tenuki/examples/example_with_simple_controls_and_gutter.html) — A19 to T1 coordinates in the margins.\n* [`example_multiboard.html`](https://aprescott.github.io/tenuki/examples/example_multiboard.html) — Multiple independent 9x9 boards on a single page.\n* [`example.html`](https://aprescott.github.io/tenuki/examples/example.html) — Just the board.\n\nThese examples are also set up to work on mobile/touch displays, because the board is set to fit within the browser window.\n\n# Installation\n\nIf you use npm, then `npm install tenuki`. Otherwise, you can download [source and built files from GitHub](https://github.com/aprescott/tenuki/releases).\n\nIt's also possible to clone this repo and run `make` against the latest commit, which will generate files in `build/` provided you have developer dependencies satisfied.\n\n# Simple usage\n\nCreate a new `tenuki.Game` instance, which displays the board itself and configures click handlers on each intersection:\n\n```html\n\u003clink rel=\"stylesheet\" href=\"build/tenuki.css\"\u003e\n\u003cscript src=\"build/tenuki.js\"\u003e\u003c/script\u003e\n\n\u003cdiv class=\"tenuki-board\"\u003e\u003c/div\u003e\n\n\u003cscript\u003e\n  var boardElement = document.querySelector(\".tenuki-board\");\n  var game = new tenuki.Game({ element: boardElement });\n\u003c/script\u003e\n```\n\nThere are no other dependencies.\n\n# Flat stone styling\n\nFor a completely flat board with no stone shadows or gradients, add the class `tenuki-board-flat`:\n\n```html\n\u003cdiv class=\"tenuki-board tenuki-board-flat\"\u003e\u003c/div\u003e\n```\n\n# Fuzzy stone placement\n\nFor fuzzy stone placement, pass `fuzzyStonePlacement: true` as a game option:\n\n```js\nnew tenuki.Game({\n  fuzzyStonePlacement: true\n});\n```\n\nWhen enabled, played stones will be randomly placed slightly off-center. If stones overlap after placement, existing stones are bumped out of the way.\n\n# Auto-scaling and responsiveness\n\nBy default, the `.tenuki-board` element will take up only the necessary amount of space in the document.\n\nIf your CSS causes the `.tenuki-board` HTML element to be set to a smaller size than it would ordinarily be, then the board will automatically shrink to fit within that size.\n\nSimilarly, if your CSS causes `.tenuki-board` to be larger than the board ordinarily would be, then the board will automatically expand to fit the container.\n\nFor example, let's say you'd like the board to be a width of 200px:\n\n```html\n\u003cstyle\u003e\n  .my-class {\n    width: 200px;\n  }\n\u003c/style\u003e\n\n\u003cdiv class=\"my-class tenuki-board\"\u003e\u003c/div\u003e\n```\n\nA full 19x19 board is usually larger than 200px. But, here, because of the `.my-class` styling, the entire board will automatically scale down to fit the 200px constraint.\n\nSimilarly, suppose the board is set to have a dynamic size based on the viewport:\n\n```html\n\u003cstyle\u003e\n  .my-class {\n    width: 50%; /* say this is 50% of the body */\n  }\n\u003c/style\u003e\n\n\u003cdiv class=\"my-class tenuki-board\"\u003e\u003c/div\u003e\n```\n\nNow the board will be sized dynamically. If the viewport changes, the board will resize.\n\n# Coordinate markers\n\nFor coordinate markers, indicating positions A19 through T1, add `data-include-coordinates=true` to the HTML element for the board:\n\n```html\n\u003cdiv class=\"tenuki-board\" data-include-coordinates=\"true\"\u003e\u003c/div\u003e\n```\n\n# SVG renderer\n\nThe default renderer uses SVG to display the board. If this is a problem, you can pass `renderer: \"dom\"` as a game setup option to switch to using a renderer based solely on DOM elements. This is _not recommended_. The all-DOM approach has worse performance and has alignment issues at browser zoom levels other than 100%, 200%, etc.\n\n# Board sizes other than 19x19\n\nYou can pass a `boardSize` option to specify the board size. If no size is given, the default of 19 is used. All sizes between 1x1 and 19x19 should work. Sizes above 19x19 will error and won't render.\n\n```js\nvar game = new tenuki.Game({\n  element: boardElement,\n  // use a 13x13 board\n  boardSize: 13\n});\n```\n\n# Handicap stones\n\nHandicap stones (2 through 9) are supported on sizes 9x9, 13x13 and 19x19.\n\n```js\nnew tenuki.Game({\n  handicapStones: 5\n});\n```\n\nBy default, handicap placement is fixed at the traditional star points. To allow free handicap placement, set `freeHandicapPlacement: true`:\n\n```js\nnew tenuki.Game({\n  handicapStones: 5,\n  freeHandicapPlacement: true\n});\n```\n\n# Configuring scoring\n\nThe default scoring method is territory scoring. The scoring rule is configured as a setup option:\n\n```js\nnew tenuki.Game({\n  scoring: \"area\" // default is \"territory\"\n});\n```\n\nValid scoring types are:\n\n  * `\"area\"`\n  * `\"territory\"`\n  * `\"equivalence\"`\n\n## Area scoring\n\nThe score for each player under area scoring is the sum of two values:\n\n* The number of stones on the board.\n* The number of points of territory.\n\nEyes in seki count as territory under area scoring.\n\n## Territory scoring\n\nThe score for each player under territory scoring is the number of points of territory, plus opponent stones you captured.\n\nEyes in seki _do not_ count as points of territory.\n\nWhen territory scoring is in use, a simple detection algorithm attempts to correctly ignore each of the following as not-territory:\n\n1. Neutral points, consisting of intersections surrounded by neither player.\n2. Intersections which would be the point of capture for a group in atari, after filling in neutral points.\n3. Eyes in seki.\n\nCounting eyes in seki relies on a way of determining whether a group of stones is in seki. Tenuki detects seki by counting eyes, after filling in other neutral points.\n\nThe more neutral points exist on the board, the more likely it is that seki detection will fail in some way.\n\n_It is strongly recommended that you fill in all neutral points before passing at the end of a game._\n\n## Equivalence scoring\n\nAn explanation of equivalence scoring can be found at the [Sensei's Library Wiki](http://senseis.xmp.net/?EquivalenceScoring), and a longer explanation of the equivalence can be found in a [commentary appendix to the AGA Rules](https://www.cs.cmu.edu/~wjh/go/rules/AGA.commentary.html).\n\nIn short, equivalence scoring implements pass stones, plus the requirement that white make one final pass prior to scoring, which makes the score from counting by area equivalent to the score from counting by territory.\n\nNote that using equivalence scoring does _not_ change how the game ends. The game will end with 2 consecutive passes, even if black makes the 2nd pass. The final white pass stone handed to black is implemented in Tenuki by the `game.score()` function, not a move by a player.\n\n# Komi\n\nThe default komi value is 0. To alter the value of white's score, specify `komi` as an option:\n\n```js\nnew tenuki.Game({\n  komi: 6.5\n});\n```\n\nKomi is not automatically chosen based on the scoring type.\n\n# Ko and superko\n\nThe default ko rule is the simple variant: immediately recreating the previous board position is not allowed. Superko is also supported with the `koRule` configuration option:\n\n```js\nnew tenuki.Game({\n  koRule: \"positional-superko\" // default is \"simple\"\n})\n```\n\nValid ko rule values are:\n\n* `\"simple\"` — Immediately recreating the previous board position is illegal.\n* `\"positional-superko\"` — Recreating _any_ previous position is illegal.\n* `\"situational-superko\"` — Is it illegal for a player to recreate any previous position which that same player was responsible for creating. This is like positional superko, but takes into account the creator of the position.\n* `\"natural-situational-superko\"` — The same as situational superko, except a player may place a stone to recreate a previous position, provided that previous position was created by a pass. This is like natural situational superko, but distinguishes between passes and board plays. More details can be found at [Sensei's Library](http://senseis.xmp.net/?NaturalSituationalSuperko).\n\n# Usage outside of a browser\n\nThe full browser environment is not required in order to use the representation of the game in JavaScript. For example, if you have a node app, you can simply create a new game without passing an element:\n\n```js\nvar Game = require(\"tenuki\").Game;\ngame = new Game();\n```\n\nFrom there, the JavaScript interface is the same as in a browser console:\n\n```js\ngame.intersectionAt(0, 0).value;\n// 'empty'\ngame.currentPlayer();\n// 'black'\ngame.isOver();\n// false\ngame.playAt(0, 0);\n// true\ngame.intersectionAt(0, 0).value;\n// 'black'\n```\n\n# Game play functions\n\nThere are functions are available on a `Game` object that can be used to control the gameplay.\n\nNote that all functions which take two integer coordinates (`y` and `x`) are measured from the top of the board and left of the board. So `y = 0` is the top-most row, and `x = 0` is the left-most row. On a 19x19 board, the top left star point (4-4) is thus at `y = 3` and `x = 3`.\n\n* `pass()`: passes for the current player.\n* `playAt(y, x)`: attempts to play a stone at `(y, x)` for the current player. If the move is illegal (because of ko, suicide, etc.), then nothing will happen. Returns `true` if the move is successful, otherwise `false`.\n* `isOver()`: returns `true` if the most recent 2 moves were passes, indicating the game is over, otherwise `false`.\n* `markDeadAt(y, x)`, `unmarkDeadAt(y, x)` and `toggleDeadAt(y, x)`: set the group of stones at `(y, x)` to dead or undead as part of marking territory. Only useful if the game is over.\n* `score()` returns scoring information, e.g., `{ black: 150, white: 130 }`. Only useful if `isOver()` is `true`, since proper scoring requires dead stone marking at the end of the game. Scoring is dependent on the scoring rules in use.\n* `undo()`: undo the most recent move.\n\nWhen rendering to a HTML board element, changing the game state will re-render the board. `pass`, `playAt`, `markDeadAt`, `unmarkDeadAt` and `toggleDeadAt` all support `{ render: false }` as an option, which will skip the board rendering step. To manually render the board with `render: false`, call `game.render()` explicitly.\n\n# Post-render callbacks\n\nThere is a configurable callback, `postRender`, which is fired each time the board is rendered, e.g., after every move.\n\nThis is useful if you want to update some other state:\n\n```js\nvar game = new tenuki.Game(boardElement);\n\ngame.callbacks.postRender = function(game) {\n  if (game.currentState().pass) {\n    console.log(game.currentState().color + \" passed\");\n  }\n\n  if (game.currentState().playedPoint) {\n    console.log(game.currentState().color + \" played \" + game.currentState().playedPoint.y + \",\" + game.currentState().playedPoint.x);\n  }\n};\n```\n\n# Running tests\n\nRun tests with `npm test`.\n\n# Developing\n\nFirst, make sure [`npm`](https://www.npmjs.com/) is installed, then run:\n\n```shell\nnpm install \u0026\u0026 make\n```\n\nTo make changes, update individual files in `src/` and `scss/`. Then, run `make` to generate files in `build/`.\n\nTo test changes, use `npm test` and load `test.html` in a browser. You can also smoke test the examples in `examples/`.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faprescott%2Ftenuki","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faprescott%2Ftenuki","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faprescott%2Ftenuki/lists"}