아두이노 8x8 LED 매트릭스 뱀 게임 만들기
부품 및 소모품
| 아두이노 프로토 실드 |
1개 |
| 아두이노 우노 (Arduino UNO) |
1개 |
| 아날로그 조이스틱 (일반형) |
1개 |
| 수/암 점퍼 와이어 |
1세트 |
| 8x8 LED 매트릭스 (단색, SparkFun LED Array) |
1개 |
앱 및 온라인 서비스
프로젝트 개요
메카노이드와 이렉터 세트 프레임으로 게임 콘솔을 만들고, Sergey Royz의 코드(꼭 확인해보세요)를 기반으로 속도 조절 등을 추가해 부드럽게 동작하도록 개선했습니다. 프레임 제작 방법은 생략하니 직접 만드시길 권장합니다. 재미있게 즐기세요! 질문이나 다음 프로젝트 추천은 댓글로 남겨주세요. 프리칭이 작동하지 않아 배선은 텍스트로 설명합니다.
회로도
아두이노 우노에 프로토 실드를 장착해 브레드보드 없이 GND와 5V를 추가로 사용합니다.
LED 매트릭스 연결 (MAX7219 기반)
- VCC → 아두이노 5V
- GND → 아두이노 GND
- DIN → 디지털 핀 10
- CS → 디지털 핀 9
- CLK → 디지털 핀 8
조이스틱 연결
- GND → 아두이노 GND
- +5V → 아두이노 5V
- VRX → 아두이노 A0
- VRY → 아두이노 A1
- SW → 디지털 핀 2
코드
Snake 게임 (C/C++)
아래 코드를 복사해 아두이노 IDE에 붙여넣기만 하면 됩니다.
/**
* Snake Game
* 고전 뱀 게임 구현: 뱀이 끊임없이 이동하며 자신과 벽에 부딪히지 않게 조작
* 먹이를 먹으면 길이가 늘고 속도가 약간 빨라짐
* 2016년 6월 23일, Sergey Royz & hunter Buzzell
*/
#include <LedControl.h>
/* 조이스틱 핀 */
#define VRX A0
#define VRY A1
#define SW 2
/* 디스플레이 핀 */
#define CLK 8
#define CS 9
#define DIN 10
#define SIZE 8
#define ADVANCE_DELAY 20
int snake[SIZE*SIZE][2];
int length;
int food[2], v[2];
bool is_game_over = false;
long current_time, prev_advance;
int blink_count;
const short messageSpeed = 5;
LedControl lc = LedControl(DIN, CLK, CS, 1);
void init_game() {
prev_advance = current_time = 0;
blink_count = 3;
int half = SIZE / 2;
length = SIZE / 3;
for (int i = 0; i < length; i++) {
snake[i][0] = half - 1;
snake[i][1] = half + i;
}
food[0] = half + 1;
food[1] = half - 1;
v[0] = 0; v[1] = -1;
}
void render() {
for (int i = 0; i < length; i++) {
lc.setLed(0, snake[i][0], snake[i][1], 1);
}
lc.setLed(0, food[0], food[1], 1);
}
void clearScreen() {
for (int x = 0; x < SIZE; x++)
for (int y = 0; y < SIZE; y++)
lc.setLed(0, x, y, 0);
}
/* 뱀을 한 칸 전진, 게임 오버면 true 반환 */
bool advance() {
int head[2] = {snake[0][0] + v[0], snake[0][1] + v[1]};
// 벽 충돌 체크
if (head[0] < 0 || head[0] >= SIZE || head[1] < 0 || head[1] >= SIZE) {
delay(1000); showGameOverMessage(); return true;
}
// 자기 몸 충돌 체크
for (int i = 0; i < length; i++) {
if (snake[i][0] == head[0] && snake[i][1] == head[1]) {
delay(1000); showGameOverMessage(); return true;
}
}
bool grow = (head[0] == food[0] && head[1] == food[1]);
if (grow) {
length++;
randomSeed(current_time);
food[0] = random(SIZE);
food[1] = random(SIZE);
}
// 몸통 이동
for (int i = length - 1; i >= 0; i--) {
snake[i + 1][0] = snake[i][0];
snake[i + 1][1] = snake[i][1];
}
snake[0][0] += v[0];
snake[0][1] += v[1];
return false;
}
void setup() {
pinMode(SW, INPUT_PULLUP);
pinMode(VRX, INPUT);
pinMode(VRY, INPUT);
attachInterrupt(digitalPinToInterrupt(SW), restart, RISING);
lc.shutdown(0, false);
lc.setIntensity(0, 8);
init_game();
render();
}
void loop() {
if (!is_game_over) {
clearScreen();
render();
if (current_time - prev_advance > ADVANCE_DELAY) {
is_game_over = advance();
prev_advance = current_time;
}
} else {
while (blink_count > 0) {
clearScreen(); delay(300);
render(); delay(300);
blink_count--;
}
}
readControls();
current_time++;
}
void restart() {
init_game();
is_game_over = false;
}
void readControls() {
int dx = map(analogRead(VRX), 0, 906, 2, -2);
int dy = map(analogRead(VRY), 0, 906, -2, 2);
if (dx != 0) dx = dx / abs(dx);
if (dy != 0) dy = dy / abs(dy);
if (dy != 0 && v[0] != 0) { v[0] = 0; v[1] = dy; }
if (dx != 0 && v[1] != 0) { v[0] = dx; v[1] = 0; }
}
/* GAME OVER 메시지 패턴 (PROGMEM 저장) */
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,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,0,1,1,1,1,0,0,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0,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,1,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,0,1,1,1,0,0,1,1,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,0,0,0,0,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,1,1,0,0,0,0,0,0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,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,0,0,0,0,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,1,1,0,0,0,0,0,0,1,1,1,1,1,1,0,0,1,1,0,1,0,1,1,0,0,1,1,1,1,1,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,1,0,0,0,1,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,1,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,0,1,1,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,0,0,0,0,0,0,1,1,1,1,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,1,1,0,0,1,1,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0,0,0,1,1,1,1,0,0,0,1,1,0,0,0,0,0,0,1,1,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,0,1,1,1,1,0,0,0,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,1,1,0,0,1,1,0,0,1,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0}
};
void showGameOverMessage() {
for (int d = 0; d < sizeof(gameOverMessage[0]) - 7; d++) {
for (int col = 0; col < 8; col++) {
delay(messageSpeed);
for (int row = 0; row < 8; row++) {
lc.setLed(0, row, col, pgm_read_byte(&(gameOverMessage[row][col + d])));
}
}
}
}