사이먼게임만들기
결과물
https://mooky1007.github.io/simon_game/
Simon Game
Let's Play Simon! Lv. 1 GAME OVER Collect! Score : 0 Best Score : 0 START
mooky1007.github.io
현재 포스트의 완성본
남은 시간 : 0
현재 점수 : 0
최고 점수 : 0
작업개요
사이먼게임은 좀 생소해서 찾아보니까 4개의 버튼이 있고
순서에 맞게 불이 들어오고 나면 그 순서를 기억했다가 순서에 맞게 누르는 게임인거같다.
처음엔 1개 이후엔 2개, 3개, 4개, ... 이런식으로 점점 기억해야할 숫자가 늘어나고
속도도 조금씩 빨라지면 괜찮을것 같다.
지난번 가위바위보랑은 다르게 이건 내가 지금 몇점이나 획득했는지도 표기되고,
내 최고 점수까지 같이 들어가면 좋을것 같다.
작업진행
JS 부터 작업해서 콘솔에서 테스트를 해보고 이후 HTML 작성 후 JS로 연결하자.
우선 클래스 선언부터 해보자.
class SimonGame {
constructor() {
this.level = 0; // 게임의 단계, 성공 시 마다 1씩 증가한다.
this.score = 0; // 현재 플레이어의 점수
this.bestScore = 0; // 플레이어의 최고점수 게임이 끝날때 비교 후 갱신
this.answerArr = []; // 게임 문제의 정답이 들어가는 배열
this.init(); // 초기화 함수 실행
}
init() { // 초기화 함수
this.level = 0;
this.score = 0;
}
start() { // 게임 시작 함수
this.createLevel(); // 레벨에 맞는 문제를 생성
this.level ++; // 테스트용 레벨 상승
}
createLevel(){
this.answerArr = []; // 기존 문제 배열 초기화
for (let i = 0; i < this.level + 1; i++) { // 레벨보다 1 많은 문제를 생성
this.answerArr.push(Math.trunc(Math.random() * 4)); // 0 ~ 3내의 4가지 숫자를 랜덤으로 생성
}
}
}
레벨 상승에 따른 문제를 생성하는 부분까지 작업을 하였다.
현재 객체에서 start() 매서드가 시행 될때마다 문제를 생성하고, 레벨이 오르게 하였다.
start를 할때마다 레벨이 오르고, 해당 레벨에 맞는 문제가 출제된다.
이제 플레이어가 문제를 보고, 시간내에 맞는 답을 제출해야 하는 매서드를 만들어보자.
class SimonGame {
constructor() {
this.level = 0; // 게임의 단계, 성공 시 마다 1씩 증가한다.
this.score = 0; // 현재 플레이어의 점수
this.bestScore = 0; // 플레이어의 최고점수 게임이 끝날때 비교 후 갱신
this.answerArr = []; // 게임 문제의 정답이 들어가는 배열
this.time = 3; // 제한시간
this.gameStatus = false; // 게임의 상태를 나타내는 변수
this.init(); // 초기화 함수 실행
}
init() { // 초기화 함수
this.level = 0;
this.score = 0;
this.time = 3;
}
checkGameStatus() { // 게임 상태 체크 함수
if (this.gameStatus) { // 게임이 진행중이면
return true;
} else { // 게임이 진행중이 아니면
return false;
}
}
start() { // 게임 시작 함수
if(this.checkGameStatus()) return; // 게임이 진행중이면 실행하지 않음
this.gameStatus = true; // 게임 상태를 진행중으로 변경
this.createLevel(); // 레벨에 맞는 문제를 생성
this.startTimer(); // 제한시간 시작
console.log(this.time);
console.log(`현재레벨: ${this.level} / 문제: ${this.answerArr}`); // 테스트용 정답 배열 출력
}
createLevel(){
this.answerArr = []; // 기존 문제 배열 초기화
for (let i = 0; i < this.level + 1; i++) { // 레벨보다 1 많은 문제를 생성
this.answerArr.push(Math.trunc(Math.random() * 4)); // 0 ~ 3내의 4가지 숫자를 랜덤으로 생성
}
}
startTimer(){
if(!this.checkGameStatus()) return; // 게임이 진행중이 아니면 실행하지 않음
this.timer = setInterval(()=>{
this.time -= 1;
console.log(this.time);
if(this.time === 0){
this.gameOver();
}
}, 1000);
}
submitAnswer(arr){ // 정답 제출 함수
if(!this.checkGameStatus()) return; // 게임이 진행중이 아니면 실행하지 않음
clearTimeout(this.timer); // 타이머 제거
if(arr.length !== this.answerArr.length){ // 정답 배열의 길이가 다르면 오답
this.gameOver();
return false;
}
for(let i = 0; i < arr.length; i++){ // 정답 배열의 길이만큼 반복
if(arr[i] !== this.answerArr[i]){ // 정답 배열의 값이 다르면 오답
this.gameOver();
return false;
}
}
this.level += 1; // 정답이면 레벨 증가
this.time = 3; // 제한시간 초기화
this.score += (this.level + (this.time * 10)); // 정답이면 레벨 + 남은 시간 x 10 만큼 점수 증가
this.gameStatus = false; // 게임 상태를 진행중으로 변경
console.log('정답');
this.start(); // 게임 시작
}
gameOver(){ // 게임오버 함수
this.gameStatus = false; // 게임 상태를 종료로 변경
clearTimeout(this.timer); // 타이머 초기화
this.init(); // 초기화
if(this.score > this.bestScore){ // 최고점수 갱신
this.bestScore = this.score;
console.log(`최고점수 갱신! ${this.bestScore}`);
}
console.log('게임오버');
}
}
이것 저것 조금 많이 추가되었다.
게임의 상태를 지정해주는 변수를 만들어서, 진행중일때는 답변제출이나, 타이머가 동작을 안하게 하였고,
연속해서 게임을 시작하는것도 방지하였다.
start() 매서드가 동작하면, 게임의 상태를 진행중(true)으로 바꾸고,
레벨에 맞는 문제를 생성하고, 제한시간을 시작한다.
제한시간이 초과되면 게임오버가 된다.
제한시간 내에 submitAnswer 함수가 실행되면
우선 배열의 길이를 체크해서 오답처리를 하는 부분을 만들어 놨는데,
실제론 필요없을것 같다. ( 화면상에서 순서의 마지막 버튼이 눌렸을때 실행될꺼기 때문에 )
삭제하도록하고.. 배열의 길이만큼 반복하면서 정답 체크를 한 뒤,
오답이면 게임오버, 정답이면 점수와 레벨, 타이머등을 초기화 해준다.
제한시간 초기화는 start 매서드 상단으로 옮겨주는게 나을듯.
아. 게임 오버시에 init을 맨뒤로 빼줘야한다.
안그러면 score가 먼저 초기화 되버려서 최고점수가 갱신이 안될듯.
이렇게 까지 만들고 콘솔에서 플레이를 해보면..
괴랄한 타자 속도 빠르기 게임이 되버린다.
이제 슬슬 HTML 작업을 준비해야 하는데 그전에 해야할게있다.
문제를 레벨별로 시간에 맞춰서 띡, 띡, 띡, 띡띡띡띡띡 이런식으로
속도에 맞게 제출해주고 기억을 하게 해야한다.
지금은 콘솔이니까 답이 보이니까 어쩔수 없지만..
애니메이션이 끝날때마다 작동하는식으로 동기적으로 작업을 해줘야 한다.
콘솔창에서 문제를 레벨 숫자에 맞게 제출해주고, 제출이 끝나면 시작 타이머를 돌려주자.
class SimonGame {
constructor() {
this.level = 0; // 게임의 단계, 성공 시 마다 1씩 증가한다.
this.score = 0; // 현재 플레이어의 점수
this.bestScore = 0; // 플레이어의 최고점수 게임이 끝날때 비교 후 갱신
this.answerArr = []; // 게임 문제의 정답이 들어가는 배열
this.time = 3; // 제한시간
this.speed = 1; // 게임 속도
this.gameStatus = false; // 게임의 상태를 나타내는 변수
this.init(); // 초기화 함수 실행
}
init() { // 초기화 함수
this.level = 0;
this.score = 0;
this.time = 3;
}
checkGameStatus() { // 게임 상태 체크 함수
if (this.gameStatus) { // 게임이 진행중이면
return true;
} else { // 게임이 진행중이 아니면
return false;
}
}
async start() { // 게임 시작 함수
if(this.checkGameStatus()) return; // 게임이 진행중이면 실행하지 않음
this.gameStatus = true; // 게임 상태를 진행중으로 변경
this.time = 3; // 제한시간 초기화
this.createLevel(); // 레벨에 맞는 문제를 생성
await this.questionStep();
this.startTimer(); // 제한시간 시작
}
questionStep() { // 문제를 출력하는 함수
console.log(`현재레벨: ${this.level} / 문제: ${this.answerArr}`); // 테스트용 정답 배열 출력
return new Promise((resolve, reject) => {
let i = 0;
const interval = setInterval(() => {
console.log(`문제 : ${this.answerArr[i]}`);
i++;
if (i >= this.answerArr.length) {
clearInterval(interval);
resolve();
}
}, 1000 / this.speed);
});
}
createLevel(){
this.answerArr = []; // 기존 문제 배열 초기화
for (let i = 0; i < this.level + 1; i++) { // 레벨보다 1 많은 문제를 생성
this.answerArr.push(Math.trunc(Math.random() * 4)); // 0 ~ 3내의 4가지 숫자를 랜덤으로 생성
}
}
startTimer(){
if(!this.checkGameStatus()) return; // 게임이 진행중이 아니면 실행하지 않음
this.timer = setInterval(()=>{
this.time -= 1;
if(this.time === 0){
this.gameOver();
}
}, 1000);
}
submitAnswer(arr){ // 정답 제출 함수
if(!this.checkGameStatus()) return; // 게임이 진행중이 아니면 실행하지 않음
clearTimeout(this.timer); // 타이머 제거
if(arr.length !== this.answerArr.length){ // 정답 배열의 길이가 다르면 오답
this.gameOver();
return false;
}
for(let i = 0; i < arr.length; i++){ // 정답 배열의 길이만큼 반복
if(arr[i] !== this.answerArr[i]){ // 정답 배열의 값이 다르면 오답
this.gameOver();
return false;
}
}
this.level += 1; // 정답이면 레벨 증가
this.score += (this.level + (this.time * 10)); // 정답이면 레벨 + 남은 시간 x 10 만큼 점수 증가
this.gameStatus = false; // 게임 상태를 진행중으로 변경
this.speed += 0.1; // 게임 속도 증가
console.log('정답');
this.start(); // 게임 시작
}
gameOver(){ // 게임오버 함수
this.gameStatus = false; // 게임 상태를 종료로 변경
clearTimeout(this.timer); // 타이머 초기화
if(this.score > this.bestScore){ // 최고점수 갱신
this.bestScore = this.score;
console.log(`최고점수 갱신! ${this.bestScore}`);
}
this.init(); // 초기화
console.log('게임오버');
}
}
남은 시간초를 출력하는 부분을 제거하고, 문제를 출제하는 부분을 로그로 찍어봤다.
이제 이거에 맞게 화면에 표시만 해주면 될꺼같다.
아 그전에 playersArr 를 변수로 만들어서 유저의 답변을 저장할 공간을 만들어 둬야한다.
그부분까지 작업하고 html 작업을 진행해보자.
class SimonGame {
constructor() {
...
this.playerArr = []; // 플레이어가 입력한 정답이 들어가는 배열
...
}
...
playerSubmit(num) { // 플레이어의 버튼 입력 동작
this.playerArr.push(num); // 플레이어 배열에 정답 넣기
if(this.playerArr.length === this.answerArr.length){
this.submitAnswer(this.playerArr);
// 플레이어 정답 배열과 문제의 길이가 같으면 제출
}
}
...
}
작업이 진행 된 곳만 표시해봤다.
주의할점은 추가해줬으니까, 정답 혹은 게임오버 등등의 함수에서
playerArr 배열도 중간중간 초기화를 해줘야한다.
이러고 나서 다시 콘솔창에서 플레이를 해보면..
그래도 조금은 하기가 편해졌다.
간단하게 html을 작성하고, js랑 연결시켜보자.
이번에는 따로 render 함수를 만들지 않고 만들어진 함수 내에 추가 예정
전체코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<style>
.game_container {
display: flex;
flex-direction: column;
width: 300px;
margin: 0 auto;
border: 1px solid #000;
padding: 20px;
justify-content: center;
align-items: center;
}
.game_container p {
margin: 0 !important;
padding: 0 !important;
}
.game_buttons {
position: relative;
}
.game_buttons button {
width: 50px;
height: 50px;
border-radius: 50%;
border: 1px solid #000;
margin: 5px;
background-color: #fff;
cursor: pointer;
margin-bottom: 20px;
}
.game_buttons button[data-number="0"].action {
background-color: red;
}
.start_btn {
margin-top: 20px;
}
.game_buttons button:disabled {
background-color: #fff;
border-color: #ccc !important;
cursor: not-allowed;
}
.game_buttons button[data-number="1"].action {
background-color: blue;
}
.game_buttons button[data-number="2"].action {
background-color: green;
}
.game_buttons button[data-number="3"].action {
background-color: yellow;
}
.game_over {
display: none;
font-size: 30px;
font-weight: bold;
color: red;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.game_over.active {
display: block;
}
</style>
<div class="game_container">
<div class="game_buttons">
<span class="game_over">게임오버</span>
<button disabled data-number="0">0</button>
<button disabled data-number="1">1</button>
<button disabled data-number="2">2</button>
<button disabled data-number="3">3</button>
</div>
<p class="timer">남은 시간 : <span>0</span></p>
<p class="score">현재 점수 : <span>0</span></p>
<p class="best_score">최고 점수 : <span>0</span></p>
<button class="start_btn">Start</button>
</div>
<script>
class SimonGame {
constructor() {
this.level = 0; // 게임의 단계, 성공 시 마다 1씩 증가한다.
this.score = 0; // 현재 플레이어의 점수
this.bestScore = 0; // 플레이어의 최고점수 게임이 끝날때 비교 후 갱신
this.answerArr = []; // 게임 문제의 정답이 들어가는 배열
this.time = 3; // 제한시간
this.speed = 1; // 게임 속도
this.gameStatus = false; // 게임의 상태를 나타내는 변수
this.playerArr = []; // 플레이어가 입력한 정답이 들어가는 배열
this.el = {
buttons: document.querySelectorAll('.game_buttons button'),
timer: document.querySelector('.timer span'),
startBtn: document.querySelector('.start_btn'),
score: document.querySelector('.score span'),
bestScore: document.querySelector('.best_score span'),
gameOver: document.querySelector('.game_over')
};
this.init(); // 초기화 함수 실행
}
init() { // 초기화 함수
this.level = 0;
this.score = 0;
this.time = 3;
this.speed = 1;
this.gameStatus = false;
this.playerAnswer = false;
this.playerArr = [];
this.answerArr = [];
this.el.timer.textContent = this.time;
this.el.score.textContent = this.score;
this.el.bestScore.textContent = this.bestScore;
this.el.startBtn.addEventListener('click', () => { this.start(); });
this.el.buttons.forEach((btn) => { // 버튼 비활성화
btn.disabled = true;
});
this.el.buttons.forEach((btn) => {
btn.addEventListener('click', (e) => {
this.playerSubmit(Number(e.target.dataset.number));
});
});
}
checkGameStatus() { // 게임 상태 체크 함수
if (this.gameStatus) { // 게임이 진행중이면
return true;
} else { // 게임이 진행중이 아니면
return false;
}
}
async start() { // 게임 시작 함수
this.playerAnswer = false;
this.el.gameOver.classList.remove('active');
if (this.checkGameStatus()) return; // 게임이 진행중이면 실행하지 않음
this.gameStatus = true; // 게임 상태를 진행중으로 변경
this.time = 3 + Math.trunc(this.level / 3); // 제한시간 초기화
this.el.timer.textContent = this.time; // 제한시간 출력
this.createLevel(); // 레벨에 맞는 문제를 생성
this.el.buttons.forEach((btn) => { // 버튼 비활성화
btn.disabled = true;
});
await this.questionStep();
this.playerAnswer = true;
setTimeout(() => this.startTimer(), 500) // 제한시간 시작
}
playerSubmit(num) {
if (!this.playerAnswer) return;
this.playerArr.push(num);
if (this.playerArr.length === this.answerArr.length) {
this.submitAnswer(this.playerArr);
}
}
questionStep() { // 문제를 출력하는 함수
return new Promise((resolve, reject) => {
let i = 0;
const interval = setInterval(async () => {
this.el.buttons[this.answerArr[i]].classList.add('action');
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 500 / this.speed);
});
this.el.buttons[this.answerArr[i]].classList.remove('action');
i++;
if (i >= this.answerArr.length) {
clearInterval(interval);
resolve();
}
}, 1000 / this.speed);
});
}
createLevel() {
this.answerArr = []; // 기존 문제 배열 초기화
this.playerArr = []; // 기존 플레이어 배열 초기화
for (let i = 0; i < this.level + 1; i++) { // 레벨보다 1 많은 문제를 생성
this.answerArr.push(Math.trunc(Math.random() * 4)); // 0 ~ 3내의 4가지 숫자를 랜덤으로 생성
}
}
startTimer() {
if (!this.checkGameStatus()) return; // 게임이 진행중이 아니면 실행하지 않음
this.el.buttons.forEach((btn) => { // 버튼 비활성화
btn.disabled = false;
});
this.timer = setInterval(() => {
this.time -= 1;
this.el.timer.textContent = this.time; // 제한시간 출력
if (this.time === 0) {
this.gameOver();
}
}, 1000);
}
submitAnswer(arr) { // 정답 제출 함수
if (!this.checkGameStatus()) return; // 게임이 진행중이 아니면 실행하지 않음
clearTimeout(this.timer); // 타이머 제거
for (let i = 0; i < arr.length; i++) { // 정답 배열의 길이만큼 반복
if (arr[i] !== this.answerArr[i]) { // 정답 배열의 값이 다르면 오답
this.gameOver();
return false;
}
}
this.level += 1; // 정답이면 레벨 증가
this.score += (this.level + (this.time * 10)); // 정답이면 레벨 + 남은 시간 x 10 만큼 점수 증가
this.el.score.textContent = this.score; // 점수 출력
this.gameStatus = false; // 게임 상태를 진행중으로 변경
this.speed += 0.1; // 게임 속도 증가
this.start(); // 게임 시작
}
gameOver() { // 게임오버 함수
this.gameStatus = false; // 게임 상태를 종료로 변경
clearTimeout(this.timer); // 타이머 초기화
if (this.score > this.bestScore) { // 최고점수 갱신
this.bestScore = this.score;;
this.el.bestScore.textContent = this.bestScore;
}
this.init(); // 초기화
this.el.gameOver.classList.add('active');
}
}
const game = new SimonGame();
</script>
</body>
</html>