From 2f2b7ac11e482b3525e98eec251908ca59a5c494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20K=C3=A4nner?= Date: Tue, 21 Jan 2025 19:04:30 +0100 Subject: [PATCH] add simulation benchmarks --- Cargo.lock | 308 ++++++++++++++++++++++++++++ battlesnake/Cargo.toml | 7 + battlesnake/benches/simulation.rs | 220 ++++++++++++++++++++ battlesnake/src/lib.rs | 1 + battlesnake/src/main.rs | 12 +- battlesnake/src/types/mod.rs | 8 +- battlesnake/src/types/simulation.rs | 46 +++-- 7 files changed, 578 insertions(+), 24 deletions(-) create mode 100644 battlesnake/benches/simulation.rs create mode 100644 battlesnake/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index e346af3..850a73c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.18" @@ -173,6 +179,7 @@ version = "2.0.0" dependencies = [ "axum", "bitvec", + "criterion", "enum-iterator", "env_logger", "log", @@ -202,6 +209,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "byteorder" version = "1.5.0" @@ -214,12 +227,70 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + [[package]] name = "colorchoice" version = "1.0.3" @@ -235,6 +306,42 @@ dependencies = [ "libc", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -260,6 +367,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -458,12 +571,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "http" version = "1.2.0" @@ -562,18 +691,48 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.169" @@ -641,6 +800,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -656,6 +824,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -674,6 +848,34 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -804,6 +1006,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.217" @@ -943,6 +1154,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.43.0" @@ -1091,12 +1312,99 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/battlesnake/Cargo.toml b/battlesnake/Cargo.toml index 5a2d705..fe0daa0 100644 --- a/battlesnake/Cargo.toml +++ b/battlesnake/Cargo.toml @@ -29,3 +29,10 @@ env_logger = "0.11" bitvec = "1.0" enum-iterator = "2.1" rand = "0.8" + +[dev-dependencies] +criterion = "0.5" + +[[bench]] +name = "simulation" +harness = false diff --git a/battlesnake/benches/simulation.rs b/battlesnake/benches/simulation.rs new file mode 100644 index 0000000..e2ff45d --- /dev/null +++ b/battlesnake/benches/simulation.rs @@ -0,0 +1,220 @@ +use std::sync::atomic::{AtomicU32, Ordering}; + +use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; + +use battlesnake::types::{ + simulation::Board, + wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings}, + Coord, +}; + +fn create_start_snake(coord: Coord) -> Battlesnake { + let id = format!("{coord:?}"); + Battlesnake { + id: id.clone(), + name: id.clone(), + health: 100, + body: vec![coord; 3], + latency: "0".into(), + head: coord, + length: 3, + shout: String::new(), + squad: id, + } +} + +fn create_standard_start_request(starts: [Coord; 4]) -> Request { + Request { + game: Game { + id: "test".into(), + ruleset: Ruleset { + name: "standard".into(), + version: "0".into(), + settings: Settings { + food_spawn_chance: 15, + minimum_food: 1, + hazard_damage_per_turn: 30, + royale: RoyaleSettings { + shrink_every_n_turns: 20, + }, + }, + }, + map: "standard".into(), + timeout: 500, + source: "other".into(), + }, + turn: 0, + board: WireBoard { + height: 11, + width: 11, + food: vec![Coord { x: 5, y: 5 }], + hazards: vec![], + snakes: vec![ + create_start_snake(starts[0]), + create_start_snake(starts[1]), + create_start_snake(starts[2]), + create_start_snake(starts[3]), + ], + }, + you: create_start_snake(starts[0]), + } +} + +fn standard(c: &mut Criterion) { + let turns_min = AtomicU32::new(u32::MAX); + let turns_max = AtomicU32::new(u32::MIN); + let turns_sum = AtomicU32::new(0); + let turns_total = AtomicU32::new(0); + let mut group = c.benchmark_group("standard"); + group.sample_size(10000); + + let benchmark = |b: &mut Bencher, board: &Board| { + b.iter(|| { + let mut board = board.clone(); + let turn = board.simulate_random(|board| { + if board.num_snakes() <= 1 { + Some(board.turn()) + } else { + None + } + }); + + if turn < turns_min.load(Ordering::Relaxed) { + turns_min.store(turn, Ordering::Relaxed); + } + if turn > turns_max.load(Ordering::Relaxed) { + turns_max.store(turn, Ordering::Relaxed); + } + turns_sum.fetch_add(turn, Ordering::Relaxed); + turns_total.fetch_add(1, Ordering::Relaxed); + }); + }; + + let request = create_standard_start_request([ + Coord { x: 1, y: 1 }, + Coord { x: 9, y: 1 }, + Coord { x: 1, y: 9 }, + Coord { x: 9, y: 9 }, + ]); + let board = Board::from(&request); + group.bench_with_input( + BenchmarkId::from_parameter("start x"), + black_box(&board), + benchmark, + ); + { + let max = turns_max.load(Ordering::Relaxed); + let min = turns_min.load(Ordering::Relaxed); + let sum = turns_sum.load(Ordering::Relaxed); + let total = turns_total.load(Ordering::Relaxed); + let avg = sum / total; + println!("turns: [{min}, {max}] avg {avg} @ {total} samples"); + turns_max.store(u32::MIN, Ordering::Relaxed); + turns_min.store(u32::MAX, Ordering::Relaxed); + turns_sum.store(0, Ordering::Relaxed); + turns_total.store(0, Ordering::Relaxed); + } + + let request = create_standard_start_request([ + Coord { x: 5, y: 1 }, + Coord { x: 1, y: 5 }, + Coord { x: 5, y: 9 }, + Coord { x: 9, y: 5 }, + ]); + let board = Board::from(&request); + group.bench_with_input( + BenchmarkId::from_parameter("start +"), + black_box(&board), + benchmark, + ); + { + let max = turns_max.load(Ordering::Relaxed); + let min = turns_min.load(Ordering::Relaxed); + let sum = turns_sum.load(Ordering::Relaxed); + let total = turns_total.load(Ordering::Relaxed); + let avg = sum / total; + println!("turns: [{min}, {max}] avg {avg} @ {total} samples"); + } +} + +fn constrictor(c: &mut Criterion) { + let turns_min = AtomicU32::new(u32::MAX); + let turns_max = AtomicU32::new(u32::MIN); + let turns_sum = AtomicU32::new(0); + let turns_total = AtomicU32::new(0); + let mut group = c.benchmark_group("constrictor"); + group.sample_size(10000); + + let benchmark = |b: &mut Bencher, board: &Board| { + b.iter(|| { + let mut board = board.clone(); + let turn = board.simulate_random(|board| { + if board.num_snakes() <= 1 { + Some(board.turn()) + } else { + None + } + }); + + if turn < turns_min.load(Ordering::Relaxed) { + turns_min.store(turn, Ordering::Relaxed); + } + if turn > turns_max.load(Ordering::Relaxed) { + turns_max.store(turn, Ordering::Relaxed); + } + turns_sum.fetch_add(turn, Ordering::Relaxed); + turns_total.fetch_add(1, Ordering::Relaxed); + }); + }; + + let mut request = create_standard_start_request([ + Coord { x: 1, y: 1 }, + Coord { x: 9, y: 1 }, + Coord { x: 1, y: 9 }, + Coord { x: 9, y: 9 }, + ]); + request.game.ruleset.name = "constrictor".into(); + let board = Board::from(&request); + group.bench_with_input( + BenchmarkId::from_parameter("start x"), + black_box(&board), + benchmark, + ); + { + let max = turns_max.load(Ordering::Relaxed); + let min = turns_min.load(Ordering::Relaxed); + let sum = turns_sum.load(Ordering::Relaxed); + let total = turns_total.load(Ordering::Relaxed); + let avg = sum / total; + println!("turns: [{min}, {max}] avg {avg} @ {total} samples"); + turns_max.store(u32::MIN, Ordering::Relaxed); + turns_min.store(u32::MAX, Ordering::Relaxed); + turns_sum.store(0, Ordering::Relaxed); + turns_total.store(0, Ordering::Relaxed); + } + + let mut request = create_standard_start_request([ + Coord { x: 5, y: 1 }, + Coord { x: 1, y: 5 }, + Coord { x: 5, y: 9 }, + Coord { x: 9, y: 5 }, + ]); + request.game.ruleset.name = "constrictor".into(); + let board = Board::from(&request); + group.bench_with_input( + BenchmarkId::from_parameter("start +"), + black_box(&board), + benchmark, + ); + { + let max = turns_max.load(Ordering::Relaxed); + let min = turns_min.load(Ordering::Relaxed); + let sum = turns_sum.load(Ordering::Relaxed); + let total = turns_total.load(Ordering::Relaxed); + let avg = sum / total; + println!("turns: [{min}, {max}] avg {avg} @ {total} samples"); + } +} + +criterion_group!(benches, standard, constrictor); +criterion_main!(benches); diff --git a/battlesnake/src/lib.rs b/battlesnake/src/lib.rs new file mode 100644 index 0000000..cd40856 --- /dev/null +++ b/battlesnake/src/lib.rs @@ -0,0 +1 @@ +pub mod types; diff --git a/battlesnake/src/main.rs b/battlesnake/src/main.rs index ad44498..6cfc1e0 100644 --- a/battlesnake/src/main.rs +++ b/battlesnake/src/main.rs @@ -1,20 +1,18 @@ -mod types; - use axum::{ extract::Json, response, routing::{get, post}, Router, }; -use log::{debug, info, warn}; -use rand::prelude::*; -use serde::Serialize; -use tokio::{net::TcpListener, time::Instant}; -use types::{ +use battlesnake::types::{ simulation::Board, wire::{Request, Response}, Direction, }; +use log::{debug, info, warn}; +use rand::prelude::*; +use serde::Serialize; +use tokio::{net::TcpListener, time::Instant}; #[tokio::main] async fn main() { diff --git a/battlesnake/src/types/mod.rs b/battlesnake/src/types/mod.rs index c3d372b..06313d7 100644 --- a/battlesnake/src/types/mod.rs +++ b/battlesnake/src/types/mod.rs @@ -6,11 +6,12 @@ pub mod wire; #[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)] pub struct Coord { - x: u8, - y: u8, + pub x: u8, + pub y: u8, } impl Coord { + #[must_use] pub fn apply(self, direction: Direction) -> Option { match direction { Direction::Up => self.y.checked_add(1).map(|y| Self { y, x: self.x }), @@ -20,7 +21,8 @@ impl Coord { } } - pub fn wrapping_apply(mut self, direction: Direction) -> Self { + #[must_use] + pub const fn wrapping_apply(mut self, direction: Direction) -> Self { match direction { Direction::Up => self.y = self.y.wrapping_add(1), Direction::Down => self.y = self.y.wrapping_sub(1), diff --git a/battlesnake/src/types/simulation.rs b/battlesnake/src/types/simulation.rs index 299c381..8c66432 100644 --- a/battlesnake/src/types/simulation.rs +++ b/battlesnake/src/types/simulation.rs @@ -121,20 +121,29 @@ impl Display for Board { } impl Board { + #[must_use] + pub const fn turn(&self) -> u32 { + self.turn + } + + #[must_use] pub fn num_snakes(&self) -> usize { self.snakes.len() } + #[must_use] pub fn is_food(&self, tile: Coord) -> bool { let index = self.coord_to_linear(tile); self.food[index] } + #[must_use] pub fn is_hazard(&self, tile: Coord) -> bool { let index = self.coord_to_linear(tile); self.hazard[index] } + #[must_use] pub fn is_free(&self, tile: Coord) -> bool { if !(tile.x < self.width && tile.y < self.height) { return false; @@ -182,6 +191,7 @@ impl Board { self.eliminate_snake_standard(); self.update_free_map(); self.spawn_food(); + self.turn += 1; } fn move_standard(&mut self, actions: &[(u8, Direction)]) { @@ -232,22 +242,30 @@ impl Board { } fn feed_snakes_standard(&mut self) { - 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); - let snake = &mut self.snakes[i]; - snake.health = 100; - let tail = snake.tail(); - snake.body.push_back(tail); + if self.constrictor { + for snake in &mut self.snakes { + snake.health = 100; + let tail = snake.tail(); + snake.body.push_back(tail); + } + } 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); + let snake = &mut self.snakes[i]; + snake.health = 100; + let tail = snake.tail(); + snake.body.push_back(tail); + } } } - } - for food_index in eaten_food { - self.food.set(food_index, false); + for food_index in eaten_food { + self.food.set(food_index, false); + } } }