diff --git a/tetros/src/drivers/vga.rs b/tetros/src/drivers/vga.rs index 0ecb950..0fa2f24 100644 --- a/tetros/src/drivers/vga.rs +++ b/tetros/src/drivers/vga.rs @@ -40,23 +40,21 @@ impl Vga13h { let seg = unsafe { self.segment() }; if self.show_fb_a { seg.copy_from_slice(&self.fb_b); - self.show_fb_a = false + self.show_fb_a = false; + self.fb_a.fill(0); } else { seg.copy_from_slice(&self.fb_a); - self.show_fb_a = true + self.show_fb_a = true; + self.fb_b.fill(0); } } pub fn get_fb(&mut self) -> &mut [u8; Vga13h::WIDTH * Vga13h::HEIGHT] { - unsafe { self.segment().try_into().unwrap() } - - /* if self.show_fb_a { &mut self.fb_b } else { &mut self.fb_a } - */ } pub fn pix_idx(x: usize, y: usize) -> usize { diff --git a/tetros/src/fall/mod.rs b/tetros/src/fall/mod.rs new file mode 100644 index 0000000..f32b19f --- /dev/null +++ b/tetros/src/fall/mod.rs @@ -0,0 +1,278 @@ +pub enum Tetromino { + /// The 1x4 line + Straight, + + /// The 2x2 square + Square, + + /// The "L" shape + AngleRight, + + /// The mirror "L" shape + AngleLeft, + + /// The skew shape "Z" + SkewRight, + + /// The mirror skew shape + SkewLeft, + + /// The "T" shape + Tee, +} + +#[derive(Debug, Clone, Copy)] +pub enum Direction { + North, + East, + South, + West, +} + +impl Direction { + pub fn rot_cw(self) -> Self { + match self { + Self::North => Self::East, + Self::East => Self::South, + Self::South => Self::West, + Self::West => Self::North, + } + } + + pub fn rot_ccw(self) -> Self { + match self { + Self::North => Self::West, + Self::West => Self::South, + Self::South => Self::East, + Self::East => Self::North, + } + } +} + +pub struct FallingTetromino { + tetromino: Tetromino, + direction: Direction, + center_x: usize, + center_y: usize, +} + +impl FallingTetromino { + pub fn new(tetromino: Tetromino, center_x: usize, center_y: usize) -> Self { + Self { + tetromino, + direction: Direction::North, + center_x, + center_y, + } + } + + pub fn translate(&mut self, x: usize, y: usize) { + self.center_x += x; + self.center_y += y; + } + + pub fn rotate_cw(&mut self) { + self.direction = self.direction.rot_cw() + } + + pub fn rotate_ccw(&mut self) { + self.direction = self.direction.rot_ccw() + } + + /// Returns the positions of this falling tetromino's tiles. + pub fn tiles(&self) -> [(usize, usize); 4] { + match (&self.tetromino, self.direction) { + (Tetromino::Square, _) => [ + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + (self.center_x, self.center_y + 1), + (self.center_x + 1, self.center_y + 1), + ], + + // + // Straight + // + (Tetromino::Straight, Direction::North) => [ + (self.center_x - 2, self.center_y - 1), + (self.center_x - 1, self.center_y - 1), + (self.center_x, self.center_y - 1), + (self.center_x + 1, self.center_y - 1), + ], + + (Tetromino::Straight, Direction::East) => [ + (self.center_x + 1, self.center_y - 2), + (self.center_x + 1, self.center_y - 1), + (self.center_x + 1, self.center_y), + (self.center_x + 1, self.center_y + 1), + ], + + (Tetromino::Straight, Direction::South) => [ + (self.center_x - 2, self.center_y + 1), + (self.center_x - 1, self.center_y + 1), + (self.center_x, self.center_y + 1), + (self.center_x + 1, self.center_y + 1), + ], + + (Tetromino::Straight, Direction::West) => [ + (self.center_x - 1, self.center_y - 2), + (self.center_x - 1, self.center_y - 1), + (self.center_x - 1, self.center_y), + (self.center_x - 1, self.center_y + 1), + ], + // + // Right Angle + // + (Tetromino::AngleRight, Direction::North) => [ + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + (self.center_x + 1, self.center_y + 1), + ], + + (Tetromino::AngleRight, Direction::East) => [ + (self.center_x - 1, self.center_y + 1), + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + ], + + (Tetromino::AngleRight, Direction::South) => [ + (self.center_x - 1, self.center_y - 1), + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + ], + + (Tetromino::AngleRight, Direction::West) => [ + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + (self.center_x + 1, self.center_y - 1), + ], + + // + // Left Angle + // + (Tetromino::AngleLeft, Direction::North) => [ + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + (self.center_x - 1, self.center_y + 1), + ], + + (Tetromino::AngleLeft, Direction::East) => [ + (self.center_x - 1, self.center_y - 1), + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + ], + + (Tetromino::AngleLeft, Direction::South) => [ + (self.center_x + 1, self.center_y - 1), + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + ], + + (Tetromino::AngleLeft, Direction::West) => [ + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + (self.center_x + 1, self.center_y + 1), + ], + + // + // Left Skew + // + (Tetromino::SkewLeft, Direction::North) => [ + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + (self.center_x + 1, self.center_y + 1), + ], + + (Tetromino::SkewLeft, Direction::East) => [ + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x - 1, self.center_y), + (self.center_x - 1, self.center_y - 1), + ], + + (Tetromino::SkewLeft, Direction::South) => [ + (self.center_x - 1, self.center_y - 1), + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + ], + + (Tetromino::SkewLeft, Direction::West) => [ + (self.center_x + 1, self.center_y - 1), + (self.center_x + 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x, self.center_y - 1), + ], + + // + // Right Skew + // + (Tetromino::SkewRight, Direction::North) => [ + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x, self.center_y - 1), + (self.center_x + 1, self.center_y - 1), + ], + + (Tetromino::SkewRight, Direction::East) => [ + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + (self.center_x + 1, self.center_y - 1), + ], + + (Tetromino::SkewRight, Direction::South) => [ + (self.center_x - 1, self.center_y + 1), + (self.center_x, self.center_y + 1), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + ], + + (Tetromino::SkewRight, Direction::West) => [ + (self.center_x - 1, self.center_y - 1), + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x, self.center_y - 1), + ], + + // + // Tee + // + (Tetromino::Tee, Direction::North) => [ + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + (self.center_x, self.center_y - 1), + ], + + (Tetromino::Tee, Direction::East) => [ + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + (self.center_x + 1, self.center_y), + ], + + (Tetromino::Tee, Direction::South) => [ + (self.center_x - 1, self.center_y), + (self.center_x, self.center_y), + (self.center_x + 1, self.center_y), + (self.center_x, self.center_y + 1), + ], + + (Tetromino::Tee, Direction::West) => [ + (self.center_x, self.center_y - 1), + (self.center_x, self.center_y), + (self.center_x, self.center_y + 1), + (self.center_x - 1, self.center_y), + ], + } + } +} diff --git a/tetros/src/lib.rs b/tetros/src/lib.rs index e8edab2..ef84702 100644 --- a/tetros/src/lib.rs +++ b/tetros/src/lib.rs @@ -4,7 +4,9 @@ #![feature(abi_x86_interrupt)] #![allow(internal_features)] -use core::arch::asm; +use core::num::NonZeroUsize; + +use fall::{FallingTetromino, Tetromino}; use lazy_static::lazy_static; use spin::Mutex; @@ -13,6 +15,7 @@ use idt::{InterruptDescriptorTable, InterruptStackFrame}; use os::thunk::ThunkData; use tetrisboard::TetrisBoard; +mod fall; mod idt; mod os; mod tetrisboard; @@ -22,6 +25,9 @@ mod drivers; pub(crate) static VGA: Mutex = Mutex::new(unsafe { Vga13h::new() }); pub(crate) static PIC: Mutex = Mutex::new(PICDriver::new(32, 32 + 8)); +pub(crate) static TICK_COUNTER: Mutex = Mutex::new(0); +pub(crate) static BOARD: Mutex = Mutex::new(TetrisBoard::new()); +pub(crate) static FALLING: Mutex> = Mutex::new(None); lazy_static! { static ref IDT: InterruptDescriptorTable = { @@ -39,8 +45,12 @@ extern "x86-interrupt" fn divide_handler(stack_frame: InterruptStackFrame) { println!("DIVIDE ERROR {:?}", stack_frame); } -extern "x86-interrupt" fn timer_handler(stack_frame: InterruptStackFrame) { - println!("TIMER {:?}", stack_frame); +extern "x86-interrupt" fn timer_handler(_stack_frame: InterruptStackFrame) { + let mut t = TICK_COUNTER.lock(); + *t = (*t).wrapping_add(1); + drop(t); + + tick(); PIC.lock().send_eoi(32); } @@ -52,6 +62,32 @@ extern "x86-interrupt" fn double_fault_handler( panic!("DOUBLE FAULT (err = 0x{error_code:x}\n{:#?}", stack_frame); } +fn tick() { + let mut v = VGA.lock(); + let t = *TICK_COUNTER.lock(); + let mut board = BOARD.lock(); + let mut fall = FALLING.lock(); + + v.swap(); + + board.draw(&mut v, fall.as_ref()); + + if let Some(fall_inner) = fall.as_mut() { + if t % 6 == 0 { + if board.tetromino_free_below(fall_inner) { + fall_inner.translate(0, 1); + fall_inner.rotate_cw(); + } else { + let mut x = None; + core::mem::swap(&mut x, &mut fall); + board.place_tetromino(x.unwrap()); + } + } + } else { + *fall = Some(FallingTetromino::new(Tetromino::AngleLeft, 5, 1)) + } +} + #[no_mangle] pub unsafe extern "C" fn start( _boot_disk: usize, @@ -86,11 +122,18 @@ pub unsafe extern "C" fn start( // Clear screen let mut v = VGA.lock(); + let board = BOARD.lock(); - let t = TetrisBoard::new(); - t.draw(&mut v); + let mut fall = FALLING.lock(); + *fall = Some(FallingTetromino::new(Tetromino::SkewRight, 5, 1)); - asm!("int $0"); + board.draw(&mut v, fall.as_ref()); + v.swap(); - panic!("kernel"); + // Important to drop these, or tick() will deadlock + drop(v); + drop(fall); + drop(board); + + loop {} } diff --git a/tetros/src/tetrisboard/cell.rs b/tetros/src/tetrisboard/cell.rs index 1662adf..42a8dbe 100644 --- a/tetros/src/tetrisboard/cell.rs +++ b/tetros/src/tetrisboard/cell.rs @@ -1,5 +1,5 @@ #[repr(u8)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum TetrisCell { Empty, Blue, diff --git a/tetros/src/tetrisboard/draw.rs b/tetros/src/tetrisboard/draw.rs index 579c2fd..ea71214 100644 --- a/tetros/src/tetrisboard/draw.rs +++ b/tetros/src/tetrisboard/draw.rs @@ -1,4 +1,4 @@ -use crate::drivers::vga::Vga13h; +use crate::{drivers::vga::Vga13h, fall::FallingTetromino}; use super::{TetrisBoard, TetrisCell}; @@ -21,9 +21,8 @@ impl TetrisBoard { } } - pub fn draw(&self, vga: &mut Vga13h) { + pub fn draw(&self, vga: &mut Vga13h, falling: Option<&FallingTetromino>) { let fb = vga.get_fb(); - // Draw cells for bx in 0..TetrisBoard::BOARD_WIDTH { for by in 0..TetrisBoard::BOARD_HEIGHT { @@ -35,6 +34,18 @@ impl TetrisBoard { } } + if let Some(falling) = falling { + for (x, y) in falling.tiles() { + let x = x as usize; + let y = y as usize; + let cell = TetrisCell::Blue; + let dx = (x + 1) * TetrisBoard::CELL_SIZE; + let dy = (y + 1) * TetrisBoard::CELL_SIZE; + + self.draw_cell(fb, cell, dx, dy); + } + } + // Draw frame for bx in 0..TetrisBoard::BOARD_WIDTH + 2 { self.draw_frame(fb, bx * TetrisBoard::CELL_SIZE, 0); diff --git a/tetros/src/tetrisboard/mod.rs b/tetros/src/tetrisboard/mod.rs index 596c0c7..761cc8d 100644 --- a/tetros/src/tetrisboard/mod.rs +++ b/tetros/src/tetrisboard/mod.rs @@ -2,6 +2,8 @@ mod cell; mod draw; pub use cell::*; +use crate::fall::FallingTetromino; + pub struct TetrisBoard { board: [TetrisCell; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT], } @@ -12,24 +14,53 @@ impl TetrisBoard { const CELL_SIZE: usize = 9; - pub fn new() -> Self { - let mut x = Self { + pub const fn new() -> Self { + Self { board: [TetrisCell::Empty; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT], - }; + } + } - x.board[0] = TetrisCell::Blue; - x.board[1] = TetrisCell::Cyan; - x.board[2] = TetrisCell::Orange; - x.board[3] = TetrisCell::Red; - x.board[4] = TetrisCell::Green; - x.board[5] = TetrisCell::Purple; - x.board[6] = TetrisCell::Yellow; + pub fn place_tetromino(&mut self, tetromino: FallingTetromino) { + for (x, y) in tetromino.tiles() { + let cell = self.get_cell_mut(x as usize, y as usize); + if let Some(cell) = cell { + *cell = TetrisCell::Blue; + } + } + } - x.board[9] = TetrisCell::Blue; - x.board[8] = TetrisCell::Blue; - x.board[10] = TetrisCell::Blue; - x.board[12] = TetrisCell::Blue; + pub fn tetromino_free_below(&self, tetromino: &FallingTetromino) -> bool { + for (x, y) in tetromino.tiles() { + let cell = self.get_cell(x as usize, y as usize + 1); + if cell != Some(&TetrisCell::Empty) { + return false; + } + } - x + return true; + } + + pub fn get_cell(&self, x: usize, y: usize) -> Option<&TetrisCell> { + if y >= TetrisBoard::BOARD_HEIGHT { + return None; + } + + if x >= TetrisBoard::BOARD_WIDTH { + return None; + } + + return Some(&self.board[y * TetrisBoard::BOARD_WIDTH + x]); + } + + pub fn get_cell_mut(&mut self, x: usize, y: usize) -> Option<&mut TetrisCell> { + if y >= TetrisBoard::BOARD_HEIGHT { + return None; + } + + if x >= TetrisBoard::BOARD_WIDTH { + return None; + } + + return Some(&mut self.board[y * TetrisBoard::BOARD_WIDTH + x]); } }