Git Product home page Git Product logo

ostinato's Introduction

Ostinato A chess library that runs on the server (Scala), on the browser (ScalaJS) and on the REPL.

Build Status GitHub license

Blogpost

Best introduction to the library with code examples in JavaScript, Scala and in the REPL.

Chess Game Parser (using ChessBoardJS)

Tool 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.

Chess Game (using ChessBoardJS)

Play 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.

Chess Game Notation Converter

Convert any pasted chess match in any known notation to any other known notation.

Chess Auto-play (using ChessBoardJS)

Two AI's playing each other (making random moves).

Scaladoc

Features

  • Supporting the following Chess notations (with variants): PGN, Algebraic, Figurine, Coordinate, Descriptive, ICCF, Smith and FEN
  • Random and Basic AI available based on minimax with alpha-beta pruning
  • Compiled for JVM and JS => can serve as backend or in the frontend
  • Fully stateless design => thread-safe & scalable; no shared mutable state & no side-effects
  • Functional design; no nulls or exception handling x Object Oriented design with inheritance but no downcasting
  • ostinato.js on the front-end, Akka HTTP Server on the back-end
  • Docker Hub image available; Kubernetes deployment, service and auto-scaling examples on the blogpost

Importing a ChessBoard

$ sbt pack && scala -cp target/pack/lib/ostinato_2.12-1.0.2.jar

scala> import ostinato.chess.core._
import ostinato.chess.core._

// Import a chessboard by drawing it
scala> val game = ChessGame.fromGridString(
        """........
          |........
          |........
          |...♜....
          |........
          |........
          |........
          |........""".stripMargin, turn = BlackChessPlayer).get


// Get available actions (note that the board keeps track of turns; 0 actions for white here!)
scala> val actions = game.board.actions
actions: Set[ostinato.chess.core.ChessAction] = Set(Black's Rook moves to a5, Black's Rook moves to d2, 
Black's Rook moves to d3, ...)

// Print them out! 
scala> actions map game.board.doAction foreach (b => println(b.get + "\n"))
// -> Shows on console (outlined horizontally for brevity)
...♜....    ........    ........    ........    ........    ........    ........
........    ........    ........    ........    ........    ........    ........
........    ...♜....    ........    ........    ........    ........    ........
........    ........    .♜......    ♜.......    .....♜..    ........    ....♜...
........    ........    ........    ........    ........    ........    ........
........    ........    ........    ........    ........    ........    ........
........    ........    ........    ........    ........    ...♜....    ........
........    ........    ........    ........    ........    ........    ........

........    ........    ........    ........    ........    ........    ........
........    ...♜....    ........    ........    ........    ........    ........
........    ........    ........    ........    ........    ........    ........
........    ........    .......♜    ......♜.    ..♜.....    ........    ........
........    ........    ........    ........    ........    ........    ...♜....
........    ........    ........    ........    ........    ...♜....    ........
........    ........    ........    ........    ........    ........    ........
...♜....    ........    ........    ........    ........    ........    ........

Importing a game from any known notation, without specifying which one

it("should parse the same game in different notations") {
      val pgn =
        Notation.parseMatchString("""[Event "Ostinato Testing"]
                                    |[Site "Buenos Aires, Argentina"]
                                    |[Date "2015.??.??"]
                                    |[Round "1"]
                                    |[Result "½–½"]
                                    |[White "Fake Player 1"]
                                    |[Black "Fake Player 2"]
                                    |
                                    |1. e4 e6 2. d4 d5 3. Nc3 Bb4 4. Bb5+ Bd7 5. Bxd7+ Qxd7 6. Nge2
                                    |dxe4 7. 0-0
                                    |""".stripMargin)

      val algebraic =
        Notation.parseMatchString("""e4 e6
                                    |d4 d5
                                    |Nc3 Bb4
                                    |Bb5+ Bd7
                                    |Bxd7+ Qxd7
                                    |Nge2 dxe4
                                    |0-0""".stripMargin)

      val figurine =
        Notation.parseMatchString("""e4 e6
                                    |d4 d5
                                    |♘c3 ♝b4
                                    |♗b5+ ♝d7
                                    |♗xd7+ ♛xd7
                                    |♘ge2 dxe4
                                    |0-0""".stripMargin)

      val coordinate =
        Notation.parseMatchString("""
                                    |1. e2-e4 e7-e6
                                    |2. d2-d4 d7-d5
                                    |3. b1-c3 f8-b4
                                    |4. f1-b5+ c8-d7
                                    |5. b5xd7+ d8xd7
                                    |6. g1-e2 d5xe4
                                    |7. 0-0""".stripMargin)

      val descriptive =
        Notation.parseMatchString("""
                                    |1. P-K4 P-K3
                                    |2. P-Q4 P-Q4
                                    |3. N-QB3 B-N5
                                    |4. B-N5ch B-Q2
                                    |5. BxBch QxB
                                    |6. KN-K2 PxP
                                    |7. 0-0
                                    |""".stripMargin)

      val iccf =
        Notation.parseMatchString("""
                                    |1. 5254 5756
                                    |2. 4244 4745
                                    |3. 2133 6824
                                    |4. 6125 3847
                                    |5. 2547 4847
                                    |6. 7152 4554
                                    |7. 5171""".stripMargin)

      val smith =
        Notation.parseMatchString("""
                                    |1. e2e4  e7e6
                                    |2. d2d4  d7d5
                                    |3. b1c3  f8b4
                                    |4. f1b5  c8d7
                                    |5. b5d7b d8d7b
                                    |6. g1e2  d5e4p
                                    |7. e1g1c""".stripMargin)

      Set(pgn, algebraic, figurine, coordinate, descriptive, iccf, smith) foreach {
        case Right(parsedGame) ⇒
          parsedGame.size shouldBe 13
          parsedGame.last shouldBe (CastlingAction.whiteKingside(), ChessGame.fromString(
            """♜♞..♚.♞♜
              |♟♟♟♛.♟♟♟
              |....♟...
              |........
              |.♝.♙♟...
              |..♘.....
              |♙♙♙.♘♙♙♙
              |♖.♗♕.♖♔.""".stripMargin, turn = BlackChessPlayer, castlingAvailable = castlingOnlyBlackAvailable,
            fullMoveNumber = 7, halfMoveClock = 1
          ).board)
        case _ ⇒
          fail
      }
    }

Some illustrative tests

  • En Passant
    it("should not find en passant take move for black pawn, since king would be threatened") {
      val game = ChessGame.fromGridString(
        """....♖...
          |........
          |...↓....
          |...♙♟...
          |........
          |........
          |....♚...
          |........""".stripMargin).get

      game.whitePlayer.pawns.head.actions(game.board).size shouldBe 1
    }
  • Algebraic Notation
CastlingAction.blackQueenside().toAn shouldBe "0-0-0"
TakeMovement(♝(XY(1, 3), WhiteChessPlayer), XY(1, -1), ♞(XY(2, 2), BlackChessPlayer)).toAn shouldBe "Bxc6"
  • FEN Notation importing/exporting
    it("should encode this board") {
      ChessGame.fromGridString(
        """.......♔
          |........
          |♚.♙.....
          |.......♟
          |........
          |........
          |........
          |........""".stripMargin).get.board.toFen shouldBe "7K/8/k1P5/7p/8/8/8/8"
    }
    it("should decode a chess setup with black en passant in FEN Notation") {
      ChessGame.fromFen("rnbqkbnr/p1pppppp/8/1p6/8/8/PPPPPPPP/RNBQKBNR w KQkq b6 4 5") shouldBe
      Some(ChessGame.fromGridString(
      """♜♞♝♛♚♝♞♜
        |♟.♟♟♟♟♟♟
        |.↓......
        |.♟......
        |........
        |........
        |♙♙♙♙♙♙♙♙
        |♖♘♗♕♔♗♘♖""".stripMargin, turn = WhiteChessPlayer, castlingFullyAvailable, 5, 4).get)
    }    

ostinato's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

ostinato's Issues

Excellent moves (!!) are not parsed correctly

The good move regex matches excellent moves, so when an excellent move is parsed, it's rendered with only one exclamation mark. Thus, an excellent move is never parsed properly.

Implement configuration changes to enable creating an NPM package

The ostinato project has been quite successful, in that:

  • it's the #1 Google Result for "chess notation converter"
  • it's gotten the attention of American Chess Magazine
  • it's being used as a converter for re-editing books in older notations
  • it's been featured as the #1 article on Scala Times #163

However, it hasn't reached the JS community, mostly because it's not very approachable.

In order to make it attractive for the JS community, two things are necessary:

  • Getting started documentation
  • The library should be packaged using NPM, so that it can be easily added to any project.

I would really appreciate some help with configuring the project such that the release would construct and publish an NPM package. Thank you!

Support full move numbers without dots (old Descriptive)

Currently, this format is parsed properly:

1. g4+  hxg4+ 
2. Kg3  Kg6 
3. Kxg4 Kh6 
4. Kf5  Kh5 
5. Ke5  Kxh4 
6. Kd5  Kg5 
7. Kc5  Kf6 
8. Kxb5 Ke7 
9. Kc6  Kd8 
10. Kb7 Kd7 
11. b5 

This one also works

g4+  hxg4+ 
Kg3  Kg6 
Kxg4 Kh6 
Kf5  Kh5 
Ke5  Kxh4 
Kd5  Kg5 
Kc5  Kf6 
Kxb5 Ke7 
Kc6  Kd8 
Kb7 Kd7 
b5 

But this one fails

1 g4+  hxg4+ 
2 Kg3  Kg6 
3 Kxg4 Kh6 
4 Kf5  Kh5 
5 Ke5  Kxh4 
6 Kd5  Kg5 
7 Kc5  Kf6 
8 Kxb5 Ke7 
9 Kc6  Kd8 
10 Kb7 Kd7 
11 b5 

Old Descriptive format sometimes omits the dots. Since Descriptive -> Algebraic is the most common use case for the converter, it should support this format.

En passant is supported, but Algebraic & Descriptive parser/converter fail to parse it

The enPassant method in AlgebraicNotationActionSerialiser and DescriptiveNotationActionSerialiser currently doesn't output e.p., so even though the library fully supports en passant movements and captures, the parser and converter don't understand or display them.

This should be a trivial fix. Waiting for some test cases to implement the fix.

dblch and dis.ch are parsed, but are never returned by the converter

Currently, the converter to Algebraic and Descriptive notation never render dblch or dis.ch in case of discovered or double checks.

Both support understanding input games that contain those modifiers, but don't output them back.

The underlying issue is that the mechanism for handling checks is boolean (e.g. isKingThreatened) rather than a count of checks.

The fix is relatively trivial if one knows where to change things:

ChessPiece calculates isThreatened using nonEmpty. There should be an extra method to count threatens. isThreatened is the biggest bottleneck in the library, so it shouldn't be made slower.

  def isThreatened(board: ChessBoard)(implicit opts: ChessOptimisations =
                                        ChessOptimisations.default): Boolean =
    threatenedBy(board).nonEmpty

ChessBoard calculates if a ChessAction is a check or not:

        val check = opts.checkForThreatens && m.fromPiece.enemy
          .kingPiece(newBoard)
          .exists(_.isThreatened(newBoard))

It should also calculate if it is a dblch or a dis.ch. In order to calculate dblch, it's enough to check if the "count of threatens" is >1. In order to calculate dis.ch, we need to know which piece is causing the threat to the King, and it will be true if the piece is not the fromPiece from the current ChessAction.

Does not convert LAN to SAN correctly

From Behting study, output from stockfish

image

f5g7
h5g5
e5f3
g5g4
d5e4
h4h3
g7f5
g2g1q
f3g1
h3h2
f5h6
g4h5
g1f3
h2h1q
h6f5
h5g4
f5e3
g4g3
e3f5
g3f2
f5d4
h1h7
e4d5
h7c7
f3e5
c7a5
d5e4
a5a2
e5g4
f2e1
d4f3
e1e2
g4e5
a2a6
e4d5
a6b5
d5d4
e2f2
d4e4
b5b7
e4d4
f2g3
d4c4
g3f4
c4c5
b7a6
c5d5
a6a5
d5d6
a5d8
d6c5
f4e4
d2d3
e4f4
c5c6
d8f6
c6d5
f6b6
d3d4
b6b3
d5d6
b3a3
d6e6
a3a6
e6d5
a6b6
f3h4
b6d8
d5e6
d8d4
e5g6
f4e4
e6e7
d4a7
e7f6
a7f2
f6e6
f2e3
e6e7
e3e2
e7f7
e2f1
f7e7
e4d5
h4g2
f1a1
g2f4
d5e4
f4e6
a1a7
e7f6
a7f2
f6e7
e4f5
g6f8
f2e3
e7d6
e3d3
d6c6
d3d2
e6c5
d2h6
c5e6
f5e5
c6c7
h6e3
c7c6
e3f3
c6d7
f3f7
d7c6
f7e8
c6c7

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.