Random generation
This commit is contained in:
parent
5022c840de
commit
1309ee7489
@ -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
72
tetros/Cargo.lock
generated
@ -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",
|
||||
]
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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:
|
||||
///
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
],
|
||||
|
||||
//
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user