Package: en-pyssant
En PyssantEn Pyssant is a chess implementation and engine.Free software: GNU General Public License version 3 or laterDocumentation:https://carmenbianca.gitlab.io/en-pyssantSource code:https://gitlab.com/carmenbianca/en-pyssantPyPI:https://pypi.python.org/pypi/en-pyssantPython: 3.4+ImportantThis project is still a heavy work in progress and will break backwards
compatibility with every release before 1.0.BackgroundEn Pyssant is a hobby project to implement a complete chess implementation and
engine in Python with a simple, straightforward API.  The public API is thusly
documented and implemented that it should be relatively simple to swap out
individual components with different implementations.The focus is on keeping the API clean and flexible.  This may come at the cost
of performance, but if performance were the primary goal, perhaps it mightn’t
have been a good idea to use Python in the first place.The goal is to keep the project thoroughly tested with unit and integration
tests.  More of the latter than the former.InstallInstalling En Pyssant should be a simple matter of executing the following
command:pip3 install --user en-pyssantUsageLongum iter est per praecepta, breve et efficax per exempla—It’s a long way by
the rules, but short and efficient with examples.First, import everything:>>> import threading
>>> import time
>>> from en_pyssant import *
>>> from en_pyssant.engine import *
>>> from en_pyssant.rules import *
>>> # Technically you should never star-import, but it makes
>>> # the examples easier.En Pyssant has a few core data types:>>> white_pawn = Piece(Type.PAWN, Side.WHITE)
>>> white_pawn
Piece(type=<Type.PAWN: 'p'>, side=<Side.WHITE: 1>)
>>> a1 = Square('a1')
>>> a1.up().up()
'a3'You can easily create a starting board, or import any other board layout from
partialForsyth-Edwards Notation (FEN):>>> board = DictBoard()
>>> board
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
>>> DictBoard.from_fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR')
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR
>>> board[a1]
Piece(type=<Type.ROOK: 'r'>, side=<Side.WHITE: 1>)
>>> print(board['a3'])
None
>>> board.put('a3', white_pawn)
rnbqkbnr/pppppppp/8/8/8/P7/PPPPPPPP/RNBQKBNRYou can also easily create a chess position in the same way, which is a complete
state of the chess game (i.e., the board and some extra information).  Find
below the diverse ways of creating the starting position:>>> position = Position()
>>> position
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
>>> Position(
...     board=DictBoard(),
...     side_to_play=Side.WHITE,
...     castling=Castling(
...         CastlingSide(True, True),
...         CastlingSide(True, True)),
...     en_passant_target=None,
...     half_move_clock=0,
...     move_count=1)
...
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
>>> Position.from_fen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1')
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
>>> position.move_count
1If Forsyth-Edwards Notation is too terse, you can easily get some pretty output
instead:>>> print(board.pretty())
  A B C D E F G H
8 r n b q k b n r
7 p p p p p p p p
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 P P P P P P P P
1 R N B Q K B N R
>>> print(position.pretty())
  A B C D E F G H
8 r n b q k b n r
7 p p p p p p p p
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 P P P P P P P P
1 R N B Q K B N R
<BLANKLINE>
FEN: rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1Boards and positions are immutable data containers.  Whenever you would normally
change the state of a position, you simply create a new one and discard the old
one.  Though typically you let En Pyssant create the new position for you by
performing moves upon the board:>>> move = Move('a2', 'a3')
>>> new_position = do_move(position, move)
>>> new_position
rnbqkbnr/pppppppp/8/8/8/P7/1PPPPPPP/RNBQKBNR b KQkq - 0 1
>>> print(new_position.board.pretty())
  A B C D E F G H
8 r n b q k b n r
7 p p p p p p p p
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 P . . . . . . .
2 . P P P P P P P
1 R N B Q K B N RYou can also useStandard Algebraic Notationto do moves.  You
are allowed to be a little creative in creating your SAN strings.  The parser is
fairly tolerant and permissive:>>> san = 'a3'  # or 'Pa3', or 'a2a3', or 'Pa2-a3'
>>> assert new_position == do_move(position, san)You can easily obtain a list of all moves or perform other game logic upon the
position.  There are 20 legal moves at the start of any chess game:>>> assert len(list(moves(position))) == 20
>>> is_check(position)
False
>>> is_checkmate(position)
FalseYou are also provided with a simple wrapper that keeps track of the current
position and the history of the game for you.  Below a simple game ofFool’s
Mate:>>> game = Game()
>>> game.position
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
>>> game.do_move('f3')
rnbqkbnr/pppppppp/8/8/8/5P2/PPPPP1PP/RNBQKBNR b KQkq - 0 1
>>> game.do_move('e5')
rnbqkbnr/pppp1ppp/8/4p3/8/5P2/PPPPP1PP/RNBQKBNR w KQkq e6 0 2
>>> game.do_move('g4')
rnbqkbnr/pppp1ppp/8/4p3/6P1/5P2/PPPPP2P/RNBQKBNR b KQkq g3 0 2
>>> game.do_move('Qh4#')
rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3
>>> print(game.position.board.pretty())
  A B C D E F G H
8 r n b . k b n r
7 p p p p . p p p
6 . . . . . . . .
5 . . . . p . . .
4 . . . . . . P q
3 . . . . . P . .
2 P P P P P . . P
1 R N B Q K B N R
>>> game.is_gameover()
<Gameover.CHECKMATE: 1>
>>> game.winner()
<Side.BLACK: 0>
>>> assert len(game.history) == 4You can also export (and import) the game asPortable Game Notation:>>> pgn = game.pgn()
>>> print(pgn)
[Result "0-1"]
<BLANKLINE>
1. f3 e5 2. g4 Qh4# 0-1
>>> new_game = Game.from_pgn(pgn)
>>> new_game.winner()
<Side.BLACK: 0>The simplest way to play a complete game of chess:>>> game = Game()
>>> while not game.is_gameover():
...     new_position = game.do_move(next(game.moves()))
...
>>> assert game.is_gameover()The most interesting thing, however, is to let the computer play for you.  Below
a simple example of utilising the engine:>>> engine = MCTSEngine()
>>> # Let the engine do its thinking magic for a few seconds.
>>> engine.think_for(3)
True
>>> engine.is_thinking()  # Thinking has just finished.
False
>>> best_move = engine.best_move()
>>> position = engine.do_move(best_move)
>>> assert position == engine.position
>>>
>>> # You can also let the engine think in a subthread.
>>> thread = threading.Thread(target=engine.think_for, args=(0,))
>>> thread.start()
>>> time.sleep(0.2)
>>> # The engine is now thinking infinitely in another thread.
>>> engine.is_thinking()
True
>>> # You can query the object while the engine is calculating.
>>> new_best_move = engine.best_move()
>>> assert best_move != new_best_move
>>> _ = engine.do_move(new_best_move)
>>> engine.is_thinking()
True
>>> # Cannot think again while thinking.
>>> engine.think_for(0)
False
>>> engine.stop_thinking()
>>> thread.join()MaintainerCarmen Bianca Bakker <carmen@carmenbianca.eu>.ContributeAny merge requests or suggestions are welcome athttps://gitlab.com/carmenbianca/en-pyssantor via e-mail to one of the
maintainers.Starting local development is very simple.  Just execute the following
commands:git clone git@gitlab.com:carmenbianca/en-pyssant.git
cd en-pyssant/
python3 -mvenv venv
source venv/bin/activate
make developYou need to runmake developat least once to set up the virtualenv.Next, runmake helpto see the available interactions.When submitting a merge request, please make sure that all the tests pass.  If
possible, also provide additional tests to accompany the changed functionality.
Always add a change log entry, and make sure to add yourself to AUTHORS.rst.You are required to add a copyright notice to the files you have changed.  It is
assumed that you license the changes in your merge request under the licence
specified in the header of those files.  If not, please be specific.  Seehttps://reuse.software/for more information on licensing.LicenceGNU General Public License version 3 or later.Change logThis change log follows theKeep a Changelogspec.  Every release contains the following sections:Addedfor new features.Changedfor changes in existing functionality.Deprecatedfor soon-to-be removed features.Removedfor now removed features.Fixedfor any bug fixes.Securityin case of vulnerabilities.The versions followsemantic versioning.0.2.0 (2018-07-04)Addedmoves_singlenow complementsmovesas a function that generates all
legal moves for a single origin square.BitBoardandTupleBoardadded.AddedPiece.from_str.Addeddo_move_with_history, which returns a(Position, HistoryRecord)tuple.ParallelEngine(base class) andMCTSEngineadded.  There is now a
fully functional engine that isn’tRandomEngine.Added more methods toEngine.ChangedCastlingAvailabilityhas been replaced withCastlingSide.  Positions
now no longer contain a dictionary ofCastlingAvailabilityobjects, but aCastlingobject.  For example:castling = {Side.WHITE: CastlingAvailability(True, True), Side.BLACK: CastlingAvailability(True, True)is nowcastling = Castling(CastlingSide(True, True), CastlingSide(True, True))In effect, this makesPositionobjects hashable and entirely immutable.The newCastlingclass still permits key lookup.  Socastling[Side.WHITE].kingsideremains valid.Square.in_boundsandSquare.gotonow also accept(int, int)tuples
in lieue ofDirectionobjects.  This is more performant because tuples
hash quicker.Board.all_piecesnow starts at a1 and goes to h8.Changed some code around to be morethreading/multiprocessing-friendly.Changed the public interface ofEngine.  Specifically:Engine.__init__now takes a position, history and ruleset instead of a
game.Engine.think_forreturns True.  If another thread is already thinking,
it returns False and does not begin thinking.Engine.stop_thinkingtakes an additionalblockingkeyword argument.0.1.7 (2018-04-02)ChangedRe-release to fix the documentation.  No changes in the codebase.0.1.5 (2018-03-13)First release.Contains almost all functionality except the chess engine itself.  You can
play chess, basically.  Just not against a hyper-intelligent computer.