아두이노 틱택토 게임 - MAX7219 LED 매트릭스와 골판지 케이스
부품 및 소모품
| 부품 |
수량 |
비고 |
| 아두이노 우노 (Arduino UNO) |
1 |
|
| MAX7219 LED 도트 매트릭스 (8x8) |
1 |
|
| 멤브레인 스위치 키패드 (4x3) |
1 |
|
필요 도구
앱 및 온라인 서비스
프로젝트 소개
안녕하세요, Hackster 커뮤니티 여러분. 저는 Simone Adobs이며, 이것은 Hackster에 올린 세 번째 프로젝트입니다. 골판지, 종이, 글루건으로 아두이노와 부품을 담는 구조물을 만든 첫 프로젝트이기도 합니다.
정면 모습
측면 모습
후면 모습
몇 시간을 들여 만든 프로젝트인데 마음에 드셨으면 좋겠습니다. 이제 프로젝트 설명을 시작하겠습니다.
트리스(틱택토) 작동 원리
원리는 매우 간단합니다. 키패드에서 숫자를 누르면 아두이노가 해당 칸을 켭니다.
- 플레이어 전환이 가능한가요?
칸을 켜는 순간 아두이노가 자동으로 다음 플레이어 차례로 전환합니다.
- 아두이노가 승리를 인식하나요?
플레이어가 이기면 LED 도트 매트릭스에 표시됩니다:
- "1" - 플레이어 1 승리
- "2" - 플레이어 2 승리
- 게임 종료 후 버튼으로 재시작 가능한가요?
키패드에서 "0" 버튼을 누르면 아두이노가 스스로 재시작하며 게임이 리셋됩니다.
플레이어 구분 방법
LED 도트 매트릭스는 적색 단색이지만, 각 플레이어마다 2x2 LED로 구성된 고유 패턴으로 구분합니다:
- 플레이어 1: \"\\\" 패턴 (왼쪽 위 → 오른쪽 아래 대각선)
- 플레이어 2: \"/\" 패턴 (오른쪽 위 → 왼쪽 아래 대각선)
질문, 제안, 비판, 코드 설명 등 무엇이든 댓글로 남겨주세요. 프로젝트가 마음에 드셨다면 좋아요를 눌러주시고 다른 프로젝트도 구경해보세요!
다른 구조물을 만들거나 제 구조물을 개조해 사진을 보내주셔도 좋습니다.
좋은 하루 되세요!
이탈리아에서 Simone <3
코드
Tech-Tris (C/C++)
#include
#include
#include
#include
#define Reset_AVR() wdt_enable(WDTO_30MS); while(1) {}
LedControl lc = LedControl(12, 10, 11, 1); // DIN, CLK, CS, 장치 수
bool player1 = true;
bool q1=false, q2=false, q3=false, q4=false, q5=false, q6=false, q7=false, q8=false, q9=false;
int row, col, rowPlays, colPlays, result, player, winner;
char hexaKeys[4][3] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
int plays[3][3] = {{0,0,0}, {0,0,0}, {0,0,0}};
byte rowPins[4] = {8, 7, 6, 5}; // 키패드 행 핀
byte colPins[3] = {4, 3, 2}; // 키패드 열 핀
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, 4, 3);
void setup() {
lc.shutdown(0, false);
lc.setIntensity(0, 0); // 밝기 최저 (0~15)
lc.clearDisplay(0);
}
void loop() {
int customKey = customKeypad.getKey();
painting(customKey);
win(result);
if (winner == 1) {
lc.clearDisplay(0);
while (winner == 1) {
char customKey = customKeypad.getKey();
if (customKey == '0') Reset_AVR();
// 플레이어 1 승리 표시: "1" 패턴 (\)
lc.setLed(0,1,4,true); lc.setLed(0,2,5,true); lc.setLed(0,3,6,true); lc.setLed(0,4,7,true);
lc.setLed(0,4,6,true); lc.setLed(0,4,5,true); lc.setLed(0,4,4,true); lc.setLed(0,4,3,true);
lc.setLed(0,4,2,true); lc.setLed(0,4,1,true); lc.setLed(0,4,0,true); lc.setLed(0,5,0,true);
lc.setLed(0,6,0,true); lc.setLed(0,3,0,true); lc.setLed(0,2,0,true);
}
} else if (winner == 2) {
lc.clearDisplay(0);
while (winner == 2) {
char customKey = customKeypad.getKey();
if (customKey == '0') Reset_AVR();
// 플레이어 2 승리 표시: "2" 패턴 (/)
lc.setLed(0,2,0,true); lc.setLed(0,3,0,true); lc.setLed(0,4,0,true); lc.setLed(0,5,0,true);
lc.setLed(0,2,1,true); lc.setLed(0,2,2,true); lc.setLed(0,2,3,true); lc.setLed(0,2,4,true);
lc.setLed(0,3,4,true); lc.setLed(0,4,4,true); lc.setLed(0,5,4,true); lc.setLed(0,5,5,true);
lc.setLed(0,5,6,true); lc.setLed(0,5,7,true); lc.setLed(0,4,7,true); lc.setLed(0,3,7,true);
lc.setLed(0,2,7,true);
}
}
}
int round_player() {
if (player1) { player1 = false; result = 1; }
else { player1 = true; result = 2; }
return result;
}
void win(int player) {
// 가로, 세로, 대각선 승리 조건 확인
if (plays[0][0]==player && plays[0][1]==player && plays[0][2]==player) winner=player;
else if (plays[1][0]==player && plays[1][1]==player && plays[1][2]==player) winner=player;
else if (plays[2][0]==player && plays[2][1]==player && plays[2][2]==player) winner=player;
else if (plays[0][0]==player && plays[1][0]==player && plays[2][0]==player) winner=player;
else if (plays[0][1]==player && plays[1][1]==player && plays[2][1]==player) winner=player;
else if (plays[0][2]==player && plays[1][2]==player && plays[2][2]==player) winner=player;
else if (plays[0][0]==player && plays[1][1]==player && plays[2][2]==player) winner=player;
else if (plays[0][2]==player && plays[1][1]==player && plays[2][0]==player) winner=player;
}
void switch_on_led(int col, int row, int colPlays, int rowPlays) {
if (player1) {
// 플레이어 1: \ 패턴 (2x2)
lc.setLed(0, col, row, true);
lc.setLed(0, col+1, row-1, true);
} else {
// 플레이어 2: / 패턴 (2x2)
lc.setLed(0, col, row-1, true);
lc.setLed(0, col+1, row, true);
}
plays[rowPlays][colPlays] = round_player();
}
void painting(int bottone) {
switch (bottone) {
case 49: // '1' 키
if (!q1) { switch_on_led(0, 7, 0, 0); q1 = true; }
break;
case 50: // '2' 키
if (!q2) { switch_on_led(3, 7, 0, 1); q2 = true; }
break;
case 51: // '3' 키
if (!q3) { switch_on_led(6, 7, 0, 2); q3 = true; }
break;
case 52: // '4' 키
if (!q4) { switch_on_led(0, 4, 1, 0); q4 = true; }
break;
case 53: // '5' 키
if (!q5) { switch_on_led(3, 4, 1, 1); q5 = true; }
break;
case 54: // '6' 키
if (!q6) { switch_on_led(6, 4, 1, 2); q6 = true; }
break;
case 55: // '7' 키
if (!q7) { switch_on_led(0, 1, 2, 0); q7 = true; }
break;
case 56: // '8' 키
if (!q8) { switch_on_led(3, 1, 2, 1); q8 = true; }
break;
case 57: // '9' 키
if (!q9) { switch_on_led(6, 1, 2, 2); q9 = true; }
break;
case 48: // '0' 키 - 리셋
Reset_AVR();
break;
}
}
회로도
Fritzing 회로도 (tris_1Nd6QnEjNE.fzz)
핵심 포인트
- MAX7219 라이브러리로 8x8 매트릭스 손쉬운 제어
- Keypad 라이브러리로 4x3 멤브레인 키패드 입력 처리
- 워치독 타이머(WDT) 활용한 소프트웨어 리셋 (Reset_AVR 매크로)
- 3x3 게임 보드를 8x8 매트릭스에 매핑하여 2x2 패턴으로 플레이어 구분
- 골판지+글루건으로 만든 저비용 인클로저