Compare commits
23 Commits
6bbcadddfd
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
6c2fd6566e
|
|||
|
9f7fbc8ef9
|
|||
|
f4ac92ff89
|
|||
|
d797dc545e
|
|||
|
e798480b72
|
|||
|
eda2ec6c93
|
|||
|
7cc7fa3e70
|
|||
|
15202df4f4
|
|||
|
797c54e83b
|
|||
|
01ccb65b64
|
|||
|
5108b4a6e2
|
|||
|
98645f01d0
|
|||
|
c7f82769e3
|
|||
|
0e8eb7b767
|
|||
|
5916a054ad
|
|||
|
c92a9e42d7
|
|||
|
0f7de146b7
|
|||
|
95c0ccfbd2
|
|||
|
802b069269
|
|||
|
ef5719f2f5
|
|||
|
d80cc79f57
|
|||
|
a7d3bb3752
|
|||
|
6ee41a112c
|
@@ -4,8 +4,10 @@ import subprocess
|
||||
import flask
|
||||
from flask_cors import CORS
|
||||
|
||||
|
||||
PIECE_TYPE = [("P", "pawn")]
|
||||
PIECE_TYPE = [
|
||||
("P", "pawn"),
|
||||
("N", "knight"),
|
||||
]
|
||||
COLOR = [
|
||||
("W", "white"),
|
||||
("B", "black"),
|
||||
@@ -15,12 +17,17 @@ HERE = os.path.abspath(os.path.dirname(__file__))
|
||||
app = flask.Flask(__name__)
|
||||
CORS(app)
|
||||
engine = subprocess.Popen(
|
||||
[os.path.join(HERE, "../rs/target/debug/schach")],
|
||||
["cargo", "run"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
cwd=os.path.join(HERE, "../rs"),
|
||||
)
|
||||
|
||||
|
||||
def reverse(zipped):
|
||||
return ((b, a) for a, b in zipped)
|
||||
|
||||
|
||||
def make_piece(piece_str):
|
||||
piece_type, color = piece_str
|
||||
return {
|
||||
@@ -33,7 +40,7 @@ def ask_engine(command):
|
||||
engine.stdin.write(f"{command}\n".encode("ascii"))
|
||||
engine.stdin.flush()
|
||||
reply = engine.stdout.readline().decode("ascii").strip()
|
||||
status, result = reply.split(",")
|
||||
status, *result = reply.split(",")
|
||||
if status != "ok":
|
||||
flask.abort(400)
|
||||
return result
|
||||
@@ -48,14 +55,14 @@ def parse_state(state_str):
|
||||
|
||||
@app.route("/get_state/")
|
||||
def get_state():
|
||||
state_str = ask_engine("get_state")
|
||||
(state_str,) = ask_engine("get_state")
|
||||
return flask.jsonify(parse_state(state_str))
|
||||
|
||||
|
||||
@app.route("/get_moves/", methods=["POST"])
|
||||
def get_moves():
|
||||
position_str = flask.request.json
|
||||
moves_str = ask_engine(f"get_moves,{position_str}")
|
||||
(moves_str,) = ask_engine(f"get_moves,{position_str}")
|
||||
moves = [moves_str[i : i + 2] for i in range(0, len(moves_str), 2)]
|
||||
return flask.jsonify(moves)
|
||||
|
||||
@@ -63,7 +70,20 @@ def get_moves():
|
||||
@app.route("/make_move/", methods=["POST"])
|
||||
def make_move():
|
||||
source, target = flask.request.json
|
||||
state_str = ask_engine(f"make_move,{source},{target}")
|
||||
(state_str,) = ask_engine(f"make_move,{source},{target}")
|
||||
return flask.jsonify(parse_state(state_str))
|
||||
|
||||
|
||||
@app.route("/choose_move/", methods=["POST"])
|
||||
def choose_move():
|
||||
color = dict(reverse(COLOR))[flask.request.json]
|
||||
move = ask_engine(f"choose_move,{color}")
|
||||
return flask.jsonify(move)
|
||||
|
||||
|
||||
@app.route("/reset/", methods=["POST"])
|
||||
def reset():
|
||||
(state_str,) = ask_engine("reset")
|
||||
return flask.jsonify(parse_state(state_str))
|
||||
|
||||
|
||||
|
||||
9
adapter/pyproject.toml
Normal file
9
adapter/pyproject.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[project]
|
||||
name = "adapter"
|
||||
version = "0.1.0"
|
||||
description = "Basic HTTP adapter for chess engine"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = [
|
||||
"flask>=3.1.0",
|
||||
"flask-cors>=5.0.0",
|
||||
]
|
||||
146
adapter/uv.lock
generated
Normal file
146
adapter/uv.lock
generated
Normal file
@@ -0,0 +1,146 @@
|
||||
version = 1
|
||||
requires-python = ">=3.12"
|
||||
|
||||
[[package]]
|
||||
name = "adapter"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
{ name = "flask-cors" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "flask", specifier = ">=3.1.0" },
|
||||
{ name = "flask-cors", specifier = ">=5.0.0" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask"
|
||||
version = "3.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "blinker" },
|
||||
{ name = "click" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/89/50/dff6380f1c7f84135484e176e0cac8690af72fa90e932ad2a0a60e28c69b/flask-3.1.0.tar.gz", hash = "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", size = 680824 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/af/47/93213ee66ef8fae3b93b3e29206f6b251e65c97bd91d8e1c5596ef15af0a/flask-3.1.0-py3-none-any.whl", hash = "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136", size = 102979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flask-cors"
|
||||
version = "5.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "flask" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4f/d0/d9e52b154e603b0faccc0b7c2ad36a764d8755ef4036acbf1582a67fb86b/flask_cors-5.0.0.tar.gz", hash = "sha256:5aadb4b950c4e93745034594d9f3ea6591f734bb3662e16e255ffbf5e89c88ef", size = 30954 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/56/07/1afa0514c876282bebc1c9aee83c6bb98fe6415cf57b88d9b06e7e29bf9c/Flask_Cors-5.0.0-py2.py3-none-any.whl", hash = "sha256:b9e307d082a9261c100d8fb0ba909eec6a228ed1b60a8315fd85f783d61910bc", size = 14463 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 },
|
||||
{ url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 },
|
||||
]
|
||||
@@ -25,15 +25,41 @@ class Backend {
|
||||
makeMove(source, target) {
|
||||
return post_json("http://localhost:3000/make_move/", [source, target]);
|
||||
}
|
||||
chooseMove(color) {
|
||||
return post_json("http://localhost:3000/choose_move/", color);
|
||||
}
|
||||
getAvailableMoves(position) {
|
||||
return post_json("http://localhost:3000/get_moves/", position);
|
||||
}
|
||||
reset() {
|
||||
return post_json("http://localhost:3000/reset/");
|
||||
}
|
||||
}
|
||||
|
||||
class Chess {
|
||||
constructor() {
|
||||
this.backend = new Backend();
|
||||
this.canvas = new visuals.Canvas();
|
||||
this.autoplay = false;
|
||||
|
||||
this.button = document.createElement("button");
|
||||
this.button.innerHTML = "Autoplay Off";
|
||||
this.button.onclick = () => {
|
||||
this.autoplay = !this.autoplay;
|
||||
this.button.innerHTML = ["Autoplay Off", "Autoplay On"][
|
||||
Number(this.autoplay)
|
||||
];
|
||||
};
|
||||
document.body.appendChild(this.button);
|
||||
|
||||
this.resetButton = document.createElement("button");
|
||||
this.resetButton.innerHTML = "Reset";
|
||||
this.resetButton.onclick = () => {
|
||||
this.backend.reset();
|
||||
this.updateState();
|
||||
};
|
||||
document.body.appendChild(this.resetButton);
|
||||
|
||||
this.configVis = new visuals.ConfigVis(this.backend.getConfig());
|
||||
this.configVis.draw(this.canvas);
|
||||
this.activeSquares = new visuals.ActiveSquares();
|
||||
@@ -46,6 +72,12 @@ class Chess {
|
||||
this.configVis.configuration = config;
|
||||
this.configVis.draw(this.canvas);
|
||||
}
|
||||
chooseMove() {
|
||||
let move = this.backend.chooseMove("black");
|
||||
if (move === null) return;
|
||||
this.backend.makeMove(...move);
|
||||
this.updateState();
|
||||
}
|
||||
initializeMove(source) {
|
||||
if (source === null) return;
|
||||
let validMoves = this.backend.getAvailableMoves(source);
|
||||
@@ -55,13 +87,19 @@ class Chess {
|
||||
this.moveSource = source;
|
||||
}
|
||||
finalizeMove(target) {
|
||||
let success;
|
||||
if (target !== null) {
|
||||
this.backend.makeMove(this.moveSource, target);
|
||||
success = this.backend.makeMove(this.moveSource, target) !== null;
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
this.activeSquares.unselectSquare(this.canvas);
|
||||
this.activeSquares.unsetMoveSquares(this.canvas);
|
||||
this.updateState();
|
||||
this.moveSource = null;
|
||||
if (this.autoplay && success) {
|
||||
this.chooseMove();
|
||||
}
|
||||
}
|
||||
click(ev) {
|
||||
let rc = this.canvas.screenCoordsToRowCol(ev.clientX, ev.clientY);
|
||||
|
||||
78
rs/Cargo.lock
generated
78
rs/Cargo.lock
generated
@@ -2,6 +2,84 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.112"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schach"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
@@ -6,3 +6,4 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.4"
|
||||
|
||||
413
rs/src/board.rs
413
rs/src/board.rs
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Eq, PartialEq, Clone)]
|
||||
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||
pub enum Color {
|
||||
Black,
|
||||
White,
|
||||
@@ -19,43 +19,58 @@ impl Color {
|
||||
Color::White => Rank::_2,
|
||||
}
|
||||
}
|
||||
fn get_direction(&self) -> i8 {
|
||||
pub fn other(&self) -> Color {
|
||||
match self {
|
||||
Color::Black => -1,
|
||||
Color::White => 1,
|
||||
Color::Black => Color::White,
|
||||
Color::White => Color::Black,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct Move_ {
|
||||
pub source: Position,
|
||||
pub target: Position,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||
pub struct Position {
|
||||
pub rank: Rank,
|
||||
pub file: File,
|
||||
pub rank: Rank,
|
||||
}
|
||||
|
||||
enum Dir {
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn new(rank: Rank, file: File) -> Position {
|
||||
pub fn new(file: File, rank: Rank) -> Position {
|
||||
Position {
|
||||
rank,
|
||||
file,
|
||||
rank,
|
||||
}
|
||||
}
|
||||
fn delta(&self, delta_rank_file: (i8, i8)) -> Result<Position, ()> {
|
||||
let (delta_rank, delta_file) = delta_rank_file;
|
||||
Ok(Position::new(
|
||||
self.rank.delta(delta_rank)?,
|
||||
self.file.delta(delta_file)?,
|
||||
))
|
||||
fn chain(&self, dirs: &[&Dir]) -> Option<Position> {
|
||||
dirs.iter().try_fold(self.clone(), |pos, dir| pos.delta(dir))
|
||||
}
|
||||
fn deltas_to_valid_positions(&self, deltas: &[(i8, i8)]) -> Vec<Position> {
|
||||
deltas
|
||||
.iter()
|
||||
.filter_map(|&delta_rank_file| self.delta(delta_rank_file).ok())
|
||||
.collect()
|
||||
fn delta(&self, direction: &Dir) -> Option<Position> {
|
||||
let (file, rank) = match direction {
|
||||
Dir::Left => (self.file.decr()?, self.rank),
|
||||
Dir::Right => (self.file.incr()?, self.rank),
|
||||
Dir::Up => (self.file, self.rank.incr()?),
|
||||
Dir::Down => (self.file, self.rank.decr()?),
|
||||
};
|
||||
Some(Position {
|
||||
file,
|
||||
rank,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||
pub enum Rank {
|
||||
_1,
|
||||
_2,
|
||||
@@ -67,7 +82,7 @@ pub enum Rank {
|
||||
_8,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||
pub enum File {
|
||||
A,
|
||||
B,
|
||||
@@ -84,137 +99,234 @@ where
|
||||
Self: Sized,
|
||||
Self: Clone,
|
||||
{
|
||||
const ALL_VALUES: [Self; 8];
|
||||
fn new(index: u8) -> Result<Self, ()> {
|
||||
Ok(Self::ALL_VALUES[Self::validate_index(index)? as usize].clone())
|
||||
}
|
||||
fn validate_index(index: u8) -> Result<u8, ()> {
|
||||
if index as usize >= Self::ALL_VALUES.len() {
|
||||
Err(())
|
||||
} else {
|
||||
Ok(index)
|
||||
}
|
||||
}
|
||||
fn get_index(&self) -> u8;
|
||||
fn delta(&self, delta: i8) -> Result<Self, ()> {
|
||||
let highest = Self::ALL_VALUES.len() as i8 - 1;
|
||||
if delta > highest
|
||||
|| delta < -highest
|
||||
|| (delta < 0 && self.get_index() < delta.abs() as u8)
|
||||
|| (delta > 0 && self.get_index() + delta as u8 > highest as u8)
|
||||
{
|
||||
Err(())
|
||||
} else {
|
||||
Self::new(((self.get_index() as i8) + delta) as u8)
|
||||
}
|
||||
}
|
||||
fn incr(&self) -> Option<Self>;
|
||||
fn decr(&self) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl GridAxis for Rank {
|
||||
const ALL_VALUES: [Rank; 8] = [
|
||||
Rank::_1,
|
||||
Rank::_2,
|
||||
Rank::_3,
|
||||
Rank::_4,
|
||||
Rank::_5,
|
||||
Rank::_6,
|
||||
Rank::_7,
|
||||
Rank::_8,
|
||||
];
|
||||
fn get_index(&self) -> u8 {
|
||||
*self as u8
|
||||
fn incr(&self) -> Option<Self> {
|
||||
match self {
|
||||
Rank::_1 => Some(Rank::_2),
|
||||
Rank::_2 => Some(Rank::_3),
|
||||
Rank::_3 => Some(Rank::_4),
|
||||
Rank::_4 => Some(Rank::_5),
|
||||
Rank::_5 => Some(Rank::_6),
|
||||
Rank::_6 => Some(Rank::_7),
|
||||
Rank::_7 => Some(Rank::_8),
|
||||
Rank::_8 => None,
|
||||
}
|
||||
}
|
||||
fn decr(&self) -> Option<Self> {
|
||||
match self {
|
||||
Rank::_1 => None,
|
||||
Rank::_2 => Some(Rank::_1),
|
||||
Rank::_3 => Some(Rank::_2),
|
||||
Rank::_4 => Some(Rank::_3),
|
||||
Rank::_5 => Some(Rank::_4),
|
||||
Rank::_6 => Some(Rank::_5),
|
||||
Rank::_7 => Some(Rank::_6),
|
||||
Rank::_8 => Some(Rank::_7),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GridAxis for File {
|
||||
const ALL_VALUES: [File; 8] = [
|
||||
File::A,
|
||||
File::B,
|
||||
File::C,
|
||||
File::D,
|
||||
File::E,
|
||||
File::F,
|
||||
File::G,
|
||||
File::H,
|
||||
];
|
||||
fn get_index(&self) -> u8 {
|
||||
*self as u8
|
||||
fn incr(&self) -> Option<Self> {
|
||||
match self {
|
||||
File::A => Some(File::B),
|
||||
File::B => Some(File::C),
|
||||
File::C => Some(File::D),
|
||||
File::D => Some(File::E),
|
||||
File::E => Some(File::F),
|
||||
File::F => Some(File::G),
|
||||
File::G => Some(File::H),
|
||||
File::H => None,
|
||||
}
|
||||
}
|
||||
fn decr(&self) -> Option<Self> {
|
||||
match self {
|
||||
File::A => None,
|
||||
File::B => Some(File::A),
|
||||
File::C => Some(File::B),
|
||||
File::D => Some(File::C),
|
||||
File::E => Some(File::D),
|
||||
File::F => Some(File::E),
|
||||
File::G => Some(File::F),
|
||||
File::H => Some(File::G),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PieceType {
|
||||
Pawn,
|
||||
Knight,
|
||||
}
|
||||
|
||||
impl PieceType {
|
||||
fn initial_setup(&self) -> Vec<(Piece, Position)> {
|
||||
match &self {
|
||||
PieceType::Pawn => [Color::Black, Color::White]
|
||||
.iter()
|
||||
.flat_map(|color| {
|
||||
File::ALL_VALUES.iter().map(|&file| {
|
||||
// Pawn
|
||||
fn new_pawn(color: Color, file: File) -> (Piece, Position) {
|
||||
let pos = Position::new(file, color.get_pawn_rank());
|
||||
(
|
||||
Piece {
|
||||
piece_type: self.clone(),
|
||||
color: color.clone(),
|
||||
piece_type: PieceType::Pawn,
|
||||
color,
|
||||
},
|
||||
Position::new(color.get_pawn_rank(), file),
|
||||
pos,
|
||||
)
|
||||
}
|
||||
fn pawn_move_rays(position: &Position, color: &Color) -> Vec<Ray> {
|
||||
let direction = match color {
|
||||
Color::Black => Dir::Down,
|
||||
Color::White => Dir::Up,
|
||||
};
|
||||
let go = |p: &Position| p.delta(&direction);
|
||||
if position.rank == color.get_pawn_rank() {
|
||||
let one = go(position).unwrap();
|
||||
let two = go(&one).unwrap();
|
||||
vec![Ray {
|
||||
positions: vec![one, two],
|
||||
}]
|
||||
} else {
|
||||
if let Some(p) = go(position) {
|
||||
vec![Ray {
|
||||
positions: vec![p],
|
||||
}]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
fn pawn_capture_rays(position: &Position, color: &Color) -> Vec<Ray> {
|
||||
let direction = match color {
|
||||
Color::Black => Dir::Down,
|
||||
Color::White => Dir::Up,
|
||||
};
|
||||
[&[&direction, &Dir::Left], &[&direction, &Dir::Right]]
|
||||
.into_iter()
|
||||
.filter_map(|dirs| position.chain(dirs))
|
||||
.map(|p| Ray {
|
||||
positions: vec![p],
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Knight
|
||||
fn new_knight(color: Color, file: File) -> (Piece, Position) {
|
||||
let pos = Position::new(file, color.get_home_rank());
|
||||
(
|
||||
Piece {
|
||||
piece_type: PieceType::Knight,
|
||||
color,
|
||||
},
|
||||
pos,
|
||||
)
|
||||
}
|
||||
fn knight_rays(position: &Position) -> Vec<Ray> {
|
||||
[
|
||||
&[&Dir::Up, &Dir::Up, &Dir::Left],
|
||||
&[&Dir::Up, &Dir::Up, &Dir::Right],
|
||||
&[&Dir::Right, &Dir::Right, &Dir::Up],
|
||||
&[&Dir::Right, &Dir::Right, &Dir::Down],
|
||||
&[&Dir::Down, &Dir::Down, &Dir::Right],
|
||||
&[&Dir::Down, &Dir::Down, &Dir::Left],
|
||||
&[&Dir::Left, &Dir::Left, &Dir::Down],
|
||||
&[&Dir::Left, &Dir::Left, &Dir::Up],
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|dirs| position.chain(dirs))
|
||||
.map(|p| Ray {
|
||||
positions: vec![p],
|
||||
})
|
||||
.collect(),
|
||||
.collect()
|
||||
}
|
||||
fn initial_setup(&self) -> Vec<(Piece, Position)> {
|
||||
match &self {
|
||||
PieceType::Pawn => vec![
|
||||
PieceType::new_pawn(Color::White, File::A),
|
||||
PieceType::new_pawn(Color::White, File::B),
|
||||
PieceType::new_pawn(Color::White, File::C),
|
||||
PieceType::new_pawn(Color::White, File::D),
|
||||
PieceType::new_pawn(Color::White, File::E),
|
||||
PieceType::new_pawn(Color::White, File::F),
|
||||
PieceType::new_pawn(Color::White, File::G),
|
||||
PieceType::new_pawn(Color::White, File::H),
|
||||
PieceType::new_pawn(Color::Black, File::A),
|
||||
PieceType::new_pawn(Color::Black, File::B),
|
||||
PieceType::new_pawn(Color::Black, File::C),
|
||||
PieceType::new_pawn(Color::Black, File::D),
|
||||
PieceType::new_pawn(Color::Black, File::E),
|
||||
PieceType::new_pawn(Color::Black, File::F),
|
||||
PieceType::new_pawn(Color::Black, File::G),
|
||||
PieceType::new_pawn(Color::Black, File::H),
|
||||
],
|
||||
PieceType::Knight => vec![
|
||||
PieceType::new_knight(Color::White, File::B),
|
||||
PieceType::new_knight(Color::White, File::G),
|
||||
PieceType::new_knight(Color::Black, File::B),
|
||||
PieceType::new_knight(Color::Black, File::G),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Piece {
|
||||
pub color: Color,
|
||||
pub piece_type: PieceType,
|
||||
}
|
||||
|
||||
struct Ray {
|
||||
positions: Vec<Position>,
|
||||
}
|
||||
|
||||
impl Piece {
|
||||
fn get_moves(&self, position: &Position) -> Vec<Position> {
|
||||
let deltas = match self.piece_type {
|
||||
PieceType::Pawn => self._pawn_get_move_deltas(position),
|
||||
};
|
||||
position.deltas_to_valid_positions(&deltas)
|
||||
fn get_move_rays(&self, position: &Position) -> Vec<Ray> {
|
||||
match self.piece_type {
|
||||
PieceType::Pawn => {
|
||||
PieceType::pawn_move_rays(position, &self.color)
|
||||
}
|
||||
fn get_captures(&self, position: &Position) -> Vec<Position> {
|
||||
let deltas = match self.piece_type {
|
||||
PieceType::Pawn => self._pawn_get_capture_deltas(),
|
||||
};
|
||||
position.deltas_to_valid_positions(&deltas)
|
||||
}
|
||||
fn _pawn_get_move_deltas(&self, position: &Position) -> Vec<(i8, i8)> {
|
||||
let direction = self.color.get_direction();
|
||||
if position.rank == self.color.get_pawn_rank() {
|
||||
vec![(direction, 0), (2 * direction, 0)]
|
||||
} else {
|
||||
vec![(direction, 0)]
|
||||
PieceType::Knight => PieceType::knight_rays(position),
|
||||
}
|
||||
}
|
||||
fn _pawn_get_capture_deltas(&self) -> Vec<(i8, i8)> {
|
||||
let direction = self.color.get_direction();
|
||||
vec![(direction, 1), (direction, -1)]
|
||||
fn get_capture_rays(&self, position: &Position) -> Vec<Ray> {
|
||||
match self.piece_type {
|
||||
PieceType::Pawn => {
|
||||
PieceType::pawn_capture_rays(position, &self.color)
|
||||
}
|
||||
PieceType::Knight => PieceType::knight_rays(position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Board {
|
||||
state: HashMap<Position, Piece>,
|
||||
}
|
||||
|
||||
impl Board {
|
||||
fn get_at(&self, position: &Position) -> Option<&Piece> {
|
||||
pub fn new() -> Board {
|
||||
Board {
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn occupied(&self, position: &Position) -> bool {
|
||||
self.state.contains_key(position)
|
||||
}
|
||||
pub fn get_at(&self, position: &Position) -> Option<&Piece> {
|
||||
self.state.get(position)
|
||||
}
|
||||
fn set_at(&mut self, position: Position, piece_or_empty: Option<Piece>) {
|
||||
pub fn set_at(
|
||||
&mut self,
|
||||
position: Position,
|
||||
piece_or_empty: Option<Piece>,
|
||||
) {
|
||||
match piece_or_empty {
|
||||
Some(piece) => self.state.insert(position, piece),
|
||||
None => self.state.remove(&position),
|
||||
};
|
||||
}
|
||||
fn move_from_to(
|
||||
pub fn relocate(
|
||||
&mut self,
|
||||
source: &Position,
|
||||
target: Position,
|
||||
@@ -223,15 +335,9 @@ impl Board {
|
||||
self.set_at(target, Some(piece));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn new() -> Board {
|
||||
Board {
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.state.clear();
|
||||
for piece_type in &[PieceType::Pawn] {
|
||||
for piece_type in &[PieceType::Pawn, PieceType::Knight] {
|
||||
for (piece, position) in piece_type.initial_setup() {
|
||||
self.set_at(position, Some(piece));
|
||||
}
|
||||
@@ -240,37 +346,72 @@ impl Board {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&Position, &Piece)> {
|
||||
self.state.iter()
|
||||
}
|
||||
fn cut_move_ray(&self, ray: Ray) -> impl Iterator<Item = Position> + '_ {
|
||||
ray.positions
|
||||
.into_iter()
|
||||
.take_while(|position| self.get_at(position).is_none())
|
||||
}
|
||||
fn find_capture_along_ray(
|
||||
&self,
|
||||
ray: Ray,
|
||||
captured_color: &Color,
|
||||
) -> Option<Position> {
|
||||
// I'm not expecting to call this if there's no piece in the position
|
||||
ray.positions.into_iter().find(|p| match self.get_at(p) {
|
||||
None => false,
|
||||
Some(piece) => &piece.color == captured_color,
|
||||
})
|
||||
}
|
||||
pub fn find_moves(
|
||||
&self,
|
||||
position: &Position,
|
||||
) -> Result<impl Iterator<Item = Position> + '_, ()> {
|
||||
if let Some(piece) = self.get_at(position) {
|
||||
Ok(piece
|
||||
.get_move_rays(position)
|
||||
.into_iter()
|
||||
.flat_map(|ray| self.cut_move_ray(ray)))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
pub fn find_captures(
|
||||
&self,
|
||||
position: &Position,
|
||||
) -> Result<impl Iterator<Item = Position> + '_, ()> {
|
||||
if let Some(piece) = self.get_at(position) {
|
||||
Ok(piece.get_capture_rays(position).into_iter().filter_map(
|
||||
|ray| self.find_capture_along_ray(ray, &piece.color.other()),
|
||||
))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
pub fn get_legal_moves(
|
||||
&self,
|
||||
position: &Position,
|
||||
) -> Option<Vec<Position>> {
|
||||
let piece = match self.get_at(position) {
|
||||
None => return None,
|
||||
Some(piece) => piece,
|
||||
};
|
||||
let valid_moves = piece
|
||||
.get_moves(position)
|
||||
.into_iter()
|
||||
.filter(|pos| self.get_at(pos).is_none());
|
||||
let valid_captures =
|
||||
piece.get_captures(position).into_iter().filter(|pos| match self
|
||||
.get_at(pos)
|
||||
{
|
||||
None => false,
|
||||
Some(other_piece) => (other_piece.color != piece.color),
|
||||
});
|
||||
Some(valid_moves.chain(valid_captures).collect())
|
||||
) -> Result<impl Iterator<Item = Position> + '_, ()> {
|
||||
Ok(self.find_moves(position)?.chain(self.find_captures(position)?))
|
||||
}
|
||||
pub fn make_move(
|
||||
&mut self,
|
||||
source: &Position,
|
||||
target: Position,
|
||||
) -> Result<(), ()> {
|
||||
if !self.get_legal_moves(source).ok_or(())?.contains(&target) {
|
||||
Err(())
|
||||
pub fn all_moves_for_color(
|
||||
&self,
|
||||
color: Color,
|
||||
) -> impl Iterator<Item = Move_> + '_ {
|
||||
self.iter()
|
||||
.filter_map(move |(source, piece)| {
|
||||
if piece.color == color {
|
||||
if let Ok(targets) = self.get_legal_moves(source) {
|
||||
Some(targets.map(|target| Move_ {
|
||||
source: source.clone(),
|
||||
target,
|
||||
}))
|
||||
} else {
|
||||
// We checked that there is a piece at source in get_legal_moves
|
||||
self.move_from_to(source, target)
|
||||
}
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
95
rs/src/engine.rs
Normal file
95
rs/src/engine.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use crate::board;
|
||||
use crate::board::Move_;
|
||||
|
||||
impl board::PieceType {
|
||||
fn value(&self) -> f32 {
|
||||
match self {
|
||||
board::PieceType::Pawn => 1.0,
|
||||
board::PieceType::Knight => 3.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl board::Color {
|
||||
fn sign(&self) -> f32 {
|
||||
match self {
|
||||
board::Color::Black => -1.0,
|
||||
board::Color::White => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Engine {
|
||||
pub board: board::Board,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new() -> Self {
|
||||
let mut board = board::Board::new();
|
||||
board.reset();
|
||||
Engine {
|
||||
board,
|
||||
}
|
||||
}
|
||||
pub fn reset(&mut self) {
|
||||
self.board.reset()
|
||||
}
|
||||
pub fn get_state(&self) -> &board::Board {
|
||||
&self.board
|
||||
}
|
||||
pub fn set_state(&mut self, board: board::Board) {
|
||||
self.board = board;
|
||||
}
|
||||
pub fn make_move(
|
||||
&mut self,
|
||||
source: &board::Position,
|
||||
target: board::Position,
|
||||
) -> Result<(), ()> {
|
||||
if !self.board.get_legal_moves(source)?.any(|pos| pos == target) {
|
||||
Err(())
|
||||
} else {
|
||||
// We checked that there is a piece at source in get_legal_moves
|
||||
self.board.relocate(source, target)
|
||||
}
|
||||
}
|
||||
pub fn choose_move(&self, color: &board::Color) -> Option<Move_> {
|
||||
Some(
|
||||
self.board
|
||||
.all_moves_for_color(color.clone())
|
||||
.map(|m| {
|
||||
let mut board = self.board.clone();
|
||||
board.relocate(&m.source, m.target.clone()).unwrap();
|
||||
(minmax(1, &board, color), m)
|
||||
})
|
||||
.max_by(|(s1, _), (s2, _)| s1.partial_cmp(s2).unwrap())?
|
||||
.1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_position(board: &board::Board) -> f32 {
|
||||
board
|
||||
.iter()
|
||||
.map(|(_, piece)| piece.color.sign() * piece.piece_type.value())
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn minmax(depth: i8, board: &board::Board, color: &board::Color) -> f32 {
|
||||
if depth == 0 {
|
||||
evaluate_position(board) * color.sign()
|
||||
} else {
|
||||
let best_opponent_move_score = board
|
||||
.all_moves_for_color(color.other())
|
||||
.map(|m| {
|
||||
let mut board = board.clone();
|
||||
board.relocate(&m.source, m.target).unwrap();
|
||||
minmax(depth - 1, &board, &color.other())
|
||||
})
|
||||
.max_by(|s1, s2| s1.partial_cmp(s2).unwrap());
|
||||
if let Some(s) = best_opponent_move_score {
|
||||
-s
|
||||
} else {
|
||||
minmax(0, board, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
extern crate rand;
|
||||
mod board;
|
||||
mod engine;
|
||||
mod ui;
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
|
||||
224
rs/src/ui.rs
224
rs/src/ui.rs
@@ -1,5 +1,6 @@
|
||||
use crate::board;
|
||||
use crate::board::GridAxis;
|
||||
use crate::engine;
|
||||
use std::io::BufRead;
|
||||
|
||||
impl std::fmt::Display for board::Color {
|
||||
@@ -15,6 +16,7 @@ impl std::fmt::Display for board::PieceType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self {
|
||||
board::PieceType::Pawn => write!(f, "P"),
|
||||
board::PieceType::Knight => write!(f, "N"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,30 +31,70 @@ trait GridAxisIO: std::fmt::Display
|
||||
where
|
||||
Self: GridAxis,
|
||||
{
|
||||
const ALL_CHARS: [char; 8];
|
||||
fn parse(c: char) -> Result<Self, ()> {
|
||||
let index = Self::ALL_CHARS.iter().position(|p| p == &c).ok_or(())?;
|
||||
Ok(Self::ALL_VALUES[index].clone())
|
||||
}
|
||||
fn parse(c: char) -> Result<Self, ()>;
|
||||
}
|
||||
|
||||
impl GridAxisIO for board::Rank {
|
||||
const ALL_CHARS: [char; 8] = ['1', '2', '3', '4', '5', '6', '7', '8'];
|
||||
fn parse(c: char) -> Result<Self, ()> {
|
||||
match c {
|
||||
'1' => Ok(board::Rank::_1),
|
||||
'2' => Ok(board::Rank::_2),
|
||||
'3' => Ok(board::Rank::_3),
|
||||
'4' => Ok(board::Rank::_4),
|
||||
'5' => Ok(board::Rank::_5),
|
||||
'6' => Ok(board::Rank::_6),
|
||||
'7' => Ok(board::Rank::_7),
|
||||
'8' => Ok(board::Rank::_8),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for board::Rank {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", Self::ALL_CHARS[self.get_index() as usize])
|
||||
let c = match self {
|
||||
board::Rank::_1 => '1',
|
||||
board::Rank::_2 => '2',
|
||||
board::Rank::_3 => '3',
|
||||
board::Rank::_4 => '4',
|
||||
board::Rank::_5 => '5',
|
||||
board::Rank::_6 => '6',
|
||||
board::Rank::_7 => '7',
|
||||
board::Rank::_8 => '8',
|
||||
};
|
||||
write!(f, "{}", c)
|
||||
}
|
||||
}
|
||||
|
||||
impl GridAxisIO for board::File {
|
||||
const ALL_CHARS: [char; 8] = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||
fn parse(c: char) -> Result<Self, ()> {
|
||||
match c {
|
||||
'a' => Ok(board::File::A),
|
||||
'b' => Ok(board::File::B),
|
||||
'c' => Ok(board::File::C),
|
||||
'd' => Ok(board::File::D),
|
||||
'e' => Ok(board::File::E),
|
||||
'f' => Ok(board::File::F),
|
||||
'g' => Ok(board::File::G),
|
||||
'h' => Ok(board::File::H),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for board::File {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", Self::ALL_CHARS[self.get_index() as usize])
|
||||
let c = match self {
|
||||
board::File::A => 'a',
|
||||
board::File::B => 'b',
|
||||
board::File::C => 'c',
|
||||
board::File::D => 'd',
|
||||
board::File::E => 'e',
|
||||
board::File::F => 'f',
|
||||
board::File::G => 'g',
|
||||
board::File::H => 'h',
|
||||
};
|
||||
write!(f, "{}", c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +104,43 @@ impl board::Position {
|
||||
let file = board::File::parse(chars.next().ok_or(())?)?;
|
||||
let rank = board::Rank::parse(chars.next().ok_or(())?)?;
|
||||
if chars.next().is_none() {
|
||||
Ok(board::Position::new(rank, file))
|
||||
Ok(board::Position::new(file, rank))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl board::PieceType {
|
||||
fn parse(c: char) -> Result<Self, ()> {
|
||||
match c {
|
||||
'P' => Ok(board::PieceType::Pawn),
|
||||
'N' => Ok(board::PieceType::Knight),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl board::Color {
|
||||
fn parse(c: char) -> Result<Self, ()> {
|
||||
match c {
|
||||
'B' => Ok(board::Color::Black),
|
||||
'W' => Ok(board::Color::White),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl board::Piece {
|
||||
fn parse(s: &str) -> Result<Self, ()> {
|
||||
let mut chars = s.chars();
|
||||
let piece_type = board::PieceType::parse(chars.next().ok_or(())?)?;
|
||||
let color = board::Color::parse(chars.next().ok_or(())?)?;
|
||||
if chars.next().is_none() {
|
||||
Ok(board::Piece {
|
||||
piece_type,
|
||||
color,
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
@@ -84,55 +162,122 @@ impl std::fmt::Display for board::Board {
|
||||
}
|
||||
}
|
||||
|
||||
impl board::Board {
|
||||
fn parse(s: &str) -> Result<Self, ()> {
|
||||
if s.chars().all(|c| char::is_ascii_alphanumeric(&c)) {
|
||||
let mut board = board::Board::new();
|
||||
for piece_pos in s.as_bytes().chunks(4) {
|
||||
let position = board::Position::parse(
|
||||
std::str::from_utf8(&piece_pos[2..]).unwrap(),
|
||||
)?;
|
||||
let piece = board::Piece::parse(
|
||||
std::str::from_utf8(&piece_pos[..2]).unwrap(),
|
||||
)?;
|
||||
board.set_at(position, Some(piece));
|
||||
}
|
||||
Ok(board)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ui {
|
||||
board: board::Board,
|
||||
engine: engine::Engine,
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
pub fn new() -> Self {
|
||||
let mut board = board::Board::new();
|
||||
board.reset();
|
||||
Ui {
|
||||
board,
|
||||
engine: engine::Engine::new(),
|
||||
}
|
||||
}
|
||||
fn get_state(&self, args: &[&str]) -> Result<String, String> {
|
||||
if !args.is_empty() {
|
||||
Err("get_state takes no args".to_owned())
|
||||
} else {
|
||||
let result = format!("{}", self.board);
|
||||
if let [] = args {
|
||||
let result = format!("{}", self.engine.get_state());
|
||||
Ok(result)
|
||||
} else {
|
||||
Err("get_state takes no args".to_owned())
|
||||
}
|
||||
}
|
||||
fn set_state(&mut self, args: &[&str]) -> Result<String, String> {
|
||||
if let [state_str] = args {
|
||||
self.engine.set_state(
|
||||
board::Board::parse(state_str).map_err(|_| {
|
||||
format!("Error parsing state {}", state_str)
|
||||
})?,
|
||||
);
|
||||
let result = format!("{}", self.engine.get_state());
|
||||
Ok(result)
|
||||
} else {
|
||||
Err("set_state takes 1 arg".to_owned())
|
||||
}
|
||||
}
|
||||
fn get_moves(&self, args: &[&str]) -> Result<String, String> {
|
||||
if args.len() != 1 {
|
||||
return Err("get_moves takes 1 arg".to_owned());
|
||||
};
|
||||
let position = board::Position::parse(args[0])
|
||||
.map_err(|_| format!("Error parsing position {}", args[0]))?;
|
||||
match self.board.get_legal_moves(&position) {
|
||||
None => {
|
||||
if let [position_str] = args {
|
||||
let position =
|
||||
board::Position::parse(position_str).map_err(|_| {
|
||||
format!("Error parsing position {}", position_str)
|
||||
})?;
|
||||
match self.engine.board.get_legal_moves(&position) {
|
||||
Err(_) => {
|
||||
let error = format!("No moves possible from {}", position);
|
||||
Err(error)
|
||||
}
|
||||
Some(positions) => {
|
||||
Ok(positions.iter().map(|pos| format!("{}", pos)).collect())
|
||||
Ok(positions) => {
|
||||
Ok(positions.map(|pos| format!("{}", pos)).collect())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err("get_moves takes 1 arg".to_owned())
|
||||
}
|
||||
}
|
||||
fn make_move(&mut self, args: &[&str]) -> Result<String, String> {
|
||||
if args.len() != 2 {
|
||||
return Err("make_move takes 2 args".to_owned());
|
||||
};
|
||||
let source = board::Position::parse(args[0])
|
||||
.map_err(|_| format!("Error parsing source {}", args[0]))?;
|
||||
let target = board::Position::parse(args[1])
|
||||
.map_err(|_| format!("Error parsing target {}", args[1]))?;
|
||||
match self.board.make_move(&source, target) {
|
||||
Err(()) => {
|
||||
Err(format!("Move not possible {}-{}", args[0], args[1]))
|
||||
if let [source_str, target_str] = args {
|
||||
let source = board::Position::parse(source_str)
|
||||
.map_err(|_| format!("Error parsing source {}", source_str))?;
|
||||
let target = board::Position::parse(target_str)
|
||||
.map_err(|_| format!("Error parsing target {}", target_str))?;
|
||||
match self.engine.make_move(&source, target) {
|
||||
Err(()) => Err(format!(
|
||||
"Move not possible {}-{}",
|
||||
source_str, target_str
|
||||
)),
|
||||
Ok(()) => Ok(format!("{}", self.engine.get_state())),
|
||||
}
|
||||
Ok(()) => Ok(self.board.to_string()),
|
||||
} else {
|
||||
Err("make_move takes 2 args".to_owned())
|
||||
}
|
||||
}
|
||||
fn choose_move(&self, args: &[&str]) -> Result<String, String> {
|
||||
if let [color_str] = args {
|
||||
if let [color_char] = color_str.chars().collect::<Vec<char>>()[..]
|
||||
{
|
||||
match self.engine.choose_move(
|
||||
&board::Color::parse(color_char).map_err(|_| {
|
||||
format!("Couldn't parse color code {}", color_char)
|
||||
})?,
|
||||
) {
|
||||
Some(move_) => {
|
||||
Ok(format!("{},{}", move_.source, move_.target))
|
||||
}
|
||||
None => {
|
||||
Err(format!("No move possible for {}", color_char))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(format!("Invalid color string {}", color_str))
|
||||
}
|
||||
} else {
|
||||
Err("choose_move takes 1 arg".to_owned())
|
||||
}
|
||||
}
|
||||
fn reset(&mut self, args: &[&str]) -> Result<String, String> {
|
||||
if let [] = args {
|
||||
self.engine.reset();
|
||||
Ok(format!("{}", self.engine.get_state()))
|
||||
} else {
|
||||
Err("reset doesn't take args".to_owned())
|
||||
}
|
||||
}
|
||||
fn handle_command(&mut self, s: &str) -> Result<String, String> {
|
||||
@@ -144,6 +289,9 @@ impl Ui {
|
||||
"get_state" => self.get_state(&args),
|
||||
"get_moves" => self.get_moves(&args),
|
||||
"make_move" => self.make_move(&args),
|
||||
"set_state" => self.set_state(&args),
|
||||
"choose_move" => self.choose_move(&args),
|
||||
"reset" => self.reset(&args),
|
||||
"" => Err("No command given".to_owned()),
|
||||
_ => Err("Invalid command".to_owned()),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user