아두이노 메모리 게임 (사이먼 클론) 만들기 – 단계별 튜토리얼
부품 및 소모품
| 부품 |
수량 |
비고 |
| 아두이노 우노 (Arduino UNO) |
1 |
|
| 330옴 저항 (SparkFun) |
4개 |
4개만 필요 |
| 10k옴 저항 (SparkFun) |
4개 |
4개만 필요 |
| 여러 색상 LED (SparkFun) |
4개 |
서로 다른 색상 4개 필요 |
| 미니 스피커 (SparkFun) |
1 |
소리 필요 없으면 생략 가능 |
| 브레드보드 (풀 사이즈, SparkFun) |
1 |
|
| 점퍼 와이어 (범용) |
1세트 |
|
앱 및 온라인 서비스
프로젝트 개요
"사이먼(Simon)" 게임 기억하시나요? 70년대생인 제 또래 많은 분들이 이 게임에 시간을 많이 쏟았을 겁니다. 게임은 단순했습니다. 서로 다른 색상의 버튼 4개가 순서대로 불이 켜지면 그 순서를 따라 누르는 방식입니다. 맞출 때마다 마지막에 색상 하나가 추가돼 난이도가 올라갑니다.
어릴 적 크리스마스 선물로 받았지만 이사를 거듭하며 상자 속에 묻혀버렸죠...
"어른"이 되어 아두이노의 즐거움을 알게 되었고, 잃어버린 추억의 친구를 되살릴 기회가 생겼습니다.
이 튜토리얼에서는 아두이노 우노, 몇 개의 LED와 저항, 비교적 간단한 회로로 비슷한 게임을 만드는 방법을 보여드립니다.
프로그램 자체는 초보자에게 약간 복잡할 수 있어 코드에 상세히 주석을 달았습니다.
가장 간단한 실행 방법은 다음과 같습니다.
- 회로도대로 정확히 배선하세요. 전선 색상은 상관없으니 검은 선이 부족해도 괜찮습니다. :)
- 소스 코드를 아두이노 에디터에 붙여넣고 보드에 업로드하세요.
- 게임을 즐기세요!
게임은 약 3초 후 모든 LED가 5번 느리게 점멸하며 시작합니다.

LED 중 하나 이상이 점멸하지 않으면 배선을 확인하세요. 제 보드에선 작은 버튼들이 제대로 안 끼워져 튀어나오곤 합니다.
배선이 맞는데도 안 되면, 작동하는 LED와 안 되는 LED를 바꿔 껴서 배선 문제인지 LED 불량인지 확인해 보세요.
5번 점멸 완료 후 LED 하나가 켜집니다. 어느 건지는 비밀입니다 :) 해당 LED의 버튼을 누르세요. 그 LED가 다시 켜지고 새로운 LED가 추가됩니다. 순서대로 올바른 버튼을 누르면 됩니다...

곧 모든 LED가 빠르게 점멸합니다. 그건 패배라는 뜻이죠! :D
마지막으로 주어졌던 시퀀스가 재생돼 점수를 셀 수 있고, 다시 5번 느린 점멸과 함께 새 게임이 시작됩니다.
버튼 입력 제한 시간은 2초입니다.
자, 즐겨보세요!
더 어렵게 만들고 싶으신가요? 코드 최상단 값을 바꿔보세요:
#define PLAYER_WAIT_TIME 2000
LED를 더 추가하고 싶다면? 회로의 이 부분을 복제하세요:

여기 주황색 선은 아두이노의 빈 핀에 연결하세요 (0, 1번 핀은 피하세요).
코드에서 다음 줄을 수정하세요:
byte noPins = 4
byte pins[] = {2, 13, 10, 8};
핀 6에 LED를 추가했다면:
byte noPins = 5
byte pins[] = {2, 13, 10, 8, 6};
아두이노 코드 작성 팁:
void setup() {
delay(3000);
Serial.begin(9600);
Reset();
}
setup() 첫 줄에 짧은 딜레이를 두세요. 아두이노가 무한 루프에 빠지거나 락업되기 쉬운데, 이 딜레이 덕분에 리셋 후 빈 스케치 업로드할 시간을 벌 수 있습니다.
이 아이디어를 발전시켜 휴대용으로도 만들었습니다...
비슷하지만 훨씬 더 진보한 게임 아이디어를 보시려면 Peter Kent의 레고 미니 메모리 게임을 확인하세요. 레고 자동차와 인터페이스한 모바일 게임으로 정말 멋집니다!
코드
메모리 게임 코드
#define PLAYER_WAIT_TIME 2000 // 버튼 간 허용 시간 - 2초
byte sequence[100]; // 점등 시퀀스 저장
byte curLen = 0; // 현재 시퀀스 길이
byte inputCount = 0; // 플레이어가 올바른 버튼을 누른 횟수 (턴당)
byte lastInput = 0; // 플레이어 마지막 입력
byte expRd = 0; // 플레이어가 켜야 할 LED
bool btnDwn = false; // 버튼 눌림 상태 체크
bool wait = false; // 프로그램의 사용자 입력 대기 여부
bool resetFlag = false; // 플레이어 패배 시 표시
byte soundPin = 5; // 스피커 출력 핀
byte noPins = 4; // 버튼/LED 개수 (작업 중엔 2개만 사용)
// LED/버튼/저항 조합 추가로 난이도 올릴 수 있음
byte pins[] = {2, 13, 10, 8}; // 버튼 입력 및 LED 출력 핀 - 다른 핀 사용 시 변경
// 요소 개수는 noPins와 일치해야 함
long inputTime = 0; // 사용자 입력 간격 타이머 변수
void setup() {
delay(3000); // 아두이노 연결 후 숨 돌릴 시간 - 원치 않으면 제거
Serial.begin(9600); // 시리얼 모니터 시작 - 제거 시 아래 Serial 참조도 모두 삭제
Reset();
}
///
/// 'dir' 값에 따라 모든 핀을 INPUT 또는 OUTPUT으로 설정
///
void setPinDirection(byte dir){
for(byte i = 0; i < noPins; i++){
pinMode(pins[i], dir);
}
}
// 모든 LED 핀에 같은 값 출력
void writeAllPins(byte val){
for(byte i = 0; i < noPins; i++){
digitalWrite(pins[i], val);
}
}
// (꽤 성가신) 비프음 내기
void beep(byte freq){
analogWrite(soundPin, 2);
delay(freq);
analogWrite(soundPin, 0);
delay(freq);
}
///
/// 모든 LED 함께 점멸
/// freq: 점멸 속도 - 작을수록 빠름, 클수록 느림
///
void flash(short freq){
setPinDirection(OUTPUT);
for(int i = 0; i < 5; i++){
writeAllPins(HIGH);
beep(50);
delay(freq);
writeAllPins(LOW);
delay(freq);
}
}
///
/// 모든 게임 변수 기본값으로 리셋
///
void Reset(){
flash(500);
curLen = 0;
inputCount = 0;
lastInput = 0;
expRd = 0;
btnDwn = false;
wait = false;
resetFlag = false;
}
///
/// 사용자 패배 처리
///
void Lose(){
flash(50);
}
///
/// 아두이노가 사용자에게 외울 시퀀스 보여주기
/// 패배 후 마지막 시퀀스 보여줄 때도 호출
///
void playSequence(){
for(int i = 0; i < curLen; i++){
Serial.print("Seq: ");
Serial.print(i);
Serial.print("Pin: ");
Serial.println(sequence[i]);
digitalWrite(sequence[i], HIGH);
delay(500);
digitalWrite(sequence[i], LOW);
delay(250);
}
}
///
/// 패배 시 발생하는 이벤트들
///
void DoLoseProcess(){
Lose(); // 모든 LED 빠른 점멸 (Lose 함수 참조)
delay(1000);
playSequence(); // 사용자에게 마지막 시퀀스 보여주기 - 최고 점수 기억용, 제 기록은 22예요 :)
delay(1000);
Reset(); // 새 게임 위해 모든 것 리셋
}
///
/// 핵심 로직
///
void loop() {
if(!wait){
//****************//
// 아두이노 턴
//****************//
setPinDirection(OUTPUT); // LED 사용
randomSeed(analogRead(A0)); // https://www.arduino.cc/en/Reference/RandomSeed
sequence[curLen] = pins[random(0,noPins)]; // 시퀀스 다음 위치에 랜덤 값 저장
curLen++; // 시퀀스 길이 증가
playSequence(); // 플레이어에게 시퀀스 보여주기
beep(50); // 플레이어 인지용 비프음
wait = true; // 이제 플레이어 턴
inputTime = millis(); // 플레이어 반응 시간 측정용 시간 저장
}else{
//***************//
// 플레이어 턴
//***************//
setPinDirection(INPUT); // 버튼 사용
if(millis() - inputTime > PLAYER_WAIT_TIME){
// 허용 시간 초과 시
DoLoseProcess(); // 게임 오버
return;
}
if(!btnDwn){
expRd = sequence[inputCount]; // 플레이어 기대 입력값
Serial.print("Expected: ");
Serial.println(expRd);
for(int i = 0; i < noPins; i++){
if(pins[i]==expRd) continue; // 정답 핀 무시
if(digitalRead(pins[i]) == HIGH){ // 버튼 눌림?
lastInput = pins[i];
resetFlag = true; // 패배 플래그
btnDwn = true; // 중복 처리 방지
Serial.print("Read: ");
Serial.println(lastInput);
}
}
}
if(digitalRead(expRd) == 1 && !btnDwn) { // 정답 버튼 눌림
inputTime = millis();
inputCount++; // 올바른 버튼 누름 횟수 증가
btnDwn = true;
Serial.print("Read: ");
Serial.println(lastInput);
}else{
if(btnDwn && digitalRead(lastInput) == LOW){ // 버튼 떼짐 확인
btnDwn = false;
delay(20);
if(resetFlag){ // 위에서 true 설정됐으면 패배
DoLoseProcess();
} else{
if(inputCount == curLen){ // 시퀀스 완료?
wait = false; // 다음 턴은 아두이노
inputCount = 0;
delay(1500);
}
}
}
}
}
}
회로도
simonsays_siKds3Jmk2.fzz
