1
0

Reorganize

This commit is contained in:
--global 2025-02-18 20:39:57 +00:00 committed by Mark
parent 622f6d1791
commit b8cd346b0a
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
31 changed files with 192 additions and 533 deletions

View File

@ -8,24 +8,91 @@ default: all
.PHONY: clean
clean:
rm -drf $(BUILD)
cd bootloader; make clean
cd tetros; cargo clean
# Make everything
# (but don't run qemu)
.PHONY: all
all: img
# Make bios bootloader
# dd if=./bootloader/build/stage2.bin of=$@ conv=notrunc bs=512 seek=40
#
# MARK: boot
#
# Compile tetros as library
LIB_SRC = ./tetros/Cargo.toml ./tetros/Cargo.lock $(shell find ./tetros/src -type f)
$(BUILD)/tetros.lib: $(LIB_SRC)
@mkdir -p $(BUILD)
cd tetros && \
env RUSTFLAGS="-C soft-float" \
cargo rustc \
--manifest-path="./Cargo.toml" \
-Z build-std=core \
-Z build-std-features=compiler-builtins-mem \
--target "./targets/x86-unknown-none.json" \
--lib \
--release \
-- \
--emit link="$(CURDIR)/$@"
# Link tetros
BIOS_LD = ./tetros/linkers/x86-unknown-none.ld
$(BUILD)/tetros.elf: $(BUILD)/tetros.lib $(BIOS_LD)
ld \
-m elf_i386 \
--gc-sections \
-z max-page-size=0x1000 \
-T "$(BIOS_LD)" \
-o "$@" \
"$<"
objcopy --only-keep-debug "$@" "$@.sym"
objcopy --strip-debug "$@"
# Wrap tetros in three-stage BIOS loader
# Parameters:
# - BIOS_SRC: source directory of bios assembly
# - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk
BIOS_SRC = ./bios
STAGE2_SECTOR = 1
$(BUILD)/bios.bin: $(wildcard $(BIOS_SRC)/*.asm) $(BUILD)/tetros.elf
@mkdir -p "$(BUILD)"
nasm \
-f bin \
-D STAGE3=$(BUILD)/tetros.elf \
-D STAGE2_SECTOR=$(STAGE2_SECTOR) \
-o "$@" \
-l "$@.lst" \
-i "$(BIOS_SRC)" \
"$(BIOS_SRC)/main.asm"
# Extract full mbr (first 512 bytes)
$(BUILD)/mbr.bin: $(BUILD)/bios.bin
@mkdir -p "$(BUILD)"
@echo ""
dd if="$<" bs=512 count=1 of="$@"
# Extract stage 2 (rest of file)
$(BUILD)/stage2.bin: $(BUILD)/bios.bin
@mkdir -p "$(BUILD)"
@echo ""
dd if="$<" bs=512 skip=1 of="$@"
#
# MARK: bundle
#
# Make full disk image
.PHONY: img
img: $(BUILD)/disk.img
$(BUILD)/disk.img:
mkdir -p $(BUILD)
cd bootloader; make
$(BUILD)/disk.img: $(BUILD)/mbr.bin $(BUILD)/stage2.bin
@mkdir -p $(BUILD)
@echo ""
dd if=/dev/zero of=$@ bs=512 count=32
dd if=./bootloader/build/512.bin of=$@ conv=notrunc bs=512
dd if=./bootloader/build/stage2.bin of=$@ conv=notrunc seek=5 bs=512
dd if="$(BUILD)/mbr.bin" of=$@ conv=notrunc bs=512
dd if="$(BUILD)/stage2.bin" of=$@ conv=notrunc seek=$(STAGE2_SECTOR) bs=512
.PHONY: qemu
qemu: $(BUILD)/disk.img
qemu-system-i386 \
-d cpu_reset \
@ -35,6 +102,20 @@ qemu: $(BUILD)/disk.img
-net none \
-serial stdio \
-fda "$<"
# Same as qemu, but with no dependency.
# Used for remote dev, where build box != run box.
.PHONY: qemu-remote
qemu-remote:
qemu-system-i386 \
-d cpu_reset \
-no-reboot \
-smp 1 -m 2048 \
-machine q35 \
-net none \
-serial stdio \
-fda "$(BUILD)/disk.img"
# -gdb tcp::26000 \
# -S
# -enable-kvm \

5
README.md Normal file
View File

@ -0,0 +1,5 @@
- fix asm loader
- clock
- input
- music
- document everything

View File

@ -64,27 +64,6 @@ gdt:
.null equ $ - gdt
dq 0
.lm64_code equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.code
at GDTEntry.flags__limith, db gdt_flag.long_mode
at GDTEntry.baseh, db 0
iend
.lm64_data equ $ - gdt
istruc GDTEntry
at GDTEntry.limitl, dw 0
at GDTEntry.basel, dw 0
at GDTEntry.basem, db 0
; AMD System Programming Manual states that the writeable bit is ignored in long mode, but ss can not be set to this descriptor without it
at GDTEntry.attribute, db gdt_attr.present | gdt_attr.user | gdt_attr.writable
at GDTEntry.flags__limith, db 0
at GDTEntry.baseh, db 0
iend
; All GTD addresses are multiples of 8,
; and thus end in three zero bits.
;

View File

@ -1 +0,0 @@
build

View File

@ -1,83 +0,0 @@
# This compiles our bootloader as a static library,
# and wraps it in a multistage loader.
BUILD = ./build
.PHONY: all
all: $(BUILD)/mbr.bin $(BUILD)/512.bin $(BUILD)/stage2.bin
.PHONY: clean
clean:
rm -drf $(BUILD)
cd bootloader; cargo clean
# Compile bootloader as library
LIB_SRC = ./bootloader/Cargo.toml ./bootloader/Cargo.lock $(shell find ./bootloader/src -type f)
$(BUILD)/bootloader.lib: $(LIB_SRC)
@mkdir -p $(BUILD)
cd bootloader && \
env RUSTFLAGS="-C soft-float" \
cargo rustc \
--manifest-path="./Cargo.toml" \
-Z build-std=core,alloc \
-Z build-std-features=compiler-builtins-mem \
--target "./targets/x86-unknown-none.json" \
--lib \
--release \
-- \
--emit link="$(CURDIR)/$@"
# Link bootloader
BIOS_LD = ./bootloader/linkers/x86-unknown-none.ld
$(BUILD)/bootloader.elf: $(BUILD)/bootloader.lib $(BIOS_LD)
ld \
-m elf_i386 \
--gc-sections \
-z max-page-size=0x1000 \
-T "$(BIOS_LD)" \
-o "$@" \
"$<"
objcopy --only-keep-debug "$@" "$@.sym"
objcopy --strip-debug "$@"
# Wrap bootloader in three-stage BIOS loader
# Parameters:
# - BIOS_SRC: source directory of bios assembly
# - STAGE3: path to linked stage 3 binary
# - STAGE2_SECTOR: the index of the first sector of the stage 2 binary on the disk
BIOS_SRC = ./bios
STAGE2_SECTOR = 5
STAGE3 = $(BUILD)/bootloader.elf
$(BUILD)/bios.bin: $(wildcard $(BIOS_SRC)/*.asm) $(STAGE3)
@mkdir -p "$(BUILD)"
nasm \
-f bin \
-D STAGE3=$(STAGE3) \
-D STAGE2_SECTOR=$(STAGE2_SECTOR) \
-o "$@" \
-l "$@.lst" \
-i "$(BIOS_SRC)" \
"$(BIOS_SRC)/main.asm"
# Extract MBR code (first 440 bytes)
# This can be used to embed this mbr in gpt-partitioned disks
$(BUILD)/mbr.bin: $(BUILD)/bios.bin
@mkdir -p "$(BUILD)"
@echo ""
dd if="$<" bs=440 count=1 of="$@"
# Extract full mbr (first 512 bytes)
# This can be used to make raw boot disks
$(BUILD)/512.bin: $(BUILD)/bios.bin
@mkdir -p "$(BUILD)"
@echo ""
dd if="$<" bs=512 count=1 of="$@"
# Extract stage 2 (rest of file)
$(BUILD)/stage2.bin: $(BUILD)/bios.bin
@mkdir -p "$(BUILD)"
@echo ""
dd if="$<" bs=512 skip=1 of="$@"

View File

@ -1,73 +0,0 @@
#![no_std]
#![feature(alloc_error_handler)]
#![feature(int_roundings)]
#![feature(lang_items)]
#![allow(internal_features)]
extern crate alloc;
use self::os::{OsMemoryEntry, OsMemoryKind};
#[macro_use]
mod os;
#[macro_use]
mod serial;
//TODO: allocate this in a more reasonable manner
static mut AREAS: [OsMemoryEntry; 1024] = [OsMemoryEntry {
base: 0,
size: 0,
kind: OsMemoryKind::Null,
}; 1024];
static mut AREAS_LEN: usize = 0;
pub fn area_add(area: OsMemoryEntry) {
unsafe {
for existing_area in &mut AREAS[0..AREAS_LEN] {
if existing_area.kind == area.kind {
if existing_area.base.unchecked_add(existing_area.size) == area.base {
existing_area.size += area.size;
return;
}
if area.base.unchecked_add(area.size) == existing_area.base {
existing_area.base = area.base;
return;
}
}
}
*AREAS.get_mut(AREAS_LEN).expect("AREAS overflowed!") = area;
AREAS_LEN += 1;
}
}
pub static mut LIVE_OPT: Option<(u64, &'static [u8])> = None;
#[allow(dead_code)]
#[derive(Debug)]
#[repr(C, packed(8))]
pub struct KernelArgs {
kernel_base: u64,
kernel_size: u64,
stack_base: u64,
stack_size: u64,
env_base: u64,
env_size: u64,
/// The base pointer to the saved RSDP.
///
/// This field can be NULL, and if so, the system has not booted with UEFI or in some other way
/// retrieved the RSDPs. The kernel or a userspace driver will thus try searching the BIOS
/// memory instead. On UEFI systems, searching is not guaranteed to actually work though.
acpi_rsdp_base: u64,
/// The size of the RSDP region.
acpi_rsdp_size: u64,
areas_base: u64,
areas_size: u64,
bootstrap_base: u64,
bootstrap_size: u64,
}
fn main() {}

View File

@ -1,84 +0,0 @@
use core::{cmp, mem, ptr};
use crate::area_add;
use crate::os::{OsMemoryEntry, OsMemoryKind};
use super::{thunk::ThunkData, MEMORY_MAP_ADDR};
#[repr(C, packed)]
struct MemoryMapEntry {
pub base: u64,
pub size: u64,
pub kind: u32,
}
pub struct MemoryMapIter {
thunk15: extern "C" fn(),
data: ThunkData,
first: bool,
}
impl MemoryMapIter {
pub fn new(thunk15: extern "C" fn()) -> Self {
Self {
thunk15,
data: ThunkData::new(),
first: true,
}
}
}
impl Iterator for MemoryMapIter {
type Item = OsMemoryEntry;
fn next(&mut self) -> Option<Self::Item> {
if self.first {
self.first = false;
} else if self.data.ebx == 0 {
return None;
}
self.data.eax = 0xE820;
self.data.ecx = mem::size_of::<MemoryMapEntry>() as u32;
self.data.edx = 0x534D4150;
self.data.edi = MEMORY_MAP_ADDR as u32;
unsafe {
self.data.with(self.thunk15);
}
//TODO: return error?
assert_eq!({ self.data.eax }, 0x534D4150);
assert_eq!({ self.data.ecx }, mem::size_of::<MemoryMapEntry>() as u32);
let entry = unsafe { ptr::read(MEMORY_MAP_ADDR as *const MemoryMapEntry) };
Some(Self::Item {
base: entry.base,
size: entry.size,
kind: match entry.kind {
0 => OsMemoryKind::Null,
1 => OsMemoryKind::Free,
3 => OsMemoryKind::Reclaim,
_ => OsMemoryKind::Reserved,
},
})
}
}
pub unsafe fn memory_map(thunk15: extern "C" fn()) -> Option<(usize, usize)> {
let mut heap_limits = None;
for entry in MemoryMapIter::new(thunk15) {
let heap_start = 1 * 1024 * 1024;
if { entry.kind } == OsMemoryKind::Free
&& entry.base <= heap_start as u64
&& (entry.base + entry.size) >= heap_start as u64
{
let heap_end = cmp::min(entry.base + entry.size, usize::MAX as u64) as usize;
if heap_end >= heap_start {
heap_limits = Some((heap_start, heap_end - heap_start));
}
}
area_add(entry);
}
heap_limits
}

View File

@ -1,119 +0,0 @@
use core::{fmt, slice};
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct VgaTextBlock {
pub char: u8,
pub color: u8,
}
#[allow(dead_code)]
#[derive(Clone, Copy)]
#[repr(u8)]
pub enum VgaTextColor {
Black = 0,
Blue = 1,
Green = 2,
Cyan = 3,
Red = 4,
Purple = 5,
Brown = 6,
Gray = 7,
DarkGray = 8,
LightBlue = 9,
LightGreen = 10,
LightCyan = 11,
LightRed = 12,
LightPurple = 13,
Yellow = 14,
White = 15,
}
pub struct Vga {
pub base: usize,
pub width: usize,
pub height: usize,
pub x: usize,
pub y: usize,
pub bg: VgaTextColor,
pub fg: VgaTextColor,
}
impl Vga {
pub const unsafe fn new(base: usize, width: usize, height: usize) -> Self {
Self {
base,
width,
height,
x: 0,
y: 0,
bg: VgaTextColor::Black,
fg: VgaTextColor::Gray,
}
}
pub unsafe fn blocks(&mut self) -> &'static mut [VgaTextBlock] {
slice::from_raw_parts_mut(self.base as *mut VgaTextBlock, self.width * self.height)
}
pub fn clear(&mut self) {
self.x = 0;
self.y = 0;
let blocks = unsafe { self.blocks() };
for i in 0..blocks.len() {
blocks[i] = VgaTextBlock {
char: 0,
color: ((self.bg as u8) << 4) | (self.fg as u8),
};
}
}
}
impl fmt::Write for Vga {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let blocks = unsafe { self.blocks() };
for c in s.chars() {
if self.x >= self.width {
self.x = 0;
self.y += 1;
}
while self.y >= self.height {
for y in 1..self.height {
for x in 0..self.width {
let i = y * self.width + x;
let j = i - self.width;
blocks[j] = blocks[i];
if y + 1 == self.height {
blocks[i].char = 0;
}
}
}
self.y -= 1;
}
match c {
'\x08' => {
if self.x > 0 {
self.x -= 1;
}
}
'\r' => {
self.x = 0;
}
'\n' => {
self.x = 0;
self.y += 1;
}
_ => {
let i = self.y * self.width + self.x;
if let Some(block) = blocks.get_mut(i) {
block.char = c as u8;
block.color = ((self.bg as u8) << 4) | (self.fg as u8);
}
self.x += 1;
}
}
}
Ok(())
}
}

View File

@ -1,22 +0,0 @@
pub use self::bios::*;
#[macro_use]
mod bios;
// Keep synced with BootloaderMemoryKind in kernel
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u64)]
pub enum OsMemoryKind {
Null = 0,
Free = 1,
Reclaim = 2,
Reserved = 3,
}
// Keep synced with BootloaderMemoryEntry in kernel
#[derive(Clone, Copy, Debug)]
#[repr(C, packed(8))]
pub struct OsMemoryEntry {
pub base: u64,
pub size: u64,
pub kind: OsMemoryKind,
}

View File

@ -35,15 +35,6 @@ dependencies = [
"spin",
]
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
dependencies = [
"spinning_top",
]
[[package]]
name = "lock_api"
version = "0.4.12"
@ -84,21 +75,11 @@ dependencies = [
"lock_api",
]
[[package]]
name = "spinning_top"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
dependencies = [
"lock_api",
]
[[package]]
name = "tetros"
version = "1.0.0"
dependencies = [
"lazy_static",
"linked_list_allocator",
"spin",
"uart_16550",
]

View File

@ -6,7 +6,7 @@ publish = false
[lib]
name = "tetros"
path = "src/main.rs"
path = "src/lib.rs"
crate-type = ["staticlib"]
@ -32,7 +32,6 @@ missing_docs = "allow"
needless_return = "allow"
new_without_default = "allow"
tabs_in_doc_comments = "allow"
expect_used = "deny"
dbg_macro = "deny"
allow_attributes = "deny"
create_dir = "deny"
@ -55,8 +54,9 @@ match_on_vec_items = "deny"
wildcard_dependencies = "deny"
negative_feature_names = "deny"
redundant_feature_names = "deny"
multiple_crate_versions = "deny"
multiple_crate_versions = "allow"
missing_safety_doc = "allow"
identity_op = "allow"
#
# MARK: dependencies
@ -64,8 +64,7 @@ multiple_crate_versions = "deny"
[dependencies]
linked_list_allocator = "0.10.5"
spin = "0.9.5"
spin = "0.9.8"
uart_16550 = "0.3.2"
[dependencies.lazy_static]

View File

@ -0,0 +1,4 @@
#[macro_use]
pub mod serial;
pub mod vga;

View File

@ -42,7 +42,7 @@ pub fn _print(args: core::fmt::Arguments<'_>) {
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {
$crate::serial::_print(format_args!($($arg)*));
$crate::drivers::serial::_print(format_args!($($arg)*));
};
}

View File

@ -1,26 +1,18 @@
use linked_list_allocator::LockedHeap;
#![no_std]
#![feature(int_roundings)]
#![feature(lang_items)]
#![allow(internal_features)]
use drivers::vga::Vga13h;
use os::thunk::ThunkData;
use spin::Mutex;
use tetris::TetrisBoard;
use vga::Vga13h;
use tetrisboard::TetrisBoard;
use self::memory_map::memory_map;
use self::thunk::ThunkData;
mod os;
mod tetrisboard;
use crate::println;
mod memory_map;
mod panic;
mod tetris;
mod thunk;
mod vga;
// Real mode memory allocation, for use with thunk
// 0x500 to 0x7BFF is free
const MEMORY_MAP_ADDR: usize = 0x1380; // 24 bytes, ends at 0x1397
const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards
#[global_allocator]
static ALLOCATOR: LockedHeap = LockedHeap::empty();
#[macro_use]
mod drivers;
pub(crate) static VGA: Mutex<Vga13h> = Mutex::new(unsafe { Vga13h::new() });
@ -67,9 +59,5 @@ pub unsafe extern "C" fn start(
let t = TetrisBoard::new();
t.draw(&mut v);
let (heap_start, heap_size) = memory_map(thunk15).expect("No memory for heap");
ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);
panic!("kernel");
}

6
tetros/src/os/mod.rs Normal file
View File

@ -0,0 +1,6 @@
pub mod thunk;
#[macro_use]
pub mod panic;
const THUNK_STACK_ADDR: usize = 0x7C00; // Grows downwards

View File

@ -1,6 +1,5 @@
//! Intrinsics for panic handling
use core::alloc::Layout;
use core::arch::asm;
use core::panic::PanicInfo;
@ -22,13 +21,6 @@ pub fn rust_begin_unwind(info: &PanicInfo<'_>) -> ! {
}
}
#[alloc_error_handler]
#[no_mangle]
#[allow(improper_ctypes_definitions)] // Layout is not repr(C)
pub extern "C" fn rust_oom(_layout: Layout) -> ! {
panic!("memory allocation failed");
}
#[allow(non_snake_case)]
#[no_mangle]
/// Required to handle panics

View File

@ -0,0 +1,31 @@
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
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,70 +1,10 @@
use super::vga::Vga13h;
use crate::drivers::vga::Vga13h;
#[repr(u8)]
#[derive(Debug, Clone, Copy)]
pub enum TetrisCell {
Empty,
Blue,
Cyan,
Orange,
Red,
Green,
Purple,
Yellow,
}
impl TetrisCell {
pub fn to_vga_color(&self) -> u8 {
match self {
Self::Empty => 0b000_00000,
Self::Blue => 0b000_00001,
Self::Cyan => 0b000_00011,
Self::Orange => 0b000_00110,
Self::Red => 0b000_00100,
Self::Green => 0b000_00010,
Self::Purple => 0b000_00101,
Self::Yellow => 0b110_000_00,
}
}
pub fn is_empty(&self) -> bool {
matches!(self, Self::Empty)
}
}
pub struct TetrisBoard {
board: [TetrisCell; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT],
}
use super::{TetrisBoard, TetrisCell};
impl TetrisBoard {
const BOARD_WIDTH: usize = 10;
const BOARD_HEIGHT: usize = 20;
const CELL_SIZE: usize = 9;
pub fn new() -> Self {
let mut x = 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;
x.board[9] = TetrisCell::Blue;
x.board[8] = TetrisCell::Blue;
x.board[10] = TetrisCell::Blue;
x.board[12] = TetrisCell::Blue;
x
}
fn draw_cell(&self, fb: &mut [u8], state: TetrisCell, x: usize, y: usize) {
let color = state.to_vga_color();
let color = state.vga_color();
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);
@ -73,7 +13,7 @@ impl TetrisBoard {
}
fn draw_frame(&self, fb: &mut [u8], x: usize, y: usize) {
let color = 0b111_000_00;
let color = 0b1110_0000;
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);

View File

@ -0,0 +1,35 @@
mod cell;
mod draw;
pub use cell::*;
pub struct TetrisBoard {
board: [TetrisCell; TetrisBoard::BOARD_WIDTH * TetrisBoard::BOARD_HEIGHT],
}
impl TetrisBoard {
const BOARD_WIDTH: usize = 10;
const BOARD_HEIGHT: usize = 20;
const CELL_SIZE: usize = 9;
pub fn new() -> Self {
let mut x = 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;
x.board[9] = TetrisCell::Blue;
x.board[8] = TetrisCell::Blue;
x.board[10] = TetrisCell::Blue;
x.board[12] = TetrisCell::Blue;
x
}
}