Да, мы можем эмулировать классические игры на Arduino! У нас есть 2 КБ RAM и 16 МГц — этого достаточно для эмуляции простых консолей 70-х годов.
Эмулятор PONG:
#include <avr/pgmspace.h>
#include <avr/io.h>
#include <avr/interrupt.h>
// Константы игры
#define SCREEN_WIDTH 64
#define SCREEN_HEIGHT 32
#define PADDLE_WIDTH 3
#define PADDLE_HEIGHT 8
#define BALL_SIZE 2
// Структуры игры
struct Game {
uint8_t ball_x;
uint8_t ball_y;
int8_t ball_dx;
int8_t ball_dy;
uint8_t paddle1_y;
uint8_t paddle2_y;
uint8_t score1;
uint8_t score2;
uint8_t speed;
} game;
// Буфер экрана (1 бит на пиксель)
uint8_t screen[SCREEN_WIDTH * SCREEN_HEIGHT / 8];
// Функция рисования пикселя
void drawPixel(uint8_t x, uint8_t y, uint8_t color) {
if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return;
uint16_t index = y * SCREEN_WIDTH / 8 + x / 8;
uint8_t bit = x % 8;
if (color) {
screen[index] |= (1 << bit);
} else {
screen[index] &= ~(1 << bit);
}
}
// Функция рисования прямоугольника
void drawRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) {
for (uint8_t i = 0; i < w; i++) {
for (uint8_t j = 0; j < h; j++) {
drawPixel(x + i, y + j, color);
}
}
}
// Очистка экрана
void clearScreen() {
memset(screen, 0, sizeof(screen));
}
// Вывод на OLED (SSD1306 через I2C)
void renderScreen() {
// Отправляем буфер на OLED
// ... (код для I2C)
}
// Обновление игры
void updateGame() {
// Движение мяча
game.ball_x += game.ball_dx;
game.ball_y += game.ball_dy;
// Отскок от стен
if (game.ball_y <= 0 || game.ball_y >= SCREEN_HEIGHT - BALL_SIZE) {
game.ball_dy = -game.ball_dy;
}
// Отскок от ракеток
if (game.ball_x <= PADDLE_WIDTH + 1) {
if (game.ball_y >= game.paddle1_y &&
game.ball_y <= game.paddle1_y + PADDLE_HEIGHT) {
game.ball_dx = -game.ball_dx;
game.ball_dx = (game.ball_dx > 0) ? game.ball_dx + 1 : game.ball_dx - 1;
game.speed++;
}
}
if (game.ball_x >= SCREEN_WIDTH - PADDLE_WIDTH - BALL_SIZE - 1) {
if (game.ball_y >= game.paddle2_y &&
game.ball_y <= game.paddle2_y + PADDLE_HEIGHT) {
game.ball_dx = -game.ball_dx;
game.ball_dx = (game.ball_dx < 0) ? game.ball_dx - 1 : game.ball_dx + 1;
game.speed++;
}
}
// Голы
if (game.ball_x < 0) {
game.score2++;
resetBall();
}
if (game.ball_x > SCREEN_WIDTH) {
game.score1++;
resetBall();
}
}
// Сброс мяча
void resetBall() {
game.ball_x = SCREEN_WIDTH / 2;
game.ball_y = SCREEN_HEIGHT / 2;
game.ball_dx = (random(0, 2) ? 1 : -1) * (game.speed / 5 + 1);
game.ball_dy = (random(0, 2) ? 1 : -1) * (game.speed / 5 + 1);
game.speed = 1;
}
// Инициализация игры
void initGame() {
game.ball_x = SCREEN_WIDTH / 2;
game.ball_y = SCREEN_HEIGHT / 2;
game.ball_dx = 1;
game.ball_dy = 1;
game.paddle1_y = SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2;
game.paddle2_y = SCREEN_HEIGHT / 2 - PADDLE_HEIGHT / 2;
game.score1 = 0;
game.score2 = 0;
game.speed = 1;
}
// Главный игровой цикл (на ассемблере для скорости!)
__attribute__((naked)) void gameLoop() {
asm volatile(
"initGame_asm:\n\t"
"CALL initGame\n\t"
"game_loop:\n\t"
// Читаем кнопки (пины A0 и A1)
"SBIC PINC, 0\n\t" // Если A0 HIGH, двигаем вверх
"DEC %0\n\t"
"SBIC PINC, 1\n\t" // Если A1 HIGH, двигаем вниз
"INC %0\n\t"
// Обновляем игру
"CALL updateGame\n\t"
// Рисуем
"CALL clearScreen\n\t"
"CALL drawGame\n\t"
"CALL renderScreen\n\t"
"RJMP game_loop\n\t"
:
: "r" (game.paddle1_y)
: "r0", "r1", "r16", "r17"
);
}