fix frontend board mapping, game-over handling, and WASM-4
- fix Xform yx and add a shared view-aware board_position helper - add GTK automatic game-over, restart, status, and multi-capture selection - pin librsvg to the 2.60 series to match GTK4/glib 0.20 - add WASM orientation, undo/redo/new/flip controls, and winner overlay - replace WASM crown images with canvas paths and remove dead files/deps - make WASM-4 playable with mouse input, crowns, winner handling, and correct mapping - move the WASM-4 release profile to the workspace root - clean up frontend dead code and update the README
This commit is contained in:
Generated
+200
-296
@@ -32,12 +32,6 @@ version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -87,20 +81,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"cairo-sys-rs 0.20.10",
|
||||
"glib 0.20.12",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b01fe135c0bd16afe262b6dea349bd5ea30e6de50708cec639aae7c5c14cc7e4"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"cairo-sys-rs 0.21.5",
|
||||
"glib 0.21.5",
|
||||
"cairo-sys-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -110,18 +92,7 @@ version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "059cc746549898cbfd9a47754288e5a958756650ef4652bbb6c5f71a6bda4f8b"
|
||||
dependencies = [
|
||||
"glib-sys 0.20.10",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cairo-sys-rs"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06c28280c6b12055b5e39e4554271ae4e6630b27c0da9148c4cf6485fc6d245c"
|
||||
dependencies = [
|
||||
"glib-sys 0.21.5",
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
@@ -234,26 +205,17 @@ checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
|
||||
|
||||
[[package]]
|
||||
name = "cssparser"
|
||||
version = "0.35.0"
|
||||
version = "0.31.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa"
|
||||
checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be"
|
||||
dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf",
|
||||
"phf 0.11.3",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser-color"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6eeef9ae8c0e112edd89eb6406b3156ffa99c7e037b3baef1dbdf4158d35c324"
|
||||
dependencies = [
|
||||
"cssparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cssparser-macros"
|
||||
version = "0.6.1"
|
||||
@@ -272,22 +234,12 @@ checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
version = "0.99.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
|
||||
dependencies = [
|
||||
"derive_more-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more-impl"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
|
||||
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
@@ -382,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.10.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8"
|
||||
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
@@ -486,8 +438,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c"
|
||||
dependencies = [
|
||||
"gdk-pixbuf-sys",
|
||||
"gio 0.20.12",
|
||||
"glib 0.20.12",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -497,9 +449,9 @@ version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96"
|
||||
dependencies = [
|
||||
"gio-sys 0.20.10",
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
@@ -510,13 +462,13 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4850c9d9c1aecd1a3eb14fadc1cdb0ac0a2298037e116264c7473e1740a32d60"
|
||||
dependencies = [
|
||||
"cairo-rs 0.20.12",
|
||||
"cairo-rs",
|
||||
"gdk-pixbuf",
|
||||
"gdk4-sys",
|
||||
"gio 0.20.12",
|
||||
"glib 0.20.12",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"pango 0.20.12",
|
||||
"pango",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -525,17 +477,28 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f6eb95798e2b46f279cf59005daf297d5b69555428f185650d71974a910473a"
|
||||
dependencies = [
|
||||
"cairo-sys-rs 0.20.10",
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
"gio-sys 0.20.10",
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"pango-sys 0.20.10",
|
||||
"pango-sys",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.14.2"
|
||||
@@ -556,25 +519,8 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"gio-sys 0.20.10",
|
||||
"glib 0.20.12",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"gio-sys 0.21.5",
|
||||
"glib 0.21.5",
|
||||
"gio-sys",
|
||||
"glib",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
@@ -586,24 +532,11 @@ version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83"
|
||||
dependencies = [
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gio-sys"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22"
|
||||
dependencies = [
|
||||
"glib-sys 0.21.5",
|
||||
"gobject-sys 0.21.5",
|
||||
"libc",
|
||||
"system-deps",
|
||||
"windows-sys 0.61.2",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -618,31 +551,10 @@ dependencies = [
|
||||
"futures-executor",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"gio-sys 0.20.10",
|
||||
"glib-macros 0.20.12",
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"libc",
|
||||
"memchr",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
"gio-sys 0.21.5",
|
||||
"glib-macros 0.21.5",
|
||||
"glib-sys 0.21.5",
|
||||
"gobject-sys 0.21.5",
|
||||
"gio-sys",
|
||||
"glib-macros",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"memchr",
|
||||
"smallvec",
|
||||
@@ -661,19 +573,6 @@ dependencies = [
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-macros"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.20.10"
|
||||
@@ -684,34 +583,13 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda"
|
||||
dependencies = [
|
||||
"glib-sys 0.20.10",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294"
|
||||
dependencies = [
|
||||
"glib-sys 0.21.5",
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
@@ -722,7 +600,7 @@ version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b86dfad7d14251c9acaf1de63bc8754b7e3b4e5b16777b6f5a748208fe9519b"
|
||||
dependencies = [
|
||||
"glib 0.20.12",
|
||||
"glib",
|
||||
"graphene-sys",
|
||||
"libc",
|
||||
]
|
||||
@@ -733,7 +611,7 @@ version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df583a85ba2d5e15e1797e40d666057b28bc2f60a67c9c24145e6db2cc3861ea"
|
||||
dependencies = [
|
||||
"glib-sys 0.20.10",
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
@@ -745,13 +623,13 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61f5e72f931c8c9f65fbfc89fe0ddc7746f147f822f127a53a9854666ac1f855"
|
||||
dependencies = [
|
||||
"cairo-rs 0.20.12",
|
||||
"cairo-rs",
|
||||
"gdk4",
|
||||
"glib 0.20.12",
|
||||
"glib",
|
||||
"graphene-rs",
|
||||
"gsk4-sys",
|
||||
"libc",
|
||||
"pango 0.20.12",
|
||||
"pango",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -760,13 +638,13 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755059de55fa6f85a46bde8caf03e2184c96bfda1f6206163c72fb0ea12436dc"
|
||||
dependencies = [
|
||||
"cairo-sys-rs 0.20.10",
|
||||
"cairo-sys-rs",
|
||||
"gdk4-sys",
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"graphene-sys",
|
||||
"libc",
|
||||
"pango-sys 0.20.10",
|
||||
"pango-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
@@ -776,19 +654,19 @@ version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f274dd0102c21c47bbfa8ebcb92d0464fab794a22fad6c3f3d5f165139a326d6"
|
||||
dependencies = [
|
||||
"cairo-rs 0.20.12",
|
||||
"cairo-rs",
|
||||
"field-offset",
|
||||
"futures-channel",
|
||||
"gdk-pixbuf",
|
||||
"gdk4",
|
||||
"gio 0.20.12",
|
||||
"glib 0.20.12",
|
||||
"gio",
|
||||
"glib",
|
||||
"graphene-rs",
|
||||
"gsk4",
|
||||
"gtk4-macros",
|
||||
"gtk4-sys",
|
||||
"libc",
|
||||
"pango 0.20.12",
|
||||
"pango",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -809,16 +687,16 @@ version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41e03b01e54d77c310e1d98647d73f996d04b2f29b9121fe493ea525a7ec03d6"
|
||||
dependencies = [
|
||||
"cairo-sys-rs 0.20.10",
|
||||
"cairo-sys-rs",
|
||||
"gdk-pixbuf-sys",
|
||||
"gdk4-sys",
|
||||
"gio-sys 0.20.10",
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"gio-sys",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"graphene-sys",
|
||||
"gsk4-sys",
|
||||
"libc",
|
||||
"pango-sys 0.20.10",
|
||||
"pango-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
@@ -1002,9 +880,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
@@ -1046,19 +924,18 @@ checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a"
|
||||
|
||||
[[package]]
|
||||
name = "librsvg"
|
||||
version = "2.61.3"
|
||||
version = "2.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44b612e3a9ec82c8ae9f2ebc9b2e8ff50aa831742686c0071998f76896cf214f"
|
||||
checksum = "a3a70cc57e9d915b91b56ae5dde1c52aaf5aec17579ab2158451d1d7a40b0c1b"
|
||||
dependencies = [
|
||||
"cairo-rs 0.21.5",
|
||||
"cairo-rs",
|
||||
"cast",
|
||||
"cssparser",
|
||||
"cssparser-color",
|
||||
"data-url",
|
||||
"encoding_rs",
|
||||
"float-cmp",
|
||||
"gio 0.21.5",
|
||||
"glib 0.21.5",
|
||||
"gio",
|
||||
"glib",
|
||||
"image",
|
||||
"itertools",
|
||||
"language-tags",
|
||||
@@ -1067,15 +944,14 @@ dependencies = [
|
||||
"markup5ever",
|
||||
"nalgebra",
|
||||
"num-traits",
|
||||
"pango 0.21.5",
|
||||
"pango",
|
||||
"pangocairo",
|
||||
"precomputed-hash",
|
||||
"rayon",
|
||||
"rctree",
|
||||
"regex",
|
||||
"rgb",
|
||||
"selectors",
|
||||
"string_cache 0.9.0",
|
||||
"string_cache",
|
||||
"system-deps",
|
||||
"tinyvec",
|
||||
"url",
|
||||
@@ -1133,13 +1009,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.35.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "311fe69c934650f8f19652b3946075f0fc41ad8757dbb68f1ca14e7900ecc1c3"
|
||||
checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
|
||||
dependencies = [
|
||||
"log",
|
||||
"phf 0.11.3",
|
||||
"phf_codegen 0.11.3",
|
||||
"string_cache",
|
||||
"string_cache_codegen",
|
||||
"tendril",
|
||||
"web_atoms",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1342,22 +1221,10 @@ version = "0.20.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6576b311f6df659397043a5fa8a021da8f72e34af180b44f7d57348de691ab5c"
|
||||
dependencies = [
|
||||
"gio 0.20.12",
|
||||
"glib 0.20.12",
|
||||
"gio",
|
||||
"glib",
|
||||
"libc",
|
||||
"pango-sys 0.20.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52d1d85e2078077a065bb7fc072783d5bcd4e51b379f22d67107d0a16937eb69"
|
||||
dependencies = [
|
||||
"gio 0.21.5",
|
||||
"glib 0.21.5",
|
||||
"libc",
|
||||
"pango-sys 0.21.5",
|
||||
"pango-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1366,47 +1233,35 @@ version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "186909673fc09be354555c302c0b3dcf753cd9fa08dcb8077fa663c80fb243fa"
|
||||
dependencies = [
|
||||
"glib-sys 0.20.10",
|
||||
"gobject-sys 0.20.10",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango-sys"
|
||||
version = "0.21.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4f06627d36ed5ff303d2df65211fc2e52ba5b17bf18dd80ff3d9628d6e06cfd"
|
||||
dependencies = [
|
||||
"glib-sys 0.21.5",
|
||||
"gobject-sys 0.21.5",
|
||||
"glib-sys",
|
||||
"gobject-sys",
|
||||
"libc",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pangocairo"
|
||||
version = "0.21.5"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b36c5c84304072939d860595d9bda2a797d3bd6f7215e20b8ccd0e72d84da8c8"
|
||||
checksum = "58890dc451db9964ac2d8874f903a4370a4b3932aa5281ff0c8d9810937ad84f"
|
||||
dependencies = [
|
||||
"cairo-rs 0.21.5",
|
||||
"glib 0.21.5",
|
||||
"cairo-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
"pango 0.21.5",
|
||||
"pango",
|
||||
"pangocairo-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pangocairo-sys"
|
||||
version = "0.21.5"
|
||||
version = "0.20.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eadbb01ad38be76e0d37e329d40ba0f3f9ef261d7b84b05201d7a0f14f819406"
|
||||
checksum = "b9952903f88aa93e2927e7bca2d1ebae64fc26545a9280b4ce6bddeda26b5c42"
|
||||
dependencies = [
|
||||
"cairo-sys-rs 0.21.5",
|
||||
"glib-sys 0.21.5",
|
||||
"cairo-sys-rs",
|
||||
"glib-sys",
|
||||
"libc",
|
||||
"pango-sys 0.21.5",
|
||||
"pango-sys",
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
@@ -1445,6 +1300,15 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
@@ -1455,16 +1319,36 @@ dependencies = [
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd"
|
||||
dependencies = [
|
||||
"phf_generator 0.10.0",
|
||||
"phf_shared 0.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6"
|
||||
dependencies = [
|
||||
"phf_shared 0.10.0",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
@@ -1481,7 +1365,7 @@ version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1490,20 +1374,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
"siphasher 0.3.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.13.1"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
"siphasher 1.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1540,6 +1424,15 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
@@ -1591,6 +1484,18 @@ version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[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",
|
||||
]
|
||||
|
||||
@@ -1599,6 +1504,9 @@ name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rawpointer"
|
||||
@@ -1652,10 +1560,10 @@ dependencies = [
|
||||
name = "rdraught-gtk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cairo-rs 0.20.12",
|
||||
"cairo-rs",
|
||||
"gdk4",
|
||||
"gio 0.20.12",
|
||||
"glib 0.20.12",
|
||||
"gio",
|
||||
"glib",
|
||||
"gtk4",
|
||||
"librsvg",
|
||||
"rdraught",
|
||||
@@ -1683,18 +1591,15 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"rdraught",
|
||||
"wasm4",
|
||||
"wasm4-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdraught-wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"console_error_panic_hook",
|
||||
"rdraught",
|
||||
"rdraught-ui-common",
|
||||
"rmath",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
@@ -1795,7 +1700,7 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1895,9 +1800,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "selectors"
|
||||
version = "0.31.0"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5685b6ae43bfcf7d2e7dfcfb5d8e8f61b46442c902531e41a32a9a8bf0ee0fb6"
|
||||
checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"cssparser",
|
||||
@@ -1905,8 +1810,8 @@ dependencies = [
|
||||
"fxhash",
|
||||
"log",
|
||||
"new_debug_unreachable",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"phf 0.10.1",
|
||||
"phf_codegen 0.10.0",
|
||||
"precomputed-hash",
|
||||
"servo_arc",
|
||||
"smallvec",
|
||||
@@ -1958,9 +1863,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "servo_arc"
|
||||
version = "0.4.3"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930"
|
||||
checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44"
|
||||
dependencies = [
|
||||
"stable_deref_trait",
|
||||
]
|
||||
@@ -1990,6 +1895,12 @@ version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.4"
|
||||
@@ -2027,26 +1938,13 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared 0.13.1",
|
||||
"precomputed-hash",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache_codegen"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_generator 0.11.3",
|
||||
"phf_shared 0.11.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2266,6 +2164,12 @@ version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.129"
|
||||
@@ -2344,18 +2248,6 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web_atoms"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414"
|
||||
dependencies = [
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"string_cache 0.8.9",
|
||||
"string_cache_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.12"
|
||||
@@ -2409,15 +2301,6 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
@@ -2499,11 +2382,12 @@ checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
|
||||
|
||||
[[package]]
|
||||
name = "xml5ever"
|
||||
version = "0.35.0"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3f1e41afb31a75aef076563b0ad3ecc24f5bd9d12a72b132222664eb76b494"
|
||||
checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69"
|
||||
dependencies = [
|
||||
"log",
|
||||
"mac",
|
||||
"markup5ever",
|
||||
]
|
||||
|
||||
@@ -2530,6 +2414,26 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerofrom"
|
||||
version = "0.1.8"
|
||||
|
||||
+7
-3
@@ -25,16 +25,14 @@ rust-version = "1.87"
|
||||
[workspace.dependencies]
|
||||
rdraught = { path = "rdraught", version = "0.1" }
|
||||
rdraught-ui-common = { path = "rdraught-ui-common", version = "0.1" }
|
||||
base64 = "0.22"
|
||||
cairo-rs = "0.20"
|
||||
cairo-sys-rs = "0.20"
|
||||
librsvg = "2.60"
|
||||
librsvg = "~2.60"
|
||||
gtk4 = {version = "0.9", features = ["v4_10"] }
|
||||
gdk4 = "0.9"
|
||||
gio = "0.20"
|
||||
glib = "0.20"
|
||||
wasm4 = "0.2"
|
||||
wasm4-sys = "0.1"
|
||||
strum = "0.27"
|
||||
strum_macros = "0.27"
|
||||
heapless = "0.8"
|
||||
@@ -44,3 +42,9 @@ console_error_panic_hook = "0.1"
|
||||
sdl3 = { version = "0.14.35" }
|
||||
sdl3-sys = { version = "0.5" }
|
||||
rmath = {version = "0.1.0", registry = "gitea"}
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
[profile.release.package.rdraught-w4]
|
||||
opt-level = "z"
|
||||
|
||||
@@ -9,12 +9,16 @@ several front ends.
|
||||
| --- | --- |
|
||||
| `rdraught` | `no_std` game engine, move generation, AI, and undo/redo history. |
|
||||
| `rdraught-ui-common` | Small geometry and shared-state helpers used by the graphical front ends. |
|
||||
| `rdraught-gtk` | GTK4 desktop front end (the most complete one). |
|
||||
| `rdraught-wasm` | Browser front end using the Canvas 2D API and `wasm-bindgen`. |
|
||||
| `rdraught-w4` | WASM-4 front end. Work in progress. |
|
||||
| `rdraught-sdl` | SDL3 front end. Work in progress. |
|
||||
| `rdraught-gtk` | Playable GTK4 desktop front end. |
|
||||
| `rdraught-wasm` | Playable browser front end using the Canvas 2D API and `wasm-bindgen`. |
|
||||
| `rdraught-w4` | Playable WASM-4 front end. |
|
||||
| `rdraught-sdl` | SDL3 prototype; not wired to the game yet. |
|
||||
| `rdraught-cli` | Scratch `rustyline` program; not a playable CLI yet. |
|
||||
|
||||
The GTK, WASM, and WASM-4 front ends are currently hot-seat games: both sides
|
||||
are played by people. The core AI is available but is not wired into a frontend
|
||||
yet.
|
||||
|
||||
## Rules
|
||||
|
||||
The rules implemented by the engine are an 8x8 draughts variant:
|
||||
@@ -35,7 +39,8 @@ The engine exposes a fixed-depth negamax search with alpha-beta pruning through
|
||||
`DraughtsGame::get_best_move(max_depth)`. A depth of zero is treated as one ply.
|
||||
Wins and losses dominate material scoring, and faster wins are preferred. The
|
||||
search has no time or node budget and no draw/repetition detection, so choose a
|
||||
modest depth for positions with many pieces.
|
||||
modest depth for positions with many pieces. The current frontends do not use
|
||||
the AI; they are hot-seat games.
|
||||
|
||||
## Requirements
|
||||
|
||||
@@ -79,6 +84,11 @@ below instead.
|
||||
cargo run -p rdraught-gtk
|
||||
```
|
||||
|
||||
Use the board orientation selector at startup, click a piece to select it, and
|
||||
click a highlighted destination to move. The header bar has undo, redo, and
|
||||
New Game buttons. The game-over dialog appears automatically; use New Game to
|
||||
start another game.
|
||||
|
||||
### Browser WASM
|
||||
|
||||
```sh
|
||||
@@ -87,6 +97,13 @@ cd rdraught-wasm
|
||||
trunk serve --open
|
||||
```
|
||||
|
||||
Click a piece and then a highlighted destination. Keyboard controls:
|
||||
|
||||
- `Ctrl+Z` — undo
|
||||
- `Ctrl+Y` or `Ctrl+Shift+Z` — redo
|
||||
- `N` — new game
|
||||
- `F` — flip the board orientation
|
||||
|
||||
The `.cargo/config.toml` inside `rdraught-wasm` is only read when Cargo runs
|
||||
from that directory.
|
||||
|
||||
@@ -97,8 +114,12 @@ cd rdraught-w4
|
||||
cargo build --release --target wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
Run Cargo from `rdraught-w4` so its linker configuration is applied. The
|
||||
resulting module is in `target/wasm32-unknown-unknown/release/`.
|
||||
Run Cargo from `rdraught-w4` so its linker configuration is applied. The module
|
||||
is written to `../target/wasm32-unknown-unknown/release/rdraught_w4.wasm`.
|
||||
|
||||
Use the mouse to select a piece and click a highlighted destination to move.
|
||||
After a win, click to start a new game. The board is shown in a fixed
|
||||
orientation for hot-seat play.
|
||||
|
||||
### SDL3
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use glib::ExitCode;
|
||||
use rdraught::{DraughtsBoard, DraughtsGame, Move, Piece, Player, Position};
|
||||
use rdraught_gtk::run;
|
||||
use std::collections::HashMap;
|
||||
use rdraught::{DraughtsGame, Move};
|
||||
use std::mem::size_of;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use gtk4::{AlertDialog, Window, prelude::IsA};
|
||||
use rdraught::Player;
|
||||
|
||||
pub(crate) async fn create_dialog<W: IsA<Window>>(window: W, winner: Player) {
|
||||
pub(crate) fn create_dialog<W: IsA<Window>>(window: W, winner: Player) {
|
||||
let msg = match winner {
|
||||
Player::Red => String::from("Red player wins"),
|
||||
Player::White => String::from("White player wins"),
|
||||
|
||||
@@ -4,7 +4,7 @@ use rdraught::Player;
|
||||
use rdraught_ui_common::SharedMutable;
|
||||
|
||||
pub(crate) fn create(application: &Application, current_player: SharedMutable<Player>) -> Window {
|
||||
let label = Label::builder().label("Main player:").build();
|
||||
let label = Label::builder().label("Board orientation:").build();
|
||||
let red_player_button = CheckButton::builder().label("Red").active(true).build();
|
||||
let white_player_button = CheckButton::builder()
|
||||
.label("White")
|
||||
@@ -44,19 +44,11 @@ pub(crate) fn create(application: &Application, current_player: SharedMutable<Pl
|
||||
layout.set_margin_bottom(10);
|
||||
layout.set_margin_start(10);
|
||||
layout.set_margin_end(10);
|
||||
let player_select_dialog = Window::builder()
|
||||
Window::builder()
|
||||
.application(application)
|
||||
.title("Rdraught")
|
||||
.modal(true)
|
||||
.child(&layout)
|
||||
.visible(true)
|
||||
.build();
|
||||
|
||||
player_select_dialog.connect_close_request(move |window| {
|
||||
if let Some(application) = window.application() {
|
||||
application.remove_window(window);
|
||||
}
|
||||
glib::Propagation::Proceed
|
||||
});
|
||||
player_select_dialog
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use core::f64::consts::PI;
|
||||
use gdk4::cairo::{Context as CairoContext, Matrix, Rectangle};
|
||||
use glib::ExitCode;
|
||||
use gtk4::glib::{MainContext, Propagation};
|
||||
use gtk4::glib::Propagation;
|
||||
use gtk4::{self as gtk, gdk::ffi::GDK_BUTTON_PRIMARY};
|
||||
use gtk4::{Application, Button, DrawingArea, HeaderBar, prelude::*};
|
||||
use gtk4::{Application, Button, DrawingArea, HeaderBar, Label, prelude::*};
|
||||
use rdraught::{
|
||||
DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication,
|
||||
RectangularBoard,
|
||||
@@ -14,7 +14,7 @@ const SQUARE_SIZE: f64 = 1.0;
|
||||
use super::final_dialog;
|
||||
use super::greeting_dialog;
|
||||
use rdraught_ui_common::{
|
||||
Point, SharedMutable, SharedMutableRef, new_shared_mut, new_shared_mut_ref,
|
||||
Point, SharedMutable, SharedMutableRef, board_position, new_shared_mut, new_shared_mut_ref,
|
||||
};
|
||||
|
||||
const CROWN_RED: &[u8] = include_bytes!("crown_red.svg");
|
||||
@@ -25,6 +25,47 @@ fn transform_point(p: &Point, m: &Matrix) -> Point {
|
||||
Point::new(x, y)
|
||||
}
|
||||
|
||||
fn player_name(player: Player) -> &'static str {
|
||||
match player {
|
||||
Player::Red => "Red",
|
||||
Player::White => "White",
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_game_state(
|
||||
rd: &SharedMutableRef<RDraughtApplication>,
|
||||
status: &Label,
|
||||
undo_button: &Button,
|
||||
redo_button: &Button,
|
||||
) -> Option<Player> {
|
||||
let rd = rd.borrow();
|
||||
let game = rd.game();
|
||||
let winner = game.winner();
|
||||
let text = match winner {
|
||||
Some(winner) => format!("{} wins", player_name(winner)),
|
||||
None => format!("{} to move", player_name(game.next_move())),
|
||||
};
|
||||
status.set_text(&text);
|
||||
undo_button.set_sensitive(winner.is_none() && rd.can_undo());
|
||||
redo_button.set_sensitive(winner.is_none() && rd.can_redo());
|
||||
winner
|
||||
}
|
||||
|
||||
fn handle_game_over(
|
||||
winner: Option<Player>,
|
||||
finished: &SharedMutable<bool>,
|
||||
drawing_area: &DrawingArea,
|
||||
window: >k::ApplicationWindow,
|
||||
) {
|
||||
if let Some(winner) = winner {
|
||||
if !finished.get() {
|
||||
finished.set(true);
|
||||
drawing_area.set_sensitive(false);
|
||||
final_dialog::create_dialog(window.clone(), winner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait AugmentedRect {
|
||||
fn from_points(tl: Point, br: Point) -> Rectangle;
|
||||
fn center(&self) -> Point;
|
||||
@@ -253,6 +294,10 @@ fn create_game_window(
|
||||
redo_button.set_icon_name("edit-redo-symbolic");
|
||||
redo_button.set_sensitive(false);
|
||||
|
||||
let finished: SharedMutable<bool> = new_shared_mut(false);
|
||||
let status = Label::new(None);
|
||||
let new_game_button = Button::with_label("New Game");
|
||||
|
||||
let selected_piece: SharedMutable<Option<Position>> = new_shared_mut(None);
|
||||
let available_moves = new_shared_mut_ref(Vec::<Move>::new());
|
||||
// Create a DrawingArea widget where we will draw the chessboard.
|
||||
@@ -261,6 +306,11 @@ fn create_game_window(
|
||||
window.set_child(Some(&drawing_area));
|
||||
|
||||
header_bar.pack_start(&undo_button);
|
||||
header_bar.pack_start(&redo_button);
|
||||
header_bar.pack_start(&new_game_button);
|
||||
header_bar.pack_end(&status);
|
||||
let _ = refresh_game_state(&rd, &status, &undo_button, &redo_button);
|
||||
|
||||
{
|
||||
let da = drawing_area.clone();
|
||||
let rd = rd.clone();
|
||||
@@ -268,19 +318,23 @@ fn create_game_window(
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let available_moves = available_moves.clone();
|
||||
let status = status.clone();
|
||||
let finished = finished.clone();
|
||||
let window = window.clone();
|
||||
undo_button.connect_clicked(move |_| {
|
||||
let mut rd = rd.borrow_mut();
|
||||
if rd.can_undo() {
|
||||
rd.undo().ok();
|
||||
redo_btn_clone.set_sensitive(rd.can_redo());
|
||||
undo_btn_clone.set_sensitive(rd.can_undo());
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
da.queue_draw();
|
||||
{
|
||||
let mut app = rd.borrow_mut();
|
||||
if app.can_undo() {
|
||||
app.undo().ok();
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
}
|
||||
}
|
||||
let winner = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
handle_game_over(winner, &finished, &da, &window);
|
||||
da.queue_draw();
|
||||
});
|
||||
}
|
||||
header_bar.pack_start(&redo_button);
|
||||
{
|
||||
let da = drawing_area.clone();
|
||||
let rd = rd.clone();
|
||||
@@ -288,16 +342,40 @@ fn create_game_window(
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let available_moves = available_moves.clone();
|
||||
let status = status.clone();
|
||||
let finished = finished.clone();
|
||||
let window = window.clone();
|
||||
redo_button.connect_clicked(move |_| {
|
||||
let mut rd = rd.borrow_mut();
|
||||
if rd.can_redo() {
|
||||
rd.redo().ok();
|
||||
redo_btn_clone.set_sensitive(rd.can_redo());
|
||||
undo_btn_clone.set_sensitive(rd.can_undo());
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
da.queue_draw();
|
||||
{
|
||||
let mut app = rd.borrow_mut();
|
||||
if app.can_redo() {
|
||||
app.redo().ok();
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
}
|
||||
}
|
||||
let winner = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
handle_game_over(winner, &finished, &da, &window);
|
||||
da.queue_draw();
|
||||
});
|
||||
}
|
||||
{
|
||||
let da = drawing_area.clone();
|
||||
let rd = rd.clone();
|
||||
let undo_btn_clone = undo_button.clone();
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let available_moves = available_moves.clone();
|
||||
let status = status.clone();
|
||||
let finished = finished.clone();
|
||||
new_game_button.connect_clicked(move |_| {
|
||||
*rd.borrow_mut() = RDraughtApplication::new(DraughtsGame::default());
|
||||
finished.set(false);
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
da.set_sensitive(true);
|
||||
let _ = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
da.queue_draw();
|
||||
});
|
||||
}
|
||||
// Get the allocation information for the widget.
|
||||
@@ -362,10 +440,7 @@ fn create_game_window(
|
||||
// Loop over rows and columns to draw each chessboard cell.
|
||||
for row in 0..DraughtsBoard::rows() {
|
||||
for col in 0..DraughtsBoard::columns() {
|
||||
let position = match current_player {
|
||||
Player::Red => Position::new(row as u8, (8 - 1 - col) as u8),
|
||||
Player::White => Position::new((8 - 1 - row) as u8, col as u8),
|
||||
};
|
||||
let position = board_position(current_player, row as u8, col as u8);
|
||||
let square = Rectangle::new(
|
||||
col as f64 * SQUARE_SIZE,
|
||||
row as f64 * SQUARE_SIZE,
|
||||
@@ -481,99 +556,74 @@ fn create_game_window(
|
||||
let window = window.clone();
|
||||
let undo_btn_clone = undo_button.clone();
|
||||
let redo_btn_clone = redo_button.clone();
|
||||
let selected_piece = selected_piece.clone();
|
||||
let finished = finished.clone();
|
||||
let status = status.clone();
|
||||
gesture.connect_pressed(move |gesture, _, x, y| {
|
||||
gesture.set_state(gtk::EventSequenceState::Claimed);
|
||||
if let Some(winner) = rd.borrow().game().winner() {
|
||||
MainContext::default()
|
||||
.spawn_local(final_dialog::create_dialog(window.clone(), winner));
|
||||
} else {
|
||||
let xform = xform.borrow();
|
||||
let inverse = {
|
||||
let mut m = *xform;
|
||||
m.invert();
|
||||
m
|
||||
};
|
||||
let p = transform_point(&Point::new(x, y), &inverse);
|
||||
if board_clone.contains(&p) {
|
||||
let p = p - board_clone.tl();
|
||||
// println!("Point: {:?}", p);
|
||||
let position = match current_player {
|
||||
Player::White => Position::new(
|
||||
(8.0 - (p.y() / SQUARE_SIZE)) as u8,
|
||||
(p.x() / SQUARE_SIZE) as u8,
|
||||
),
|
||||
Player::Red => Position::new(
|
||||
(p.y() / SQUARE_SIZE) as u8,
|
||||
(8.0 - p.x() / SQUARE_SIZE) as u8,
|
||||
),
|
||||
};
|
||||
// println!("Selected position: {:?}", position);
|
||||
let piece = {
|
||||
let rd = rd.borrow();
|
||||
let draughts_game = rd.game();
|
||||
position
|
||||
.clone()
|
||||
.map_or(Piece::NoPiece, |it| draughts_game.piece_at(it))
|
||||
// println!("Selected piece: {:?}", piece);
|
||||
};
|
||||
let am = available_moves.replace(Vec::new());
|
||||
let mut move_applied = false;
|
||||
if !am.is_empty() {
|
||||
if let Ok(pos) = position {
|
||||
for mv in am.into_iter() {
|
||||
if mv.get_end_position() == Some(pos) {
|
||||
let mut rd_app = rd.borrow_mut();
|
||||
// println!("Applied move: {:?}", mv);
|
||||
if rd_app.apply_move(mv).is_err() {
|
||||
continue;
|
||||
}
|
||||
// let game_copy = rd_app.game().clone();
|
||||
// thread::spawn(move || {
|
||||
// if let (Some(mv), analyzed_moves) =
|
||||
// game_copy.get_best_move(10)
|
||||
// {
|
||||
// println!(
|
||||
// "Next best move: {:?}, analyzed moves: {}",
|
||||
// mv, analyzed_moves
|
||||
// );
|
||||
// }
|
||||
// });
|
||||
move_applied = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if move_applied {
|
||||
selected_piece.set(None);
|
||||
let rd = rd.borrow();
|
||||
undo_btn_clone.set_sensitive(rd.can_undo());
|
||||
redo_btn_clone.set_sensitive(rd.can_redo());
|
||||
if finished.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
let xform = xform.borrow();
|
||||
let inverse = {
|
||||
let mut m = *xform;
|
||||
m.invert();
|
||||
m
|
||||
};
|
||||
let p = transform_point(&Point::new(x, y), &inverse);
|
||||
if !board_clone.contains(&p) {
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
drawing_area.queue_draw();
|
||||
return;
|
||||
}
|
||||
|
||||
let p = p - board_clone.tl();
|
||||
let screen_row = (p.y() / SQUARE_SIZE) as u8;
|
||||
let screen_col = (p.x() / SQUARE_SIZE) as u8;
|
||||
let position = board_position(current_player, screen_row, screen_col).ok();
|
||||
let am = available_moves.replace(Vec::new());
|
||||
let mut move_applied = false;
|
||||
if let Some(pos) = position {
|
||||
for mv in am {
|
||||
if mv.get_end_position() == Some(pos) {
|
||||
let mut rd_app = rd.borrow_mut();
|
||||
if rd_app.apply_move(mv).is_err() {
|
||||
continue;
|
||||
}
|
||||
move_applied = true;
|
||||
break;
|
||||
}
|
||||
if !move_applied {
|
||||
let position = position.ok();
|
||||
match piece.player() {
|
||||
Some(Player::Red) => selected_piece.set(position),
|
||||
Some(Player::White) => selected_piece.set(position),
|
||||
None => selected_piece.set(None),
|
||||
}
|
||||
if piece.player().is_none() {
|
||||
selected_piece.set(None)
|
||||
} else {
|
||||
let mut am = available_moves.borrow_mut();
|
||||
selected_piece.set(position);
|
||||
if let Some(position) = position {
|
||||
for mv in rd.borrow().game().moves_for_piece(position) {
|
||||
am.push(mv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if move_applied {
|
||||
if let Some(pending) = rd.borrow().game().pending_capture() {
|
||||
selected_piece.set(Some(pending));
|
||||
let mut moves = available_moves.borrow_mut();
|
||||
moves.clear();
|
||||
for mv in rd.borrow().game().moves_for_piece(pending) {
|
||||
moves.push(mv);
|
||||
}
|
||||
} else {
|
||||
selected_piece.set(None);
|
||||
available_moves.borrow_mut().clear();
|
||||
}
|
||||
let winner = refresh_game_state(&rd, &status, &undo_btn_clone, &redo_btn_clone);
|
||||
handle_game_over(winner, &finished, &drawing_area, &window);
|
||||
} else {
|
||||
let mut moves = available_moves.borrow_mut();
|
||||
moves.clear();
|
||||
selected_piece.set(None);
|
||||
if let Some(position) = position {
|
||||
for mv in rd.borrow().game().moves_for_piece(position) {
|
||||
selected_piece.set(Some(position));
|
||||
moves.push(mv);
|
||||
}
|
||||
}
|
||||
drawing_area.queue_draw();
|
||||
}
|
||||
drawing_area.queue_draw();
|
||||
});
|
||||
}
|
||||
// Assign the gesture to the treeview
|
||||
@@ -582,11 +632,6 @@ fn create_game_window(
|
||||
}
|
||||
|
||||
fn on_activate(application: &Application, game: DraughtsGame) {
|
||||
// Initialize GTK before using any GTK functions.
|
||||
if gtk::init().is_err() {
|
||||
panic!("Failed to initialize GTK.");
|
||||
}
|
||||
|
||||
let current_player = new_shared_mut(Player::Red);
|
||||
let dialog = greeting_dialog::create(application, current_player.clone());
|
||||
let application = application.clone();
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use rdraught::{DraughtsBoard, Error, Player, Position, RectangularBoard};
|
||||
|
||||
/// Maps a screen square to a board square for the given point of view.
|
||||
///
|
||||
/// Both views place pieces on screen squares where
|
||||
/// `(screen_row + screen_col) % 2 == 1`.
|
||||
pub fn board_position(view: Player, screen_row: u8, screen_col: u8) -> Result<Position, Error> {
|
||||
let rows = DraughtsBoard::rows() as u8;
|
||||
let columns = DraughtsBoard::columns() as u8;
|
||||
if screen_row >= rows || screen_col >= columns {
|
||||
return Err(Error::InvalidPosition);
|
||||
}
|
||||
match view {
|
||||
Player::White => Position::new(rows - 1 - screen_row, screen_col),
|
||||
Player::Red => Position::new(screen_row, columns - 1 - screen_col),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::board_position;
|
||||
use rdraught::{Player, Position};
|
||||
|
||||
#[test]
|
||||
fn white_view_mapping() {
|
||||
assert_eq!(
|
||||
Position::new(7, 1).unwrap(),
|
||||
board_position(Player::White, 0, 1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Position::new(0, 0).unwrap(),
|
||||
board_position(Player::White, 7, 0).unwrap()
|
||||
);
|
||||
assert!(board_position(Player::White, 0, 0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn red_view_mapping() {
|
||||
assert_eq!(
|
||||
Position::new(0, 6).unwrap(),
|
||||
board_position(Player::Red, 0, 1).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
Position::new(7, 7).unwrap(),
|
||||
board_position(Player::Red, 7, 0).unwrap()
|
||||
);
|
||||
assert!(board_position(Player::Red, 0, 0).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn both_views_have_32_squares() {
|
||||
for view in [Player::White, Player::Red] {
|
||||
let mut count = 0;
|
||||
for row in 0..8 {
|
||||
for col in 0..8 {
|
||||
if board_position(view, row, col).is_ok() {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(32, count);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_bounds_is_rejected() {
|
||||
assert!(board_position(Player::White, 8, 0).is_err());
|
||||
assert!(board_position(Player::Red, 0, 8).is_err());
|
||||
}
|
||||
}
|
||||
@@ -205,7 +205,7 @@ impl Xform {
|
||||
}
|
||||
|
||||
pub fn yx(&self) -> f64 {
|
||||
self.0[(1, 0)]
|
||||
self.0[(0, 1)]
|
||||
}
|
||||
|
||||
pub fn yy(&self) -> f64 {
|
||||
@@ -259,3 +259,41 @@ impl Display for Xform {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{Point, Xform};
|
||||
use core::f64::consts::PI;
|
||||
|
||||
fn close(actual: f64, expected: f64) {
|
||||
assert!((actual - expected).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotation_uses_all_matrix_components() {
|
||||
let rotation = Xform::rot(PI / 2.0);
|
||||
close(rotation.yx(), 1.0);
|
||||
close(rotation.xy(), -1.0);
|
||||
|
||||
let point = Point::new(1.0, 0.0) * &rotation;
|
||||
close(point.x(), 0.0);
|
||||
close(point.y(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translation_and_scaling() {
|
||||
let transform = Xform::scale(2.0, 3.0) * Xform::xlate(3.0, 4.0);
|
||||
let point = Point::new(1.0, 2.0) * &transform;
|
||||
close(point.x(), 5.0);
|
||||
close(point.y(), 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inversion_round_trips() {
|
||||
let transform = Xform::xlate(3.0, 4.0) * Xform::rot(0.3) * Xform::scale(2.0, 0.5);
|
||||
let point = Point::new(1.0, 2.0);
|
||||
let result = (point * &transform) * &transform.invert();
|
||||
close(result.x(), point.x());
|
||||
close(result.y(), point.y());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
mod board;
|
||||
mod geo2d;
|
||||
mod types;
|
||||
|
||||
pub use board::board_position;
|
||||
pub use geo2d::{Point, Rect, Xform};
|
||||
pub use types::{SharedMutable, SharedMutableRef, new_shared_mut, new_shared_mut_ref};
|
||||
|
||||
@@ -10,13 +10,7 @@ version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasm4.workspace = true
|
||||
wasm4-sys.workspace = true
|
||||
rdraught.workspace = true
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
|
||||
|
||||
+193
-186
@@ -1,26 +1,196 @@
|
||||
#![no_main]
|
||||
#![no_std]
|
||||
|
||||
use rdraught::{DraughtsBoard, DraughtsGame, Piece, Position};
|
||||
use core::arch::wasm32::unreachable;
|
||||
use rdraught::{DraughtsGame, Piece, Player, Position};
|
||||
use wasm4::draw::{Color, DrawIndex, DrawIndices, Framebuffer};
|
||||
use wasm4::rt::{Resources, Runtime};
|
||||
// use wasm4 as w4;
|
||||
use core::arch::wasm32::unreachable;
|
||||
use wasm4::sys::DRAW_COLORS;
|
||||
use wasm4::sys::{DRAW_COLORS, MOUSE_BUTTONS, MOUSE_LEFT, MOUSE_X, MOUSE_Y};
|
||||
use wasm4::trace;
|
||||
// mod wasm4;
|
||||
|
||||
// use wasm4::{DRAW_COLORS, oval, rect};
|
||||
const SQUARE_SIZE: i32 = 20;
|
||||
const BOARD_SIZE: i32 = 160;
|
||||
|
||||
// mod palette;
|
||||
|
||||
// static mut GAME: DraughtsGame = DraughtsGame::default();
|
||||
struct Rdraught_W4 {
|
||||
struct RdraughtW4 {
|
||||
framebuffer: Framebuffer,
|
||||
game: DraughtsGame,
|
||||
selected: Option<Position>,
|
||||
mouse_down: bool,
|
||||
}
|
||||
|
||||
impl Runtime for Rdraught_W4 {
|
||||
impl RdraughtW4 {
|
||||
fn set_colors(indices: [DrawIndex; 4]) {
|
||||
unsafe { *DRAW_COLORS = DrawIndices::from_array(indices).into_u16() }
|
||||
}
|
||||
|
||||
fn draw_piece(&self, piece: Piece, top_left: [i32; 2]) {
|
||||
let color = match piece.player() {
|
||||
Some(Player::White) => DrawIndex::First,
|
||||
Some(Player::Red) => DrawIndex::Second,
|
||||
None => return,
|
||||
};
|
||||
Self::set_colors([
|
||||
color,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
self.framebuffer.oval(
|
||||
[top_left[0] + 2, top_left[1] + 2],
|
||||
[(SQUARE_SIZE - 4) as u32, (SQUARE_SIZE - 4) as u32],
|
||||
);
|
||||
|
||||
if piece.is_crowned() {
|
||||
Self::set_colors([
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
self.framebuffer
|
||||
.rect([top_left[0] + 7, top_left[1] + 7], [6, 6]);
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_selection(&self) {
|
||||
let Some(selected) = self.selected else {
|
||||
return;
|
||||
};
|
||||
let x = selected.col() as i32 * SQUARE_SIZE;
|
||||
let y = selected.row() as i32 * SQUARE_SIZE;
|
||||
|
||||
Self::set_colors([
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
self.framebuffer.rect([x, y], [SQUARE_SIZE as u32, 2]);
|
||||
self.framebuffer
|
||||
.rect([x, y + SQUARE_SIZE - 2], [SQUARE_SIZE as u32, 2]);
|
||||
self.framebuffer.rect([x, y], [2, SQUARE_SIZE as u32]);
|
||||
self.framebuffer
|
||||
.rect([x + SQUARE_SIZE - 2, y], [2, SQUARE_SIZE as u32]);
|
||||
|
||||
for mv in self.game.moves_for_piece(selected) {
|
||||
if let Some(end) = mv.get_end_position() {
|
||||
self.framebuffer.rect(
|
||||
[
|
||||
end.col() as i32 * SQUARE_SIZE + 8,
|
||||
end.row() as i32 * SQUARE_SIZE + 8,
|
||||
],
|
||||
[4, 4],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn draw_board(&self) {
|
||||
for row in 0..8i32 {
|
||||
for col in 0..8i32 {
|
||||
let indices = if (row + col) % 2 == 0 {
|
||||
[
|
||||
DrawIndex::Third,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
]
|
||||
} else {
|
||||
[
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
]
|
||||
};
|
||||
Self::set_colors(indices);
|
||||
let top_left = [col * SQUARE_SIZE, row * SQUARE_SIZE];
|
||||
self.framebuffer
|
||||
.rect(top_left, [SQUARE_SIZE as u32, SQUARE_SIZE as u32]);
|
||||
|
||||
if let Ok(position) = Position::new(row as u8, col as u8) {
|
||||
let piece = self.game.piece_at(position);
|
||||
if piece.is_present() {
|
||||
self.draw_piece(piece, top_left);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.draw_selection();
|
||||
}
|
||||
|
||||
fn draw_winner(&self, winner: Player) {
|
||||
Self::set_colors([
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
self.framebuffer.rect([0, 70], [BOARD_SIZE as u32, 28]);
|
||||
|
||||
Self::set_colors([
|
||||
DrawIndex::First,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
let (message, x) = match winner {
|
||||
Player::Red => ("Red wins", 48),
|
||||
Player::White => ("White wins", 40),
|
||||
};
|
||||
self.framebuffer.text(message, [x, 74]);
|
||||
self.framebuffer.text("click to restart", [16, 88]);
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.game = DraughtsGame::default();
|
||||
self.selected = None;
|
||||
}
|
||||
|
||||
fn handle_click(&mut self, x: i32, y: i32) {
|
||||
if self.game.winner().is_some() {
|
||||
self.reset();
|
||||
return;
|
||||
}
|
||||
if x < 0 || y < 0 || x >= BOARD_SIZE || y >= BOARD_SIZE {
|
||||
self.selected = None;
|
||||
return;
|
||||
}
|
||||
|
||||
let row = (y / SQUARE_SIZE) as u8;
|
||||
let col = (x / SQUARE_SIZE) as u8;
|
||||
let Ok(position) = Position::new(row, col) else {
|
||||
self.selected = None;
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(selected) = self.selected {
|
||||
let found = self
|
||||
.game
|
||||
.moves_for_piece(selected)
|
||||
.find(|mv| mv.get_end_position() == Some(position));
|
||||
if let Some(mv) = found {
|
||||
if self.game.check_and_apply_move(&mv).is_ok() {
|
||||
self.selected = self.game.pending_capture();
|
||||
} else {
|
||||
self.selected = None;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if self.game.pending_capture().is_some() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if self.game.moves_for_piece(position).next().is_some() {
|
||||
self.selected = Some(position);
|
||||
} else {
|
||||
self.selected = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Runtime for RdraughtW4 {
|
||||
fn start(rs: Resources) -> Self {
|
||||
rs.framebuffer.replace_palette([
|
||||
Color(0x00edeada),
|
||||
@@ -28,196 +198,33 @@ impl Runtime for Rdraught_W4 {
|
||||
Color(0x005d8d60),
|
||||
Color(0x00100f24),
|
||||
]);
|
||||
Rdraught_W4 {
|
||||
RdraughtW4 {
|
||||
framebuffer: rs.framebuffer,
|
||||
game: DraughtsGame::default(),
|
||||
selected: None,
|
||||
mouse_down: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
let indices = DrawIndices::from_array([
|
||||
DrawIndex::Third,
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
unsafe {
|
||||
*DRAW_COLORS = indices.into_u16();
|
||||
let (mouse_x, mouse_y, buttons) = unsafe { (*MOUSE_X, *MOUSE_Y, *MOUSE_BUTTONS) };
|
||||
let pressed = buttons & MOUSE_LEFT != 0;
|
||||
if pressed && !self.mouse_down {
|
||||
self.handle_click(mouse_x as i32, mouse_y as i32);
|
||||
}
|
||||
self.mouse_down = pressed;
|
||||
|
||||
self.framebuffer.rect([10, 10], [50, 50]);
|
||||
self.framebuffer.oval([50, 50], [10, 10]);
|
||||
let SQUARE_SIZE = 20u32;
|
||||
for i in 0..8i32 {
|
||||
for j in 0..8i32 {
|
||||
let indices = if (i + j) % 2 == 0 {
|
||||
DrawIndices::from_array([
|
||||
DrawIndex::Third,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
])
|
||||
} else {
|
||||
DrawIndices::from_array([
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Fourth,
|
||||
DrawIndex::Transparent,
|
||||
])
|
||||
};
|
||||
unsafe {
|
||||
*DRAW_COLORS = indices.into_u16();
|
||||
}
|
||||
let tl = [i as i32 * SQUARE_SIZE as i32, j as i32 * SQUARE_SIZE as i32];
|
||||
self.framebuffer.rect(tl, [SQUARE_SIZE, SQUARE_SIZE]);
|
||||
|
||||
let score = self.game.score_for_player(rdraught::Player::Red);
|
||||
// let msg = format!("{}", score);
|
||||
// w4::trace(msg.as_str());
|
||||
|
||||
let piece = self.game.piece_at(Position::new(i as u8, j as u8).unwrap());
|
||||
match piece {
|
||||
Piece::SimpleWhitePawn => {
|
||||
let indices = DrawIndices::from_array([
|
||||
DrawIndex::First,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
unsafe {
|
||||
*DRAW_COLORS = indices.into_u16();
|
||||
}
|
||||
self.framebuffer.oval(tl, [SQUARE_SIZE, SQUARE_SIZE]);
|
||||
}
|
||||
Piece::SimpleRedPawn => {
|
||||
let indices = DrawIndices::from_array([
|
||||
DrawIndex::Second,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
DrawIndex::Transparent,
|
||||
]);
|
||||
unsafe {
|
||||
*DRAW_COLORS = indices.into_u16();
|
||||
}
|
||||
self.framebuffer.oval(tl, [SQUARE_SIZE, SQUARE_SIZE]);
|
||||
}
|
||||
Piece::CrownedRedPawn => {}
|
||||
Piece::CrownedWhitePawn => {}
|
||||
Piece::NoPiece => {}
|
||||
}
|
||||
}
|
||||
self.draw_board();
|
||||
if let Some(winner) = self.game.winner() {
|
||||
self.draw_winner(winner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wasm4::main! { Rdraught_W4 }
|
||||
|
||||
// use wasm4::*;
|
||||
// use wasm4_sys;
|
||||
|
||||
// #[unsafe(no_mangle)]
|
||||
// fn update() {
|
||||
// unsafe {
|
||||
// wasm4_sys::rect(10, 10, 32, 32);
|
||||
// }
|
||||
// }
|
||||
// #[unsafe(no_mangle)]
|
||||
// fn start() {
|
||||
// palette::change_palette(0usize);
|
||||
// palette::set_draw_color(2u16);
|
||||
// }
|
||||
|
||||
// #[unsafe(no_mangle)]
|
||||
// fn update() {
|
||||
// // if self.count % 60 == 0 {
|
||||
// // w4::trace("tick");
|
||||
// // self.count = 0;
|
||||
// // }
|
||||
// // self.count += 1;
|
||||
// // let indices = DrawIndices::from_array([
|
||||
// // DrawIndex::Third,
|
||||
// // DrawIndex::Fourth,
|
||||
// // DrawIndex::Fourth,
|
||||
// // DrawIndex::Transparent,
|
||||
// // ]);
|
||||
// // unsafe {
|
||||
// // *DRAW_COLORS = 0x;
|
||||
// // }
|
||||
// palette::set_draw_color([3, 4, 4, 0]);
|
||||
|
||||
// rect(10, 10, 50, 50);
|
||||
// oval(50, 50, 10, 10);
|
||||
// let SQUARE_SIZE = 20u32;
|
||||
// for i in 0..8i32 {
|
||||
// for j in 0..8i32 {
|
||||
// // let indices = if (i + j) % 2 == 0 {
|
||||
// // DrawIndices::from_array([
|
||||
// // DrawIndex::Third,
|
||||
// // DrawIndex::Transparent,
|
||||
// // DrawIndex::Fourth,
|
||||
// // DrawIndex::Transparent,
|
||||
// // ])
|
||||
// // } else {
|
||||
// // DrawIndices::from_array([
|
||||
// // DrawIndex::Fourth,
|
||||
// // DrawIndex::Transparent,
|
||||
// // DrawIndex::Fourth,
|
||||
// // DrawIndex::Transparent,
|
||||
// // ])
|
||||
// // };
|
||||
// // unsafe {
|
||||
// // *DRAW_COLORS = indices.into_u16();
|
||||
// // }
|
||||
// let tl = [i as i32 * SQUARE_SIZE as i32, j as i32 * SQUARE_SIZE as i32];
|
||||
// rect(tl.0, tl.1, SQUARE_SIZE, SQUARE_SIZE);
|
||||
|
||||
// // let score = self.game.score_for_player(rdraught::Player::Red);
|
||||
// // let msg = format!("{}", score);
|
||||
// // w4::trace(msg.as_str());
|
||||
// unsafe {
|
||||
// let piece = GAME.piece_at(Position::new(i as u8, j as u8).unwrap());
|
||||
// match piece {
|
||||
// Piece::SimpleWhitePawn => {
|
||||
// // let indices = DrawIndices::from_array([
|
||||
// // DrawIndex::First,
|
||||
// // DrawIndex::Transparent,
|
||||
// // DrawIndex::Transparent,
|
||||
// // DrawIndex::Transparent,
|
||||
// // ]);
|
||||
// // unsafe {
|
||||
// // *DRAW_COLORS = indices.into_u16();
|
||||
// // }
|
||||
// oval(tl, [SQUARE_SIZE, SQUARE_SIZE]);
|
||||
// }
|
||||
// Piece::SimpleRedPawn => {
|
||||
// // let indices = DrawIndices::from_array([
|
||||
// // DrawIndex::Second,
|
||||
// // DrawIndex::Transparent,
|
||||
// // DrawIndex::Transparent,
|
||||
// // DrawIndex::Transparent,
|
||||
// // ]);
|
||||
// // unsafe {
|
||||
// // *DRAW_COLORS = indices.into_u16();
|
||||
// // }
|
||||
// oval(tl, [SQUARE_SIZE, SQUARE_SIZE]);
|
||||
// }
|
||||
// Piece::CrownedRedPawn => {}
|
||||
// Piece::CrownedWhitePawn => {}
|
||||
// Piece::NoPiece => {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
wasm4::main! { RdraughtW4 }
|
||||
|
||||
#[panic_handler]
|
||||
fn panic_handler(_panic_info: &core::panic::PanicInfo<'_>) -> ! {
|
||||
trace("panic error");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(cause) = _panic_info.payload().downcast_ref::<&str>() {
|
||||
trace(cause);
|
||||
}
|
||||
|
||||
unreachable()
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
use super::wasm4::DRAW_COLORS;
|
||||
use super::wasm4::PALETTE;
|
||||
|
||||
type Palette = [u32; 4];
|
||||
|
||||
pub const W4_DEFAULT: Palette = [0xe0f8cf, 0x86c06c, 0x306850, 0x071821];
|
||||
|
||||
pub const PONG_OG: Palette = [0x1b1b1b, 0xdcdcdc, 0x1b1b1b, 0xdcdcdc];
|
||||
|
||||
pub const ICE_CREAM_GB: Palette = [0xfff6d3, 0xf9a875, 0xeb6b6f, 0x7c3f58];
|
||||
|
||||
pub const HOLLOW: Palette = [0x0f0f1b, 0x565a75, 0xc5b7be, 0xf9fbf5];
|
||||
|
||||
const PALETTES: [Palette; 4] = [PONG_OG, ICE_CREAM_GB, HOLLOW, W4_DEFAULT];
|
||||
|
||||
#[inline]
|
||||
fn palette_by_idx<T: Into<usize>>(idx: T) -> Palette {
|
||||
PALETTES[idx.into() % PALETTES.len()]
|
||||
}
|
||||
|
||||
pub fn change_palette<T: Into<usize>>(idx: T) {
|
||||
unsafe {
|
||||
*PALETTE = palette_by_idx(idx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_draw_color<T: Into<u16>>(idx: T) {
|
||||
unsafe { *DRAW_COLORS = idx.into() }
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
//
|
||||
// WASM-4: https://wasm4.org/docs
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Platform Constants │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
pub const SCREEN_SIZE: u32 = 160;
|
||||
pub const FONT_SIZE: u32 = 8;
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Memory Addresses │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
pub static mut PALETTE: *mut [u32; 4] = 0x04 as *mut [u32; 4];
|
||||
pub const DRAW_COLORS: *mut u16 = 0x14 as *mut u16;
|
||||
pub const GAMEPAD1: *const u8 = 0x16 as *const u8;
|
||||
pub const GAMEPAD2: *const u8 = 0x17 as *const u8;
|
||||
pub const GAMEPAD3: *const u8 = 0x18 as *const u8;
|
||||
pub const GAMEPAD4: *const u8 = 0x19 as *const u8;
|
||||
pub const MOUSE_X: *const i16 = 0x1a as *const i16;
|
||||
pub const MOUSE_Y: *const i16 = 0x1c as *const i16;
|
||||
pub const MOUSE_BUTTONS: *const u8 = 0x1e as *const u8;
|
||||
pub static mut FRAMEBUFFER: *mut [u8; 6400] = 0xa0 as *mut [u8; 6400];
|
||||
|
||||
pub const BUTTON_1: u8 = 1;
|
||||
pub const BUTTON_2: u8 = 2;
|
||||
pub const BUTTON_LEFT: u8 = 16;
|
||||
pub const BUTTON_RIGHT: u8 = 32;
|
||||
pub const BUTTON_UP: u8 = 64;
|
||||
pub const BUTTON_DOWN: u8 = 128;
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Drawing Functions │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
/// Copies pixels to the framebuffer.
|
||||
pub fn blit(sprite: &[u8], x: i32, y: i32, width: u32, height: u32, flags: u32) {
|
||||
unsafe { extern_blit(sprite.as_ptr(), x, y, width, height, flags) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "blit"]
|
||||
fn extern_blit(sprite: *const u8, x: i32, y: i32, width: u32, height: u32, flags: u32);
|
||||
}
|
||||
|
||||
/// Copies a subregion within a larger sprite atlas to the framebuffer.
|
||||
pub fn blit_sub(
|
||||
sprite: &[u8],
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
src_x: u32,
|
||||
src_y: u32,
|
||||
stride: u32,
|
||||
flags: u32,
|
||||
) {
|
||||
unsafe {
|
||||
extern_blit_sub(
|
||||
sprite.as_ptr(),
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
src_x,
|
||||
src_y,
|
||||
stride,
|
||||
flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "blitSub"]
|
||||
fn extern_blit_sub(
|
||||
sprite: *const u8,
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: u32,
|
||||
height: u32,
|
||||
src_x: u32,
|
||||
src_y: u32,
|
||||
stride: u32,
|
||||
flags: u32,
|
||||
);
|
||||
}
|
||||
|
||||
pub const BLIT_2BPP: u32 = 1;
|
||||
pub const BLIT_1BPP: u32 = 0;
|
||||
pub const BLIT_FLIP_X: u32 = 2;
|
||||
pub const BLIT_FLIP_Y: u32 = 4;
|
||||
pub const BLIT_ROTATE: u32 = 8;
|
||||
|
||||
/// Draws a line between two points.
|
||||
pub fn line(x1: i32, y1: i32, x2: i32, y2: i32) {
|
||||
unsafe { extern_line(x1, y1, x2, y2) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "line"]
|
||||
fn extern_line(x1: i32, y1: i32, x2: i32, y2: i32);
|
||||
}
|
||||
|
||||
/// Draws an oval (or circle).
|
||||
pub fn oval(x: i32, y: i32, width: u32, height: u32) {
|
||||
unsafe { extern_oval(x, y, width, height) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "oval"]
|
||||
fn extern_oval(x: i32, y: i32, width: u32, height: u32);
|
||||
}
|
||||
|
||||
/// Draws a rectangle.
|
||||
pub fn rect(x: i32, y: i32, width: u32, height: u32) {
|
||||
unsafe { extern_rect(x, y, width, height) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "rect"]
|
||||
fn extern_rect(x: i32, y: i32, width: u32, height: u32);
|
||||
}
|
||||
|
||||
/// Draws text using the built-in system font.
|
||||
pub fn text(text: &str, x: i32, y: i32) {
|
||||
unsafe { extern_text(text.as_ptr(), text.len(), x, y) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "textUtf8"]
|
||||
fn extern_text(text: *const u8, length: usize, x: i32, y: i32);
|
||||
}
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Sound Functions │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
/// Plays a sound tone.
|
||||
pub fn tone(frequency: u32, duration: u32, volume: u32, flags: u32) {
|
||||
unsafe { extern_tone(frequency, duration, volume, flags) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "tone"]
|
||||
fn extern_tone(frequency: u32, duration: u32, volume: u32, flags: u32);
|
||||
}
|
||||
|
||||
pub const TONE_PULSE1: u32 = 0;
|
||||
pub const TONE_PULSE2: u32 = 1;
|
||||
pub const TONE_TRIANGLE: u32 = 2;
|
||||
pub const TONE_NOISE: u32 = 3;
|
||||
pub const TONE_MODE1: u32 = 0;
|
||||
pub const TONE_MODE2: u32 = 4;
|
||||
pub const TONE_MODE3: u32 = 8;
|
||||
pub const TONE_MODE4: u32 = 12;
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Storage Functions │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
extern "C" {
|
||||
/// Reads up to `size` bytes from persistent storage into the pointer `dest`.
|
||||
pub fn diskr(dest: *mut u8, size: u32) -> u32;
|
||||
|
||||
/// Writes up to `size` bytes from the pointer `src` into persistent storage.
|
||||
pub fn diskw(src: *const u8, size: u32) -> u32;
|
||||
}
|
||||
|
||||
// ┌───────────────────────────────────────────────────────────────────────────┐
|
||||
// │ │
|
||||
// │ Other Functions │
|
||||
// │ │
|
||||
// └───────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
extern "C" {
|
||||
/// Copies `size` bytes from `srcPtr` into `destPtr`.
|
||||
#[link_name = "memcpy"]
|
||||
pub fn memcpy(dest: *mut u8, src: *const u8, size: usize) -> usize;
|
||||
|
||||
/// Fills memory at `destPtr` with `size` bytes of the fixed value `value`.
|
||||
#[link_name = "memset"]
|
||||
pub fn memset(dest: *mut u8, byte: u8, size: usize) -> usize;
|
||||
}
|
||||
|
||||
/// Prints a message to the debug console.
|
||||
pub fn trace(text: &str) {
|
||||
unsafe { extern_trace(text.as_ptr(), text.len()) }
|
||||
}
|
||||
extern "C" {
|
||||
#[link_name = "traceUtf8"]
|
||||
fn extern_trace(trace: *const u8, length: usize);
|
||||
}
|
||||
@@ -8,22 +8,15 @@ repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
# [lib]
|
||||
# crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
rdraught.workspace = true
|
||||
rdraught-ui-common.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
console_error_panic_hook.workspace = true
|
||||
rmath.workspace = true
|
||||
base64.workspace = true
|
||||
|
||||
|
||||
[dependencies.web-sys]
|
||||
workspace = true
|
||||
features = [
|
||||
'DomMatrix',
|
||||
'CanvasRenderingContext2d',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
@@ -31,9 +24,8 @@ features = [
|
||||
'EventTarget',
|
||||
'HtmlCanvasElement',
|
||||
'HtmlElement',
|
||||
'KeyboardEvent',
|
||||
'MouseEvent',
|
||||
'Node',
|
||||
'HtmlImageElement',
|
||||
'SvgImageElement',
|
||||
'Window',
|
||||
]
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="fill:none; fill-opacity:0; fill-rule:evenodd; stroke:#ffc837; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0)">
|
||||
<path
|
||||
d="M 22.5,11.63 L 22.5,6"
|
||||
style="fill:none; stroke:#ffc837; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 20,8 L 25,8"
|
||||
style="fill:none; stroke:#ffc837; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"
|
||||
style="fill:#ffffff; stroke:#ffc837; stroke-linecap:butt; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "
|
||||
style="fill:#ffffff; stroke:#ffc837;" />
|
||||
<path
|
||||
d="M 11.5,30 C 17,27 27,27 32.5,30"
|
||||
style="fill:none; stroke:#ffc837;" />
|
||||
<path
|
||||
d="M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5"
|
||||
style="fill:none; stroke:#ffc837;" />
|
||||
<path
|
||||
d="M 11.5,37 C 17,34 27,34 32.5,37"
|
||||
style="fill:none; stroke:#ffc837;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="45" height="45">
|
||||
<g style="fill:none; fill-opacity:0; fill-rule:evenodd; stroke:#776b00; stroke-width:1.5; stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4; stroke-dasharray:none; stroke-opacity:1;" transform="translate(0,0)">
|
||||
<path
|
||||
d="M 22.5,11.63 L 22.5,6"
|
||||
style="fill:none; stroke:#776b00; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 20,8 L 25,8"
|
||||
style="fill:none; stroke:#776b00; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 22.5,25 C 22.5,25 27,17.5 25.5,14.5 C 25.5,14.5 24.5,12 22.5,12 C 20.5,12 19.5,14.5 19.5,14.5 C 18,17.5 22.5,25 22.5,25"
|
||||
style="fill:#ffffff; stroke:#776b00; stroke-linecap:butt; stroke-linejoin:miter;" />
|
||||
<path
|
||||
d="M 11.5,37 C 17,40.5 27,40.5 32.5,37 L 32.5,30 C 32.5,30 41.5,25.5 38.5,19.5 C 34.5,13 25,16 22.5,23.5 L 22.5,27 L 22.5,23.5 C 19,16 9.5,13 6.5,19.5 C 3.5,25.5 11.5,29.5 11.5,29.5 L 11.5,37 z "
|
||||
style="fill:#ffffff; stroke:#776b00;" />
|
||||
<path
|
||||
d="M 11.5,30 C 17,27 27,27 32.5,30"
|
||||
style="fill:none; stroke:#776b00;" />
|
||||
<path
|
||||
d="M 11.5,33.5 C 17,30.5 27,30.5 32.5,33.5"
|
||||
style="fill:none; stroke:#776b00;" />
|
||||
<path
|
||||
d="M 11.5,37 C 17,34 27,34 32.5,37"
|
||||
style="fill:none; stroke:#776b00;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,61 +0,0 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use web_sys::CanvasRenderingContext2d;
|
||||
use web_sys::MouseEvent;
|
||||
extern crate console_error_panic_hook;
|
||||
|
||||
// #[wasm_bindgen(start)]
|
||||
fn main() -> Result<(), JsValue> {
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let canvas = document
|
||||
.create_element("canvas")?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()?;
|
||||
document.body().unwrap().append_child(&canvas)?;
|
||||
canvas.set_width(640);
|
||||
canvas.set_height(480);
|
||||
canvas.style().set_property("border", "solid")?;
|
||||
let context = canvas
|
||||
.get_context("2d")?
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
|
||||
let context = Rc::new(context);
|
||||
let pressed = Rc::new(Cell::new(false));
|
||||
{
|
||||
let context = context.clone();
|
||||
let pressed = pressed.clone();
|
||||
let closure = Closure::<dyn FnMut(_)>::new(move |event: web_sys::MouseEvent| {
|
||||
context.begin_path();
|
||||
context.move_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
pressed.set(true);
|
||||
});
|
||||
canvas.add_event_listener_with_callback("mousedown", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
{
|
||||
let context = context.clone();
|
||||
let pressed = pressed.clone();
|
||||
let closure = Closure::<dyn FnMut(_)>::new(move |event: web_sys::MouseEvent| {
|
||||
if pressed.get() {
|
||||
context.line_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
context.stroke();
|
||||
context.begin_path();
|
||||
context.move_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
}
|
||||
});
|
||||
canvas.add_event_listener_with_callback("mousemove", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
{
|
||||
let closure = Closure::<dyn FnMut(_)>::new(move |event: web_sys::MouseEvent| {
|
||||
pressed.set(false);
|
||||
context.line_to(event.offset_x() as f64, event.offset_y() as f64);
|
||||
context.stroke();
|
||||
});
|
||||
canvas.add_event_listener_with_callback("mouseup", closure.as_ref().unchecked_ref())?;
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+294
-302
@@ -1,156 +1,18 @@
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose::STANDARD as b64;
|
||||
use core::f64::consts::PI;
|
||||
use rdraught::{DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RectangularBoard};
|
||||
use rdraught_ui_common::{
|
||||
Point, Rect, SharedMutable, SharedMutableRef, Xform, new_shared_mut, new_shared_mut_ref,
|
||||
use rdraught::{
|
||||
DraughtsBoard, DraughtsGame, Move, Piece, Player, Position, RDraughtApplication,
|
||||
RectangularBoard,
|
||||
};
|
||||
use rdraught_ui_common::{Point, Rect, Xform, board_position, new_shared_mut_ref};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{CanvasRenderingContext2d, Document, HtmlImageElement, MouseEvent};
|
||||
use web_sys::{CanvasRenderingContext2d, KeyboardEvent, MouseEvent};
|
||||
|
||||
extern crate console_error_panic_hook;
|
||||
const CROWN_COLOR: &str = "#ffd700";
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
// Use `js_namespace` here to bind `console.log(..)` instead of just
|
||||
// `log(..)`
|
||||
#[wasm_bindgen(js_namespace = console)]
|
||||
fn log(s: &str);
|
||||
|
||||
// The `console.log` is quite polymorphic, so we can bind it with multiple
|
||||
// signatures. Note that we need to use `js_name` to ensure we always call
|
||||
// `log` in JS.
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
fn log_u32(a: u32);
|
||||
|
||||
// Multiple arguments too!
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
fn log_many(a: &str, b: &str);
|
||||
}
|
||||
|
||||
macro_rules! console_log {
|
||||
// Note that this is using the `log` function imported above during
|
||||
// `bare_bones`
|
||||
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
|
||||
}
|
||||
|
||||
struct PieceDrawer {
|
||||
red_crown: HtmlImageElement,
|
||||
white_crown: HtmlImageElement,
|
||||
}
|
||||
|
||||
impl PieceDrawer {
|
||||
fn new(document: &Document) -> Result<PieceDrawer, JsValue> {
|
||||
const CROWN_RED: &[u8] = include_bytes!("crown_red.svg");
|
||||
const CROWN_WHITE: &[u8] = include_bytes!("crown_white.svg");
|
||||
let red_crown = document
|
||||
.create_element("img")?
|
||||
.dyn_into::<HtmlImageElement>()?;
|
||||
let src = b64.encode(CROWN_RED);
|
||||
let src = format!("data:image/svg+xml;base64,{}", src);
|
||||
red_crown.set_src(&src);
|
||||
let white_crown = document
|
||||
.create_element("img")?
|
||||
.dyn_into::<HtmlImageElement>()?;
|
||||
let src = b64.encode(CROWN_WHITE);
|
||||
let src = format!("data:image/svg+xml;base64,{}", src);
|
||||
white_crown.set_src(&src);
|
||||
Ok(PieceDrawer {
|
||||
red_crown,
|
||||
white_crown,
|
||||
})
|
||||
}
|
||||
|
||||
fn draw_piece(
|
||||
&self,
|
||||
ctx: &CanvasRenderingContext2d,
|
||||
xform: &Xform,
|
||||
cell: Rect,
|
||||
piece: Piece,
|
||||
) -> Result<(), JsValue> {
|
||||
ctx.save();
|
||||
let center = cell.center();
|
||||
let outer_radius = cell.width() * 0.3;
|
||||
let thickness = outer_radius * 0.5;
|
||||
let vertical_scale_factor = 0.7;
|
||||
{
|
||||
let p = Point::new(0.0, cell.center().y()) * xform;
|
||||
let xform = xform.clone()
|
||||
* Xform::xlate(-p.x(), -p.y())
|
||||
* Xform::scale(1.0, vertical_scale_factor)
|
||||
* Xform::xlate(p.x(), p.y());
|
||||
apply_xform(ctx, &xform)?;
|
||||
}
|
||||
|
||||
ctx.set_fill_style_str("#000");
|
||||
ctx.begin_path();
|
||||
|
||||
ctx.arc(
|
||||
center.x(),
|
||||
center.y() + thickness / 2.0,
|
||||
outer_radius,
|
||||
0.0,
|
||||
PI,
|
||||
)?;
|
||||
ctx.line_to(center.x() - outer_radius, center.y() - thickness / 2.0);
|
||||
ctx.arc(
|
||||
center.x(),
|
||||
center.y() - thickness / 2.0,
|
||||
outer_radius,
|
||||
PI,
|
||||
2.0 * PI,
|
||||
)?;
|
||||
ctx.line_to(center.x() + outer_radius, center.y() + thickness / 2.0);
|
||||
ctx.fill();
|
||||
|
||||
match piece.player() {
|
||||
Some(Player::Red) => {
|
||||
ctx.set_fill_style_str("#ff0000");
|
||||
}
|
||||
Some(Player::White) => {
|
||||
ctx.set_fill_style_str("#ffffff");
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
let radius = cell.width() * 0.275;
|
||||
ctx.begin_path();
|
||||
ctx.arc(
|
||||
center.x(),
|
||||
center.y() - thickness / 2.0,
|
||||
radius,
|
||||
0.0,
|
||||
2.0 * PI,
|
||||
)?;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
ctx.save();
|
||||
if let Some(player) = piece.player() {
|
||||
if piece.is_crowned() {
|
||||
{
|
||||
let p = Point::new(cell.center().x(), cell.center().y()) * xform;
|
||||
let f = 0.55;
|
||||
let xform = xform.clone()
|
||||
* Xform::xlate(-p.x(), -p.y())
|
||||
* Xform::scale(1.0, vertical_scale_factor)
|
||||
* Xform::scale(f, f)
|
||||
* Xform::xlate(p.x(), p.y());
|
||||
apply_xform(ctx, &xform)?;
|
||||
}
|
||||
let image = match player {
|
||||
Player::White => self.white_crown.clone(),
|
||||
Player::Red => self.red_crown.clone(),
|
||||
};
|
||||
ctx.draw_image_with_html_image_element_and_dw_and_dh(
|
||||
&image,
|
||||
cell.tl().x(),
|
||||
cell.tl().y() - thickness / 1.0,
|
||||
cell.width(),
|
||||
cell.height(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
Ok(())
|
||||
fn player_name(player: Player) -> &'static str {
|
||||
match player {
|
||||
Player::Red => "Red",
|
||||
Player::White => "White",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,42 +27,129 @@ fn apply_xform(ctx: &CanvasRenderingContext2d, xform: &Xform) -> Result<(), JsVa
|
||||
)
|
||||
}
|
||||
|
||||
fn from_ctx(ctx: &CanvasRenderingContext2d) -> Result<Xform, JsValue> {
|
||||
let m = ctx.get_transform()?;
|
||||
Ok(Xform::new(m.a(), m.c(), m.b(), m.d(), m.e(), m.f()))
|
||||
fn draw_crown(ctx: &CanvasRenderingContext2d, center: Point, size: f64) -> Result<(), JsValue> {
|
||||
let width = size * 0.35;
|
||||
let height = size * 0.2;
|
||||
let top = center.y() - height / 2.0;
|
||||
|
||||
ctx.begin_path();
|
||||
ctx.move_to(center.x() - width / 2.0, top);
|
||||
ctx.line_to(center.x() - width / 4.0, top + height);
|
||||
ctx.line_to(center.x(), top - height * 0.25);
|
||||
ctx.line_to(center.x() + width / 4.0, top + height);
|
||||
ctx.line_to(center.x() + width / 2.0, top);
|
||||
ctx.close_path();
|
||||
ctx.set_fill_style_str(CROWN_COLOR);
|
||||
ctx.fill();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn map_row(current_player: Player, row: usize) -> usize {
|
||||
match current_player {
|
||||
Player::White => DraughtsBoard::rows() - 1 - row,
|
||||
Player::Red => row,
|
||||
fn draw_piece(
|
||||
ctx: &CanvasRenderingContext2d,
|
||||
xform: &Xform,
|
||||
cell: Rect,
|
||||
piece: Piece,
|
||||
) -> Result<(), JsValue> {
|
||||
if !piece.is_present() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
let center = cell.center();
|
||||
let outer_radius = cell.width() * 0.3;
|
||||
let thickness = outer_radius * 0.5;
|
||||
let vertical_scale_factor = 0.7;
|
||||
{
|
||||
let p = Point::new(0.0, cell.center().y()) * xform;
|
||||
let piece_xform = xform.clone()
|
||||
* Xform::xlate(-p.x(), -p.y())
|
||||
* Xform::scale(1.0, vertical_scale_factor)
|
||||
* Xform::xlate(p.x(), p.y());
|
||||
apply_xform(ctx, &piece_xform)?;
|
||||
}
|
||||
|
||||
ctx.set_fill_style_str("#000");
|
||||
ctx.begin_path();
|
||||
ctx.arc(
|
||||
center.x(),
|
||||
center.y() + thickness / 2.0,
|
||||
outer_radius,
|
||||
0.0,
|
||||
PI,
|
||||
)?;
|
||||
ctx.line_to(center.x() - outer_radius, center.y() - thickness / 2.0);
|
||||
ctx.arc(
|
||||
center.x(),
|
||||
center.y() - thickness / 2.0,
|
||||
outer_radius,
|
||||
PI,
|
||||
2.0 * PI,
|
||||
)?;
|
||||
ctx.line_to(center.x() + outer_radius, center.y() + thickness / 2.0);
|
||||
ctx.fill();
|
||||
|
||||
match piece.player() {
|
||||
Some(Player::Red) => ctx.set_fill_style_str("#ff0000"),
|
||||
Some(Player::White) => ctx.set_fill_style_str("#ffffff"),
|
||||
None => {}
|
||||
}
|
||||
let radius = cell.width() * 0.275;
|
||||
ctx.begin_path();
|
||||
ctx.arc(
|
||||
center.x(),
|
||||
center.y() - thickness / 2.0,
|
||||
radius,
|
||||
0.0,
|
||||
2.0 * PI,
|
||||
)?;
|
||||
ctx.fill();
|
||||
|
||||
if piece.is_crowned() {
|
||||
draw_crown(
|
||||
ctx,
|
||||
Point::new(center.x(), center.y() - thickness / 2.0),
|
||||
cell.width(),
|
||||
)?;
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
trait Agent {}
|
||||
fn draw_highlight(ctx: &CanvasRenderingContext2d, cell: &Rect, color: &str) -> Result<(), JsValue> {
|
||||
ctx.save();
|
||||
ctx.set_shadow_blur(300.0);
|
||||
ctx.set_shadow_color(color);
|
||||
ctx.begin_path();
|
||||
ctx.move_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.line_to(cell.tl().x(), cell.br().y());
|
||||
ctx.line_to(cell.br().x(), cell.br().y());
|
||||
ctx.line_to(cell.br().x(), cell.tl().y());
|
||||
ctx.line_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.clip();
|
||||
ctx.begin_path();
|
||||
ctx.set_stroke_style_str(color);
|
||||
ctx.set_line_width(4.0);
|
||||
ctx.move_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.line_to(cell.tl().x(), cell.br().y());
|
||||
ctx.line_to(cell.br().x(), cell.br().y());
|
||||
ctx.line_to(cell.br().x(), cell.tl().y());
|
||||
ctx.line_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct App {
|
||||
game: DraughtsGame,
|
||||
rd: RDraughtApplication,
|
||||
board: Rect,
|
||||
screen: Rect,
|
||||
xform: Xform,
|
||||
selected_piece: Option<Position>,
|
||||
available_moves: Vec<Move>,
|
||||
main_player: Player,
|
||||
red_agent: Box<dyn Agent>,
|
||||
white_agent: Box<dyn Agent>,
|
||||
}
|
||||
|
||||
struct Human {}
|
||||
|
||||
impl Human {
|
||||
fn new() -> Human {
|
||||
Human {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Agent for Human {}
|
||||
|
||||
impl Default for App {
|
||||
fn default() -> Self {
|
||||
let board = Rect::from_size(
|
||||
@@ -209,172 +158,174 @@ impl Default for App {
|
||||
App::SQUARE_SIZE * DraughtsBoard::rows() as f64,
|
||||
);
|
||||
App {
|
||||
game: DraughtsGame::default(),
|
||||
rd: RDraughtApplication::new(DraughtsGame::default()),
|
||||
board,
|
||||
screen: Rect::new(Point::new(0.0, 0.0), Point::new(0.0, 0.0)),
|
||||
xform: Xform::default(),
|
||||
selected_piece: None,
|
||||
available_moves: Vec::new(),
|
||||
main_player: Player::White,
|
||||
red_agent: Box::new(Human::new()),
|
||||
white_agent: Box::new(Human::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
const SQUARE_SIZE: f64 = 100.0;
|
||||
fn draw(
|
||||
&mut self,
|
||||
ctx: &CanvasRenderingContext2d,
|
||||
piece_drawer: &PieceDrawer,
|
||||
) -> Result<(), JsValue> {
|
||||
|
||||
fn new_game(&mut self) {
|
||||
self.rd = RDraughtApplication::new(DraughtsGame::default());
|
||||
self.selected_piece = None;
|
||||
self.available_moves.clear();
|
||||
}
|
||||
|
||||
fn flip_view(&mut self) {
|
||||
self.main_player = match self.main_player {
|
||||
Player::White => Player::Red,
|
||||
Player::Red => Player::White,
|
||||
};
|
||||
self.selected_piece = None;
|
||||
self.available_moves.clear();
|
||||
}
|
||||
|
||||
fn update_selection_after_move(&mut self) {
|
||||
if let Some(pending) = self.rd.game().pending_capture() {
|
||||
self.selected_piece = Some(pending);
|
||||
self.available_moves = self.rd.game().moves_for_piece(pending).collect();
|
||||
} else {
|
||||
self.selected_piece = None;
|
||||
self.available_moves.clear();
|
||||
}
|
||||
}
|
||||
|
||||
fn draw(&mut self, ctx: &CanvasRenderingContext2d) -> Result<(), JsValue> {
|
||||
ctx.clear_rect(0.0, 0.0, self.screen.width(), self.screen.height());
|
||||
let board = &self.board;
|
||||
let f = f64::min(
|
||||
let factor = f64::min(
|
||||
self.screen.width() / board.width(),
|
||||
self.screen.height() / board.height(),
|
||||
);
|
||||
ctx.save();
|
||||
self.xform = Xform::default()
|
||||
* Xform::xlate(-board.center().x(), -board.center().y())
|
||||
* Xform::scale(f, f)
|
||||
* Xform::scale(factor, factor)
|
||||
* Xform::xlate(self.screen.center().x(), self.screen.center().y());
|
||||
apply_xform(ctx, &self.xform)?;
|
||||
// let board_center = board.center();
|
||||
// let screen_center = screen.center();
|
||||
// let xlate = -board_center;
|
||||
// ctx.restore();
|
||||
// ctx.translate(-xlate.x(), -xlate.y())?;
|
||||
// ctx.scale(f, f)?;
|
||||
// ctx.translate(screen_center.x(), screen_center.y())?;
|
||||
// console_log!("xform2: {}", from_ctx(ctx)?);
|
||||
let available_moves: Vec<Position> = self
|
||||
|
||||
let available_positions: Vec<Position> = self
|
||||
.available_moves
|
||||
.iter()
|
||||
.filter_map(Move::get_end_position)
|
||||
.collect();
|
||||
for i in 0..DraughtsBoard::rows() {
|
||||
for j in 0..DraughtsBoard::columns() {
|
||||
|
||||
for screen_row in 0..DraughtsBoard::rows() {
|
||||
for screen_col in 0..DraughtsBoard::columns() {
|
||||
let cell = Rect::new(
|
||||
Point::new(
|
||||
board.tl().x() + i as f64 * App::SQUARE_SIZE,
|
||||
board.tl().y() + j as f64 * App::SQUARE_SIZE,
|
||||
board.tl().x() + screen_col as f64 * App::SQUARE_SIZE,
|
||||
board.tl().y() + screen_row as f64 * App::SQUARE_SIZE,
|
||||
),
|
||||
Point::new(
|
||||
board.tl().x() + (i + 1) as f64 * App::SQUARE_SIZE,
|
||||
board.tl().y() + (j + 1) as f64 * App::SQUARE_SIZE,
|
||||
board.tl().x() + (screen_col + 1) as f64 * App::SQUARE_SIZE,
|
||||
board.tl().y() + (screen_row + 1) as f64 * App::SQUARE_SIZE,
|
||||
),
|
||||
);
|
||||
if (i + j) % 2 == 0 {
|
||||
if (screen_row + screen_col) % 2 == 0 {
|
||||
ctx.set_fill_style_str("#cccc99");
|
||||
} else {
|
||||
ctx.set_fill_style_str("#666633");
|
||||
}
|
||||
ctx.fill_rect(cell.tl().x(), cell.tl().y(), cell.width(), cell.height());
|
||||
if (i + j) % 2 != 0 {
|
||||
let current_player = Player::White;
|
||||
let position = Position::new(map_row(current_player, j) as u8, i as u8)
|
||||
.expect("board loop only visits valid squares");
|
||||
let piece = self.game.piece_at(position);
|
||||
|
||||
let board_position =
|
||||
board_position(self.main_player, screen_row as u8, screen_col as u8).ok();
|
||||
if let Some(position) = board_position {
|
||||
let piece = self.rd.game().piece_at(position);
|
||||
if piece.is_present() {
|
||||
piece_drawer.draw_piece(ctx, &self.xform, cell.clone(), piece)?;
|
||||
draw_piece(ctx, &self.xform, cell.clone(), piece)?;
|
||||
}
|
||||
if let Some(selected_piece) = self.selected_piece {
|
||||
if selected_piece == position {
|
||||
// console_log!("Selected: {}", selected_piece);
|
||||
ctx.save();
|
||||
ctx.set_shadow_blur(300.0);
|
||||
ctx.set_shadow_color("#ffffff");
|
||||
ctx.begin_path();
|
||||
ctx.move_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.clip();
|
||||
ctx.set_stroke_style_str("#fff");
|
||||
ctx.set_line_width(4.0);
|
||||
ctx.begin_path();
|
||||
ctx.move_to(board.tl().x(), board.tl().y());
|
||||
ctx.line_to(board.br().x(), board.tl().y());
|
||||
ctx.line_to(board.br().x(), board.br().y());
|
||||
ctx.line_to(board.tl().x(), board.br().y());
|
||||
ctx.line_to(board.tl().x(), board.tl().y());
|
||||
ctx.move_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
if available_moves.contains(&position) {
|
||||
ctx.save();
|
||||
ctx.set_shadow_blur(300.0);
|
||||
ctx.set_shadow_color("#0000ff");
|
||||
ctx.begin_path();
|
||||
ctx.move_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.clip();
|
||||
ctx.set_stroke_style_str("#0f0");
|
||||
ctx.set_line_width(4.0);
|
||||
ctx.begin_path();
|
||||
ctx.move_to(board.tl().x(), board.tl().y());
|
||||
ctx.line_to(board.br().x(), board.tl().y());
|
||||
ctx.line_to(board.br().x(), board.br().y());
|
||||
ctx.line_to(board.tl().x(), board.br().y());
|
||||
ctx.line_to(board.tl().x(), board.tl().y());
|
||||
ctx.move_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.tl().y());
|
||||
ctx.line_to(cell.br().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.br().y());
|
||||
ctx.line_to(cell.tl().x(), cell.tl().y());
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
if board_position == self.selected_piece {
|
||||
draw_highlight(ctx, &cell, "#ffffff")?;
|
||||
}
|
||||
if let Some(position) = board_position {
|
||||
if available_positions.contains(&position) {
|
||||
draw_highlight(ctx, &cell, "#00ff00")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
|
||||
if let Some(winner) = self.rd.game().winner() {
|
||||
ctx.save();
|
||||
ctx.set_fill_style_str("rgba(0, 0, 0, 0.65)");
|
||||
ctx.fill_rect(0.0, 0.0, self.screen.width(), self.screen.height());
|
||||
ctx.set_fill_style_str("#ffffff");
|
||||
ctx.set_font("48px sans-serif");
|
||||
ctx.set_text_align("center");
|
||||
ctx.set_text_baseline("middle");
|
||||
ctx.fill_text(
|
||||
&format!("{} wins", player_name(winner)),
|
||||
self.screen.center().x(),
|
||||
self.screen.center().y(),
|
||||
)?;
|
||||
ctx.set_font("20px sans-serif");
|
||||
ctx.fill_text(
|
||||
"Press N for a new game",
|
||||
self.screen.center().x(),
|
||||
self.screen.center().y() + 40.0,
|
||||
)?;
|
||||
ctx.restore();
|
||||
} else {
|
||||
ctx.save();
|
||||
ctx.set_fill_style_str("#ffffff");
|
||||
ctx.set_font("14px sans-serif");
|
||||
ctx.set_text_align("left");
|
||||
ctx.set_text_baseline("top");
|
||||
ctx.fill_text(
|
||||
&format!(
|
||||
"{} to move | Ctrl+Z undo Ctrl+Y redo N new F flip",
|
||||
player_name(self.rd.game().next_move())
|
||||
),
|
||||
10.0,
|
||||
10.0,
|
||||
)?;
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
let document = web_sys::window().unwrap().document().unwrap();
|
||||
let canvas = document
|
||||
.create_element("canvas")?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()?;
|
||||
canvas.style().set_property("background-color", "black")?;
|
||||
document.body().unwrap().append_child(&canvas)?;
|
||||
// canvas.style().set_property("border", "solid")?;
|
||||
let window = web_sys::window().expect("should have a window in this context");
|
||||
let width = window.inner_width().unwrap().as_f64().unwrap();
|
||||
let height = window.inner_height().unwrap().as_f64().unwrap();
|
||||
canvas.set_width(width as u32);
|
||||
canvas.set_height(height as u32);
|
||||
|
||||
let app = new_shared_mut_ref(App::default());
|
||||
let ctx = canvas
|
||||
.get_context("2d")?
|
||||
.unwrap()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
|
||||
let piece_drawer = new_shared_mut_ref(PieceDrawer::new(&document)?);
|
||||
.dyn_into::<CanvasRenderingContext2d>()?;
|
||||
{
|
||||
let piece_drawer = piece_drawer.clone();
|
||||
let screen = Rect::new(Point::new(0.0, 0.0), Point::new(width, height));
|
||||
app.borrow_mut().screen = screen;
|
||||
app.borrow_mut().draw(&ctx, &piece_drawer.borrow())?;
|
||||
let app = app.clone();
|
||||
app.borrow_mut().screen = Rect::new(Point::new(0.0, 0.0), Point::new(width, height));
|
||||
app.borrow_mut().draw(&ctx)?;
|
||||
}
|
||||
|
||||
{
|
||||
let app = app.clone();
|
||||
let canvas = canvas.clone();
|
||||
let ctx = ctx.clone();
|
||||
let piece_drawer = piece_drawer.clone();
|
||||
let closure = Closure::<dyn FnMut()>::new(move || {
|
||||
let window = web_sys::window().expect("should have a window in this context");
|
||||
let width = window.inner_width().unwrap().as_f64().unwrap() as u32;
|
||||
@@ -385,64 +336,105 @@ fn main() -> Result<(), JsValue> {
|
||||
Point::new(0.0, 0.0),
|
||||
Point::new(canvas.width() as f64, canvas.height() as f64),
|
||||
);
|
||||
app.borrow_mut().draw(&ctx, &piece_drawer.borrow()).unwrap();
|
||||
app.borrow_mut().draw(&ctx).unwrap();
|
||||
});
|
||||
let window = web_sys::window().expect("should have a window in this context");
|
||||
window.set_onresize(Some(closure.as_ref().unchecked_ref()));
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
{
|
||||
let app = app.clone();
|
||||
let piece_drawer = piece_drawer.clone();
|
||||
let ctx = ctx.clone();
|
||||
let mouse_click_handler = Closure::wrap(Box::new(move |event: MouseEvent| {
|
||||
let mut app = app.borrow_mut();
|
||||
if app.rd.game().winner().is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let xform = app.xform.invert();
|
||||
let board = &app.board;
|
||||
let position = Point::new(event.x() as f64, event.y() as f64) * &xform - board.tl();
|
||||
if position.x() > 0.0
|
||||
&& position.x() < board.width()
|
||||
&& position.y() > 0.0
|
||||
&& position.y() < board.height()
|
||||
let position =
|
||||
Point::new(event.offset_x() as f64, event.offset_y() as f64) * &xform - board.tl();
|
||||
if position.x() <= 0.0
|
||||
|| position.x() >= board.width()
|
||||
|| position.y() <= 0.0
|
||||
|| position.y() >= board.height()
|
||||
{
|
||||
let col = f64::floor(position.x() / App::SQUARE_SIZE) as usize;
|
||||
let row = f64::floor(position.y() / App::SQUARE_SIZE) as usize;
|
||||
let row = map_row(app.main_player, row);
|
||||
if (row + col) % 2 == 0 {
|
||||
let position = Position::new(row as u8, col as u8).unwrap();
|
||||
let selected_move = app
|
||||
.available_moves
|
||||
.iter()
|
||||
.find(|mv| mv.get_end_position() == Some(position))
|
||||
.map(Move::clone);
|
||||
if app.selected_piece.is_some() && selected_move.is_some() {
|
||||
let mv = selected_move.unwrap();
|
||||
if app.game.check_and_apply_move(&mv).is_ok() {
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
}
|
||||
} else {
|
||||
app.selected_piece = Some(position);
|
||||
let mut available_moves = Vec::<Move>::new();
|
||||
for mv in app.game.moves_for_piece(position) {
|
||||
available_moves.push(mv);
|
||||
}
|
||||
app.available_moves = available_moves;
|
||||
// console_log!("Clicked at: ({}, {})", col, row);
|
||||
}
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
app.draw(&ctx).unwrap();
|
||||
return;
|
||||
}
|
||||
|
||||
let screen_col = (position.x() / App::SQUARE_SIZE).floor() as u8;
|
||||
let screen_row = (position.y() / App::SQUARE_SIZE).floor() as u8;
|
||||
let Ok(position) = board_position(app.main_player, screen_row, screen_col) else {
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
app.draw(&ctx).unwrap();
|
||||
return;
|
||||
};
|
||||
|
||||
let selected_move = app
|
||||
.available_moves
|
||||
.iter()
|
||||
.find(|mv| mv.get_end_position() == Some(position))
|
||||
.cloned();
|
||||
if app.selected_piece.is_some() && selected_move.is_some() {
|
||||
if app.rd.apply_move(selected_move.unwrap()).is_ok() {
|
||||
app.update_selection_after_move();
|
||||
}
|
||||
} else {
|
||||
let has_moves = app.rd.game().moves_for_piece(position).next().is_some();
|
||||
if has_moves {
|
||||
app.selected_piece = Some(position);
|
||||
app.available_moves = app.rd.game().moves_for_piece(position).collect();
|
||||
} else {
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
}
|
||||
} else {
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
}
|
||||
app.draw(&ctx, &piece_drawer.borrow()).unwrap();
|
||||
app.draw(&ctx).unwrap();
|
||||
}) as Box<dyn FnMut(MouseEvent)>);
|
||||
canvas.set_onclick(Some(mouse_click_handler.as_ref().unchecked_ref()));
|
||||
mouse_click_handler.forget();
|
||||
}
|
||||
|
||||
{
|
||||
let app = app.clone();
|
||||
let ctx = ctx.clone();
|
||||
let key_handler = Closure::wrap(Box::new(move |event: KeyboardEvent| {
|
||||
let mut app = app.borrow_mut();
|
||||
let key = event.key().to_ascii_lowercase();
|
||||
let ctrl = event.ctrl_key();
|
||||
let shift = event.shift_key();
|
||||
let handled = if ctrl && (key == "y" || (key == "z" && shift)) {
|
||||
app.rd.redo().ok();
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
true
|
||||
} else if ctrl && key == "z" {
|
||||
app.rd.undo().ok();
|
||||
app.selected_piece = None;
|
||||
app.available_moves.clear();
|
||||
true
|
||||
} else if key == "n" {
|
||||
app.new_game();
|
||||
true
|
||||
} else if key == "f" {
|
||||
app.flip_view();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if handled {
|
||||
event.prevent_default();
|
||||
app.draw(&ctx).unwrap();
|
||||
}
|
||||
}) as Box<dyn FnMut(KeyboardEvent)>);
|
||||
window.set_onkeydown(Some(key_handler.as_ref().unchecked_ref()));
|
||||
key_handler.forget();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub(crate) type SharedMutable<T> = Rc<Cell<T>>;
|
||||
pub(crate) type SharedMutableRef<T> = Rc<RefCell<T>>;
|
||||
pub(crate) fn new_shared_mut_ref<T>(obj: T) -> SharedMutableRef<T> {
|
||||
Rc::new(RefCell::new(obj))
|
||||
}
|
||||
|
||||
pub(crate) fn new_shared_mut<T>(obj: T) -> SharedMutable<T> {
|
||||
Rc::new(Cell::new(obj))
|
||||
}
|
||||
Reference in New Issue
Block a user