Update the build environment and some architectural elements of the Kifu app #210

Merged
savanni merged 13 commits from kifu/flake into main 2024-02-28 04:42:58 +00:00
63 changed files with 802 additions and 5874 deletions

335
Cargo.lock generated
View File

@ -120,6 +120,17 @@ version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]]
name = "async-channel"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener 2.5.3",
"futures-core",
]
[[package]] [[package]]
name = "async-channel" name = "async-channel"
version = "2.1.1" version = "2.1.1"
@ -133,6 +144,126 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-executor"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c"
dependencies = [
"async-lock 3.3.0",
"async-task",
"concurrent-queue",
"fastrand 2.0.1",
"futures-lite 2.2.0",
"slab",
]
[[package]]
name = "async-global-executor"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c"
dependencies = [
"async-channel 2.1.1",
"async-executor",
"async-io 2.3.1",
"async-lock 3.3.0",
"blocking",
"futures-lite 2.2.0",
"once_cell",
]
[[package]]
name = "async-io"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock 2.8.0",
"autocfg 1.1.0",
"cfg-if",
"concurrent-queue",
"futures-lite 1.13.0",
"log 0.4.20",
"parking",
"polling 2.8.0",
"rustix 0.37.27",
"slab",
"socket2 0.4.10",
"waker-fn",
]
[[package]]
name = "async-io"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f97ab0c5b00a7cdbe5a371b9a782ee7be1316095885c8a4ea1daf490eb0ef65"
dependencies = [
"async-lock 3.3.0",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite 2.2.0",
"parking",
"polling 3.4.0",
"rustix 0.38.28",
"slab",
"tracing",
"windows-sys 0.52.0",
]
[[package]]
name = "async-lock"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b"
dependencies = [
"event-listener 4.0.1",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-std"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-channel 1.9.0",
"async-global-executor",
"async-io 1.13.0",
"async-lock 2.8.0",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite 1.13.0",
"gloo-timers",
"kv-log-macro",
"log 0.4.20",
"memchr",
"once_cell",
"pin-project-lite",
"pin-utils",
"slab",
"wasm-bindgen-futures",
]
[[package]]
name = "async-task"
version = "4.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.77" version = "0.1.77"
@ -153,6 +284,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "atomic-write-file" name = "atomic-write-file"
version = "0.1.2" version = "0.1.2"
@ -275,6 +412,22 @@ dependencies = [
"generic-array 0.14.7", "generic-array 0.14.7",
] ]
[[package]]
name = "blocking"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
dependencies = [
"async-channel 2.1.1",
"async-lock 3.3.0",
"async-task",
"fastrand 2.0.1",
"futures-io",
"futures-lite 2.2.0",
"piper",
"tracing",
]
[[package]] [[package]]
name = "build_html" name = "build_html"
version = "2.4.0" version = "2.4.0"
@ -872,6 +1025,15 @@ dependencies = [
"zune-inflate", "zune-inflate",
] ]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.1" version = "2.0.1"
@ -938,7 +1100,7 @@ checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6"
name = "fitnesstrax" name = "fitnesstrax"
version = "0.5.0" version = "0.5.0"
dependencies = [ dependencies = [
"async-channel", "async-channel 2.1.1",
"async-trait", "async-trait",
"chrono", "chrono",
"chrono-tz", "chrono-tz",
@ -1138,6 +1300,34 @@ version = "0.3.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa"
[[package]]
name = "futures-lite"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
dependencies = [
"fastrand 1.9.0",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
dependencies = [
"fastrand 2.0.1",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.29" version = "0.3.29"
@ -1394,6 +1584,18 @@ dependencies = [
"system-deps", "system-deps",
] ]
[[package]]
name = "gloo-timers"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c"
dependencies = [
"futures-channel",
"futures-core",
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "gm-control-panel" name = "gm-control-panel"
version = "0.1.0" version = "0.1.0"
@ -1765,7 +1967,7 @@ dependencies = [
"httpdate", "httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.5.5",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@ -1902,6 +2104,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "intl-memoizer" name = "intl-memoizer"
version = "0.5.1" version = "0.5.1"
@ -1921,6 +2132,17 @@ dependencies = [
"unic-langid", "unic-langid",
] ]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.9.0" version = "2.9.0"
@ -1950,7 +2172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"rustix", "rustix 0.38.28",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2000,6 +2222,7 @@ dependencies = [
name = "kifu-core" name = "kifu-core"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-std",
"chrono", "chrono",
"config", "config",
"config-derive", "config-derive",
@ -2009,13 +2232,14 @@ dependencies = [
"serde_json", "serde_json",
"sgf", "sgf",
"thiserror", "thiserror",
"typeshare",
] ]
[[package]] [[package]]
name = "kifu-gtk" name = "kifu-gtk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-channel 2.1.1",
"async-std",
"cairo-rs", "cairo-rs",
"gio", "gio",
"glib", "glib",
@ -2029,6 +2253,15 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log 0.4.20",
]
[[package]] [[package]]
name = "language-tags" name = "language-tags"
version = "0.2.2" version = "0.2.2"
@ -2105,6 +2338,12 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.12" version = "0.4.12"
@ -2135,6 +2374,9 @@ name = "log"
version = "0.4.20" version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "logger" name = "logger"
@ -2687,6 +2929,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
dependencies = [
"atomic-waker",
"fastrand 2.0.1",
"futures-io",
]
[[package]] [[package]]
name = "pkcs1" name = "pkcs1"
version = "0.7.5" version = "0.7.5"
@ -2748,6 +3001,36 @@ dependencies = [
"miniz_oxide 0.7.1", "miniz_oxide 0.7.1",
] ]
[[package]]
name = "polling"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg 1.1.0",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
"libc",
"log 0.4.20",
"pin-project-lite",
"windows-sys 0.48.0",
]
[[package]]
name = "polling"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30054e72317ab98eddd8561db0f6524df3367636884b7b21b703e4b280a84a14"
dependencies = [
"cfg-if",
"concurrent-queue",
"pin-project-lite",
"rustix 0.38.28",
"tracing",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@ -3197,6 +3480,20 @@ dependencies = [
"semver", "semver",
] ]
[[package]]
name = "rustix"
version = "0.37.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.28" version = "0.38.28"
@ -3206,7 +3503,7 @@ dependencies = [
"bitflags 2.4.1", "bitflags 2.4.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys 0.4.12",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -3466,6 +3763,16 @@ version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.5" version = "0.5.5"
@ -3810,9 +4117,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand 2.0.1",
"redox_syscall", "redox_syscall",
"rustix", "rustix 0.38.28",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -3953,7 +4260,7 @@ dependencies = [
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2", "socket2 0.5.5",
"tokio-macros", "tokio-macros",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -4339,6 +4646,12 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "value-bag"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126e423afe2dd9ac52142e7e9d5ce4135d7e13776c529d27fd6bc49f19e3280b"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
@ -4384,6 +4697,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "waker-fn"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
[[package]] [[package]]
name = "want" name = "want"
version = "0.3.1" version = "0.3.1"

View File

@ -1,30 +0,0 @@
all: test bin
test: kifu-core/test-oneshot sgf/test-oneshot
bin: kifu-gtk
kifu-core/dev:
cd kifu/core && make test
kifu-core/test:
cd kifu/core && make test
kifu-core/test-oneshot:
cd kifu/core && make test-oneshot
kifu-gtk:
cd kifu/gtk && make release
kifu-gtk/dev:
cd kifu/gtk && make dev
kifu-pwa:
cd kifu/pwa && make release
kifu-pwa/dev:
pushd kifu/pwa && make dev
kifu-pwa/server:
pushd kifu/pwa && make server

View File

@ -1,73 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
RUST_ALL_TARGETS=(
"changeset"
"config"
"config-derive"
"coordinates"
"cyberpunk-splash"
"dashboard"
"emseries"
"file-service"
"fitnesstrax"
"fluent-ergonomics"
"geo-types"
"gm-control-panel"
"hex-grid"
"ifc"
"kifu-core"
"kifu-gtk"
"memorycache"
"nom-training"
"result-extended"
"screenplay"
"sgf"
"tree"
)
build_rust_targets() {
local CMD=$1
local TARGETS=${@/$CMD}
for target in $TARGETS; do
MODULE=$target CMD=$CMD ./builders/rust.sh
done
}
build_dist() {
local TARGETS=${@/$CMD}
for target in $TARGETS; do
if [ -f $target/dist.sh ]; then
build_rust_targets release ${TARGETS[*]}
cd $target && ./dist.sh
fi
done
}
export CARGO=`which cargo`
if [ -z "${TARGET-}" ]; then
TARGET="all"
fi
if [ -z "${CMD-}" ]; then
CMD="test release"
fi
if [ "${CMD}" == "clean" ]; then
cargo clean
exit 0
fi
for cmd in $CMD; do
if [ "${CMD}" == "dist" ]; then
build_dist $TARGET
elif [ "${TARGET}" == "all" ]; then
build_rust_targets $cmd ${RUST_ALL_TARGETS[*]}
else
build_rust_targets $cmd $TARGET
fi
done

View File

@ -1,41 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [ ! -z "$MODULE" ]; then
MODULE="-p $MODULE"
fi
if [ -z "${PARAMS-}" ]; then
PARAMS=""
fi
case $CMD in
build)
$CARGO build $MODULE $PARAMS
;;
lint)
$CARGO clippy $MODULE $PARAMS -- -Dwarnings
;;
test)
$CARGO test $MODULE $PARAMS
;;
run)
$CARGO run $MODULE $PARAMS
;;
release)
$CARGO clippy $MODULE $PARAMS -- -Dwarnings
$CARGO build --release $MODULE $PARAMS
$CARGO test --release $MODULE $PARAMS
;;
clean)
$CARGO clean $MODULE
;;
"")
echo "No command specified. Use build | lint | test | run | release | clean"
;;
*)
echo "$CMD is unknown. Use build | lint | test | run | release | clean"
;;
esac

View File

@ -70,6 +70,7 @@
dashboard = attrs: { nativeBuildInputs = gtkNativeInputs; }; dashboard = attrs: { nativeBuildInputs = gtkNativeInputs; };
fitnesstrax = import ./fitnesstrax/app/override.nix { gtkNativeInputs = gtkNativeInputs; }; fitnesstrax = import ./fitnesstrax/app/override.nix { gtkNativeInputs = gtkNativeInputs; };
kifu-gtk = import ./kifu/gtk/override.nix { gtkNativeInputs = gtkNativeInputs; };
}; };
}; };
@ -83,6 +84,7 @@
dashboard = cargo_nix.workspaceMembers.dashboard.build; dashboard = cargo_nix.workspaceMembers.dashboard.build;
file-service = cargo_nix.workspaceMembers.file-service.build; file-service = cargo_nix.workspaceMembers.file-service.build;
fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build; fitnesstrax = cargo_nix.workspaceMembers.fitnesstrax.build;
kifu-gtk = cargo_nix.workspaceMembers.kifu-gtk.build;
all = pkgs.symlinkJoin { all = pkgs.symlinkJoin {
name = "all"; name = "all";
@ -91,6 +93,7 @@
dashboard dashboard
file-service file-service
fitnesstrax fitnesstrax
kifu-gtk
]; ];
}; };

View File

@ -1,6 +0,0 @@
SOURCES = $(shell find ../core -name "*.rs")
dist/index.ts: $(SOURCES)
mkdir -p dist
typeshare ../core --lang=typescript --output-file=dist/index.ts

View File

@ -1,13 +0,0 @@
{
"name": "core-types",
"version": "0.0.1",
"description": "",
"types": "dist/index.ts",
"main": "dist/index.ts",
"scripts": {
"build": "make",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>",
"license": "GPL-3.0-or-later"
}

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-std = { version = "1" }
chrono = { version = "0.4" } chrono = { version = "0.4" }
config = { path = "../../config" } config = { path = "../../config" }
config-derive = { path = "../../config-derive" } config-derive = { path = "../../config-derive" }
@ -14,7 +15,6 @@ grid = { version = "0.9" }
serde_json = { version = "1" } serde_json = { version = "1" }
serde = { version = "1", features = [ "derive" ] } serde = { version = "1", features = [ "derive" ] }
thiserror = { version = "1" } thiserror = { version = "1" }
typeshare = { version = "1" }
[dev-dependencies] [dev-dependencies]
cool_asserts = { version = "2" } cool_asserts = { version = "2" }

View File

@ -1,17 +1,20 @@
use crate::{ use crate::{
database::Database,
types::{AppState, Config, ConfigOption, DatabasePath, GameState, Player, Rank}, types::{AppState, Config, ConfigOption, DatabasePath, GameState, Player, Rank},
ui::{configuration, home, playing_field, ConfigurationView, HomeView, PlayingFieldView}, ui::{configuration, home, playing_field, ConfigurationView, HomeView, PlayingFieldView},
}; };
use async_std::channel::{Receiver, Sender};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
path::PathBuf, path::PathBuf,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use typeshare::typeshare;
pub trait Observable<T> {
fn subscribe(&self) -> Receiver<T>;
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum CoreRequest { pub enum CoreRequest {
ChangeSetting(ChangeSettingRequest), ChangeSetting(ChangeSettingRequest),
CreateGame(CreateGameRequest), CreateGame(CreateGameRequest),
@ -23,34 +26,28 @@ pub enum CoreRequest {
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum ChangeSettingRequest { pub enum ChangeSettingRequest {
LibraryPath(String), LibraryPath(String),
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct PlayStoneRequest { pub struct PlayStoneRequest {
pub column: u8, pub column: u8,
pub row: u8, pub row: u8,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct CreateGameRequest { pub struct CreateGameRequest {
pub black_player: PlayerInfoRequest, pub black_player: PlayerInfoRequest,
pub white_player: PlayerInfoRequest, pub white_player: PlayerInfoRequest,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub enum PlayerInfoRequest { pub enum PlayerInfoRequest {
Hotseat(HotseatPlayerRequest), Hotseat(HotseatPlayerRequest),
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct HotseatPlayerRequest { pub struct HotseatPlayerRequest {
pub name: String, pub name: String,
pub rank: Option<String>, pub rank: Option<String>,
@ -66,8 +63,6 @@ impl From<HotseatPlayerRequest> for Player {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum CoreResponse { pub enum CoreResponse {
ConfigurationView(ConfigurationView), ConfigurationView(ConfigurationView),
HomeView(HomeView), HomeView(HomeView),
@ -75,37 +70,48 @@ pub enum CoreResponse {
UpdatedConfigurationView(ConfigurationView), UpdatedConfigurationView(ConfigurationView),
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CoreApp { pub enum CoreNotification {
config: Arc<RwLock<Config>>, BoardUpdated,
state: Arc<RwLock<AppState>>,
} }
impl CoreApp { #[derive(Clone, Debug)]
pub fn new(config_path: std::path::PathBuf) -> Self { pub struct Core {
let config = Config::from_path(config_path).expect("configuration to open"); // config: Arc<RwLock<Config>>,
// state: Arc<RwLock<AppState>>,
database: Arc<RwLock<Option<Database>>>,
subscribers: Arc<RwLock<Vec<Sender<CoreNotification>>>>,
}
let db_path: DatabasePath = config.get().unwrap(); impl Core {
let state = Arc::new(RwLock::new(AppState::new(db_path))); pub fn new(_config: Config) -> Self {
// let config = Config::from_path(config_path).expect("configuration to open");
// let state = Arc::new(RwLock::new(AppState::new(db_path)));
Self { Self {
config: Arc::new(RwLock::new(config)), // config: Arc::new(RwLock::new(config)),
state, // state,
database: Arc::new(RwLock::new(None)),
subscribers: Arc::new(RwLock::new(vec![])),
} }
} }
/*
pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse { pub async fn dispatch(&self, request: CoreRequest) -> CoreResponse {
match request { match request {
CoreRequest::ChangeSetting(request) => match request { CoreRequest::ChangeSetting(request) => match request {
ChangeSettingRequest::LibraryPath(path) => { ChangeSettingRequest::LibraryPath(path) => {
let mut config = self.config.write().unwrap(); // let mut config = self.config.write().unwrap();
config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from( // config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from(
path, // path,
)))); // ))));
CoreResponse::UpdatedConfigurationView(configuration(&config)) // CoreResponse::UpdatedConfigurationView(configuration(&config))
unimplemented!()
} }
}, },
CoreRequest::CreateGame(create_request) => { CoreRequest::CreateGame(create_request) => {
/*
let mut app_state = self.state.write().unwrap(); let mut app_state = self.state.write().unwrap();
let white_player = { let white_player = {
match create_request.white_player { match create_request.white_player {
@ -124,30 +130,48 @@ impl CoreApp {
}); });
let game_state = app_state.game.as_ref().unwrap(); let game_state = app_state.game.as_ref().unwrap();
CoreResponse::PlayingFieldView(playing_field(game_state)) CoreResponse::PlayingFieldView(playing_field(game_state))
*/
unimplemented!()
} }
CoreRequest::Home => { CoreRequest::Home => {
CoreResponse::HomeView(home(self.state.read().unwrap().database.all_games())) // CoreResponse::HomeView(home(self.state.read().unwrap().database.all_games()))
unimplemented!()
} }
CoreRequest::OpenConfiguration => { CoreRequest::OpenConfiguration => {
CoreResponse::ConfigurationView(configuration(&self.config.read().unwrap())) // CoreResponse::ConfigurationView(configuration(&self.config.read().unwrap()))
unimplemented!()
} }
CoreRequest::PlayingField => { CoreRequest::PlayingField => {
let app_state = self.state.read().unwrap(); // let app_state = self.state.read().unwrap();
let game = app_state.game.as_ref().unwrap(); // let game = app_state.game.as_ref().unwrap();
CoreResponse::PlayingFieldView(playing_field(game)) // CoreResponse::PlayingFieldView(playing_field(game))
unimplemented!()
} }
CoreRequest::PlayStone(request) => { CoreRequest::PlayStone(request) => {
let mut app_state = self.state.write().unwrap(); // let mut app_state = self.state.write().unwrap();
app_state.place_stone(request); // app_state.place_stone(request);
let game = app_state.game.as_ref().unwrap(); // let game = app_state.game.as_ref().unwrap();
CoreResponse::PlayingFieldView(playing_field(game)) // CoreResponse::PlayingFieldView(playing_field(game))
unimplemented!()
} }
CoreRequest::StartGame => { CoreRequest::StartGame => {
unimplemented!() unimplemented!()
} }
} }
} }
*/
pub async fn run(&self) {} // pub async fn run(&self) {}
}
impl Observable<CoreNotification> for Core {
fn subscribe(&self) -> Receiver<CoreNotification> {
let mut subscribers = self.subscribers.write().unwrap();
let (sender, receiver) = async_std::channel::unbounded();
subscribers.push(sender);
receiver
}
} }

View File

@ -2,12 +2,12 @@ use crate::{BoardError, Color, Size};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct Board { pub struct Goban {
pub size: Size, pub size: Size,
pub groups: Vec<Group>, pub groups: Vec<Group>,
} }
impl std::fmt::Display for Board { impl std::fmt::Display for Goban {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(f, " ")?; write!(f, " ")?;
// for c in 'A'..'U' { // for c in 'A'..'U' {
@ -31,7 +31,7 @@ impl std::fmt::Display for Board {
} }
} }
impl PartialEq for Board { impl PartialEq for Goban {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
if self.size != other.size { if self.size != other.size {
return false; return false;
@ -51,7 +51,7 @@ impl PartialEq for Board {
} }
} }
impl Board { impl Goban {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
size: Size { size: Size {
@ -77,7 +77,7 @@ pub struct Coordinate {
pub row: u8, pub row: u8,
} }
impl Board { impl Goban {
pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> { pub fn place_stone(mut self, coordinate: Coordinate, color: Color) -> Result<Self, BoardError> {
if self.stone(&coordinate).is_some() { if self.stone(&coordinate).is_some() {
return Err(BoardError::InvalidPosition); return Err(BoardError::InvalidPosition);
@ -224,8 +224,8 @@ mod test {
* A stone placed in a suicidal position is legal if it captures other stones first. * A stone placed in a suicidal position is legal if it captures other stones first.
*/ */
fn with_example_board(test: impl FnOnce(Board)) { fn with_example_board(test: impl FnOnce(Goban)) {
let board = Board::from_coordinates( let board = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 3, row: 3 }, Color::White), (Coordinate { column: 3, row: 3 }, Color::White),
(Coordinate { column: 3, row: 4 }, Color::White), (Coordinate { column: 3, row: 4 }, Color::White),
@ -284,7 +284,7 @@ mod test {
#[test] #[test]
fn it_gets_adjacencies_for_coordinate() { fn it_gets_adjacencies_for_coordinate() {
let board = Board::new(); let board = Goban::new();
for column in 0..19 { for column in 0..19 {
for row in 0..19 { for row in 0..19 {
for coordinate in board.adjacencies(&Coordinate { column, row }) { for coordinate in board.adjacencies(&Coordinate { column, row }) {
@ -302,7 +302,7 @@ mod test {
#[test] #[test]
fn it_counts_individual_liberties() { fn it_counts_individual_liberties() {
let board = Board::from_coordinates( let board = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 3, row: 3 }, Color::White), (Coordinate { column: 3, row: 3 }, Color::White),
(Coordinate { column: 0, row: 3 }, Color::White), (Coordinate { column: 0, row: 3 }, Color::White),
@ -357,7 +357,7 @@ mod test {
#[test] #[test]
fn stones_share_liberties() { fn stones_share_liberties() {
with_example_board(|board: Board| { with_example_board(|board: Goban| {
let test_cases = vec![ let test_cases = vec![
( (
board.clone(), board.clone(),
@ -567,11 +567,11 @@ mod test {
#[test] #[test]
fn validate_group_comparisons() { fn validate_group_comparisons() {
{ {
let b1 = Board::from_coordinates( let b1 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(), vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
) )
.unwrap(); .unwrap();
let b2 = Board::from_coordinates( let b2 = Goban::from_coordinates(
vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(), vec![(Coordinate { column: 7, row: 9 }, Color::White)].into_iter(),
) )
.unwrap(); .unwrap();
@ -580,7 +580,7 @@ mod test {
} }
{ {
let b1 = Board::from_coordinates( let b1 = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 7, row: 9 }, Color::White), (Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 10 }, Color::White), (Coordinate { column: 8, row: 10 }, Color::White),
@ -588,7 +588,7 @@ mod test {
.into_iter(), .into_iter(),
) )
.unwrap(); .unwrap();
let b2 = Board::from_coordinates( let b2 = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 8, row: 10 }, Color::White), (Coordinate { column: 8, row: 10 }, Color::White),
(Coordinate { column: 7, row: 9 }, Color::White), (Coordinate { column: 7, row: 9 }, Color::White),
@ -603,7 +603,7 @@ mod test {
#[test] #[test]
fn two_boards_can_be_compared() { fn two_boards_can_be_compared() {
let board = Board::from_coordinates( let board = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 7, row: 9 }, Color::White), (Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 8 }, Color::White), (Coordinate { column: 8, row: 8 }, Color::White),

View File

@ -2,8 +2,8 @@ extern crate config_derive;
mod api; mod api;
pub use api::{ pub use api::{
ChangeSettingRequest, CoreApp, CoreRequest, CoreResponse, CreateGameRequest, ChangeSettingRequest, Core, CoreNotification, CoreRequest, CoreResponse, CreateGameRequest,
HotseatPlayerRequest, PlayerInfoRequest, HotseatPlayerRequest, Observable, PlayerInfoRequest,
}; };
mod board; mod board;
@ -12,6 +12,6 @@ pub use board::*;
mod database; mod database;
mod types; mod types;
pub use types::{BoardError, Color, Rank, Size}; pub use types::{BoardError, Color, Config, ConfigOption, DatabasePath, Player, Rank, Size};
pub mod ui; pub mod ui;

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
api::PlayStoneRequest, api::PlayStoneRequest,
board::{Board, Coordinate}, board::{Coordinate, Goban},
database::Database, database::Database,
}; };
use config::define_config; use config::define_config;
@ -8,7 +8,6 @@ use config_derive::ConfigOption;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{path::PathBuf, time::Duration}; use std::{path::PathBuf, time::Duration};
use thiserror::Error; use thiserror::Error;
use typeshare::typeshare;
define_config! { define_config! {
DatabasePath(DatabasePath), DatabasePath(DatabasePath),
@ -25,6 +24,12 @@ impl std::ops::Deref for DatabasePath {
} }
} }
impl From<String> for DatabasePath {
fn from(s: String) -> Self {
Self(PathBuf::from(s))
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)]
pub struct Me(Player); pub struct Me(Player);
@ -46,14 +51,12 @@ pub enum BoardError {
} }
#[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Hash, Eq, Serialize, Deserialize)]
#[typeshare]
pub enum Color { pub enum Color {
Black, Black,
White, White,
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub struct Size { pub struct Size {
pub width: u8, pub width: u8,
pub height: u8, pub height: u8,
@ -93,7 +96,6 @@ impl AppState {
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[typeshare]
pub enum Rank { pub enum Rank {
Kyu(u8), Kyu(u8),
Dan(u8), Dan(u8),
@ -126,8 +128,8 @@ pub struct Player {
#[derive(Debug)] #[derive(Debug)]
pub struct GameState { pub struct GameState {
pub board: Board, pub board: Goban,
pub past_positions: Vec<Board>, pub past_positions: Vec<Goban>,
pub conversation: Vec<String>, pub conversation: Vec<String>,
pub current_player: Color, pub current_player: Color,
@ -142,7 +144,7 @@ pub struct GameState {
impl Default for GameState { impl Default for GameState {
fn default() -> Self { fn default() -> Self {
Self { Self {
board: Board::new(), board: Goban::new(),
past_positions: vec![], past_positions: vec![],
conversation: vec![], conversation: vec![],
current_player: Color::Black, current_player: Color::Black,
@ -194,7 +196,7 @@ mod test {
#[test] #[test]
fn current_player_remains_the_same_after_self_capture() { fn current_player_remains_the_same_after_self_capture() {
let mut state = GameState::default(); let mut state = GameState::default();
state.board = Board::from_coordinates( state.board = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 17, row: 0 }, Color::White), (Coordinate { column: 17, row: 0 }, Color::White),
(Coordinate { column: 17, row: 1 }, Color::White), (Coordinate { column: 17, row: 1 }, Color::White),
@ -215,7 +217,7 @@ mod test {
#[test] #[test]
fn ko_rules_are_enforced() { fn ko_rules_are_enforced() {
let mut state = GameState::default(); let mut state = GameState::default();
state.board = Board::from_coordinates( state.board = Goban::from_coordinates(
vec![ vec![
(Coordinate { column: 7, row: 9 }, Color::White), (Coordinate { column: 7, row: 9 }, Color::White),
(Coordinate { column: 8, row: 8 }, Color::White), (Coordinate { column: 8, row: 8 }, Color::White),

View File

@ -3,10 +3,8 @@ use crate::{
ui::Field, ui::Field,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct ConfigurationView { pub struct ConfigurationView {
pub library: Field<()>, pub library: Field<()>,
} }

View File

@ -1,9 +1,7 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sgf::{Game, GameResult, Win}; use sgf::{Game, GameResult, Win};
use typeshare::typeshare;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
#[typeshare]
pub struct GamePreviewElement { pub struct GamePreviewElement {
pub date: String, pub date: String,
pub name: String, pub name: String,

View File

@ -1,8 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Menu<T: PartialEq + Eq> { pub struct Menu<T: PartialEq + Eq> {
current: Option<T>, current: Option<T>,
options: Vec<T>, options: Vec<T>,

View File

@ -1,11 +1,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare;
pub mod game_preview; pub mod game_preview;
pub mod menu; pub mod menu;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Action<A> { pub struct Action<A> {
pub id: String, pub id: String,
pub label: String, pub label: String,
@ -13,7 +11,6 @@ pub struct Action<A> {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Toggle<A> { pub struct Toggle<A> {
pub id: String, pub id: String,
pub label: String, pub label: String,
@ -22,7 +19,6 @@ pub struct Toggle<A> {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct Field<A> { pub struct Field<A> {
pub id: String, pub id: String,
pub label: String, pub label: String,

View File

@ -1,7 +1,6 @@
use crate::ui::{Action, GamePreviewElement}; use crate::ui::{Action, GamePreviewElement};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sgf::Game; use sgf::Game;
use typeshare::typeshare;
fn rank_strings() -> Vec<String> { fn rank_strings() -> Vec<String> {
vec![ vec![
@ -18,7 +17,6 @@ fn rank_strings() -> Vec<String> {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub enum PlayerElement { pub enum PlayerElement {
Hotseat(HotseatPlayerElement), Hotseat(HotseatPlayerElement),
// Remote(RemotePlayerElement), // Remote(RemotePlayerElement),
@ -32,7 +30,6 @@ impl Default for PlayerElement {
} }
#[derive(Clone, Debug, Serialize, Deserialize, Default)] #[derive(Clone, Debug, Serialize, Deserialize, Default)]
#[typeshare]
pub struct HotseatPlayerElement { pub struct HotseatPlayerElement {
pub placeholder: Option<String>, pub placeholder: Option<String>,
pub default_rank: Option<String>, pub default_rank: Option<String>,
@ -40,15 +37,12 @@ pub struct HotseatPlayerElement {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct RemotePlayerElement {} pub struct RemotePlayerElement {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct BotPlayerElement {} pub struct BotPlayerElement {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct HomeView { pub struct HomeView {
pub black_player: PlayerElement, pub black_player: PlayerElement,
pub white_player: PlayerElement, pub white_player: PlayerElement,

View File

@ -3,10 +3,8 @@ use crate::{
ui::types, ui::types,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct PlayingFieldView { pub struct PlayingFieldView {
pub board: types::BoardElement, pub board: types::BoardElement,
pub player_card_black: types::PlayerCardElement, pub player_card_black: types::PlayerCardElement,

View File

@ -1,20 +1,17 @@
use crate::types::{Color, Size}; use crate::types::{Color, Size};
use crate::{ use crate::{
api::{CoreRequest, PlayStoneRequest}, api::{CoreRequest, PlayStoneRequest},
Board, Coordinate, Coordinate, Goban,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use typeshare::typeshare;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[typeshare]
pub struct Jitter { pub struct Jitter {
pub x: i8, pub x: i8,
pub y: i8, pub y: i8,
} }
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[typeshare]
pub struct StoneElement { pub struct StoneElement {
pub color: Color, pub color: Color,
pub jitter: Jitter, pub jitter: Jitter,
@ -32,8 +29,6 @@ impl StoneElement {
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[typeshare]
#[serde(tag = "type", content = "content")]
pub enum IntersectionElement { pub enum IntersectionElement {
Unplayable, Unplayable,
Empty(CoreRequest), Empty(CoreRequest),
@ -41,7 +36,6 @@ pub enum IntersectionElement {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct BoardElement { pub struct BoardElement {
pub size: Size, pub size: Size,
pub spaces: Vec<IntersectionElement>, pub spaces: Vec<IntersectionElement>,
@ -80,8 +74,8 @@ impl BoardElement {
} }
} }
impl From<&Board> for BoardElement { impl From<&Goban> for BoardElement {
fn from(board: &Board) -> Self { fn from(board: &Goban) -> Self {
let spaces: Vec<IntersectionElement> = let spaces: Vec<IntersectionElement> =
(0..board.size.height) (0..board.size.height)
.map(|row| { .map(|row| {
@ -114,7 +108,6 @@ impl Default for BoardElement {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct PlayerCardElement { pub struct PlayerCardElement {
pub color: Color, pub color: Color,
pub name: String, pub name: String,
@ -123,11 +116,9 @@ pub struct PlayerCardElement {
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct ChatElement { pub struct ChatElement {
pub messages: Vec<String>, pub messages: Vec<String>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[typeshare]
pub struct TextFieldElement {} pub struct TextFieldElement {}

View File

@ -1,47 +0,0 @@
<!doctype html>
<html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="styles.css" />
<body>
<div class="menu">
<ul>
<li> <a href="index.html">Index</a> </li>
<li> <a href="playing.html">Game Board for playing</a> </li>
<li> <a href="database.html">Game database</a> </li>
<li> <a href="analysis.html">Game board for analysis</a> </li>
<li> Connection management </li>
<li> Challenge list </li>
<li> Friends list </li>
<li> Open challenges </li>
</ul>
</div>
<div class="widget">
<div> US Go Congress 2023, DDK Tournament, Round 1, Board 25 </div>
<div class="game-analysis">
<div class="game-analysis__board">
<img src="game-screen.jpg" />
</div>
<div class="game-analysis__tree">
<div>
<img src="game-tree.jpg" />
</div>
<p> Shoring up the wall. Any chance to capture that group was lost a couple of moves back and Savanni knows it. </p>
</div>
</div>
<div class="game-info">
<div class="player-info">
<div> Savanni (10k) </div>
</div>
<div class="player-info">
<div> Opal (10k) </div>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,118 +0,0 @@
<!doctype html>
<html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="styles.css" />
<body>
<div class="menu">
<ul>
<li> <a href="index.html">Index</a> </li>
<li> <a href="playing.html">Game Board for playing</a> </li>
<li> <a href="database.html">Game database</a> </li>
<li> Game board for analysis </li>
<li> Connection management </li>
<li> Challenge list </li>
<li> Friends list </li>
<li> Open challenges </li>
</ul>
</div>
<div class="widget">
<div class="game-filter">
<input class="game-filter__term" placeholder="date" />
<input class="game-filter__term" placeholder="player name" />
<input class="game-filter__term" placeholder="minimum strength" />
<input class="game-filter__term" placeholder="maximum strength" />
</div>
<div class="game-database">
<div class="game-entry">
<a href="analysis.html"><img class="game-entry__icon" src="game-thumbnail.jpg" /></a>
<div class="game-entry__info-card">
<div class="game-entry__info-row">
2016-06-15
</div>
<div class="game-entry__info-row">
(B) Alpha Go (w)
</div>
<div class="game-entry__info-row">
(W) Lee Sedol
</div>
</div>
</div>
<div class="game-entry">
<img class="game-entry__icon" src="game-thumbnail.jpg" />
<div class="game-entry__info-card">
<div class="game-entry__info-row">
2016-06-15
</div>
<div class="game-entry__info-row">
(B) Alpha Go (w)
</div>
<div class="game-entry__info-row">
(W) Lee Sedol
</div>
</div>
</div>
<div class="game-entry">
<img class="game-entry__icon" src="game-thumbnail.jpg" />
<div class="game-entry__info-card">
<div class="game-entry__info-row">
2016-06-15
</div>
<div class="game-entry__info-row">
(B) Alpha Go (w)
</div>
<div class="game-entry__info-row">
(W) Lee Sedol
</div>
</div>
</div>
<div class="game-entry">
<img class="game-entry__icon" src="game-thumbnail.jpg" />
<div class="game-entry__info-card">
<div class="game-entry__info-row">
2016-06-15
</div>
<div class="game-entry__info-row">
(B) Alpha Go (w)
</div>
<div class="game-entry__info-row">
(W) Lee Sedol
</div>
</div>
</div>
<div class="game-entry">
<img class="game-entry__icon" src="game-thumbnail.jpg" />
<div class="game-entry__info-card">
<div class="game-entry__info-row">
2016-06-15
</div>
<div class="game-entry__info-row">
(B) Alpha Go (w)
</div>
<div class="game-entry__info-row">
(W) Lee Sedol
</div>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -1,24 +0,0 @@
<!doctype html>
<html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="styles.css" />
<body>
<div class="menu">
<ul>
<li> <a href="playing.html">Game Board for playing</a> </li>
<li> <a href="database.html">Game database</a> </li>
<li> <a href="analysis.html">Game board for analysis</a> </li>
<li> Connection management </li>
<li> Challenge list </li>
<li> Friends list </li>
<li> Open challenges </li>
</ul>
</div>
<div class="widget">
</div>
</body>
</html>

View File

@ -1,49 +0,0 @@
<!doctype html>
<html>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="styles.css" />
<body>
<div class="menu">
<ul>
<li> <a href="index.html">Index</a> </li>
<li> <a href="playing.html">Game Board for playing</a> </li>
<li> Game database </li>
<li> Game board for analysis </li>
<li> Connection management </li>
<li> Challenge list </li>
<li> Friends list </li>
<li> Open challenges </li>
</ul>
</div>
<div class="widget">
<img src="game-screen.jpg" />
<div class="game-info">
<div class="player-info">
<div> Savanni (10k) </div> <div> 24:53 </div>
</div>
<div class="player-info">
<div> Opal (10k) </div> <div> 25:00 </div>
</div>
</div>
<div class="chat">
<div>
<textarea cols="80" rows="25">
[22:05] Savanni: oops
[22:06] Opal: you know I'll take advantage of that, right?
</textarea>
</div>
<div>
<input size="60"></input>
</div>
</div>
</div>
</body>
</html>

View File

@ -1,270 +0,0 @@
(;FF[4]
CA[UTF-8]
GM[1]
DT[2022-02-17]
GN[Amika Matĉo]
PC[https://online-go.com/review/823151]
PB[mearss25]
PW[savanni.dgerinel]
BR[16k]
WR[10k]
TM[1500]OT[15 fischer]
RE[W+46.5]
SZ[19]
KM[7.5]
RU[AGA]
;B[pd]SQ[ph]SQ[oi]LB[pi:B]LB[qi:A]SQ[pj]LB[qj:C]
;W[dp]
;B[dd]
;W[qp]
;B[ch]
;W[np]
;B[cl]
;W[qf]
;B[qh]
(;W[of]
(;B[nc]
;W[oi]
;B[pj]
;W[md]
;B[nk]
;W[jd]
;B[gc]
(;W[jj]
;B[mi]
;W[mj]
;B[lj]
;W[nj]
;B[ok]
;W[li]
;B[ki]
;W[lh]
;B[kj]
;W[kh]
;B[mk]
(;W[pi]
;B[qi]
;W[ji]
;B[ni]
;W[oj]
;B[nh]
;W[mh]
;B[oh]
;W[ng]
;B[ig]
;W[qc]
;B[pf]
;W[pg]
;B[ph]
;W[pe]
;B[qd]
;W[od]
;B[oc]
;W[pc]
;B[nd]
;W[oe]
;B[og]
;W[rd]
;B[mc]
;W[mf]
;B[ld]
;W[me]
;B[jh]
;W[cc]
;B[cd]
;W[dc]
;B[ec]
;W[eb]
;B[fd]
;W[fb]
;B[gb]
;W[bd]
;B[be]
;W[bc]
;B[ad]
;W[ce]
;B[de]
;W[ac]
;B[cf]
;W[ae]
;B[fa]
;W[ea]
;B[ga]
;W[ca]
;B[cq]
;W[dq]
;B[cp]
;W[cn]
;B[bn]
;W[do]
;B[cm]
;W[cr]
;B[co]
;W[dn]
;B[dm]
;W[fn]
;B[fm]
;W[hn]
;B[il]
;W[jp]
;B[hq]
;W[jq]
;B[er]
;W[dr]
;B[fq]
;W[ir]
;B[io]
;W[jm]
;B[kn]
;W[jn]
;B[ho]
;W[gn]
;B[hr]
;W[gp]
;B[gr]
;W[es]
;B[fs]
;W[ds]
;B[eq]
;W[hs]
;B[go]
;W[fp]
;B[en]
;W[eo]
;B[br]
;W[fo]
;B[mo]
;W[mp]
;B[lp]
;W[lq]
;B[ko]
;W[jo]
;B[km]
;W[ql]
;B[pl]
;W[pm]
;B[om]
;W[pn]
;B[on]
;W[oo]
;B[no]
;W[op]
;B[qk]
;W[rl]
;B[rk]
;W[rn]
;B[qg]
;W[rf]
;B[rg]
;W[kf]
;B[if]
;W[kc]
;B[lb]
;W[lc]
;B[kb]
;W[jb]
;B[ma]
;W[ob]
;B[nb]
;W[pa]
;B[ka]
;W[je]
;B[ie]
;W[id]
;B[hd]
;W[jl]
;B[ii]
;W[ik]
;B[hk]
;W[hl]
;B[gl]
;W[hm]
;B[ij]
;W[jk]
;B[gm]
;W[bf]
;B[bg]
;W[ce]
;B[cg]
;W[be]
;B[fh]
;W[jg]
;B[ih]
;W[em]
;B[el]
;W[gk]
;B[hj]
;W[fl]
;B[gj]
;W[fk]
;B[ek]
;W[kp]
;B[fj]
;W[sl]
;B[sk]
;W[kk]
;B[lk]
;W[lo]
;B[ln]
;W[lp]
;B[ll]
;W[sf]
;B[sg]
;W[ib]
;B[ic]
;W[jc]
;B[hc]
;W[is]
;B[bs]
;W[fc]
;B[ed]
;W[ne]
;B[na]
;W[oa]
;B[qa]
;W[pb]
;B[rc]
;W[qe]
;B[ag]
;W[jf]
;B[pf]
;W[kl]
;B[mm]
;W[pg]
;B[]
;W[pf]
;B[]
;W[]
)(;W[lk]
;B[kk]
;W[ll]
;B[kl]
;W[km]
;B[jm]
;W[kn]
))(;W[mj]
))(;B[nd]
;W[oh]
;B[qk]
))(;W[qc]
(;B[qd]
;W[pc]
;B[od]
;W[rd]
;B[re]
;W[rc]
;B[rf]
;W[nc]
;B[me]
)(;B[pc]TR[jc]
;W[qd]
;B[pe]
(;W[rf]
;B[og]
)(;W[pf]
;B[of]
;W[oe]
(;B[ne]
)(;B[nf]
)))))

View File

@ -1,44 +0,0 @@
body {
background: hsl(0 0% 85%);
display: flex;
}
.menu {
border-right: 1px solid black;
padding: 8px;
}
.game-info {
display: flex;
justify-content: space-between;
}
.widget {
}
.game-database {
display: flex;
flex-wrap: wrap;
}
.game-entry {
display: flex;
width: 500px;
margin: 8px;
}
.game-entry__icon {
width: 300px;
}
.game-analysis {
display: flex;
}
.game-analysis__board {
flex-shrink: 1;
}
.game-analysis__tree {
flex-shrink: 0;
}

564
kifu/ffi/wasm/Cargo.lock generated
View File

@ -1,564 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bumpalo"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cxx"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn 2.0.15",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
[[package]]
name = "cxxbridge-macro"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "grid"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0634107a3a005070dd73e27e74ecb691a94e9e5ba7829f434db7fbf73a6b5c47"
dependencies = [
"no-std-compat",
]
[[package]]
name = "iana-time-zone"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]]
name = "itoa"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kifu-core"
version = "0.1.0"
dependencies = [
"grid",
"serde",
"thiserror",
"typeshare",
]
[[package]]
name = "kifu-wasm"
version = "0.1.0"
dependencies = [
"kifu-core",
"serde",
"serde-wasm-bindgen",
"wasm-bindgen",
"wasm-bindgen-futures",
]
[[package]]
name = "libc"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "no-std-compat"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "scratch"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "serde"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-wasm-bindgen"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
dependencies = [
"js-sys",
"serde",
"wasm-bindgen",
]
[[package]]
name = "serde_derive"
version = "1.0.160"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "serde_json"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "typeshare"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f44d1a2f454cb35fbe05b218c410792697e76bd868f48d3a418f2cd1a7d527d6"
dependencies = [
"chrono",
"serde",
"serde_json",
"typeshare-annotation",
]
[[package]]
name = "typeshare-annotation"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc670d0e358428857cc3b4bf504c691e572fccaec9542ff09212d3f13d74b7a9"
dependencies = [
"quote",
"syn 1.0.109",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "wasm-bindgen"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "web-sys"
version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View File

@ -1,19 +0,0 @@
[package]
name = "kifu-wasm"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
kifu-core = { path = "../../core" }
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.5.0"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "*"
[package.metadata.wasm-pack.profile.release]
wasm-opt = false

View File

@ -1,3 +0,0 @@
all:
wasm-pack build --target web

View File

@ -1,26 +0,0 @@
use kifu_core::{CoreRequest, CoreResponse};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
pub fn log(s: &str);
}
#[wasm_bindgen]
#[derive(Debug)]
pub struct CoreApp(kifu_core::CoreApp);
#[wasm_bindgen]
impl CoreApp {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self(kifu_core::CoreApp::new())
}
#[wasm_bindgen]
pub async fn dispatch(&self, param: &JsValue) -> JsValue {
let request: CoreRequest = serde_wasm_bindgen::from_value(param.clone()).unwrap();
serde_wasm_bindgen::to_value(&self.0.dispatch(request).await).unwrap()
}
}

View File

@ -10,6 +10,8 @@ screenplay = []
[dependencies] [dependencies]
adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] } adw = { version = "0.5", package = "libadwaita", features = [ "v1_2" ] }
async-channel = { version = "2" }
async-std = { version = "1" }
cairo-rs = { version = "0.18" } cairo-rs = { version = "0.18" }
gio = { version = "0.18" } gio = { version = "0.18" }
glib = { version = "0.18" } glib = { version = "0.18" }

View File

@ -1,7 +1,7 @@
fn main() { fn main() {
glib_build_tools::compile_resources( glib_build_tools::compile_resources(
&["resources"], &["resources"],
"resources/gresources.xml", "gresources.xml",
"com.luminescent-dreams.kifu-gtk.gresource", "com.luminescent-dreams.kifu-gtk.gresource",
); );
} }

17
kifu/gtk/override.nix Normal file
View File

@ -0,0 +1,17 @@
{ gtkNativeInputs }:
attrs:
let
gsettingsDir = "${attrs.crateName}-${attrs.version}";
in {
nativeBuildInputs = gtkNativeInputs;
postInstall = ''
install -Dt $out/share/applications resources/kifu.desktop
install -Dt $out/gsettings-schemas/${gsettingsDir}/glib-2.0/schemas resources/com.luminescent-dreams.kifu.gschema.xml
glib-compile-schemas $out/gsettings-schemas/${gsettingsDir}/glib-2.0/schemas
'';
# preFixup = ''
# gappsWrapperArgs+=(
# --prefix XDG_DATA_DIRS : $out/gsettings-schemas/${gsettingsDir}
# )
# '';
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<schemalist>
<schema id="com.luminescent-dreams.kifu-gtk.dev" path="/com/luminescent-dreams/kifu-gtk/dev/">
<key name="database-path" type="s">
<default>""</default>
<summary>Path to the directory of games</summary>
</key>
<key name="language" type="s">
<default>""</default>
<summary>Language override, use system settings if empty</summary>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<schemalist>
<schema id="com.luminescent-dreams.kifu-gtk" path="/com/luminescent-dreams/kifu-gtk/">
<key name="database-path" type="s">
<default>""</default>
<summary>Path to the directory of games</summary>
</key>
<key name="language" type="s">
<default>""</default>
<summary>Language override, use system settings if empty</summary>
</key>
</schema>
</schemalist>

View File

@ -0,0 +1,5 @@
[Desktop Entry]
Version=0.2
Type=Application
Name=Kifu
Exec=kifu

View File

@ -1,22 +1,30 @@
pub mod ui; pub mod ui;
use kifu_core::{CoreApp, CoreRequest, CoreResponse};
use std::sync::Arc; mod view_models;
mod views;
use async_std::task::yield_now;
use kifu_core::{Core, CoreRequest, CoreResponse, Observable};
use std::{rc::Rc, sync::Arc};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
#[derive(Clone)] #[derive(Clone)]
pub struct CoreApi { pub struct CoreApi {
pub gtk_tx: gtk::glib::Sender<CoreResponse>,
pub rt: Arc<Runtime>, pub rt: Arc<Runtime>,
pub core: CoreApp, pub core: Core,
} }
impl CoreApi { impl CoreApi {
pub fn dispatch(&self, request: CoreRequest) { pub fn dispatch(&self, request: CoreRequest) {
self.rt.spawn({ /*
spawn({
/*
let gtk_tx = self.gtk_tx.clone(); let gtk_tx = self.gtk_tx.clone();
let core = self.core.clone(); let core = self.core.clone();
async move { gtk_tx.send(core.dispatch(request).await) } async move { gtk_tx.send(core.dispatch(request).await) }
*/
}); });
*/
} }
} }
@ -30,3 +38,48 @@ where
println!("[Trace: {}] {:?}", trace_name, end - start); println!("[Trace: {}] {:?}", trace_name, end - start);
result result
} }
/// LocalObserver creates a task on the current thread which watches the specified observer for notifications and calls the handler function with each one.
///
/// The LocalObserver starts a task which listens for notifications during the constructor. When the observer goes out of scope, it will make a point of aborting the task. This combination means that anything which uses the observer can create it, hold on to a reference of it, and then drop it when done, and not have to do anything else with the observer object.
struct LocalObserver<T> {
join_handle: glib::JoinHandle<()>,
handler: Rc<dyn Fn(T)>,
}
impl<T: 'static> LocalObserver<T> {
/// Construct a new LocalObserver and start it running.
///
/// observable -- any object which emits events
/// handler -- a function which can process events
fn new(observable: &dyn Observable<T>, handler: impl Fn(T) + 'static) -> Self {
let listener = observable.subscribe();
let handler = Rc::new(handler);
let join_handle = glib::spawn_future_local({
let handler = handler.clone();
async move {
loop {
match listener.recv().await {
Ok(msg) => handler(msg),
Err(_) => {
// recv only fails if the channel has been closed and no other notifications are pending. This will break out of the loop and terminate the observer.
return;
}
}
yield_now().await;
}
}
});
Self {
join_handle,
handler,
}
}
}
impl<T> Drop for LocalObserver<T> {
fn drop(&mut self) {
// Abort the task when the observer goes out of scope.
self.join_handle.abort();
}
}

View File

@ -1,5 +1,5 @@
use adw::prelude::*; use adw::prelude::*;
use kifu_core::{CoreApp, CoreRequest, CoreResponse}; use kifu_core::{Config, ConfigOption, Core, CoreRequest, CoreResponse, DatabasePath};
use kifu_gtk::{ use kifu_gtk::{
perftrace, perftrace,
ui::{AppWindow, ConfigurationPage, Home, PlayingField}, ui::{AppWindow, ConfigurationPage, Home, PlayingField},
@ -7,6 +7,11 @@ use kifu_gtk::{
}; };
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
const APP_ID_DEV: &str = "com.luminescent-dreams.kifu-gtk.dev";
const APP_ID_PROD: &str = "com.luminescent-dreams.kifu-gtk";
const RESOURCE_BASE_PATH: &str = "/com/luminescent-dreams/kifu-gtk/";
fn handle_response(api: CoreApi, app_window: &AppWindow, message: CoreResponse) { fn handle_response(api: CoreApi, app_window: &AppWindow, message: CoreResponse) {
let playing_field = Arc::new(RwLock::new(None)); let playing_field = Arc::new(RwLock::new(None));
match message { match message {
@ -48,6 +53,17 @@ fn main() {
gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource") gio::resources_register_include!("com.luminescent-dreams.kifu-gtk.gresource")
.expect("Failed to register resources"); .expect("Failed to register resources");
let app_id = if std::env::var_os("ENV") == Some("dev".into()) {
APP_ID_DEV
} else {
APP_ID_PROD
};
let settings = gio::Settings::new(app_id);
let db_path: String = settings.string("database-path").into();
let mut config = Config::new();
config.set(ConfigOption::DatabasePath(db_path.into()));
let runtime = Arc::new( let runtime = Arc::new(
tokio::runtime::Builder::new_multi_thread() tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
@ -55,6 +71,7 @@ fn main() {
.unwrap(), .unwrap(),
); );
/*
let config_path = std::env::var("CONFIG") let config_path = std::env::var("CONFIG")
.map(std::path::PathBuf::from) .map(std::path::PathBuf::from)
.or({ .or({
@ -66,15 +83,18 @@ fn main() {
}) })
}) })
.expect("no config path could be found"); .expect("no config path could be found");
*/
let core = CoreApp::new(config_path); let core = Core::new(config);
/*
let core_handle = runtime.spawn({ let core_handle = runtime.spawn({
let core = core.clone(); let core = core.clone();
async move { async move {
core.run().await; core.run().await;
} }
}); });
*/
let app = adw::Application::builder() let app = adw::Application::builder()
.application_id("com.luminescent-dreams.kifu-gtk") .application_id("com.luminescent-dreams.kifu-gtk")
@ -84,13 +104,9 @@ fn main() {
app.connect_activate({ app.connect_activate({
let runtime = runtime.clone(); let runtime = runtime.clone();
move |app| { move |app| {
let (gtk_tx, gtk_rx) =
gtk::glib::MainContext::channel::<CoreResponse>(gtk::glib::Priority::DEFAULT);
let app_window = AppWindow::new(app); let app_window = AppWindow::new(app);
let api = CoreApi { let api = CoreApi {
gtk_tx,
rt: runtime.clone(), rt: runtime.clone(),
core: core.clone(), core: core.clone(),
}; };
@ -106,6 +122,7 @@ fn main() {
app_window.window.present(); app_window.window.present();
/*
gtk_rx.attach(None, { gtk_rx.attach(None, {
let api = api.clone(); let api = api.clone();
move |message| { move |message| {
@ -115,6 +132,7 @@ fn main() {
glib::ControlFlow::Continue glib::ControlFlow::Continue
} }
}); });
*/
api.dispatch(CoreRequest::Home); api.dispatch(CoreRequest::Home);
} }
@ -123,5 +141,5 @@ fn main() {
println!("running the gtk loop"); println!("running the gtk loop");
app.run(); app.run();
let _ = runtime.block_on(core_handle); /* let _ = runtime.block_on(core_handle); */
} }

View File

@ -0,0 +1,38 @@
/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of Kifu.
Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::LocalObserver;
use kifu_core::{Core, CoreNotification};
pub struct GameReviewViewModel {
core: Core,
notification_observer: LocalObserver<CoreNotification>,
widget: gtk::Box,
}
impl GameReviewViewModel {
fn new(core: Core) -> Self {
let notification_observer = LocalObserver::new(&core, |msg| {
println!("GameReviewViewModel called with message: {:?}", msg)
});
Self {
core,
notification_observer,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
}
}
}

View File

@ -0,0 +1,70 @@
/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of Kifu.
Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
use async_std::{channel::Receiver, task::yield_now};
use kifu_core::{Color, Core, CoreNotification, Goban, Observable, Player};
use std::{cell::RefCell, rc::Rc, time::Duration};
use crate::LocalObserver;
pub struct GameState {
goban: Goban,
white_clock: Duration,
black_clock: Duration,
white_score: f32,
black_score: f32,
current: Color,
}
struct GameViewModelPrivate {
white: Player, /* Maybe this should be PlayerState, instead, combining the player info, current clock, and current captures. */
black: Player,
state: GameState,
}
/// The Game View Model manages the current state of the game. It shows the two player cards, the board, the current capture count, the current player, and it maintains the UI for the clock (bearing in mind that the real clock is managed in the core). This view model should only be created once the details of the game, whether a game in progress or a new game (this view model won't know the difference) is known.
pub struct GameViewModel {
core: Core,
notification_observer: LocalObserver<CoreNotification>,
widget: gtk::Box,
data: Rc<RefCell<GameViewModelPrivate>>,
}
impl GameViewModelPrivate {
fn handle(&mut self, _message: CoreNotification) {}
}
impl GameViewModel {
pub fn new(white: Player, black: Player, game: GameState, core: Core) -> Self {
let data = Rc::new(RefCell::new(GameViewModelPrivate {
white,
black,
state: game,
}));
let notification_observer = LocalObserver::new(&core, |msg| {
println!("GameViewModelHandler called with message: {:?}", msg)
});
Self {
core,
notification_observer,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
data,
}
}
}

View File

@ -0,0 +1,54 @@
/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of Kifu.
Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::LocalObserver;
use kifu_core::{Core, CoreNotification};
/// Home controls the view that the user sees when starting the application if there are no games in progress. It provides a window into the database, showing a list of recently recorded games. It also provides the UI for starting a new game. This will render an empty database view if the user hasn't configured a database yet.
pub struct HomeViewModel {
core: Core,
notification_observer: LocalObserver<CoreNotification>,
widget: gtk::Box,
}
impl HomeViewModel {
fn new(core: Core) -> Self {
let notification_observer = LocalObserver::new(&core, |msg| {
println!("DatabaseViewModelHandler called with message: {:?}", msg)
});
Self {
core,
notification_observer,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
}
}
/// Create a new game with the given parameters.
fn new_game(&self) {
unimplemented!()
}
/// Select a game from the database to show in detail. This will require a transition away from this view model into a different one.
fn select_game(&self) {
unimplemented!()
}
/// Delete a game from the database.
fn delete_game(&self) {
unimplemented!()
}
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of Kifu.
Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
/*
Every view model requires a reference to the app so that it can call functions on the core, and a notification receiver so that it can receive messages from the core.
The view model is primary over the view. It will construct the view, it can make major changes to the view or even swap for another related view. It must listen for all messages from the core, discarding those that aren't relevant to it. It will also convert requests from sync to async.
*/
mod game_view_model;
pub use game_view_model::GameViewModel;
mod game_review_view_model;
pub use game_review_view_model::GameReviewViewModel;
mod home_view_model;
pub use home_view_model::HomeViewModel;
mod settings_view_model;
pub use settings_view_model::SettingsViewModel;

View File

@ -0,0 +1,38 @@
/*
Copyright 2024, Savanni D'Gerinel <savanni@luminescent-dreams.com>
This file is part of Kifu.
Kifu is free software: you can redistribute it and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Kifu is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License along with Kifu. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::LocalObserver;
use kifu_core::{Core, CoreNotification};
pub struct SettingsViewModel {
core: Core,
notification_observer: LocalObserver<CoreNotification>,
widget: gtk::Box,
}
impl SettingsViewModel {
fn new(core: Core) -> Self {
let notification_observer = LocalObserver::new(&core, |msg| {
println!("SettingsViewModel called with message: {:?}", msg)
});
Self {
core,
notification_observer,
widget: gtk::Box::new(gtk::Orientation::Horizontal, 0),
}
}
}

View File

View File

@ -1,16 +0,0 @@
release: kifu-wasm core-types
NODE_ENV=production npm run build
dev: kifu-wasm core-types
npm run build
kifu-wasm:
pushd ../ffi/wasm && make && popd
core-types:
pushd ../core-types && make && popd
server:
npx http-server ./dist

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
{
"name": "kifu-pwa",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"author": "Savanni D'Gerinel <savanni@luminescent-dreams.com>",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@types/lodash": "^4.14.194",
"copy-webpack-plugin": "^11.0.0",
"ts-loader": "^9.4.2",
"typescript": "^5.0.4",
"webpack": "^5.82.0",
"webpack-cli": "^5.1.0"
},
"dependencies": {
"lodash": "^4.17.21"
}
}

View File

@ -1 +0,0 @@
export const assertNever = (_: never) => {};

View File

@ -1,262 +0,0 @@
import { BoardElement, Color, Size, CoreRequest } from "core-types";
import { assertNever } from "../assertNever";
const MARGIN = 20;
const BOARD_WIDTH = 800;
const BOARD_HEIGHT = 800;
type Pixel = { x: number; y: number };
type Coordinate = { column: number; row: number };
export interface GoBoardProps {
board: BoardElement;
onClick: (_: CoreRequest) => void;
}
export class GoBoard {
private board: BoardElement;
private pen: Pen;
private cursorLocation: Coordinate | null;
private backgroundBoard: HTMLCanvasElement;
private currentBoardState: HTMLCanvasElement;
canvas: HTMLCanvasElement;
constructor({ board, onClick }: GoBoardProps) {
this.board = board;
this.canvas = document.createElement("canvas");
this.canvas.classList.add("board");
this.canvas.width = BOARD_WIDTH;
this.canvas.height = BOARD_HEIGHT;
this.pen = new Pen(
this.canvas.width,
this.canvas.height,
MARGIN,
this.board.size.width,
this.board.size.height
);
this.backgroundBoard = document.createElement("canvas");
this.backgroundBoard.width = BOARD_WIDTH;
this.backgroundBoard.height = BOARD_HEIGHT;
this.renderBackgroundBoard();
this.currentBoardState = document.createElement("canvas");
this.currentBoardState.width = BOARD_WIDTH;
this.currentBoardState.height = BOARD_HEIGHT;
this.cursorLocation = null;
this.canvas.onmousemove = (event) => {
const bounds = this.canvas.getBoundingClientRect();
const coordinate = {
x: event.clientX - bounds.x,
y: event.clientY - bounds.y,
};
let address = this.pen.address(coordinate);
if (this.cursorLocation != address) {
this.cursorLocation = this.pen.address(coordinate);
this.renderBoard();
}
};
this.canvas.onclick = (_) => {
if (this.cursorLocation) {
const intersection =
this.board.spaces[boardAddress(this.board.size, this.cursorLocation)];
switch (intersection.type) {
case "Unplayable":
break;
case "Empty":
onClick(intersection.content);
break;
case "Filled":
break;
default:
assertNever(intersection);
}
}
};
}
setBoard(board: BoardElement) {
this.board = board;
this.pen = new Pen(
this.canvas.width,
this.canvas.height,
MARGIN,
this.board.size.width,
this.board.size.height
);
this.renderBoard();
}
renderBoard() {
// Cache:
// - Standard blank board with background
// - Current board state without the ghost stone
const ctx = this.canvas.getContext("2d");
if (!ctx) {
alert("could not get the canvas context");
return null;
}
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
ctx.drawImage(this.backgroundBoard, 0, 0);
let col = 0;
let row = 0;
for (let idx = 0; idx < this.board.spaces.length; idx++) {
const space = this.board.spaces[idx];
switch (space.type) {
case "Filled":
this.pen.stone(ctx, { column: col, row: row }, space.content.color);
break;
default:
break;
}
col = col + 1;
if (col == this.board.size.width) {
col = 0;
row = row + 1;
}
}
if (this.cursorLocation) {
this.pen.ghostStone(ctx, this.cursorLocation, Color.White);
}
}
private renderBackgroundBoard() {
const ctx = this.backgroundBoard.getContext("2d");
if (!ctx) {
alert("could not get the background canvas context");
return;
}
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.beginPath();
for (var col = 0; col < this.board.size.width; col++) {
ctx.moveTo(MARGIN + col * this.pen.hspaceBetween, MARGIN);
ctx.lineTo(
MARGIN + col * this.pen.hspaceBetween,
MARGIN + (this.board.size.height - 1) * this.pen.vspaceBetween
);
}
for (var row = 0; row < this.board.size.height; row++) {
ctx.moveTo(MARGIN, MARGIN + row * this.pen.vspaceBetween);
ctx.lineTo(
MARGIN + (this.board.size.width - 1) * this.pen.hspaceBetween,
MARGIN + row * this.pen.vspaceBetween
);
}
ctx.closePath();
ctx.stroke();
this.pen.starPoint(ctx, { column: 3, row: 3 });
this.pen.starPoint(ctx, { column: 3, row: 9 });
this.pen.starPoint(ctx, { column: 3, row: 15 });
this.pen.starPoint(ctx, { column: 9, row: 3 });
this.pen.starPoint(ctx, { column: 9, row: 9 });
this.pen.starPoint(ctx, { column: 9, row: 15 });
this.pen.starPoint(ctx, { column: 15, row: 3 });
this.pen.starPoint(ctx, { column: 15, row: 9 });
this.pen.starPoint(ctx, { column: 15, row: 15 });
}
}
class Pen {
margin: number;
hspaceBetween: number;
vspaceBetween: number;
constructor(
width: number,
height: number,
margin: number,
columns: number,
rows: number
) {
this.margin = margin;
this.hspaceBetween = (width - margin * 2) / (columns - 1);
this.vspaceBetween = (height - margin * 2) / (rows - 1);
}
starPoint(ctx: CanvasRenderingContext2D, addr: Coordinate) {
ctx.fillStyle = "rgba(0, 0, 0, 1.0);";
ctx.beginPath();
const pixel = this.position(addr);
ctx.moveTo(pixel.x, pixel.y);
ctx.arc(pixel.x, pixel.y, 5, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}
ghostStone(ctx: CanvasRenderingContext2D, addr: Coordinate, color: Color) {
switch (color) {
case Color.White:
ctx.fillStyle = "rgba(230, 230, 230, 0.5)";
break;
case Color.Black:
ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
break;
}
this.drawStone(ctx, addr);
}
stone(ctx: CanvasRenderingContext2D, addr: Coordinate, color: Color) {
switch (color) {
case Color.White:
ctx.fillStyle = "rgb(230, 230, 230)";
break;
case Color.Black:
ctx.fillStyle = "rgb(0, 0, 0)";
break;
}
this.drawStone(ctx, addr);
}
drawStone(ctx: CanvasRenderingContext2D, addr: Coordinate) {
ctx.beginPath();
const radius = this.hspaceBetween / 2 - 2;
const pixel = this.position(addr);
ctx.moveTo(pixel.x, pixel.y);
ctx.arc(pixel.x, pixel.y, radius, 0, 2.0 * Math.PI);
ctx.closePath();
ctx.fill();
}
position(addr: Coordinate): Pixel {
return {
x: this.margin + addr.column * this.hspaceBetween,
y: this.margin + addr.row * this.vspaceBetween,
};
}
address(pixel: Pixel): Coordinate | null {
if (
Math.round(pixel.x) < this.margin ||
Math.round(pixel.y) < this.margin
) {
return null;
} else {
return {
column: Math.round(
(Math.round(pixel.x) - this.margin) / this.hspaceBetween
),
row: Math.round(
(Math.round(pixel.y) - this.margin) / this.vspaceBetween
),
};
}
}
}
const boardAddress = (size: Size, coordinate: Coordinate): number =>
coordinate.column + size.width * coordinate.row;

View File

@ -1,24 +0,0 @@
import init, { CoreApp } from "kifu-wasm";
import { CoreResponse, CoreRequest, PlayingFieldView } from "core-types";
export class CoreApi {
core: CoreApp;
constructor() {
let app = new CoreApp();
this.core = app;
}
async dispatch(request: CoreRequest): Promise<CoreResponse> {
return await this.core.dispatch(request);
}
async playingField(): Promise<PlayingFieldView> {
return (await this.dispatch({ type: "PlayingField" })).content;
}
}
export const initCore = async (): Promise<CoreApi> => {
await init();
return new CoreApi();
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 479 KiB

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="utf-8" />
<link rel="manifest" href="/manifest.json">
<link rel="stylesheet" href="kifu.css">
<title>Kifu</title>
<script type="module" src="kifu-bundle.js"></script>
</head>
<body>
<h1> Kifu </h1>
<div id="root"></div>
<script>
if('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js', { scope: '/' });
}
</script>
</body>
</html>

View File

@ -1,22 +0,0 @@
html {
background: rgb(243, 243, 243);
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
font-size: 15pt;
}
html, body {
height: 100%;
margin: 0;
}
body {
display: grid;
}
canvas {
border: 1px solid black;
}
.board {
background-color: rgb(150, 150, 150);
}

View File

@ -1,62 +0,0 @@
import { GoBoard } from "./components/Board";
import { CoreRequest, CoreResponse } from "core-types";
import { CoreApi, initCore } from "./coreApi";
// import { assertNever } from "./assertNever";
class UIState {
private currentView: GoBoard | null;
private rootElement: HTMLElement;
coreApi: CoreApi;
constructor(coreApi: CoreApi, root: HTMLElement) {
this.currentView = null;
this.rootElement = root;
this.coreApi = coreApi;
if (!root) {
console.log("root element not found");
return;
}
}
processResponse(response: CoreResponse) {
switch (response.type) {
case "PlayingFieldView":
if (this.currentView) {
this.currentView.setBoard(response.content.board);
} else {
this.currentView = new GoBoard({
board: response.content.board,
onClick: async (request: CoreRequest) => {
const response = await this.coreApi.dispatch(request);
this.processResponse(response);
},
});
this.rootElement?.appendChild(this.currentView.canvas);
this.currentView.renderBoard();
}
break;
default:
console.log("impossible branch: ", response);
alert("impossible branch");
// assertNever(response);
break;
}
}
}
const main = async () => {
let coreApi = await initCore();
let response = await coreApi.dispatch({ type: "PlayingField" });
const root = document.getElementById("root");
if (!root) {
console.log("root element not present");
return;
}
const uiState = new UIState(coreApi, root);
uiState.processResponse(response);
};
main();

View File

@ -1,17 +0,0 @@
{
"lang": "en-us",
"name": "Kifu",
"short_name": "Kifu",
"description": "An app for playing Go",
"start_url": "/",
"background_color": "#2f3d58",
"theme_color": "#2f3d58",
"orientation": "any",
"display": "standalone",
"icons": [
{
"src": "/icon512.png",
"sizes": "512x512"
}
]
}

View File

@ -1,29 +0,0 @@
const CACHE_NAME = 'kifu-pwa-3';
self.addEventListener('install', event => {
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
cache.addAll([
'/',
'/kifu-bundle.js',
'/kifu.css',
]);
})());
});
self.addEventListener('fetch', event => {
event.respondWith((async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(event.request);
if (cachedResponse) {
return cachedResponse;
} else {
try {
const fetchResponse = await fetch(event.request);
cache.put(event.request, fetchResponse.clone());
return fetchResponse;
} catch (e) {
}
}
})());
});

View File

@ -1,28 +0,0 @@
const CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
mode: "development",
entry: {
"kifu-bundle": "./src/main.ts",
sw: "./src/sw.js",
},
module: {
rules: [
{ test: /\.ts$/, use: "ts-loader", exclude: /node_modules/ },
{ test: /\.wasm$/, type: "asset/inline" }
],
},
plugins: [
new CopyWebpackPlugin({
patterns: [
{ from: "src/index.html" },
{ from: "src/kifu.css" },
{ from: "src/manifest.json" },
{ from: "src/icon512.png" }
]
})
],
resolve: {
extensions: ['.ts'],
}
}

1981
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
{
"name": "tools",
"workspaces": [
"kifu/ffi/wasm/pkg",
"kifu/core-types",
"kifu/pwa"
]
}

View File

@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
}
}