From eb1235be848ad5dfc9300b661a33e3af3f8b5dfb Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Sat, 17 Jul 2021 23:37:03 +0200 Subject: [PATCH 01/21] Implement counting of b2b chain --- bot/Cargo.toml | 3 +++ bot/src/dag.rs | 11 ++++++++++- bot/src/desktop.rs | 16 ++++++++-------- bot/src/evaluation/changed.rs | 6 ++++++ bot/src/evaluation/standard.rs | 6 ++++++ bot/src/lib.rs | 3 +++ bot/src/modes/normal.rs | 8 +++++++- libtetris/Cargo.toml | 3 +++ libtetris/src/board.rs | 23 +++++++++++++++++++++-- opening-book/pc-gen/src/main.rs | 12 +++++++++--- opening-book/src/data.rs | 2 +- tbp/src/main.rs | 6 +++++- 12 files changed, 82 insertions(+), 17 deletions(-) diff --git a/bot/Cargo.toml b/bot/Cargo.toml index 97d41bd..4c01afc 100644 --- a/bot/Cargo.toml +++ b/bot/Cargo.toml @@ -31,3 +31,6 @@ webutil = { git = "https://github.com/MinusKelvin/webutil", rev = "5a54126" } futures-util = "0.3" getrandom = { version = "0.1", features = ["wasm-bindgen"] } console_error_panic_hook = "0.1.6" + +[features] +tetrio_garbage = ["libtetris/tetrio_garbage"] diff --git a/bot/src/dag.rs b/bot/src/dag.rs index 04c08d1..083c46a 100644 --- a/bot/src/dag.rs +++ b/bot/src/dag.rs @@ -164,6 +164,9 @@ struct SimplifiedBoard<'c> { combo: u32, bag: EnumSet, reserve: Piece, + #[cfg(feature = "tetrio_garbage")] + back_to_back: u32, + #[cfg(not(feature = "tetrio_garbage"))] back_to_back: bool, reserve_is_hold: bool, } @@ -599,7 +602,13 @@ impl + 'static, R: Clone + 'static> DagState { plan } - pub fn reset(&mut self, field: [[bool; 10]; 40], b2b: bool, combo: u32) -> Option { + pub fn reset( + &mut self, + field: [[bool; 10]; 40], + #[cfg(feature = "tetrio_garbage")] b2b: u32, + #[cfg(not(feature = "tetrio_garbage"))] b2b: bool, + combo: u32, + ) -> Option { let garbage_lines; if b2b == self.board.b2b_bonus && combo == self.board.combo { let mut b = Board::::new(); diff --git a/bot/src/desktop.rs b/bot/src/desktop.rs index f43592d..df6b4e1 100644 --- a/bot/src/desktop.rs +++ b/bot/src/desktop.rs @@ -94,14 +94,14 @@ impl Interface { /// Note: combo is not the same as the displayed combo in guideline games. Here, it is the /// number of consecutive line clears achieved. So, generally speaking, if "x Combo" appears /// on the screen, you need to use x+1 here. - pub fn reset(&self, field: [[bool; 10]; 40], b2b_active: bool, combo: u32) { - self.send - .send(BotMsg::Reset { - field, - b2b: b2b_active, - combo, - }) - .ok(); + pub fn reset( + &self, + field: [[bool; 10]; 40], + #[cfg(feature = "tetrio_garbage")] b2b: u32, + #[cfg(not(feature = "tetrio_garbage"))] b2b: bool, + combo: u32, + ) { + self.send.send(BotMsg::Reset { field, b2b, combo }).ok(); } /// Specifies a line that Cold Clear should analyze before making any moves. diff --git a/bot/src/evaluation/changed.rs b/bot/src/evaluation/changed.rs index e6528d0..9f70271 100644 --- a/bot/src/evaluation/changed.rs +++ b/bot/src/evaluation/changed.rs @@ -240,6 +240,12 @@ impl Evaluator for Standard { }; acc_eval += self.move_time * move_time; + #[cfg(feature = "tetrio_garbage")] + if board.b2b_bonus > 0 { + transient_eval += self.back_to_back; + } + + #[cfg(not(feature = "tetrio_garbage"))] if board.b2b_bonus { transient_eval += self.back_to_back; } diff --git a/bot/src/evaluation/standard.rs b/bot/src/evaluation/standard.rs index 17f3504..b467f5d 100644 --- a/bot/src/evaluation/standard.rs +++ b/bot/src/evaluation/standard.rs @@ -240,6 +240,12 @@ impl Evaluator for Standard { }; acc_eval += self.move_time * move_time; + #[cfg(feature = "tetrio_garbage")] + if board.b2b_bonus > 0 { + transient_eval += self.back_to_back; + } + + #[cfg(not(feature = "tetrio_garbage"))] if board.b2b_bonus { transient_eval += self.back_to_back; } diff --git a/bot/src/lib.rs b/bot/src/lib.rs index db4b695..6a61b30 100644 --- a/bot/src/lib.rs +++ b/bot/src/lib.rs @@ -40,6 +40,9 @@ enum BotMsg { Reset { #[serde(with = "BigArray")] field: [[bool; 10]; 40], + #[cfg(feature = "tetrio_garbage")] + b2b: u32, + #[cfg(not(feature = "tetrio_garbage"))] b2b: bool, combo: u32, }, diff --git a/bot/src/modes/normal.rs b/bot/src/modes/normal.rs index a858b0d..4470c98 100644 --- a/bot/src/modes/normal.rs +++ b/bot/src/modes/normal.rs @@ -82,7 +82,13 @@ impl BotState { self.tree.add_next_piece(piece); } - pub fn reset(&mut self, field: [[bool; 10]; 40], b2b: bool, combo: u32) { + pub fn reset( + &mut self, + field: [[bool; 10]; 40], + #[cfg(feature = "tetrio_garbage")] b2b: u32, + #[cfg(not(feature = "tetrio_garbage"))] b2b: bool, + combo: u32, + ) { let plan = self.tree.get_plan(); if let Some(garbage_lines) = self.tree.reset(field, b2b, combo) { for path in &mut self.forced_analysis_lines { diff --git a/libtetris/Cargo.toml b/libtetris/Cargo.toml index 951c2f3..d828647 100644 --- a/libtetris/Cargo.toml +++ b/libtetris/Cargo.toml @@ -15,3 +15,6 @@ rand = "0.7.0" fumen = { version = "0.1", optional = true } pcf = { git = "https://github.com/MinusKelvin/pcf", rev = "64cd955", optional = true } + +[features] +tetrio_garbage = [] diff --git a/libtetris/src/board.rs b/libtetris/src/board.rs index 59843d4..82acda3 100644 --- a/libtetris/src/board.rs +++ b/libtetris/src/board.rs @@ -12,6 +12,9 @@ pub struct Board { cells: ArrayVec<[R; 40]>, column_heights: [i32; 10], pub combo: u32, + #[cfg(feature = "tetrio_garbage")] + pub b2b_bonus: u32, + #[cfg(not(feature = "tetrio_garbage"))] pub b2b_bonus: bool, pub hold_piece: Option, next_pieces: VecDeque, @@ -36,6 +39,9 @@ impl Board { cells: [*R::EMPTY; 40].into(), column_heights: [0; 10], combo: 0, + #[cfg(feature = "tetrio_garbage")] + b2b_bonus: 0, + #[cfg(not(feature = "tetrio_garbage"))] b2b_bonus: false, hold_piece: None, next_pieces: VecDeque::new(), @@ -48,13 +54,14 @@ impl Board { field: [[bool; 10]; 40], bag_remain: EnumSet, hold: Option, - b2b: bool, + #[cfg(feature = "tetrio_garbage")] b2b: u32, + #[cfg(not(feature = "tetrio_garbage"))] b2b: bool, combo: u32, ) -> Self { let mut board = Board { cells: [*R::EMPTY; 40].into(), column_heights: [0; 10], - combo: combo, + combo, b2b_bonus: b2b, hold_piece: hold, next_pieces: VecDeque::new(), @@ -179,6 +186,18 @@ impl Board { let mut did_b2b = false; if placement_kind.is_clear() { + #[cfg(feature = "tetrio_garbage")] + if placement_kind.is_hard() { + if self.b2b_bonus > 0 { + garbage_sent += 1; + did_b2b = true; + } + self.b2b_bonus += 1; + } else { + self.b2b_bonus = 0; + } + + #[cfg(not(feature = "tetrio_garbage"))] if placement_kind.is_hard() { if self.b2b_bonus { garbage_sent += 1; diff --git a/opening-book/pc-gen/src/main.rs b/opening-book/pc-gen/src/main.rs index eecd526..ca05632 100644 --- a/opening-book/pc-gen/src/main.rs +++ b/opening-book/pc-gen/src/main.rs @@ -139,7 +139,7 @@ fn main() { [[false; 10]; 40], initial_bag.bag, initial_bag.hold, - false, + Default::default(), 0, ) .into(); @@ -200,8 +200,14 @@ fn process_soln( bag: BagWithHold, ) -> ArrayVec<[(Position, FallingPiece); 10]> { let mut poses = ArrayVec::new(); - let mut pos: Position = - libtetris::Board::new_with_state([[false; 10]; 40], bag.bag, bag.hold, false, 0).into(); + let mut pos: Position = libtetris::Board::new_with_state( + [[false; 10]; 40], + bag.bag, + bag.hold, + Default::default(), + 0, + ) + .into(); let mut b = pcf::BitBoard(0); for p in soln { let mv = libtetris::FallingPiece::from(*p.srs_piece(b).first().unwrap()); diff --git a/opening-book/src/data.rs b/opening-book/src/data.rs index 813e16b..9f12d99 100644 --- a/opening-book/src/data.rs +++ b/opening-book/src/data.rs @@ -32,7 +32,7 @@ impl Position { field[y][x] = self.rows[y] & 1 << x != 0; } } - let mut board = Board::new_with_state(field, self.bag, self.extra, false, 0); + let mut board = Board::new_with_state(field, self.bag, self.extra, Default::default(), 0); let soft_drop = !board.above_stack(&mv); let clear = board.lock_piece(mv).placement_kind.is_clear(); let mut position = *self; diff --git a/tbp/src/main.rs b/tbp/src/main.rs index f9d298e..11c3f07 100644 --- a/tbp/src/main.rs +++ b/tbp/src/main.rs @@ -35,7 +35,11 @@ fn main() -> Result<()> { b.bag = bag_state.iter().copied().map(from_tbp_piece).collect(); } b.combo = combo; - b.b2b_bonus = back_to_back; + #[cfg(feature = "tetrio_garbage")] + let b2b = if back_to_back { 1 } else { 0 }; + #[cfg(not(feature = "tetrio_garbage"))] + let b2b = back_to_back; + b.b2b_bonus = b2b; let mut field = [[false; 10]; 40]; for y in 0..40 { for x in 0..10 { From 9c08e72a314ffbc888c71b609e81a443a291f354 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Sun, 18 Jul 2021 00:04:19 +0200 Subject: [PATCH 02/21] Implement tetr.io garbage --- cc-client/Cargo.toml | 3 +++ libtetris/src/board.rs | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/cc-client/Cargo.toml b/cc-client/Cargo.toml index f1efee7..e85dc87 100644 --- a/cc-client/Cargo.toml +++ b/cc-client/Cargo.toml @@ -33,3 +33,6 @@ wasm-bindgen-futures = "0.4" [build-dependencies] build-utils = { git = "https://github.com/MinusKelvin/game-util-rs", rev = "4a39948" } + +[features] +tetrio_garbage = ["cold-clear/tetrio_garbage"] \ No newline at end of file diff --git a/libtetris/src/board.rs b/libtetris/src/board.rs index 82acda3..1201c0a 100644 --- a/libtetris/src/board.rs +++ b/libtetris/src/board.rs @@ -189,7 +189,6 @@ impl Board { #[cfg(feature = "tetrio_garbage")] if placement_kind.is_hard() { if self.b2b_bonus > 0 { - garbage_sent += 1; did_b2b = true; } self.b2b_bonus += 1; @@ -208,11 +207,30 @@ impl Board { self.b2b_bonus = false; } - if self.combo as usize >= COMBO_GARBAGE.len() { - garbage_sent += COMBO_GARBAGE.last().unwrap(); - } else { - garbage_sent += COMBO_GARBAGE[self.combo as usize]; - } + #[cfg(feature = "tetrio_garbage")] + { + // garbage here is an f32 to make the calculation as accurate to the + // tetrio javascript code as possible + const B2B_BONUS_LOG: f32 = 0.8; + const COMBO_MINIFIER_LOG: f32 = 1.25; + + let mut garbage = garbage_sent as f32; + if did_b2b { + let log = ((self.b2b_bonus as f32 - 1.0) * B2B_BONUS_LOG).ln_1p(); + let a = (1.0 + log).floor() + if self.b2b_bonus == 2 { 0.0 } else { (1.0 + log.fract()) / 3.0 }; + + garbage += a; + } + + garbage *= 1.0 + 0.25 * self.combo as f32; + garbage = (self.combo as f32 * COMBO_MINIFIER_LOG).ln_1p().max(garbage); + garbage_sent = garbage as u32; + } + + #[cfg(not(feature = "tetrio_garbage"))] + { + garbage_sent += COMBO_GARBAGE[(self.combo as usize).min(COMBO_GARBAGE.len() - 1)]; + } self.combo += 1; } else { @@ -220,6 +238,11 @@ impl Board { } let perfect_clear = self.column_heights == [0; 10]; + #[cfg(feature = "tetrio_garbage")] + if perfect_clear { + garbage_sent += 10; + } + #[cfg(not(feature = "tetrio_garbage"))] if perfect_clear { garbage_sent = 10; } @@ -250,7 +273,7 @@ impl Board { hold } - pub fn next_queue<'a>(&'a self) -> impl DoubleEndedIterator + 'a { + pub fn next_queue<'a>(&'a self) -> impl DoubleEndedIterator + 'a { self.next_pieces.iter().copied() } From 2461a552953982f955ac4d95c885e89de9132518 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Sun, 18 Jul 2021 02:10:03 +0200 Subject: [PATCH 03/21] Implement tetr.io evaluation --- bot/src/evaluation/changed.rs | 51 ++++++++++++++++++++++++++++------ bot/src/evaluation/standard.rs | 50 +++++++++++++++++++++++++++------ libtetris/src/board.rs | 3 +- 3 files changed, 86 insertions(+), 18 deletions(-) diff --git a/bot/src/evaluation/changed.rs b/bot/src/evaluation/changed.rs index 9f70271..3a3e33b 100644 --- a/bot/src/evaluation/changed.rs +++ b/bot/src/evaluation/changed.rs @@ -1,11 +1,19 @@ -use libtetris::*; use serde::{Deserialize, Serialize}; +use libtetris::*; + use super::*; #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(default)] pub struct Standard { + #[cfg(feature = "tetrio_garbage")] + pub b2b_chain: i32, + #[cfg(feature = "tetrio_garbage")] + pub b2b_chain_log: i32, + #[cfg(feature = "tetrio_garbage")] + pub combo_multiplier: i32, + pub back_to_back: i32, pub bumpiness: i32, pub bumpiness_sq: i32, @@ -49,6 +57,13 @@ pub struct Standard { impl Default for Standard { fn default() -> Self { Standard { + #[cfg(feature = "tetrio_garbage")] + b2b_chain: 0, + #[cfg(feature = "tetrio_garbage")] + b2b_chain_log: 0, + #[cfg(feature = "tetrio_garbage")] + combo_multiplier: 0, + back_to_back: 52, bumpiness: -24, bumpiness_sq: -7, @@ -94,6 +109,13 @@ impl Default for Standard { impl Standard { pub fn fast_config() -> Self { Standard { + #[cfg(feature = "tetrio_garbage")] + b2b_chain: 0, + #[cfg(feature = "tetrio_garbage")] + b2b_chain_log: 0, + #[cfg(feature = "tetrio_garbage")] + combo_multiplier: 0, + back_to_back: 10, bumpiness: -7, bumpiness_sq: -28, @@ -156,8 +178,8 @@ impl Evaluator for Standard { for mv in candidates.into_iter() { if incoming == 0 || mv.board.column_heights()[3..6] - .iter() - .all(|h| incoming as i32 - mv.lock.garbage_sent as i32 + h <= 20) + .iter() + .all(|h| incoming as i32 - mv.lock.garbage_sent as i32 + h <= 20) { return mv; } @@ -189,6 +211,12 @@ impl Evaluator for Standard { if lock.b2b { acc_eval += self.b2b_clear; } + + #[cfg(feature = "tetrio_garbage")] + if lock.combo > Some(0) { + acc_eval += self.combo_garbage * lock.garbage_sent as i32; + } + #[cfg(not(feature = "tetrio_garbage"))] if let Some(combo) = lock.combo { let combo = combo.min(11) as usize; acc_eval += self.combo_garbage * libtetris::COMBO_GARBAGE[combo] as i32; @@ -244,12 +272,19 @@ impl Evaluator for Standard { if board.b2b_bonus > 0 { transient_eval += self.back_to_back; } - #[cfg(not(feature = "tetrio_garbage"))] if board.b2b_bonus { transient_eval += self.back_to_back; } + #[cfg(feature = "tetrio_garbage")] + { + transient_eval += self.b2b_chain * board.b2b_bonus as i32; + transient_eval += (self.b2b_chain_log as f32 * (board.b2b_bonus as f32).ln()) as i32; + + transient_eval += self.combo_multiplier * board.combo as i32; + } + let highest_point = *board.column_heights().iter().max().unwrap() as i32; transient_eval += self.top_quarter * (highest_point - 15).max(0); transient_eval += self.top_half * (highest_point - 10).max(0); @@ -327,10 +362,10 @@ impl Evaluator for Standard { if self.row_transitions != 0 { transient_eval += self.row_transitions * (0..40) - .map(|y| *board.get_row(y)) - .map(|r| (r | 0b1_00000_00000) ^ (1 | r << 1)) - .map(|d| d.count_ones() as i32) - .sum::(); + .map(|y| *board.get_row(y)) + .map(|r| (r | 0b1_00000_00000) ^ (1 | r << 1)) + .map(|d| d.count_ones() as i32) + .sum::(); } if self.bumpiness | self.bumpiness_sq != 0 { diff --git a/bot/src/evaluation/standard.rs b/bot/src/evaluation/standard.rs index b467f5d..c26f896 100644 --- a/bot/src/evaluation/standard.rs +++ b/bot/src/evaluation/standard.rs @@ -1,11 +1,19 @@ -use libtetris::*; use serde::{Deserialize, Serialize}; +use libtetris::*; + use super::*; #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(default)] pub struct Standard { + #[cfg(feature = "tetrio_garbage")] + pub b2b_chain: i32, + #[cfg(feature = "tetrio_garbage")] + pub b2b_chain_log: i32, + #[cfg(feature = "tetrio_garbage")] + pub combo_multiplier: i32, + pub back_to_back: i32, pub bumpiness: i32, pub bumpiness_sq: i32, @@ -49,6 +57,13 @@ pub struct Standard { impl Default for Standard { fn default() -> Self { Standard { + #[cfg(feature = "tetrio_garbage")] + b2b_chain: 0, + #[cfg(feature = "tetrio_garbage")] + b2b_chain_log: 0, + #[cfg(feature = "tetrio_garbage")] + combo_multiplier: 0, + back_to_back: 52, bumpiness: -24, bumpiness_sq: -7, @@ -94,6 +109,13 @@ impl Default for Standard { impl Standard { pub fn fast_config() -> Self { Standard { + #[cfg(feature = "tetrio_garbage")] + b2b_chain: 0, + #[cfg(feature = "tetrio_garbage")] + b2b_chain_log: 0, + #[cfg(feature = "tetrio_garbage")] + combo_multiplier: 0, + back_to_back: 10, bumpiness: -7, bumpiness_sq: -28, @@ -156,8 +178,8 @@ impl Evaluator for Standard { for mv in candidates.into_iter() { if incoming == 0 || mv.board.column_heights()[3..6] - .iter() - .all(|h| incoming as i32 - mv.lock.garbage_sent as i32 + h <= 20) + .iter() + .all(|h| incoming as i32 - mv.lock.garbage_sent as i32 + h <= 20) { return mv; } @@ -189,6 +211,11 @@ impl Evaluator for Standard { if lock.b2b { acc_eval += self.b2b_clear; } + #[cfg(feature = "tetrio_garbage")] + if lock.combo > Some(0) { + acc_eval += self.combo_garbage * lock.garbage_sent as i32; + } + #[cfg(not(feature = "tetrio_garbage"))] if let Some(combo) = lock.combo { let combo = combo.min(11) as usize; acc_eval += self.combo_garbage * libtetris::COMBO_GARBAGE[combo] as i32; @@ -244,12 +271,19 @@ impl Evaluator for Standard { if board.b2b_bonus > 0 { transient_eval += self.back_to_back; } - #[cfg(not(feature = "tetrio_garbage"))] if board.b2b_bonus { transient_eval += self.back_to_back; } + #[cfg(feature = "tetrio_garbage")] + { + transient_eval += self.b2b_chain * board.b2b_bonus as i32; + transient_eval += (self.b2b_chain_log as f32 * (board.b2b_bonus as f32).ln()) as i32; + + transient_eval += self.combo_multiplier * board.combo as i32; + } + let highest_point = *board.column_heights().iter().max().unwrap() as i32; transient_eval += self.top_quarter * (highest_point - 15).max(0); transient_eval += self.top_half * (highest_point - 10).max(0); @@ -327,10 +361,10 @@ impl Evaluator for Standard { if self.row_transitions != 0 { transient_eval += self.row_transitions * (0..40) - .map(|y| *board.get_row(y)) - .map(|r| (r | 0b1_00000_00000) ^ (1 | r << 1)) - .map(|d| d.count_ones() as i32) - .sum::(); + .map(|y| *board.get_row(y)) + .map(|r| (r | 0b1_00000_00000) ^ (1 | r << 1)) + .map(|d| d.count_ones() as i32) + .sum::(); } if self.bumpiness | self.bumpiness_sq != 0 { diff --git a/libtetris/src/board.rs b/libtetris/src/board.rs index 1201c0a..6a9a12d 100644 --- a/libtetris/src/board.rs +++ b/libtetris/src/board.rs @@ -217,9 +217,8 @@ impl Board { let mut garbage = garbage_sent as f32; if did_b2b { let log = ((self.b2b_bonus as f32 - 1.0) * B2B_BONUS_LOG).ln_1p(); - let a = (1.0 + log).floor() + if self.b2b_bonus == 2 { 0.0 } else { (1.0 + log.fract()) / 3.0 }; - garbage += a; + garbage += (1.0 + log).floor() + if self.b2b_bonus == 2 { 0.0 } else { (1.0 + log.fract()) / 3.0 }; } garbage *= 1.0 + 0.25 * self.combo as f32; From f0f9b2b9ddc810996ccd57fc00434a693b091dc2 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Sun, 18 Jul 2021 07:50:45 +0200 Subject: [PATCH 04/21] Add tetr.io values to optimizer --- bot/src/evaluation/standard.rs | 2 +- optimizer/Cargo.toml | 3 +++ optimizer/src/battle.rs | 14 ++++++++++---- optimizer/src/mutate.rs | 16 ++++++++++++++-- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/bot/src/evaluation/standard.rs b/bot/src/evaluation/standard.rs index c26f896..39721bb 100644 --- a/bot/src/evaluation/standard.rs +++ b/bot/src/evaluation/standard.rs @@ -279,7 +279,7 @@ impl Evaluator for Standard { #[cfg(feature = "tetrio_garbage")] { transient_eval += self.b2b_chain * board.b2b_bonus as i32; - transient_eval += (self.b2b_chain_log as f32 * (board.b2b_bonus as f32).ln()) as i32; + transient_eval += (self.b2b_chain_log as f32 * (board.b2b_bonus as f32).ln_1p()) as i32; transient_eval += self.combo_multiplier * board.combo as i32; } diff --git a/optimizer/Cargo.toml b/optimizer/Cargo.toml index bc29e1f..c9ddeea 100644 --- a/optimizer/Cargo.toml +++ b/optimizer/Cargo.toml @@ -15,3 +15,6 @@ serde_json = "1" bincode = "1" libflate = "0.1" rand = "0.7.0" + +[features] +tetrio_garbage = ["cold-clear/tetrio_garbage"] \ No newline at end of file diff --git a/optimizer/src/battle.rs b/optimizer/src/battle.rs index 72302c0..9e9a45b 100644 --- a/optimizer/src/battle.rs +++ b/optimizer/src/battle.rs @@ -1,10 +1,11 @@ use std::collections::VecDeque; +use rand::prelude::*; +use serde::{Deserialize, Serialize}; + use battle::{Battle, Event, GameConfig, PieceMoveExecutor, Replay}; use cold_clear::evaluation::Evaluator; use libtetris::{Board, ColoredRow, Controller, FallingPiece}; -use rand::prelude::*; -use serde::{Deserialize, Serialize}; pub struct BotInput { pub controller: Controller, @@ -93,9 +94,14 @@ pub fn do_battle( p1: impl Evaluator + Clone, p2: impl Evaluator + Clone, ) -> Option<(InfoReplay, bool)> { + #[cfg(feature = "tetrio_garbage")] + let config = GameConfig::fast_config(); + #[cfg(not(feature = "tetrio_garbage"))] + let config = GameConfig::default(); + let mut battle = Battle::new( - GameConfig::default(), - GameConfig::default(), + config, + config, thread_rng().gen(), thread_rng().gen(), thread_rng().gen(), diff --git a/optimizer/src/mutate.rs b/optimizer/src/mutate.rs index 0f7e118..0f75f5d 100644 --- a/optimizer/src/mutate.rs +++ b/optimizer/src/mutate.rs @@ -12,6 +12,12 @@ pub trait Mutateable: Default { impl Mutateable for Standard { fn generate(sub_name: String) -> Self { Standard { + #[cfg(feature = "tetrio_garbage")] + b2b_chain: thread_rng().gen_range(-999, 1000), + #[cfg(feature = "tetrio_garbage")] + b2b_chain_log: thread_rng().gen_range(-999, 1000), + #[cfg(feature = "tetrio_garbage")] + combo_multiplier: thread_rng().gen_range(-999, 1000), back_to_back: thread_rng().gen_range(-999, 1000), bumpiness: thread_rng().gen_range(-999, 1000), bumpiness_sq: thread_rng().gen_range(-999, 1000), @@ -64,13 +70,19 @@ impl Mutateable for Standard { use_bag: true, timed_jeopardy: true, - stack_pc_damage: false, + stack_pc_damage: cfg!(feature = "tetrio_garbage"), sub_name: Some(sub_name), } } fn crossover(parent1: &Self, parent2: &Self, sub_name: String) -> Self { Standard { + #[cfg(feature = "tetrio_garbage")] + b2b_chain: crossover_gene(parent1.b2b_chain, parent2.b2b_chain), + #[cfg(feature = "tetrio_garbage")] + b2b_chain_log: crossover_gene(parent1.b2b_chain_log, parent2.b2b_chain_log), + #[cfg(feature = "tetrio_garbage")] + combo_multiplier: crossover_gene(parent1.combo_multiplier, parent2.combo_multiplier), back_to_back: crossover_gene(parent1.back_to_back, parent2.back_to_back), bumpiness: crossover_gene(parent1.bumpiness, parent2.bumpiness), bumpiness_sq: crossover_gene(parent1.bumpiness_sq, parent2.bumpiness_sq), @@ -123,7 +135,7 @@ impl Mutateable for Standard { use_bag: true, timed_jeopardy: true, - stack_pc_damage: false, + stack_pc_damage: cfg!(feature = "tetrio_garbage"), sub_name: Some(sub_name), } } From 3647f2271a223aefe80ea90627894d1b0bdcfba4 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Tue, 20 Jul 2021 23:33:21 +0200 Subject: [PATCH 05/21] Change from f32 to f64 (what JS uses) --- libtetris/src/board.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libtetris/src/board.rs b/libtetris/src/board.rs index 6a9a12d..6df5a9e 100644 --- a/libtetris/src/board.rs +++ b/libtetris/src/board.rs @@ -209,20 +209,20 @@ impl Board { #[cfg(feature = "tetrio_garbage")] { - // garbage here is an f32 to make the calculation as accurate to the + // garbage here is an f64 to make the calculation as accurate to the // tetrio javascript code as possible - const B2B_BONUS_LOG: f32 = 0.8; - const COMBO_MINIFIER_LOG: f32 = 1.25; + const B2B_BONUS_LOG: f64 = 0.8; + const COMBO_MINIFIER_LOG: f64 = 1.25; - let mut garbage = garbage_sent as f32; + let mut garbage = garbage_sent as f64; if did_b2b { - let log = ((self.b2b_bonus as f32 - 1.0) * B2B_BONUS_LOG).ln_1p(); + let log = ((self.b2b_bonus as f64 - 1.0) * B2B_BONUS_LOG).ln_1p(); garbage += (1.0 + log).floor() + if self.b2b_bonus == 2 { 0.0 } else { (1.0 + log.fract()) / 3.0 }; } - garbage *= 1.0 + 0.25 * self.combo as f32; - garbage = (self.combo as f32 * COMBO_MINIFIER_LOG).ln_1p().max(garbage); + garbage *= 1.0 + 0.25 * self.combo as f64; + garbage = (self.combo as f64 * COMBO_MINIFIER_LOG).ln_1p().max(garbage); garbage_sent = garbage as u32; } From 6e45f05d18d9c504ee8289e713045d6d42855633 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Wed, 21 Jul 2021 00:15:05 +0200 Subject: [PATCH 06/21] Always add (combo_)garbage to eval for tetrio --- bot/src/evaluation/changed.rs | 2 +- bot/src/evaluation/standard.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bot/src/evaluation/changed.rs b/bot/src/evaluation/changed.rs index 3a3e33b..1acf3cb 100644 --- a/bot/src/evaluation/changed.rs +++ b/bot/src/evaluation/changed.rs @@ -213,7 +213,7 @@ impl Evaluator for Standard { } #[cfg(feature = "tetrio_garbage")] - if lock.combo > Some(0) { + if lock.combo.is_some() { acc_eval += self.combo_garbage * lock.garbage_sent as i32; } #[cfg(not(feature = "tetrio_garbage"))] diff --git a/bot/src/evaluation/standard.rs b/bot/src/evaluation/standard.rs index 39721bb..79af36c 100644 --- a/bot/src/evaluation/standard.rs +++ b/bot/src/evaluation/standard.rs @@ -212,7 +212,7 @@ impl Evaluator for Standard { acc_eval += self.b2b_clear; } #[cfg(feature = "tetrio_garbage")] - if lock.combo > Some(0) { + if lock.combo.is_some() { acc_eval += self.combo_garbage * lock.garbage_sent as i32; } #[cfg(not(feature = "tetrio_garbage"))] From d84f86309d5321b099ebc8e4560159213058d811 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Tue, 10 Aug 2021 03:17:46 +0200 Subject: [PATCH 07/21] Modify eval --- bot/src/evaluation/changed.rs | 71 +++++++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/bot/src/evaluation/changed.rs b/bot/src/evaluation/changed.rs index 1acf3cb..139b5ea 100644 --- a/bot/src/evaluation/changed.rs +++ b/bot/src/evaluation/changed.rs @@ -107,15 +107,9 @@ impl Default for Standard { } impl Standard { + #[cfg(not(feature = "tetrio_garbage"))] pub fn fast_config() -> Self { Standard { - #[cfg(feature = "tetrio_garbage")] - b2b_chain: 0, - #[cfg(feature = "tetrio_garbage")] - b2b_chain_log: 0, - #[cfg(feature = "tetrio_garbage")] - combo_multiplier: 0, - back_to_back: 10, bumpiness: -7, bumpiness_sq: -28, @@ -154,6 +148,67 @@ impl Standard { sub_name: None, } } + + #[cfg(feature = "tetrio_garbage")] + pub fn fast_config() -> Self { + Standard { + b2b_chain: 214, + b2b_chain_log: 1, + combo_multiplier: 41, + back_to_back: 56, + bumpiness: 11, + bumpiness_sq: -26, + row_transitions: -124, + height: -58, + top_half: -765, + top_quarter: -499, + jeopardy: -5, + cavity_cells: -168, + cavity_cells_sq: -33, + overhang_cells: -54, + overhang_cells_sq: 14, + covered_cells: 9, + covered_cells_sq: -17, + tslot: [ + 112, + 151, + 35, + 380, + ], + well_depth: 74, + max_well_depth: 31, + well_column: [ + 24, + 6, + 24, + 56, + 23, + 310, + 85, + 142, + 118, + 33, + ], + b2b_clear: 128, + clear1: -141, + clear2: -93, + clear3: -63, + clear4: 385, + tspin1: 126, + tspin2: 559, + tspin3: 602, + mini_tspin1: -157, + mini_tspin2: -215, + perfect_clear: 988, + combo_garbage: 166, + move_time: -4, + wasted_t: -188, + use_bag: true, + timed_jeopardy: true, + stack_pc_damage: true, + sub_name: None, + } + } } impl Evaluator for Standard { @@ -280,7 +335,7 @@ impl Evaluator for Standard { #[cfg(feature = "tetrio_garbage")] { transient_eval += self.b2b_chain * board.b2b_bonus as i32; - transient_eval += (self.b2b_chain_log as f32 * (board.b2b_bonus as f32).ln()) as i32; + transient_eval += (self.b2b_chain_log as f32 * (board.b2b_bonus as f32).ln_1p()) as i32; transient_eval += self.combo_multiplier * board.combo as i32; } From 425bc91f5ade4216073fafc5be6f208ec576c1a1 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Tue, 10 Aug 2021 20:52:36 +0200 Subject: [PATCH 08/21] Copy weights from changed.rs to standard.rs --- bot/src/evaluation/standard.rs | 69 ++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/bot/src/evaluation/standard.rs b/bot/src/evaluation/standard.rs index 79af36c..e9ded7d 100644 --- a/bot/src/evaluation/standard.rs +++ b/bot/src/evaluation/standard.rs @@ -107,15 +107,9 @@ impl Default for Standard { } impl Standard { + #[cfg(not(feature = "tetrio_garbage"))] pub fn fast_config() -> Self { Standard { - #[cfg(feature = "tetrio_garbage")] - b2b_chain: 0, - #[cfg(feature = "tetrio_garbage")] - b2b_chain_log: 0, - #[cfg(feature = "tetrio_garbage")] - combo_multiplier: 0, - back_to_back: 10, bumpiness: -7, bumpiness_sq: -28, @@ -154,6 +148,67 @@ impl Standard { sub_name: None, } } + + #[cfg(feature = "tetrio_garbage")] + pub fn fast_config() -> Self { + Standard { + b2b_chain: 214, + b2b_chain_log: 1, + combo_multiplier: 41, + back_to_back: 56, + bumpiness: 11, + bumpiness_sq: -26, + row_transitions: -124, + height: -58, + top_half: -765, + top_quarter: -499, + jeopardy: -5, + cavity_cells: -168, + cavity_cells_sq: -33, + overhang_cells: -54, + overhang_cells_sq: 14, + covered_cells: 9, + covered_cells_sq: -17, + tslot: [ + 112, + 151, + 35, + 380, + ], + well_depth: 74, + max_well_depth: 31, + well_column: [ + 24, + 6, + 24, + 56, + 23, + 310, + 85, + 142, + 118, + 33, + ], + b2b_clear: 128, + clear1: -141, + clear2: -93, + clear3: -63, + clear4: 385, + tspin1: 126, + tspin2: 559, + tspin3: 602, + mini_tspin1: -157, + mini_tspin2: -215, + perfect_clear: 988, + combo_garbage: 166, + move_time: -4, + wasted_t: -188, + use_bag: true, + timed_jeopardy: true, + stack_pc_damage: true, + sub_name: None, + } + } } impl Evaluator for Standard { From 3190b919cf824c42880881885b92e759de81c73a Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Fri, 20 Aug 2021 17:27:21 +1000 Subject: [PATCH 09/21] make diskbook only available on desktop --- opening-book/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opening-book/src/lib.rs b/opening-book/src/lib.rs index 479e23a..9c3e791 100644 --- a/opening-book/src/lib.rs +++ b/opening-book/src/lib.rs @@ -25,12 +25,14 @@ pub struct Book(BookType); enum BookType { Memory(MemoryBook), + #[cfg(not(target_arch = "wasm32"))] Disk(DiskBook), } #[derive(Clone, Serialize, Deserialize)] pub struct MemoryBook(HashMap); +#[cfg(not(target_arch = "wasm32"))] pub struct DiskBook { index: HashMap, file: File, @@ -144,6 +146,7 @@ impl MemoryBook { } } + #[cfg(not(target_arch = "wasm32"))] pub fn save_as_disk_book(&self, mut to: impl Write) -> bincode::Result<()> { to.write_all(&DiskBook::MAGIC_BYTES)?; let mut index = HashMap::with_capacity(self.0.len()); @@ -188,6 +191,7 @@ impl MemoryBook { } } +#[cfg(not(target_arch = "wasm32"))] impl DiskBook { const MAGIC_BYTES: [u8; 4] = [0xB7, 0x1E, 0xA0, 0x73]; const MAGIC: u32 = u32::from_le_bytes(Self::MAGIC_BYTES); @@ -282,6 +286,7 @@ impl Book { pub fn suggest_move(&self, state: &Board) -> Option { match &self.0 { BookType::Memory(b) => b.suggest_move(state), + #[cfg(not(target_arch = "wasm32"))] BookType::Disk(b) => b.suggest_move(state), } } @@ -293,6 +298,7 @@ impl From for Book { } } +#[cfg(not(target_arch = "wasm32"))] impl From for Book { fn from(v: DiskBook) -> Book { Book(BookType::Disk(v)) From cff85e5960cdc53a28d54fed3f424c843da26968 Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Fri, 20 Aug 2021 17:39:39 +1000 Subject: [PATCH 10/21] make compile on web again --- bot/src/modes/mod.rs | 5 ++++- bot/src/web.rs | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/bot/src/modes/mod.rs b/bot/src/modes/mod.rs index 5905b42..508decb 100644 --- a/bot/src/modes/mod.rs +++ b/bot/src/modes/mod.rs @@ -260,7 +260,10 @@ pub mod pcloop { pub fn think(&mut self) -> Option { unreachable!() } - pub fn next_move(&mut self) -> Result<(Move, Info), bool> { + pub fn suggest_move(&mut self) -> Result<(Move, Info), bool> { + unreachable!() + } + pub fn play_move(&mut self, mv: FallingPiece) -> bool { unreachable!() } pub fn solution(&mut self, _: Option>) { diff --git a/bot/src/web.rs b/bot/src/web.rs index 4582551..f6ff3c0 100644 --- a/bot/src/web.rs +++ b/bot/src/web.rs @@ -8,7 +8,6 @@ use webutil::worker::{Worker, WorkerSender}; use crate::evaluation::Evaluator; use crate::modes::{ModeSwitchedBot, Task, TaskResult}; -use crate::moves::Move; use crate::{BotMsg, BotPollState, Info, Options}; // trait aliases (#41517) would make my life SOOOOO much easier @@ -57,11 +56,11 @@ impl Interface { /// It is recommended that you call this function the frame before the piece spawns so that the /// bot has time to finish its current thinking cycle and supply the move. /// - /// Once a move is chosen, the bot will update its internal state to the result of the piece - /// being placed correctly and the move will become available by calling `poll_next_move`. - pub fn request_next_move(&self, incoming: u32) { + /// Once a move is chosen, the move will become available by calling `poll_next_move` or + /// `block_next_move`. To update the bot state according to this move, call `play_next_move`. + pub fn suggest_next_move(&self, incoming: u32) { if let Some(worker) = &self.0 { - worker.send(&BotMsg::NextMove(incoming)).ok().unwrap(); + worker.send(&BotMsg::SuggestMove(incoming)).ok().unwrap(); } } @@ -90,7 +89,7 @@ impl Interface { /// Waits for the bot to provide the previously requested move. /// /// `None` is returned if the bot is dead. - pub async fn next_move(&mut self) -> Option<(Move, Info)> { + pub async fn block_next_move(&mut self) -> Option<(Move, Info)> { match self.0.as_ref()?.recv().await { Some(v) => Some(v), None => { @@ -100,6 +99,13 @@ impl Interface { } } + /// Updates the internal bot state according to the move played. + pub fn play_next_move(&self, mv: FallingPiece) { + if let Some(worker) = &self.0 { + worker.send(&BotMsg::PlayMove(mv)).ok(); + } + } + /// Adds a new piece to the end of the queue. /// /// If speculation is enabled, the piece *must* be in the bag. For example, if in the current @@ -173,7 +179,7 @@ fn bot_thread( // (books tend to be very large, possibly not useful?) loop { - let new_tasks = state.think(&eval, |mv, info| send.send(&Some((mv, info)))); + let new_tasks = state.think(&eval, |v| send.send(&Some(v))); for task in new_tasks { task_send.send(task).ok().unwrap(); } From a6c469f7e5cb61425149911ae031a5fcbcca78c0 Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Fri, 20 Aug 2021 17:40:05 +1000 Subject: [PATCH 11/21] make tbp interface work on web hopefully --- tbp/Cargo.toml | 8 +- tbp/src/lib.rs | 200 ++++++++++++++++++++++++++++++++++++++++++++++++ tbp/src/main.rs | 172 ++++------------------------------------- 3 files changed, 220 insertions(+), 160 deletions(-) create mode 100644 tbp/src/lib.rs diff --git a/tbp/Cargo.toml b/tbp/Cargo.toml index d6ebf56..95d4e45 100644 --- a/tbp/Cargo.toml +++ b/tbp/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tbp" +name = "cc-tbp" version = "0.1.0" authors = ["MinusKelvin "] edition = "2018" @@ -8,6 +8,7 @@ edition = "2018" [dependencies] cold-clear = { path = "../bot" } +futures = "0.3.16" libtetris = { path = "../libtetris" } serde = { version = "1.0.124", features = ["derive"] } serde-big-array = "0.3.2" @@ -19,3 +20,8 @@ branch = "main" features = [ "randomizer" ] + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = { version = "0.2.73", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4.23" +web-sys = { version = "0.3.50", features = ["DedicatedWorkerGlobalScope"] } diff --git a/tbp/src/lib.rs b/tbp/src/lib.rs new file mode 100644 index 0000000..1d7e9df --- /dev/null +++ b/tbp/src/lib.rs @@ -0,0 +1,200 @@ +use std::convert::Infallible; + +use futures::{Sink, SinkExt, Stream, StreamExt}; +use tbp::randomizer::RandomizerState; +use tbp::{BotMessage, FrontendMessage}; + +pub async fn run( + mut incoming: impl Stream + Unpin, + mut outgoing: impl Sink + Unpin, +) { + let mut bot = None; + + outgoing + .send(BotMessage::Info { + name: "Cold Clear".to_string(), + version: "2020-05-05".to_string(), + author: "MinusKelvin".to_string(), + features: tbp::Feature::enabled(), + }) + .await + .unwrap(); + + while let Some(msg) = incoming.next().await { + match msg { + FrontendMessage::Rules { randomizer: _ } => { + outgoing.send(BotMessage::Ready).await.unwrap(); + } + FrontendMessage::Start { + hold, + queue, + combo, + back_to_back, + board, + randomizer, + } => { + let mut b = libtetris::Board::new(); + b.hold_piece = hold.map(from_tbp_piece); + for piece in queue { + b.add_next_piece(from_tbp_piece(piece)); + } + if let RandomizerState::SevenBag { bag_state } = &randomizer { + b.bag = bag_state.iter().copied().map(from_tbp_piece).collect(); + } + b.combo = combo; + b.b2b_bonus = back_to_back; + let mut field = [[false; 10]; 40]; + for y in 0..40 { + for x in 0..10 { + field[y][x] = board[y][x].is_some(); + } + } + b.set_field(field); + + let options = cold_clear::Options { + speculate: matches!(randomizer, RandomizerState::SevenBag { .. }), + ..Default::default() + }; + let eval = cold_clear::evaluation::Standard::default(); + + #[cfg(not(target_arch = "wasm32"))] + { + bot = Some(cold_clear::Interface::launch(b, options, eval, None)); + } + #[cfg(target_arch = "wasm32")] + { + bot = Some(cold_clear::Interface::launch("worker.js", b, options, eval).await); + } + } + FrontendMessage::Stop => { + bot = None; + } + FrontendMessage::Suggest => { + if let Some(ref mut bot) = bot { + bot.suggest_next_move(0); + #[cfg(not(target_arch = "wasm32"))] + let mvs = bot.block_next_move(); + #[cfg(target_arch = "wasm32")] + let mvs = bot.block_next_move().await; + let moves = + mvs.map_or(vec![], |(mv, _)| vec![to_tbp_move(mv.expected_location)]); + outgoing + .send(BotMessage::Suggestion { moves }) + .await + .unwrap(); + } + } + FrontendMessage::Play { mv } => { + if let Some(ref mut bot) = bot { + bot.play_next_move(from_tbp_move(mv)); + } + } + FrontendMessage::NewPiece { piece } => { + if let Some(ref mut bot) = bot { + bot.add_next_piece(from_tbp_piece(piece)); + } + } + FrontendMessage::Quit => return, + } + } +} + +fn from_tbp_piece(v: tbp::Piece) -> libtetris::Piece { + match v { + tbp::Piece::I => libtetris::Piece::I, + tbp::Piece::O => libtetris::Piece::O, + tbp::Piece::T => libtetris::Piece::T, + tbp::Piece::L => libtetris::Piece::L, + tbp::Piece::J => libtetris::Piece::J, + tbp::Piece::S => libtetris::Piece::S, + tbp::Piece::Z => libtetris::Piece::Z, + } +} + +fn to_tbp_piece(v: libtetris::Piece) -> tbp::Piece { + match v { + libtetris::Piece::I => tbp::Piece::I, + libtetris::Piece::O => tbp::Piece::O, + libtetris::Piece::T => tbp::Piece::T, + libtetris::Piece::L => tbp::Piece::L, + libtetris::Piece::J => tbp::Piece::J, + libtetris::Piece::S => tbp::Piece::S, + libtetris::Piece::Z => tbp::Piece::Z, + } +} + +fn from_tbp_move(v: tbp::Move) -> libtetris::FallingPiece { + libtetris::FallingPiece { + kind: libtetris::PieceState( + from_tbp_piece(v.location.kind), + match v.location.orientation { + tbp::Orientation::North => libtetris::RotationState::North, + tbp::Orientation::South => libtetris::RotationState::South, + tbp::Orientation::East => libtetris::RotationState::East, + tbp::Orientation::West => libtetris::RotationState::West, + }, + ), + x: v.location.x, + y: v.location.y, + tspin: match v.spin { + tbp::Spin::None => libtetris::TspinStatus::None, + tbp::Spin::Mini => libtetris::TspinStatus::Mini, + tbp::Spin::Full => libtetris::TspinStatus::Full, + }, + } +} + +fn to_tbp_move(v: libtetris::FallingPiece) -> tbp::Move { + tbp::Move { + location: tbp::PieceLocation { + kind: to_tbp_piece(v.kind.0), + orientation: match v.kind.1 { + libtetris::RotationState::North => tbp::Orientation::North, + libtetris::RotationState::South => tbp::Orientation::South, + libtetris::RotationState::East => tbp::Orientation::East, + libtetris::RotationState::West => tbp::Orientation::West, + }, + x: v.x, + y: v.y, + }, + spin: match v.tspin { + libtetris::TspinStatus::None => tbp::Spin::None, + libtetris::TspinStatus::Mini => tbp::Spin::Mini, + libtetris::TspinStatus::Full => tbp::Spin::Full, + }, + } +} + +#[cfg(target_arch = "wasm32")] +mod web { + use futures::channel::mpsc::unbounded; + use wasm_bindgen::{prelude::*, JsCast}; + use wasm_bindgen_futures::spawn_local; + + #[wasm_bindgen] + extern "C" { + #[wasm_bindgen(js_name = self)] + static global: web_sys::DedicatedWorkerGlobalScope; + } + + #[wasm_bindgen(start)] + pub fn start() { + let (send, incoming) = unbounded(); + let closure = Closure::wrap(Box::new(move |e: web_sys::MessageEvent| { + send.unbounded_send(e.data().into_serde().unwrap()).unwrap(); + }) as Box); + + global + .add_event_listener_with_callback("message", closure.into_js_value().unchecked_ref()) + .unwrap(); + + let outgoing = Box::pin(futures::sink::unfold((), |_, msg| { + global + .post_message(&JsValue::from_serde(&msg).unwrap()) + .unwrap(); + async { Ok(()) } + })); + + spawn_local(crate::run(incoming, outgoing)); + } +} diff --git a/tbp/src/main.rs b/tbp/src/main.rs index f9d298e..f9e27f0 100644 --- a/tbp/src/main.rs +++ b/tbp/src/main.rs @@ -1,164 +1,18 @@ -use std::io::{stdout, Result}; - -use tbp::randomizer::RandomizerState; -use tbp::{BotMessage, FrontendMessage}; - -fn main() -> Result<()> { - let mut bot = None; - - send(&BotMessage::Info { - name: "Cold Clear".to_string(), - version: "2020-05-05".to_string(), - author: "MinusKelvin".to_string(), - features: tbp::Feature::enabled(), - }); - - loop { - match receive() { - FrontendMessage::Rules { randomizer: _ } => { - send(&BotMessage::Ready); - } - FrontendMessage::Start { - hold, - queue, - combo, - back_to_back, - board, - randomizer, - } => { - let mut b = libtetris::Board::new(); - b.hold_piece = hold.map(from_tbp_piece); - for piece in queue { - b.add_next_piece(from_tbp_piece(piece)); - } - if let RandomizerState::SevenBag { bag_state } = &randomizer { - b.bag = bag_state.iter().copied().map(from_tbp_piece).collect(); - } - b.combo = combo; - b.b2b_bonus = back_to_back; - let mut field = [[false; 10]; 40]; - for y in 0..40 { - for x in 0..10 { - field[y][x] = board[y][x].is_some(); - } - } - b.set_field(field); - - bot = Some(cold_clear::Interface::launch( - b, - cold_clear::Options { - speculate: matches!(randomizer, RandomizerState::SevenBag { .. }), - ..Default::default() - }, - cold_clear::evaluation::Standard::default(), - None, - )); - } - FrontendMessage::Stop => { - bot = None; - } - FrontendMessage::Suggest => { - if let Some(ref mut bot) = bot { - bot.suggest_next_move(0); - let moves = bot - .block_next_move() - .map_or(vec![], |(mv, _)| vec![to_tbp_move(mv.expected_location)]); - send(&BotMessage::Suggestion { moves }); - } - } - FrontendMessage::Play { mv } => { - if let Some(ref mut bot) = bot { - bot.play_next_move(from_tbp_move(mv)); - } - } - FrontendMessage::NewPiece { piece } => { - if let Some(ref mut bot) = bot { - bot.add_next_piece(from_tbp_piece(piece)); - } - } - FrontendMessage::Quit => return Ok(()), - } - } -} - -fn send(v: &BotMessage) { - serde_json::to_writer(stdout(), v).unwrap(); - println!(); -} - -fn receive() -> FrontendMessage { - let mut line = String::new(); - loop { - line.clear(); +fn main() { + let incoming = futures::stream::repeat_with(|| { + let mut line = String::new(); std::io::stdin().read_line(&mut line).unwrap(); - if let Ok(msg) = serde_json::from_str(&line) { - return msg; - } - } -} - -fn from_tbp_piece(v: tbp::Piece) -> libtetris::Piece { - match v { - tbp::Piece::I => libtetris::Piece::I, - tbp::Piece::O => libtetris::Piece::O, - tbp::Piece::T => libtetris::Piece::T, - tbp::Piece::L => libtetris::Piece::L, - tbp::Piece::J => libtetris::Piece::J, - tbp::Piece::S => libtetris::Piece::S, - tbp::Piece::Z => libtetris::Piece::Z, - } -} + serde_json::from_str(&line).unwrap() + }); -fn to_tbp_piece(v: libtetris::Piece) -> tbp::Piece { - match v { - libtetris::Piece::I => tbp::Piece::I, - libtetris::Piece::O => tbp::Piece::O, - libtetris::Piece::T => tbp::Piece::T, - libtetris::Piece::L => tbp::Piece::L, - libtetris::Piece::J => tbp::Piece::J, - libtetris::Piece::S => tbp::Piece::S, - libtetris::Piece::Z => tbp::Piece::Z, - } -} + let outgoing = futures::sink::unfold((), |_, msg| { + serde_json::to_writer(std::io::stdout(), &msg).unwrap(); + println!(); + async { Ok(()) } + }); -fn from_tbp_move(v: tbp::Move) -> libtetris::FallingPiece { - libtetris::FallingPiece { - kind: libtetris::PieceState( - from_tbp_piece(v.location.kind), - match v.location.orientation { - tbp::Orientation::North => libtetris::RotationState::North, - tbp::Orientation::South => libtetris::RotationState::South, - tbp::Orientation::East => libtetris::RotationState::East, - tbp::Orientation::West => libtetris::RotationState::West, - }, - ), - x: v.location.x, - y: v.location.y, - tspin: match v.spin { - tbp::Spin::None => libtetris::TspinStatus::None, - tbp::Spin::Mini => libtetris::TspinStatus::Mini, - tbp::Spin::Full => libtetris::TspinStatus::Full, - }, - } -} + futures::pin_mut!(incoming); + futures::pin_mut!(outgoing); -fn to_tbp_move(v: libtetris::FallingPiece) -> tbp::Move { - tbp::Move { - location: tbp::PieceLocation { - kind: to_tbp_piece(v.kind.0), - orientation: match v.kind.1 { - libtetris::RotationState::North => tbp::Orientation::North, - libtetris::RotationState::South => tbp::Orientation::South, - libtetris::RotationState::East => tbp::Orientation::East, - libtetris::RotationState::West => tbp::Orientation::West, - }, - x: v.x, - y: v.y, - }, - spin: match v.tspin { - libtetris::TspinStatus::None => tbp::Spin::None, - libtetris::TspinStatus::Mini => tbp::Spin::Mini, - libtetris::TspinStatus::Full => tbp::Spin::Full, - }, - } + futures::executor::block_on(cc_tbp::run(incoming, outgoing)); } From 629dec66542d504cdf73b7fae435d03cf6621451 Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Fri, 10 Sep 2021 14:06:24 +1000 Subject: [PATCH 12/21] update game-util, add windows subsystem annotation --- cc-client/Cargo.toml | 2 +- cc-client/src/main.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cc-client/Cargo.toml b/cc-client/Cargo.toml index f1efee7..cd2d811 100644 --- a/cc-client/Cargo.toml +++ b/cc-client/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib", "rlib"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -game-util = { git = "https://github.com/MinusKelvin/game-util-rs", rev = "4a39948" } +game-util = { git = "https://github.com/MinusKelvin/game-util-rs", rev = "d9bc506" } cold-clear = { path = "../bot" } libtetris = { path = "../libtetris" } battle = { path = "../battle" } diff --git a/cc-client/src/main.rs b/cc-client/src/main.rs index 29cb1f3..4229e70 100644 --- a/cc-client/src/main.rs +++ b/cc-client/src/main.rs @@ -1,3 +1,5 @@ +#![cfg_attr(windows, windows_subsystem = "windows")] + fn main() { if let Some(path) = std::env::var_os("CARGO_MANIFEST_DIR") { std::env::set_current_dir(path).ok(); From 7b4abc931948d69f6f0b4eb7d401167c1cdedb03 Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Fri, 10 Sep 2021 14:26:39 +1000 Subject: [PATCH 13/21] add libgtk-3 to CI dependencies --- .github/workflows/client.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/client.yml b/.github/workflows/client.yml index 62906e7..32cd7c9 100644 --- a/.github/workflows/client.yml +++ b/.github/workflows/client.yml @@ -37,7 +37,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install libraries - run: sudo apt install libasound2-dev libudev-dev + run: sudo apt install libasound2-dev libudev-dev libgtk-3-dev - name: Build run: cargo build --release --bin cc-client - name: Artifact From 24ef169297e96a185b31ba3e4b90fabec9cf522d Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Tue, 12 Oct 2021 13:22:22 +1100 Subject: [PATCH 14/21] add required [lib] section and entry point for web tbp --- tbp/Cargo.toml | 3 +++ tbp/src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tbp/Cargo.toml b/tbp/Cargo.toml index 95d4e45..919ed8a 100644 --- a/tbp/Cargo.toml +++ b/tbp/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["MinusKelvin "] edition = "2018" +[lib] +crate-type = ["cdylib", "rlib"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/tbp/src/lib.rs b/tbp/src/lib.rs index 1d7e9df..8cd5c4d 100644 --- a/tbp/src/lib.rs +++ b/tbp/src/lib.rs @@ -177,7 +177,7 @@ mod web { static global: web_sys::DedicatedWorkerGlobalScope; } - #[wasm_bindgen(start)] + #[wasm_bindgen] pub fn start() { let (send, incoming) = unbounded(); let closure = Closure::wrap(Box::new(move |e: web_sys::MessageEvent| { From 295a1673d6e14037f3aa09bc64a916a20e615a11 Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Wed, 27 Oct 2021 10:34:02 +1100 Subject: [PATCH 15/21] add show_plan option --- cc-client/src/battle_ui.rs | 20 +++++++++-- cc-client/src/lib.rs | 21 ++++++++++-- cc-client/src/player_draw.rs | 66 +++++++++++++++++++----------------- cc-client/src/realtime.rs | 8 ++++- cc-client/src/replay.rs | 22 ++++++++++-- 5 files changed, 97 insertions(+), 40 deletions(-) diff --git a/cc-client/src/battle_ui.rs b/cc-client/src/battle_ui.rs index 1c99b84..f4b0f13 100644 --- a/cc-client/src/battle_ui.rs +++ b/cc-client/src/battle_ui.rs @@ -12,10 +12,24 @@ pub struct BattleUi { } impl BattleUi { - pub fn new(battle: &Battle, p1_name: String, p2_name: String) -> Self { + pub fn new( + battle: &Battle, + p1_name: String, + p1_show_plan: bool, + p2_name: String, + p2_show_plan: bool, + ) -> Self { BattleUi { - player_1_graphics: PlayerDrawState::new(battle.player_1.board.next_queue(), p1_name), - player_2_graphics: PlayerDrawState::new(battle.player_2.board.next_queue(), p2_name), + player_1_graphics: PlayerDrawState::new( + battle.player_1.board.next_queue(), + p1_name, + p1_show_plan, + ), + player_2_graphics: PlayerDrawState::new( + battle.player_2.board.next_queue(), + p2_name, + p2_show_plan, + ), time: 0, } } diff --git a/cc-client/src/lib.rs b/cc-client/src/lib.rs index 11c7f3f..4933496 100644 --- a/cc-client/src/lib.rs +++ b/cc-client/src/lib.rs @@ -191,7 +191,11 @@ pub fn main() { el_proxy, executor, state: match replay_file { - Some(f) => Box::new(ReplayGame::new(f)), + Some(f) => Box::new(ReplayGame::new( + f, + options.p1.show_plan, + options.p2.show_plan, + )), None => Box::new(RealtimeGame::new(options, 0, 0).await), }, p1: p1_gamepad, @@ -237,13 +241,26 @@ impl Default for Options { } } -#[derive(Serialize, Deserialize, Clone, Default)] +#[derive(Serialize, Deserialize, Clone)] #[serde(default)] struct PlayerConfig { controls: input::UserInput, game: GameConfig, bot_config: BotConfig, is_bot: bool, + show_plan: bool, +} + +impl Default for PlayerConfig { + fn default() -> Self { + Self { + controls: Default::default(), + game: Default::default(), + bot_config: Default::default(), + is_bot: false, + show_plan: true, + } + } } impl PlayerConfig diff --git a/cc-client/src/player_draw.rs b/cc-client/src/player_draw.rs index 815bdc7..7b5e8ba 100644 --- a/cc-client/src/player_draw.rs +++ b/cc-client/src/player_draw.rs @@ -14,6 +14,7 @@ pub struct PlayerDrawState { statistics: Statistics, garbage_queue: u32, dead: bool, + show_plan: bool, hold_piece: Option, next_queue: VecDeque, game_time: u32, @@ -31,7 +32,7 @@ enum State { } impl PlayerDrawState { - pub fn new(queue: impl IntoIterator, name: String) -> Self { + pub fn new(queue: impl IntoIterator, name: String, show_plan: bool) -> Self { PlayerDrawState { board: ArrayVec::from([*ColoredRow::EMPTY; 40]), state: State::Delay, @@ -46,6 +47,7 @@ impl PlayerDrawState { clear_splash: None, name, info: None, + show_plan, } } @@ -331,41 +333,43 @@ impl PlayerDrawState { 0, ); - if let Some(ref info) = self.info { - let mut has_pc = false; - for (_, l) in info.plan() { - if l.perfect_clear { - has_pc = true; + if self.show_plan { + if let Some(ref info) = self.info { + let mut has_pc = false; + for (_, l) in info.plan() { + if l.perfect_clear { + has_pc = true; + } } - } - // Draw bot plan - let mut y_map = [0; 40]; - for i in 0..40 { - y_map[i] = i as i32; - } - for (placement, lock) in info.plan() { - for &(x, y, d) in &placement.cells_with_connections() { - res.sprite_batch.draw( - &res.sprites.plan[d.as_usize()], - point2(offset_x + x as f32 + 4.0, y_map[y as usize] as f32 + 3.25), - cell_color_to_color(placement.kind.0.color()), - ); - } - let mut new_map = [0; 40]; - let mut j = 0; + // Draw bot plan + let mut y_map = [0; 40]; for i in 0..40 { - if !lock.cleared_lines.contains(&i) { - new_map[j] = y_map[i as usize]; - j += 1; - } + y_map[i] = i as i32; } - y_map = new_map; + for (placement, lock) in info.plan() { + for &(x, y, d) in &placement.cells_with_connections() { + res.sprite_batch.draw( + &res.sprites.plan[d.as_usize()], + point2(offset_x + x as f32 + 4.0, y_map[y as usize] as f32 + 3.25), + cell_color_to_color(placement.kind.0.color()), + ); + } + let mut new_map = [0; 40]; + let mut j = 0; + for i in 0..40 { + if !lock.cleared_lines.contains(&i) { + new_map[j] = y_map[i as usize]; + j += 1; + } + } + y_map = new_map; - if !has_pc && lock.placement_kind.is_hard() && lock.placement_kind.is_clear() - || lock.perfect_clear - { - break; + if !has_pc && lock.placement_kind.is_hard() && lock.placement_kind.is_clear() + || lock.perfect_clear + { + break; + } } } } diff --git a/cc-client/src/realtime.rs b/cc-client/src/realtime.rs index 0d89e91..30ce3f4 100644 --- a/cc-client/src/realtime.rs +++ b/cc-client/src/realtime.rs @@ -54,7 +54,13 @@ impl RealtimeGame { battle.replay.p1_name = p1_name.clone(); battle.replay.p2_name = p2_name.clone(); RealtimeGame { - ui: BattleUi::new(&battle, p1_name, p2_name), + ui: BattleUi::new( + &battle, + p1_name, + options.p1.show_plan, + p2_name, + options.p2.show_plan, + ), battle, options: Some(options), p1_input, diff --git a/cc-client/src/replay.rs b/cc-client/src/replay.rs index ea13009..e3ccd0e 100644 --- a/cc-client/src/replay.rs +++ b/cc-client/src/replay.rs @@ -22,10 +22,12 @@ pub struct ReplayGame { p1_info_updates: VecDeque>, p2_info_updates: VecDeque>, start_delay: u32, + p1_show_plan: bool, + p2_show_plan: bool, } impl ReplayGame { - pub fn new(file: impl Into) -> Self { + pub fn new(file: impl Into, p1_show_plan: bool, p2_show_plan: bool) -> Self { let file = file.into(); let InfoReplay { replay, @@ -41,13 +43,21 @@ impl ReplayGame { replay.garbage_seed, ); ReplayGame { - ui: BattleUi::new(&battle, replay.p1_name, replay.p2_name), + ui: BattleUi::new( + &battle, + replay.p1_name, + p1_show_plan, + replay.p2_name, + p2_show_plan, + ), battle, updates: replay.updates, p1_info_updates, p2_info_updates, start_delay: 500, file, + p1_show_plan, + p2_show_plan, } } } @@ -100,7 +110,13 @@ impl crate::State for ReplayGame { replay.p2_seed, replay.garbage_seed, ); - self.ui = BattleUi::new(&battle, replay.p1_name, replay.p2_name); + self.ui = BattleUi::new( + &battle, + replay.p1_name, + self.p1_show_plan, + replay.p2_name, + self.p2_show_plan, + ); self.battle = battle; self.updates = replay.updates; self.p1_info_updates = p1_info_updates; From d16782047c9a84730ec7ac01f605c8ac1f8dedba Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Sun, 21 Nov 2021 19:06:25 +1100 Subject: [PATCH 16/21] Add garbage messiness config option --- battle/Cargo.toml | 3 ++- battle/src/game.rs | 2 +- battle/src/lib.rs | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/battle/Cargo.toml b/battle/Cargo.toml index 705e69e..c4dee26 100644 --- a/battle/Cargo.toml +++ b/battle/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] libtetris = { path = "../libtetris" } +ordered-float = { version = "2.8.0", features = ["serde"] } rand = "0.7.0" rand_pcg = "0.2.0" -serde = { version = "1", features = ["derive"] } \ No newline at end of file +serde = { version = "1", features = ["derive"] } diff --git a/battle/src/game.rs b/battle/src/game.rs index d48de53..7697844 100644 --- a/battle/src/game.rs +++ b/battle/src/game.rs @@ -414,7 +414,7 @@ impl Game { let mut col = rng.gen_range(0, 10); let mut garbage_columns = vec![]; for _ in 0..self.garbage_queue.min(self.config.max_garbage_add) { - if rng.gen_bool(1.0 / 3.0) { + if rng.gen_bool(self.config.garbage_messiness.into_inner()) { col = rng.gen_range(0, 10); } garbage_columns.push(col); diff --git a/battle/src/lib.rs b/battle/src/lib.rs index 2d4625b..7ebe737 100644 --- a/battle/src/lib.rs +++ b/battle/src/lib.rs @@ -1,3 +1,4 @@ +use ordered_float::NotNan; use serde::{Deserialize, Serialize}; mod battle; @@ -24,6 +25,7 @@ pub struct GameConfig { pub max_garbage_add: u32, pub move_lock_rule: u32, pub garbage_blocking: bool, + pub garbage_messiness: NotNan, } impl Default for GameConfig { @@ -41,6 +43,7 @@ impl Default for GameConfig { max_garbage_add: 10, move_lock_rule: 15, garbage_blocking: false, + garbage_messiness: NotNan::new(0.3).unwrap(), } } } @@ -59,6 +62,7 @@ impl GameConfig { max_garbage_add: 20, move_lock_rule: 15, garbage_blocking: true, + garbage_messiness: NotNan::new(0.0).unwrap(), } } } From bf37ad9057922f66b369d705b68fceb6e0ab2da8 Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Wed, 5 Jan 2022 16:25:07 +1100 Subject: [PATCH 17/21] correct tbp version (fixes #31) --- tbp/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tbp/Cargo.toml b/tbp/Cargo.toml index 919ed8a..ffb3baf 100644 --- a/tbp/Cargo.toml +++ b/tbp/Cargo.toml @@ -18,8 +18,7 @@ serde-big-array = "0.3.2" serde_json = "1.0.64" [dependencies.tbp] -git = "https://github.com/tetris-bot-protocol/tbp-rs" -branch = "main" +version = "1.0" features = [ "randomizer" ] From 13b097af114b81a6b205ee43086d5e4131ff967c Mon Sep 17 00:00:00 2001 From: MinusKelvin Date: Sun, 9 Jan 2022 12:05:14 +1100 Subject: [PATCH 18/21] add Board::set_cell_color --- libtetris/src/board.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libtetris/src/board.rs b/libtetris/src/board.rs index 59843d4..f64fdc5 100644 --- a/libtetris/src/board.rs +++ b/libtetris/src/board.rs @@ -141,6 +141,18 @@ impl Board { } } + pub fn set_cell_color(&mut self, x: i32, y: i32, color: CellColor) { + self.cells[y as usize].set(x as usize, color); + let h = &mut self.column_heights[x as usize]; + if color != CellColor::Empty { + *h = (*h).max(y + 1); + } else if *h == y + 1 { + while *h > 0 && !self.cells[*h as usize - 1].get(x as usize) { + *h -= 1; + } + } + } + pub fn obstructed(&self, piece: &FallingPiece) -> bool { piece.cells().iter().any(|&(x, y)| self.occupied(x, y)) } From f891afe1dcc598adc784a7cbd9425e6482792e2b Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Mon, 10 Jan 2022 13:36:17 +0100 Subject: [PATCH 19/21] Refactor deal_garbage into send_garbage and receive_garbage --- battle/src/game.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/battle/src/game.rs b/battle/src/game.rs index 7697844..675f0a7 100644 --- a/battle/src/game.rs +++ b/battle/src/game.rs @@ -203,7 +203,8 @@ impl Game { self.state = GameState::SpawnDelay(self.config.spawn_delay); let mut events = vec![Event::EndOfLineClearDelay]; if !self.config.garbage_blocking { - self.deal_garbage(&mut events, garbage_rng); + self.send_garbage(&mut events); + self.receive_garbage(&mut events, garbage_rng); } events } @@ -394,21 +395,25 @@ impl Game { events.push(Event::GameOver); } else if locked.cleared_lines.is_empty() { self.state = GameState::SpawnDelay(self.config.spawn_delay); - self.deal_garbage(events, garbage_rng); + self.receive_garbage(events, garbage_rng); } else { self.attacking += locked.garbage_sent; self.state = GameState::LineClearDelay(self.config.line_clear_delay); } } - fn deal_garbage(&mut self, events: &mut Vec, rng: &mut impl Rng) { + fn send_garbage(&mut self, events: &mut Vec) { if self.attacking > self.garbage_queue { self.attacking -= self.garbage_queue; + events.push(Event::GarbageSent(self.attacking)); self.garbage_queue = 0; } else { self.garbage_queue -= self.attacking; - self.attacking = 0; } + self.attacking = 0; + } + + fn receive_garbage(&mut self, events: &mut Vec, rng: &mut impl Rng) { if self.garbage_queue > 0 { let mut dead = false; let mut col = rng.gen_range(0, 10); @@ -426,9 +431,6 @@ impl Game { events.push(Event::GameOver); self.state = GameState::GameOver; } - } else if self.attacking > 0 { - events.push(Event::GarbageSent(self.attacking)); - self.attacking = 0; } } } From 85b8b6a5aaef0cff87557b5c5c7caa7ffada3fa3 Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Mon, 10 Jan 2022 13:37:34 +0100 Subject: [PATCH 20/21] Implement tetrio_garbage in battle --- battle/Cargo.toml | 3 +++ battle/src/game.rs | 8 ++++++++ cc-client/Cargo.toml | 2 +- optimizer/Cargo.toml | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/battle/Cargo.toml b/battle/Cargo.toml index c4dee26..aefc97c 100644 --- a/battle/Cargo.toml +++ b/battle/Cargo.toml @@ -12,3 +12,6 @@ ordered-float = { version = "2.8.0", features = ["serde"] } rand = "0.7.0" rand_pcg = "0.2.0" serde = { version = "1", features = ["derive"] } + +[features] +tetrio_garbage = [] diff --git a/battle/src/game.rs b/battle/src/game.rs index 675f0a7..55a8e9f 100644 --- a/battle/src/game.rs +++ b/battle/src/game.rs @@ -202,10 +202,18 @@ impl Game { GameState::LineClearDelay(0) => { self.state = GameState::SpawnDelay(self.config.spawn_delay); let mut events = vec![Event::EndOfLineClearDelay]; + #[cfg(not(feature = "tetrio_garbage"))] if !self.config.garbage_blocking { self.send_garbage(&mut events); self.receive_garbage(&mut events, garbage_rng); } + #[cfg(feature = "tetrio_garbage")] + { + self.send_garbage(&mut events); + if !self.config.garbage_blocking { + self.receive_garbage(&mut events, garbage_rng); + } + } events } GameState::LineClearDelay(ref mut delay) => { diff --git a/cc-client/Cargo.toml b/cc-client/Cargo.toml index d630931..5782173 100644 --- a/cc-client/Cargo.toml +++ b/cc-client/Cargo.toml @@ -35,4 +35,4 @@ wasm-bindgen-futures = "0.4" build-utils = { git = "https://github.com/MinusKelvin/game-util-rs", rev = "4a39948" } [features] -tetrio_garbage = ["cold-clear/tetrio_garbage"] \ No newline at end of file +tetrio_garbage = ["cold-clear/tetrio_garbage", "libtetris/tetrio_garbage", "battle/tetrio_garbage"] diff --git a/optimizer/Cargo.toml b/optimizer/Cargo.toml index c9ddeea..7f1f667 100644 --- a/optimizer/Cargo.toml +++ b/optimizer/Cargo.toml @@ -17,4 +17,4 @@ libflate = "0.1" rand = "0.7.0" [features] -tetrio_garbage = ["cold-clear/tetrio_garbage"] \ No newline at end of file +tetrio_garbage = ["cold-clear/tetrio_garbage", "libtetris/tetrio_garbage", "battle/tetrio_garbage"] From 47e4e2731e3001cbf78fce6d7c35c47e4127e92c Mon Sep 17 00:00:00 2001 From: Sebastian Widua Date: Mon, 10 Jan 2022 14:31:01 +0100 Subject: [PATCH 21/21] Fix blocking for ppt (whoops) --- battle/src/game.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/battle/src/game.rs b/battle/src/game.rs index 55a8e9f..1904a06 100644 --- a/battle/src/game.rs +++ b/battle/src/game.rs @@ -403,6 +403,7 @@ impl Game { events.push(Event::GameOver); } else if locked.cleared_lines.is_empty() { self.state = GameState::SpawnDelay(self.config.spawn_delay); + self.send_garbage(events); self.receive_garbage(events, garbage_rng); } else { self.attacking += locked.garbage_sent;