GithubHelp home page GithubHelp logo

alexobviously / bishop Goto Github PK

View Code? Open in Web Editor NEW
17.0 2.0 7.0 1.19 MB

A chess logic package for Dart with flexible variant support

Home Page: https://pub.dev/packages/bishop

License: Other

Dart 100.00%
chess dart variant xiangqi board-game

bishop's Introduction

Bishop

A chess logic package with flexible variant support.

Bishop is designed with flexibility in mind. The goal is to be able to build and play any arbitrary variant of chess without having to build a new package every time. It supports a variety of fairy piece variants, with pieces that move in unconventional ways, and piece definition with Betza notation. It also supports variants with altered rules, such as King of the Hill and Atomic chess, Asian games like Xiangqi, and variants with various implementations of hands and gating like Crazyhouse, Seirawan and Musketeer. It's also possible to implement fairly complex custom logic with the actions system.

Of course, it also supports standard chess.

Bishop is written in pure dart with no dependencies.

Take a look at the Squares package for a Flutter chessboard widget designed to be interoperable with Bishop.

Feel free to request variants or rules - just create an issue.

Contents

Feature Overview

Basic Functionality

Move Generation

Piece Defintion

Variant Defintion

Regions

Actions

JSON Serialisation


Features

  • Game logic - making moves, detecting end conditions, etc
  • Legal move generation
  • FEN & PGN input and output
  • Easy and flexible variant definition
  • Importing and exporting variants as JSON for portability
  • Different board sizes
  • Fairy pieces
    • Arbitrary move configuration
    • Betza parsing
    • Hoppers like the Xiangqi Cannon
  • Hands & dropping
  • Versatile promotion handling, supporting cases like optional promotion, piece limits and pieces with different options
  • Powerful action system with an accessible API for building custom game logic, for example:
    • Exploding pieces like Atomic chess
    • Spawning pieces on the board, moving existing pieces around
    • Invalidating moves based on arbitrary conditions, e.g. Xiangqi's flying generals rule
  • Flex gating (e.g. Seirawan)
  • Fixed gating (e.g. Musketeer)
  • Forced captures (e.g. Antichess)
  • Different game end conditions for variants like Three-Check, Racing Kings or King of the Hill
  • Board regions for piece movement restriction (e.g. Xiangqi palaces), piece behaviour changes (e.g. Xiangqi soldier) and win conditions (e.g. King of the Hill)
  • A basic engine that works with any definable variant (not strong but fine for testing)

Planned Features

  • Janggi, Shogi and their variants
  • Draughts, and chained moves in general
  • Support for Bughouse, and similar games
  • Chasing rules, such as those Xiangqi has
  • Pieces with multi-leg moves
  • Variants with multiple moves per turn (e.g. double move, Duck Chess)
  • A lot more: see the issue tracker and feel free to submit your own requests

Built-in Variants

There are over 50 built-in variants in total.
Chess, Chess960, Crazyhouse, Atomic, Horde, Racing Kings, Antichess, Capablanca, Grand, Seirawan, Three Check, King of the Hill, Musketeer, Xiangqi (+ Mini Xiangqi & Manchu), Three Kings, Kinglet, Hoppel-Poppel, Orda (+ Mirror), Shako, Dobutsu, Andernach, Jeson Mor, a variety of small board variants, and many more.


A Random Game

Playing a random game is easy!

final game = Game();

while (!game.gameOver) {
    game.makeRandomMove();
}
print(game.ascii());
print(game.pgn());

Move Generation

Get a list of legal moves, formatted in SAN notation:

Game game = Game(variant: Variant.grand());
List<Move> moves = game.generateLegalMoves();
print(moves.map((e) => g.toSan(e)).toList());

Pick a move with algebraic notation, and play it:

Game game = Game();
Move? m = g.getMove('e2e4')!; // returns null if the move isn't found
bool result = game.makeMove(m); // returns false if the move is invalid

Start a game from an arbitrary position

Game game = Game(fen: 'rnbq1bnr/ppppkppp/8/4p3/4P3/8/PPPPKPPP/RNBQ1BNR w - - 2 3');

Piece Definition

Fairy piece types can be easily defined using Betza notation, for example the Amazon can be defined like: PieceType amazon = PieceType.fromBetza('QN');. More complicated pieces such as Musketeer Chess's Fortress can also be easily configured: PieceType fortress = PieceType.fromBetza('B3vND').

If you're feeling particularly adventurous, you can also define a List<MoveDefinition> manually and build a PieceType with the default constructor.

There are lots of examples in piece_type.dart to learn from, but here's a basic rundown.

Movement Atoms

Single capital letters represent a basic movement direction. Here are the basic directions:

G Z C H C Z G     W = Wazir     (1,0)
Z A N D N A Z     F = Ferz      (1,1)
C N F W F N C     D = Dabbaba   (2,0)
H D W * W D H     N = Knight    (2,1)
C N F W F N C     A = Alfil     (2,2)
Z A N D N A Z     C = Camel     (3,1)
G Z C H C Z G     Z = Zebra     (3,2)

These can be combined like NC, which would define the Unicorn from Musketeer Chess, which moves as either a Knight or a Camel. There is also the K (king) shorthand, which is equal to WF.

Bishop's implementation of Betza parsing also supports directional atoms in parentheses like (4,1) (the 'Giraffe'), so a Knight could also be defined as PieceType.fromBetza('(2,1)') (or (1,2)).

Bishop also implements a special movement atom *, which means movement to any square on the board is allowed. This is not part of any Betza standard, but I hereby propose it! It allows defining the Duck from Duck Chess as m*.

Move Modality

By default, any atoms specified will produce both quiet (non-capturing) and capturing moves. The modifiers m and c specify moves that either only capture or never capture. For example, mNcB defines a 'Knibis', a piece that moves like a knight but captures like a bishop.

Range

The range of a movement atom is specified by a number after it. For example, F2 means a piece that can move two squares diagonally.

  • The range is taken to be 1 by default, so W is the same as W1.
  • 0 means infinite range, so W0 is a rook.
  • This works for any atom! So N0 is a 'Nightrider', a piece that can make many Knight moves in the same direction. (4,1)2 makes one or two Giraffe moves (although you would need a huge board for this to be useful).
  • Pieces with range are known as 'sliders', and they can be blocked, i.e. they can't jump over pieces.
  • Shorthands R, B and Q (rook, bishop and queen) are equal to W0, F0 and W0F0 (or RB) respectively.
  • The old Betza repeated atom shorthand, e.g. WW meaning W0 is considered obsolete and does not work in Bishop.

Directional Modifiers

What if you want a knight that can only move forward, like the Shogi Knight? That can be defined with fN. A rook that only moves horizontally is sR. A bishop that only moves right is rB.

More complex directional modifiers are available, like fsN - a knight that only moves forwards but only on the horizontal moves, or rbN, a knight that only moves to one square behind it on the right (from d4 to e2).\

I won't go through all of the modifiers here because there are a lot. See the Betza reference.

Functional Modifiers

  • n (lame leaper/non-jumper): moves with this modifier cannot jump over pieces. For example, nN is the Xiangqi knight - it moves like a chess knight, but if there is a piece in the way in the longer direction, it cannot make the move.
  • i (first move only): atoms with this modifier can only be made if it is the piece's first move.
  • e (en-passant): atoms with this modifier can capture en-passant. This modifier doesn't exclude normal captures.
  • p (unlimited hopper): applied to sliding moves to change them so that they must jump over a piece. Pieces making these moves can land anywhere after the jump, but must jump over a piece. The most well known example is probably the Xiangqi cannon: mRcpR (moves as a rook, captures as a rook but must jump a piece first).
  • g (limited hopper): like p, but the piece can only land on the square directly after the piece it jumps over, like the Grasshopper: gQ.

Combining Modifiers / Example

Modifiers only apply to the atom directly following them. Other than that, the order of operations is unimportant; igfrR is the same as gfriR. Bear in mind that some of the directional modifiers are two characters long - fr is not the same as rf (for oblique pieces).

Let's break down the standard chess pawn, since it is surprisingly complicated and probably the inspiration for half of these modifiers.

'fmWfceFifmnD':

  • fmW: moves orthogonally forward exactly one square (fW), doesn't capture this way (m).
  • fceF: captures diagonally forward exactly one square (fF), doesn't move this way (c), can en-passant (e).
  • ifmnD: moves forward exactly two squares (fD), doesn't capture this way (m), can be blocked by a piece halfway through the move (n), can only make this move as the piece's first move (i).

Things Bishop doesn't support (yet)

Some of the modern Betza notation extensions allow specifying a whole load of other behaviour. Some of these are planned for the relatively near future for Bishop.

Most importantly, chained/multi-leg moves will be included soon.

Some features like the 'destroy own piece' modifier or drop restrictions aren't a priority since Bishop has more flexible ways to define these with things like actions and drop builders. It is possible that some of these will be included as 'shortcuts' that are compiled to actions etc in the variant building process.

As usual, if you're reading this and wishing some specific modifier was included, feel free to file an issue or start a discussion on the repo page.


Variant Definition

Variants can be arbitrarily defined with quite a lot of different options. For example, standard chess is defined like this:

Variant chess = Variant(
    name: 'Chess',
    boardSize: BoardSize.standard,
    startPosition: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
    castlingOptions: CastlingOptions.standard,
    materialConditions: MaterialConditions.standard,
    outputOptions: OutputOptions.standard,
    promotionOptions: PromotionOptions.standard,
    enPassant: true,
    halfMoveDraw: 100,
    repetitionDraw: 3,
    pieceTypes: {
      'P': PieceType.pawn(),
      'N': PieceType.knight(),
      'B': PieceType.bishop(),
      'R': PieceType.rook(),
      'Q': PieceType.queen(),
      'K': PieceType.king(),
    },
  );
};

Of course there is a default Variant.standard() constructor for this, and other variants can be built based on this too using Variant.copyWith(). For example, Capablanca Chess can be defined like this:

Variant capablanca = Variant.standard().withPieces({
    'A': PieceType.archbishop(),
    'C': PieceType.chancellor(),
  }).copyWith(
    name: 'Capablanca Chess',
    boardSize: BoardSize(10, 8),
    startPosition:
        'rnabqkbcnr/pppppppppp/10/10/10/10/PPPPPPPPPP/RNABQKBCNR w KQkq - 0 1',
    castlingOptions: CastlingOptions.capablanca,
  );
}

For variants such as Chess960, which can start from many positions, a startPosBuilder function can be defined, that returns a FEN string. A builder for Chess960 is included.


Regions

It is possible to easily define regions on the board that affect gameplay in certain ways. Currently supported:

  • Winning the game on entering a region. For example, King of the Hill where the game ends when a player's king enters the centre.
  • Changing the behaviour of a piece if it's in a region. For example, soldiers in Xiangqi behave differently depending on which side of the river they're on.
  • Restricting the movement of pieces. For example, the advisors and generals in Xiangqi that cannot move out of their palace, and elephants that can't cross the river.
  • Allowing promotion only in a specific area (use Variant.promotionOptions: RegionPromotion()).
  • Allowing drops only in a specific area.

To define region behaviour, you need one or more BoardRegion defintions in Variant.regions, and RegionEffects in the pieces you want to use them, using the keys used to define them. For simple rectangular regions, you will usually want RectRegion (which also has factory constructors for common cases, e.g. a whole rank, a whole file). There are also UnionRegion and IntersectRegion, which allow combining multiple regions into one.

A good simple example is the King of the Hill variant, in which a single region is defined in the centre of the board, and when a player moves their king into it, they win. The definition is below:

static Variant kingOfTheHill() =>
      Variant.standard().copyWith(name: 'King of the Hill').withPieces({
        'K': PieceType.king().withRegionEffect(
          RegionEffect.winGame(white: 'hill', black: 'hill'),
        ),
      }).withRegion(
        'hill',
        RectRegion(
          startFile: Bishop.fileD,
          endFile: Bishop.fileE,
          startRank: Bishop.rank4,
          endRank: Bishop.rank5,
        ),
      );

Here is a more complex definition of a variant in which bishops cannot leave their side of the board, and knights turn into kniroos (pieces with knight+rook movement) when they cross to the opponent's side:

final standard = Variant.standard();
Map<String, PieceType> pieceTypes = {...standard.pieceTypes};
pieceTypes['B'] = PieceType.bishop().copyWith(
  regionEffects: [
    RegionEffect.movement(white: 'whiteSide', black: 'blackSide')
  ],
);
pieceTypes['N'] = PieceType.knight().copyWith(
  regionEffects: [
    RegionEffect.changePiece(
      pieceType: PieceType.kniroo(),
      whiteRegion: 'blackSide',
      blackRegion: 'whiteSide',
    )
  ],
);
final v = Variant.standard().copyWith(
  regions: {
    'whiteSide':
        RectRegion(startRank: Bishop.rank1, endRank: Bishop.rank4),
    'blackSide':
        RectRegion(startRank: Bishop.rank5, endRank: Bishop.rank8),
  },
  pieceTypes: pieceTypes,
);

For a more familiar example of a variant with complex region effects, see the Xiangqi definition.


Actions

Basics

Actions are used to define complex custom behaviour that is not covered by other parameters. Actions are functions that take the state of the game and a move, and return a list of modifications. These modifications can be things such as adding and removing pieces from hands and gates, setting the result of the game, and most importantly, changing the contents of squares. Actions can also be used to validate moves with custom logic, allowing for more complex piece behaviour.

Actions have four parameters:

  • event: What type of event triggers this action.
  • precondition: A condition that is checked before any action from the triggering event is executed, i.e. it will judge the state of the game as it was before actions started being executed.
  • condition: A condition that is checked during the sequence of executing actions, i.e. it will judge the state of the game as it is, having been modified by any previous actions in the sequence before it.
  • action: The function that actually acts on a game state and returns a list of modifications (ActionEffects).

Conditions

Note that it's also possible to include the behaviour of condition in action, by simply checking it in there and returning an empty list. The existence of condition is for ease of use, while precondition is primarily for efficiency.

In general, if your condition does not depend on the outcome of previous actions (likely the case unless your variant has several actions), then you should put it in precondition.

A condition is a function that takes in an ActionTrigger and returns a bool, which determines whether the action will execute. A simple example:

ActionCondition isCapture = (ActionTrigger trigger) => trigger.move.capture;

The above condition will allow its following action to execute if the move triggering the condition is a capture. Since this action doesn't depend on the state of the board, only the move, it can always be a precondition.

Action Functions

Now to actually enacting effects from actions!

Here's an example of an ActionDefinition that adds a pawn to the moving player's hand:

ActionDefinition addPawnToHand = (ActionTrigger trigger) => [
  EffectAddToHand(
    trigger.piece.colour,
    trigger.variant.pieceIndexLookup['P']!,
  ),
];

Pretty simple, right? It returns a list of effects that tell Bishop how you want to modify the state. These handle all the logical intricacies, such as tracking pieces and modifying hashes, to make the process of building actions simpler. In this case the list returns a single EffectAddToHand that adds the piece with symbol P to the moving player's (trigger.piece.colour's) hand.

This can then be put together in an Action for a variant like this:

Action pawnAdder = Action(
  event: ActionEvent.afterMove,
  action: addPawnToHand,
),

Variant v = Variant.standard().copyWith(
  actions: [pawnAdder], 
  handOptions: HandOptions.enabledOnly,
);

Piece-specific actions

Let's say we want a pawn to be added to the player's hand as defined above, but only when a Knight is moved. This can obviously be achieved by modifying the function, or adding a condition, but Bishop also offers another API for this common use case.

Variant v = Variant.standard().copyWith(
  pieceTypes: {
    'N': PieceType.knight().withAction(pawnAdder),
    /// ...all other piece types
  },
  handOptions: HandOptions.enabledOnly,
);

For reference, the other way to do this would be to keep the action in Variant.actions and define it as:

Action pawnAdder = Action(
  event: ActionEvent.afterMove,
  precondition: Conditions.movingPieceIs('N'),
  action: addPawnToHand,
),

It's basically a matter of taste which of these you decide to use.

If you want to see more complex examples, look at Action.flyingGenerals (Xiangqi's rule that prevents the generals from facing each other), and Variant.atomic (a variant in which pieces explode on capture).


JSON Serialisation

It's possible to import and export Bishop variants in JSON format, simply use the Variant.fromJson() constructor, and export with Variant.toJson(). In most cases, this will be straightforward, and require no further configuration.

There are some parameters, namely PromotionOptions and Action classes, that require type adapters to be registered if custom implementations are built. Note that this isn't necessary if you don't want to use serialisation, and most likely only the most complex apps with user-generated variants will need this. This is relatively straightforward though - simply create a BishopTypeAdapter that implements the JSON import and export functionality and include it in either Variant.adapters or the adapters parameter in fromJson/toJson. See example/json.dart for a demonstration of how to do this. Also, all built-in variants are included in JSON form in example/json for reference.

Serialisation currently has a few limitations:

  • Piece types that aren't built with PieceType.fromBetza() aren't supported yet.
  • Parameterised conditions in Actions currently cannot be exported, because they are just function closures. For example, ActionCheckRoyalsAlive optionally takes a condition; if this condition is set, then the action will not be exported with the variant. If it isn't set, then there will be no problems. This will probably result in conditions being refactored into a form that works with type adapters.

Thanks

Thanks to the following projects for inspiration and reference:

bishop's People

Contributors

alexobviously avatar deathcoder avatar govind-maheshwari2 avatar malaschitz avatar

Stargazers

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

Watchers

 avatar  avatar

bishop's Issues

Deferred effects

Effects that don't happen immediately, but rather contain a condition that's checked after each move and can return wait, cancel, or trigger. So an effect that is meant to happen in 3 turns can contain a condition that compares state.halfMoves to the half moves at the time the effect was created +6, and trigger when it is, or wait otherwise. It's also possible to make a variation of this where the effect can be cancelled (I can think of some interesting ideas like a timed mine that explodes after x turns unless destroyed).

This is also needed for Janggi's bikjang rule - flying generals except it happens after the other player's turn if they don't move out of the way (side note: it seems like there are many variations of this rule where it forces a draw or a win or it's optional and so on, so the implementation needs to be flexible).

Support rules relating to perpetual checks

This can be handled by a new event: ActionEvent.onCheck. Probably the simple way is to allow analysing the state history in actions, but this would require linking states or something like that, maybe there's some other way of tracking checks. I'm averse to putting more specific case fields in BishopState though so maybe linking is the best way (as in, BishopState has a field linking to its previous state).

This handles part of (but not all of) #16

Variant-specific FEN parsing/export

FEN is not great for every variant. Or at least, the meaning of the parts of the FEN are different enough across variants that they should maybe have their own (composable) generators and parsers.

Rules that modify player perspectives (e.g. fog of war)

Although it's technically possible to do this sort of thing (and maybe implement it in Square Bishop), it would be useful to implement this sort of thing in Bishop itself, so that variants could define this sort of behaviour.

I'm not sure exactly how much work this is. I think it's mostly going to be just definitions in variants about how perspective processing should be applied, and output functions. What we probably want is a function that provides a user-facing version of the game state that goes through a filter, which applies the processing. This will probably require a rework of Square Bishop but that's fine, and it will likely be an improvement anyway.

https://www.chess.com/terms/fog-of-war-chess - an interesting first use case

Depends on #40

Variants with secrets

Secrets meaning pieces that one or both players don't know about, or pieces that have some flag that isn't visible to the opponent (or both players).

Interesting examples where this would be necessary:
Banqi (https://en.wikipedia.org/wiki/Banqi) - supposedly a Xiangqi variant but actually not really related to it at all. Basically the board is covered in pieces, but neither player knows what any of them are until they're turned over.

Stratego (https://en.wikipedia.org/wiki/Stratego) - kind of like Banqi but the players know what their own pieces are.

Beirut Chess (https://en.wikipedia.org/wiki/Beirut_chess) - each player secretly attaches a bomb to one piece that they can detonate at any time.

This also depends on #30 and maybe also the move refactor

Mobility Regions

Support for restricting certain piece types to specific areas of the board, for example generals, advisors and elephants in Xiangqi all have their movement restricted to certain areas of the board.

We probably want some MobilityRegion class that takes upper and lower rank and file limits, and maybe just check them in addMove for simplicity.

Atomic support

More generally, a system to allow custom effects on piece actions (move, capture, captured)

Chained moves

Moves that have multiple components, that aren't necessarily the same, and that can repeat

The initial goal is to make draughts/checkers, but a versatile system is planned, probably following betza 2.0 notation

Not to be confused with #37

Games with points win conditions sometimes drawn by stalemate

e.g.

Domination variant
White: Kd2
   +------------------------+
 8 | q  .  .  .  .  k  .  r |
 7 | r  .  p  .  .  p  .  n |
 6 | n  .  .  p  p  .  .  b |
 5 | P  p  .  N  .  .  p  . |
 4 | .  .  P  P  .  .  .  . |
 3 | B  .  .  .  .  N  .  . |
 2 | .  .  R  K  P  .  B  P |
 1 | .  .  .  .  .  .  b  . |
   +------------------------+
     a  b  c  d  e  f  g  h
1. c3 h5 2. a4 b6 3. Qb3 a5 4. g3 e6 5. Qd5 d6 6. Qg5 Bd7 7. Qe7+ Kxe7 8. Nf3 Nf6 9. c4 h4 10. Ra2 Nh7 11. b4 Na6 12. Ba3 Ra7 13. Rb2 hxg3 14. bxa5 g5 15. Bg2 Bxa4 16. Rg1 Bc2 17. Nc3 gxf2+ 18. Kf1 Bg7 19. Ra2 Kf8 20. Rxc2 fxg1=B 21. Nd5 b5 22. d4 Bh6 23. Ke1 Qa8 24. Kd2
Drawn by stalemate

I guess it's something like white definitely wins on points next move regardless (I think 14 points but obv only one piece can be captured), so black doesn't have any legal moves.

The solution I think is for certain types of end result to not count towards move-delegalisation.

X-FEN output

https://en.wikipedia.org/wiki/X-FEN
Basically use standard FEN castling notation (KQkq), unless there is an ambiguity and then use Shredder-FEN (AFaf). So this can result in strings like AQaq.

Requires determining ambiguity

Duck chess

https://www.chess.com/terms/duck-chess

Requirements:

  • Multi-move turns (#37)
  • Pieces that both players can move (#41)
  • Pieces that are uncapturable
  • Pieces that can move anywhere
  • Ending the game by capturing the king (could be done with an action but might be worth building it into GameEndConditions) - this is simply done with ActionCheckPieceCount(pieceType: 'K'), like kinglet chess

BUG Chess960: When rook is in check, castling is impossible.

import 'package:bishop/bishop.dart' as bishop;

void main() {
  String pos = 'RKNQRNBB';
  String fen = '${pos.toLowerCase()}/pppppppp/8/8/8/8/PPPPPPPP/$pos w';
  bishop.Game game = bishop.Game(variant: bishop.Variant.chess960(), fen: fen);
  List<String> moves =
      'g2g3,d7d5,d2d4,c8b6,c1b3,f8d7,b3c5,g7g6,c5d7,d8d7,f1e3,f7f5,f2f4,e7e6,g3g4,g8f7,g4f5,e6f5,d1d3,d7a4,b2b3,a4d4'
          .split(',');
  for (var m in moves) {
    game.makeMoveString(m);
  }
  print(game.ascii());
  print(game.fen);
  var mvs = game.generateLegalMoves();
  for (var m in mvs) {
    print(game.toAlgebraic(m));
  }
}

Output:

   +------------------------+
 8 | r  k  .  .  r  .  .  b |
 7 | p  p  p  .  .  b  .  p |
 6 | .  n  .  .  .  .  p  . |
 5 | .  .  .  p  .  p  .  . |
 4 | .  .  .  q  .  P  .  . |
 3 | .  P  .  Q  N  .  .  . |
 2 | P  .  P  .  P  .  .  P |
 1 | R  K  .  .  R  .  B  B |
   +------------------------+
     a  b  c  d  e  f  g  h  
rk2r2b/ppp2b1p/1n4p1/3p1p2/3q1P2/1P1QN3/P1P1P2P/RK2R1BB w EAea - 0 12
b3b4
d3c4
d3b5
d3a6
d3e4
d3f5
d3d4
d3d2
d3d1
d3c3
e3c4
e3d5
e3f5
e3g4
e3d1
e3g2
e3f1
a2a3
a2a4
c2c3
c2c4
h2h3
h2h4
b1c1
e1d1
e1c1
e1f1
g1f2
h1g2
h1f3
h1e4
h1d5

In this position White should be allowed a big castle - move b1a1. In this move the king moves from b1 to c1. But castling is not among the legal moves.

Custom state stored in secret board squares

I've been thinking about allowing custom variant-specific state to be built into BishopState for a while now, but I don't really want to extend it with a generic or anything that will make the base use cases more cumbersome. Then I remembered that we have a whole invisible board on the side of the playable board full of empty squares. These squares have to be 'empty' for the off-board detection to work, but that only means the piece part of the square encoding has to be 0. There are 12 whole bits of flags on each square that can be used that currently don't do anything. It means the state is limited to ints but I think that's really the only likely use case anyway?

Technically this is currently already possible. You can use EffectModifySquare to modify one of the invisible squares, and read it in actions. However, it would nice to build some helpers for this, and a fully working example.

I was thinking about Taxi Chess as the example. Basically we just need to store two amounts (players' money balances), check them to validate moves, and adjust them based on moves made.

One issue with doing that is that I probably want to do custom output (variant-specific) first, so that I don't need to build some ugly output hack in the play example for showing cash.

Three-check variant

More generally, support different endgame conditions.

In the case of three-check, the number of checks needs to be tracked for each side, and the way game ending detection that isn't a draw is determined needs to be generalised.

Hopper pieces

Support hopper pieces, e.g. the xiangqi/cờ tướng cannon.
Requires:

  • betza parsing
  • modifying slider code: which pieces to stop at, tracking jumped pieces

More complex drop logic

Currently the logic for determining which squares can be dropped to is fixed, but it would be nicer to allow more complex definitions like e.g. pieces that can only be dropped on the centre 4 squares, or on the player's first rank, etc. Currently possible with actions but maybe worth abstracting that logic out.

BUG Grand Chess: A pawn can only turn into a picked up piece

There are actually two errors here:

  1. The pawn can only promote into a picked up piece.
  2. The pawn does not have to be knocked on the 8th rank. It can move to 8 or 9 rank without promoting. It must be transformed on row 10.

Wikipedia: A pawn that reaches a player's eighth or ninth ranks can elect to either promote or remain a pawn, but it must promote upon reaching the tenth rank. Unlike standard chess, a pawn may be promoted only to a captured piece of the same colour. (So, it is impossible for either side to own two queens, or two marshals, or three rooks, etc.) If, and for as long as, no captured piece is available to promote to, a pawn on a player's ninth rank must stay on the ninth rank, but it can still give check.

Problem 1 I can probably fix, but problem 2 probably requires some new parameter for Variant (or change promotion to enum) ?

Variant: Horde chess

Depends on #25
Also has some finicky rules regarding en passant - probably solvable with change piece type region effects but it's worth investigating if there's something that needs generalising.

Ability to pass turn

This will probably result in Move getting refactored a bit. It would probably be possible just to allow Move.to to be nullable but if we're adding more varied types of moves, I'd prefer to just have Move become an interface and make variations of it. This might also allow for refactoring the makeMove function to make it significantly more readable.

Castling in chess960 when King is on g1

When the king is on square g1, the move for castling (g1h1) is not generated.

import 'package:bishop/bishop.dart' as bishop;

void main() {
  String pos = 'BBQNRNKR';
  String fen = '${pos.toLowerCase()}/pppppppp/8/8/8/8/PPPPPPPP/$pos w';
  print(fen);

  bishop.Game game = bishop.Game(variant: bishop.Variant.chess960(), fen: fen);
  game.makeMoveString('f1g3');
  game.makeMoveString('f8g6');
  print(game.ascii());
  print(game.fen);
  var moves = game.generateLegalMoves();
  moves.forEach((m) {
    print(m.algebraic());
  });
}

Output

bbqnrnkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNRNKR w
   +------------------------+
 8 | b  b  q  n  r  .  k  r |
 7 | p  p  p  p  p  p  p  p |
 6 | .  .  .  .  .  .  n  . |
 5 | .  .  .  .  .  .  .  . |
 4 | .  .  .  .  .  .  .  . |
 3 | .  .  .  .  .  .  N  . |
 2 | P  P  P  P  P  P  P  P |
 1 | B  B  Q  N  R  .  K  R |
   +------------------------+
     a  b  c  d  e  f  g  h  
bbqnr1kr/pppppppp/6n1/8/8/6N1/PPPPPPPP/BBQNR1KR w HEhe - 2 2
g3e4
g3f5
g3h5
g3f1
a2a3
a2a4
b2b3
b2b4
c2c3
c2c4
d2d3
d2d4
e2e3
e2e4
f2f3
f2f4
h2h3
h2h4
d1c3
d1e3
e1f1
g1f1

Forced capture rule

A rule required by antichess and also checkers/draughts - don't allow quiet moves if captures are available.

After a chess rook move from the h column, castling is impossible.

import 'package:bishop/bishop.dart' as bishop;

void main() {
  bishop.Game game = bishop.Game(variant: bishop.Variant.standard());
  game.makeMoveString('a2a4');
  game.makeMoveString('e7e6');
  game.makeMoveString('a1a3');
  game.makeMoveString('d7d6');
  game.makeMoveString('a3h3');
  game.makeMoveString('c7c6');
  print(game.fen); // rnbqkbnr/pp3ppp/2ppp3/8/P7/7R/1PPPPPPP/1NBQKBNR w Kkq - 0 4
  game.makeMoveString('h3a3');
  print(game.fen); // rnbqkbnr/pp3ppp/2ppp3/8/P7/R7/1PPPPPPP/1NBQKBNR b kq - 1 4
}

Generate SAN performance issue

I noticed a significant performance issue when it comes to generating the SAN movement string as a String.
After parsing the PGN file, I wanted to write a method that would allow me to jump to a specific FEN position. To do this, I needed to move through the moves using the Bishop library.

Unfortunately, jumping to a move such as 50 caused a lag of about 4 seconds. Jumping to move 50 involved:

setting the starting position
generating a list of possible moves to the possibleMoves list
finding the move that corresponds to my next move (myMove), i.e., finding a possibleMoves such that move.sanMove == myMove
And it turns out that this procedure takes about 4 seconds in a PGN file with 80 moves (40 white, 40 black).

After switching to Chess 0.7 library, the time is around 50ms.

I noticed that checking for a check or a checkmate seems to take a significant amount of time.

JSON import/export

It would be pretty nice if it were possible to define variants in JSON files, making variants super portable.
Actually for most of the fields it's trivial, but there are some exceptions.

Firstly, piece types called with the fromBetza (i.e. almost all of them) constructor, though that isn't too bad, we just check for a betza field in the json and vice versa.

The more complex exceptions are those with custom logic callbacks, such as the new actions and promo logic. I see two possible solutions:

  1. allow embedding code in the json file (ugly, maybe risky?)
  2. require registering functions for json export in some way - like when you create an Action, it has an optional id field, which links to some table you can provide to the json import function

Make en passant more flexible

Original comment: Should probably roll enPassant and firstMoveRanks into one EnPassantOptions class, to match the new paradigm and clean up all the chess-specific stuff in Variant.

But I've also realised that there are some more complex versions of en passant that we probably want to support. For example, when pawns can move 3+ spaces on the first move, it should be possible to capture them en passant on any of the intermediate squares (see: wildebeest chess, chess on a really big board).

I think the solution is to make en passant an implementation of the upcoming custom move generator, and have the en passant options param at the variant level just compile to a custom generator.

Another issue is with storing en passant squares in the state and zobrist hash. I think we can safely make it a list of ints in the state, and maybe just store one in the hash.

Bughouse support

What we'll likely want is a controller outside of the Bishop object that orchestrates multiple games, and methods on Bishop to add pieces to the hand.

Also: research into other similar variants and if there are enough close ones to necessitate a more general solution to concurrent game variants.

Custom promotion logic

Currently we have a small selection of hardcoded promotion rules - pieces with promotable all get back the same list of promotable pieces. It's possible to define multiple promotion ranks, but it will always be the case that the final rank forces promo and the others are optional (i.e. you also get back a move without a promo piece).

Some cases that aren't supported but should be:

  • In Grand Chess (#12 ), only pieces that have been captured can be promoted to, so you can't have two queens for example. This is extra complicated because we don't currently have a way to know with 100% certainty which pieces have been captured. I think the best way is to conceptualise this as 'piece limits' instead, which should cover most use cases, and cases that aren't covered by this are probably so specific that you're going to need to write custom logic anyway.
  • Variants where pieces all promote to different pieces, like Shogi.

So the requirements are:

  • Nothing needed to implement the base case (standard chess), and no performance reduction for this case.
  • Minimal effort to implement common rules like those above - probably we just provide ready-made functions like with actions, but I think it's also important to simplify/clarify the API for looking up a piece by symbol.
  • It should be possible to define piece-specific promotion mapping (like Shogi) in the piece definitions themselves, which will presumably compile to promotion rules in the variant, like piece actions do.
  • Completely flexible for more complex rules.

I think the solution is to provide a callback for building promotion pieces in the variant, much like how actions work.

Add Images to README

It is much easier to decide if you want to use a package, if there are images in the Readme. Btw your package looks great so far.

Xiangqi chasing rule

Xiangqi doesn't allow repetition and the rules can are quite complicated and it seems there isn't a total consensus on them, so we will have to allow flexibility. But the gist of it is that you aren't allow to perpetually check your opponent, and if you do then you lose.

Moves where the piece doesn't move

It would be good to support a type of move where a piece can capture without moving itself. Currently this is definitely possible through implementing an action that 'teleports' the piece back to its original square after the move, and this might actually be enough. But it's worth investigating potential value in having this as part of the move definition. I think the decider for this will be whether there are variants with the sort of behaviour already in existence that would require it. Note that it's also possible through an action to only 'teleport' after certain moves.

Chess.com pgn games cannot be loaded

when i try to load a pgn generated from chess.com i get an error:

The following RangeError was thrown running a test:
RangeError (end): Invalid value: Not in inclusive range 1..1226: -2

When the exception was thrown, this was the stack:
#0      RangeError.checkValidRange (dart:core/errors.dart:379:9)
#1      _StringBase.substring (dart:core-patch/string_patch.dart:398:27)
#2      parsePgn (package:bishop/src/pgn.dart:32:24)

here is the example i'm using:

1. d4 {[%clk 0:09:58.6]} 1... d5 {[%clk 0:09:58.1]} 2. Nc3 {[%clk 0:09:55.4]} 2... Bf5 {[%clk 0:09:55.4]} 3. f3 {[%clk 0:09:41.3]} 3... Nc6 {[%clk 0:09:49.9]} 4. e3 {[%clk 0:09:34.3]} 4... Nf6 {[%clk 0:09:47]} 5. g4 {[%clk 0:09:29.9]} 5... Nxg4 {[%clk 0:09:07.5]} 6. fxg4 {[%clk 0:09:19.3]} 6... Bxg4 {[%clk 0:08:39.9]} 7. Qxg4 {[%clk 0:09:16.8]} 7... Qd7 {[%clk 0:08:18.4]} 8. Qxd7+ {[%clk 0:08:51.6]} 8... Kxd7 {[%clk 0:08:15.6]} 9. Nxd5 {[%clk 0:08:47.5]} 9... e6 {[%clk 0:08:04.4]} 10. Nc3 {[%clk 0:08:24]} 10... Nb4 {[%clk 0:07:38.2]} 11. Kd1 {[%clk 0:07:38.9]} 11... c5 {[%clk 0:07:03.5]} 12. Nf3 {[%clk 0:07:20.1]} 12... Be7 {[%clk 0:06:10.2]} 13. Bh3 {[%clk 0:06:59.7]} 13... g5 {[%clk 0:05:48.1]} 14. Ng1 {[%clk 0:06:45.5]} 14... h5 {[%clk 0:05:25.2]} 15. Nf3 {[%clk 0:06:40.1]} 15... g4 {[%clk 0:05:16.4]} 16. Ne5+ {[%clk 0:06:08.1]} 16... Ke8 {[%clk 0:04:26.2]} 17. Bf1 {[%clk 0:05:56.5]} 17... f6 {[%clk 0:03:28.4]} 18. Nd3 {[%clk 0:05:42.6]} 18... Bd6 {[%clk 0:03:15]} 19. a3 {[%clk 0:05:13.6]} 19... Nd5 {[%clk 0:03:11.2]} 20. e4 {[%clk 0:04:56.2]} 20... Ne3+ {[%clk 0:03:08.4]} 21. Bxe3 {[%clk 0:04:42.9]} 21... g3 {[%clk 0:02:57.5]} 22. hxg3 {[%clk 0:04:39.6]} 22... Bxg3 {[%clk 0:02:54.2]} 23. Nxc5 {[%clk 0:04:29.5]} 1-0

my understanding is that the clk annotations are breaking the parser because they are enclosed in [ ] brackets...

this regex r'((?<!\{)\[(.+)\])+' seems to parse pgns containing the example above correctly

Allow for more complex first move/castling behaviour

Ouk Chaktrang (https://www.pychess.org/variants/cambodian) is pretty interesting because it has some unique first move behaviour - the king and queen both have moves that only work as their first move. We can encode these with the f betza flag, but currently the logic for determining whether a first move can execute is based only on what rank the piece is on, because previously it has only been used for pawns.

To complicate things further (or simplify, idk, since castling needs a rework anyway), the first move data for these pieces is stored in the castling part of the FEN, at least in fairy/pychess.

So we need:

  • More flexible methods for importing/exporting FEN strings
  • More flexible first move logic
  • Also a way to remove first move flags with the indirect rook attack that Ouk Chaktrang has, but this can be done with actions

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.