Compare commits

..

22 Commits

Author SHA1 Message Date
701cb1f5a4 I don't fucking know any more
All checks were successful
Build / build (push) Successful in 5m37s
2025-08-14 12:22:18 +02:00
a198352aa9 upgrade rust version 2025-08-14 12:22:07 +02:00
f52b9e531e make recording game data optional
Some checks failed
Build / build (push) Failing after 1m41s
2025-08-01 19:03:54 +02:00
bb6d716773 cleanup
All checks were successful
Build / build (push) Successful in 4m56s
2025-08-01 18:36:16 +02:00
bb036f83d5 use more efficient lazy static
All checks were successful
Build / build (push) Successful in 1m59s
2025-07-06 21:36:11 +02:00
7b28a4c4f6 check for initial food placement
All checks were successful
Build / build (push) Successful in 1m59s
2025-07-06 19:49:34 +02:00
f0156ccec4 add multithreading for seed cracking
All checks were successful
Build / build (push) Successful in 1m55s
2025-06-24 20:59:32 +02:00
2c2bfd3489 fix fast validation
All checks were successful
Build / build (push) Successful in 1m51s
2025-06-24 20:49:56 +02:00
263b0642f4 calculate less
All checks were successful
Build / build (push) Successful in 2m38s
Speed up validation by precalculating some stuff and toing a fast return
wherever possible
2025-06-24 20:07:25 +02:00
081c41b753 try some seedcracking
All checks were successful
Build / build (push) Successful in 3m55s
2025-06-24 17:28:11 +02:00
549d4fe36d modularize simulation 2025-06-24 17:27:56 +02:00
6f54282c9a improve xtask 2025-06-24 17:27:29 +02:00
6f3a1d138a ignore assets and games 2025-06-24 17:27:13 +02:00
0876f1ed3c explicitly select rust toolchain 2025-06-24 17:27:01 +02:00
87fc6cccd2 update dependencies
All checks were successful
Build / build (push) Successful in 1m12s
2025-06-07 21:17:38 +02:00
2f87e2aa60 improve log output
All checks were successful
Build / build (push) Successful in 1m7s
2025-06-06 21:38:34 +02:00
5f1d3dfc4f build local snakes in release
All checks were successful
Build / build (push) Successful in 1m10s
2025-06-06 21:29:59 +02:00
d44538b749 make number of compute threads configurable
All checks were successful
Build / build (push) Successful in 1m15s
2025-06-06 21:24:18 +02:00
b4b332bdbb reduce regression parallelism
All checks were successful
Build / build (push) Successful in 2m52s
2025-06-06 21:16:22 +02:00
b97d7c895a add multithreading 2025-06-06 21:15:26 +02:00
e5600fe038 use faster rng
All checks were successful
Build / build (push) Successful in 1m54s
2025-04-25 19:55:42 +02:00
15d90357ec remove unused import
All checks were successful
Build / build (push) Successful in 2m6s
2025-04-25 15:04:09 +02:00
23 changed files with 2929 additions and 537 deletions

2
.gitignore vendored
View File

@@ -1 +1,3 @@
target target
assets
games

484
Cargo.lock generated
View File

@@ -13,9 +13,9 @@ dependencies = [
[[package]] [[package]]
name = "adler2" name = "adler2"
version = "2.0.0" version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
@@ -26,6 +26,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]] [[package]]
name = "anes" name = "anes"
version = "0.1.6" version = "0.1.6"
@@ -34,9 +40,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.18" version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@@ -49,37 +55,37 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.10" version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.6" version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.7" version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"once_cell", "once_cell_polyfill",
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -90,15 +96,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.8.3" version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
dependencies = [ dependencies = [
"axum-core", "axum-core",
"base64", "base64",
@@ -153,10 +159,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "backtrace" name = "az"
version = "0.3.74" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]]
name = "backtrace"
version = "0.3.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cfg-if", "cfg-if",
@@ -178,22 +190,34 @@ name = "battlesnake"
version = "2.0.0" version = "2.0.0"
dependencies = [ dependencies = [
"axum", "axum",
"az",
"bitvec", "bitvec",
"blanket",
"criterion", "criterion",
"enum-iterator", "enum-iterator",
"enum_dispatch",
"env_logger", "env_logger",
"float-ord", "float-ord",
"futures-util",
"hashbrown",
"log", "log",
"rand", "rand",
"serde", "serde",
"serde_json",
"tokio", "tokio",
] ]
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]] [[package]]
name = "bitvec" name = "bitvec"
@@ -207,6 +231,17 @@ dependencies = [
"wyz", "wyz",
] ]
[[package]]
name = "blanket"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56791e4bd64c99fc361e01008f45c984baa93f12a0957d1b3c51dd2c6baab453"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -218,9 +253,15 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.17.0" version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "bytemuck"
version = "1.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
[[package]] [[package]]
name = "bytes" name = "bytes"
@@ -236,9 +277,21 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cfg_aliases"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "ciborium" name = "ciborium"
@@ -269,34 +322,49 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.37" version = "4.5.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive",
] ]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.37" version = "4.5.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
dependencies = [ dependencies = [
"anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.104",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]] [[package]]
name = "colored" name = "colored"
@@ -305,7 +373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -319,25 +387,22 @@ dependencies = [
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.5.1" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928"
dependencies = [ dependencies = [
"anes", "anes",
"cast", "cast",
"ciborium", "ciborium",
"clap", "clap",
"criterion-plot", "criterion-plot",
"is-terminal",
"itertools", "itertools",
"num-traits", "num-traits",
"once_cell",
"oorandom", "oorandom",
"plotters", "plotters",
"rayon", "rayon",
"regex", "regex",
"serde", "serde",
"serde_derive",
"serde_json", "serde_json",
"tinytemplate", "tinytemplate",
"walkdir", "walkdir",
@@ -345,9 +410,9 @@ dependencies = [
[[package]] [[package]]
name = "criterion-plot" name = "criterion-plot"
version = "0.5.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338"
dependencies = [ dependencies = [
"cast", "cast",
"itertools", "itertools",
@@ -380,9 +445,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]] [[package]]
name = "crunchy" name = "crunchy"
version = "0.2.3" version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
@@ -442,7 +507,19 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
]
[[package]]
name = "enum_dispatch"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.104",
] ]
[[package]] [[package]]
@@ -486,6 +563,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.1" version = "1.2.1"
@@ -516,6 +599,17 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.31" version = "0.3.31"
@@ -535,6 +629,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"pin-project-lite", "pin-project-lite",
@@ -542,6 +637,14 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "generate"
version = "0.1.0"
dependencies = [
"az",
"bytemuck",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -554,9 +657,9 @@ dependencies = [
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@@ -572,9 +675,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.9" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75249d144030531f8dee69fe9cea04d3edf809a017ae445e2abdff6629e86633" checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@@ -601,15 +704,20 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.15.2" version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]] [[package]]
name = "hermit-abi" name = "heck"
version = "0.5.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "http" name = "http"
@@ -679,12 +787,12 @@ dependencies = [
[[package]] [[package]]
name = "hyper-util" name = "hyper-util"
version = "0.1.11" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-util", "futures-core",
"http", "http",
"http-body", "http-body",
"hyper", "hyper",
@@ -695,23 +803,23 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.9.0" version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown",
] ]
[[package]] [[package]]
name = "is-terminal" name = "io-uring"
version = "0.4.16" version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [ dependencies = [
"hermit-abi", "bitflags 2.9.1",
"cfg-if",
"libc", "libc",
"windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -722,9 +830,9 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [ dependencies = [
"either", "either",
] ]
@@ -737,9 +845,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]] [[package]]
name = "jiff" name = "jiff"
version = "0.2.9" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ec30f7142be6fe14e1b021f50b85db8df2d4324ea6e91ec3e5dcde092021d0" checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [ dependencies = [
"jiff-static", "jiff-static",
"log", "log",
@@ -750,13 +858,13 @@ dependencies = [
[[package]] [[package]]
name = "jiff-static" name = "jiff-static"
version = "0.2.9" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "526b834d727fd59d37b076b0c3236d9adde1b1729a4361e20b2026f738cc1dbe" checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
] ]
[[package]] [[package]]
@@ -777,15 +885,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.172" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"scopeguard", "scopeguard",
@@ -805,9 +913,18 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "memmap2"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "mime" name = "mime"
@@ -817,22 +934,22 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.8" version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
[[package]] [[package]]
name = "mio" name = "mio"
version = "1.0.3" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
dependencies = [ dependencies = [
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.1+wasi-snapshot-preview1",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -876,6 +993,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
version = "11.1.5" version = "11.1.5"
@@ -884,9 +1007,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.3" version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core",
@@ -894,9 +1017,9 @@ dependencies = [
[[package]] [[package]]
name = "parking_lot_core" name = "parking_lot_core"
version = "0.9.10" version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@@ -953,9 +1076,9 @@ dependencies = [
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.11.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]] [[package]]
name = "portable-atomic-util" name = "portable-atomic-util"
@@ -995,9 +1118,9 @@ dependencies = [
[[package]] [[package]]
name = "r-efi" name = "r-efi"
version = "5.2.0" version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "radium" name = "radium"
@@ -1007,9 +1130,9 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.1" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
@@ -1056,11 +1179,11 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.11" version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@@ -1103,15 +1226,15 @@ dependencies = [
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.20" version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
[[package]] [[package]]
name = "ryu" name = "ryu"
@@ -1134,6 +1257,24 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "seed-cracker"
version = "0.1.0"
dependencies = [
"az",
"battlesnake",
"bytemuck",
"clap",
"enum-iterator",
"env_logger",
"hashbrown",
"log",
"memmap2",
"rayon",
"serde_json",
"static_init",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.219" version = "1.0.219"
@@ -1151,14 +1292,14 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@@ -1210,27 +1351,24 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.0" version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.9" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -1240,10 +1378,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]] [[package]]
name = "syn" name = "static_init"
version = "2.0.100" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" checksum = "8bae1df58c5fea7502e8e352ec26b5579f6178e1fdb311e088580c980dee25ed"
dependencies = [
"bitflags 1.3.2",
"cfg_aliases 0.2.1",
"libc",
"parking_lot",
"parking_lot_core",
"static_init_macro",
"winapi",
]
[[package]]
name = "static_init_macro"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f"
dependencies = [
"cfg_aliases 0.1.1",
"memchr",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[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.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1279,7 +1462,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
] ]
[[package]] [[package]]
@@ -1294,20 +1477,22 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.44.2" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
"io-uring",
"libc", "libc",
"mio", "mio",
"parking_lot", "parking_lot",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"windows-sys 0.52.0", "windows-sys",
] ]
[[package]] [[package]]
@@ -1318,7 +1503,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
] ]
[[package]] [[package]]
@@ -1335,9 +1520,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.7.14" version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@@ -1387,9 +1572,9 @@ dependencies = [
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.33" version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
dependencies = [ dependencies = [
"once_cell", "once_cell",
] ]
@@ -1453,9 +1638,9 @@ dependencies = [
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasi" name = "wasi"
@@ -1488,7 +1673,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -1510,7 +1695,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@@ -1534,23 +1719,36 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.9" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]] [[package]]
name = "windows-sys" name = "winapi-x86_64-pc-windows-gnu"
version = "0.52.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
@@ -1631,7 +1829,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@@ -1653,20 +1851,20 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.24" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.24" version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn 2.0.104",
] ]

View File

@@ -1,5 +1,9 @@
[workspace] [workspace]
members = ["battlesnake", "xtask"] members = ["battlesnake", "generate", "seed-cracker", "xtask"]
resolver = "3" resolver = "3"
default-members = ["battlesnake"] default-members = ["battlesnake"]
[profile.release]
lto = "fat"
codegen-units = 1

View File

@@ -1,4 +1,4 @@
FROM rust:1.86-bullseye AS build FROM rust:1.88-bullseye AS build
COPY battlesnake/ /usr/app COPY battlesnake/ /usr/app
WORKDIR /usr/app WORKDIR /usr/app

View File

@@ -3,6 +3,7 @@ authors = ["Max Känner"]
name = "battlesnake" name = "battlesnake"
version = "2.0.0" version = "2.0.0"
edition = "2024" edition = "2024"
rust-version = "1.88"
readme = "README.md" readme = "README.md"
repository = "https://git.mkaenner.de/max/battlesnake" repository = "https://git.mkaenner.de/max/battlesnake"
@@ -10,6 +11,7 @@ keywords = ["battlesnake"]
description = """ description = """
A simple Battlesnake written in Rust A simple Battlesnake written in Rust
""" """
default-run = "battlesnake"
[lints.clippy] [lints.clippy]
pedantic = "warn" pedantic = "warn"
@@ -30,9 +32,15 @@ bitvec = "1.0"
enum-iterator = "2.1" enum-iterator = "2.1"
rand = "0.9" rand = "0.9"
float-ord = "0.3" float-ord = "0.3"
futures-util = "0.3"
az = "1.2"
blanket = "0.4"
hashbrown = "0.15"
serde_json = "1.0"
enum_dispatch = "0.3"
[dev-dependencies] [dev-dependencies]
criterion = "0.5" criterion = "0.7"
[[bench]] [[bench]]
name = "simulation" name = "simulation"

View File

@@ -1,15 +1,19 @@
use std::sync::{ use std::{
atomic::{AtomicU32, Ordering}, hint::black_box,
Arc, sync::{
Arc,
atomic::{AtomicU32, Ordering},
},
}; };
use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; use criterion::{Bencher, BenchmarkId, Criterion, criterion_group, criterion_main};
use battlesnake::types::{ use battlesnake::types::{
simulation::Board,
wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings},
Coord, Coord,
simulation::Game as Board,
wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings},
}; };
use rand::{SeedableRng, rngs::SmallRng};
fn create_start_snake(coord: Coord) -> Battlesnake { fn create_start_snake(coord: Coord) -> Battlesnake {
let id: Arc<str> = format!("{coord:?}").into(); let id: Arc<str> = format!("{coord:?}").into();
@@ -74,13 +78,8 @@ fn standard(c: &mut Criterion) {
let benchmark = |b: &mut Bencher, board: &Board| { let benchmark = |b: &mut Bencher, board: &Board| {
b.iter(|| { b.iter(|| {
let mut board = board.clone(); let mut board = board.clone();
let turn = board.simulate_random(|board| { board.simulate_random(&mut SmallRng::from_os_rng());
if board.num_snakes() <= 1 { let turn = board.board.turn();
Some(board.turn())
} else {
None
}
});
if turn < turns_min.load(Ordering::Relaxed) { if turn < turns_min.load(Ordering::Relaxed) {
turns_min.store(turn, Ordering::Relaxed); turns_min.store(turn, Ordering::Relaxed);
@@ -151,13 +150,8 @@ fn constrictor(c: &mut Criterion) {
let benchmark = |b: &mut Bencher, board: &Board| { let benchmark = |b: &mut Bencher, board: &Board| {
b.iter(|| { b.iter(|| {
let mut board = board.clone(); let mut board = board.clone();
let turn = board.simulate_random(|board| { board.simulate_random(&mut SmallRng::from_os_rng());
if board.num_snakes() <= 1 { let turn = board.board.turn();
Some(board.turn())
} else {
None
}
});
if turn < turns_min.load(Ordering::Relaxed) { if turn < turns_min.load(Ordering::Relaxed) {
turns_min.store(turn, Ordering::Relaxed); turns_min.store(turn, Ordering::Relaxed);

View File

@@ -1,39 +1,127 @@
use std::env; use std::{
env,
sync::atomic::{AtomicUsize, Ordering},
};
use axum::{ use axum::{
Router, Router,
extract::Json, extract::{Json, State},
response, response,
routing::{get, post}, routing::{get, post},
}; };
use battlesnake::types::{ use battlesnake::types::{
Direction, Direction,
simulation::Board, simulation::{Board, Game},
wire::{Request, Response}, wire::{Request, Response},
}; };
use float_ord::FloatOrd; use float_ord::FloatOrd;
use futures_util::future::join_all;
use hashbrown::HashMap;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use rand::{prelude::*, rng}; use rand::prelude::*;
use serde::Serialize; use serde::Serialize;
use tokio::{ use tokio::{
fs::File,
io::AsyncWriteExt,
net::TcpListener, net::TcpListener,
sync::mpsc::{UnboundedSender, unbounded_channel},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
static THREADS: AtomicUsize = AtomicUsize::new(1);
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
env_logger::init(); env_logger::init();
let record = env::var("RECORD")
.ok()
.and_then(|record| {
record
.parse()
.inspect_err(|err| error!("Unable to parse record options: {err}"))
.ok()
})
.unwrap_or(false);
let (sender, receiver) = if record {
let (sender, receiver) = unbounded_channel();
(Some(sender), Some(receiver))
} else {
(None, None)
};
debug!("Creating routes"); debug!("Creating routes");
let app = Router::new() let app = Router::new()
.route("/", get(info)) .route("/", get(info))
.route("/start", post(start)) .route("/start", post(start))
.route("/move", post(get_move)) .route("/move", post(get_move))
.route("/end", post(end)); .route("/end", post(end))
.with_state(sender);
let threads = env::var("THREADS")
.ok()
.and_then(|threads| {
threads
.parse()
.inspect_err(|err| error!("Unable to parse number of threads: {err}"))
.ok()
})
.unwrap_or(1);
THREADS.store(threads, Ordering::Relaxed);
debug!("Creating listener"); debug!("Creating listener");
let port = env::var("PORT").unwrap_or_else(|_| "8000".into()); let port = env::var("PORT").unwrap_or_else(|_| "8000".into());
let listener = TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap(); let listener = TcpListener::bind(format!("0.0.0.0:{port}")).await.unwrap();
if let Some(mut receiver) = receiver {
debug!("Starting observer");
tokio::spawn(async move {
let mut games = HashMap::new();
while let Some((request_type, request)) = receiver.recv().await {
match request_type {
RequestType::Start => {
let game_id = request.game.id.clone();
info!("Got start request {game_id}");
if let Some(old_requests) = games.insert(game_id, vec![request]) {
warn!("evicted duplicate game: {old_requests:?}");
}
}
RequestType::GetMove => {
let game_id = request.game.id.clone();
info!("Got move request {game_id}");
games.entry(game_id).or_default().push(request);
}
RequestType::End => {
let game_id = request.game.id.clone();
info!("Got end request {game_id}");
if let Some(mut requests) = games.remove(&game_id) {
requests.push(request);
let json = match serde_json::to_vec_pretty(&requests) {
Ok(json) => json,
Err(e) => {
error!("Unable to serealize json: {e}");
continue;
}
};
match File::create_new(format!("games/{game_id}.json")).await {
Ok(mut file) => {
if let Err(e) = file.write_all(&json).await {
error!("Unable to write jsone: {e}");
}
}
Err(e) => error!("Unable to open file: {e}"),
}
} else {
warn!("end of game without game: {request:?}");
}
}
}
}
warn!("Observer stopped");
});
}
debug!("Starting server"); debug!("Starting server");
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
@@ -60,22 +148,66 @@ struct Info {
version: &'static str, version: &'static str,
} }
async fn start(request: Json<Request>) { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
let board = Board::from(&*request); enum RequestType {
Start,
GetMove,
End,
}
async fn start(
State(sender): State<Option<UnboundedSender<(RequestType, Request)>>>,
Json(request): Json<Request>,
) {
if let Some(sender) = sender
&& let Err(e) = sender.send((RequestType::Start, request.clone()))
{
warn!("Unable to observe request: {e}");
}
let board = Board::from(&request);
info!("got start request: {board}"); info!("got start request: {board}");
} }
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
async fn get_move(request: Json<Request>) -> response::Json<Response> { async fn get_move(
State(sender): State<Option<UnboundedSender<(RequestType, Request)>>>,
Json(request): Json<Request>,
) -> response::Json<Response> {
let start = Instant::now(); let start = Instant::now();
let board = Board::from(&*request); if let Some(sender) = sender
&& let Err(e) = sender.send((RequestType::GetMove, request.clone()))
{
warn!("Unable to observe request: {e}");
}
let mut board = Game::from(&request);
board.agent = |snake, direction, board| {
let food = board.food().collect::<Vec<_>>();
if food.is_empty() {
return 1.0;
}
let head = board.head(snake);
let next_head = head.wrapping_apply(direction);
let dist = food
.iter()
.map(|food| u16::from(food.x.abs_diff(head.x)) + u16::from(food.y.abs_diff(head.y)))
.min()
.unwrap_or(1);
let next_dist = food
.iter()
.map(|food| {
u16::from(food.x.abs_diff(next_head.x)) + u16::from(food.y.abs_diff(next_head.y))
})
.min()
.unwrap_or(100);
f32::from(next_dist) / f32::from(dist)
};
let timeout = Duration::from_millis(u64::from(request.game.timeout)); let timeout = Duration::from_millis(u64::from(request.game.timeout));
let id = board.get_id(&request.you.id).unwrap_or_else(|| { let id = board.board.get_id(&request.you.id).unwrap_or_else(|| {
error!("My id is not in the simulation board"); error!("My id is not in the simulation board");
0 0
}); });
debug!("got move request: {board}"); debug!("got move request: {}", board.board);
let actions = board.valid_actions(id).collect::<Vec<_>>(); let actions = board.board.valid_actions(id).collect::<Vec<_>>();
if actions.len() <= 1 { if actions.len() <= 1 {
info!( info!(
"only one possible action. Fast forwarding {:?}", "only one possible action. Fast forwarding {:?}",
@@ -88,124 +220,122 @@ async fn get_move(request: Json<Request>) -> response::Json<Response> {
} }
info!("valid actions: {actions:?}"); info!("valid actions: {actions:?}");
tokio::task::spawn_blocking(move || { if start.elapsed() > Duration::from_millis(10) {
if start.elapsed() > Duration::from_millis(10) { error!(
error!( "The calculation started late ({}ms)",
"The calculation started late ({}ms)", start.elapsed().as_millis()
start.elapsed().as_millis() );
); }
} let score_fn: fn(&Board, u8) -> u32 = match &*request.game.ruleset.name {
let base_turns = board.turn(); "solo" => score_solo,
let rolling_horizon = (u32::from(request.you.length) * 3).max(32); _ => score_standard,
let last_turn = base_turns + rolling_horizon; };
let end_condition: &dyn Fn(&Board) -> Option<_> = match &*request.game.ruleset.name {
"solo" => &|board| {
if board.valid_actions(0).count() == 0 || board.turn() > last_turn {
Some(())
} else {
None
}
},
_ => &|board| {
if board.num_snakes() <= 1 || board.turn() > last_turn {
Some(())
} else {
None
}
},
};
let score_fn: &dyn Fn(&Board, u8) -> u32 = match &*request.game.ruleset.name {
"solo" => &|board, id| u32::try_from(board.length(id)).unwrap_or(0),
_ => &|board, id| {
if board.alive(id) {
1 + u32::from(board.max_length() == board.length(id))
} else {
0
}
},
};
let mut mcts_managers: Vec<_> = (0..request.board.snakes.len()) let action_futures = (0..THREADS.load(Ordering::Relaxed)).map(|_| {
.map(|id| MctsManager::new(u8::try_from(id).unwrap())) let request = request.clone();
.collect(); let board = board.clone();
let c = f32::sqrt(2.0); let mut rng = SmallRng::from_os_rng();
while start.elapsed() < timeout * 4 / 5 { tokio::task::spawn_blocking(move || {
let mut board = board.clone(); let mut mcts_managers: Vec<_> = (0..request.board.snakes.len())
while end_condition(&board).is_none() { .map(|id| MctsManager::new(u8::try_from(id).unwrap()))
let actions: Vec<_> = mcts_managers .collect();
.iter_mut() let c = f32::sqrt(2.0);
.filter_map(|mcts_manager| { let mut mcts_actions = Vec::new();
while start.elapsed() < timeout * 4 / 5 {
let mut board = board.clone();
let mut game_over = false;
while !game_over {
mcts_actions.clear();
mcts_actions.extend(mcts_managers.iter_mut().filter_map(|mcts_manager| {
mcts_manager mcts_manager
.next_action(&board, c) .next_action(&board.board, c, &mut rng)
.map(|action| (mcts_manager.snake, action)) .map(|action| (mcts_manager.snake, action))
}) }));
.collect(); game_over = board.next_turn_random(&mcts_actions, &mut rng);
board.next_turn(&actions); if mcts_actions.is_empty() {
if actions.is_empty() { break;
break; }
}
if !game_over {
board.simulate_random(&mut rng);
}
for mcts_manager in &mut mcts_managers {
let id = mcts_manager.snake;
let score = score_fn(&board.board, id);
mcts_manager.apply_score(score);
} }
} }
board.simulate_random(end_condition); let my_mcts_manager = mcts_managers.into_iter().nth(usize::from(id)).unwrap();
for mcts_manager in &mut mcts_managers {
let id = mcts_manager.snake;
let score = score_fn(&board, id);
mcts_manager.apply_score(score);
}
}
let my_mcts_manager = &mcts_managers[usize::from(id)];
for action in actions {
let score = my_mcts_manager.base.next[usize::from(action)]
.as_ref()
.map(|info| info.score as f32 / info.played as f32);
if let Some(score) = score {
info!("{action:?} -> {score}");
} else {
info!("{action:?} -> None");
}
}
let action = my_mcts_manager let actions = my_mcts_manager.base.next.map(|action| {
.base action.map_or(0.0, |action| action.score as f32 / action.played as f32)
.next });
.iter()
.enumerate()
.filter_map(|(index, info)| {
info.as_ref().map(|info| {
(
match index {
0 => Direction::Up,
1 => Direction::Down,
2 => Direction::Left,
3 => Direction::Right,
_ => unreachable!(),
},
info,
)
})
})
.max_by_key(|(_, info)| FloatOrd(info.score as f32 / info.played as f32))
.map(|(action, _)| action);
if let Some(action) = action { (actions, my_mcts_manager.base.played)
info!(
"found action {action:?} after {} simulations.",
my_mcts_manager.base.played
);
} else {
warn!("unable to find a valid action");
}
info!("chose {action:?}");
response::Json(Response {
direction: action.unwrap_or(Direction::Up),
shout: None,
}) })
});
let (scores, played) = join_all(action_futures).await.into_iter().fold(
([0.0; 4], 0),
|(mut total, mut games), actions| {
if let Ok((actions, new_games)) = actions {
for i in 0..total.len() {
total[i] += actions[i];
}
games += new_games;
}
(total, games)
},
);
for action in actions {
let score = scores[usize::from(action)];
info!("{action:?} -> {score}");
}
let action = scores
.into_iter()
.enumerate()
.max_by_key(|(_, score)| FloatOrd(*score))
.map(|(index, _)| match index {
0 => Direction::Up,
1 => Direction::Down,
2 => Direction::Left,
3 => Direction::Right,
_ => unreachable!(),
});
if let Some(action) = action {
info!("found action {action:?} after {played} simulations.",);
} else {
warn!("unable to find a valid action");
}
info!("chose {action:?}");
response::Json(Response {
direction: action.unwrap_or(Direction::Up),
shout: None,
}) })
.await
.unwrap()
} }
async fn end(request: Json<Request>) { const fn score_solo(board: &Board, _id: u8) -> u32 {
let board = Board::from(&*request); board.turn()
}
fn score_standard(board: &Board, id: u8) -> u32 {
if board.alive(id) {
1 + u32::from(board.max_length() == board.length(id))
} else {
0
}
}
async fn end(
State(sender): State<Option<UnboundedSender<(RequestType, Request)>>>,
Json(request): Json<Request>,
) {
if let Some(sender) = sender
&& let Err(e) = sender.send((RequestType::End, request.clone()))
{
warn!("Unable to observe request: {e}");
}
let board = Board::from(&request);
info!("got end request: {board}"); info!("got end request: {board}");
} }
@@ -273,7 +403,7 @@ impl MctsManager {
self.expanded = false; self.expanded = false;
} }
fn next_action(&mut self, board: &Board, c: f32) -> Option<Direction> { fn next_action(&mut self, board: &Board, c: f32, rng: &mut impl RngCore) -> Option<Direction> {
if self.expanded { if self.expanded {
return None; return None;
} }
@@ -294,11 +424,7 @@ impl MctsManager {
.collect(); .collect();
trace!("got actions: {ucts:?}"); trace!("got actions: {ucts:?}");
if ucts.iter().any(|(_, uct)| uct.is_none()) { if ucts.iter().any(|(_, uct)| uct.is_none()) {
let action = ucts let action = ucts.iter().filter(|(_, uct)| uct.is_none()).choose(rng)?.0;
.iter()
.filter(|(_, uct)| uct.is_none())
.choose(&mut rng())?
.0;
self.expanded = true; self.expanded = true;
current.next[usize::from(action)].replace(ActionInfo::new()); current.next[usize::from(action)].replace(ActionInfo::new());
self.actions.push(action); self.actions.push(action);

View File

@@ -1,10 +1,12 @@
use std::fmt::Display;
use enum_iterator::Sequence; use enum_iterator::Sequence;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod simulation; pub mod simulation;
pub mod wire; pub mod wire;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
pub struct Coord { pub struct Coord {
pub x: u8, pub x: u8,
pub y: u8, pub y: u8,
@@ -33,7 +35,13 @@ impl Coord {
} }
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Sequence)] impl Display for Coord {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Sequence)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Direction { pub enum Direction {
/// Move in positive y direction /// Move in positive y direction

View File

@@ -0,0 +1,43 @@
mod royale;
mod standard;
use blanket::blanket;
use enum_dispatch::enum_dispatch;
use rand::Rng;
use royale::Royale;
use standard::Standard;
use crate::types::wire::Game;
use super::Board;
#[blanket(derive(Ref, Arc, Mut, Box))]
#[enum_dispatch(Maps)]
pub trait Map {
/// Called before the board is updated
fn pre_update(&self, board: &mut Board, rng: &mut impl Rng);
/// Called after the board is updated
fn post_update(&self, board: &mut Board, rng: &mut impl Rng);
}
#[enum_dispatch]
#[derive(Debug, Clone, Copy)]
pub enum Maps {
Standard,
Royale,
}
pub trait MapId {
/// ID of the map for detection
const ID: &str;
}
impl From<&Game> for Maps {
fn from(value: &Game) -> Self {
match value.map.as_ref() {
Royale::ID => Royale::from(&value.ruleset.settings).into(),
_ => Standard::from(&value.ruleset.settings).into(),
}
}
}

View File

@@ -0,0 +1,86 @@
use rand::{Rng, seq::IteratorRandom};
use crate::types::{Coord, Direction, simulation::Board, wire::Settings};
use super::{Map, MapId, standard::Standard};
#[derive(Debug, Clone, Copy)]
pub struct Royale {
standard: Standard,
shrink_every_n_turns: u8,
}
impl Map for Royale {
fn pre_update(&self, board: &mut Board, rng: &mut impl Rng) {
self.standard.pre_update(board, rng);
}
fn post_update(&self, board: &mut Board, rng: &mut impl Rng) {
self.standard.post_update(board, rng);
// Royale uses the current turn to generate hazards, not the previous turn that's in the
// board state
let turn = board.turn + 1;
if turn < u32::from(self.shrink_every_n_turns) {
return;
}
let side = enum_iterator::all::<Direction>()
.choose(rng)
.unwrap_or(Direction::Up);
match side {
Direction::Up => {
if let Some(i) = board.hazard.first_zero() {
let y = board.linear_to_coord(i).y;
for x in 0..board.width {
let i = board.coord_to_linear(Coord { x, y });
board.hazard.set(i, true);
}
}
}
Direction::Down => {
if let Some(i) = board.hazard.last_zero() {
let y = board.linear_to_coord(i).y;
for x in 0..board.width {
let i = board.coord_to_linear(Coord { x, y });
board.hazard.set(i, true);
}
}
}
Direction::Left => {
if let Some(i) = board.hazard.first_zero() {
let x = board.linear_to_coord(i).y;
for y in 0..board.height {
let i = board.coord_to_linear(Coord { x, y });
board.hazard.set(i, true);
}
}
}
Direction::Right => {
if let Some(i) = board.hazard.last_zero() {
let x = board.linear_to_coord(i).y;
for y in 0..board.height {
let i = board.coord_to_linear(Coord { x, y });
board.hazard.set(i, true);
}
}
}
}
}
}
impl MapId for Royale {
const ID: &str = "royale";
}
impl From<&Settings> for Royale {
fn from(value: &Settings) -> Self {
let standard = Standard::from(value);
let shrink_every_n_turns = value.royale.shrink_every_n_turns;
Self {
standard,
shrink_every_n_turns,
}
}
}

View File

@@ -0,0 +1,78 @@
use az::SaturatingAs;
use rand::{Rng, seq::SliceRandom};
use crate::types::{Coord, simulation::Board, wire::Settings};
use super::{Map, MapId};
#[derive(Debug, Clone, Copy)]
pub struct Standard {
min_food: u16,
food_spawn_chance: u8,
}
impl Map for Standard {
fn pre_update(&self, _board: &mut Board, _rng: &mut impl Rng) {}
fn post_update(&self, board: &mut Board, rng: &mut impl Rng) {
let food_needed = self.check_food_needing_placement(board, rng);
if food_needed > 0 {
Self::place_food_randomly(board, rng, food_needed);
}
}
}
impl MapId for Standard {
const ID: &str = "standard";
}
impl From<&Settings> for Standard {
fn from(value: &Settings) -> Self {
let min_food = value.minimum_food;
let food_spawn_chance = value.food_spawn_chance;
Self {
min_food,
food_spawn_chance,
}
}
}
impl Standard {
fn check_food_needing_placement(self, board: &Board, rng: &mut impl rand::Rng) -> u16 {
let num_current_food: u16 = board.food.count_ones().saturating_as();
if num_current_food < self.min_food {
return self.min_food - num_current_food;
}
if self.food_spawn_chance > 0 && (100 - rng.random_range(0..100)) < self.food_spawn_chance {
return 1;
}
0
}
fn place_food_randomly(board: &mut Board, rng: &mut impl rand::Rng, food_needed: u16) {
let mut unoccupied_points: Vec<_> = board.get_unoccupied_points(false, false).collect();
Self::place_food_ramdomly_at_positions(
board,
rng,
food_needed,
unoccupied_points.as_mut_slice(),
);
}
fn place_food_ramdomly_at_positions(
board: &mut Board,
rng: &mut impl rand::Rng,
food_needed: u16,
positions: &mut [Coord],
) {
let food_needed = usize::from(food_needed).min(positions.len());
positions.shuffle(rng);
for tile in &positions[..food_needed] {
let i = board.coord_to_linear(*tile);
board.food.set(i, true);
}
}
}

View File

@@ -1,3 +1,6 @@
mod maps;
mod rules;
use std::{ use std::{
collections::VecDeque, collections::VecDeque,
fmt::Display, fmt::Display,
@@ -6,9 +9,12 @@ use std::{
sync::Arc, sync::Arc,
}; };
use az::SaturatingAs;
use bitvec::prelude::*; use bitvec::prelude::*;
use log::{error, warn}; use log::{error, warn};
use rand::{prelude::*, rng}; use maps::{Map, Maps};
use rand::prelude::*;
use rules::{Ruleset, Rulesets};
use super::{Coord, Direction, wire::Request}; use super::{Coord, Direction, wire::Request};
@@ -58,19 +64,89 @@ impl DerefMut for SmallBitBox {
} }
} }
#[derive(Clone)]
pub struct Game {
pub board: Board,
map: Maps,
ruleset: Rulesets,
pub agent: fn(snake: u8, direction: Direction, board: &Board) -> f32,
}
impl From<&Request> for Game {
fn from(value: &Request) -> Self {
let board = value.into();
let map = (&value.game).into();
let ruleset = (&value.game.ruleset).into();
Self {
board,
map,
ruleset,
agent: |_, _, _| 1.0,
}
}
}
impl Game {
pub fn simulate_random(&mut self, rng: &mut impl Rng) {
loop {
let random_actions: Vec<_> = (0..self.board.snakes.len())
.filter_map(|i| {
self.board
.valid_actions_index(i)
.collect::<Vec<_>>()
.choose_weighted(rng, |&direction| {
(self.agent)(i.saturating_as(), direction, &self.board)
})
.ok()
.map(|&direction| (i.saturating_as(), direction))
})
.collect();
if self.next_turn(&random_actions, rng) {
break;
}
}
}
pub fn next_turn_random(&mut self, actions: &[(u8, Direction)], rng: &mut impl Rng) -> bool {
let random_actions: Vec<_> = (0..self.board.snakes.len())
.filter_map(|i| {
actions
.iter()
.find(|(j, _)| i == usize::from(*j))
.copied()
.or_else(|| {
self.board
.valid_actions_index(i)
.collect::<Vec<_>>()
.choose_weighted(rng, |&direction| {
(self.agent)(i.saturating_as(), direction, &self.board)
})
.ok()
.map(|&direction| (i.saturating_as(), direction))
})
})
.collect();
self.next_turn(&random_actions, rng)
}
pub fn next_turn(&mut self, actions: &[(u8, Direction)], rng: &mut impl Rng) -> bool {
self.map.pre_update(&mut self.board, rng);
let game_over = self.ruleset.execute(&mut self.board, actions);
self.map.post_update(&mut self.board, rng);
self.board.turn += 1;
game_over
}
}
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Board { pub struct Board {
width: u8, width: u8,
height: u8, height: u8,
hazard_damage: u8,
food_spawn_chance: u8,
min_food: u16,
turn: u32, turn: u32,
food: SmallBitBox, food: SmallBitBox,
hazard: SmallBitBox, hazard: SmallBitBox,
free: SmallBitBox, free: SmallBitBox,
snakes: Vec<Snake>, snakes: Vec<Snake>,
constrictor: bool,
id_map: Arc<[(u8, Arc<str>)]>, id_map: Arc<[(u8, Arc<str>)]>,
} }
@@ -94,15 +170,11 @@ impl From<&Request> for Board {
let mut board = Self { let mut board = Self {
width, width,
height, height,
hazard_damage: value.game.ruleset.settings.hazard_damage_per_turn,
food_spawn_chance: value.game.ruleset.settings.food_spawn_chance,
min_food: value.game.ruleset.settings.minimum_food,
turn: value.turn, turn: value.turn,
food: SmallBitBox::new(false, fields), food: SmallBitBox::new(false, fields),
hazard: SmallBitBox::new(false, fields), hazard: SmallBitBox::new(false, fields),
free: SmallBitBox::new(true, fields), free: SmallBitBox::new(true, fields),
snakes: Vec::with_capacity(value.board.snakes.len()), snakes: Vec::with_capacity(value.board.snakes.len()),
constrictor: &*value.game.ruleset.name == "constrictor",
id_map: id_map.into(), id_map: id_map.into(),
}; };
@@ -116,11 +188,12 @@ impl From<&Request> for Board {
board.hazard.set(index, true); board.hazard.set(index, true);
} }
let constrictor = value.game.ruleset.name.as_ref() == "constrictor";
for (id, snake) in value.board.snakes.iter().enumerate() { for (id, snake) in value.board.snakes.iter().enumerate() {
for &tile in snake for &tile in snake
.body .body
.iter() .iter()
.take(snake.body.len() - usize::from(!board.constrictor)) .take(snake.body.len() - usize::from(!constrictor))
{ {
let index = board.coord_to_linear(tile); let index = board.coord_to_linear(tile);
board.free.set(index, false); board.free.set(index, false);
@@ -139,17 +212,7 @@ impl From<&Request> for Board {
impl Display for Board { impl Display for Board {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!( writeln!(f, "{}x{} @ {}", self.width, self.height, self.turn)?;
f,
"{} {}x{} {}% ({}) {}dmg @ {}",
if self.constrictor { "constrictor" } else { "" },
self.width,
self.height,
self.food_spawn_chance,
self.min_food,
self.hazard_damage,
self.turn
)?;
for y in (0..self.height).rev() { for y in (0..self.height).rev() {
for x in 0..self.width { for x in 0..self.width {
@@ -201,10 +264,15 @@ impl Board {
} }
#[must_use] #[must_use]
pub fn num_snakes(&self) -> usize { pub const fn num_snakes(&self) -> usize {
self.snakes.len() self.snakes.len()
} }
#[must_use]
pub fn head(&self, id: u8) -> Coord {
self.snakes[usize::from(id)].head()
}
#[must_use] #[must_use]
pub fn alive(&self, id: u8) -> bool { pub fn alive(&self, id: u8) -> bool {
self.id_to_index(id).is_some() self.id_to_index(id).is_some()
@@ -233,6 +301,10 @@ impl Board {
self.food[index] self.food[index]
} }
pub fn food(&self) -> impl Iterator<Item = Coord> {
self.food.iter_ones().map(|i| self.linear_to_coord(i))
}
#[must_use] #[must_use]
pub fn is_hazard(&self, tile: Coord) -> bool { pub fn is_hazard(&self, tile: Coord) -> bool {
let index = self.coord_to_linear(tile); let index = self.coord_to_linear(tile);
@@ -255,47 +327,6 @@ impl Board {
.flat_map(|index| self.valid_actions_index(index)) .flat_map(|index| self.valid_actions_index(index))
} }
#[must_use]
pub fn random_action(&self, id: u8) -> Direction {
let Some(index) = self.id_to_index(id) else {
return Direction::Up;
};
self.valid_actions_index(index)
.choose(&mut rng())
.unwrap_or(Direction::Up)
}
pub fn random_actions(&self) -> impl Iterator<Item = (u8, Direction)> + use<'_> {
(0..self.snakes.len()).map(|index| {
(
self.snakes[index].id,
self.valid_actions_index(index)
.choose(&mut rng())
.unwrap_or(Direction::Up),
)
})
}
pub fn simulate_random<T>(&mut self, stop: impl Fn(&Self) -> Option<T>) -> T {
loop {
if let Some(score) = stop(self) {
break score;
}
self.next_turn(&[]);
}
}
pub fn next_turn(&mut self, actions: &[(u8, Direction)]) {
self.move_standard(actions);
self.starvation_standard();
self.hazard_damage_standard();
self.feed_snakes_standard();
self.eliminate_snake_standard();
self.update_free_map();
self.spawn_food();
self.turn += 1;
}
fn id_to_index(&self, id: u8) -> Option<usize> { fn id_to_index(&self, id: u8) -> Option<usize> {
self.snakes.binary_search_by_key(&id, |snake| snake.id).ok() self.snakes.binary_search_by_key(&id, |snake| snake.id).ok()
} }
@@ -306,127 +337,6 @@ impl Board {
.filter(move |direction| self.is_free(head.wrapping_apply(*direction))) .filter(move |direction| self.is_free(head.wrapping_apply(*direction)))
} }
fn move_standard(&mut self, actions: &[(u8, Direction)]) {
for i in 0..self.snakes.len() {
let snake = &self.snakes[i];
let action = actions.iter().find(|(id, _)| *id == snake.id).map_or_else(
|| {
self.valid_actions_index(i)
.choose(&mut rng())
.unwrap_or(Direction::Up)
},
|(_, action)| *action,
);
let new_head = snake.head().wrapping_apply(action);
self.snakes[i].advance(new_head);
}
}
fn starvation_standard(&mut self) {
for snake in &mut self.snakes {
snake.health = snake.health.saturating_sub(1);
}
}
fn hazard_damage_standard(&mut self) {
let mut i = 0;
while i < self.snakes.len() {
let head = self.snakes[i].head();
if self.is_in_bounds(head) {
let head_index = self.coord_to_linear(head);
if self.hazard[head_index] && !self.food[head_index] {
let health = &mut self.snakes[i].health;
*health = health.saturating_sub(1);
if *health == 0 {
let snake = self.snakes.remove(i);
for tile in snake.body {
let index = self.coord_to_linear(tile);
self.free.set(index, true);
}
continue;
}
}
}
i += 1;
}
}
fn feed_snakes_standard(&mut self) {
if self.constrictor {
for snake in &mut self.snakes {
snake.feed();
}
} else {
let mut eaten_food = vec![];
for i in 0..self.snakes.len() {
let head = self.snakes[i].head();
if self.is_in_bounds(head) {
let head_index = self.coord_to_linear(head);
if self.food[head_index] {
eaten_food.push(head_index);
self.snakes[i].feed();
}
}
}
for food_index in eaten_food {
self.food.set(food_index, false);
}
}
}
fn eliminate_snake_standard(&mut self) {
// eliminate out of health and out of bounds
let mut i = 0;
while i < self.snakes.len() {
let snake = &self.snakes[i];
if snake.health == 0 || !self.is_in_bounds(snake.head()) {
let snake = self.snakes.remove(i);
for tile in snake.body.iter().skip(1) {
if self.is_in_bounds(*tile) {
let index = self.coord_to_linear(*tile);
self.free.set(index, true);
}
}
continue;
}
i += 1;
}
// look for collisions
let mut collisions = vec![];
for snake in &self.snakes {
let head = snake.head();
let head_index = self.coord_to_linear(head);
if !self.free[head_index] {
collisions.push(snake.id);
continue;
}
for snake2 in &self.snakes {
if snake.id != snake2.id
&& snake.head() == snake2.head()
&& snake.body.len() <= snake2.body.len()
{
collisions.push(snake.id);
break;
}
}
}
// apply collisions
let mut i = 0;
while i < self.snakes.len() {
if collisions.contains(&self.snakes[i].id) {
let snake = self.snakes.remove(i);
for tile in snake.body {
let index = self.coord_to_linear(tile);
self.free.set(index, true);
}
continue;
}
i += 1;
}
}
fn update_free_map(&mut self) { fn update_free_map(&mut self) {
// free tails // free tails
for snake in &self.snakes { for snake in &self.snakes {
@@ -445,65 +355,49 @@ impl Board {
} }
} }
fn spawn_food(&mut self) { fn get_unoccupied_points(
let food_needed = self.check_food_needing_placement(); &self,
include_possible_moves: bool,
if food_needed > 0 { include_hazards: bool,
self.place_food_randomly(food_needed); ) -> impl Iterator<Item = Coord> + use<'_> {
} let possible_moves: Vec<_> = if include_possible_moves {
} Vec::new()
} else {
fn check_food_needing_placement(&self) -> u16 { self.snakes
let min_food = self.min_food; .iter()
let food_spawn_chance = self.food_spawn_chance; .flat_map(|snake| {
let num_current_food = u16::try_from(self.food.count_ones()).unwrap_or(u16::MAX); let head = snake.head();
enum_iterator::all::<Direction>()
if num_current_food < min_food { .map(move |direction| head.wrapping_apply(direction))
return min_food - num_current_food; .filter(|tile| self.is_in_bounds(*tile))
} .map(|tile| self.coord_to_linear(tile))
if food_spawn_chance > 0 && (100 - rng().random_range(0..100)) < food_spawn_chance { })
return 1; .collect()
} };
self.free
0
}
fn place_food_randomly(&mut self, amount: u16) {
let tails: Vec<_> = self
.snakes
.iter() .iter()
.map(|snake| self.coord_to_linear(snake.tail())) .zip(self.food.iter())
.collect(); .zip(self.hazard.iter())
let possible_moves: Vec<_> = self
.snakes
.iter()
.flat_map(|snake| {
let head = snake.head();
enum_iterator::all::<Direction>()
.map(move |direction| head.wrapping_apply(direction))
.filter(|tile| self.is_in_bounds(*tile))
.map(|tile| self.coord_to_linear(tile))
})
.collect();
let unoccupied_points = self
.free
.iter()
.by_vals()
.enumerate() .enumerate()
.zip(self.hazard.iter().by_vals()) .filter(move |(i, ((free, food), hazard))| {
.filter_map(|((i, free), hazard)| (!hazard && free).then_some(i)) **free
.filter(|i| !tails.contains(i)) && !**food
.filter(|i| !possible_moves.contains(i)); && (include_hazards || !**hazard)
&& (include_possible_moves || !possible_moves.contains(i))
for food_spot in unoccupied_points.choose_multiple(&mut rng(), usize::from(amount)) { })
self.food.set(food_spot, true); .map(|(i, _)| self.linear_to_coord(i))
}
} }
fn coord_to_linear(&self, coord: Coord) -> usize { fn coord_to_linear(&self, coord: Coord) -> usize {
usize::from(coord.x) + usize::from(coord.y) * usize::from(self.width) usize::from(coord.x) + usize::from(coord.y) * usize::from(self.width)
} }
fn linear_to_coord(&self, linear: usize) -> Coord {
let x = (linear % usize::from(self.width)).saturating_as();
let y = (linear / usize::from(self.width)).saturating_as();
Coord { x, y }
}
const fn is_in_bounds(&self, coord: Coord) -> bool { const fn is_in_bounds(&self, coord: Coord) -> bool {
coord.x < self.width && coord.y < self.height coord.x < self.width && coord.y < self.height
} }

View File

@@ -0,0 +1,38 @@
use crate::types::{Direction, simulation::Board, wire};
use super::{
Ruleset,
standard::{DamageHazards, eliminate_snakes, game_over, move_snakes, reduce_snake_health},
};
#[derive(Debug, Clone, Copy)]
pub struct Constrictor {
damage_hazards: DamageHazards,
}
impl From<&wire::Ruleset> for Constrictor {
fn from(value: &wire::Ruleset) -> Self {
Self {
damage_hazards: (&value.settings).into(),
}
}
}
impl Ruleset for Constrictor {
fn execute(&self, board: &mut Board, actions: &[(u8, Direction)]) -> bool {
let game_over = game_over(board);
move_snakes(board, actions);
reduce_snake_health(board);
self.damage_hazards.execute(board);
eliminate_snakes(board);
grow_snakes(board);
board.update_free_map();
game_over
}
}
fn grow_snakes(board: &mut Board) {
for snake in &mut board.snakes {
snake.feed();
}
}

View File

@@ -0,0 +1,37 @@
mod constrictor;
mod solo;
mod standard;
use constrictor::Constrictor;
use enum_dispatch::enum_dispatch;
use solo::Solo;
use standard::Standard;
use crate::types::{Direction, wire};
use super::Board;
#[enum_dispatch]
pub trait Ruleset {
/// executes one turn of the ruleset.
/// Returns true if the game is over
fn execute(&self, board: &mut Board, actions: &[(u8, Direction)]) -> bool;
}
#[enum_dispatch(Ruleset)]
#[derive(Debug, Clone, Copy)]
pub enum Rulesets {
Standard,
Constrictor,
Solo,
}
impl From<&wire::Ruleset> for Rulesets {
fn from(value: &wire::Ruleset) -> Self {
match value.name.as_ref() {
"solo" => Solo::from(value).into(),
"constrictor" => Constrictor::from(value).into(),
_ => Standard::from(value).into(),
}
}
}

View File

@@ -0,0 +1,36 @@
use crate::types::{Direction, simulation::Board, wire};
use super::{
Ruleset,
standard::{DamageHazards, eliminate_snakes, feed_snakes, move_snakes, reduce_snake_health},
};
#[derive(Debug, Clone, Copy)]
pub struct Solo {
damage_hazards: DamageHazards,
}
impl From<&wire::Ruleset> for Solo {
fn from(value: &wire::Ruleset) -> Self {
Self {
damage_hazards: (&value.settings).into(),
}
}
}
impl Ruleset for Solo {
fn execute(&self, board: &mut Board, actions: &[(u8, Direction)]) -> bool {
let game_over = game_over(board);
move_snakes(board, actions);
reduce_snake_health(board);
self.damage_hazards.execute(board);
feed_snakes(board);
eliminate_snakes(board);
board.update_free_map();
game_over
}
}
pub const fn game_over(board: &Board) -> bool {
board.num_snakes() == 0
}

View File

@@ -0,0 +1,180 @@
use crate::types::{
Direction,
simulation::Board,
wire::{self, Settings},
};
use super::Ruleset;
#[derive(Debug, Clone, Copy)]
pub struct Standard {
damage_hazards: DamageHazards,
}
impl From<&wire::Ruleset> for Standard {
fn from(value: &wire::Ruleset) -> Self {
Self {
damage_hazards: (&value.settings).into(),
}
}
}
impl Ruleset for Standard {
fn execute(&self, board: &mut Board, actions: &[(u8, Direction)]) -> bool {
let game_over = game_over(board);
move_snakes(board, actions);
reduce_snake_health(board);
self.damage_hazards.execute(board);
feed_snakes(board);
eliminate_snakes(board);
board.update_free_map();
game_over
}
}
pub fn move_snakes(board: &mut Board, actions: &[(u8, Direction)]) {
for i in 0..board.snakes.len() {
let snake = &board.snakes[i];
let action = actions.iter().find(|(id, _)| *id == snake.id).map_or_else(
|| {
let head = snake.body[0];
let previous_head = snake.body[1];
let delta_x = i16::from(head.x) - i16::from(previous_head.x);
let delta_y = i16::from(head.y) - i16::from(previous_head.y);
if delta_x == 0 && delta_y == 0 {
Direction::Up
} else if delta_x.abs() > delta_y.abs() {
if delta_x < 0 {
Direction::Left
} else {
Direction::Right
}
} else if delta_y < 0 {
Direction::Down
} else {
Direction::Up
}
},
|(_, action)| *action,
);
let new_head = snake.head().wrapping_apply(action);
board.snakes[i].advance(new_head);
}
}
pub fn reduce_snake_health(board: &mut Board) {
for snake in &mut board.snakes {
snake.health = snake.health.saturating_sub(1);
}
}
#[derive(Debug, Clone, Copy)]
pub struct DamageHazards {
damage: u8,
}
impl DamageHazards {
pub fn execute(self, board: &mut Board) {
let mut i = 0;
while i < board.snakes.len() {
let head = board.snakes[i].head();
if board.is_in_bounds(head) {
let head_index = board.coord_to_linear(head);
if board.hazard[head_index] && !board.food[head_index] {
let health = &mut board.snakes[i].health;
*health = health.saturating_sub(self.damage);
if *health == 0 {
let snake = board.snakes.remove(i);
for tile in snake.body {
let index = board.coord_to_linear(tile);
board.free.set(index, true);
}
continue;
}
}
}
i += 1;
}
}
}
impl From<&Settings> for DamageHazards {
fn from(value: &Settings) -> Self {
let damage = value.hazard_damage_per_turn;
Self { damage }
}
}
pub fn eliminate_snakes(board: &mut Board) {
// eliminate out of health and out of bounds
let mut i = 0;
while i < board.snakes.len() {
let snake = &board.snakes[i];
if snake.health == 0 || !board.is_in_bounds(snake.head()) {
let snake = board.snakes.remove(i);
for tile in snake.body.iter().skip(1) {
if board.is_in_bounds(*tile) {
let index = board.coord_to_linear(*tile);
board.free.set(index, true);
}
}
continue;
}
i += 1;
}
// look for collisions
let mut collisions = vec![];
for snake in &board.snakes {
let head = snake.head();
let head_index = board.coord_to_linear(head);
if !board.free[head_index] {
collisions.push(snake.id);
continue;
}
for snake2 in &board.snakes {
if snake.id != snake2.id
&& snake.head() == snake2.head()
&& snake.body.len() <= snake2.body.len()
{
collisions.push(snake.id);
break;
}
}
}
// apply collisions
let mut i = 0;
while i < board.snakes.len() {
if collisions.contains(&board.snakes[i].id) {
let snake = board.snakes.remove(i);
for tile in snake.body {
let index = board.coord_to_linear(tile);
board.free.set(index, true);
}
continue;
}
i += 1;
}
}
pub fn feed_snakes(board: &mut Board) {
let mut eaten_food = vec![];
for i in 0..board.snakes.len() {
let head = board.snakes[i].head();
if board.is_in_bounds(head) {
let head_index = board.coord_to_linear(head);
if board.food[head_index] {
eaten_food.push(head_index);
board.snakes[i].feed();
}
}
}
for food_index in eaten_food {
board.food.set(food_index, false);
}
}
pub const fn game_over(board: &Board) -> bool {
board.num_snakes() <= 1
}

View File

@@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
use super::{Coord, Direction}; use super::{Coord, Direction};
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Request { pub struct Request {
/// Game object describing the game being played. /// Game object describing the game being played.
pub game: Game, pub game: Game,
@@ -16,7 +16,7 @@ pub struct Request {
pub you: Battlesnake, pub you: Battlesnake,
} }
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Game { pub struct Game {
/// A unique identifier for this Game. /// A unique identifier for this Game.
pub id: Arc<str>, pub id: Arc<str>,
@@ -36,7 +36,7 @@ pub struct Game {
pub source: Arc<str>, pub source: Arc<str>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Ruleset { pub struct Ruleset {
/// Name of the ruleset being used to run this game. /// Name of the ruleset being used to run this game.
pub name: Arc<str>, pub name: Arc<str>,
@@ -47,7 +47,7 @@ pub struct Ruleset {
pub settings: Settings, pub settings: Settings,
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Settings { pub struct Settings {
/// Percentage chance of spawning a new food every round. /// Percentage chance of spawning a new food every round.
@@ -61,14 +61,14 @@ pub struct Settings {
pub royale: RoyaleSettings, pub royale: RoyaleSettings,
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RoyaleSettings { pub struct RoyaleSettings {
/// The number of turns between generating new hazards (shrinking the safe board space). /// The number of turns between generating new hazards (shrinking the safe board space).
pub shrink_every_n_turns: u8, pub shrink_every_n_turns: u8,
} }
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Board { pub struct Board {
/// The number of rows in the y-axis of the game board. /// The number of rows in the y-axis of the game board.
pub height: u8, pub height: u8,
@@ -83,7 +83,7 @@ pub struct Board {
pub snakes: Vec<Battlesnake>, pub snakes: Vec<Battlesnake>,
} }
#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub struct Battlesnake { pub struct Battlesnake {
/// Unique identifier for this Battlesnake in the context of the current Game. /// Unique identifier for this Battlesnake in the context of the current Game.
pub id: Arc<str>, pub id: Arc<str>,

8
generate/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "generate"
version = "0.1.0"
edition = "2024"
[dependencies]
az = "1.2"
bytemuck = "1.23"

47
generate/src/main.rs Normal file
View File

@@ -0,0 +1,47 @@
use std::{
fs::File,
io::{self, BufWriter, Write},
};
use az::Az;
use bytemuck::cast_slice;
fn main() -> Result<(), io::Error> {
let mut chain = BufWriter::new(File::create("assets/chain")?);
let mut indices = vec![0u32; (i32::MAX - 1).az::<usize>()];
let mut x = 1;
let mut count = 0;
loop {
if count % 1_000_000 == 0 {
println!("current: {count:0>10}");
}
let next = seedrand(x);
indices[x.az::<usize>() - 1] = count;
chain.write_all(&next.to_ne_bytes())?;
x = next;
count += 1;
if x == 1 {
break;
}
}
File::create("assets/table")?.write_all(cast_slice(&indices))?;
Ok(())
}
const fn seedrand(x: i32) -> i32 {
const A: i32 = 48_271;
const Q: i32 = 44_488;
const R: i32 = 3_399;
let hi = x / Q;
let lo = x % Q;
let x = A * lo - R * hi;
if x < 0 { x + i32::MAX } else { x }
}

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "stable"

19
seed-cracker/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "seed-cracker"
version = "0.1.0"
edition = "2024"
[dependencies]
az = "1.2"
battlesnake = { path = "../battlesnake/" }
bytemuck = "1.23"
clap = { version = "4.5", features = ["derive"] }
hashbrown = "0.15"
log = "0.4"
memmap2 = "0.9"
rayon = "1.10"
static_init = "1.0"
serde_json = "1.0"
env_logger = "0.11"
enum-iterator = "2.1"

1568
seed-cracker/src/main.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@ use std::{
net::TcpStream, net::TcpStream,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
sync::{Mutex, atomic::AtomicUsize}, sync::Mutex,
thread::sleep, thread::sleep,
time::Duration, time::Duration,
}; };
@@ -239,6 +239,8 @@ fn regression() -> Result<(), DynError> {
let res = try_regression(); let res = try_regression();
snake.kill().and(prod.kill())?; snake.kill().and(prod.kill())?;
stop_local_docker()?;
stop_production()?;
let (won, draw, loose) = res?; let (won, draw, loose) = res?;
let games = won + draw + loose; let games = won + draw + loose;
println!( println!(
@@ -346,7 +348,7 @@ fn run_snake(port: u16, log: Option<&str>) -> Result<Child, DynError> {
} }
let mut snake = snake let mut snake = snake
.args( .args(
["run", "--bin", "battlesnake"] ["run", "--bin", "battlesnake", "--release"]
.map(str::to_string) .map(str::to_string)
.into_iter() .into_iter()
.chain(env::args().skip(2)), .chain(env::args().skip(2)),
@@ -401,6 +403,13 @@ fn run_local_docker(port: u16) -> Result<Child, DynError> {
} }
} }
fn stop_local_docker() -> Result<(), DynError> {
Command::new("docker")
.args(["stop", "battlesnake-regression-local"])
.status()?;
Ok(())
}
fn run_production(port: u16) -> Result<Child, DynError> { fn run_production(port: u16) -> Result<Child, DynError> {
let mut snake = Command::new("docker") let mut snake = Command::new("docker")
.args([ .args([
@@ -431,6 +440,13 @@ fn run_production(port: u16) -> Result<Child, DynError> {
} }
} }
fn stop_production() -> Result<(), DynError> {
Command::new("docker")
.args(["stop", "battlesnake-regression-production"])
.status()?;
Ok(())
}
fn docker() -> Result<(), DynError> { fn docker() -> Result<(), DynError> {
if !Command::new("docker") if !Command::new("docker")
.current_dir(project_root()) .current_dir(project_root())