Compare commits
61 Commits
751cc2cc04
...
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
|
|||
|
6bbcadddfd
|
|||
|
a1207f2404
|
|||
|
c49bedf0ad
|
|||
|
4ae73410bd
|
|||
|
27dce5f7a3
|
|||
|
9bb6a60ded
|
|||
|
8d93acf950
|
|||
|
f9212a8291
|
|||
|
941a5c072a
|
|||
|
d36a948936
|
|||
|
b9f39fa0b4
|
|||
|
aa899740c6
|
|||
|
6d433a6d01
|
|||
|
1f5e58d981
|
|||
|
7788aea805
|
|||
|
9e8b633447
|
|||
|
f8c7323b47
|
|||
|
7143427dd2
|
|||
|
7dabb49cd4
|
|||
|
b5c252bd4d
|
|||
|
57931b29de
|
|||
|
4f3469b3e3
|
|||
|
81626aefec
|
|||
|
02e7d7e984
|
|||
|
458f87fd61
|
|||
|
e45fd3981a
|
|||
|
72a691de90
|
|||
|
cc58260943
|
|||
|
799c91fea0
|
|||
|
51e5228145
|
|||
|
6279e076ac
|
|||
|
7edf459485
|
|||
|
b246ba83d1
|
|||
|
28011b8f93
|
|||
|
cb44ae4184
|
|||
|
ab1e70e7d4
|
|||
|
0540b262dd
|
|||
|
a3ae7c9f71
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
target/
|
||||||
91
adapter/adapter.py
Normal file
91
adapter/adapter.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import flask
|
||||||
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
PIECE_TYPE = [
|
||||||
|
("P", "pawn"),
|
||||||
|
("N", "knight"),
|
||||||
|
]
|
||||||
|
COLOR = [
|
||||||
|
("W", "white"),
|
||||||
|
("B", "black"),
|
||||||
|
]
|
||||||
|
|
||||||
|
HERE = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
engine = subprocess.Popen(
|
||||||
|
["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 {
|
||||||
|
"piece_type": dict(PIECE_TYPE)[piece_type],
|
||||||
|
"color": dict(COLOR)[color],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(",")
|
||||||
|
if status != "ok":
|
||||||
|
flask.abort(400)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def parse_state(state_str):
|
||||||
|
return {
|
||||||
|
state_str[i + 2 : i + 4]: make_piece(state_str[i : i + 2])
|
||||||
|
for i in range(0, len(state_str), 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/get_state/")
|
||||||
|
def 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 = [moves_str[i : i + 2] for i in range(0, len(moves_str), 2)]
|
||||||
|
return flask.jsonify(moves)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/make_move/", methods=["POST"])
|
||||||
|
def make_move():
|
||||||
|
source, target = flask.request.json
|
||||||
|
(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))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True, host="127.0.0.1", port=3000)
|
||||||
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 },
|
||||||
|
]
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<title>Chess</title>
|
<title>Chess</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="main.js"></script>
|
<script type="module" src="main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions" : {
|
"compilerOptions": {
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"target": "es6"
|
"target": "es2017"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
180
frontend/main.js
180
frontend/main.js
@@ -1,92 +1,120 @@
|
|||||||
const BOX_SIDE = 80;
|
import * as visuals from "./visuals.js";
|
||||||
const INDEX_MARGIN = BOX_SIDE / 2;
|
|
||||||
const INDEX_LETTERS = 'abcdefgh';
|
|
||||||
|
|
||||||
function createCanvas() {
|
function get_json(url) {
|
||||||
let canvas = document.createElement('canvas');
|
var request = new XMLHttpRequest();
|
||||||
let boardSide = BOX_SIDE * 8 + INDEX_MARGIN;
|
request.open("get", url, false);
|
||||||
canvas.width = boardSide;
|
request.send(null);
|
||||||
canvas.height = boardSide;
|
return JSON.parse(request.responseText);
|
||||||
document.body.appendChild(canvas);
|
|
||||||
return canvas
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawChessBoard(canvas) {
|
function post_json(url, json) {
|
||||||
let ctx = canvas.getContext('2d');
|
var request = new XMLHttpRequest();
|
||||||
|
request.open("post", url, false);
|
||||||
|
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||||
|
request.send(JSON.stringify(json));
|
||||||
|
if (request.status != 200) return null;
|
||||||
|
return JSON.parse(request.responseText);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw squares
|
class Backend {
|
||||||
ctx.fillStyle = '#c0c0c0';
|
getConfig() {
|
||||||
for (let r = 0; r < 8; r++) {
|
return new Map(
|
||||||
for (let c = 0; c < 8; c++) {
|
Object.entries(get_json("http://localhost:3000/get_state/"))
|
||||||
if ((r + c) % 2) ctx.fillRect(c*BOX_SIDE + INDEX_MARGIN,
|
);
|
||||||
r*BOX_SIDE + INDEX_MARGIN,
|
|
||||||
BOX_SIDE, BOX_SIDE);
|
|
||||||
}
|
}
|
||||||
|
makeMove(source, target) {
|
||||||
|
return post_json("http://localhost:3000/make_move/", [source, target]);
|
||||||
}
|
}
|
||||||
|
chooseMove(color) {
|
||||||
// Draw letters
|
return post_json("http://localhost:3000/choose_move/", color);
|
||||||
ctx.fillStyle = 'black';
|
}
|
||||||
let fontsize = INDEX_MARGIN / 2;
|
getAvailableMoves(position) {
|
||||||
ctx.font = `${fontsize}px Monospace`;
|
return post_json("http://localhost:3000/get_moves/", position);
|
||||||
for (let idx = 0; idx < 8; idx++) {
|
}
|
||||||
ctx.fillText(INDEX_LETTERS[idx],
|
reset() {
|
||||||
BOX_SIDE*idx + BOX_SIDE/2 + INDEX_MARGIN - fontsize / 4,
|
return post_json("http://localhost:3000/reset/");
|
||||||
INDEX_MARGIN / 2);
|
|
||||||
ctx.fillText((8-idx).toString(),
|
|
||||||
INDEX_MARGIN / 2 - fontsize / 4,
|
|
||||||
BOX_SIDE*idx + BOX_SIDE/2 + INDEX_MARGIN + fontsize / 4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawPiece(canvas, piece, field) {
|
class Chess {
|
||||||
let canvasRect = canvas.getBoundingClientRect();
|
constructor() {
|
||||||
let rc = boardIndexToRowCol(field);
|
this.backend = new Backend();
|
||||||
|
this.canvas = new visuals.Canvas();
|
||||||
|
this.autoplay = false;
|
||||||
|
|
||||||
let im = new Image(BOX_SIDE, BOX_SIDE);
|
this.button = document.createElement("button");
|
||||||
im.src = `./icons/${piece}.svg`;
|
this.button.innerHTML = "Autoplay Off";
|
||||||
im.onload = () => {
|
this.button.onclick = () => {
|
||||||
let topPx = canvasRect.top + INDEX_MARGIN + rc[0]*BOX_SIDE + window.pageYOffset;
|
this.autoplay = !this.autoplay;
|
||||||
let leftPx = canvasRect.left + INDEX_MARGIN + rc[1]*BOX_SIDE + window.pageXOffset;
|
this.button.innerHTML = ["Autoplay Off", "Autoplay On"][
|
||||||
im.style.position = 'absolute';
|
Number(this.autoplay)
|
||||||
im.style.top = `${topPx}px`;
|
];
|
||||||
im.style.left = `${leftPx}px`;
|
|
||||||
};
|
};
|
||||||
document.body.appendChild(im);
|
document.body.appendChild(this.button);
|
||||||
}
|
|
||||||
|
|
||||||
function _setupBoard(canvas) {
|
this.resetButton = document.createElement("button");
|
||||||
drawPiece(_canvas, 'rook_black', 'a8');
|
this.resetButton.innerHTML = "Reset";
|
||||||
drawPiece(_canvas, 'rook_black', 'h8');
|
this.resetButton.onclick = () => {
|
||||||
drawPiece(_canvas, 'knight_black', 'b8');
|
this.backend.reset();
|
||||||
drawPiece(_canvas, 'knight_black', 'g8');
|
this.updateState();
|
||||||
drawPiece(_canvas, 'bishop_black', 'c8');
|
};
|
||||||
drawPiece(_canvas, 'bishop_black', 'f8');
|
document.body.appendChild(this.resetButton);
|
||||||
drawPiece(_canvas, 'queen_black', 'd8');
|
|
||||||
drawPiece(_canvas, 'king_black', 'e8');
|
|
||||||
|
|
||||||
for (let colLetter of INDEX_LETTERS) {
|
this.configVis = new visuals.ConfigVis(this.backend.getConfig());
|
||||||
drawPiece(canvas, 'pawn_black', `${colLetter}7`);
|
this.configVis.draw(this.canvas);
|
||||||
drawPiece(canvas, 'pawn_white', `${colLetter}2`);
|
this.activeSquares = new visuals.ActiveSquares();
|
||||||
|
this.moveSource = null;
|
||||||
|
this.canMoveTo = [];
|
||||||
|
document.onclick = (ev) => this.click(ev);
|
||||||
|
}
|
||||||
|
updateState() {
|
||||||
|
let config = this.backend.getConfig();
|
||||||
|
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);
|
||||||
|
if (validMoves === null) return;
|
||||||
|
this.activeSquares.selectSquare(this.canvas, source);
|
||||||
|
this.activeSquares.setMoveSquares(this.canvas, validMoves);
|
||||||
|
this.moveSource = source;
|
||||||
|
}
|
||||||
|
finalizeMove(target) {
|
||||||
|
let success;
|
||||||
|
if (target !== null) {
|
||||||
|
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);
|
||||||
|
let position;
|
||||||
|
if (rc === null) {
|
||||||
|
position = null;
|
||||||
|
} else {
|
||||||
|
position = visuals.rowColToBoardIndex(...rc);
|
||||||
|
}
|
||||||
|
if (this.moveSource === null) {
|
||||||
|
this.initializeMove(position);
|
||||||
|
} else {
|
||||||
|
this.finalizeMove(position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
drawPiece(_canvas, 'rook_white', 'a1');
|
|
||||||
drawPiece(_canvas, 'rook_white', 'h1');
|
|
||||||
drawPiece(_canvas, 'knight_white', 'b1');
|
|
||||||
drawPiece(_canvas, 'knight_white', 'g1');
|
|
||||||
drawPiece(_canvas, 'bishop_white', 'c1');
|
|
||||||
drawPiece(_canvas, 'bishop_white', 'f1');
|
|
||||||
drawPiece(_canvas, 'queen_white', 'd1');
|
|
||||||
drawPiece(_canvas, 'king_white', 'e1');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function boardIndexToRowCol(boardIndex) {
|
new Chess();
|
||||||
let colLetter = boardIndex[0];
|
|
||||||
let rowDigit = boardIndex[1];
|
|
||||||
let row = 8 - parseInt(rowDigit);
|
|
||||||
let col = INDEX_LETTERS.indexOf(colLetter);
|
|
||||||
return [row, col];
|
|
||||||
}
|
|
||||||
|
|
||||||
var _canvas = createCanvas();
|
|
||||||
drawChessBoard(_canvas);
|
|
||||||
_setupBoard(_canvas);
|
|
||||||
|
|||||||
196
frontend/visuals.js
Normal file
196
frontend/visuals.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
const BOX_SIDE = 80;
|
||||||
|
const INDEX_MARGIN = BOX_SIDE / 2;
|
||||||
|
const INDEX_LETTERS = "abcdefgh";
|
||||||
|
|
||||||
|
export class Canvas {
|
||||||
|
constructor() {
|
||||||
|
this._canvas = document.createElement("canvas");
|
||||||
|
this._ctx = this._canvas.getContext("2d");
|
||||||
|
let boardSide = BOX_SIDE * 8 + INDEX_MARGIN;
|
||||||
|
this._canvas.width = boardSide;
|
||||||
|
this._canvas.height = boardSide;
|
||||||
|
document.body.appendChild(this._canvas);
|
||||||
|
this.drawChessBoard();
|
||||||
|
}
|
||||||
|
decideColor(r, c) {
|
||||||
|
if ((r + c) % 2) return "#c0c0c0";
|
||||||
|
else return "#ffffff";
|
||||||
|
}
|
||||||
|
drawSquareColor(color, r, c) {
|
||||||
|
const crtStyle = this._ctx.fillStyle;
|
||||||
|
this._ctx.fillStyle = color;
|
||||||
|
this._ctx.fillRect(
|
||||||
|
c * BOX_SIDE + INDEX_MARGIN,
|
||||||
|
r * BOX_SIDE + INDEX_MARGIN,
|
||||||
|
BOX_SIDE,
|
||||||
|
BOX_SIDE
|
||||||
|
);
|
||||||
|
this._ctx.fillStyle = crtStyle;
|
||||||
|
}
|
||||||
|
drawChessBoard() {
|
||||||
|
// Draw squares
|
||||||
|
for (let r = 0; r < 8; r++) {
|
||||||
|
for (let c = 0; c < 8; c++) {
|
||||||
|
this.drawSquareColor(this.decideColor(r, c), r, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw letters
|
||||||
|
this._ctx.fillStyle = "black";
|
||||||
|
let fontsize = INDEX_MARGIN / 2;
|
||||||
|
this._ctx.font = `${fontsize}px Monospace`;
|
||||||
|
for (let idx = 0; idx < 8; idx++) {
|
||||||
|
this._ctx.fillText(
|
||||||
|
INDEX_LETTERS[idx],
|
||||||
|
BOX_SIDE * idx + BOX_SIDE / 2 + INDEX_MARGIN - fontsize / 4,
|
||||||
|
INDEX_MARGIN / 2
|
||||||
|
);
|
||||||
|
this._ctx.fillText(
|
||||||
|
(8 - idx).toString(),
|
||||||
|
INDEX_MARGIN / 2 - fontsize / 4,
|
||||||
|
BOX_SIDE * idx + BOX_SIDE / 2 + INDEX_MARGIN + fontsize / 4
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screenCoordsToRowCol(cx, cy) {
|
||||||
|
let canvasRect = this._canvas.getBoundingClientRect();
|
||||||
|
if (cx > canvasRect.right || cy > canvasRect.bottom) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
let canvasX = cx - canvasRect.left;
|
||||||
|
let canvasY = cy - canvasRect.top;
|
||||||
|
let row = Math.floor((canvasY - INDEX_MARGIN) / BOX_SIDE);
|
||||||
|
let col = Math.floor((canvasX - INDEX_MARGIN) / BOX_SIDE);
|
||||||
|
return [row, col];
|
||||||
|
}
|
||||||
|
rowColToScreenCoords(row, col) {
|
||||||
|
let canvasRect = this._canvas.getBoundingClientRect();
|
||||||
|
let topPx =
|
||||||
|
canvasRect.top + INDEX_MARGIN + row * BOX_SIDE + window.pageYOffset;
|
||||||
|
let leftPx =
|
||||||
|
canvasRect.left + INDEX_MARGIN + col * BOX_SIDE + window.pageXOffset;
|
||||||
|
return [topPx, leftPx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function boardIndexToRowCol(boardIndex) {
|
||||||
|
let [colLetter, rowDigit] = boardIndex;
|
||||||
|
let row = 8 - parseInt(rowDigit);
|
||||||
|
let col = INDEX_LETTERS.indexOf(colLetter);
|
||||||
|
return [row, col];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rowColToBoardIndex(row, col) {
|
||||||
|
let rowDigit = 8 - row;
|
||||||
|
let colLetter = INDEX_LETTERS[col];
|
||||||
|
return `${colLetter}${rowDigit}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ActiveSquares {
|
||||||
|
constructor() {
|
||||||
|
this.selectedSquare = null;
|
||||||
|
this.moveSquares = [];
|
||||||
|
}
|
||||||
|
selectSquare(canvas, position) {
|
||||||
|
let [r, c] = boardIndexToRowCol(position);
|
||||||
|
canvas.drawSquareColor("#c0c0ff", r, c);
|
||||||
|
this.selectedSquare = position;
|
||||||
|
}
|
||||||
|
setMoveSquares(canvas, moveSquares) {
|
||||||
|
moveSquares.forEach((position) => {
|
||||||
|
let [r, c] = boardIndexToRowCol(position);
|
||||||
|
canvas.drawSquareColor("#ffffc0", r, c);
|
||||||
|
this.moveSquares.push(position);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unsetMoveSquares(canvas) {
|
||||||
|
this.moveSquares.forEach((position) => {
|
||||||
|
let [r, c] = boardIndexToRowCol(position);
|
||||||
|
canvas.drawSquareColor(canvas.decideColor(r, c), r, c);
|
||||||
|
});
|
||||||
|
this.moveSquares = [];
|
||||||
|
}
|
||||||
|
unselectSquare(canvas) {
|
||||||
|
if (this.selectedSquare === null) return;
|
||||||
|
let [r, c] = boardIndexToRowCol(this.selectedSquare);
|
||||||
|
canvas.drawSquareColor(canvas.decideColor(r, c), r, c);
|
||||||
|
this.selectedSquare = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PieceVis {
|
||||||
|
constructor(piece) {
|
||||||
|
this.piece = piece;
|
||||||
|
this.im = null;
|
||||||
|
}
|
||||||
|
get ["icon"]() {
|
||||||
|
return `./icons/${this.piece.piece_type}_${this.piece.color}.svg`;
|
||||||
|
}
|
||||||
|
draw(canvas, position) {
|
||||||
|
let [topPx, leftPx] = canvas.rowColToScreenCoords(
|
||||||
|
...boardIndexToRowCol(position),
|
||||||
|
canvas
|
||||||
|
);
|
||||||
|
if (this.im === null) {
|
||||||
|
let im = new Image(BOX_SIDE, BOX_SIDE);
|
||||||
|
im.src = this.icon;
|
||||||
|
im.onload = () => {
|
||||||
|
im.style.position = "absolute";
|
||||||
|
im.style.top = `${topPx}px`;
|
||||||
|
im.style.left = `${leftPx}px`;
|
||||||
|
};
|
||||||
|
document.body.appendChild(im);
|
||||||
|
this.im = im;
|
||||||
|
} else {
|
||||||
|
this.im.style.top = `${topPx}px`;
|
||||||
|
this.im.style.left = `${leftPx}px`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
undraw() {
|
||||||
|
if (this.im !== null) {
|
||||||
|
this.im.remove();
|
||||||
|
this.im = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pieceEqual(pieceA, pieceB) {
|
||||||
|
return (
|
||||||
|
pieceA.color === pieceB.color && pieceA.piece_type === pieceB.piece_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConfigVis {
|
||||||
|
constructor(configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.piecesVis = new Map();
|
||||||
|
}
|
||||||
|
draw(canvas) {
|
||||||
|
for (let [position, piece] of this.configuration) {
|
||||||
|
if (this.piecesVis.has(position)) {
|
||||||
|
if (pieceEqual(this.piecesVis.get(position).piece, piece)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let pv = this.piecesVis.get(position);
|
||||||
|
if (pv !== undefined) {
|
||||||
|
pv.undraw();
|
||||||
|
}
|
||||||
|
pv = new PieceVis(piece);
|
||||||
|
pv.draw(canvas, position);
|
||||||
|
this.piecesVis.set(position, pv);
|
||||||
|
}
|
||||||
|
for (let [position, pv] of this.piecesVis) {
|
||||||
|
if (!this.configuration.has(position)) {
|
||||||
|
pv.undraw();
|
||||||
|
this.piecesVis.delete(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
undraw() {
|
||||||
|
for (let pv of this.piecesVis.values()) {
|
||||||
|
pv.undraw();
|
||||||
|
}
|
||||||
|
this.piecesVis.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
85
rs/Cargo.lock
generated
Normal file
85
rs/Cargo.lock
generated
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# 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"
|
||||||
9
rs/Cargo.toml
Normal file
9
rs/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "schach"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rand = "0.8.4"
|
||||||
417
rs/src/board.rs
Normal file
417
rs/src/board.rs
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
|
pub enum Color {
|
||||||
|
Black,
|
||||||
|
White,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
fn get_home_rank(&self) -> Rank {
|
||||||
|
match self {
|
||||||
|
Color::Black => Rank::_8,
|
||||||
|
Color::White => Rank::_1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_pawn_rank(&self) -> Rank {
|
||||||
|
match self {
|
||||||
|
Color::Black => Rank::_7,
|
||||||
|
Color::White => Rank::_2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn other(&self) -> Color {
|
||||||
|
match self {
|
||||||
|
Color::Black => Color::White,
|
||||||
|
Color::White => Color::Black,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Move_ {
|
||||||
|
pub source: Position,
|
||||||
|
pub target: Position,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
|
||||||
|
pub struct Position {
|
||||||
|
pub file: File,
|
||||||
|
pub rank: Rank,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Dir {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
pub fn new(file: File, rank: Rank) -> Position {
|
||||||
|
Position {
|
||||||
|
file,
|
||||||
|
rank,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn chain(&self, dirs: &[&Dir]) -> Option<Position> {
|
||||||
|
dirs.iter().try_fold(self.clone(), |pos, dir| pos.delta(dir))
|
||||||
|
}
|
||||||
|
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, Debug)]
|
||||||
|
pub enum Rank {
|
||||||
|
_1,
|
||||||
|
_2,
|
||||||
|
_3,
|
||||||
|
_4,
|
||||||
|
_5,
|
||||||
|
_6,
|
||||||
|
_7,
|
||||||
|
_8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
|
pub enum File {
|
||||||
|
A,
|
||||||
|
B,
|
||||||
|
C,
|
||||||
|
D,
|
||||||
|
E,
|
||||||
|
F,
|
||||||
|
G,
|
||||||
|
H,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait GridAxis
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
Self: Clone,
|
||||||
|
{
|
||||||
|
fn incr(&self) -> Option<Self>;
|
||||||
|
fn decr(&self) -> Option<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridAxis for Rank {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
// Pawn
|
||||||
|
fn new_pawn(color: Color, file: File) -> (Piece, Position) {
|
||||||
|
let pos = Position::new(file, color.get_pawn_rank());
|
||||||
|
(
|
||||||
|
Piece {
|
||||||
|
piece_type: PieceType::Pawn,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
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_move_rays(&self, position: &Position) -> Vec<Ray> {
|
||||||
|
match self.piece_type {
|
||||||
|
PieceType::Pawn => {
|
||||||
|
PieceType::pawn_move_rays(position, &self.color)
|
||||||
|
}
|
||||||
|
PieceType::Knight => PieceType::knight_rays(position),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn relocate(
|
||||||
|
&mut self,
|
||||||
|
source: &Position,
|
||||||
|
target: Position,
|
||||||
|
) -> Result<(), ()> {
|
||||||
|
let (_, piece) = self.state.remove_entry(source).ok_or(())?;
|
||||||
|
self.set_at(target, Some(piece));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.state.clear();
|
||||||
|
for piece_type in &[PieceType::Pawn, PieceType::Knight] {
|
||||||
|
for (piece, position) in piece_type.initial_setup() {
|
||||||
|
self.set_at(position, Some(piece));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
) -> Result<impl Iterator<Item = Position> + '_, ()> {
|
||||||
|
Ok(self.find_moves(position)?.chain(self.find_captures(position)?))
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
rs/src/main.rs
Normal file
8
rs/src/main.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
extern crate rand;
|
||||||
|
mod board;
|
||||||
|
mod engine;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
|
fn main() -> Result<(), ()> {
|
||||||
|
ui::Ui::new().run()
|
||||||
|
}
|
||||||
315
rs/src/ui.rs
Normal file
315
rs/src/ui.rs
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
use crate::board;
|
||||||
|
use crate::board::GridAxis;
|
||||||
|
use crate::engine;
|
||||||
|
use std::io::BufRead;
|
||||||
|
|
||||||
|
impl std::fmt::Display for board::Color {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
board::Color::White => write!(f, "W"),
|
||||||
|
board::Color::Black => write!(f, "B"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for board::Piece {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}{}", self.piece_type, self.color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait GridAxisIO: std::fmt::Display
|
||||||
|
where
|
||||||
|
Self: GridAxis,
|
||||||
|
{
|
||||||
|
fn parse(c: char) -> Result<Self, ()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GridAxisIO for board::Rank {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl board::Position {
|
||||||
|
fn parse(s: &str) -> Result<Self, ()> {
|
||||||
|
let mut chars = s.chars();
|
||||||
|
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(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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for board::Position {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "{}{}", self.file, self.rank)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for board::Board {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
for (position, piece) in self.iter() {
|
||||||
|
write!(f, "{}{}", piece, position)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
engine: engine::Engine,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ui {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Ui {
|
||||||
|
engine: engine::Engine::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_state(&self, args: &[&str]) -> Result<String, String> {
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
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 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())),
|
||||||
|
}
|
||||||
|
} 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> {
|
||||||
|
let mut cmd = s.split(',');
|
||||||
|
// There will be at least an empty string => otherwise panic
|
||||||
|
let cmd_type = cmd.next().unwrap();
|
||||||
|
let args: Vec<&str> = cmd.collect();
|
||||||
|
match cmd_type {
|
||||||
|
"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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn run(&mut self) -> Result<(), ()> {
|
||||||
|
let stdin = std::io::stdin();
|
||||||
|
let mut input_lines = stdin.lock().lines();
|
||||||
|
loop {
|
||||||
|
match input_lines.next() {
|
||||||
|
None => break Ok(()),
|
||||||
|
Some(line) => match line {
|
||||||
|
Err(_) => break Err(()),
|
||||||
|
Ok(line) => match self.handle_command(&line) {
|
||||||
|
Ok(result) => println!("ok,{}", result),
|
||||||
|
Err(reason) => println!("err,{}", reason),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user