럿고의 개발 노트

[인프런] 웹 게임을 만들며 배우는 Vue 7. 틱택토 본문

Vue.js Note/[인프런] 웹 게임을 만들며 배우는 Vue(동영상 강의)

[인프런] 웹 게임을 만들며 배우는 Vue 7. 틱택토

KimSeYun 2019. 11. 16. 18:16

[인프런] 웹 게임을 만들며 배우는 Vue

7. 틱택토

7.1. 2차원배열(테이블 구조 짜기)

7.2. this.$root, this.$parent

7.3. Vue.set

7.4. 틱택토 완성하기

7.5. EventBus 사용하기

7.6. Vuex 구조 세팅하기

7.7, Vuex Mutations

7.8. Vuex state 사용하기

7.9. mapState와 Q&A

7.10. Vuex devtools 분석히기

7.11. slot

- 예제(웹 게임)을 통해 Vue를 만나는 시간으로, 예제를 풀면서 주석으로 배운 내용과 알아야 할 내용을 최대한 적어 놓았습니다. 코드와 주석을 참고해주세요!. 노드와 웹팩을 사용하여 설정파일들이 존재합니다. 아래 깃허브를 참고해주세요.

Ex1. 틱택토

[main.js]

1
2
3
4
import Vue from 'vue';
import TicTacToe from './TicTacToe';
 
new Vue(TicTacToe).$mount('#root');
cs


[package.json]

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
{
  "name""tic-tac-toe",
  "version""1.0.0",
  "description""",
  "main""index.js",
  "scripts": {
    "bulid""webpack --watch",
    "dev""webpack-dev-server --hot"
  },
  "author""",
  "license""ISC",
  "dependencies": {
    "vue""^2.6.10",
    "vue-loader""^15.7.2",
    "vue-template-compiler""^2.6.10",
    "webpack""^4.41.2",
    "webpack-cli""^3.3.10"
  },
  "devDependencies": {
    "css-loader""^3.2.0",
    "vue-style-loader""^4.1.2",
    "webpack-dev-server""^3.9.0"
  }
}
 
cs


[webpack.config.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
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const path = require('path'); // 경로 불러오는 것
 
module.exports = { // 노드에 모듈을 만듬, 이안에 웹팩 모든 설정을 넣으면 됨.
    mode:'development'// 배포할건지 개발한건지
    devtool: 'eval'// 개발할때는 eval, 배포할때는 hid,den-source-map
    resolve: {
        extensions: ['.js''.vue'], // 이걸 하면 import할때 확장자를 넣어줄 필요가 없다.
    },
    entry: { // 스크립트를 모인것 중에서 대표적인 스크립트
        app : path.join(__dirname, 'main.js'), // app은 하나로 합친 스크립트 이름
    },
    module: { // 웹팩의 핵심 // entry로 처리하다가 이상한거 나오면 loader를 실행
        rules: [{ // 합칠때 어떻게 합칠지를 설정해주는 것
            test: /\.vue$/// .vue로 끝나는 파일은 vue-loader를 사용하겠다, 정규표현식
            loader: 'vue-loader'// npm i vue-loader
        },{
            test: /\.css$/
            use: [ // loader와 user는 똑같은 기능
                'vue-style-loader',
                'css-loader',
            ]
        }],
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
    output: {
        filename: '[name].js'// 출력할 파일 이름(최종결과)
        path: path.join(__dirname, 'dist'), // 폴더 경로
        publicPath: '/dist'// webpack-dev-server 세팅시 필요
    },
};
 
// entry, module, plugins, output이 주 설정 나머지는 부가적인 설정
cs


[TicTacToe.html]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>틱택토</title>
</head>
<body>
<!-- 2차원배열을 이해하기가 가장 좋은 예제가 틱택토
    엑셀이나 DB같은 것이 2차원배열을 이용하는데, 2차원배열을 잘 다루는것이 중요하다
    Vue에서 배열을 인덱스로 값을 변경하면 보여지는 화면에서는 반영이 되지 않음.
    그러나 배열의 메서드를 사용하면 화면에 반영은 됨
    vuex는 중앙데이터관리실이라고 생각하면 됨 컴포넌트 연결이 10개 이상이 되버리면 최하위가 최상위 데이터를 가져오기 위해서는
    엄청난 v-bind가 필요한데, 그 문제점을 해결하기 위해 나타난것이 vuex
    
-->
    <div id="root"></div>
    <script src ="dist/app.js"></script>
</body>
</html>
cs


[TicTacToe.vue]

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
<template>
    <div>
        <div>{{turn}}님의 턴입니다.</div>
        <table-component :table-data="tableData"></table-component>
        <div v-if="winner">{{winner}}의 승리!</div>
    </div>
</template>
 
<script>
import Vue from 'vue';
import TableComponent from './TableComponent';
 
export default {
components: {
    TableComponent,
},
 data(){
     return{
         tableData: [
             [''''''],
             [''''''],
             [''''''],
         ],
         turn: 'O',
         winner: '',
     };
 },
 methods: {
     onChangData(){
         // this.tableData[1][0] = 'X'; 작동하지 않음
         //Vue.set(this.tableData[1], 0, 'X'); -> 작동하는 방법 1
         this.$set(this.tableData[1], 0'X'); // Vue.set과 동일
     }
 },
}
</script>
 
<style> 
    table{
        border-collapse: collapse;
    }
    td{
        border: 1px solid black;
        width: 40px;
        height: 40px;
        text-align: center;
    }
</style>
cs


[TableComponent.vue]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<table>
    <tr-component v-for="(rowData, index) in tableData" :key="index" :row-data="rowData" :row-index="index"></tr-component>
</table>
</template>
 
<script>
import TrComponent from './TrComponent'
export default {
    props: {
        tableData: Array,
    },
    components: {
        TrComponent,
    }
}
</script>
 
<style scoped>
 
</style>
cs


[TrComponent.vue]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
    <tr>
        <td-component v-for="(cellData, index) in rowData" :key="index" :cell-data="cellData" :cell-index="index" :row-index="rowIndex"></td-component>
    </tr>
</template>
 
<script>
import TdComponent from './TdComponent';
export default {
    props:{
        rowData: Array,
        rowIndex: Number,
    },
    data(){
        return{
 
        };
    },
    components:{
        TdComponent,
    }
}
</script>
cs


[TdComponent.vue]

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
<template>
    <td @click="onClickTd">{{cellData}}</td>
</template>
 
<script>
export default {
    props: {
        cellData: String,
        rowIndex: Number,
        cellIndex: Number,
    },
    methods: {
      onClickTd() {
        if (this.cellData) return;
        const rootData = this.$root.$data;
        this.$set(rootData.tableData[this.rowIndex], this.cellIndex, rootData.turn);
        let win = false;
        if (rootData.tableData[this.rowIndex][0=== rootData.turn && rootData.tableData[this.rowIndex][1=== rootData.turn && rootData.tableData[this.rowIndex][2=== rootData.turn) {
          win = true;
        }
        if (rootData.tableData[0][this.cellIndex] === rootData.turn && rootData.tableData[1][this.cellIndex] === rootData.turn && rootData.tableData[2][this.cellIndex] === rootData.turn) {
          win = true;
        }
        if (rootData.tableData[0][0=== rootData.turn && rootData.tableData[1][1=== rootData.turn && rootData.tableData[2][2=== rootData.turn) {
          win = true;
        }
        if (rootData.tableData[0][2=== rootData.turn && rootData.tableData[1][1=== rootData.turn && rootData.tableData[2][0=== rootData.turn) {
          win = true;
        }
        if (win) { // 이긴 경우: 3줄 달성
          rootData.winner = rootData.turn;
          rootData.turn = 'O';
          rootData.tableData = [[''''''], [''''''], ['''''']];
        } else { // 무승부
          let all = true// all이 true면 무승부라는 뜻
          rootData.tableData.forEach((row) => { // 무승부 검사
            row.forEach((cell) => {
              if (!cell) {
                all = false;
              }
            });
          });
          if (all) { // 무승부
            rootData.winner = '';
            rootData.turn = 'O';
            rootData.tableData = [[''''''], [''''''], ['''''']];
          } else {
            rootData.turn = rootData.turn === 'O' ? 'X' : 'O';
          }
        }
      }
    }
};
</script>
cs


깃허브 주소 : https://github.com/ksy90101/Vue.js/tree/master/vue-Webgame-inflearn/Chapter07


Ex2. 틱택토 - EventBus

[main.js]

1
2
3
4
import Vue from 'vue';
import TicTacToe from './TicTacToe';
 
new Vue(TicTacToe).$mount('#root');
cs


[package.json]

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
{
  "name""tic-tac-toe",
  "version""1.0.0",
  "description""",
  "main""index.js",
  "scripts": {
    "bulid""webpack --watch",
    "dev""webpack-dev-server --hot"
  },
  "author""",
  "license""ISC",
  "dependencies": {
    "vue""^2.6.10",
    "vue-loader""^15.7.2",
    "vue-template-compiler""^2.6.10",
    "webpack""^4.41.2",
    "webpack-cli""^3.3.10"
  },
  "devDependencies": {
    "css-loader""^3.2.0",
    "vue-style-loader""^4.1.2",
    "webpack-dev-server""^3.9.0"
  }
}
 
cs


[webpack.config.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
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const path = require('path'); // 경로 불러오는 것
 
module.exports = { // 노드에 모듈을 만듬, 이안에 웹팩 모든 설정을 넣으면 됨.
    mode:'development'// 배포할건지 개발한건지
    devtool: 'eval'// 개발할때는 eval, 배포할때는 hid,den-source-map
    resolve: {
        extensions: ['.js''.vue'], // 이걸 하면 import할때 확장자를 넣어줄 필요가 없다.
    },
    entry: { // 스크립트를 모인것 중에서 대표적인 스크립트
        app : path.join(__dirname, 'main.js'), // app은 하나로 합친 스크립트 이름
    },
    module: { // 웹팩의 핵심 // entry로 처리하다가 이상한거 나오면 loader를 실행
        rules: [{ // 합칠때 어떻게 합칠지를 설정해주는 것
            test: /\.vue$/// .vue로 끝나는 파일은 vue-loader를 사용하겠다, 정규표현식
            loader: 'vue-loader'// npm i vue-loader
        },{
            test: /\.css$/
            use: [ // loader와 user는 똑같은 기능
                'vue-style-loader',
                'css-loader',
            ]
        }],
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
    output: {
        filename: '[name].js'// 출력할 파일 이름(최종결과)
        path: path.join(__dirname, 'dist'), // 폴더 경로
        publicPath: '/dist'// webpack-dev-server 세팅시 필요
    },
};
 
// entry, module, plugins, output이 주 설정 나머지는 부가적인 설정
cs


[TicTacToe.html]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>틱택토</title>
</head>
<body>
<!-- 2차원배열을 이해하기가 가장 좋은 예제가 틱택토
    엑셀이나 DB같은 것이 2차원배열을 이용하는데, 2차원배열을 잘 다루는것이 중요하다
    Vue에서 배열을 인덱스로 값을 변경하면 보여지는 화면에서는 반영이 되지 않음.
    그러나 배열의 메서드를 사용하면 화면에 반영은 됨
    vuex는 중앙데이터관리실이라고 생각하면 됨 컴포넌트 연결이 10개 이상이 되버리면 최하위가 최상위 데이터를 가져오기 위해서는
    엄청난 v-bind가 필요한데, 그 문제점을 해결하기 위해 나타난것이 vuex
    
-->
    <div id="root"></div>
    <script src ="dist/app.js"></script>
</body>
</html>
cs


[EventBus.js]

1
2
3
4
5
6
7
// 이벤트를 중앙에 관리한다고 생각하면 됨
// 루트컴포넌트안에서 모든 데이터 처리를 할 수 있다는 것이 장점으로 루트 컴포넌트안에 다 몰아 넣어서 자식 컴포넌트애들한테 필요한걸 줌
// 단점은 루트컴포넌트안에 엄청 길어진다는 단점이 존재
 
import Vue from 'vue';
 
export default new Vue();
cs


[TicTacToe.vue]

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
<template>
    <div>
        <div>{{turn}}님의 턴입니다.</div>
        <table-component :table-data="tableData"></table-component>
        <div v-if="winner">{{winner}}의 승리!</div>
    </div>
</template>
 
<script>
import Vue from 'vue';
import TableComponent from './TableComponent';
import EventBus from './EventBus';
 
export default {
components: {
    TableComponent,
},
 data(){
     return{
         tableData: [
             [''''''],
             [''''''],
             [''''''],
         ],
         turn: 'O',
         winner: '',
     };
 },
 methods: {
     onChangData(){
         // this.tableData[1][0] = 'X'; 작동하지 않음
         //Vue.set(this.tableData[1], 0, 'X'); -> 작동하는 방법 1
         this.$set(this.tableData[1], 0'X'); // Vue.set과 동일
     },
     onClickTd(rowIndex, cellIndex) {
        console.log(rowIndex, cellIndex);
        this.$set(this.tableData[rowIndex], cellIndex, this.turn);
        let win = false;
        if (this.tableData[rowIndex][0=== this.turn && this.tableData[rowIndex][1=== this.turn && this.tableData[rowIndex][2=== this.turn) {
          win = true;
        }
        if (this.tableData[0][cellIndex] === this.turn && this.tableData[1][cellIndex] === this.turn && this.tableData[2][cellIndex] === this.turn) {
          win = true;
        }
        if (this.tableData[0][0=== this.turn && this.tableData[1][1=== this.turn && this.tableData[2][2=== this.turn) {
          win = true;
        }
        if (this.tableData[0][2=== this.turn && this.tableData[1][1=== this.turn && this.tableData[2][0=== this.turn) {
          win = true;
        }
        if (win) { // 이긴 경우: 3줄 달성
          this.winner = this.turn;
          this.turn = 'O';
          this.tableData = [[''''''], [''''''], ['''''']];
        } else { // 무승부
          let all = true// all이 true면 무승부라는 뜻
          this.tableData.forEach((row) => { // 무승부 검사
            row.forEach((cell) => {
              if (!cell) {
                all = false;
              }
            });
          });
          if (all) { // 무승부
            this.winner = '';
            this.turn = 'O';
            this.tableData = [[''''''], [''''''], ['''''']];
          } else {
            this.turn = this.turn === 'O' ? 'X' : 'O';
          }
        }
      },
    },
 created(){
     EventBus.$on('clickTd', this.onClickTd) // 사용자 정의 이벤트를 정의할 수 있다.
 }
}
</script>
 
<style> 
    table{
        border-collapse: collapse;
    }
    td{
        border: 1px solid black;
        width: 40px;
        height: 40px;
        text-align: center;
    }
</style>
cs


[TableComponent.vue]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<table>
    <tr-component v-for="(rowData, index) in tableData" :key="index" :row-data="rowData" :row-index="index"></tr-component>
</table>
</template>
 
<script>
import TrComponent from './TrComponent'
export default {
    props: {
        tableData: Array,
    },
    components: {
        TrComponent,
    }
}
</script>
 
<style scoped>
 
</style>
cs


[TrComponent.vue]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
    <tr>
        <td-component v-for="(cellData, index) in rowData" :key="index" :cell-data="cellData" :cell-index="index" :row-index="rowIndex"></td-component>
    </tr>
</template>
 
<script>
import TdComponent from './TdComponent';
export default {
    props:{
        rowData: Array,
        rowIndex: Number,
    },
    data(){
        return{
 
        };
    },
    components:{
        TdComponent,
    }
}
</script>
cs


[TdComponent.vue]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
    <td @click="onClickTd">{{cellData}}</td>
</template>
 
<script>
import EventBus from './EventBus';
export default {
  
    props: {
        cellData: String,
        rowIndex: Number,
        cellIndex: Number,
    },
    methods: {
      onClickTd() {
        if(this.cellData) return;
 
        EventBus.$emit('clickTd', this.rowIndex, this.cellIndex);
      }
    }
};
</script>
cs


깃허브 주소 : https://github.com/ksy90101/Vue.js/tree/master/vue-Webgame-inflearn/Chapter07-EventBus


Ex3. 틱택토 - Vuex

[main.js]

1
2
3
4
import Vue from 'vue';
import TicTacToe from './TicTacToe';
 
new Vue(TicTacToe).$mount('#root');
cs


[package.json]

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
{
  "name""tic-tac-toe",
  "version""1.0.0",
  "description""",
  "main""index.js",
  "scripts": {
    "bulid""webpack --watch",
    "dev""webpack-dev-server --hot"
  },
  "author""",
  "license""ISC",
  "dependencies": {
    "vue""^2.6.10",
    "vue-loader""^15.7.2",
    "vue-template-compiler""^2.6.10",
    "vuex""^3.1.2",
    "webpack""^4.41.2",
    "webpack-cli""^3.3.10"
  },
  "devDependencies": {
    "css-loader""^3.2.0",
    "vue-style-loader""^4.1.2",
    "webpack-dev-server""^3.9.0"
  }
}
 
cs



[webpack.config.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
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const path = require('path'); // 경로 불러오는 것
 
module.exports = { // 노드에 모듈을 만듬, 이안에 웹팩 모든 설정을 넣으면 됨.
    mode:'development'// 배포할건지 개발한건지
    devtool: 'eval'// 개발할때는 eval, 배포할때는 hid,den-source-map
    resolve: {
        extensions: ['.js''.vue'], // 이걸 하면 import할때 확장자를 넣어줄 필요가 없다.
    },
    entry: { // 스크립트를 모인것 중에서 대표적인 스크립트
        app : path.join(__dirname, 'main.js'), // app은 하나로 합친 스크립트 이름
    },
    module: { // 웹팩의 핵심 // entry로 처리하다가 이상한거 나오면 loader를 실행
        rules: [{ // 합칠때 어떻게 합칠지를 설정해주는 것
            test: /\.vue$/// .vue로 끝나는 파일은 vue-loader를 사용하겠다, 정규표현식
            loader: 'vue-loader'// npm i vue-loader
        },{
            test: /\.css$/
            use: [ // loader와 user는 똑같은 기능
                'vue-style-loader',
                'css-loader',
            ]
        }],
    },
    plugins: [
        new VueLoaderPlugin(),
    ],
    output: {
        filename: '[name].js'// 출력할 파일 이름(최종결과)
        path: path.join(__dirname, 'dist'), // 폴더 경로
        publicPath: '/dist'// webpack-dev-server 세팅시 필요
    },
};
 
// entry, module, plugins, output이 주 설정 나머지는 부가적인 설정
cs



[TicTacToe.html]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>틱택토</title>
</head>
<body>
<!-- 2차원배열을 이해하기가 가장 좋은 예제가 틱택토
    엑셀이나 DB같은 것이 2차원배열을 이용하는데, 2차원배열을 잘 다루는것이 중요하다
    Vue에서 배열을 인덱스로 값을 변경하면 보여지는 화면에서는 반영이 되지 않음.
    그러나 배열의 메서드를 사용하면 화면에 반영은 됨
    vuex는 중앙데이터관리실이라고 생각하면 됨 컴포넌트 연결이 10개 이상이 되버리면 최하위가 최상위 데이터를 가져오기 위해서는
    엄청난 v-bind가 필요한데, 그 문제점을 해결하기 위해 나타난것이 vuex
    
-->
    <div id="root"></div>
    <script src ="dist/app.js"></script>
</body>
</html>
cs


[store.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
// vuex는 store를 여러개 만들어도 됨
// 여기서 데이터를 중앙관리할수 있도록 도와줌
// export vs export default의 차이는 export default는 import할때 이름을 마음대로 지정할수 있고 또 마음대로 불러 올수 있는데
// export는 중괄호를 묶어서 가져와야 함 그리고 여러개 부를수도 있음
import Vuex from 'vuex';
import Vue from 'vue';
 
Vue.use(Vuex); // vue와 vuex를 연결해줘야 함
 
export const SET_WINNER = 'SET_WINNER'// 뮤테이션을 변수로 빼는것, 이렇게 많이 사용
export const CLICK_CELL = 'CLICK_CELL'// import { SET_WINNER, CLICK_CELL} from './store';
export const CHANGE_TURN = 'CHANGE_TURN';
export const RESET_GAME = 'RESET_GAME';
export const NO_WINNER = 'NO_WINNER';
 
export default new Vuex.Store({ // import store from './store';
    state:{
        tableData: [
            [''''''],
            [''''''],
            [''''''],
        ],
        turn: 'O',
        winner: '',
    },// vue의 data와 비슷
    getters: {
        turnMessage(state){
            return state.turn + '님이 승리하셨습니다.';
        },
    }, // vue의 computed와 비슷
    mutations: {
       [SET_WINNER](state, winner){ // 뮤테이션은 대문자로 적는것이 규칙
            state.winner = winner;
        },
        [CLICK_CELL](state, { row, cell }){ // data -> row, cell이 들어있음
            Vue.set(state.tableData[row] ,cell ,state.turn);
        },
        [CHANGE_TURN](state){
            state.turn = state.turn === 'O' ? 'X' : 'O';
        },
        [RESET_GAME](state){
            state.turn = 'O';
            state.tableData = [
                ['''',' '],
                ['''',' '],
                ['''',' '],
            ];
        },
        [NO_WINNER](state){
            state.winner = '';
        }
    }, // state를 수정할때 사용, 동기적으로
    actions: {
 
    }, // 비동기를 사용할때 또는 여러 뮤테이션을 연달아 실행할 때
});
cs


[TicTacToe.vue]

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
<template>
  <div>
    <div>{{turn}}님의 턴입니다.</div>
    <table-component> <!-- 다른 컴포넌트에 태그를 보내는 곳(slot) -->
      <tr v-for="(rowData, rowIndex) in tableData" :key="rowIndex">
        <td @click="onClickTd(rowIndex, cellIndex)" v-for="(cellData, cellIndex) in rowData" :key="cellIndex">{{cellData}}</td>
      </tr>
    </table-component>
    <div v-if="winner">{{winner}}님의 승리!</div>
  </div>
</template>
 
<script>
  // [0, 1, 2, 3, 4 ,12, 7, 8, 9, 10, 13, 156] 
  //  0  1  2  3  4  5    6  7  8  9  10, 11,  12
  // 배열이 값이 수정되거나 값이 계속 늘어날 경우 :key로 index를 사용하는 것이 좋다
  // 랜더링을 할때 화면을 다시 그려야 할지 말지를 결정하는 것이 key(즉, 인덱스가 추가되면 그 인덱스에 대해서만 화면에 새로 그려짐)
  // 근데, 중간값이 삭제가 되면 뒤의 값들에 인덱스가 삭제되기 때문에 삭제 된 후부터 다시 그려버린다(이럴때는 단점이 될 것이다)
 
  import { mapState } from 'vuex';
  import store, { CHANGE_TURN, CLICK_CELL, NO_WINNER, RESET_GAME, SET_WINNER } from './store';
  import TableComponent from './TableComponent';
  export default {
    store,
    components: {
      TableComponent,
    },
    data() {
      return {
        data: 1,
      }
    },
    computed: {
      ...mapState(['winner''turn''tableData']),
      // winner() {
      //   return this.$store.state.winner;
      // },
      // turn() {
      //   return this.$store.state.turn;
      // },
    },
    methods: {
      onClickTd(rowIndex, cellIndex) {
        console.log(this.cellData);
        if (this.tableData[rowIndex][cellIndex]) return;
        this.$store.commit(CLICK_CELL, { row: rowIndex, cell: cellIndex });
        let win = false;
        if (this.tableData[rowIndex][0=== this.turn && this.tableData[rowIndex][1=== this.turn && this.tableData[rowIndex][2=== this.turn) {
          win = true;
        }
        if (this.tableData[0][cellIndex] === this.turn && this.tableData[1][cellIndex] === this.turn && this.tableData[2][cellIndex] === this.turn) {
          win = true;
        }
        if (this.tableData[0][0=== this.turn && this.tableData[1][1=== this.turn && this.tableData[2][2=== this.turn) {
          win = true;
        }
        if (this.tableData[0][2=== this.turn && this.tableData[1][1=== this.turn && this.tableData[2][0=== this.turn) {
          win = true;
        }
        if (win) { // 이긴 경우: 3줄 달성
          this.$store.commit(SET_WINNER, this.turn);
          this.$store.commit(RESET_GAME);
        } else { // 무승부
          let all = true// all이 true면 무승부라는 뜻
          this.tableData.forEach((row) => { // 무승부 검사
            row.forEach((cell) => {
              if (!cell) {
                all = false;
              }
            });
          });
          if (all) { // 무승부
            this.$store.commit(NO_WINNER);
            this.$store.commit(RESET_GAME);
          } else {
            this.$store.commit(CHANGE_TURN);
          }
        }
      }
    }
  };
</script>
 
<style>
  table {
    border-collapse: collapse;
  }
  td {
    border: 1px solid black;
    width: 40px;
    height: 40px;
    text-align: center;
  }
</style>
cs



[TableComponet.vue]


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
<template>
<table>
    <slot></slot> <!-- 부모가 보낸 태그를 받는 법 -->
</table>
</template>
 
<script>
import TrComponent from './TrComponent'
export default {
    components: {
        TrComponent,
    },
    computed: {
        tableData() {
            return this.$store.state.tableData;
        },
        // turnMessage(){ // getters의 값을 가져오는 것
        //     return this.$stroe.getters.turnMessage;
        // }
    },
}
</script>
 
<style scoped>
 
</style>
cs



깃허브 주소 : https://github.com/ksy90101/Vue.js/tree/master/vue-Webgame-inflearn/Chapter07-Vuex





출처

https://www.inflearn.com/course/web-game-vue/dashboard

Comments