Как стать автором
Обновить

Создаём арканоид в VGA-текстовом режиме на Rust без std и alloc (своя мини ОС)

Время на прочтение3 мин
Количество просмотров3.7K
Тян из 80х
Тян из 80х

Здравствуйте, уважаемые читатели!

В этой статье я хотел бы поделиться необычным и вдохновляющим проектом — реализацией арканоида в текстовом VGA-режиме, написанного полностью на Rust, без использования стандартной библиотеки и даже без аллокации памяти (#![no_std] + no_alloc).

Проект работает напрямую с VGA-памятью и PS/2 клавиатурой через порт 0x60, создавая абсолютно нативную игру в стиле 80-х, но с современным вниманием к качеству кода. И всё это — с участием милого талисмана Platinum-tan.

Зачем вообще создавать игру в текстовом режиме?

Для нас, разработчиков, работающих с высокоуровневыми фреймворками и богатыми API, подобные проекты становятся отличной возможностью:

  • Погрузиться в низкоуровневую архитектуру x86

  • Попрактиковаться в чистой архитектуре и системном программировании

  • Понять, как можно обойтись без аллокации, без стандартных зависимостей

  • И просто — почувствовать дух ретро

  • Архитектура проекта

Проект написан на Rust с использованием #![no_std] и bootimage, что позволяет компилировать игру в виде загрузочного ядра.

Основные модули:

Модуль

Назначение

vga_buffer

Прямая работа с VGA-буфером 0xb8000

port_io

Чтение клавиш напрямую с inb(0x60)

arkanoid

Игровая логика, отрисовка, физика мячика

main.rs

Точка входа, игровой цикл, обработка клавиш

Особенности реализации

  • Мячик (o) отскакивает от стен, блоков и платформы

  • Платформа (=======) управляется клавишами A и D

  • Цвета блоков зависят от строки (радуга в ASCII)

  • Поддержка SPACE для начала и R для рестарта

  • Вывод очков и милых сообщений от Platinum-tan

Немного магии: как работают цвета

ColorCode::new(Color::Red, Color::Black)

Каждый символ в VGA-буфере имеет не только ASCII-байт, но и байт цвета. Мы изменяем эти цвета на лету — и получаем полноцветный «текстовый» арканоид.

Чтение клавиш без драйвера

pub unsafe fn inb(port: u16) -> u8 {
    let value: u8;
    core::arch::asm!(\"in al, dx\", out(\"al\") value, in(\"dx\") port);
    value
}

Клавиши считываются напрямую из порта 0x60 — без драйверов, без ОС, просто железо и вы.

Вывод информации в виртуальную машину кастомный и реализован через vga_buffer так как мы не можем использовать вывод с использованием no_std

...

impl Writer {
    pub fn new() -> Self {
        Self {
            column_position: 0,
            color_code: ColorCode::new(Color::Yellow, Color::Black),
            buffer: unsafe { &mut *(VGA_BUFFER_ADDR as *mut Buffer) },
        }
    }

    pub fn write_byte(&mut self, byte: u8) {
        match byte {
            b'\n' => self.new_line(),
            byte => {
                if self.column_position >= BUFFER_WIDTH {
                    self.new_line();
                }

                let row = BUFFER_HEIGHT - 1;
                let col = self.column_position;

                self.buffer.chars[row][col].write(ScreenChar {
                    ascii_character: byte,
                    color_code: self.color_code,
                });

                self.column_position += 1;
            }
        }
    }

    pub fn write_string(&mut self, s: &str) {
        for byte in s.bytes() {
            match byte {
                0x20..=0x7e | b'\n' => self.write_byte(byte),
                _ => self.write_byte(0xfe),
            }
        }
    }

    pub fn new_line(&mut self) {
        for row in 1..BUFFER_HEIGHT {
            for col in 0..BUFFER_WIDTH {
                let c = self.buffer.chars[row][col].read();
                self.buffer.chars[row - 1][col].write(c);
            }
        }
        self.clear_row(BUFFER_HEIGHT - 1);
        self.column_position = 0;
    }

    fn clear_row(&mut self, row: usize) {
        let blank = ScreenChar {
            ascii_character: b' ',
            color_code: self.color_code,
        };
        for col in 0..BUFFER_WIDTH {
            self.buffer.chars[row][col].write(blank);
        }
    }

    /// Выводит строку по центру последней строки экрана
    pub fn write_centered(&mut self, text: &str) {
        let len = text.len().min(BUFFER_WIDTH);
        let padding = (BUFFER_WIDTH - len) / 2;
        self.column_position = padding;
        self.write_string(text);
    }

...

Как собрать и запустить

Установите Rust nightly и bootimage:

rustup install nightly
rustup component add rust-src --toolchain nightly
cargo install bootimage

Соберите ядро:

cargo +nightly bootimage -Z build-std=core,compiler_builtins --target x86_64-platinum_os.json -Z build-std-features=compiler-builtins-mem --target-dir target/x86_64-platinum_os

Запустите в QEMU:

qemu-system-x86_64 -drive format=raw,file=target\x86_64-platinum_os\x86_64-platinum_os\debug\bootimage-platinum_os.bin -serial stdio -no-reboot -no-shutdown

Демо: Скриншот

Сам арканоид
Сам арканоид

Вдохновение

Это не просто технический эксперимент — это способ выразить любовь к железу, к языку Rust, и к эстетике ASCII-графики. Если вы когда-нибудь мечтали создать свою игру без ОС — сейчас самое время.

Репозиторий и весь код

Спасибо за внимание!

Теги:
Хабы:
+27
Комментарии25

Публикации

Работа

Rust разработчик
8 вакансий

Ближайшие события