#include "LedControl.h"
struct Pin {//管脚设置部分
static const short joystickX = A2;
static const short joystickY = A3;
static const short joystickVCC = 15;
static const short joystickGND = 14;
static const short CLK = 3;
static const short CS = 4;
static const short DIN = 5;
};
const short initialSnakeLength = 3;//初始蛇的长度
void setup() {
Serial.begin(9600); // 设置波特率
initialize(); // 初始化
showSnakeMessage(); // //滚动显示字符SNAKE
}
void loop() {
generateFood(); // 如果没用食物,则创造一个
scanJoystick(); // 判定摇杆方向
calculateSnake(); // 调整蛇的位置,判断是否吃到食物以及是否失败
GameStates(); //判断游戏状态,是否结束,结束则播放动画
}
LedControl matrix(Pin::DIN, Pin::CLK, Pin::CS, 1);
struct Point {
int row = 0, col = 0;
Point(int row = 0, int col = 0): row(row), col(col) {}
};
struct Coordinate {
int x = 0, y = 0;
Coordinate(int x = 0, int y = 0): x(x), y(y) {}
};
bool win = false;
bool gameOver = false;
Point snake;//创建数组为蛇的身体
Point food(-1, -1);//创建食物位置的数组,未创建食物前食物的初始位置,即不显示食物
Coordinate joystickHome(500, 500);//设定摇杆默认原点
int snakeLength = initialSnakeLength; //设置蛇的长度为初始设定长度
int snakeDirection = 0; // 没使用摇杆前,蛇不移动
const short up = 1;
const short right = 2;
const short down = 3;
const short left = 4;
const int joystickThreshold = 160;//摇杆阈值设定,越小则摇杆越灵敏,160为合适值
int gameboard[8][8] = {};//创建蛇的身体数组
//创造食物和判定获胜
void generateFood() {
if (food.row == -1 || food.col == -1) {
if (snakeLength >= 64) {
win = true;//如果蛇的长度到达64,则判定获胜
return;
}
do {
food.col = random(8);
food.row = random(8);
} while (gameboard[food.row][food.col] > 0);//重新设置食物位置为随机
}
matrix.setLed(0, food.row, food.col,1);//显示下一个食物的位置
}
void scanJoystick() {
int previousDirection = snakeDirection;//备份当前蛇的方向
delay(100);
analogRead(Pin::joystickY) < joystickHome.y - joystickThreshold ? snakeDirection = up : 0;
analogRead(Pin::joystickY) > joystickHome.y + joystickThreshold ? snakeDirection = down : 0;
analogRead(Pin::joystickX) < joystickHome.x - joystickThreshold ? snakeDirection = left : 0;
analogRead(Pin::joystickX) > joystickHome.x + joystickThreshold ? snakeDirection = right : 0;
// 忽略180度的方向变化,不能原地回头
snakeDirection + 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0;
snakeDirection - 2 == previousDirection && previousDirection != 0 ? snakeDirection = previousDirection : 0;
}
// 调整蛇的位置,判断是否吃到食物
void calculateSnake() {
switch (snakeDirection) {
case up:
snake.row--;//蛇向上移动
fixEdge();//如果蛇超出边缘,则会导致蛇出现在屏幕的另一侧
matrix.setLed(0, snake.row, snake.col, 1);
break;
case right:
snake.col++;//蛇向右移动
fixEdge();
matrix.setLed(0, snake.row, snake.col, 1);
break;
case down:
snake.row++;//蛇向下移动
fixEdge();
matrix.setLed(0, snake.row, snake.col, 1);
break;
case left:
snake.col--;//蛇向左移动
fixEdge();
matrix.setLed(0, snake.row, snake.col, 1);
break;
default://没有移动则退出
return;
}
//判断是否失败
if (gameboard[snake.row][snake.col] > 1 && snakeDirection != 0) {
//判断蛇头的位置是否与身体重合,同时规避刚开始的情况
gameOver = true;
return;
}
// 判断是否吃到食物
if (snake.row == food.row && snake.col == food.col) {//如果蛇头和食物的坐标重合
food.row = -1; //重置食物
food.col = -1;
snakeLength++;//蛇长度增加
//增加所有蛇身的部分
for (int row = 0; row < 8; row++) {//横向增加
for (int col = 0; col < 8; col++) {//纵向增加
if (gameboard[row][col] > 0 ) {
gameboard[row][col]++;
}
}
}
}
gameboard[snake.row][snake.col] = snakeLength + 1;
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (gameboard[row][col] > 0 ) {
gameboard[row][col]--;
}
matrix.setLed(0, row, col, gameboard[row][col] == 0 ? 0 : 1);//显示蛇的身体
}
}
}
// 如果蛇超出边缘,则会导致蛇出现在屏幕的另一侧
void fixEdge() {
snake.col < 0 ? snake.col += 8 : 0;
snake.col > 7 ? snake.col -= 8 : 0;
snake.row < 0 ? snake.row += 8 : 0;
snake.row > 7 ? snake.row -= 8 : 0;
}
void GameStates() {
if (gameOver || win) {
unrollSnake();//重置蛇和食物
if (gameOver) showGameOverMessage();//如果输了则滚动播放GG!
else if (win) ;
//重置游戏内容
win = false;
gameOver = false;
snake.row = random(8);//设置蛇为随机位置
snake.col = random(8);
food.row = -1;
food.col = -1;
snakeLength = initialSnakeLength;//设置蛇的长度为初始设定长度
snakeDirection = 0;//没使用摇杆前,蛇不移动
memset(gameboard, 0, sizeof(gameboard[0][0]) * 8 * 8);//清零蛇的身体的数组
matrix.clearDisplay(0);//清屏
}
}
void unrollSnake() {
matrix.setLed(0, food.row, food.col, 0);//使食物消失
delay(800);
for (int i = 1; i <= snakeLength; i++) {
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (gameboard[row][col] == i) {
matrix.setLed(0, row, col, 0);
delay(100); //按顺序使蛇的身体消失
}
}
}
}
}
void initialize() {
pinMode(Pin::joystickVCC, OUTPUT);//设置joystickVCC为输出模式
digitalWrite(Pin::joystickVCC, HIGH);//设置joystickVCC为高电平
pinMode(Pin::joystickGND, OUTPUT);//设置joystickGND为输出模式
digitalWrite(Pin::joystickGND, LOW);//设置joystickGND为高电平
matrix.shutdown(0, false);//status - true 时打开对应点阵的节能模式,需要正常使用时需配置为 false
matrix.setIntensity(0, 6);//设置亮度值(0-15由暗变亮)
matrix.clearDisplay(0);//点阵屏/数码管清屏
snake.row = random(8);
snake.col = random(8);//设置蛇的初始位置为随机
}
//字模部分
const PROGMEM bool snakeMessage[8][56] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
const PROGMEM bool gameOverMessage[8][90] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}
};
//滚动显示字符SNAKE
void showSnakeMessage() {
[&] {
for (int d = 0; d < sizeof(snakeMessage[0]) - 7; d++) {
for (int col = 0; col < 8; col++) {
delay(5);
for (int row = 0; row < 8; row++) {
matrix.setLed(0, row, col, pgm_read_byte(&(snakeMessage[row][col + d])));
}
}
}
}();
matrix.clearDisplay(0);
}
//滚动显示GG!
void showGameOverMessage() {
[&] {
for (int d = 0; d < sizeof(gameOverMessage[0]) - 7; d++) {
for (int col = 0; col < 8; col++) {
delay(5);
for (int row = 0; row < 8; row++) {
matrix.setLed(0, row, col, pgm_read_byte(&(gameOverMessage[row][col + d])));
}
}
}
}();
matrix.clearDisplay(0);
}