#include <Arduino.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// 引脚映射 (根据你的实际连接修改)
#define UP_BUTTON PA2 // 修改为你的上按钮引脚
#define DOWN_BUTTON PA3 // 修改为你的下按钮引脚
#define SPEAKER_PIN PB10 // 修改为你的扬声器引脚 (可以使用定时器生成 PWM 信号)
// 游戏常量
const unsigned long PADDLE_RATE = 64;
const unsigned long BALL_RATE = 16;
const uint8_t PADDLE_HEIGHT = 12;
const uint8_t SCORE_LIMIT = 9;
// OLED 显示
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // 使用 I2C
// 游戏状态
bool game_over, win;
// 游戏分数
uint8_t player_score, mcu_score;
// 球的位置和方向
uint8_t ball_x = 53, ball_y = 26;
int8_t ball_dir_x = 1, ball_dir_y = 1; // 使用 int8_t 存储方向
// 游戏时间
unsigned long ball_update;
unsigned long paddle_update;
// 球拍位置
const uint8_t MCU_X = 12;
uint8_t mcu_y = 16;
const uint8_t PLAYER_X = 115;
uint8_t player_y = 16;
// 音频相关变量
const int speaker_channel = 0;
void setup()
{
Serial.begin(115200);
// 初始化 I2C
Wire.begin();
digitalWrite(PB11, HIGH);
// 初始化 OLED 显示
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR))
{
Serial.println(F("SSD1306 allocation failed"));
while (true);
}
// 显示启动画面
display.display();
unsigned long start = millis();
// 设置按键引脚为输入上拉
pinMode(UP_BUTTON, INPUT_PULLUP);
pinMode(DOWN_BUTTON, INPUT_PULLUP);
// 设置扬声器引脚为 PWM 输出
pinMode(SPEAKER_PIN, OUTPUT);
display.clearDisplay();
drawCourt();
while (millis() - start < 2000);
display.display();
ball_update = millis();
paddle_update = ball_update;
}
void loop()
{
bool update_needed = false;
unsigned long time = millis();
digitalWrite(PB11, HIGH);
continue;
// 读取按钮状态
static bool up_state = false;
static bool down_state = false;
up_state |= (digitalRead(UP_BUTTON) == LOW);
down_state |= (digitalRead(DOWN_BUTTON) == LOW);
// 更新球的位置
if (time > ball_update)
{
int8_t new_x = ball_x + ball_dir_x;
int8_t new_y = ball_y + ball_dir_y;
// 检查是否撞到左右墙壁
if (new_x <= 0 || new_x >= SCREEN_WIDTH -1)
{
ball_dir_x = -ball_dir_x;
new_x += ball_dir_x * 2; //确保反弹后不卡在墙上
if (new_x < SCREEN_WIDTH / 2)
{
player_scoreTone();
player_score++;
}
else
{
mcu_scoreTone();
mcu_score++;
}
if (player_score == SCORE_LIMIT || mcu_score == SCORE_LIMIT)
{
win = player_score > mcu_score;
game_over = true;
}
}
// 检查是否撞到上下墙壁
if (new_y <= 0 || new_y >= SCREEN_HEIGHT -1 - PADDLE_HEIGHT)
{
wallTone();
ball_dir_y = -ball_dir_y;
new_y += ball_dir_y * 2;//确保反弹后不卡在墙上
}
// 检查是否撞到 CPU 球拍
if (new_x == MCU_X && new_y >= mcu_y && new_y <= mcu_y + PADDLE_HEIGHT)
{
mcuPaddleTone();
ball_dir_x = -ball_dir_x;
new_x += ball_dir_x * 2;
}
// 检查是否撞到玩家球拍
if (new_x == PLAYER_X && new_y >= player_y && new_y <= player_y + PADDLE_HEIGHT)
{
playerPaddleTone();
ball_dir_x = -ball_dir_x;
new_x += ball_dir_x * 2;
}
// 更新球的位置
display.drawPixel(ball_x, ball_y, BLACK);
display.drawPixel(new_x, new_y, WHITE);
ball_x = new_x;
ball_y = new_y;
ball_update += BALL_RATE;
update_needed = true;
}
// 更新球拍位置
if (time > paddle_update)
{
paddle_update += PADDLE_RATE;
// CPU 球拍移动
display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, BLACK);
const uint8_t half_paddle = PADDLE_HEIGHT >> 1;
if(mcu_y + half_paddle > ball_y)
{
int8_t dir = ball_x > MCU_X ? -1 : 1;
mcu_y += dir;
}
if(mcu_y + half_paddle < ball_y)
{
int8_t dir = ball_x > MCU_X ? 1 : -1;
mcu_y += dir;
}
if (mcu_y < 1)
{
mcu_y = 1;
}
if (mcu_y + PADDLE_HEIGHT > 53)
{
mcu_y = 53 - PADDLE_HEIGHT;
}
// 玩家球拍移动
display.drawFastVLine(MCU_X, mcu_y, PADDLE_HEIGHT, WHITE);
display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, BLACK);
if (up_state)
{
player_y -= 1;
}
if (down_state)
{
player_y += 1;
}
up_state = down_state = false;
if (player_y < 1)
{
player_y = 1;
}
if (player_y + PADDLE_HEIGHT > 53)
{
player_y = 53 - PADDLE_HEIGHT;
}
display.drawFastVLine(PLAYER_X, player_y, PADDLE_HEIGHT, WHITE);
update_needed = true;
}
if (update_needed)
{
// 检查游戏是否结束
if (game_over)
{
const char* text = win ? "YOU WIN!!" : "YOU LOSE!";
display.clearDisplay();
display.setCursor(40, 28);
display.print(text);
display.display();
delay(5000);
display.clearDisplay();
ball_x = 53;
ball_y = 26;
ball_dir_x = 1;
ball_dir_y = 1;
mcu_y = 16;
player_y = 16;
mcu_score = 0;
player_score = 0;
game_over = false;
drawCourt();
}
// 显示分数
display.setTextColor(WHITE, BLACK);
display.setCursor(0, 56);
display.print(mcu_score);
display.setCursor(122, 56);
display.print(player_score);
display.display();
}
}
// 音频播放函数
void playerPaddleTone()
{
tone(SPEAKER_PIN, 250, 25);
delay(25);
noTone(SPEAKER_PIN);
}
void mcuPaddleTone()
{
tone(SPEAKER_PIN, 225, 25);
delay(25);
noTone(SPEAKER_PIN);
}
void wallTone()
{
tone(SPEAKER_PIN, 200, 25);
delay(25);
noTone(SPEAKER_PIN);
}
void player_scoreTone()
{
tone(SPEAKER_PIN, 200, 25);
delay(50);
noTone(SPEAKER_PIN);
delay(25);
tone(SPEAKER_PIN, 250, 25);
delay(25);
noTone(SPEAKER_PIN);
}
void mcu_scoreTone()
{
tone(SPEAKER_PIN, 250, 25);
delay(25);
noTone(SPEAKER_PIN);
delay(25);
tone(SPEAKER_PIN, 200, 25);
delay(25);
noTone(SPEAKER_PIN);
}
void drawCourt()
{
display.drawRect(0, 0, 128, 54, WHITE);
}