Compare commits
26 commits
Author | SHA1 | Date | |
---|---|---|---|
dece | 412b019584 | ||
dece | 94bd4ec01c | ||
dece | 53a96f8787 | ||
dece | 47cc483d9e | ||
dece | 54cfd43911 | ||
dece | 7ff52bc2b5 | ||
dece | 85d5ba1a62 | ||
dece | 3d5bbb8d2c | ||
dece | 97444db39c | ||
dece | 5a6893acc7 | ||
dece | 9595d0f435 | ||
dece | ce62d3ab3a | ||
dece | 5efdd5407b | ||
dece | 8b0f4c9255 | ||
dece | b62dd1c076 | ||
dece | d958e617d0 | ||
dece | fce38693bf | ||
dece | b4dd16d87d | ||
dece | 5b678dd595 | ||
dece | 4cea0e34e9 | ||
dece | 91e1fbbe21 | ||
dece | e4d2b20e23 | ||
dece | e114138a48 | ||
dece | 9c6327ec91 | ||
dece | ea58c72436 | ||
dece | 8485714a24 |
80
Cargo.lock
generated
80
Cargo.lock
generated
|
@ -1,13 +1,5 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.3.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -31,11 +23,6 @@ name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cfg-if"
|
|
||||||
version = "0.1.10"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "2.33.1"
|
version = "2.33.1"
|
||||||
|
@ -50,44 +37,6 @@ dependencies = [
|
||||||
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "const-random"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "const-random-macro"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dashmap"
|
|
||||||
version = "3.11.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"ahash 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.1.14"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.13"
|
version = "0.1.13"
|
||||||
|
@ -101,20 +50,6 @@ name = "libc"
|
||||||
version = "0.2.71"
|
version = "0.2.71"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_cpus"
|
|
||||||
version = "1.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-hack"
|
|
||||||
version = "0.5.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -138,7 +73,6 @@ name = "vatu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dashmap 3.11.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -146,11 +80,6 @@ name = "vec_map"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasi"
|
|
||||||
version = "0.9.0+wasi-snapshot-preview1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
|
@ -171,25 +100,16 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
"checksum ahash 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
|
|
||||||
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||||
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
|
||||||
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
|
||||||
"checksum const-random 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "2f1af9ac737b2dd2d577701e59fd09ba34822f6f2ebdb30a7647405d9e55e16a"
|
|
||||||
"checksum const-random-macro 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "25e4c606eb459dd29f7c57b2e0879f2b6f14ee130918c2b78ccb58a9624e6c7a"
|
|
||||||
"checksum dashmap 3.11.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8cfcd41ae02d60edded204341d2798ba519c336c51a37330aa4b98a1128def32"
|
|
||||||
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
|
||||||
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
|
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
|
||||||
"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
|
||||||
"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
|
|
||||||
"checksum proc-macro-hack 0.5.16 (registry+https://github.com/rust-lang/crates.io-index)" = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
|
|
||||||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
|
||||||
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||||
"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
|
||||||
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
|
||||||
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
|
@ -6,4 +6,3 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dashmap = "3.11"
|
|
||||||
|
|
18
README.md
18
README.md
|
@ -67,6 +67,8 @@ cp external/lichess-bot/config.yml.example /tmp/vatu-config/config.yml
|
||||||
docker build -f res/docker/Dockerfile -t vatu .
|
docker build -f res/docker/Dockerfile -t vatu .
|
||||||
# Run with the config folder mounted at /config.
|
# Run with the config folder mounted at /config.
|
||||||
docker run -v /tmp/vatu-config:/config -ti vatu
|
docker run -v /tmp/vatu-config:/config -ti vatu
|
||||||
|
# In the container, use the following command:
|
||||||
|
python lichess-bot.py --config /config/config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,10 +76,12 @@ docker run -v /tmp/vatu-config:/config -ti vatu
|
||||||
TODO
|
TODO
|
||||||
----
|
----
|
||||||
|
|
||||||
- Support time constraints
|
- [X] Support time constraints
|
||||||
- Proper unmake mechanism instead of allocating boards like there is no tomorrow
|
- [X] Unmake mechanism instead of allocating nodes like there is no tomorrow
|
||||||
- Precompute some pieces moves, maybe
|
- [X] Precompute some pieces moves, maybe (done for knights)
|
||||||
- Transposition table that does not actually slows search down
|
- [ ] Transposition table that does not actually slows search down
|
||||||
- Check Zobrist hashes for previous point
|
- [ ] Check Zobrist hashes for previous point
|
||||||
- Actual bitboard
|
- [X] Actual bitboard
|
||||||
- Multithreading (never)
|
- [ ] Some kind of move ordering could be great
|
||||||
|
- [ ] Multithreading (never)
|
||||||
|
- [ ] Avoid 3-fold repetitions when winning
|
||||||
|
|
30
res/scripts/gen_king_rays.py
Executable file
30
res/scripts/gen_king_rays.py
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Pre-compute king ras bitboards for each square."""
|
||||||
|
|
||||||
|
TEMPLATE = """\
|
||||||
|
/// Pre-computed king rays.
|
||||||
|
pub const KING_RAYS: [Bitboard; 64] = [
|
||||||
|
{}
|
||||||
|
];
|
||||||
|
"""
|
||||||
|
|
||||||
|
DIRS = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)]
|
||||||
|
|
||||||
|
def bit_pos(square):
|
||||||
|
return 1 << square
|
||||||
|
|
||||||
|
def get_rays():
|
||||||
|
rays = []
|
||||||
|
for f in range(8):
|
||||||
|
for r in range(8):
|
||||||
|
bitboard = 0
|
||||||
|
for dir_f, dir_r in DIRS:
|
||||||
|
ray_f = f + dir_f
|
||||||
|
ray_r = r + dir_r
|
||||||
|
if ray_f < 0 or ray_f > 7 or ray_r < 0 or ray_r > 7:
|
||||||
|
continue
|
||||||
|
bitboard |= bit_pos(ray_f * 8 + ray_r)
|
||||||
|
rays.append(" 0b{:064b},".format(bitboard))
|
||||||
|
return rays
|
||||||
|
|
||||||
|
print(TEMPLATE.format("\n".join(get_rays())))
|
30
res/scripts/gen_knight_rays.py
Executable file
30
res/scripts/gen_knight_rays.py
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Pre-compute knight ray bitboards for each square."""
|
||||||
|
|
||||||
|
TEMPLATE = """\
|
||||||
|
/// Pre-computed knight rays.
|
||||||
|
pub const KNIGHT_RAYS: [Bitboard; 64] = [
|
||||||
|
{}
|
||||||
|
];
|
||||||
|
"""
|
||||||
|
|
||||||
|
DIRS = [(1, 2), (2, 1), (2, -1), (1, -2), (-1, -2), (-2, -1), (-2, 1), (-1, 2)]
|
||||||
|
|
||||||
|
def bit_pos(square):
|
||||||
|
return 1 << square
|
||||||
|
|
||||||
|
def get_rays():
|
||||||
|
rays = []
|
||||||
|
for f in range(8):
|
||||||
|
for r in range(8):
|
||||||
|
bitboard = 0
|
||||||
|
for dir_f, dir_r in DIRS:
|
||||||
|
ray_f = f + dir_f
|
||||||
|
ray_r = r + dir_r
|
||||||
|
if ray_f < 0 or ray_f > 7 or ray_r < 0 or ray_r > 7:
|
||||||
|
continue
|
||||||
|
bitboard |= bit_pos(ray_f * 8 + ray_r)
|
||||||
|
rays.append(" 0b{:064b},".format(bitboard))
|
||||||
|
return rays
|
||||||
|
|
||||||
|
print(TEMPLATE.format("\n".join(get_rays())))
|
39
res/scripts/gen_pawn_captures.py
Executable file
39
res/scripts/gen_pawn_captures.py
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Pre-compute pawn captures bitboards for each square."""
|
||||||
|
|
||||||
|
TEMPLATE = """\
|
||||||
|
/// Pre-computed pawn captures.
|
||||||
|
pub const PAWN_CAPTURES: [[Bitboard; 64]; 2] = [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
];
|
||||||
|
"""
|
||||||
|
|
||||||
|
def bit_pos(square):
|
||||||
|
return 1 << square
|
||||||
|
|
||||||
|
def get_captures():
|
||||||
|
both_captures = []
|
||||||
|
for direction in [1, -1]:
|
||||||
|
captures = []
|
||||||
|
for f in range(8):
|
||||||
|
for r in range(8):
|
||||||
|
bitboard = 0
|
||||||
|
prog_r = r + direction
|
||||||
|
if 0 < prog_r < 7:
|
||||||
|
prev_f = f - 1
|
||||||
|
if prev_f >= 0:
|
||||||
|
bitboard |= bit_pos(prev_f * 8 + prog_r)
|
||||||
|
next_f = f + 1
|
||||||
|
if next_f <= 7:
|
||||||
|
bitboard |= bit_pos(next_f * 8 + prog_r)
|
||||||
|
captures.append(" 0b{:064b},".format(bitboard))
|
||||||
|
both_captures.append(captures)
|
||||||
|
return both_captures
|
||||||
|
|
||||||
|
CAPTURES = get_captures()
|
||||||
|
print(TEMPLATE.format("\n".join(CAPTURES[0]), "\n".join(CAPTURES[1])))
|
38
res/scripts/gen_pawn_progresses.py
Executable file
38
res/scripts/gen_pawn_progresses.py
Executable file
|
@ -0,0 +1,38 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Pre-compute pawn progress bitboards for each square."""
|
||||||
|
|
||||||
|
TEMPLATE = """\
|
||||||
|
/// Pre-computed pawn progresses.
|
||||||
|
pub const PAWN_PROGRESSES: [[Bitboard; 64]; 2] = [
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{}
|
||||||
|
],
|
||||||
|
];
|
||||||
|
"""
|
||||||
|
|
||||||
|
def bit_pos(square):
|
||||||
|
return 1 << square
|
||||||
|
|
||||||
|
def get_progresses():
|
||||||
|
both_progresses = []
|
||||||
|
for direction in [1, -1]:
|
||||||
|
progresses = []
|
||||||
|
for f in range(8):
|
||||||
|
for r in range(8):
|
||||||
|
bitboard = 0
|
||||||
|
if 0 < r < 7:
|
||||||
|
prog_r = r + direction
|
||||||
|
bitboard |= bit_pos(f * 8 + prog_r)
|
||||||
|
if direction == 1 and r == 1:
|
||||||
|
bitboard |= bit_pos(f * 8 + prog_r + 1)
|
||||||
|
elif direction == -1 and r == 6:
|
||||||
|
bitboard |= bit_pos(f * 8 + (prog_r - 1))
|
||||||
|
progresses.append(" 0b{:064b},".format(bitboard))
|
||||||
|
both_progresses.append(progresses)
|
||||||
|
return both_progresses
|
||||||
|
|
||||||
|
PROGRESSES = get_progresses()
|
||||||
|
print(TEMPLATE.format("\n".join(PROGRESSES[0]), "\n".join(PROGRESSES[1])))
|
5
res/scripts/gen_squares.py
Executable file
5
res/scripts/gen_squares.py
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
for f in range(8):
|
||||||
|
for r in range(8):
|
||||||
|
print("pub const {}{}: Pos = {};".format(chr(f + 65), r + 1, f * 8 + r))
|
56
res/scripts/gen_zobrist_keys.py
Executable file
56
res/scripts/gen_zobrist_keys.py
Executable file
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Generate Zobrist keys."""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
random.seed("vatu stop playing your queen first you have other pieces")
|
||||||
|
|
||||||
|
TEMPLATE = """\
|
||||||
|
pub const ZOBRIST_PIECES: [[[ZobristHash; 64]; 6]; 2] = [
|
||||||
|
{}
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const ZOBRIST_BLACK_TURN: ZobristHash = {};
|
||||||
|
|
||||||
|
pub const ZOBRIST_CASTLE_WH_K: usize = 0;
|
||||||
|
pub const ZOBRIST_CASTLE_WH_Q: usize = 1;
|
||||||
|
pub const ZOBRIST_CASTLE_BL_K: usize = 2;
|
||||||
|
pub const ZOBRIST_CASTLE_BL_Q: usize = 3;
|
||||||
|
|
||||||
|
pub const ZOBRIST_CASTLES: [ZobristHash; 4] = [
|
||||||
|
{}
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const ZOBRIST_EN_PASSANT: [ZobristHash; 8] = [
|
||||||
|
{}
|
||||||
|
];
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def gen_hash():
|
||||||
|
return random.getrandbits(64)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_pieces_keys():
|
||||||
|
pieces_str = ""
|
||||||
|
for color in range(2):
|
||||||
|
pieces_str += " [\n"
|
||||||
|
for piece in range(6):
|
||||||
|
pieces_str += " [\n"
|
||||||
|
for square in range(64):
|
||||||
|
k = gen_hash()
|
||||||
|
pieces_str += " {},\n".format(k)
|
||||||
|
pieces_str += " ],\n"
|
||||||
|
pieces_str += " ],\n"
|
||||||
|
return pieces_str
|
||||||
|
|
||||||
|
|
||||||
|
print(
|
||||||
|
TEMPLATE.format(
|
||||||
|
gen_pieces_keys(),
|
||||||
|
gen_hash(),
|
||||||
|
"\n".join([" {},".format(gen_hash()) for _ in range(4)]),
|
||||||
|
"\n".join([" {},".format(gen_hash()) for _ in range(8)]),
|
||||||
|
)
|
||||||
|
)
|
|
@ -7,7 +7,6 @@ use crate::board;
|
||||||
use crate::engine;
|
use crate::engine;
|
||||||
use crate::movement::Move;
|
use crate::movement::Move;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
use crate::notation;
|
|
||||||
use crate::rules;
|
use crate::rules;
|
||||||
use crate::stats;
|
use crate::stats;
|
||||||
|
|
||||||
|
@ -104,27 +103,24 @@ impl Analyzer {
|
||||||
|
|
||||||
if self.debug {
|
if self.debug {
|
||||||
self.log(format!("Analyzing node:\n{}", &self.node));
|
self.log(format!("Analyzing node:\n{}", &self.node));
|
||||||
let moves = self.node.get_player_moves(true);
|
let moves = self.node.get_player_moves();
|
||||||
self.log(format!("Legal moves: {}", notation::move_list_to_string(&moves)));
|
self.log(format!("Legal moves: {}", Move::list_to_uci_string(&moves)));
|
||||||
self.log(format!("Move time: {}", self.time_limit));
|
self.log(format!("Move time: {}", self.time_limit));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.start_time = Some(Instant::now());
|
self.start_time = Some(Instant::now());
|
||||||
self.current_per_second_timer = Some(Instant::now());
|
self.current_per_second_timer = Some(Instant::now());
|
||||||
let (max_score, best_move) = self.negamax(&self.node.clone(), MIN_F32, MAX_F32, 0);
|
let (max_score, best_move) = self.negamax(&mut self.node.clone(), MIN_F32, MAX_F32, 0);
|
||||||
|
|
||||||
if best_move.is_some() {
|
if let Some(m) = best_move {
|
||||||
let log_str = format!(
|
let log_str = format!("Best move {} evaluated {}", m.to_uci_string(), max_score);
|
||||||
"Best move {} evaluated {}",
|
|
||||||
notation::move_to_string(&best_move.unwrap()), max_score
|
|
||||||
);
|
|
||||||
self.log(log_str);
|
self.log(log_str);
|
||||||
self.report_best_move(best_move);
|
self.report_best_move(Some(m));
|
||||||
} else {
|
} else {
|
||||||
// If no best move could be found, checkmate is unavoidable; send the first legal move.
|
// If no best move could be found, checkmate is unavoidable; send the first legal move.
|
||||||
self.log("Checkmate is unavoidable.".to_string());
|
self.log("Checkmate is unavoidable.".to_string());
|
||||||
let moves = rules::get_player_moves(&self.node.board, &self.node.game_state, true);
|
let moves = rules::get_player_moves(&mut self.node.board, &mut self.node.game_state);
|
||||||
let m = if moves.len() > 0 { Some(moves[0]) } else { None };
|
let m = if moves.len() > 0 { Some(moves[0].clone()) } else { None };
|
||||||
self.report_best_move(m);
|
self.report_best_move(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +131,7 @@ impl Analyzer {
|
||||||
self.time_limit = if args.move_time != -1 {
|
self.time_limit = if args.move_time != -1 {
|
||||||
args.move_time
|
args.move_time
|
||||||
} else {
|
} else {
|
||||||
let (time, inc) = if board::is_white(self.node.game_state.color) {
|
let (time, inc) = if self.node.game_state.color == board::WHITE {
|
||||||
(args.white_time, args.white_inc)
|
(args.white_time, args.white_inc)
|
||||||
} else {
|
} else {
|
||||||
(args.black_time, args.black_inc)
|
(args.black_time, args.black_inc)
|
||||||
|
@ -162,7 +158,7 @@ impl Analyzer {
|
||||||
/// lower score bound and `beta` the upper bound.
|
/// lower score bound and `beta` the upper bound.
|
||||||
fn negamax(
|
fn negamax(
|
||||||
&mut self,
|
&mut self,
|
||||||
node: &Node,
|
node: &mut Node,
|
||||||
alpha: f32,
|
alpha: f32,
|
||||||
beta: f32,
|
beta: f32,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
|
@ -189,18 +185,21 @@ impl Analyzer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get negamax for playable moves.
|
// Get negamax for playable moves.
|
||||||
let moves = node.get_player_moves(true);
|
let mut moves = node.get_player_moves();
|
||||||
let mut alpha = alpha;
|
let mut alpha = alpha;
|
||||||
let mut best_score = MIN_F32;
|
let mut best_score = MIN_F32;
|
||||||
let mut best_move = None;
|
let mut best_move = None;
|
||||||
for m in moves {
|
for m in &mut moves {
|
||||||
let mut sub_node = node.clone();
|
let hash_changes = node.apply_move(m);
|
||||||
sub_node.apply_move(&m);
|
let result = self.negamax(node, -beta, -alpha, depth + 1);
|
||||||
let result = self.negamax(&sub_node, -beta, -alpha, depth + 1);
|
node.unmake_move(m, hash_changes);
|
||||||
let score = -result.0;
|
let score = -result.0;
|
||||||
|
if self.debug && depth == 0 {
|
||||||
|
self.log(format!("move {} evaluated {}", m.to_uci_string(), score));
|
||||||
|
}
|
||||||
if score > best_score {
|
if score > best_score {
|
||||||
best_score = score;
|
best_score = score;
|
||||||
best_move = Some(m);
|
best_move = Some(m.to_owned());
|
||||||
}
|
}
|
||||||
if best_score > alpha {
|
if best_score > alpha {
|
||||||
alpha = best_score;
|
alpha = best_score;
|
||||||
|
|
1028
src/board.rs
1028
src/board.rs
File diff suppressed because it is too large
Load diff
|
@ -1,19 +1,53 @@
|
||||||
//! Castling flags.
|
//! Castling flags.
|
||||||
|
|
||||||
pub const CASTLING_WH_K: u8 = 0b00000001;
|
use crate::board::{Bitboard, RANK_1, RANK_8};
|
||||||
pub const CASTLING_WH_Q: u8 = 0b00000010;
|
|
||||||
pub const CASTLING_WH_MASK: u8 = 0b00000011;
|
|
||||||
pub const CASTLING_BL_K: u8 = 0b00000100;
|
|
||||||
pub const CASTLING_BL_Q: u8 = 0b00001000;
|
|
||||||
pub const CASTLING_BL_MASK: u8 = 0b00001100;
|
|
||||||
pub const CASTLING_K_MASK: u8 = 0b00000101;
|
|
||||||
pub const CASTLING_Q_MASK: u8 = 0b00001010;
|
|
||||||
pub const CASTLING_MASK: u8 = 0b00001111;
|
|
||||||
|
|
||||||
/// Castling sides parameters.
|
pub type Castle = u8;
|
||||||
|
|
||||||
|
pub const CASTLE_WH_K: Castle = 0b00000001;
|
||||||
|
pub const CASTLE_WH_Q: Castle = 0b00000010;
|
||||||
|
pub const CASTLE_WH_MASK: Castle = 0b00000011;
|
||||||
|
pub const CASTLE_BL_K: Castle = 0b00000100;
|
||||||
|
pub const CASTLE_BL_Q: Castle = 0b00001000;
|
||||||
|
pub const CASTLE_BL_MASK: Castle = 0b00001100;
|
||||||
|
pub const CASTLE_K_MASK: Castle = 0b00000101;
|
||||||
|
pub const CASTLE_Q_MASK: Castle = 0b00001010;
|
||||||
|
pub const CASTLE_MASK: Castle = 0b00001111;
|
||||||
|
|
||||||
|
/// Index castling masks with their color.
|
||||||
|
pub const CASTLE_MASK_BY_COLOR: [Castle; 2] = [CASTLE_WH_MASK, CASTLE_BL_MASK];
|
||||||
|
|
||||||
|
/// Index castling ranks with their color.
|
||||||
|
pub const CASTLE_RANK_BY_COLOR: [i8; 2] = [RANK_1, RANK_8];
|
||||||
|
|
||||||
|
pub const CASTLE_SIDE_K: usize = 0;
|
||||||
|
pub const CASTLE_SIDE_Q: usize = 1;
|
||||||
|
pub const NUM_CASTLE_SIDES: usize = 2;
|
||||||
|
|
||||||
|
/// Index castling sides using CASTLE_SIDE_K and CASTLE_SIDE_Q.
|
||||||
|
pub const CASTLE_SIDES: [Castle; 2] = [CASTLE_K_MASK, CASTLE_Q_MASK];
|
||||||
|
|
||||||
|
/// Castle paths that must not be under attack, by color and side.
|
||||||
///
|
///
|
||||||
/// For both sides, the 3-uple contains files that should be empty
|
/// This includes the original king position, its target square and
|
||||||
/// and not attacked, an optional file that should be empty for
|
/// the square in between.
|
||||||
/// queen-side, and the castling side-mask.
|
pub const CASTLE_LEGALITY_PATHS: [[Bitboard; 2]; 2] = [
|
||||||
pub const CASTLING_SIDES: [([i8; 2], Option<i8>, u8); 2] =
|
[
|
||||||
[([5i8, 6i8], None, CASTLING_K_MASK), ([3i8, 2i8], Some(1i8), CASTLING_Q_MASK)];
|
0b00000000_00000001_00000001_00000001_00000000_00000000_00000000_00000000, // White Kside.
|
||||||
|
0b00000000_00000000_00000000_00000001_00000001_00000001_00000000_00000000, // White Qside.
|
||||||
|
], [
|
||||||
|
0b00000000_10000000_10000000_10000000_00000000_00000000_00000000_00000000, // Black Kside.
|
||||||
|
0b00000000_00000000_00000000_10000000_10000000_10000000_00000000_00000000, // Black Qside.
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Castle paths that must be empty.
|
||||||
|
pub const CASTLE_MOVE_PATHS: [[Bitboard; 2]; 2] = [
|
||||||
|
[
|
||||||
|
0b00000000_00000001_00000001_00000000_00000000_00000000_00000000_00000000, // White Kside.
|
||||||
|
0b00000000_00000000_00000000_00000000_00000001_00000001_00000001_00000000, // White Qside.
|
||||||
|
], [
|
||||||
|
0b00000000_10000000_10000000_00000000_00000000_00000000_00000000_00000000, // Black Kside.
|
||||||
|
0b00000000_00000000_00000000_00000000_10000000_10000000_10000000_00000000, // Black Qside.
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
|
@ -11,9 +11,9 @@ use std::thread;
|
||||||
use crate::analysis;
|
use crate::analysis;
|
||||||
use crate::board;
|
use crate::board;
|
||||||
use crate::castling;
|
use crate::castling;
|
||||||
use crate::movement::{self, Move};
|
use crate::fen;
|
||||||
|
use crate::movement::Move;
|
||||||
use crate::node::Node;
|
use crate::node::Node;
|
||||||
use crate::notation;
|
|
||||||
use crate::uci;
|
use crate::uci;
|
||||||
|
|
||||||
/// Analysis engine.
|
/// Analysis engine.
|
||||||
|
@ -62,6 +62,8 @@ pub enum Cmd {
|
||||||
WorkerInfo(Vec<analysis::AnalysisInfo>),
|
WorkerInfo(Vec<analysis::AnalysisInfo>),
|
||||||
/// Send best move found by analysis worker.
|
/// Send best move found by analysis worker.
|
||||||
WorkerBestMove(Option<Move>),
|
WorkerBestMove(Option<Move>),
|
||||||
|
/// Log current node.
|
||||||
|
LogNode,
|
||||||
|
|
||||||
// Commands that can be sent by the engine.
|
// Commands that can be sent by the engine.
|
||||||
|
|
||||||
|
@ -123,7 +125,17 @@ impl Engine {
|
||||||
// Workers commands.
|
// Workers commands.
|
||||||
Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())),
|
Cmd::Log(s) => self.reply(Cmd::Log(s.to_string())),
|
||||||
Cmd::WorkerInfo(infos) => self.reply(Cmd::Info(infos.to_vec())),
|
Cmd::WorkerInfo(infos) => self.reply(Cmd::Info(infos.to_vec())),
|
||||||
Cmd::WorkerBestMove(m) => self.reply(Cmd::BestMove(*m)),
|
Cmd::WorkerBestMove(m) => self.reply(Cmd::BestMove(m.clone())),
|
||||||
|
// Other commands.
|
||||||
|
Cmd::LogNode => {
|
||||||
|
let mut s = vec!();
|
||||||
|
self.node.board.draw_to(&mut s);
|
||||||
|
self.reply(Cmd::Log(format!(
|
||||||
|
"Current node:\n{}{}",
|
||||||
|
String::from_utf8_lossy(&s),
|
||||||
|
self.node.game_state
|
||||||
|
)));
|
||||||
|
}
|
||||||
_ => eprintln!("Not an engine input command: {:?}", cmd),
|
_ => eprintln!("Not an engine input command: {:?}", cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,29 +153,30 @@ impl Engine {
|
||||||
/// Apply a FEN string to the engine state, replacing it.
|
/// Apply a FEN string to the engine state, replacing it.
|
||||||
///
|
///
|
||||||
/// For speed purposes, it assumes values are always valid.
|
/// For speed purposes, it assumes values are always valid.
|
||||||
fn apply_fen(&mut self, fen: ¬ation::Fen) {
|
fn apply_fen(&mut self, fen: &fen::Fen) {
|
||||||
// Placement.
|
// Placement.
|
||||||
self.node.board = board::new_from_fen(&fen.placement);
|
self.node.board = board::Board::new_from_fen(&fen.placement);
|
||||||
// Color.
|
// Color.
|
||||||
match fen.color.chars().next().unwrap() {
|
match fen.color.chars().next().unwrap() {
|
||||||
'w' => self.node.game_state.color = board::SQ_WH,
|
'w' => self.node.game_state.color = board::WHITE,
|
||||||
'b' => self.node.game_state.color = board::SQ_BL,
|
'b' => self.node.game_state.color = board::BLACK,
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
};
|
||||||
// Castling.
|
// Castling.
|
||||||
|
self.node.game_state.castling = 0;
|
||||||
for c in fen.castling.chars() {
|
for c in fen.castling.chars() {
|
||||||
match c {
|
match c {
|
||||||
'K' => self.node.game_state.castling |= castling::CASTLING_WH_K,
|
'K' => self.node.game_state.castling |= castling::CASTLE_WH_K,
|
||||||
'Q' => self.node.game_state.castling |= castling::CASTLING_WH_Q,
|
'Q' => self.node.game_state.castling |= castling::CASTLE_WH_Q,
|
||||||
'k' => self.node.game_state.castling |= castling::CASTLING_BL_K,
|
'k' => self.node.game_state.castling |= castling::CASTLE_BL_K,
|
||||||
'q' => self.node.game_state.castling |= castling::CASTLING_BL_Q,
|
'q' => self.node.game_state.castling |= castling::CASTLE_BL_Q,
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// En passant.
|
// En passant.
|
||||||
self.node.game_state.en_passant = match fen.en_passant.as_ref() {
|
self.node.game_state.en_passant = match fen.en_passant.as_ref() {
|
||||||
"-" => None,
|
"-" => None,
|
||||||
p => Some(board::pos(p)),
|
s => Some(board::sq_from_string(s)),
|
||||||
};
|
};
|
||||||
// Half moves.
|
// Half moves.
|
||||||
self.node.game_state.halfmove = fen.halfmove.parse::<i32>().ok().unwrap();
|
self.node.game_state.halfmove = fen.halfmove.parse::<i32>().ok().unwrap();
|
||||||
|
@ -172,13 +185,10 @@ impl Engine {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a series of moves to the current node.
|
/// Apply a series of moves to the current node.
|
||||||
fn apply_moves(&mut self, moves: &Vec<Move>) {
|
fn apply_moves(&mut self, moves: &mut Vec<Move>) {
|
||||||
moves.iter().for_each(|m| self.apply_move(m));
|
for m in moves.iter_mut() {
|
||||||
}
|
m.apply_to(&mut self.node.board, &mut self.node.game_state);
|
||||||
|
}
|
||||||
/// Apply a move to the current node.
|
|
||||||
fn apply_move(&mut self, m: &Move) {
|
|
||||||
movement::apply_move_to(&mut self.node.board, &mut self.node.game_state, m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start working on board, returning the best move found.
|
/// Start working on board, returning the best move found.
|
||||||
|
@ -221,11 +231,11 @@ impl Engine {
|
||||||
self.apply_fen(&fen);
|
self.apply_fen(&fen);
|
||||||
},
|
},
|
||||||
uci::PositionArgs::Startpos => {
|
uci::PositionArgs::Startpos => {
|
||||||
let fen = notation::parse_fen(notation::FEN_START).unwrap();
|
let fen = fen::parse_fen(fen::FEN_START).unwrap();
|
||||||
self.apply_fen(&fen);
|
self.apply_fen(&fen);
|
||||||
},
|
},
|
||||||
uci::PositionArgs::Moves(moves) => {
|
uci::PositionArgs::Moves(moves) => {
|
||||||
self.apply_moves(&moves);
|
self.apply_moves(&mut moves.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
55
src/fen.rs
Normal file
55
src/fen.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
//! Functions to parse FEN strings.
|
||||||
|
|
||||||
|
use crate::board;
|
||||||
|
|
||||||
|
pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||||
|
|
||||||
|
/// FEN notation for positions, split into fields.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Fen {
|
||||||
|
pub placement: String,
|
||||||
|
pub color: String,
|
||||||
|
pub castling: String,
|
||||||
|
pub en_passant: String,
|
||||||
|
pub halfmove: String,
|
||||||
|
pub fullmove: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_fen(i: &str) -> Option<Fen> {
|
||||||
|
let fields: Vec<&str> = i.split_whitespace().collect();
|
||||||
|
parse_fen_fields(&fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_fen_fields(fields: &[&str]) -> Option<Fen> {
|
||||||
|
if fields.len() < 6 {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
Some(Fen {
|
||||||
|
placement: fields[0].to_string(),
|
||||||
|
color: fields[1].to_string(),
|
||||||
|
castling: fields[2].to_string(),
|
||||||
|
en_passant: fields[3].to_string(),
|
||||||
|
halfmove: fields[4].to_string(),
|
||||||
|
fullmove: fields[5].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn en_passant_to_string(ep: Option<board::Square>) -> String {
|
||||||
|
ep.and_then(|p| Some(board::sq_to_string(p))).unwrap_or("-".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_fen() {
|
||||||
|
let fen_start = parse_fen(FEN_START).unwrap();
|
||||||
|
assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
|
||||||
|
assert_eq!(&fen_start.color, "w");
|
||||||
|
assert_eq!(&fen_start.castling, "KQkq");
|
||||||
|
assert_eq!(&fen_start.en_passant, "-");
|
||||||
|
assert_eq!(&fen_start.halfmove, "0");
|
||||||
|
assert_eq!(&fen_start.fullmove, "1");
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,14 @@ pub mod analysis;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod castling;
|
pub mod castling;
|
||||||
pub mod engine;
|
pub mod engine;
|
||||||
|
pub mod fen;
|
||||||
pub mod movement;
|
pub mod movement;
|
||||||
pub mod node;
|
pub mod node;
|
||||||
pub mod notation;
|
pub mod precomputed;
|
||||||
pub mod rules;
|
pub mod rules;
|
||||||
pub mod stats;
|
pub mod stats;
|
||||||
pub mod uci;
|
pub mod uci;
|
||||||
|
pub mod zobrist;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let args = App::new("Vatu")
|
let args = App::new("Vatu")
|
||||||
|
|
522
src/movement.rs
522
src/movement.rs
|
@ -1,230 +1,374 @@
|
||||||
//! Move functions along with some castling helpers.
|
//! Move functions along with some castling helpers.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use crate::board::*;
|
use crate::board::*;
|
||||||
use crate::castling::*;
|
use crate::castling::*;
|
||||||
use crate::rules;
|
use crate::rules::GameState;
|
||||||
|
use crate::zobrist::*;
|
||||||
const START_WH_K_POS: Pos = pos("e1");
|
|
||||||
const START_BL_K_POS: Pos = pos("e8");
|
|
||||||
|
|
||||||
/// A movement, with before/after positions and optional promotion.
|
/// A movement, with before/after positions and optional promotion.
|
||||||
pub type Move = (Pos, Pos, Option<u8>);
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Move {
|
||||||
/// Apply a move `m` to copies to `board` and `game_state`.
|
/// Square from which a piece moves.
|
||||||
///
|
pub source: Square,
|
||||||
/// Can be used for conveniance but it's better to write in existing
|
/// Square to which a piece moves.
|
||||||
/// instances as often as possible using `apply_move_to`.
|
pub dest: Square,
|
||||||
pub fn apply_move(
|
/// Promotion piece for pawns reaching the last rank.
|
||||||
board: &Board,
|
pub promotion: Option<Piece>,
|
||||||
game_state: &rules::GameState,
|
/// Captured piece, if any.
|
||||||
m: &Move
|
pub capture: Option<Piece>,
|
||||||
) -> (Board, rules::GameState) {
|
/// Castle options before the move. This is set when the move is first applied.
|
||||||
let mut new_board = board.clone();
|
pub old_castles: Castle,
|
||||||
let mut new_state = game_state.clone();
|
|
||||||
apply_move_to(&mut new_board, &mut new_state, m);
|
|
||||||
(new_board, new_state)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update `board` and `game_state` to reflect the move `m`.
|
impl fmt::Debug for Move {
|
||||||
///
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
/// The board is updated with correct piece placement.
|
write!(f, "{}", self.to_uci_string())
|
||||||
///
|
|
||||||
/// The game state is updated with the new player turn and the new
|
|
||||||
/// castling options.
|
|
||||||
pub fn apply_move_to(
|
|
||||||
board: &mut Board,
|
|
||||||
game_state: &mut rules::GameState,
|
|
||||||
m: &Move
|
|
||||||
) {
|
|
||||||
// If a rook is taken, remove its castling option. Needs to be checked before we update board.
|
|
||||||
if m.1 == pos("a1") && get_square(board, &pos("a1")) == SQ_WH_R {
|
|
||||||
game_state.castling &= !CASTLING_WH_Q;
|
|
||||||
} else if m.1 == pos("h1") && get_square(board, &pos("h1")) == SQ_WH_R {
|
|
||||||
game_state.castling &= !CASTLING_WH_K;
|
|
||||||
} else if m.1 == pos("a8") && get_square(board, &pos("a8")) == SQ_BL_R {
|
|
||||||
game_state.castling &= !CASTLING_BL_Q;
|
|
||||||
} else if m.1 == pos("h8") && get_square(board, &pos("h8")) == SQ_BL_R {
|
|
||||||
game_state.castling &= !CASTLING_BL_K;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update board and game state.
|
|
||||||
apply_move_to_board(board, m);
|
|
||||||
game_state.color = opposite(game_state.color);
|
|
||||||
|
|
||||||
// If the move is a castle, remove it from castling options.
|
|
||||||
if let Some(castle) = get_castle(m) {
|
|
||||||
match castle {
|
|
||||||
CASTLING_WH_K | CASTLING_WH_Q => game_state.castling &= !CASTLING_WH_MASK,
|
|
||||||
CASTLING_BL_K | CASTLING_BL_Q => game_state.castling &= !CASTLING_BL_MASK,
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Else, check if the king or a rook moved to update castling options.
|
|
||||||
else {
|
|
||||||
let piece = get_square(board, &m.1);
|
|
||||||
if is_white(piece) && game_state.castling & CASTLING_WH_MASK != 0 {
|
|
||||||
match get_type(piece) {
|
|
||||||
SQ_K => {
|
|
||||||
if m.0 == pos("e1") {
|
|
||||||
game_state.castling &= !CASTLING_WH_MASK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SQ_R => {
|
|
||||||
if m.0 == pos("a1") {
|
|
||||||
game_state.castling &= !CASTLING_WH_Q;
|
|
||||||
} else if m.0 == pos("h1") {
|
|
||||||
game_state.castling &= !CASTLING_WH_K;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else if is_black(piece) && game_state.castling & CASTLING_BL_MASK != 0 {
|
|
||||||
match get_type(piece) {
|
|
||||||
SQ_K => {
|
|
||||||
if m.0 == pos("e8") {
|
|
||||||
game_state.castling &= !CASTLING_BL_MASK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SQ_R => {
|
|
||||||
if m.0 == pos("a8") {
|
|
||||||
game_state.castling &= !CASTLING_BL_Q;
|
|
||||||
} else if m.0 == pos("h8") {
|
|
||||||
game_state.castling &= !CASTLING_BL_K;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a move `m` into `board`.
|
/// Null move string in UCI exchanges.
|
||||||
pub fn apply_move_to_board(board: &mut Board, m: &Move) {
|
pub const UCI_NULL_MOVE_STR: &str = "0000";
|
||||||
if let Some(castle) = get_castle(m) {
|
|
||||||
match castle {
|
|
||||||
CASTLING_WH_K => {
|
|
||||||
move_piece(board, &START_WH_K_POS, &pos("g1"));
|
|
||||||
move_piece(board, &pos("h1"), &pos("f1"));
|
|
||||||
}
|
|
||||||
CASTLING_WH_Q => {
|
|
||||||
move_piece(board, &START_WH_K_POS, &pos("c1"));
|
|
||||||
move_piece(board, &pos("a1"), &pos("d1"));
|
|
||||||
}
|
|
||||||
CASTLING_BL_K => {
|
|
||||||
move_piece(board, &START_BL_K_POS, &pos("g8"));
|
|
||||||
move_piece(board, &pos("h8"), &pos("f8"));
|
|
||||||
}
|
|
||||||
CASTLING_BL_Q => {
|
|
||||||
move_piece(board, &START_BL_K_POS, &pos("c8"));
|
|
||||||
move_piece(board, &pos("a8"), &pos("d8"));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
move_piece(board, &m.0, &m.1);
|
|
||||||
if let Some(prom_type) = m.2 {
|
|
||||||
let color = get_color(get_square(board, &m.1));
|
|
||||||
set_square(board, &m.1, color|prom_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the corresponding castling flag for this move.
|
impl Move {
|
||||||
pub fn get_castle(m: &Move) -> Option<u8> {
|
/// Build a move from `source` to `dest`, no promotion.
|
||||||
if m.0 == pos("e1") {
|
pub const fn new(source: Square, dest: Square) -> Move {
|
||||||
if m.1 == pos("c1") {
|
Move { source, dest, promotion: None, capture: None, old_castles: 0 }
|
||||||
Some(CASTLING_WH_Q)
|
}
|
||||||
} else if m.1 == pos("g1") {
|
|
||||||
Some(CASTLING_WH_K)
|
/// Build a move from `source` to `dest`, with a promotion.
|
||||||
|
pub const fn new_promotion(source: Square, dest: Square, promotion: Piece) -> Move {
|
||||||
|
Move { source, dest, promotion: Some(promotion), capture: None, old_castles: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply this move to `board` and `game_state`.
|
||||||
|
///
|
||||||
|
/// Set automatic queen promotion for pawns, register captured
|
||||||
|
/// pieces and castle options.
|
||||||
|
pub fn apply_to(&mut self, board: &mut Board, game_state: &mut GameState) -> ZobristHash {
|
||||||
|
// Changes to a Zobrist hash due to this move.
|
||||||
|
let mut changes: ZobristHash = 0;
|
||||||
|
// Save current castling options to unmake later.
|
||||||
|
self.old_castles = game_state.castling;
|
||||||
|
|
||||||
|
let piece = board.get_piece_on(self.source);
|
||||||
|
if piece == KING {
|
||||||
|
// Handle king castling.
|
||||||
|
if let Some(castle) = self.get_castle() {
|
||||||
|
match castle {
|
||||||
|
CASTLE_WH_K => {
|
||||||
|
board.move_square(E1, G1);
|
||||||
|
board.move_square(H1, F1);
|
||||||
|
game_state.castling &= !CASTLE_WH_MASK;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_K];
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_Q];
|
||||||
|
}
|
||||||
|
CASTLE_WH_Q => {
|
||||||
|
board.move_square(E1, C1);
|
||||||
|
board.move_square(A1, D1);
|
||||||
|
game_state.castling &= !CASTLE_WH_MASK;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_K];
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_Q];
|
||||||
|
}
|
||||||
|
CASTLE_BL_K => {
|
||||||
|
board.move_square(E8, G8);
|
||||||
|
board.move_square(H8, F8);
|
||||||
|
game_state.castling &= !CASTLE_BL_MASK;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_K];
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_Q];
|
||||||
|
}
|
||||||
|
CASTLE_BL_Q => {
|
||||||
|
board.move_square(E8, C8);
|
||||||
|
board.move_square(A8, D8);
|
||||||
|
game_state.castling &= !CASTLE_BL_MASK;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_K];
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_Q];
|
||||||
|
}
|
||||||
|
_ => { panic!("Invalid castle.") }
|
||||||
|
}
|
||||||
|
game_state.color = opposite(game_state.color);
|
||||||
|
changes ^= ZOBRIST_BLACK_TURN;
|
||||||
|
return changes
|
||||||
|
} else {
|
||||||
|
// If the king moved from starting square, remove it from castling options.
|
||||||
|
if self.source == E1 {
|
||||||
|
game_state.castling &= !CASTLE_WH_MASK;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_K];
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_Q];
|
||||||
|
}
|
||||||
|
else if self.source == E8 {
|
||||||
|
game_state.castling &= !CASTLE_BL_MASK;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_K];
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_Q];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record captured piece if any.
|
||||||
|
if !board.is_empty(self.dest) {
|
||||||
|
let captured_piece = board.get_piece_on(self.dest);
|
||||||
|
changes ^= get_piece_hash(opposite(game_state.color), captured_piece, self.dest);
|
||||||
|
self.capture = Some(captured_piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the piece and apply promotion if any.
|
||||||
|
board.move_square(self.source, self.dest);
|
||||||
|
changes ^= get_piece_hash(game_state.color, piece, self.source);
|
||||||
|
if let Some(promotion_piece) = self.promotion {
|
||||||
|
board.set_piece(self.dest, PAWN, promotion_piece);
|
||||||
|
changes ^= get_piece_hash(game_state.color, promotion_piece, self.dest);
|
||||||
} else {
|
} else {
|
||||||
None
|
changes ^= get_piece_hash(game_state.color, piece, self.dest);
|
||||||
}
|
}
|
||||||
} else if m.0 == pos("e8") {
|
|
||||||
if m.1 == pos("c8") {
|
|
||||||
Some(CASTLING_BL_Q)
|
|
||||||
} else if m.1 == pos("g8") {
|
|
||||||
Some(CASTLING_BL_K)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the move for this castle.
|
// If a rook moved, remove the castle side.
|
||||||
pub fn get_castle_move(castle: u8) -> Move {
|
if self.source == A1 || self.dest == A1 {
|
||||||
match castle {
|
game_state.castling &= !CASTLE_WH_Q;
|
||||||
CASTLING_WH_Q => (pos("e1"), pos("c1"), None),
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_Q];
|
||||||
CASTLING_WH_K => (pos("e1"), pos("g1"), None),
|
}
|
||||||
CASTLING_BL_Q => (pos("e8"), pos("c8"), None),
|
else if self.source == H1 || self.dest == H1 {
|
||||||
CASTLING_BL_K => (pos("e8"), pos("g8"), None),
|
game_state.castling &= !CASTLE_WH_K;
|
||||||
_ => panic!("Illegal castling requested: {:08b}", castle),
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_K];
|
||||||
|
}
|
||||||
|
else if self.source == A8 || self.dest == A8 {
|
||||||
|
game_state.castling &= !CASTLE_BL_Q;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_Q];
|
||||||
|
}
|
||||||
|
else if self.source == H8 || self.dest == H8 {
|
||||||
|
game_state.castling &= !CASTLE_BL_K;
|
||||||
|
changes ^= ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_K];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, switch to the opposing player in the game state.
|
||||||
|
game_state.color = opposite(game_state.color);
|
||||||
|
changes ^= ZOBRIST_BLACK_TURN;
|
||||||
|
|
||||||
|
// Return changes as a Zobrist hash.
|
||||||
|
changes
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unmake a move.
|
||||||
|
pub fn unmake(&self, board: &mut Board, game_state: &mut GameState) {
|
||||||
|
// Always restore previous castle options.
|
||||||
|
game_state.castling = self.old_castles;
|
||||||
|
// If the move is a castle, unmake it properly.
|
||||||
|
let piece = board.get_piece_on(self.dest);
|
||||||
|
if piece == KING {
|
||||||
|
if let Some(castle) = self.get_castle() {
|
||||||
|
match castle {
|
||||||
|
CASTLE_WH_K => { board.move_square(G1, E1); board.move_square(F1, H1); }
|
||||||
|
CASTLE_WH_Q => { board.move_square(C1, E1); board.move_square(D1, A1); }
|
||||||
|
CASTLE_BL_K => { board.move_square(G8, E8); board.move_square(F8, H8); }
|
||||||
|
CASTLE_BL_Q => { board.move_square(C8, E8); board.move_square(D8, A8); }
|
||||||
|
_ => { panic!("Invalid castle.") }
|
||||||
|
}
|
||||||
|
game_state.color = opposite(game_state.color);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move the piece back.
|
||||||
|
board.move_square(self.dest, self.source);
|
||||||
|
|
||||||
|
// Cancel the promotion.
|
||||||
|
if let Some(piece) = self.promotion {
|
||||||
|
board.set_piece(self.source, piece, PAWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore captured piece.
|
||||||
|
if let Some(piece) = self.capture {
|
||||||
|
board.set_square(self.dest, game_state.color, piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And switch back to previous player.
|
||||||
|
game_state.color = opposite(game_state.color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the corresponding castling flag for this move.
|
||||||
|
pub fn get_castle(&self) -> Option<Castle> {
|
||||||
|
match (self.source, self.dest) {
|
||||||
|
(E1, C1) => Some(CASTLE_WH_Q),
|
||||||
|
(E1, G1) => Some(CASTLE_WH_K),
|
||||||
|
(E8, C8) => Some(CASTLE_BL_Q),
|
||||||
|
(E8, G8) => Some(CASTLE_BL_K),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the move for this castle.
|
||||||
|
pub fn get_castle_move(castle: u8) -> Move {
|
||||||
|
match castle {
|
||||||
|
CASTLE_WH_Q => Move::new(E1, C1),
|
||||||
|
CASTLE_WH_K => Move::new(E1, G1),
|
||||||
|
CASTLE_BL_Q => Move::new(E8, C8),
|
||||||
|
CASTLE_BL_K => Move::new(E8, G8),
|
||||||
|
_ => panic!("Illegal castling requested: {:08b}", castle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse an UCI move algebraic notation string to a Move.
|
||||||
|
pub fn from_uci_string(m_str: &str) -> Move {
|
||||||
|
Move {
|
||||||
|
source: sq_from_string(&m_str[0..2]),
|
||||||
|
dest: sq_from_string(&m_str[2..4]),
|
||||||
|
promotion: if m_str.len() == 5 {
|
||||||
|
Some(match m_str.as_bytes()[4] {
|
||||||
|
b'b' => BISHOP,
|
||||||
|
b'n' => KNIGHT,
|
||||||
|
b'r' => ROOK,
|
||||||
|
b'q' => QUEEN,
|
||||||
|
_ => panic!("What is the opponent doing? This is illegal, I'm out."),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
capture: None,
|
||||||
|
old_castles: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a string containing the UCI algebraic notation of this move.
|
||||||
|
pub fn to_uci_string(&self) -> String {
|
||||||
|
let mut move_string = String::new();
|
||||||
|
move_string.push_str(&sq_to_string(self.source));
|
||||||
|
move_string.push_str(&sq_to_string(self.dest));
|
||||||
|
if let Some(piece) = self.promotion {
|
||||||
|
move_string.push(match piece {
|
||||||
|
QUEEN => 'q',
|
||||||
|
BISHOP => 'b',
|
||||||
|
KNIGHT => 'n',
|
||||||
|
ROOK => 'r',
|
||||||
|
_ => panic!("What are you doing? Promote to a legal piece.")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
move_string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Debug only: create a space-separated string of moves.
|
||||||
|
pub(crate) fn list_to_uci_string(moves: &Vec<Move>) -> String {
|
||||||
|
moves.iter().map(|m| m.to_uci_string()).collect::<Vec<_>>().join(" ")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::notation::parse_move;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_move_to_board() {
|
fn test_apply_to_board() {
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
// Put 2 enemy knights on board.
|
// Put 2 enemy knights on board.
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_N);
|
b.set_square(D4, WHITE, KNIGHT);
|
||||||
set_square(&mut b, &pos("f4"), SQ_BL_N);
|
b.set_square(F4, BLACK, KNIGHT);
|
||||||
// Move white knight in a position attacked by black knight.
|
// Move white knight in a position attacked by black knight.
|
||||||
apply_move_to_board(&mut b, &(pos("d4"), pos("e6"), None));
|
let mut m = Move::new(D4, E6);
|
||||||
assert_eq!(get_square(&b, &pos("d4")), SQ_E);
|
m.apply_to(&mut b, &mut gs);
|
||||||
assert_eq!(get_square(&b, &pos("e6")), SQ_WH_N);
|
assert!(b.is_empty(D4));
|
||||||
assert_eq!(num_pieces(&b), 2);
|
assert_eq!(b.get_color_on(E6), WHITE);
|
||||||
|
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
||||||
|
assert_eq!(count_bits(b.combined()), 2);
|
||||||
|
assert!(m.capture.is_none());
|
||||||
// Sack it with black knight
|
// Sack it with black knight
|
||||||
apply_move_to_board(&mut b, &(pos("f4"), pos("e6"), None));
|
let mut m = Move::new(F4, E6);
|
||||||
assert_eq!(get_square(&b, &pos("e6")), SQ_BL_N);
|
m.apply_to(&mut b, &mut gs);
|
||||||
assert_eq!(num_pieces(&b), 1);
|
assert_eq!(b.get_color_on(E6), BLACK);
|
||||||
|
assert_eq!(b.get_piece_on(E6), KNIGHT);
|
||||||
|
assert_eq!(count_bits(b.combined()), 1);
|
||||||
|
assert_eq!(m.capture.unwrap(), KNIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_apply_move_to_castling() {
|
fn test_apply_to_castling() {
|
||||||
let mut b = new();
|
let mut b = Board::new();
|
||||||
let mut gs = rules::GameState::new();
|
let mut gs = GameState::new();
|
||||||
assert_eq!(gs.castling, CASTLING_MASK);
|
assert_eq!(gs.castling, CASTLE_MASK);
|
||||||
|
|
||||||
// On a starting board, start by making place for all castles.
|
// On a starting board, start by making place for all castles.
|
||||||
clear_square(&mut b, &pos("b1"));
|
b.clear_square(B1, WHITE, KNIGHT);
|
||||||
clear_square(&mut b, &pos("c1"));
|
b.clear_square(C1, WHITE, BISHOP);
|
||||||
clear_square(&mut b, &pos("d1"));
|
b.clear_square(D1, WHITE, QUEEN);
|
||||||
clear_square(&mut b, &pos("f1"));
|
b.clear_square(F1, WHITE, BISHOP);
|
||||||
clear_square(&mut b, &pos("g1"));
|
b.clear_square(G1, WHITE, KNIGHT);
|
||||||
clear_square(&mut b, &pos("b8"));
|
b.clear_square(B8, BLACK, KNIGHT);
|
||||||
clear_square(&mut b, &pos("c8"));
|
b.clear_square(C8, BLACK, BISHOP);
|
||||||
clear_square(&mut b, &pos("d8"));
|
b.clear_square(D8, BLACK, QUEEN);
|
||||||
clear_square(&mut b, &pos("f8"));
|
b.clear_square(F8, BLACK, BISHOP);
|
||||||
clear_square(&mut b, &pos("g8"));
|
b.clear_square(G8, BLACK, KNIGHT);
|
||||||
// White queen-side castling.
|
// White queen-side castling.
|
||||||
apply_move_to(&mut b, &mut gs, &parse_move("e1c1"));
|
Move::new(E1, C1).apply_to(&mut b, &mut gs);
|
||||||
assert!(is_piece(get_square(&b, &pos("c1")), SQ_WH_K));
|
assert_eq!(b.get_color_on(C1), WHITE);
|
||||||
assert!(is_piece(get_square(&b, &pos("d1")), SQ_WH_R));
|
assert_eq!(b.get_piece_on(C1), KING);
|
||||||
assert!(is_empty(&b, &pos("a1")));
|
assert_eq!(b.get_color_on(D1), WHITE);
|
||||||
assert!(is_empty(&b, &pos("e1")));
|
assert_eq!(b.get_piece_on(D1), ROOK);
|
||||||
assert_eq!(gs.castling, CASTLING_BL_MASK);
|
assert!(b.is_empty(A1));
|
||||||
|
assert!(b.is_empty(E1));
|
||||||
|
assert_eq!(gs.castling, CASTLE_BL_MASK);
|
||||||
// Black king-side castling.
|
// Black king-side castling.
|
||||||
apply_move_to(&mut b, &mut gs, &parse_move("e8g8"));
|
Move::new(E8, G8).apply_to(&mut b, &mut gs);
|
||||||
assert!(is_piece(get_square(&b, &pos("g8")), SQ_BL_K));
|
assert_eq!(b.get_color_on(G8), BLACK);
|
||||||
assert!(is_piece(get_square(&b, &pos("f8")), SQ_BL_R));
|
assert_eq!(b.get_piece_on(G8), KING);
|
||||||
assert!(is_empty(&b, &pos("h8")));
|
assert_eq!(b.get_color_on(F8), BLACK);
|
||||||
assert!(is_empty(&b, &pos("e8")));
|
assert_eq!(b.get_piece_on(F8), ROOK);
|
||||||
|
assert!(b.is_empty(H8));
|
||||||
|
assert!(b.is_empty(E8));
|
||||||
|
// At the end, no more castling options for both sides.
|
||||||
assert_eq!(gs.castling, 0);
|
assert_eq!(gs.castling, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unmake() {
|
||||||
|
let mut b = Board::new_empty();
|
||||||
|
let mut gs = GameState::new();
|
||||||
|
|
||||||
|
// On unmaking a move, the white pawn is back to its original square.
|
||||||
|
b.set_square(D4, WHITE, PAWN);
|
||||||
|
let mut m = Move::new(D4, D5);
|
||||||
|
m.apply_to(&mut b, &mut gs);
|
||||||
|
m.unmake(&mut b, &mut gs);
|
||||||
|
assert!(b.is_empty(D5));
|
||||||
|
assert_eq!(b.get_color_on(D4), WHITE);
|
||||||
|
assert_eq!(b.get_piece_on(D4), PAWN);
|
||||||
|
|
||||||
|
// Castle options should be properly unmade.
|
||||||
|
b.set_square(E1, WHITE, KING);
|
||||||
|
b.set_square(H1, WHITE, ROOK);
|
||||||
|
let mut m = Move::new(E1, G1);
|
||||||
|
m.apply_to(&mut b, &mut gs);
|
||||||
|
assert!(!b.is_empty(G1));
|
||||||
|
assert!(!b.is_empty(F1));
|
||||||
|
assert_eq!(gs.castling, CASTLE_MASK ^ CASTLE_WH_MASK);
|
||||||
|
m.unmake(&mut b, &mut gs);
|
||||||
|
assert!(b.is_empty(G1));
|
||||||
|
assert!(b.is_empty(F1));
|
||||||
|
assert_eq!(b.get_piece_on(E1), KING);
|
||||||
|
assert_eq!(b.get_piece_on(H1), ROOK);
|
||||||
|
assert_eq!(gs.castling, CASTLE_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_castle() {
|
fn test_get_castle() {
|
||||||
assert_eq!(get_castle(&parse_move("e1c1")), Some(CASTLING_WH_Q));
|
assert_eq!(Move::new(E1, C1).get_castle(), Some(CASTLE_WH_Q));
|
||||||
assert_eq!(get_castle(&parse_move("e1g1")), Some(CASTLING_WH_K));
|
assert_eq!(Move::new(E1, G1).get_castle(), Some(CASTLE_WH_K));
|
||||||
assert_eq!(get_castle(&parse_move("e8c8")), Some(CASTLING_BL_Q));
|
assert_eq!(Move::new(E8, C8).get_castle(), Some(CASTLE_BL_Q));
|
||||||
assert_eq!(get_castle(&parse_move("e8g8")), Some(CASTLING_BL_K));
|
assert_eq!(Move::new(E8, G8).get_castle(), Some(CASTLE_BL_K));
|
||||||
assert_eq!(get_castle(&parse_move("d2d4")), None);
|
assert_eq!(Move::new(D2, D4).get_castle(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_uci_string() {
|
||||||
|
assert_eq!(Move::new(A1, D4).to_uci_string(), "a1d4");
|
||||||
|
assert_eq!(Move::new(H8, A8).to_uci_string(), "h8a8");
|
||||||
|
assert_eq!(Move::new_promotion(H7, H8, QUEEN).to_uci_string(), "h7h8q");
|
||||||
|
assert_eq!(Move::new_promotion(H7, H8, KNIGHT).to_uci_string(), "h7h8n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_uci_string() {
|
||||||
|
assert_eq!(Move::from_uci_string("a1d4"), Move::new(A1, D4));
|
||||||
|
assert_eq!(Move::from_uci_string("a7a8q"), Move::new_promotion(A7, A8, QUEEN));
|
||||||
|
assert_eq!(Move::from_uci_string("a7a8r"), Move::new_promotion(A7, A8, ROOK));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
55
src/node.rs
55
src/node.rs
|
@ -1,42 +1,52 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
|
|
||||||
use crate::board;
|
use crate::board;
|
||||||
use crate::movement::{self, Move};
|
use crate::movement::Move;
|
||||||
use crate::rules;
|
use crate::rules;
|
||||||
use crate::stats;
|
use crate::stats;
|
||||||
|
use crate::zobrist;
|
||||||
|
|
||||||
/// Analysis node: a board along with the game state.
|
/// Analysis node: a board along with the game state.
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Node {
|
pub struct Node {
|
||||||
/// Board for this node.
|
/// Board for this node.
|
||||||
pub board: board::Board,
|
pub board: board::Board,
|
||||||
/// Game state.
|
/// Game state.
|
||||||
pub game_state: rules::GameState,
|
pub game_state: rules::GameState,
|
||||||
|
/// Zobrist hash of the node.
|
||||||
|
pub hash: zobrist::ZobristHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node {
|
impl Node {
|
||||||
/// Create a new node for an empty board and a new game state.
|
/// Create a new node for an empty board and a new game state.
|
||||||
pub fn new() -> Node {
|
pub fn new() -> Node {
|
||||||
Node {
|
Node {
|
||||||
board: board::new_empty(),
|
board: board::Board::new_empty(),
|
||||||
game_state: rules::GameState::new(),
|
game_state: rules::GameState::new(),
|
||||||
|
hash: zobrist::get_new_game_hash(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a move to this node.
|
/// Apply a move to this node.
|
||||||
pub fn apply_move(&mut self, m: &Move) {
|
pub fn apply_move(&mut self, m: &mut Move) -> zobrist::ZobristHash {
|
||||||
movement::apply_move_to(&mut self.board, &mut self.game_state, m);
|
let changes = m.apply_to(&mut self.board, &mut self.game_state);
|
||||||
|
self.hash ^= changes;
|
||||||
|
changes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unmake_move(&mut self, m: &Move, changes: zobrist::ZobristHash) {
|
||||||
|
m.unmake(&mut self.board, &mut self.game_state);
|
||||||
|
self.hash ^= changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return player moves from this node.
|
/// Return player moves from this node.
|
||||||
pub fn get_player_moves(&self, commit: bool) -> Vec<Move> {
|
pub fn get_player_moves(&mut self) -> Vec<Move> {
|
||||||
rules::get_player_moves(&self.board, &self.game_state, commit)
|
rules::get_player_moves(&mut self.board, &mut self.game_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute stats for both players for this node.
|
/// Compute stats for both players for this node.
|
||||||
pub fn compute_stats(&self) -> (stats::BoardStats, stats::BoardStats) {
|
pub fn compute_stats(&mut self) -> (stats::BoardStats, stats::BoardStats) {
|
||||||
stats::compute_stats(&self.board, &self.game_state)
|
stats::BoardStats::new_from(&mut self.board, &mut self.game_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,29 +63,8 @@ impl fmt::Debug for Node {
|
||||||
impl fmt::Display for Node {
|
impl fmt::Display for Node {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let mut s = vec!();
|
let mut s = vec!();
|
||||||
board::draw(&self.board, &mut s);
|
self.board.draw_to(&mut s);
|
||||||
let board_drawing = String::from_utf8_lossy(&s).to_string();
|
let board_drawing = String::from_utf8_lossy(&s).to_string();
|
||||||
write!(
|
write!(f, "{}{}", board_drawing, self.game_state)
|
||||||
f,
|
|
||||||
"* Board:\n{}\n\
|
|
||||||
* Game state:\n{}",
|
|
||||||
board_drawing, self.game_state
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for Node {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.board.iter().zip(other.board.iter()).all(|(a, b)| a == b)
|
|
||||||
&& self.game_state == other.game_state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for Node {}
|
|
||||||
|
|
||||||
impl Hash for Node {
|
|
||||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
|
||||||
self.board.iter().for_each(|square| state.write_u8(*square));
|
|
||||||
self.game_state.hash(state);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
111
src/notation.rs
111
src/notation.rs
|
@ -1,111 +0,0 @@
|
||||||
//! Functions using various notations.
|
|
||||||
|
|
||||||
use crate::board::*;
|
|
||||||
use crate::movement::Move;
|
|
||||||
|
|
||||||
pub const NULL_MOVE: &str = "0000";
|
|
||||||
|
|
||||||
/// Create a string containing the UCI algebraic notation of this move.
|
|
||||||
pub fn move_to_string(m: &Move) -> String {
|
|
||||||
let mut move_string = String::new();
|
|
||||||
move_string.push_str(&pos_string(&m.0));
|
|
||||||
move_string.push_str(&pos_string(&m.1));
|
|
||||||
if let Some(prom) = m.2 {
|
|
||||||
move_string.push(match prom {
|
|
||||||
SQ_Q => 'q',
|
|
||||||
SQ_B => 'b',
|
|
||||||
SQ_N => 'n',
|
|
||||||
SQ_R => 'r',
|
|
||||||
_ => panic!("What are you doing? Promote to a legal piece.")
|
|
||||||
});
|
|
||||||
}
|
|
||||||
move_string
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse an UCI move algebraic notation string to a Move.
|
|
||||||
pub fn parse_move(m_str: &str) -> Move {
|
|
||||||
let prom = if m_str.len() == 5 {
|
|
||||||
Some(match m_str.as_bytes()[4] {
|
|
||||||
b'b' => SQ_B,
|
|
||||||
b'n' => SQ_N,
|
|
||||||
b'r' => SQ_R,
|
|
||||||
b'q' => SQ_Q,
|
|
||||||
_ => panic!("What is the opponent doing? This is illegal, I'm out."),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
(pos(&m_str[0..2]), pos(&m_str[2..4]), prom)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a space-separated string of moves. Used for debugging.
|
|
||||||
pub fn move_list_to_string(moves: &Vec<Move>) -> String {
|
|
||||||
moves.iter().map(|m| move_to_string(m)).collect::<Vec<_>>().join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FEN_START: &str = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
||||||
|
|
||||||
/// FEN notation for positions, split into fields.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Fen {
|
|
||||||
pub placement: String,
|
|
||||||
pub color: String,
|
|
||||||
pub castling: String,
|
|
||||||
pub en_passant: String,
|
|
||||||
pub halfmove: String,
|
|
||||||
pub fullmove: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_fen(i: &str) -> Option<Fen> {
|
|
||||||
let fields: Vec<&str> = i.split_whitespace().collect();
|
|
||||||
parse_fen_fields(&fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_fen_fields(fields: &[&str]) -> Option<Fen> {
|
|
||||||
if fields.len() < 6 {
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
Some(Fen {
|
|
||||||
placement: fields[0].to_string(),
|
|
||||||
color: fields[1].to_string(),
|
|
||||||
castling: fields[2].to_string(),
|
|
||||||
en_passant: fields[3].to_string(),
|
|
||||||
halfmove: fields[4].to_string(),
|
|
||||||
fullmove: fields[5].to_string(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn en_passant_to_string(ep: Option<Pos>) -> String {
|
|
||||||
ep.and_then(|p| Some(pos_string(&p))).unwrap_or("-".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_move_to_string() {
|
|
||||||
assert_eq!(move_to_string(&((0, 0), (3, 3), None)), "a1d4");
|
|
||||||
assert_eq!(move_to_string(&((7, 7), (0, 7), None)), "h8a8");
|
|
||||||
assert_eq!(move_to_string(&((7, 6), (7, 7), Some(SQ_Q))), "h7h8q");
|
|
||||||
assert_eq!(move_to_string(&((7, 6), (7, 7), Some(SQ_N))), "h7h8n");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_move() {
|
|
||||||
assert_eq!(parse_move("a1d4"), ((0, 0), (3, 3), None));
|
|
||||||
assert_eq!(parse_move("a7a8q"), ((0, 6), (0, 7), Some(SQ_Q)));
|
|
||||||
assert_eq!(parse_move("a7a8r"), ((0, 6), (0, 7), Some(SQ_R)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_fen() {
|
|
||||||
let fen_start = parse_fen(FEN_START).unwrap();
|
|
||||||
assert_eq!(&fen_start.placement, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
|
|
||||||
assert_eq!(&fen_start.color, "w");
|
|
||||||
assert_eq!(&fen_start.castling, "KQkq");
|
|
||||||
assert_eq!(&fen_start.en_passant, "-");
|
|
||||||
assert_eq!(&fen_start.halfmove, "0");
|
|
||||||
assert_eq!(&fen_start.fullmove, "1");
|
|
||||||
}
|
|
||||||
}
|
|
1308
src/precomputed.rs
Normal file
1308
src/precomputed.rs
Normal file
File diff suppressed because it is too large
Load diff
878
src/rules.rs
878
src/rules.rs
File diff suppressed because it is too large
Load diff
264
src/stats.rs
264
src/stats.rs
|
@ -1,7 +1,7 @@
|
||||||
//! Board statistics used for heuristics.
|
//! Board statistics used for heuristics.
|
||||||
|
|
||||||
use crate::board::*;
|
use crate::board::*;
|
||||||
use crate::rules;
|
use crate::rules::{GameState, get_player_moves};
|
||||||
|
|
||||||
/// Storage for board pieces stats.
|
/// Storage for board pieces stats.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
@ -27,6 +27,20 @@ impl BoardStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create two new BoardStats objects from the board, for both sides.
|
||||||
|
///
|
||||||
|
/// The playing color will have its stats filled in the first
|
||||||
|
/// BoardStats object, its opponent in the second.
|
||||||
|
pub fn new_from(board: &mut Board, game_state: &mut GameState) -> (BoardStats, BoardStats) {
|
||||||
|
let mut stats = (BoardStats::new(), BoardStats::new());
|
||||||
|
let mut gs = game_state.clone();
|
||||||
|
stats.0.compute(board, &mut gs);
|
||||||
|
gs.color = opposite(gs.color);
|
||||||
|
stats.1.compute(board, &mut gs);
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reset all stats to 0.
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.num_pawns = 0;
|
self.num_pawns = 0;
|
||||||
self.num_bishops = 0;
|
self.num_bishops = 0;
|
||||||
|
@ -39,6 +53,88 @@ impl BoardStats {
|
||||||
self.num_isolated_pawns = 0;
|
self.num_isolated_pawns = 0;
|
||||||
self.mobility = 0;
|
self.mobility = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fill `stats` from given `board` and `game_state`.
|
||||||
|
///
|
||||||
|
/// Only the current playing side stats are created,
|
||||||
|
/// prepare the game_state accordingly.
|
||||||
|
pub fn compute(&mut self, board: &mut Board, game_state: &mut GameState) {
|
||||||
|
self.reset();
|
||||||
|
let color = game_state.color;
|
||||||
|
// Compute mobility for all pieces.
|
||||||
|
self.mobility = get_player_moves(board, game_state).len() as i32;
|
||||||
|
// Compute amount of each piece.
|
||||||
|
for file in 0..8 {
|
||||||
|
for rank in 0..8 {
|
||||||
|
let square = sq(file, rank);
|
||||||
|
if board.is_empty(square) || board.get_color_on(square) != color {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
match board.get_piece_on(square) {
|
||||||
|
ROOK => self.num_rooks += 1,
|
||||||
|
KNIGHT => self.num_knights += 1,
|
||||||
|
BISHOP => self.num_bishops += 1,
|
||||||
|
QUEEN => self.num_queens += 1,
|
||||||
|
KING => self.num_kings += 1,
|
||||||
|
PAWN => {
|
||||||
|
self.num_pawns += 1;
|
||||||
|
let pawn_bb = board.by_color_and_piece(color, PAWN);
|
||||||
|
|
||||||
|
// Check for doubled pawns.
|
||||||
|
let file_bb = FILES[file as usize];
|
||||||
|
if (pawn_bb ^ bit_pos(square)) & file_bb != 0 {
|
||||||
|
self.num_doubled_pawns += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for isolated and backward pawns.
|
||||||
|
let (iso_on_prev_file, bw_on_prev_file) = if file > FILE_A {
|
||||||
|
self.find_isolated_and_backward(pawn_bb, square, color, file - 1)
|
||||||
|
} else {
|
||||||
|
(true, true)
|
||||||
|
};
|
||||||
|
let (iso_on_next_file, bw_on_next_file) = if file < FILE_H {
|
||||||
|
self.find_isolated_and_backward(pawn_bb, square, color, file + 1)
|
||||||
|
} else {
|
||||||
|
(true, true)
|
||||||
|
};
|
||||||
|
if iso_on_prev_file && iso_on_next_file {
|
||||||
|
self.num_isolated_pawns += 1;
|
||||||
|
}
|
||||||
|
if bw_on_prev_file && bw_on_next_file {
|
||||||
|
self.num_backward_pawns += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find isolated and backward pawns from `square` perspective.
|
||||||
|
///
|
||||||
|
/// `bb` is the bitboard of `color`. `square` is only used to have
|
||||||
|
/// the reference rank. `file` is the file to inspect. To detect
|
||||||
|
/// isolated and backward pawns, `bb` should be the bitboard of
|
||||||
|
/// pawns of `color`.
|
||||||
|
fn find_isolated_and_backward(
|
||||||
|
&mut self,
|
||||||
|
bb: Bitboard,
|
||||||
|
square: Square,
|
||||||
|
color: Color,
|
||||||
|
file: i8
|
||||||
|
) -> (bool, bool) {
|
||||||
|
if bb & FILES[file as usize] == 0 {
|
||||||
|
// If the piece is isolated for this file, it's backward as well.
|
||||||
|
(true, true)
|
||||||
|
} else {
|
||||||
|
let backward_file_bb = if color == WHITE {
|
||||||
|
before_on_file(file, sq_rank(square)) | bit_pos(sq(file, sq_rank(square)))
|
||||||
|
} else {
|
||||||
|
after_on_file(file, sq_rank(square)) | bit_pos(sq(file, sq_rank(square)))
|
||||||
|
};
|
||||||
|
(false, bb & backward_file_bb == 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for BoardStats {
|
impl std::fmt::Display for BoardStats {
|
||||||
|
@ -54,126 +150,6 @@ impl std::fmt::Display for BoardStats {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create two new BoardStats objects from the board, for both sides.
|
|
||||||
///
|
|
||||||
/// See `compute_stats_into` for details.
|
|
||||||
pub fn compute_stats(board: &Board, game_state: &rules::GameState) -> (BoardStats, BoardStats) {
|
|
||||||
let mut stats = (BoardStats::new(), BoardStats::new());
|
|
||||||
compute_stats_into(board, game_state, &mut stats);
|
|
||||||
stats
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute stats for both the current player and its opponent.
|
|
||||||
///
|
|
||||||
/// The playing color will have its stats filled in the first
|
|
||||||
/// BoardStats object, its opponent in the second.
|
|
||||||
pub fn compute_stats_into(
|
|
||||||
board: &Board,
|
|
||||||
game_state: &rules::GameState,
|
|
||||||
stats: &mut (BoardStats, BoardStats)
|
|
||||||
) {
|
|
||||||
let mut gs = game_state.clone();
|
|
||||||
compute_color_stats_into(board, &gs, &mut stats.0);
|
|
||||||
gs.color = opposite(gs.color);
|
|
||||||
compute_color_stats_into(board, &gs, &mut stats.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fill `stats` from given `board` and `game_state`.
|
|
||||||
///
|
|
||||||
/// Only the current playing side stats are created,
|
|
||||||
/// prepare the game_state accordingly.
|
|
||||||
pub fn compute_color_stats_into(
|
|
||||||
board: &Board,
|
|
||||||
game_state: &rules::GameState,
|
|
||||||
stats: &mut BoardStats,
|
|
||||||
) {
|
|
||||||
stats.reset();
|
|
||||||
let color = game_state.color;
|
|
||||||
// Compute mobility for all pieces.
|
|
||||||
stats.mobility = rules::get_player_moves(board, game_state, true).len() as i32;
|
|
||||||
// Compute amount of each piece.
|
|
||||||
for (piece, p) in get_piece_iterator(board) {
|
|
||||||
let (pos_f, pos_r) = p;
|
|
||||||
if piece == SQ_E || !is_color(piece, color) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
match get_type(piece) {
|
|
||||||
SQ_R => stats.num_rooks += 1,
|
|
||||||
SQ_N => stats.num_knights += 1,
|
|
||||||
SQ_B => stats.num_bishops += 1,
|
|
||||||
SQ_Q => stats.num_queens += 1,
|
|
||||||
SQ_K => stats.num_kings += 1,
|
|
||||||
SQ_P => {
|
|
||||||
stats.num_pawns += 1;
|
|
||||||
let mut doubled = false;
|
|
||||||
let mut isolated = true;
|
|
||||||
let mut backward = true;
|
|
||||||
for r in 0..8 {
|
|
||||||
// Check for doubled pawns.
|
|
||||||
if
|
|
||||||
!doubled &&
|
|
||||||
is_piece(get_square(board, &(pos_f, r)), color|SQ_P) && r != pos_r
|
|
||||||
{
|
|
||||||
doubled = true;
|
|
||||||
}
|
|
||||||
// Check for isolated pawns.
|
|
||||||
if
|
|
||||||
isolated &&
|
|
||||||
(
|
|
||||||
// Check on the left file if not on a-file...
|
|
||||||
(
|
|
||||||
pos_f > POS_MIN &&
|
|
||||||
is_piece(get_square(board, &(pos_f - 1, r)), color|SQ_P)
|
|
||||||
) ||
|
|
||||||
// Check on the right file if not on h-file...
|
|
||||||
(
|
|
||||||
pos_f < POS_MAX &&
|
|
||||||
is_piece(get_square(board, &(pos_f + 1, r)), color|SQ_P)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
{
|
|
||||||
isolated = false;
|
|
||||||
}
|
|
||||||
// Check for backward pawns.
|
|
||||||
if backward {
|
|
||||||
if color == SQ_WH && r <= pos_r {
|
|
||||||
if (
|
|
||||||
pos_f > POS_MIN &&
|
|
||||||
is_type(get_square(board, &(pos_f - 1, r)), SQ_P)
|
|
||||||
) || (
|
|
||||||
pos_f < POS_MAX &&
|
|
||||||
is_type(get_square(board, &(pos_f + 1, r)), SQ_P)
|
|
||||||
) {
|
|
||||||
backward = false;
|
|
||||||
}
|
|
||||||
} else if color == SQ_BL && r >= pos_r {
|
|
||||||
if (
|
|
||||||
pos_f > POS_MIN &&
|
|
||||||
is_type(get_square(board, &(pos_f - 1, r)), SQ_P)
|
|
||||||
) || (
|
|
||||||
pos_f < POS_MAX &&
|
|
||||||
is_type(get_square(board, &(pos_f + 1, r)), SQ_P)
|
|
||||||
) {
|
|
||||||
backward = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if doubled {
|
|
||||||
stats.num_doubled_pawns += 1;
|
|
||||||
}
|
|
||||||
if isolated {
|
|
||||||
stats.num_isolated_pawns += 1;
|
|
||||||
}
|
|
||||||
if backward {
|
|
||||||
stats.num_backward_pawns += 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -181,8 +157,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_compute_stats() {
|
fn test_compute_stats() {
|
||||||
// Check that initial stats are correct.
|
// Check that initial stats are correct.
|
||||||
let b = new();
|
let mut b = Board::new();
|
||||||
let gs = rules::GameState::new();
|
let mut gs = GameState::new();
|
||||||
let initial_stats = BoardStats {
|
let initial_stats = BoardStats {
|
||||||
num_pawns: 8,
|
num_pawns: 8,
|
||||||
num_bishops: 2,
|
num_bishops: 2,
|
||||||
|
@ -195,56 +171,54 @@ mod tests {
|
||||||
num_isolated_pawns: 0,
|
num_isolated_pawns: 0,
|
||||||
mobility: 20,
|
mobility: 20,
|
||||||
};
|
};
|
||||||
let mut stats = compute_stats(&b, &gs);
|
let mut stats = BoardStats::new_from(&mut b, &mut gs);
|
||||||
eprintln!("{}", stats.0);
|
|
||||||
eprintln!("{}", stats.1);
|
|
||||||
assert!(stats.0 == stats.1);
|
assert!(stats.0 == stats.1);
|
||||||
assert!(stats.0 == initial_stats);
|
assert!(stats.0 == initial_stats);
|
||||||
|
|
||||||
// Check that doubled pawns are correctly counted.
|
// Check that doubled pawns are correctly counted.
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_P);
|
b.set_square(D4, WHITE, PAWN);
|
||||||
set_square(&mut b, &pos("d6"), SQ_WH_P);
|
b.set_square(D6, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 2);
|
assert_eq!(stats.0.num_doubled_pawns, 2);
|
||||||
// Add a pawn on another file, no changes expected.
|
// Add a pawn on another file, no changes expected.
|
||||||
set_square(&mut b, &pos("e6"), SQ_WH_P);
|
b.set_square(E6, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 2);
|
assert_eq!(stats.0.num_doubled_pawns, 2);
|
||||||
// Add a pawn backward in the d-file: there are now 3 doubled pawns.
|
// Add a pawn backward in the d-file: there are now 3 doubled pawns.
|
||||||
set_square(&mut b, &pos("d2"), SQ_WH_P);
|
b.set_square(D2, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 3);
|
assert_eq!(stats.0.num_doubled_pawns, 3);
|
||||||
|
|
||||||
// Check that isolated and backward pawns are correctly counted.
|
// Check that isolated and backward pawns are correctly counted.
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 2); // A bit weird?
|
assert_eq!(stats.0.num_backward_pawns, 2); // A bit weird?
|
||||||
// Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore.
|
// Protect d4 pawn with a friend in e3: it is not isolated nor backward anymore.
|
||||||
set_square(&mut b, &pos("e3"), SQ_WH_P);
|
b.set_square(E3, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 5);
|
assert_eq!(stats.0.num_doubled_pawns, 5);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 1);
|
assert_eq!(stats.0.num_backward_pawns, 1);
|
||||||
// Add an adjacent friend to d2 pawn: no pawns are left isolated or backward.
|
// Add an adjacent friend to d2 pawn: no pawns are left isolated or backward.
|
||||||
set_square(&mut b, &pos("c2"), SQ_WH_P);
|
b.set_square(C2, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 5);
|
assert_eq!(stats.0.num_doubled_pawns, 5);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 0);
|
assert_eq!(stats.0.num_backward_pawns, 0);
|
||||||
// Add an isolated/backward white pawn in a far file.
|
// Add an isolated/backward white pawn in a far file.
|
||||||
set_square(&mut b, &pos("a2"), SQ_WH_P);
|
b.set_square(A2, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 5);
|
assert_eq!(stats.0.num_doubled_pawns, 5);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 1);
|
assert_eq!(stats.0.num_isolated_pawns, 1);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 1);
|
assert_eq!(stats.0.num_backward_pawns, 1);
|
||||||
|
|
||||||
// Check for pawns that are backward but not isolated.
|
// Check for pawns that are backward but not isolated.
|
||||||
let mut b = new_empty();
|
let mut b = Board::new_empty();
|
||||||
// Here, d4 pawn protects both e5 and e3, but it is backward.
|
// Here, d4 pawn protects both e5 and e3, but it is backward.
|
||||||
set_square(&mut b, &pos("d4"), SQ_WH_P);
|
b.set_square(D4, WHITE, PAWN);
|
||||||
set_square(&mut b, &pos("e5"), SQ_WH_P);
|
b.set_square(E5, WHITE, PAWN);
|
||||||
set_square(&mut b, &pos("e3"), SQ_WH_P);
|
b.set_square(E3, WHITE, PAWN);
|
||||||
compute_color_stats_into(&b, &gs, &mut stats.0);
|
stats.0.compute(&mut b, &mut gs);
|
||||||
assert_eq!(stats.0.num_doubled_pawns, 2);
|
assert_eq!(stats.0.num_doubled_pawns, 2);
|
||||||
assert_eq!(stats.0.num_isolated_pawns, 0);
|
assert_eq!(stats.0.num_isolated_pawns, 0);
|
||||||
assert_eq!(stats.0.num_backward_pawns, 1);
|
assert_eq!(stats.0.num_backward_pawns, 1);
|
||||||
|
|
34
src/uci.rs
34
src/uci.rs
|
@ -7,8 +7,8 @@ use std::thread;
|
||||||
|
|
||||||
use crate::analysis::AnalysisInfo;
|
use crate::analysis::AnalysisInfo;
|
||||||
use crate::engine;
|
use crate::engine;
|
||||||
use crate::movement::Move;
|
use crate::fen;
|
||||||
use crate::notation;
|
use crate::movement::{Move, UCI_NULL_MOVE_STR};
|
||||||
|
|
||||||
const VATU_NAME: &str = env!("CARGO_PKG_NAME");
|
const VATU_NAME: &str = env!("CARGO_PKG_NAME");
|
||||||
const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
const VATU_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
||||||
|
@ -57,6 +57,10 @@ pub enum UciCmd {
|
||||||
Position(Vec<PositionArgs>),
|
Position(Vec<PositionArgs>),
|
||||||
Go(Vec<GoArgs>),
|
Go(Vec<GoArgs>),
|
||||||
Quit,
|
Quit,
|
||||||
|
|
||||||
|
// Unofficial commands mostly for debugging.
|
||||||
|
VatuNode,
|
||||||
|
|
||||||
Unknown(String),
|
Unknown(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +68,7 @@ pub enum UciCmd {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PositionArgs {
|
pub enum PositionArgs {
|
||||||
Startpos,
|
Startpos,
|
||||||
Fen(notation::Fen),
|
Fen(fen::Fen),
|
||||||
Moves(Vec<Move>),
|
Moves(Vec<Move>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +202,9 @@ impl Uci {
|
||||||
self.send_engine_command(engine::Cmd::Stop);
|
self.send_engine_command(engine::Cmd::Stop);
|
||||||
},
|
},
|
||||||
UciCmd::Quit => return false,
|
UciCmd::Quit => return false,
|
||||||
|
UciCmd::VatuNode => {
|
||||||
|
self.send_engine_command(engine::Cmd::LogNode);
|
||||||
|
}
|
||||||
UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); }
|
UciCmd::Unknown(c) => { self.log(format!("Unknown command: {}", c)); }
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -271,7 +278,7 @@ impl Uci {
|
||||||
s.push_str(&format!(" nps {}", n));
|
s.push_str(&format!(" nps {}", n));
|
||||||
}
|
}
|
||||||
AnalysisInfo::CurrentMove(m) => {
|
AnalysisInfo::CurrentMove(m) => {
|
||||||
s.push_str(&format!(" currmove {}", notation::move_to_string(m)));
|
s.push_str(&format!(" currmove {}", m.to_uci_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,11 +287,10 @@ impl Uci {
|
||||||
|
|
||||||
/// Send best move.
|
/// Send best move.
|
||||||
fn send_bestmove(&mut self, m: &Option<Move>) {
|
fn send_bestmove(&mut self, m: &Option<Move>) {
|
||||||
let move_str = match m {
|
self.send(&format!(
|
||||||
Some(m) => notation::move_to_string(m),
|
"bestmove {}",
|
||||||
None => notation::NULL_MOVE.to_string(),
|
if let Some(m) = m { m.to_uci_string() } else { UCI_NULL_MOVE_STR.to_string() }
|
||||||
};
|
));
|
||||||
self.send(&format!("bestmove {}", move_str));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,6 +298,9 @@ impl Uci {
|
||||||
// UCI command parsers
|
// UCI command parsers
|
||||||
|
|
||||||
/// Parse an UCI command.
|
/// Parse an UCI command.
|
||||||
|
///
|
||||||
|
/// Handle main UCI commands: position, go, etc. The command "p" is an
|
||||||
|
/// alias to "position".
|
||||||
fn parse_command(s: &str) -> UciCmd {
|
fn parse_command(s: &str) -> UciCmd {
|
||||||
if s.len() == 0 {
|
if s.len() == 0 {
|
||||||
return UciCmd::Unknown("Empty command.".to_string());
|
return UciCmd::Unknown("Empty command.".to_string());
|
||||||
|
@ -303,9 +312,10 @@ fn parse_command(s: &str) -> UciCmd {
|
||||||
"isready" => UciCmd::IsReady,
|
"isready" => UciCmd::IsReady,
|
||||||
"ucinewgame" => UciCmd::UciNewGame,
|
"ucinewgame" => UciCmd::UciNewGame,
|
||||||
"stop" => UciCmd::Stop,
|
"stop" => UciCmd::Stop,
|
||||||
"position" => parse_position_command(&fields[1..]),
|
"position" | "p" => parse_position_command(&fields[1..]),
|
||||||
"go" => parse_go_command(&fields[1..]),
|
"go" => parse_go_command(&fields[1..]),
|
||||||
"quit" => UciCmd::Quit,
|
"quit" => UciCmd::Quit,
|
||||||
|
"vatunode" => UciCmd::VatuNode,
|
||||||
c => UciCmd::Unknown(c.to_string()),
|
c => UciCmd::Unknown(c.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,7 +329,7 @@ fn parse_position_command(fields: &[&str]) -> UciCmd {
|
||||||
match fields[i] {
|
match fields[i] {
|
||||||
// Subcommand "fen" is followed by a FEN string.
|
// Subcommand "fen" is followed by a FEN string.
|
||||||
"fen" => {
|
"fen" => {
|
||||||
if let Some(fen) = notation::parse_fen_fields(&fields[i + 1 .. i + 7]) {
|
if let Some(fen) = fen::parse_fen_fields(&fields[i + 1 .. i + 7]) {
|
||||||
subcommands.push(PositionArgs::Fen(fen))
|
subcommands.push(PositionArgs::Fen(fen))
|
||||||
} else {
|
} else {
|
||||||
return UciCmd::Unknown(format!("Bad format for position fen"))
|
return UciCmd::Unknown(format!("Bad format for position fen"))
|
||||||
|
@ -332,7 +342,7 @@ fn parse_position_command(fields: &[&str]) -> UciCmd {
|
||||||
"moves" => {
|
"moves" => {
|
||||||
let mut moves = vec!();
|
let mut moves = vec!();
|
||||||
while i + 1 < num_fields {
|
while i + 1 < num_fields {
|
||||||
moves.push(notation::parse_move(fields[i + 1]));
|
moves.push(Move::from_uci_string(fields[i + 1]));
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
subcommands.push(PositionArgs::Moves(moves));
|
subcommands.push(PositionArgs::Moves(moves));
|
||||||
|
|
49
src/zobrist.rs
Normal file
49
src/zobrist.rs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
//! Functions related to Zobrist hashes.
|
||||||
|
|
||||||
|
use crate::board::*;
|
||||||
|
|
||||||
|
pub type ZobristHash = u64;
|
||||||
|
|
||||||
|
pub fn get_new_game_hash() -> ZobristHash {
|
||||||
|
return
|
||||||
|
get_piece_hash(WHITE, ROOK, A1)
|
||||||
|
^ get_piece_hash(WHITE, KNIGHT, B1)
|
||||||
|
^ get_piece_hash(WHITE, BISHOP, C1)
|
||||||
|
^ get_piece_hash(WHITE, QUEEN, D1)
|
||||||
|
^ get_piece_hash(WHITE, KING, E1)
|
||||||
|
^ get_piece_hash(WHITE, BISHOP, F1)
|
||||||
|
^ get_piece_hash(WHITE, KNIGHT, G1)
|
||||||
|
^ get_piece_hash(WHITE, ROOK, H1)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, A2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, B2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, C2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, D2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, E2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, F2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, G2)
|
||||||
|
^ get_piece_hash(WHITE, PAWN, H2)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, A7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, B7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, C7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, D7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, E7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, F7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, G7)
|
||||||
|
^ get_piece_hash(BLACK, PAWN, H7)
|
||||||
|
^ get_piece_hash(BLACK, ROOK, A8)
|
||||||
|
^ get_piece_hash(BLACK, KNIGHT, B8)
|
||||||
|
^ get_piece_hash(BLACK, BISHOP, C8)
|
||||||
|
^ get_piece_hash(BLACK, QUEEN, D8)
|
||||||
|
^ get_piece_hash(BLACK, KING, E8)
|
||||||
|
^ get_piece_hash(BLACK, BISHOP, F8)
|
||||||
|
^ get_piece_hash(BLACK, KNIGHT, G8)
|
||||||
|
^ get_piece_hash(BLACK, ROOK, H8)
|
||||||
|
^ ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_K]
|
||||||
|
^ ZOBRIST_CASTLES[ZOBRIST_CASTLE_WH_Q]
|
||||||
|
^ ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_K]
|
||||||
|
^ ZOBRIST_CASTLES[ZOBRIST_CASTLE_BL_Q]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_piece_hash(color: Color, piece: Piece, square: Square) -> ZobristHash {
|
||||||
|
ZOBRIST_PIECES[color][piece][square as usize]
|
||||||
|
}
|
Reference in a new issue