jonathan-laurent / AlphaZero.jl

A generic, simple and fast implementation of Deepmind's AlphaZero algorithm.
https://jonathan-laurent.github.io/AlphaZero.jl/stable/
MIT License
1.23k stars 138 forks source link

chess AI? #14

Open alemelis opened 4 years ago

alemelis commented 4 years ago

[removed]

(I was asking how to add a chess implementation, then I found the guidelines about adding a new game)

jonathan-laurent commented 4 years ago

I am reopening this because adding support for chess is something I am interested in.

The main thing to consider is that for AlphaZero to be effective on a relatively hard game such as chess, we will probably need heavy support for distributed learning.

This raises a very interesting challenge: how can we push AlphaZero.jl towards solving harder and harder games, while keeping its codebase simple and accessible enough for students and researchers who want to experiment on a single machine.

oscardssmith commented 4 years ago

The lc0 approach of having separated training and engine might be good here. It makes distributed computing easier.

alemelis commented 4 years ago

@jonathan-laurent sorry for closing, happy to re-open for discussion

I've already implemented what I think should go in the game.jl file as the package Bobby.jl. There are some parts of the API yet to be added (for example, a random game function), but the most of the game logic is there.

Note that Bobby was from the beginning not supposed to use a minmax approach, hence it's not super fast. For reference, Chess.jl by Tord Romstad (yep, Stockfish developer) gets to ~75Mnodes/s while Bobby is one order of magnitude slower with ~5Mnodes/s. However, the beauty of AlphaZero approach is exactly that you don't need 800Mnodes/s (stockfish c++) to be competitive.

I'd be interested in porting Bobby to this repo, at least to make a working example.

oscardssmith commented 3 years ago

One thing that could be a problem with using Bobby.jl is that it's move description is rather verbose. Lc0 runs into significant memory pressure, so we use a format for storing moves that only takes 2 bytes. For longer searches, this matters a lot as search is bottle-necked on memory (if you have really fast gpu).

alemelis commented 3 years ago

@oscardssmith

this is the Move struct in Bobby

mutable struct Move
    type::Symbol
    from::UInt64
    to::UInt64
    take::Piece
    enpassant::UInt64
    promotion::Symbol
    castling::UInt8
end

whereas in Lc0 you managed to squeeze all the basic info in 16bit (!) https://github.com/LeelaChessZero/lc0/blob/0622049bbcb2f348a75083c957041eada36b5cbd/src/chess/bitboard.h#L285-L289

it seems like you only need from, to, and promotion to define a move. I guess then that en-passant and castling are checked elsewhere.

Reducing to

mutable struct Move
    from::UInt64
    to::UInt64
    promotion::Symbol
end

can be done, but changing to Move::UInt16 will probably mean rewriting the entire engine.

I guess the best thing to do here is to use this engine (even tho it may be seen as cheating) https://github.com/romstad/Chess.jl

oscardssmith commented 3 years ago

You actually can fit en peasant, castling, capturing and promotions into the 2 bytes (the reason capturing is hard is because to unmake the move, you need to know what you captured. This link https://www.chessprogramming.org/Encoding_Moves does a pretty good job of explaining the details. The weirdest type of move is probably capture promotions, where you store both the piece you captured and what you promoted to. To facilitate this, you can use the fact that if you know the starting square of the pawn capture promotion, you know that it ends on the back rank, so you can use the 3 bits that would normally store the row to which the piece is moving to instead store what piece you promoted to.