유튜브 : https://www.youtube.com/watch?v=QTcIXok9wNY
미리보기(데모) : https://ohikmyeong.github.io/wds-snake-game/answer-with/
HTML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<!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>(Answer With)Snake Game</title>
<link rel="stylesheet" type="text/css" href="./style.css"/>
<script src="js/main.js" type="module"></script>
</head>
<body>
<div id="score">0</div>
<section id="game-board"></section>
<div id="modal">GAME OVER..</div>
</body>
</html>
|
cs |
CSS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
@charset "utf-8";
:root{
--bg:rgb(43, 43, 56);
--game-board:rgb(193, 196, 211);
--snake:linear-gradient(to bottom, rgb(56, 244, 184),rgb(0, 164, 112));
--snake-head:conic-gradient(rgb(56, 244, 184), rgb(0, 96, 29));
--food:linear-gradient(to bottom, rgb(255, 243, 16),rgb(255, 208, 0));
--game-over:linear-gradient(to bottom, rgb(244, 56, 56),rgb(91, 0, 0));
--game-over-head:conic-gradient(rgb(255,255,255), rgb(255, 89, 0), rgb(128, 0, 0));
--border:.4vmin solid black;
--grid-size:21;
}
*{margin:0;padding:0;box-sizing:border-box;}
html,body{
display:flex; flex-flow:column nowrap;
justify-content:center; align-items:center;
gap:2rem;
width:100%;height:100vh;
background:var(--bg);}
/* [SCORE] */
#score{
margin-top:1em;
font-size:3vmax;font-weight:bold;color:#fff;}
/* [GAME BOARD] */
#game-board{
display:grid;
grid-template-columns:repeat(var(--grid-size),1fr);
grid-template-rows:repeat(var(--grid-size),1fr);
position:relative;
width:80vmin;aspect-ratio:1/1;
background:var(--game-board);}
.snake{
background:var(--snake);
border:var(--border);}
.snake.head{background:var(--snake-head);z-index:100;}
/* game over */
.snake.gameover{background:var(--game-over);}
.snake.head.gameover{background:var(--game-over-head);}
.food{
background:var(--food);
border:var(--border);}
/* [MODAL] */
#modal{
position:fixed;
width:100%;
top:50%;left:50%; transform:translate(-50%,0);
padding:.5em 0 .6em;
background:rgba(0,0,0,.3);
backdrop-filter:blur(20px);
text-align:center; font-weight:bold; font-size:10vmin; color:red;
opacity:0;
transition:all .3s;}
#modal.on{opacity:1; transform:translate(-50%,-50%);}
|
cs |
main.js
1
2
3
4
|
import { Game } from "./game.js";
const GAME = new Game();
GAME.init();
|
cs |
Game.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
import { Food } from "./food.js";
import { KeyInput } from "./keyboardInput.js";
import { Snake } from "./snake.js";
export class Game{
constructor(){
this.$board = document.getElementById('game-board');
this.lastRenderTime = 0;
this.playable = true;
this.$modal = document.getElementById('modal');
this.$score= document.getElementById('score');
this.SCORE = 0;
this.GRID_SIZE = 21;
this.IPT = new KeyInput(this);
this.SNAKE = new Snake(this);
this.FOOD = new Food(this);
}//constructor
init(){
requestAnimationFrame(this.repeat);
this.IPT.add_key_event();
}//init
repeat = async (currTime) => {
this.playable && requestAnimationFrame(this.repeat);
if(!this.playable){
this.game_over();
if(await this.is_replay()) this.reset_all();
}
const secSinceLastRender = (currTime - this.lastRenderTime) / 1000;
if(secSinceLastRender < 1 / this.SNAKE.SPEED) return;
// console.log(secSinceLastRender);
this.lastRenderTime = currTime;
this.update();
this.draw();
}//repeat
update(){
this.SNAKE.update();
this.playable = this.is_game_over();
this.FOOD.update();
}//update
draw(){
this.reset_board();
this.SNAKE.draw();
this.FOOD.draw();
}//draw
reset_board(){
this.$board.innerHTML = '';
}//reset_board
update_score(){
this.SCORE += (this.SNAKE.snakeBody.length) * 10;
this.$score.textContent = this.SCORE;
}//update_score
is_game_over(){
const first = this.SNAKE.snakeBody[0];
if(first.x <= 0){
first.x = 1;
return false;}
if(first.x > this.GRID_SIZE){
first.x = this.GRID_SIZE;
return false;}
if(first.y <= 0){
first.y = 1;
return false;}
if(first.y > this.GRID_SIZE){
first.y = this.GRID_SIZE;
return false;}
if(this.SNAKE.is_snake_collide()){return false;}
return true;
}//is_game_over
game_over(){
this.$modal.classList.add('on');
const $$snake = Array.from(this.$board.getElementsByClassName('snake'));
$$snake.forEach($snake => $snake.classList.add('gameover'));
}//game_over
is_replay(){
return new Promise(res =>{
setTimeout(()=>{
const result = confirm('Replay?');
res(result)
},500);
})
}//is_replay
reset_all(){
this.reset_board();
this.$modal.classList.remove('on');
this.lastRenderTime = 0;
this.SCORE = 0;
this.$score.textContent = this.SCORE;
this.IPT.reset();
this.SNAKE.reset();
this.FOOD.reset();
requestAnimationFrame(this.repeat);
}//reset_all
}//class-Game
|
cs |
food.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
export class Food{
constructor(GAME){
this.GAME = GAME;
this.SNAKE = this.GAME.SNAKE;
this.foodPos = this.randomPos();
this.EXPANSION_RATE = 5;
}//constructor
reset(){
this.foodPos = this.randomPos();
}//reset
randomPos(){
let foodPos
while(!foodPos || this.SNAKE.is_on_snake(foodPos)){
foodPos = {
x : parseInt(Math.random() * this.GAME.GRID_SIZE + 1),
y: parseInt(Math.random() * this.GAME.GRID_SIZE + 1)
};
}
// console.log("foodPos",foodPos);
return foodPos;
}//randomPos
update(){
if(!this.SNAKE.is_on_snake(this.foodPos)) return;
this.SNAKE.expand_snake(this.EXPANSION_RATE);
this.foodPos = this.randomPos();
this.GAME.update_score();
}//update
draw(){
const $food = document.createElement('DIV');
$food.classList.add('food');
$food.style.gridRowStart = this.foodPos.y;
$food.style.gridColumnStart = this.foodPos.x;
this.GAME.$board.appendChild($food);
}//draw
}//class-Food
|
cs |
snake.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
export class Snake{
constructor(GAME){
this.GAME = GAME;
this.SPEED = 4;
this.snakeBody = [
{x:11,y:11},
];
this.newSegments = 0;
}//constructor
reset(){
this.snakeBody = [{x:11,y:11}];
this.newSegments = 0;
}
update(){
const len = this.snakeBody.length - 2;
// console.log('update');
for(let i=len; i>=0; i--){
// console.log(this.snakeBody[i+1], this.snakeBody[i]);
this.snakeBody[i+1] = {...this.snakeBody[i]};
}//for
const{x,y} = this.GAME.IPT.direction;
this.snakeBody[0].x += x;
this.snakeBody[0].y += y;
}//update
draw(){
this.snakeBody.forEach((segment,idx) => {
const $snake = this.make_snake(idx);
const {x,y} = segment;
$snake.style.gridRowStart = y;
$snake.style.gridColumnStart = x;
this.GAME.$board.appendChild($snake);
});
// console.log(this.snakeBody);
}//draw
make_snake(idx){
const $snake = document.createElement('DIV');
$snake.classList.add('snake');
if(idx==0) $snake.classList.add('head');
return $snake;
}//make_snake
expand_snake(EXPANSION_RATE){
this.newSegments += EXPANSION_RATE;
// console.log('newSegment',this.newSegments, 'expansion',EXPANSION_RATE);
this.add_snake();
}//expand_snake
is_on_snake(foodPos){
return this.snakeBody.some(seg => is_same_pos(seg,foodPos));
}//is_on_snake
add_snake(){
for(let i=0; i<this.newSegments; i++){
this.snakeBody.push({...this.snakeBody[this.snakeBody.length - 1]});
}//for
console.log('body Length:',this.snakeBody.length);
this.newSegments = 0;
}//add_snake
is_snake_collide(){
const first = this.snakeBody[0];
const rest = this.snakeBody.slice(1);
return rest.some(seg => is_same_pos(seg,first));
}//is_snake_collide
}//class-Snake
export function is_same_pos(pos1,pos2){
return (pos1.x == pos2.x && pos1.y == pos2.y);
}//is_same_pos
|
cs |
ipt.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
export class KeyInput{
constructor(GAME){
this.GAME = GAME;
this.direction = {x:0, y:0};
this.lastDirection = {x:0, y:0};
this.DIR = {up:"ArrowUp",down:"ArrowDown",left:"ArrowLeft",right:"ArrowRight"};
}//constructor
reset(){
this.direction = {x:0, y:0};
this.lastDirection = {x:0, y:0};
}//reset
add_key_event(){
window.addEventListener('keydown',this.on_key_down);
}//add_key_event
on_key_down = (e) => {
if(this.CURR == e.key) return;
this.CURR = e.key;
switch(e.key){
case this.DIR.up :
if (this.lastDirection.y !== 0) break;
this.direction.x = 0;
this.direction.y = -1;
break;
case this.DIR.down :
if (this.lastDirection.y !== 0) break;
this.direction.x = 0;
this.direction.y = 1;
break;
case this.DIR.left :
if (this.lastDirection.x !== 0) break;
this.direction.x = -1;
this.direction.y = 0;
break;
case this.DIR.right :
if (this.lastDirection.x !== 0) break;
this.direction.x = 1;
this.direction.y = 0;
break;
default : break;
}//switch
this.lastDirection = this.direction;
}//on_key_down
}//class-KeyInput
|
cs |
'CSS&JS > 👀Study and Copy' 카테고리의 다른 글
[Online Tutorials]Glowing Box (0) | 2022.12.16 |
---|---|
[Nomad Coders] 바닐라 JS로 그림판 만들기 - 2021ver (0) | 2022.08.26 |
[WDS]Intro.js 바닐라 자바스크립트로 구현하기 (0) | 2022.08.18 |
[WDS] Flip Card Time Turner (0) | 2022.08.17 |
WDS,OT - Magic CSS Indicator (0) | 2022.08.09 |