1
0

Random generation

This commit is contained in:
Mark 2025-03-01 09:31:48 -08:00
parent 5022c840de
commit 1309ee7489
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
9 changed files with 261 additions and 94 deletions

View File

@ -1,7 +1,7 @@
- better colors
- random spawning
- improve collisions
- controls (input)
- clear lines
- deadlocks
- enum and consts for interrupt index
- fix asm loader
- document everything

72
tetros/Cargo.lock generated
View File

@ -45,6 +45,40 @@ dependencies = [
"scopeguard",
]
[[package]]
name = "proc-macro2"
version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_core",
"zerocopy",
]
[[package]]
name = "rand_core"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
[[package]]
name = "raw-cpuid"
version = "10.7.0"
@ -75,12 +109,24 @@ dependencies = [
"lock_api",
]
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tetros"
version = "1.0.0"
dependencies = [
"bitflags 2.8.0",
"lazy_static",
"rand",
"spin",
"uart_16550",
]
@ -96,6 +142,12 @@ dependencies = [
"x86",
]
[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[package]]
name = "x86"
version = "0.52.0"
@ -106,3 +158,23 @@ dependencies = [
"bitflags 1.3.2",
"raw-cpuid",
]
[[package]]
name = "zerocopy"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -57,6 +57,7 @@ redundant_feature_names = "deny"
multiple_crate_versions = "allow"
missing_safety_doc = "allow"
identity_op = "allow"
comparison_chain = "allow"
#
# MARK: dependencies
@ -65,6 +66,7 @@ identity_op = "allow"
[dependencies]
bitflags = "2.8.0"
rand = { version = "0.9.0", features = ["small_rng"], default-features = false }
spin = "0.9.8"
uart_16550 = "0.3.2"

View File

@ -1,4 +1,53 @@
use core::slice;
use rand::seq::IndexedRandom;
use crate::RNG;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VgaColor {
Black,
Gray,
Blue,
Cyan,
Orange,
Red,
Green,
Purple,
Yellow,
}
impl VgaColor {
pub fn as_u8(self) -> u8 {
match self {
Self::Black => 0b0000_0000,
Self::Gray => 0b1110_0000,
Self::Blue => 0b0000_0001,
Self::Cyan => 0b0000_0011,
Self::Orange => 0b0000_0110,
Self::Red => 0b0000_0100,
Self::Green => 0b0000_0010,
Self::Purple => 0b0000_0101,
Self::Yellow => 0b1100_0000,
}
}
/// Pick a random non-utility color
pub fn choose_rand() -> Self {
let colors = [
VgaColor::Blue,
VgaColor::Cyan,
VgaColor::Orange,
VgaColor::Red,
VgaColor::Green,
VgaColor::Purple,
VgaColor::Yellow,
];
let mut rng = RNG.lock();
*(colors.choose(&mut rng).unwrap())
}
}
/// VGA driver for mode 0x13:
///

View File

@ -4,18 +4,16 @@
#![feature(abi_x86_interrupt)]
#![allow(internal_features)]
use core::num::NonZeroUsize;
use fall::{FallingTetromino, Tetromino};
use core::arch::asm;
use lazy_static::lazy_static;
use rand::{rngs::SmallRng, SeedableRng};
use spin::Mutex;
use drivers::{pic::PICDriver, vga::Vga13h};
use idt::{InterruptDescriptorTable, InterruptStackFrame};
use os::thunk::ThunkData;
use tetrisboard::TetrisBoard;
use tetrisboard::{FallingTetromino, TetrisBoard};
mod fall;
mod idt;
mod os;
mod tetrisboard;
@ -23,60 +21,73 @@ mod tetrisboard;
#[macro_use]
mod drivers;
const PIC_OFFSET: u8 = 32;
pub(crate) static VGA: Mutex<Vga13h> = Mutex::new(unsafe { Vga13h::new() });
pub(crate) static PIC: Mutex<PICDriver> = Mutex::new(PICDriver::new(32, 32 + 8));
pub(crate) static PIC: Mutex<PICDriver> = Mutex::new(PICDriver::new(PIC_OFFSET, PIC_OFFSET + 8));
pub(crate) static TICK_COUNTER: Mutex<u32> = Mutex::new(0);
pub(crate) static BOARD: Mutex<TetrisBoard> = Mutex::new(TetrisBoard::new());
pub(crate) static FALLING: Mutex<Option<FallingTetromino>> = Mutex::new(None);
pub(crate) static RUN_TICKS: Mutex<bool> = Mutex::new(false);
lazy_static! {
static ref RNG: Mutex<SmallRng> = Mutex::new(SmallRng::seed_from_u64(1337));
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.divide_error.set_handler_fn(divide_handler);
idt.double_fault.set_handler_fn(double_fault_handler);
idt.interrupts[0].set_handler_fn(timer_handler);
idt.interrupts[InterruptIndex::Timer.as_idx()].set_handler_fn(timer_handler);
idt
};
}
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
Timer = PIC_OFFSET,
Keyboard,
}
impl InterruptIndex {
fn as_u8(self) -> u8 {
self as u8
}
fn as_idx(self) -> usize {
usize::from(self.as_u8() - PIC_OFFSET)
}
}
extern "x86-interrupt" fn divide_handler(stack_frame: InterruptStackFrame) {
println!("DIVIDE ERROR {:?}", stack_frame);
}
extern "x86-interrupt" fn timer_handler(_stack_frame: InterruptStackFrame) {
let mut t = TICK_COUNTER.lock();
*t = (*t).wrapping_add(1);
drop(t);
if !*RUN_TICKS.lock() {
PIC.lock().send_eoi(InterruptIndex::Timer.as_u8());
return;
}
tick();
// Step tick counter
let t = {
let mut t = TICK_COUNTER.lock();
*t = (*t).wrapping_add(1);
*t
};
PIC.lock().send_eoi(32);
}
extern "x86-interrupt" fn double_fault_handler(
stack_frame: InterruptStackFrame,
error_code: u32,
) -> ! {
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);
@ -84,8 +95,17 @@ fn tick() {
}
}
} else {
*fall = Some(FallingTetromino::new(Tetromino::AngleLeft, 5, 1))
*fall = Some(FallingTetromino::random(5, 1))
}
PIC.lock().send_eoi(InterruptIndex::Timer.as_u8());
}
extern "x86-interrupt" fn double_fault_handler(
stack_frame: InterruptStackFrame,
error_code: u32,
) -> ! {
panic!("DOUBLE FAULT (err = 0x{error_code:x}\n{:#?}", stack_frame);
}
#[no_mangle]
@ -120,20 +140,11 @@ pub unsafe extern "C" fn start(
pic.init();
}
// Clear screen
let mut v = VGA.lock();
let board = BOARD.lock();
*RUN_TICKS.lock() = true;
let mut fall = FALLING.lock();
*fall = Some(FallingTetromino::new(Tetromino::SkewRight, 5, 1));
board.draw(&mut v, fall.as_ref());
v.swap();
// Important to drop these, or tick() will deadlock
drop(v);
drop(fall);
drop(board);
loop {}
// Do-nothing loop,
// frames are driven by interrupts
loop {
asm!("hlt")
}
}

View File

@ -1,31 +0,0 @@
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TetrisCell {
Empty,
Blue,
Cyan,
Orange,
Red,
Green,
Purple,
Yellow,
}
impl TetrisCell {
pub fn vga_color(&self) -> u8 {
match self {
Self::Empty => 0b0000_0000,
Self::Blue => 0b0000_0001,
Self::Cyan => 0b0000_0011,
Self::Orange => 0b0000_0110,
Self::Red => 0b0000_0100,
Self::Green => 0b0000_0010,
Self::Purple => 0b0000_0101,
Self::Yellow => 0b1100_0000,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
}

View File

@ -1,10 +1,10 @@
use crate::{drivers::vga::Vga13h, fall::FallingTetromino};
use crate::drivers::vga::{Vga13h, VgaColor};
use super::{TetrisBoard, TetrisCell};
use super::{falling::FallingTetromino, TetrisBoard, TetrisCell};
impl TetrisBoard {
fn draw_cell(&self, fb: &mut [u8], state: TetrisCell, x: usize, y: usize) {
let color = state.vga_color();
fn draw_cell(&self, fb: &mut [u8], color: VgaColor, x: usize, y: usize) {
let color = color.as_u8();
for yo in 0..TetrisBoard::CELL_SIZE {
let left = Vga13h::pix_idx(x, y + yo);
let right = Vga13h::pix_idx(x + TetrisBoard::CELL_SIZE, y + yo);
@ -13,7 +13,7 @@ impl TetrisBoard {
}
fn draw_frame(&self, fb: &mut [u8], x: usize, y: usize) {
let color = 0b1110_0000;
let color = VgaColor::Gray.as_u8();
for yo in 0..TetrisBoard::CELL_SIZE {
let left = Vga13h::pix_idx(x, y + yo);
let right = Vga13h::pix_idx(x + TetrisBoard::CELL_SIZE, y + yo);
@ -23,6 +23,7 @@ impl TetrisBoard {
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 {
@ -30,17 +31,21 @@ impl TetrisBoard {
let dx = (bx + 1) * TetrisBoard::CELL_SIZE;
let dy = (by + 1) * TetrisBoard::CELL_SIZE;
self.draw_cell(fb, cell, dx, dy);
if let TetrisCell::Filled { color } = cell {
self.draw_cell(fb, color, dx, dy);
} else {
self.draw_cell(fb, VgaColor::Black, dx, dy);
}
}
}
// Draw falling tetromino
if let Some(falling) = falling {
for (x, y) in falling.tiles() {
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);
self.draw_cell(fb, falling.color, dx, dy);
}
}

View File

@ -1,3 +1,8 @@
use rand::seq::IndexedRandom;
use crate::{drivers::vga::VgaColor, RNG};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Tetromino {
/// The 1x4 line
Straight,
@ -21,6 +26,24 @@ pub enum Tetromino {
Tee,
}
impl Tetromino {
/// Pick a random tetromino
pub fn choose_rand() -> Self {
let ominos = [
Tetromino::Straight,
Tetromino::Square,
Tetromino::AngleRight,
Tetromino::AngleLeft,
Tetromino::SkewRight,
Tetromino::SkewLeft,
Tetromino::Tee,
];
let mut rng = RNG.lock();
*(ominos.choose(&mut rng).unwrap())
}
}
#[derive(Debug, Clone, Copy)]
pub enum Direction {
North,
@ -54,21 +77,46 @@ pub struct FallingTetromino {
direction: Direction,
center_x: usize,
center_y: usize,
pub color: VgaColor,
}
impl FallingTetromino {
pub fn new(tetromino: Tetromino, center_x: usize, center_y: usize) -> Self {
pub fn new(tetromino: Tetromino, color: VgaColor, center_x: usize, center_y: usize) -> Self {
Self {
tetromino,
direction: Direction::North,
color,
center_x,
center_y,
}
}
pub fn translate(&mut self, x: usize, y: usize) {
self.center_x += x;
self.center_y += y;
pub fn random(center_x: usize, center_y: usize) -> Self {
Self::new(
Tetromino::choose_rand(),
VgaColor::choose_rand(),
center_x,
center_y,
)
}
pub fn translate(&mut self, x: i16, y: i16) {
if x > 0 {
let x = usize::try_from(x).unwrap();
self.center_x += x;
} else if x < 0 {
let x = usize::try_from(-x).unwrap();
self.center_x -= x;
}
if y > 0 {
let y = usize::try_from(y).unwrap();
self.center_y += y;
} else if y < 0 {
let y = usize::try_from(-y).unwrap();
self.center_y -= y;
}
}
pub fn rotate_cw(&mut self) {
@ -119,6 +167,7 @@ impl FallingTetromino {
(self.center_x - 1, self.center_y),
(self.center_x - 1, self.center_y + 1),
],
//
// Right Angle
//
@ -195,7 +244,7 @@ impl FallingTetromino {
(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),
(self.center_x - 1, self.center_y + 1),
],
(Tetromino::SkewLeft, Direction::South) => [
@ -209,7 +258,7 @@ impl FallingTetromino {
(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),
(self.center_x, self.center_y + 1),
],
//
@ -223,7 +272,7 @@ impl FallingTetromino {
],
(Tetromino::SkewRight, Direction::East) => [
(self.center_x, self.center_y - 1),
(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),
@ -240,7 +289,7 @@ impl FallingTetromino {
(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),
(self.center_x, self.center_y + 1),
],
//

View File

@ -1,8 +1,16 @@
mod cell;
mod draw;
pub use cell::*;
mod falling;
use crate::fall::FallingTetromino;
pub use falling::FallingTetromino;
use crate::drivers::vga::VgaColor;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TetrisCell {
Empty,
Filled { color: VgaColor },
}
pub struct TetrisBoard {
board: [TetrisCell; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT],
@ -24,7 +32,9 @@ impl TetrisBoard {
for (x, y) in tetromino.tiles() {
let cell = self.get_cell_mut(x, y);
if let Some(cell) = cell {
*cell = TetrisCell::Blue;
*cell = TetrisCell::Filled {
color: tetromino.color,
};
}
}
}