CSS&JS/👀Study and Copy

[JS]바닐라 자바스크립트로 지뢰찾기 만들기 (제로초 렛츠기릿 자바스크립트 강의)

arancia_ 2022. 2. 7. 16:52

제로초(조현영)님의 인프런/유튜브 무료 강의 "렛츠기릿 자바스크립트" 공부 내용입니다.

 

[무료] [리뉴얼] 렛츠기릿 자바스크립트 - 인프런 | 강의

본 강의에서는 자바스크립트를 활용해 프로그래밍 사고력을 기르는 연습을 합니다. 웹 게임인 구구단을 시작으로 끝말잇기, 숫자 야구, 반응 속도 테스트, 틱택토, 로또 추첨기, 가위바위보, 카

www.inflearn.com

 

본 강의에서 2022년 기준 12강에 해당하는 지뢰찾기를 class 문법으로 재구성했습니다.
기본 기능들은 강의 내용과 동일하며, 노란색 바탕은 추가한 기능입니다.
마우스 이벤트는 추가한 기능 때문에 addEventListener 부분이 다르니 유의해주세용

구현한 기능

  • form으로 행,열,지뢰의 갯수를 받아 table을 그린다.
  • form의 submit버튼으로 게임 중지/reset 후 시작 을 할 수 있다.
  • 진행시간 표시 및, 게임 종료시 걸린 시간을 표시한다. (setTimeout)
  • 게임이 종료되면 관련 이벤트는 remove한다.
  • 지뢰가 아닌 칸을 눌렀을 시, 주변의 지뢰 갯수를 표시한다.
    • 주변의 지뢰 갯수가 0이면 0,0과 이미 열린칸과 지뢰가 있는 칸, 깃발이 있는 칸을 제외하고 재귀적으로 (-1,-1)부터 (1,1)까지 열어준다. (recursive 재귀!)
  • 첫클릭에는 지뢰가 뜨지 않게 한다.
    • 첫클릭이 지뢰라면 해당 칸은 주변 지뢰갯수 표시로 변경하고, 다른 지뢰가 아닌 칸에 지뢰를 옮긴다.
  • 마우스 이벤트는 다음과 같다.
    • 좌클릭 : 칸을 연다. 지뢰라면 게임이 패배로 끝나고, 지뢰가 아니라면 숫자를 표시하거나 연속으로 연다.
    • 우클릭 : ?(question) >> 🚩(flag) >> (없음) >> ?(question)....을 번갈아 표시한다.
    • 좌클릭+우클릭 : 해당 칸이 열린칸이면
      • 깃발 표시를 하지 않음 - (-1,-1)~(1,1) 까지 임시로 영역을 표시한다(.checking)
      • 깃발 표시를 하였고, 주변 지뢰 갯수와 일치함 : 깃발이 표시되지 않은 (-1,-1)~(1,1)칸을 연다. 이 과정에서 recursive가 발생할 수 있다. 깃발의 갯수가 일치하나 위치가 틀린경우엔 깃발을 표시 하지 않았지만 지뢰가 있는 칸도 강제로 열기 때문에 패배처리 된다.

 

소스코드

Mine_02.zip
0.01MB

 

깃허브 주소 : https://github.com/OhIkmyeong/zerocho_mine_sweeper

 

GitHub - OhIkmyeong/zerocho_mine_sweeper: 제로초JS 렛츠기릿 자바스크립트 강의 지뢰찾기

제로초JS 렛츠기릿 자바스크립트 강의 지뢰찾기. Contribute to OhIkmyeong/zerocho_mine_sweeper development by creating an account on GitHub.

github.com

 

 

 

index.html

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
<!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>지뢰찾기 Mine 002</title>
<link rel="stylesheet" type="text/css" href="./src/style.css"/>
<script src="./src/main.js" type="module"></script>
</head>
<body>
    <!-- 지뢰 개수 입력 받기 -->
    <form id="ipt-form">
        <input type="number" min="2" max="20" value="5" placeholder="행" id="ipt-row"/>
        <input type="number" min="2" max="20" value="5" placeholder="열" id="ipt-col"/>
        <input type="number" value="5" placeholder="지뢰 갯수" id="ipt-mine"/>
        <input type="submit" value="시작" id="ipt-btn"/><!-- 시작/정지 -->
    </form>
 
    <!-- 타이머,깃발 표시 겸 승리/패배시 메세지 표시까지 -->
    <section id="info">
        <div id="message"></div><!-- 게임 준비중/😊/승리/패배 -->
        <div id="timer"></div><!-- 00:00 -->
        <div id="flag"></div><!-- 🚩NN -->
    </section>
    
    <!-- 테이블 영역 -->
    <table id="tbl">
        <tbody>
        </tbody>
    </table><!-- tbl -->
</body>
</html>
cs

 

style.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
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
@charset "utf-8";
*{margin:0;padding:0;box-sizing:border-box;}
input{
    background:transparent;
    padding:1em;
    border:2px solid rgba(255,255,255,.5); border-radius:4px;
    font-size:inherit;color:inherit;}
html,body{
    display:flex; flex-flow:column nowrap;
    justify-content:center; align-items:center;
    width:100%; min-height:100vh;
    background:#000; 
    font-size:20px;color:#fff;}
 
/* form */
input[type="number"]{width:10ch;}
#ipt-mine{font-weight:bold;color:red;}
#ipt-btn{
    padding:.8em 2em; margin-left:1em;
    background:mediumturquoise;
    font-weight:bold;color:#000;
    cursor:pointer;}
 
    #ipt-btn:hover{
        transform:translate(2px,2px);
        filter:brightness(90%);}
 
    #ipt-btn.started{background-color:rgba(220, 20, 60, 0.5); color:#fff;}
 
/* info */
#info{
    display:flex; flex-flow:row wrap;
    justify-content:center;
    position:relative; 
    width:100%;
    padding:1em; margin:2rem 0;
    background:rgba(255,255,255,.1);}
 
#message{
    width:100%; position:relative;
    margin-bottom:1rem;
    text-align:center;font-size:1.5rem;}
 
    #message:empty::after{
        content:'게임 준비중...'; position:absolute;
        left:50%; top:50%;
        transform:translate(-50%,-15%);
        opacity:.3;
        font-size:1rem;}
 
#flag{
    position:relative;
    margin-left:2rem;
    color:crimson;}
 
#flag::before{
    content:'🚩';
    margin-right:10px;}
 
    #flag:empty:before{display:none;}
 
 
/* 테이블 */
#tbl{
    position:relative;
    border-collapse:collapse;}
 
#tbl td{
    position:relative;
    width:50px; height:50px; line-height:50px;
    background:rgba(255,255,255,.2);
    border:1px solid rgba(255,255,255,.5);
    text-align:center;
    cursor:pointer; user-select:none;}
 
 
    #tbl td.opened{background:#111;}
 
    #tbl td.question{background:rgba(255, 189, 65, 0.8);}
    #tbl td.question::after{
        content:"❓";}
 
    #tbl td.flag{background:rgba(255, 0, 0, 0.3);}
    #tbl td.flag::after{content:'🚩';}
 
    #tbl td.bomb{background:rgba(255, 0, 0, 1);}
    #tbl td.bomb::after{content:'💣';}
 
    #tbl td.checking{
        filter:brightness(50%);}
cs



main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Game } from "./game.js";
 
const $form = document.getElementById('ipt-form');
const GAME = new Game();
 
$form.addEventListener('submit',e => {
    e.preventDefault();
    const target = e.target;
    //지뢰 개수 검사
    const row = parseInt(target["ipt-row"].value);
    const col = parseInt(target["ipt-col"].value);
    const mine = parseInt(target["ipt-mine"].value);
 
    if(mine >= row * col){
        alert('지뢰의 갯수를 좀 더 적게 조정해주세용');
        target["ipt-mine"].value = row * col - 1;
        return;
    }//if
    
    //시작인지 종료인지 판단
    const $btn = document.getElementById("ipt-btn");
    $btn.value ==  "시작" ? GAME.init(row,col,mine) : GAME.quit(GAME.reason.stop);
});
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
import { DataField } from "./data.js";
import { Handle } from "./handle.js";
import { Info } from "./info.js";
import { Tbl } from "./tbl.js";
import { Timer } from "./timer.js";
 
export class Game{
    constructor(){
        this.DATA = new DataField(this);
        this.TBL = new Tbl(this);
        this.HANDLE = new Handle(this);
        this.INFO = new Info(this);
        this.TIMER = new Timer(this);
 
        this.reason = Object.freeze({
            win : "🍀승리!", lost : "패배..😥"stop:"장비를 정지합니다...😯"
        });
 
        this.firstClicked = false;
    }//constructor
 
    ////////// METHOD //////////
    init(row,col,mine){
        this.change_btn();
 
        //DATA 관련 총괄
        this.DATA.init(row,col,mine);
 
        //table 그리기
        this.TBL.reset_table();
        this.TBL.draw_table();
 
        //이벤트 리스너 등록
        this.HANDLE.add_listener();
        
        //인포 지웠다가
        this.INFO.reset();
        //시간 시작
        this.TIMER.start_timer();
        //깃발 표시
        this.INFO.started();
    }//init
 
    /* form의 버튼을 바꿈 */
    change_btn(){
        const $btn = document.getElementById("ipt-btn");
        $btn.value = $btn.value ==  "시작" ? "정지" : "시작";
        $btn.classList.toggle('started');
    }//change_btn
 
    /* 승리 판정 */
    is_win(){
        const {row,col,mine} = this.DATA.NUM;
        const opened = document.querySelectorAll('.opened').length;
        if(row * col - mine == opened){ this.quit(this.reason.win);}
    }//is_win
 
    /* 게임 종료 */
    quit(reason){
        this.change_btn();
        this.firstClicked = false;
        //지뢰 전부 보여주고
        this.DATA.reveal_all_mine();
        //이벤트 제거하고
        this.HANDLE.remove_listener();
        //인포 화면 관련, 타이머...
        this.TIMER.reset_timer();
        this.INFO.display_message(reason);
    }//quit
}//class - Game
cs

 

data.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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
export class DataField{
    constructor(GAME){
        this.GAME = GAME;
        this.NUM = {row:undefined,col:undefined,mine:undefined};
        this.CODE = Object.freeze({
            UNOPEN : -1, QUESTION : -2, FLAG : -3,
            QUESTION_M:-4, FLAG_M:-5, MINE:-999,
            OPENED:0 //0~8
        });
 
        this.ANSWER = [];
    }//constructor
    /////// METHOD ////////
    /* init */
    init(row,col,mine){
        this.set_num(row,col,mine);
        this.plant_unopen();
        this.plant_mines();
 
        console.log(this.ANSWER);
    }//init
 
    /* 초기 정보를 저장한다 */
    set_num(row,col,mine){
        this.NUM.row = row;
        this.NUM.col = col;
        this.NUM.mine = mine;
    }//set_num
 
    /* 일반 값으로 초기세팅 */
    plant_unopen(){
        const {row,col} = this.NUM;
        const {UNOPEN} = this.CODE;
        this.ANSWER = Array.from(Array(row), ()=>new Array(col).fill(UNOPEN));
    }//plant_unopen
 
    /* 지뢰 심기 */
    plant_mines(){
        const {row,col,mine} = this.NUM;
        const {MINE} = this.CODE;
        const shuffle = this.get_shuffle(row,col,mine);
 
        shuffle.forEach(elem => {
            const rIdx = Math.floor(elem / row);
            const cIdx = elem % row;
            this.ANSWER[rIdx][cIdx] = MINE;
        });
    }//plant_mines
 
    /* 피셔예이츠 셔플 */
    get_shuffle(row,col,mine){
        const candidate = Array(row * col).fill().map((el,idx)=>idx);
        const shuffle = [];
        while(shuffle.length < mine){
            const random = Math.floor(Math.random() * candidate.length);
            const popped = candidate.splice(random,1)[0];
            shuffle.push(popped);
        }//while
 
        return shuffle;
    }//get_shuffle
 
    /* 지뢰갯수 세서 숫자넣기 */
    fill_number(rIdx,cIdx){
        const $td = this.GAME.HANDLE.$tbl.children[rIdx].children[cIdx];
        const mineCase = [this.CODE.MINE, this.CODE.FLAG_M, this.CODE.QUESTION_M];
 
        //early return
        if($td.classList.contains('opened')){return;}
        if(this.ANSWER[rIdx][cIdx] == this.CODE.FLAG){return;}
        if(mineCase.includes(this.ANSWER[rIdx][cIdx])){return;}
 
        let num_mine = 0;
        
        mineCase.includes(this.ANSWER[rIdx - 1]?.[cIdx-1]) && num_mine++;
        mineCase.includes(this.ANSWER[rIdx - 1]?.[cIdx]) && num_mine++
        mineCase.includes(this.ANSWER[rIdx - 1]?.[cIdx+1]) && num_mine++
 
        mineCase.includes(this.ANSWER[rIdx][cIdx-1]) && num_mine++
        mineCase.includes(this.ANSWER[rIdx][cIdx+1]) && num_mine++
 
        mineCase.includes(this.ANSWER[rIdx + 1]?.[cIdx-1]) && num_mine++
        mineCase.includes(this.ANSWER[rIdx + 1]?.[cIdx]) && num_mine++
        mineCase.includes(this.ANSWER[rIdx + 1]?.[cIdx+1]) && num_mine++
 
        $td.textContent = num_mine == 0 ? "" : num_mine;
        this.ANSWER[rIdx][cIdx] = num_mine;
        this.GAME.HANDLE.opened($td, true);
 
        if(this.ANSWER[rIdx][cIdx] == 0){this.clicked_zero(rIdx,cIdx);}
    }//fill_number
 
    /* 클릭한 영역이 0일때 */
    clicked_zero(rIdx,cIdx){
        this.ANSWER[rIdx - 1]?.[cIdx-1&& this.fill_number(rIdx-1, cIdx-1); 
        this.ANSWER[rIdx - 1]?.[cIdx] && this.fill_number(rIdx-1, cIdx);
        this.ANSWER[rIdx - 1]?.[cIdx+1&& this.fill_number(rIdx-1, cIdx+1);
 
        this.ANSWER[rIdx][cIdx-1&& this.fill_number(rIdx, cIdx-1); 
        this.ANSWER[rIdx][cIdx+1&& this.fill_number(rIdx, cIdx+1); 
 
        this.ANSWER[rIdx + 1]?.[cIdx-1&& this.fill_number(rIdx+1, cIdx-1); 
        this.ANSWER[rIdx + 1]?.[cIdx] && this.fill_number(rIdx+1, cIdx);
        this.ANSWER[rIdx + 1]?.[cIdx+1&& this.fill_number(rIdx+1, cIdx+1);
    }//clicked_zero
 
    /* 좌클릭 + 우클릭 : open_auto with numbers */
    open_auto_3x3(rIdx,cIdx){
        let bombs = this.ANSWER[rIdx][cIdx];
        let flags = 0;
        const $tr = this.GAME.HANDLE.$tbl.children;
 
        this.ANSWER[rIdx - 1]?.[cIdx-1&& this.is_flag($tr,rIdx-1,cIdx-1&& flags++;
        this.ANSWER[rIdx - 1]?.[cIdx] && this.is_flag($tr,rIdx-1,cIdx) && flags++;
        this.ANSWER[rIdx - 1]?.[cIdx+1&& this.is_flag($tr,rIdx-1,cIdx+1&& flags++;
 
        this.ANSWER[rIdx][cIdx-1&& this.is_flag($tr,rIdx,cIdx-1&& flags++;
        this.ANSWER[rIdx][cIdx+1&& this.is_flag($tr,rIdx,cIdx+1&& flags++;
 
        this.ANSWER[rIdx + 1]?.[cIdx-1&& this.is_flag($tr,rIdx+1,cIdx-1&& flags++;
        this.ANSWER[rIdx + 1]?.[cIdx] && this.is_flag($tr,rIdx+1,cIdx) && flags++;
        this.ANSWER[rIdx + 1]?.[cIdx+1&& this.is_flag($tr,rIdx+1,cIdx+1&& flags++;
 
        //flag의 갯수가 0이 아니고, 갯수가 같다면
        if(flags!==0 && flags == bombs){
            const rowStart = rIdx - 1 < 0 ? 0 : rIdx - 1;
            const rowEnd = rIdx + 1 >= this.NUM.row ? this.NUM.row -1 : rIdx+1;
            const colStart = cIdx - 1 < 0 ? 0 : cIdx - 1;
            const colEnd = cIdx + 1 >= this.NUM.col ? this.NUM.col -1 : cIdx+1;
 
            for(let r=rowStart; r<=rowEnd; r++){
                for(let c = colStart; c<=colEnd; c++){
                    console.log(r,c);
                    const target = $tr[r].children[c];
                    this.GAME.HANDLE.left(target,r,c);
                }//for-c
            }//for-r
        }//if
    }//open_auto_3x3
 
    /* 깃발 가졌는지 확인 */
    is_flag($tr,r,c){
        if($tr[r].children[c].classList.contains('flag')){return true;}
        return false;
    }//is_flag
 
    /* 지뢰 모두 까기 */
    reveal_all_mine(){
        const mineCase = [this.CODE.MINE, this.CODE.FLAG_M, this.CODE.QUESTION_M];
        const $tr = this.GAME.HANDLE.$tbl.children;
 
        for(let r = 0; r<this.NUM.row; r++){
            for(let c=0; c<this.NUM.col; c++){
                const cell = this.ANSWER[r][c];
                if(mineCase.includes(cell)){
                    const $td = $tr[r].children[c]; 
                    this.GAME.HANDLE.question($td,false);
                    this.GAME.HANDLE.flag($td,false);
                    $td.classList.add('bomb');
                }//if
            }//for-c
        }//for-r
    }//reveal_all_mine
 
    transfer_mine(rIdx,cIdx){
        /* early return */
        if(this.GAME.HANDLE.find_upopen){return;}
 
        const {UNOPEN, MINE} = this.CODE;
 
        if(this.ANSWER[rIdx][cIdx] == UNOPEN){
            this.GAME.HANDLE.find_upopen = true;
            this.ANSWER[rIdx][cIdx] = MINE;
            //개발자 모드
            // const $td = this.GAME.HANDLE.$tbl.children[rIdx].children[cIdx];
            // this.GAME.TBL.developer_mode($td);
            return;}
 
        this.ANSWER[rIdx-1]?.[cIdx-1&& this.transfer_mine(rIdx-1, cIdx-1);
        this.ANSWER[rIdx-1]?.[cIdx] && this.transfer_mine(rIdx-1, cIdx);
        this.ANSWER[rIdx-1]?.[cIdx+1&& this.transfer_mine(rIdx-1, cIdx+1);
 
        this.ANSWER[rIdx-1]?.[cIdx-1&& this.transfer_mine(rIdx, cIdx-1);
        this.ANSWER[rIdx-1]?.[cIdx+1&& this.transfer_mine(rIdx, cIdx+1);
 
        this.ANSWER[rIdx+1]?.[cIdx-1&& this.transfer_mine(rIdx+1, cIdx-1);
        this.ANSWER[rIdx+1]?.[cIdx] && this.transfer_mine(rIdx+1, cIdx);
        this.ANSWER[rIdx+1]?.[cIdx+1&& this.transfer_mine(rIdx+1, cIdx+1);
    }//transfer_mine
}//DataField
cs

 

tbl.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
export class Tbl{
    constructor(GAME){
        this.GAME = GAME;
    }//constructor
 
    ////////// METHOD //////////
    draw_table(){
        const {row,col} = this.GAME.DATA.NUM;
        const $tbl = document.getElementById('tbl').getElementsByTagName('TBODY')[0];
        const DATA = this.GAME.DATA;
        const ANSWER = DATA.ANSWER;
        const {MINE} = DATA.CODE;
 
        for(let r=0; r<row; r++){
            const $tr = document.createElement('TR');
            for(let c=0; c<col; c++){
                const $td = document.createElement('TD');
                $tr.appendChild($td);
                // ANSWER[r][c] == MINE && this.developer_mode($td); //개발자 모드
            }
            $tbl.appendChild($tr);
        }//for
    }//draw_table
 
    reset_table(){ 
        const $tbl = document.getElementById('tbl').getElementsByTagName('TBODY')[0];
        $tbl.innerHTML = '';}//reset_table
 
    /* 개발자모드 */
    developer_mode($td){$td.textContent = 'X';}
 
    /* 클래스 달기 */
}//class-Tbl
cs

 

handle.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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
export class Handle{
    constructor(GAME){
        this.GAME = GAME;
 
        this.$tbl = document.getElementById('tbl').getElementsByTagName('TBODY')[0];
 
        this.find_unopen = false;
    }//constructor
 
    ////////// METHOD //////////
    /* 이벤트 등록 */
    add_listener(){
        //우클릭 방지
        this.$tbl.addEventListener('contextmenu'this.on_context);
 
        //마우스 이벤트 총괄 (좌,우,좌+우)
        this.$tbl.addEventListener('mousedown'this.on_down);
 
        //마우스 뗄 때 checking 해제
        this.$tbl.addEventListener('mouseup',this.disable_checking);
    }//add_listener
 
    /* 이벤트 제거 */
    remove_listener(){
        this.$tbl.removeEventListener('contextmenu'this.on_context);
        this.$tbl.removeEventListener('mousedown'this.on_down);
        this.$tbl.removeEventListener('mouseup',this.disable_checking);
    }//remove_listener
 
    /* 클릭 좌 우 좌+우 */
    on_down = (e) =>{
        //early return
        const target = e.target;
        if(target.tagName !== "TD"){return;}
 
        const rIdx = target.parentElement.rowIndex;
        const cIdx = target.cellIndex;
 
        //열린걸 눌렀지만 좌+우 클릭인 경우
        if(target.classList.contains('opened')){
            if(e.buttons == 3){this.left_right(rIdx,cIdx);}
            return;
        }//if
 
 
        /* 좌클릭 or 우클릭 */
        switch(e.button){
            case 0 : 
                this.left(target,rIdx,cIdx);
                break;
 
            case 2 : 
                this.right(target,rIdx,cIdx);
                break;
        }//switch        
    }//on_down
 
    /* 좌 + 우 클릭 */
    left_right(rIdx,cIdx){
        const ANSWER = this.GAME.DATA.ANSWER;
        //단순한 class toggle
        this.check_3x3(rIdx,cIdx,ANSWER);
 
        //깃발표시와 mine의 갯수가 일치한다면 open //위치가 틀릴시엔 자폭된다.
        this.GAME.DATA.open_auto_3x3(rIdx,cIdx);
    }//left_right
 
    /* 좌클릭 */
    left(target,rIdx,cIdx){
        //early return
        if(target.classList.contains('opened')){return;}
        if(target.classList.contains('question')){return;}
        if(target.classList.contains('flag')){return;}
 
        const DATA = this.GAME.DATA;
        const ANSWER = DATA.ANSWER;
        const {UNOPEN,MINE} = DATA.CODE;
        const clicked = ANSWER[rIdx][cIdx];
        
        switch(clicked){
            case UNOPEN :
                this.GAME.firstClicked = true;
                DATA.fill_number(rIdx,cIdx);
                this.GAME.is_win();
                break;
                
            case MINE :
                //첫클릭이 지뢰였던 경우엔
                if(!this.GAME.firstClicked){
                    this.GAME.firstClicked = true;
                    //지뢰 재귀로 옮기고
                    DATA.transfer_mine(rIdx,cIdx);
 
                    //숫자로 표기해조
                    ANSWER[rIdx][cIdx] = UNOPEN;
                    DATA.fill_number(rIdx,cIdx);
                    return;
                }
                //그 외엔 얄짤 없는 패배다.
                const {lost} = this.GAME.reason;
                this.GAME.quit(lost);
                break;
        }//switch
    }//left
 
    /* 우클릭 판정 */
    right(target,rIdx,cIdx){
        //early return
        if(target.classList.contains('opened')){return;}
        
        const DATA = this.GAME.DATA;
        const ANSWER = DATA.ANSWER;
        const {UNOPEN,QUESTION,FLAG,QUESTION_M,FLAG_M,MINE} = DATA.CODE;
        const cell = ANSWER[rIdx][cIdx];
        switch(cell){
            case UNOPEN :
                ANSWER[rIdx][cIdx] = QUESTION;
                this.question(target, true);
                break;
 
            case QUESTION :
                ANSWER[rIdx][cIdx] = FLAG;
                this.question(target, false);
                this.flag(target, true);
                this.GAME.INFO.display_flag(-1);
                break;
 
            case FLAG :
                ANSWER[rIdx][cIdx] = UNOPEN;
                this.flag(target, false);
                this.GAME.INFO.display_flag(+1);
                break;
 
            case MINE :
                ANSWER[rIdx][cIdx] = QUESTION_M;
                this.question(target, true);
                break;
 
            case QUESTION_M :
                ANSWER[rIdx][cIdx] = FLAG_M;
                this.question(target, false);
                this.flag(target, true);
                this.GAME.INFO.display_flag(-1);
                break;
 
            case FLAG_M :
                ANSWER[rIdx][cIdx] = MINE;
                this.flag(target, false);
                this.GAME.INFO.display_flag(+1);
                break;
            default:break;
        }//switch
    }//right
 
    /* 정직한 우클릭시 기본 동작 막기 */
    on_context = (e) =>{e.preventDefault();}
 
    /* 마우스 up */
    disable_checking = ()=>{
        const all_check = this.$tbl.querySelectorAll('.checking');
        all_check.forEach(td => { this.checking(td,false); });
    }//disable_checking
 
    /* 클래스 관련 */
    //checking 여러개
    check_3x3(rIdx,cIdx,ANSWER){
        const $allTR = this.$tbl.children;
        ANSWER[rIdx - 1]?.[cIdx-1&& this.check1x1($allTR,rIdx-1,cIdx-1);
        ANSWER[rIdx - 1]?.[cIdx] && this.check1x1($allTR,rIdx-1,cIdx);
        ANSWER[rIdx - 1]?.[cIdx+1&& this.check1x1($allTR,rIdx-1,cIdx+1)
 
        ANSWER[rIdx][cIdx-1&& this.check1x1($allTR,rIdx,cIdx-1)
        ANSWER[rIdx][cIdx+1&& this.check1x1($allTR,rIdx,cIdx+1)
 
        ANSWER[rIdx + 1]?.[cIdx-1&& this.check1x1($allTR,rIdx+1,cIdx-1);
        ANSWER[rIdx + 1]?.[cIdx] && this.check1x1($allTR,rIdx+1,cIdx);
        ANSWER[rIdx + 1]?.[cIdx+1&& this.check1x1($allTR,rIdx+1,cIdx+1);
    }//check_3x3
 
    check1x1($allTR,r,c){
        const $td = $allTR[r].children[c];
        if($td.classList.contains('opened')){return;}
        if($td.classList.contains('question')){return;}
        if($td.classList.contains('flag')){return;}
        this.checking($td,true);
    }//check1x1
 
    //checking 단일
    checking(td, bool){ td.classList.toggle('checking',bool);}
 
    //question
    question(td,bool){ td.classList.toggle('question',bool);}
 
    //flag
    flag(td,bool){ td.classList.toggle('flag',bool);}
 
    //opened
    opened(td,bool){td.classList.toggle('opened',bool);}
}//class-Handle
cs

 

info.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
export class Info{
    constructor(GAME) {
        this.GAME = GAME;
        
        this.$message = document.getElementById('message');
        this.$flag = document.getElementById('flag');
        this.flag = 0;
    }//constructor
 
    started(){
        this.$message.textContent = `😊`;
        this.flag = this.GAME.DATA.NUM.mine;
        this.display_flag();
    }//started
 
    /* 깃발표시 */
    display_flag(num){
        this.flag += num ?? 0;
        this.$flag.textContent = this.flag;
    }//display_flag
 
    /* 메세지 표시 */
    display_message(reason){
        this.$message.textContent = reason;
    }//display_message
 
    /* 리셋 */
    reset(){
        this.$message.textContent = '';
        this.$flag.textContent = '';
        this.flag = 0;
    }//reset
    
}//class-Info
cs

 

timer.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
export class Timer{
    constructor(GAME){
        this.GAME = GAME;
 
        this.$timer = document.getElementById('timer');
 
        this.timer = undefined;
        this.start = null;
        this.passed = null;
        this.end = null;
    }//constructor
 
    ////////// METHOD //////////
    start_timer(){
        this.start = new Date();
        this.run_timer();
    }//start_timer
 
    run_timer(){
        this.end = new Date();
        this.passed = Math.floor((this.end - this.start) / 1000);
        this.display_timer();
        this.timer = setTimeout(()=>{
            this.run_timer();
        },1000);
    }//run_timer
 
    reset_timer(){
        clearTimeout(this.timer);
        this.timer = null;
        this.start = null;
        this.passed = null;
        this.end = null;
    }//reset_timer
 
    /* 타이머 표시 */
    display_timer(){
        const mm = String(Math.floor(this.passed / 60)).padStart(2,"0");
        const ss = String(this.passed % 60).padStart(2,"0");
        this.$timer.textContent = `${mm}:${ss}`;
    }//display_timer
}//class-Timer
cs

'CSS&JS > 👀Study and Copy' 카테고리의 다른 글

2048 클론코딩  (0) 2022.06.07
getter,setter  (0) 2022.06.03
[JS]class에서 this를 할당하는 방법  (0) 2022.01.26
[JS]이진법/십진법 재귀함수  (0) 2022.01.24
1분코딩 - BBC 인터랙티브 클론코딩  (0) 2021.12.27