Compare commits
26 Commits
4ae73410bd
...
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
|
@@ -4,8 +4,10 @@ import subprocess
|
|||||||
import flask
|
import flask
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
||||||
|
PIECE_TYPE = [
|
||||||
PIECE_TYPE = [("P", "pawn")]
|
("P", "pawn"),
|
||||||
|
("N", "knight"),
|
||||||
|
]
|
||||||
COLOR = [
|
COLOR = [
|
||||||
("W", "white"),
|
("W", "white"),
|
||||||
("B", "black"),
|
("B", "black"),
|
||||||
@@ -15,12 +17,17 @@ HERE = os.path.abspath(os.path.dirname(__file__))
|
|||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
engine = subprocess.Popen(
|
engine = subprocess.Popen(
|
||||||
[os.path.join(HERE, "../rs/target/debug/schach")],
|
["cargo", "run"],
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=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):
|
def make_piece(piece_str):
|
||||||
piece_type, color = piece_str
|
piece_type, color = piece_str
|
||||||
return {
|
return {
|
||||||
@@ -33,29 +40,52 @@ def ask_engine(command):
|
|||||||
engine.stdin.write(f"{command}\n".encode("ascii"))
|
engine.stdin.write(f"{command}\n".encode("ascii"))
|
||||||
engine.stdin.flush()
|
engine.stdin.flush()
|
||||||
reply = engine.stdout.readline().decode("ascii").strip()
|
reply = engine.stdout.readline().decode("ascii").strip()
|
||||||
status, result = reply.split(",")
|
status, *result = reply.split(",")
|
||||||
if status != "ok":
|
if status != "ok":
|
||||||
flask.abort(400)
|
flask.abort(400)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@app.route("/get_state/")
|
def parse_state(state_str):
|
||||||
def get_state():
|
return {
|
||||||
state_str = ask_engine("get_state")
|
|
||||||
state = {
|
|
||||||
state_str[i + 2 : i + 4]: make_piece(state_str[i : i + 2])
|
state_str[i + 2 : i + 4]: make_piece(state_str[i : i + 2])
|
||||||
for i in range(0, len(state_str), 4)
|
for i in range(0, len(state_str), 4)
|
||||||
}
|
}
|
||||||
return flask.jsonify(state)
|
|
||||||
|
|
||||||
|
@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"])
|
@app.route("/get_moves/", methods=["POST"])
|
||||||
def get_moves():
|
def get_moves():
|
||||||
position_str = flask.request.json
|
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)]
|
moves = [moves_str[i : i + 2] for i in range(0, len(moves_str), 2)]
|
||||||
return flask.jsonify(moves)
|
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__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True, host="127.0.0.1", port=3000)
|
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 },
|
||||||
|
]
|
||||||
@@ -17,26 +17,49 @@ function post_json(url, json) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Backend {
|
class Backend {
|
||||||
constructor() {
|
getConfig() {
|
||||||
this.config = new Configuration(
|
return new Map(
|
||||||
new Map(Object.entries(get_json("http://localhost:3000/get_state/")))
|
Object.entries(get_json("http://localhost:3000/get_state/"))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
getConfig() {
|
|
||||||
return this.config;
|
|
||||||
}
|
|
||||||
makeMove(source, target) {
|
makeMove(source, target) {
|
||||||
this.config.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) {
|
getAvailableMoves(position) {
|
||||||
return post_json("http://localhost:3000/get_moves/", position);
|
return post_json("http://localhost:3000/get_moves/", position);
|
||||||
}
|
}
|
||||||
|
reset() {
|
||||||
|
return post_json("http://localhost:3000/reset/");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Chess {
|
class Chess {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.backend = new Backend();
|
this.backend = new Backend();
|
||||||
this.canvas = new visuals.Canvas();
|
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 = new visuals.ConfigVis(this.backend.getConfig());
|
||||||
this.configVis.draw(this.canvas);
|
this.configVis.draw(this.canvas);
|
||||||
this.activeSquares = new visuals.ActiveSquares();
|
this.activeSquares = new visuals.ActiveSquares();
|
||||||
@@ -44,11 +67,17 @@ class Chess {
|
|||||||
this.canMoveTo = [];
|
this.canMoveTo = [];
|
||||||
document.onclick = (ev) => this.click(ev);
|
document.onclick = (ev) => this.click(ev);
|
||||||
}
|
}
|
||||||
syncBackend() {
|
updateState() {
|
||||||
let config = this.backend.getConfig();
|
let config = this.backend.getConfig();
|
||||||
this.configVis.configuration = config;
|
this.configVis.configuration = config;
|
||||||
this.configVis.draw(this.canvas);
|
this.configVis.draw(this.canvas);
|
||||||
}
|
}
|
||||||
|
chooseMove() {
|
||||||
|
let move = this.backend.chooseMove("black");
|
||||||
|
if (move === null) return;
|
||||||
|
this.backend.makeMove(...move);
|
||||||
|
this.updateState();
|
||||||
|
}
|
||||||
initializeMove(source) {
|
initializeMove(source) {
|
||||||
if (source === null) return;
|
if (source === null) return;
|
||||||
let validMoves = this.backend.getAvailableMoves(source);
|
let validMoves = this.backend.getAvailableMoves(source);
|
||||||
@@ -58,13 +87,19 @@ class Chess {
|
|||||||
this.moveSource = source;
|
this.moveSource = source;
|
||||||
}
|
}
|
||||||
finalizeMove(target) {
|
finalizeMove(target) {
|
||||||
|
let success;
|
||||||
if (target !== null) {
|
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.unselectSquare(this.canvas);
|
||||||
this.activeSquares.unsetMoveSquares(this.canvas);
|
this.activeSquares.unsetMoveSquares(this.canvas);
|
||||||
this.syncBackend();
|
this.updateState();
|
||||||
this.moveSource = null;
|
this.moveSource = null;
|
||||||
|
if (this.autoplay && success) {
|
||||||
|
this.chooseMove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
click(ev) {
|
click(ev) {
|
||||||
let rc = this.canvas.screenCoordsToRowCol(ev.clientX, ev.clientY);
|
let rc = this.canvas.screenCoordsToRowCol(ev.clientX, ev.clientY);
|
||||||
@@ -82,30 +117,4 @@ class Chess {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Configuration {
|
|
||||||
constructor(board) {
|
|
||||||
this.board = new Map(board);
|
|
||||||
}
|
|
||||||
getAt(position) {
|
|
||||||
if (!this.board.has(position)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.board.get(position);
|
|
||||||
}
|
|
||||||
setAt(position, piece) {
|
|
||||||
this.board.set(position, piece);
|
|
||||||
}
|
|
||||||
dropAt(position) {
|
|
||||||
this.board.delete(position);
|
|
||||||
}
|
|
||||||
makeMove(source, target) {
|
|
||||||
const piece = this.getAt(source);
|
|
||||||
if (piece === null) return;
|
|
||||||
else {
|
|
||||||
this.setAt(target, piece);
|
|
||||||
this.dropAt(source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new Chess();
|
new Chess();
|
||||||
|
|||||||
@@ -154,15 +154,21 @@ class PieceVis {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pieceEqual(pieceA, pieceB) {
|
||||||
|
return (
|
||||||
|
pieceA.color === pieceB.color && pieceA.piece_type === pieceB.piece_type
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export class ConfigVis {
|
export class ConfigVis {
|
||||||
constructor(configuration) {
|
constructor(configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.piecesVis = new Map();
|
this.piecesVis = new Map();
|
||||||
}
|
}
|
||||||
draw(canvas) {
|
draw(canvas) {
|
||||||
for (let [position, piece] of this.configuration.board) {
|
for (let [position, piece] of this.configuration) {
|
||||||
if (this.piecesVis.has(position)) {
|
if (this.piecesVis.has(position)) {
|
||||||
if (this.piecesVis.get(position).piece == piece) {
|
if (pieceEqual(this.piecesVis.get(position).piece, piece)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,7 +181,7 @@ export class ConfigVis {
|
|||||||
this.piecesVis.set(position, pv);
|
this.piecesVis.set(position, pv);
|
||||||
}
|
}
|
||||||
for (let [position, pv] of this.piecesVis) {
|
for (let [position, pv] of this.piecesVis) {
|
||||||
if (!this.configuration.board.has(position)) {
|
if (!this.configuration.has(position)) {
|
||||||
pv.undraw();
|
pv.undraw();
|
||||||
this.piecesVis.delete(position);
|
this.piecesVis.delete(position);
|
||||||
}
|
}
|
||||||
|
|||||||
78
rs/Cargo.lock
generated
78
rs/Cargo.lock
generated
@@ -2,6 +2,84 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
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]]
|
[[package]]
|
||||||
name = "schach"
|
name = "schach"
|
||||||
version = "0.1.0"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
rand = "0.8.4"
|
||||||
|
|||||||
413
rs/src/board.rs
413
rs/src/board.rs
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone)]
|
#[derive(Eq, PartialEq, Clone, Debug)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
Black,
|
Black,
|
||||||
White,
|
White,
|
||||||
@@ -19,43 +19,58 @@ impl Color {
|
|||||||
Color::White => Rank::_2,
|
Color::White => Rank::_2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_direction(&self) -> i8 {
|
pub fn other(&self) -> Color {
|
||||||
match self {
|
match self {
|
||||||
Color::Black => -1,
|
Color::Black => Color::White,
|
||||||
Color::White => 1,
|
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 struct Position {
|
||||||
pub rank: Rank,
|
|
||||||
pub file: File,
|
pub file: File,
|
||||||
|
pub rank: Rank,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Dir {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Up,
|
||||||
|
Down,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
pub fn new(rank: Rank, file: File) -> Position {
|
pub fn new(file: File, rank: Rank) -> Position {
|
||||||
Position {
|
Position {
|
||||||
rank,
|
|
||||||
file,
|
file,
|
||||||
|
rank,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn delta(&self, delta_rank_file: (i8, i8)) -> Result<Position, ()> {
|
fn chain(&self, dirs: &[&Dir]) -> Option<Position> {
|
||||||
let (delta_rank, delta_file) = delta_rank_file;
|
dirs.iter().try_fold(self.clone(), |pos, dir| pos.delta(dir))
|
||||||
Ok(Position::new(
|
|
||||||
self.rank.delta(delta_rank)?,
|
|
||||||
self.file.delta(delta_file)?,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
fn deltas_to_valid_positions(&self, deltas: &[(i8, i8)]) -> Vec<Position> {
|
fn delta(&self, direction: &Dir) -> Option<Position> {
|
||||||
deltas
|
let (file, rank) = match direction {
|
||||||
.iter()
|
Dir::Left => (self.file.decr()?, self.rank),
|
||||||
.filter_map(|&delta_rank_file| self.delta(delta_rank_file).ok())
|
Dir::Right => (self.file.incr()?, self.rank),
|
||||||
.collect()
|
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 {
|
pub enum Rank {
|
||||||
_1,
|
_1,
|
||||||
_2,
|
_2,
|
||||||
@@ -67,7 +82,7 @@ pub enum Rank {
|
|||||||
_8,
|
_8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
pub enum File {
|
pub enum File {
|
||||||
A,
|
A,
|
||||||
B,
|
B,
|
||||||
@@ -84,137 +99,234 @@ where
|
|||||||
Self: Sized,
|
Self: Sized,
|
||||||
Self: Clone,
|
Self: Clone,
|
||||||
{
|
{
|
||||||
const ALL_VALUES: [Self; 8];
|
fn incr(&self) -> Option<Self>;
|
||||||
fn new(index: u8) -> Result<Self, ()> {
|
fn decr(&self) -> Option<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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GridAxis for Rank {
|
impl GridAxis for Rank {
|
||||||
const ALL_VALUES: [Rank; 8] = [
|
fn incr(&self) -> Option<Self> {
|
||||||
Rank::_1,
|
match self {
|
||||||
Rank::_2,
|
Rank::_1 => Some(Rank::_2),
|
||||||
Rank::_3,
|
Rank::_2 => Some(Rank::_3),
|
||||||
Rank::_4,
|
Rank::_3 => Some(Rank::_4),
|
||||||
Rank::_5,
|
Rank::_4 => Some(Rank::_5),
|
||||||
Rank::_6,
|
Rank::_5 => Some(Rank::_6),
|
||||||
Rank::_7,
|
Rank::_6 => Some(Rank::_7),
|
||||||
Rank::_8,
|
Rank::_7 => Some(Rank::_8),
|
||||||
];
|
Rank::_8 => None,
|
||||||
fn get_index(&self) -> u8 {
|
}
|
||||||
*self as u8
|
}
|
||||||
|
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 {
|
impl GridAxis for File {
|
||||||
const ALL_VALUES: [File; 8] = [
|
fn incr(&self) -> Option<Self> {
|
||||||
File::A,
|
match self {
|
||||||
File::B,
|
File::A => Some(File::B),
|
||||||
File::C,
|
File::B => Some(File::C),
|
||||||
File::D,
|
File::C => Some(File::D),
|
||||||
File::E,
|
File::D => Some(File::E),
|
||||||
File::F,
|
File::E => Some(File::F),
|
||||||
File::G,
|
File::F => Some(File::G),
|
||||||
File::H,
|
File::G => Some(File::H),
|
||||||
];
|
File::H => None,
|
||||||
fn get_index(&self) -> u8 {
|
}
|
||||||
*self as u8
|
}
|
||||||
|
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)]
|
#[derive(Clone)]
|
||||||
pub enum PieceType {
|
pub enum PieceType {
|
||||||
Pawn,
|
Pawn,
|
||||||
|
Knight,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PieceType {
|
impl PieceType {
|
||||||
fn initial_setup(&self) -> Vec<(Piece, Position)> {
|
// Pawn
|
||||||
match &self {
|
fn new_pawn(color: Color, file: File) -> (Piece, Position) {
|
||||||
PieceType::Pawn => [Color::Black, Color::White]
|
let pos = Position::new(file, color.get_pawn_rank());
|
||||||
.iter()
|
|
||||||
.flat_map(|color| {
|
|
||||||
File::ALL_VALUES.iter().map(|&file| {
|
|
||||||
(
|
(
|
||||||
Piece {
|
Piece {
|
||||||
piece_type: self.clone(),
|
piece_type: PieceType::Pawn,
|
||||||
color: color.clone(),
|
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 struct Piece {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub piece_type: PieceType,
|
pub piece_type: PieceType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Ray {
|
||||||
|
positions: Vec<Position>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Piece {
|
impl Piece {
|
||||||
fn get_moves(&self, position: &Position) -> Vec<Position> {
|
fn get_move_rays(&self, position: &Position) -> Vec<Ray> {
|
||||||
let deltas = match self.piece_type {
|
match self.piece_type {
|
||||||
PieceType::Pawn => self._pawn_get_move_deltas(position),
|
PieceType::Pawn => {
|
||||||
};
|
PieceType::pawn_move_rays(position, &self.color)
|
||||||
position.deltas_to_valid_positions(&deltas)
|
|
||||||
}
|
}
|
||||||
fn get_captures(&self, position: &Position) -> Vec<Position> {
|
PieceType::Knight => PieceType::knight_rays(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)]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn _pawn_get_capture_deltas(&self) -> Vec<(i8, i8)> {
|
fn get_capture_rays(&self, position: &Position) -> Vec<Ray> {
|
||||||
let direction = self.color.get_direction();
|
match self.piece_type {
|
||||||
vec![(direction, 1), (direction, -1)]
|
PieceType::Pawn => {
|
||||||
|
PieceType::pawn_capture_rays(position, &self.color)
|
||||||
|
}
|
||||||
|
PieceType::Knight => PieceType::knight_rays(position),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
state: HashMap<Position, Piece>,
|
state: HashMap<Position, Piece>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Board {
|
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)
|
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 {
|
match piece_or_empty {
|
||||||
Some(piece) => self.state.insert(position, piece),
|
Some(piece) => self.state.insert(position, piece),
|
||||||
None => self.state.remove(&position),
|
None => self.state.remove(&position),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
fn move_from_to(
|
pub fn relocate(
|
||||||
&mut self,
|
&mut self,
|
||||||
source: &Position,
|
source: &Position,
|
||||||
target: Position,
|
target: Position,
|
||||||
@@ -223,15 +335,9 @@ impl Board {
|
|||||||
self.set_at(target, Some(piece));
|
self.set_at(target, Some(piece));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new() -> Board {
|
|
||||||
Board {
|
|
||||||
state: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.state.clear();
|
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() {
|
for (piece, position) in piece_type.initial_setup() {
|
||||||
self.set_at(position, Some(piece));
|
self.set_at(position, Some(piece));
|
||||||
}
|
}
|
||||||
@@ -240,37 +346,72 @@ impl Board {
|
|||||||
pub fn iter(&self) -> impl Iterator<Item = (&Position, &Piece)> {
|
pub fn iter(&self) -> impl Iterator<Item = (&Position, &Piece)> {
|
||||||
self.state.iter()
|
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(
|
pub fn get_legal_moves(
|
||||||
&self,
|
&self,
|
||||||
position: &Position,
|
position: &Position,
|
||||||
) -> Option<Vec<Position>> {
|
) -> Result<impl Iterator<Item = Position> + '_, ()> {
|
||||||
let piece = match self.get_at(position) {
|
Ok(self.find_moves(position)?.chain(self.find_captures(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())
|
|
||||||
}
|
}
|
||||||
pub fn make_move(
|
pub fn all_moves_for_color(
|
||||||
&mut self,
|
&self,
|
||||||
source: &Position,
|
color: Color,
|
||||||
target: Position,
|
) -> impl Iterator<Item = Move_> + '_ {
|
||||||
) -> Result<(), ()> {
|
self.iter()
|
||||||
if !self.get_legal_moves(source).ok_or(())?.contains(&target) {
|
.filter_map(move |(source, piece)| {
|
||||||
Err(())
|
if piece.color == color {
|
||||||
|
if let Ok(targets) = self.get_legal_moves(source) {
|
||||||
|
Some(targets.map(|target| Move_ {
|
||||||
|
source: source.clone(),
|
||||||
|
target,
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
// We checked that there is a piece at source in get_legal_moves
|
None
|
||||||
self.move_from_to(source, target)
|
}
|
||||||
}
|
} 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 board;
|
||||||
|
mod engine;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
fn main() -> Result<(), ()> {
|
fn main() -> Result<(), ()> {
|
||||||
|
|||||||
224
rs/src/ui.rs
224
rs/src/ui.rs
@@ -1,5 +1,6 @@
|
|||||||
use crate::board;
|
use crate::board;
|
||||||
use crate::board::GridAxis;
|
use crate::board::GridAxis;
|
||||||
|
use crate::engine;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
|
||||||
impl std::fmt::Display for board::Color {
|
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 {
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
board::PieceType::Pawn => write!(f, "P"),
|
board::PieceType::Pawn => write!(f, "P"),
|
||||||
|
board::PieceType::Knight => write!(f, "N"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,30 +31,70 @@ trait GridAxisIO: std::fmt::Display
|
|||||||
where
|
where
|
||||||
Self: GridAxis,
|
Self: GridAxis,
|
||||||
{
|
{
|
||||||
const ALL_CHARS: [char; 8];
|
fn parse(c: char) -> Result<Self, ()>;
|
||||||
fn parse(c: char) -> Result<Self, ()> {
|
|
||||||
let index = Self::ALL_CHARS.iter().position(|p| p == &c).ok_or(())?;
|
|
||||||
Ok(Self::ALL_VALUES[index].clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GridAxisIO for board::Rank {
|
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 {
|
impl std::fmt::Display for board::Rank {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
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 {
|
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 {
|
impl std::fmt::Display for board::File {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
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 file = board::File::parse(chars.next().ok_or(())?)?;
|
||||||
let rank = board::Rank::parse(chars.next().ok_or(())?)?;
|
let rank = board::Rank::parse(chars.next().ok_or(())?)?;
|
||||||
if chars.next().is_none() {
|
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 {
|
} else {
|
||||||
Err(())
|
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 {
|
pub struct Ui {
|
||||||
board: board::Board,
|
engine: engine::Engine,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ui {
|
impl Ui {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut board = board::Board::new();
|
|
||||||
board.reset();
|
|
||||||
Ui {
|
Ui {
|
||||||
board,
|
engine: engine::Engine::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn get_state(&self, args: &[&str]) -> Result<String, String> {
|
fn get_state(&self, args: &[&str]) -> Result<String, String> {
|
||||||
if !args.is_empty() {
|
if let [] = args {
|
||||||
Err("get_state takes no args".to_owned())
|
let result = format!("{}", self.engine.get_state());
|
||||||
} else {
|
|
||||||
let result = format!("{}", self.board);
|
|
||||||
Ok(result)
|
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> {
|
fn get_moves(&self, args: &[&str]) -> Result<String, String> {
|
||||||
if args.len() != 1 {
|
if let [position_str] = args {
|
||||||
return Err("get_moves takes 1 arg".to_owned());
|
let position =
|
||||||
};
|
board::Position::parse(position_str).map_err(|_| {
|
||||||
let position = board::Position::parse(args[0])
|
format!("Error parsing position {}", position_str)
|
||||||
.map_err(|_| format!("Error parsing position {}", args[0]))?;
|
})?;
|
||||||
match self.board.get_legal_moves(&position) {
|
match self.engine.board.get_legal_moves(&position) {
|
||||||
None => {
|
Err(_) => {
|
||||||
let error = format!("No moves possible from {}", position);
|
let error = format!("No moves possible from {}", position);
|
||||||
Err(error)
|
Err(error)
|
||||||
}
|
}
|
||||||
Some(positions) => {
|
Ok(positions) => {
|
||||||
Ok(positions.iter().map(|pos| format!("{}", pos)).collect())
|
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> {
|
fn make_move(&mut self, args: &[&str]) -> Result<String, String> {
|
||||||
if args.len() != 2 {
|
if let [source_str, target_str] = args {
|
||||||
return Err("make_move takes 2 args".to_owned());
|
let source = board::Position::parse(source_str)
|
||||||
};
|
.map_err(|_| format!("Error parsing source {}", source_str))?;
|
||||||
let source = board::Position::parse(args[0])
|
let target = board::Position::parse(target_str)
|
||||||
.map_err(|_| format!("Error parsing source {}", args[0]))?;
|
.map_err(|_| format!("Error parsing target {}", target_str))?;
|
||||||
let target = board::Position::parse(args[1])
|
match self.engine.make_move(&source, target) {
|
||||||
.map_err(|_| format!("Error parsing target {}", args[1]))?;
|
Err(()) => Err(format!(
|
||||||
match self.board.make_move(&source, target) {
|
"Move not possible {}-{}",
|
||||||
Err(()) => {
|
source_str, target_str
|
||||||
Err(format!("Move not possible {}-{}", args[0], args[1]))
|
)),
|
||||||
|
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> {
|
fn handle_command(&mut self, s: &str) -> Result<String, String> {
|
||||||
@@ -144,6 +289,9 @@ impl Ui {
|
|||||||
"get_state" => self.get_state(&args),
|
"get_state" => self.get_state(&args),
|
||||||
"get_moves" => self.get_moves(&args),
|
"get_moves" => self.get_moves(&args),
|
||||||
"make_move" => self.make_move(&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("No command given".to_owned()),
|
||||||
_ => Err("Invalid command".to_owned()),
|
_ => Err("Invalid command".to_owned()),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user