fix bag logic

This commit is contained in:
Theis Pieter Hollebeek 2025-03-15 01:24:14 +01:00
parent 7caef1ce4a
commit e11653d8dc
2 changed files with 92 additions and 81 deletions

View File

@ -2,6 +2,13 @@ use crate::actions::{Action, ActionsHeld};
use crate::board::Board; use crate::board::Board;
use crate::tetromino::{Direction, DirectionDiff, Tetromino}; use crate::tetromino::{Direction, DirectionDiff, Tetromino};
pub enum SoundEffect {
HardDrop,
LineClear(usize),
Move,
Rotation,
}
#[derive(Debug)] #[derive(Debug)]
pub struct CurrentTetromino { pub struct CurrentTetromino {
pub tetromino: Tetromino, pub tetromino: Tetromino,
@ -42,6 +49,7 @@ pub struct Game {
pub game_over: bool, pub game_over: bool,
pub board: Board, pub board: Board,
pub next_tetrominos: [Tetromino; 3], pub next_tetrominos: [Tetromino; 3],
bag: Bag,
pub current_tetromino: CurrentTetromino, pub current_tetromino: CurrentTetromino,
pub held_tetromino: Option<Tetromino>, pub held_tetromino: Option<Tetromino>,
has_swapped_held: bool, has_swapped_held: bool,
@ -49,74 +57,68 @@ pub struct Game {
pub ticks: usize, pub ticks: usize,
} }
pub enum SoundEffect { struct Bag {
HardDrop, inner: [Tetromino; 7],
LineClear(usize), idx: usize,
Move,
Rotation,
} }
pub struct Score { impl Bag {
pub level: usize, fn new() -> Self {
pub points: usize,
pub lines: usize,
pub combo: usize,
back_to_back: bool,
}
impl Score {
const fn new() -> Self {
Self { Self {
level: 0, inner: Self::random_tetrominos(),
points: 0, idx: 0,
lines: 0,
combo: 0,
back_to_back: false,
} }
} }
pub fn random_tetrominos() -> [Tetromino; 7] {
use rand::seq::IndexedRandom;
let sample = [
Tetromino::I,
Tetromino::J,
Tetromino::L,
Tetromino::O,
Tetromino::S,
Tetromino::T,
Tetromino::Z,
];
fn level_up(&mut self, lines_cleared: usize) { debug_assert_eq!(sample.len(), 7, "each piece should only appear once");
self.lines += lines_cleared;
if self.lines > self.level * 5 { sample
self.level += 1; .choose_multiple_array(&mut rand::rng())
self.lines = 0; .expect("both arrays should have a length of 7")
}
} }
fn take_next(&mut self) -> Tetromino {
fn point_multiplier_from_lines_cleared(lines_cleared: usize) -> f32 { if self.idx >= self.inner.len() {
match lines_cleared { self.idx = 0;
0 => 0.0, self.inner = Self::random_tetrominos();
1 => 100.0,
2 => 300.0,
3 => 500.0,
4 => 800.0,
_ => unreachable!("we cannot clear more than 4 lines"),
} }
}
fn combos(&self, lines_cleared: usize) -> usize { let uninitialized_tetromino = Tetromino::I;
if lines_cleared > 0 { let current = std::mem::replace(&mut self.inner[self.idx], uninitialized_tetromino);
self.combo * 50 * self.level self.idx += 1;
} else { current
0
}
} }
} }
impl Game { impl Game {
pub fn new() -> Self { pub fn new() -> Self {
let mut bag = Bag::new();
Self { Self {
game_over: false, game_over: false,
board: Board::new(), board: Board::new(),
next_tetrominos: std::array::from_fn(|_| Tetromino::random()), next_tetrominos: std::array::from_fn(|_| bag.take_next()),
current_tetromino: CurrentTetromino::new(Tetromino::random()), current_tetromino: CurrentTetromino::new(bag.take_next()),
held_tetromino: None, held_tetromino: None,
bag,
has_swapped_held: false, has_swapped_held: false,
score: Score::new(), score: Score::new(),
ticks: 0, ticks: 0,
} }
} }
fn take_next_in_bag(&mut self, mut last: Tetromino) -> Tetromino {
fn take_next_up(&mut self) -> Tetromino {
let mut last = self.bag.take_next();
for value in self.next_tetrominos.iter_mut().rev() { for value in self.next_tetrominos.iter_mut().rev() {
std::mem::swap(value, &mut last) std::mem::swap(value, &mut last)
} }
@ -265,7 +267,7 @@ impl Game {
} }
fn place_current_tetromino(&mut self) { fn place_current_tetromino(&mut self) {
let next = CurrentTetromino::new(self.take_next_in_bag(Tetromino::random())); let next = CurrentTetromino::new(self.take_next_up());
let current = std::mem::replace(&mut self.current_tetromino, next); let current = std::mem::replace(&mut self.current_tetromino, next);
let pattern = current.tetromino.pattern(&current.direction); let pattern = current.tetromino.pattern(&current.direction);
@ -301,7 +303,7 @@ impl Game {
let held_or_first_in_bag_tetromino = self let held_or_first_in_bag_tetromino = self
.held_tetromino .held_tetromino
.take() .take()
.unwrap_or_else(|| self.take_next_in_bag(Tetromino::random())); .unwrap_or_else(|| self.take_next_up());
let current_tetromino = CurrentTetromino::new(held_or_first_in_bag_tetromino); let current_tetromino = CurrentTetromino::new(held_or_first_in_bag_tetromino);
let old_tetromino = std::mem::replace(&mut self.current_tetromino, current_tetromino); let old_tetromino = std::mem::replace(&mut self.current_tetromino, current_tetromino);
self.held_tetromino.replace(old_tetromino.tetromino); self.held_tetromino.replace(old_tetromino.tetromino);
@ -309,26 +311,49 @@ impl Game {
} }
} }
#[cfg(test)] pub struct Score {
mod test { pub level: usize,
use super::{Board, CurrentTetromino, Game, Score, Tetromino}; pub points: usize,
pub lines: usize,
pub combo: usize,
back_to_back: bool,
}
#[test] impl Score {
fn advance_bag() { const fn new() -> Self {
let mut game = Game { Self {
game_over: false, level: 0,
board: Board::new(), points: 0,
score: Score::new(), lines: 0,
next_tetrominos: [Tetromino::I, Tetromino::J, Tetromino::O], combo: 0,
current_tetromino: CurrentTetromino::new(Tetromino::J), back_to_back: false,
held_tetromino: None, }
has_swapped_held: false, }
ticks: 0,
}; fn level_up(&mut self, lines_cleared: usize) {
assert_eq!(game.take_next_in_bag(Tetromino::S), Tetromino::I); self.lines += lines_cleared;
assert_eq!( if self.lines > self.level * 5 {
game.next_tetrominos, self.level += 1;
[Tetromino::J, Tetromino::O, Tetromino::S] self.lines = 0;
); }
}
fn point_multiplier_from_lines_cleared(lines_cleared: usize) -> f32 {
match lines_cleared {
0 => 0.0,
1 => 100.0,
2 => 300.0,
3 => 500.0,
4 => 800.0,
_ => unreachable!("we cannot clear more than 4 lines"),
}
}
fn combos(&self, lines_cleared: usize) -> usize {
if lines_cleared > 0 {
self.combo * 50 * self.level
} else {
0
}
} }
} }

View File

@ -38,20 +38,6 @@ pub enum DirectionDiff {
} }
impl Tetromino { impl Tetromino {
pub fn random() -> Self {
let v: u8 = rand::random();
match v % 7 {
0 => Self::I,
1 => Self::J,
2 => Self::L,
3 => Self::O,
4 => Self::S,
5 => Self::T,
6 => Self::Z,
_ => unreachable!("v%7 is always in range 0..=6"),
}
}
pub fn pattern(&self, direction: &Direction) -> Vec<(usize, usize)> { pub fn pattern(&self, direction: &Direction) -> Vec<(usize, usize)> {
self.raw_pattern(direction) self.raw_pattern(direction)
.into_iter() .into_iter()