#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED 顯示器寬度,以像素為單位
#define SCREEN_HEIGHT 64 // OLED 顯示器高度,以像素為單位
// 連接到 I2C 的 SSD1306 顯示器的聲明(SDA、SCL 引腳)
#define OLED_RESET 4 // 复位引脚号(如果与 Arduino 复位引脚共用,则为 -1)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const byte buttonPins[] = {4, 2, 5, 3}; // 左、上、右、下按钮的引脚
int converterPins[5] = {1, 1, 1, 1, 1}; // 左、上、右、下、中按鈕
//下列是搖桿的接腳
const int SW_pin = 8; // digital pin connected to switch output
const int X_pin = A0; // analog pin connected to X output
const int Y_pin = A1; // analog pin connected to Y output
typedef enum {
START, // 遊戲開始狀態
RUNNING, // 遊戲進行中狀態
GAMEOVER // 遊戲結束狀態
} State;
typedef enum {
LEFT, // 向左移動
UP, // 向上移動
RIGHT, // 向右移動
DOWN // 向下移動
} Direction;
#define SNAKE_PIECE_SIZE 3 // 貪食蛇每節大小
#define MAX_SANKE_LENGTH 165 // 貪食蛇最大長度
#define MAP_SIZE_X 20 // 地圖寬度
#define MAP_SIZE_Y 20 // 地圖高度
#define STARTING_SNAKE_SIZE 5 // 初始貪食蛇長度
#define SNAKE_MOVE_DELAY 30 // 貪食蛇移動延遲時間
State gameState; // 遊戲狀態
int8_t snake[MAX_SANKE_LENGTH][2]; // 貪食蛇位置
uint8_t snake_length; // 貪食蛇長度
Direction dir; // 當前方向
Direction newDir; // 新方向
int8_t fruit[2]; // 水果位置
void setup() {
pinMode(SW_pin, INPUT);
digitalWrite(SW_pin, HIGH);//搖桿中央按鈕
Serial.begin(9600);
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
Serial.println(F("SSD1306 failed"));
for(;;);
}
for (byte i = 0; i < 4; i++) {
pinMode(buttonPins[i], INPUT_PULLUP);
}
randomSeed(analogRead(A0));
setupGame();
}
void setupGame() {
gameState = START; // 設置遊戲狀態為開始
dir = RIGHT; // 設置方向為向右
newDir = RIGHT; // 設置新方向為向右
resetSnake(); // 重置貪食蛇
generateFruit(); // 生成水果
display.clearDisplay(); // 清空顯示器
drawMap(); // 繪製地圖
drawScore(); // 繪製分數
drawPressToStart(); // 繪製按下按鈕開始的提示
display.display(); // 顯示
}
void resetSnake() {
snake_length = STARTING_SNAKE_SIZE; // 設置貪食蛇初始長度
for(int i = 0; i < snake_length; i++) {
snake[i][0] = MAP_SIZE_X / 2 - i;
snake[i][1] = MAP_SIZE_Y / 2;
}
}
int moveTime = 0;
void loop() {
switch(gameState) {
case START: // 遊戲開始狀態
if(buttonPress()) gameState = RUNNING; // 如果按鈕按下,進入遊戲進行中狀態
break;
case RUNNING: // 遊戲進行中狀態
moveTime++; // 移動時間計數
readDirection(); // 讀取方向
if(moveTime >= SNAKE_MOVE_DELAY) {
dir = newDir; // 更新方向
display.clearDisplay(); // 清空顯示器
if(moveSnake()) { // 移動貪食蛇
gameState = GAMEOVER; // 遊戲結束
drawGameover(); // 繪製遊戲結束畫面
delay(1000);
}
drawMap(); // 繪製地圖
drawScore(); // 繪製分數
display.display(); // 顯示
checkFruit(); // 檢查是否吃到水果
moveTime = 0; // 重置移動時間計數
}
break;
case GAMEOVER: // 遊戲結束狀態
if(buttonPress()) { // 如果按鈕按下
delay(500);
setupGame(); // 重新設置遊戲
gameState = START; // 回到遊戲開始狀態
}
break;
}
delay(10);
}
bool buttonPress() { //檢查按鈕按下(遊戲是否開始)
if (analogRead(X_pin) <= 500 ) {
return true;
}
if (analogRead(Y_pin) >= 600 ) {
return true;
}
if (analogRead(X_pin) >= 600 ) {
return true;
}
if (analogRead(Y_pin) <= 500 ) {
return true;
}
if (digitalRead(8)== LOW) { // 中間
return true;
}
return false;
}
void converter() {
static int lastX = 0;
static int lastY = 0;
static int lastSW = HIGH;
int newX = analogRead(X_pin);
int newY = analogRead(Y_pin);
int newSW = digitalRead(SW_pin);
if (newX != lastX || newY != lastY || newSW != lastSW) {
lastX = newX;
lastY = newY;
lastSW = newSW;
if (newY <= 500) { // 下
converterPins[3] = 0; // 下
} else converterPins[3] = 1;
if (newX <= 500) { // 左
converterPins[0] = 0; // 左
} else converterPins[0] = 1;
if (newY >= 600) { // 上
converterPins[1] = 0; // 上
} else converterPins[1] = 1;
if (newX >= 600) { // 右
converterPins[2] = 0; // 右
} else converterPins[2] = 1;
if (newSW == LOW) { // 中間
converterPins[4] = 0;
} else converterPins[4] = 1;
}
}
void readDirection() { //從按鈕讀取新的方向
if (analogRead(X_pin) <= 500 && 0 != ((int)dir + 2) % 4) {
newDir = (Direction)0;
return;
}
if (analogRead(Y_pin) >= 600 && 1 != ((int)dir + 2) % 4) {
newDir = (Direction)1;
return;
}
if (analogRead(X_pin) >= 600 && 2 != ((int)dir + 2) % 4) {
newDir = (Direction)2;
return;
}
if (analogRead(Y_pin) <= 500&& 3 != ((int)dir + 2) % 4) {
newDir = (Direction)3;
return;
}
}
bool moveSnake() { //用於移動貪食蛇
int8_t x = snake[0][0];
int8_t y = snake[0][1];
switch(dir) {
case LEFT:
x -= 1;
break;
case UP:
y -= 1;
break;
case RIGHT:
x += 1;
break;
case DOWN:
y += 1;
break;
}
if(collisionCheck(x, y))
return true;
for(int i = snake_length - 1; i > 0; i--) {
snake[i][0] = snake[i - 1][0];
snake[i][1] = snake[i - 1][1];
}
snake[0][0] = x;
snake[0][1] = y;
return false;
}
void checkFruit() {
if(fruit[0] == snake[0][0] && fruit[1] == snake[0][1])
{
if(snake_length + 1 <= MAX_SANKE_LENGTH)
snake_length++;
generateFruit();
}
}
void generateFruit() {
bool b = false;
do {
b = false;
fruit[0] = random(0, MAP_SIZE_X);
fruit[1] = random(0, MAP_SIZE_Y);
for(int i = 0; i < snake_length; i++) {
if(fruit[0] == snake[i][0] && fruit[1] == snake[i][1]) {
b = true;
continue;
}
}
} while(b);
}
bool collisionCheck(int8_t x, int8_t y) {
for(int i = 1; i < snake_length; i++) {
if(x == snake[i][0] && y == snake[i][1]) return true;
}
if(x < 0 || y < 0 || x >= MAP_SIZE_X || y >= MAP_SIZE_Y) return true;
return false;
}
void drawMap() {
int offsetMapX = SCREEN_WIDTH - SNAKE_PIECE_SIZE * MAP_SIZE_X - 2;
int offsetMapY = 2;
display.drawRect(fruit[0] * SNAKE_PIECE_SIZE + offsetMapX, fruit[1] * SNAKE_PIECE_SIZE + offsetMapY, SNAKE_PIECE_SIZE, SNAKE_PIECE_SIZE, SSD1306_INVERSE);
display.drawRect(offsetMapX - 2, 0, SNAKE_PIECE_SIZE * MAP_SIZE_X + 4, SNAKE_PIECE_SIZE * MAP_SIZE_Y + 4, SSD1306_WHITE);
for(int i = 0; i < snake_length; i++) {
display.fillRect(snake[i][0] * SNAKE_PIECE_SIZE + offsetMapX, snake[i][1] * SNAKE_PIECE_SIZE + offsetMapY, SNAKE_PIECE_SIZE, SNAKE_PIECE_SIZE, SSD1306_WHITE);
}
}
void drawScore() {
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(2,2);
display.print(F("score:"));
display.println(snake_length - STARTING_SNAKE_SIZE);
}
void drawPressToStart() {
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(2,20);
display.print(F("Press\nStart!"));
}
void drawGameover() {
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(2,30);
display.println(F(" GaMe\n OvEr :("));
}