Часть 27: Эмулятор игровой консоли на Arduino (PONG на 2 КБ!)

Да, мы можем эмулировать классические игры на 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"
    );
}

Вам также может понравиться

About the Author: ардуинчиков

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

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