{"id":20639281,"url":"https://github.com/marianogappa/ostinato","last_synced_at":"2025-09-19T00:21:34.847Z","repository":{"id":141891049,"uuid":"45385184","full_name":"marianogappa/ostinato","owner":"marianogappa","description":"A chess library that runs on the server (Scala) and on the browser (ScalaJS).","archived":false,"fork":false,"pushed_at":"2019-08-25T19:32:14.000Z","size":2430,"stargazers_count":54,"open_issues_count":7,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-09T23:32:59.569Z","etag":null,"topics":["chess","chess-game","chess-notation","fen-notation","ostinato","scala","scalajs"],"latest_commit_sha":null,"homepage":"http://marianogappa.github.io/ostinato","language":"Scala","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/marianogappa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-11-02T09:35:53.000Z","updated_at":"2025-08-06T06:56:29.000Z","dependencies_parsed_at":"2024-01-05T21:47:47.757Z","dependency_job_id":null,"html_url":"https://github.com/marianogappa/ostinato","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/marianogappa/ostinato","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianogappa%2Fostinato","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianogappa%2Fostinato/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianogappa%2Fostinato/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianogappa%2Fostinato/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marianogappa","download_url":"https://codeload.github.com/marianogappa/ostinato/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marianogappa%2Fostinato/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":274743971,"owners_count":25341136,"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","status":"online","status_checked_at":"2025-09-12T02:00:09.324Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["chess","chess-game","chess-notation","fen-notation","ostinato","scala","scalajs"],"created_at":"2024-11-16T15:23:21.452Z","updated_at":"2025-09-19T00:21:34.815Z","avatar_url":"https://github.com/marianogappa.png","language":"Scala","funding_links":[],"categories":["Misc"],"sub_categories":[],"readme":"![Ostinato](ostinato.jpg?v=1)\nA chess library that runs on the server (Scala), on the browser (ScalaJS) and on the REPL.\n\n[![Build Status](https://img.shields.io/travis/marianogappa/ostinato.svg)](https://travis-ci.org/marianogappa/ostinato)\n[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/MarianoGappa/ostinato/master/LICENSE)\n\n[Blogpost](https://marianogappa.github.io/software/2017/03/24/ostinato-a-chess-engine-written-in-scala-that-runs-on-the-browser-docker-and-the-repl/)\n\nBest introduction to the library with code examples in JavaScript, Scala and in the REPL.\n\n[Chess Game Parser (using ChessBoardJS)](http://marianogappa.github.io/ostinato-examples/parser)\n\nTool to paste any chess match in any known notation and browse through the moves via Chessboard. Also play from any board state against the AI, or convert to any other notation.\n\n[Chess Game (using ChessBoardJS)](http://marianogappa.github.io/ostinato-examples/play)\n\nPlay a Chess Match against the AI. A hacker will be able to play from whatever board starting position, as white or black, configuring AI's strength and enabling debug mode.\n\n[Chess Game Notation Converter](http://marianogappa.github.io/ostinato-examples/convert)\n\nConvert any pasted chess match in any known notation to any other known notation.\n\n[Chess Auto-play (using ChessBoardJS)](http://marianogappa.github.io/ostinato-examples/autoplay)\n\nTwo AI's playing each other (making random moves).\n\n[Scaladoc](http://marianogappa.github.io/ostinato/docs)\n\n## Features\n\n- Supporting the following Chess notations (with variants): PGN, Algebraic, Figurine, Coordinate, Descriptive, ICCF, Smith and FEN\n- Random and Basic AI available based on minimax with alpha-beta pruning\n- Compiled for JVM and JS =\u003e can serve as backend or in the frontend\n- Fully stateless design =\u003e thread-safe \u0026 scalable; no shared mutable state \u0026 no side-effects\n- `Functional design; no nulls or exception handling` x `Object Oriented design with inheritance but no downcasting`\n- ostinato.js on the front-end, Akka HTTP Server on the back-end\n- [Docker Hub image](https://hub.docker.com/r/marianogappa/ostinato/) available; Kubernetes deployment, service and auto-scaling examples on the [blogpost](http://marianogappa.github.io/software/2017/03/23/ostinato-a-chess-engine-written-in-scala-that-runs-on-the-browser-docker-and-the-repl/)\n\n### Importing a ChessBoard\n```\n$ sbt pack \u0026\u0026 scala -cp target/pack/lib/ostinato_2.12-1.0.2.jar\n\nscala\u003e import ostinato.chess.core._\nimport ostinato.chess.core._\n\n// Import a chessboard by drawing it\nscala\u003e val game = ChessGame.fromGridString(\n        \"\"\"........\n          |........\n          |........\n          |...♜....\n          |........\n          |........\n          |........\n          |........\"\"\".stripMargin, turn = BlackChessPlayer).get\n\n\n// Get available actions (note that the board keeps track of turns; 0 actions for white here!)\nscala\u003e val actions = game.board.actions\nactions: Set[ostinato.chess.core.ChessAction] = Set(Black's Rook moves to a5, Black's Rook moves to d2, \nBlack's Rook moves to d3, ...)\n\n// Print them out! \nscala\u003e actions map game.board.doAction foreach (b =\u003e println(b.get + \"\\n\"))\n// -\u003e Shows on console (outlined horizontally for brevity)\n...♜....    ........    ........    ........    ........    ........    ........\n........    ........    ........    ........    ........    ........    ........\n........    ...♜....    ........    ........    ........    ........    ........\n........    ........    .♜......    ♜.......    .....♜..    ........    ....♜...\n........    ........    ........    ........    ........    ........    ........\n........    ........    ........    ........    ........    ........    ........\n........    ........    ........    ........    ........    ...♜....    ........\n........    ........    ........    ........    ........    ........    ........\n\n........    ........    ........    ........    ........    ........    ........\n........    ...♜....    ........    ........    ........    ........    ........\n........    ........    ........    ........    ........    ........    ........\n........    ........    .......♜    ......♜.    ..♜.....    ........    ........\n........    ........    ........    ........    ........    ........    ...♜....\n........    ........    ........    ........    ........    ...♜....    ........\n........    ........    ........    ........    ........    ........    ........\n...♜....    ........    ........    ........    ........    ........    ........\n```\n\n### Importing a game from any known notation, without specifying which one\n```\nit(\"should parse the same game in different notations\") {\n      val pgn =\n        Notation.parseMatchString(\"\"\"[Event \"Ostinato Testing\"]\n                                    |[Site \"Buenos Aires, Argentina\"]\n                                    |[Date \"2015.??.??\"]\n                                    |[Round \"1\"]\n                                    |[Result \"½–½\"]\n                                    |[White \"Fake Player 1\"]\n                                    |[Black \"Fake Player 2\"]\n                                    |\n                                    |1. e4 e6 2. d4 d5 3. Nc3 Bb4 4. Bb5+ Bd7 5. Bxd7+ Qxd7 6. Nge2\n                                    |dxe4 7. 0-0\n                                    |\"\"\".stripMargin)\n\n      val algebraic =\n        Notation.parseMatchString(\"\"\"e4 e6\n                                    |d4 d5\n                                    |Nc3 Bb4\n                                    |Bb5+ Bd7\n                                    |Bxd7+ Qxd7\n                                    |Nge2 dxe4\n                                    |0-0\"\"\".stripMargin)\n\n      val figurine =\n        Notation.parseMatchString(\"\"\"e4 e6\n                                    |d4 d5\n                                    |♘c3 ♝b4\n                                    |♗b5+ ♝d7\n                                    |♗xd7+ ♛xd7\n                                    |♘ge2 dxe4\n                                    |0-0\"\"\".stripMargin)\n\n      val coordinate =\n        Notation.parseMatchString(\"\"\"\n                                    |1. e2-e4 e7-e6\n                                    |2. d2-d4 d7-d5\n                                    |3. b1-c3 f8-b4\n                                    |4. f1-b5+ c8-d7\n                                    |5. b5xd7+ d8xd7\n                                    |6. g1-e2 d5xe4\n                                    |7. 0-0\"\"\".stripMargin)\n\n      val descriptive =\n        Notation.parseMatchString(\"\"\"\n                                    |1. P-K4 P-K3\n                                    |2. P-Q4 P-Q4\n                                    |3. N-QB3 B-N5\n                                    |4. B-N5ch B-Q2\n                                    |5. BxBch QxB\n                                    |6. KN-K2 PxP\n                                    |7. 0-0\n                                    |\"\"\".stripMargin)\n\n      val iccf =\n        Notation.parseMatchString(\"\"\"\n                                    |1. 5254 5756\n                                    |2. 4244 4745\n                                    |3. 2133 6824\n                                    |4. 6125 3847\n                                    |5. 2547 4847\n                                    |6. 7152 4554\n                                    |7. 5171\"\"\".stripMargin)\n\n      val smith =\n        Notation.parseMatchString(\"\"\"\n                                    |1. e2e4  e7e6\n                                    |2. d2d4  d7d5\n                                    |3. b1c3  f8b4\n                                    |4. f1b5  c8d7\n                                    |5. b5d7b d8d7b\n                                    |6. g1e2  d5e4p\n                                    |7. e1g1c\"\"\".stripMargin)\n\n      Set(pgn, algebraic, figurine, coordinate, descriptive, iccf, smith) foreach {\n        case Right(parsedGame) ⇒\n          parsedGame.size shouldBe 13\n          parsedGame.last shouldBe (CastlingAction.whiteKingside(), ChessGame.fromString(\n            \"\"\"♜♞..♚.♞♜\n              |♟♟♟♛.♟♟♟\n              |....♟...\n              |........\n              |.♝.♙♟...\n              |..♘.....\n              |♙♙♙.♘♙♙♙\n              |♖.♗♕.♖♔.\"\"\".stripMargin, turn = BlackChessPlayer, castlingAvailable = castlingOnlyBlackAvailable,\n            fullMoveNumber = 7, halfMoveClock = 1\n          ).board)\n        case _ ⇒\n          fail\n      }\n    }\n```\n\n### Some illustrative tests\n\n- En Passant\n```\n    it(\"should not find en passant take move for black pawn, since king would be threatened\") {\n      val game = ChessGame.fromGridString(\n        \"\"\"....♖...\n          |........\n          |...↓....\n          |...♙♟...\n          |........\n          |........\n          |....♚...\n          |........\"\"\".stripMargin).get\n\n      game.whitePlayer.pawns.head.actions(game.board).size shouldBe 1\n    }\n```\n\n- Algebraic Notation\n```\nCastlingAction.blackQueenside().toAn shouldBe \"0-0-0\"\nTakeMovement(♝(XY(1, 3), WhiteChessPlayer), XY(1, -1), ♞(XY(2, 2), BlackChessPlayer)).toAn shouldBe \"Bxc6\"\n```\n\n- FEN Notation importing/exporting\n```\n    it(\"should encode this board\") {\n      ChessGame.fromGridString(\n        \"\"\".......♔\n          |........\n          |♚.♙.....\n          |.......♟\n          |........\n          |........\n          |........\n          |........\"\"\".stripMargin).get.board.toFen shouldBe \"7K/8/k1P5/7p/8/8/8/8\"\n    }\n    it(\"should decode a chess setup with black en passant in FEN Notation\") {\n      ChessGame.fromFen(\"rnbqkbnr/p1pppppp/8/1p6/8/8/PPPPPPPP/RNBQKBNR w KQkq b6 4 5\") shouldBe\n      Some(ChessGame.fromGridString(\n      \"\"\"♜♞♝♛♚♝♞♜\n        |♟.♟♟♟♟♟♟\n        |.↓......\n        |.♟......\n        |........\n        |........\n        |♙♙♙♙♙♙♙♙\n        |♖♘♗♕♔♗♘♖\"\"\".stripMargin, turn = WhiteChessPlayer, castlingFullyAvailable, 5, 4).get)\n    }    \n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarianogappa%2Fostinato","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarianogappa%2Fostinato","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarianogappa%2Fostinato/lists"}