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

от автора

Тян из 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-графики. Если вы когда-нибудь мечтали создать свою игру без ОС — сейчас самое время.

Репозиторий и весь код: https://github.com/digkill/platinum-os-arkanoid

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


ссылка на оригинал статьи https://habr.com/ru/articles/907162/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *