added tictactoc sammple

This commit is contained in:
Shuozhe 2021-06-17 16:24:32 +02:00
parent 5c0b13fd8d
commit 18eb69bd84
6 changed files with 319 additions and 88 deletions

View File

@ -1767,16 +1767,6 @@
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"cacache": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
@ -1803,53 +1793,6 @@
"unique-filename": "^1.1.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -1866,16 +1809,6 @@
"minipass": "^3.1.1"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
},
"terser-webpack-plugin": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz",
@ -1892,18 +1825,6 @@
"terser": "^4.6.12",
"webpack-sources": "^1.4.3"
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.2.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz",
"integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
}
}
}
},
@ -11454,6 +11375,87 @@
}
}
},
"vue-loader-v16": {
"version": "npm:vue-loader@16.2.0",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz",
"integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==",
"dev": true,
"optional": true,
"requires": {
"chalk": "^4.1.0",
"hash-sum": "^2.0.0",
"loader-utils": "^2.0.0"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"optional": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
"dev": true,
"optional": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"optional": true,
"requires": {
"color-name": "~1.1.4"
}
},
"color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"optional": true
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"optional": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"optional": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"optional": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
"vue-style-loader": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz",

View File

@ -1,21 +1,21 @@
<template>
<div id="app">
<Dashboard />
<GameUI />
</div>
</template>
<script>
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import Dashboard from './components/Dashboard.vue'
import GameUI from './components/GameUI.vue'
export default {
export default {
name: 'App',
components: {
Dashboard
GameUI
}
}
}
</script>
<style>

104
clientapp/src/Game.js Normal file
View File

@ -0,0 +1,104 @@
class Game {
constructor() {
this.players = []
this.winningCondition = [
["00", "01", "02"],
["00", "11", "22"],
["00", "10", "20"],
["02", "11", "20"],
["10", "11", "12"],
["02", "12", "22"],
["01", "11", "21"],
["20", "21", "22"]
];
this.currentTurn = "X";
this.drawScore = 0;
this.isGameEnded = false;
this._winningConditions =
this.winningCondition.map(x => x.join(","));
}
changeTurn() {
this.currentTurn = this.currentTurn == this.players[0].sign ? this.players[1].sign : this.players[0].sign;
}
reset() {
this.currentTurn = this.players[0].sign;
this.players.forEach(x => {
x.clicks = [];
})
}
checkWinner(btn) {
if (btn.innerHTML == this.players[0].sign) {
this.players[0].clicks.push(btn.id);
} else {
this.players[1].clicks.push(btn.id);
}
this.players.forEach(x => {
x.sortClicks()
x.setPosition();
})
if (this.players[0].positions.length > 2 || this.players[1].positions.length > 2) {
let player1_combinations = this.getCombination(this.players[0].positions, 3, 0);
console.log(player1_combinations)
let player2_combinations = this.getCombination(this.players[1].positions, 3, 0);
//find p1,p2 common with winning conditions
let player1_common = this._winningConditions.filter(value =>
player1_combinations.includes(value)
);
let player2_common = this._winningConditions.filter(value =>
player2_combinations.includes(value)
);
return this.isGameOver(player1_common, player2_common);
} else {
return false;
}
}
isGameOver(player1_common, player2_common) {
if (player1_common.length < 1 && player2_common.length < 1)
return false;
let gameOver = false;
if (player1_common > player2_common) {
gameOver = true
this.players[0].score += 1;
alert("Player One Won");
} else if (player2_common > player1_common) {
gameOver = true
this.players[1].score += 1;
alert("Player Two Won");
} else if (this.players[0].clicks.length > 4 || this.players[1].clicks.length > 4) {
gameOver = true
this.drawScore += 1;
alert("Draw");
} else {
gameOver = false
}
return gameOver;
}
getCombination(input, len, start) {
const result = new Array(3);
let combinations = new Array();
combine(input, len, start);
function combine(input, len, start) {
if (len === 0) {
combinations.push(result.join(","));
return;
}
for (var i = start; i <= input.length - len; i++) {
result[result.length - len] = input[i];
combine(input, len - 1, i + 1);
}
}
return combinations;
}
}
export default Game;

17
clientapp/src/Player.js Normal file
View File

@ -0,0 +1,17 @@
class Player {
constructor(sign) {
this.sign = sign;
this.clicks = [];
this.score = 0;
this.combinations = [];
this.positions = []
}
setPosition() {
this.positions = this.clicks.join(",").split(",");
}
sortClicks() {
this.clicks.sort();
}
}
export default Player;

View File

@ -38,3 +38,28 @@ h1 {
.mrgnbtm {
margin-top: 20px;
}
.gamebox button {
width: 100px;
height: 100px;
border: 1px solid #000;
display: inline-block;
font-size: 30px;
color: #ff6a00;
}
.gamebox {
margin: 0 auto;
max-width: 300px;
display: grid;
justify-items: center;
align-content: space-around;
grid-template-columns: repeat(3, 1fr);
}
.replay {
border: 1px solid #ddd;
height: 60px;
margin-top: 20px;
padding: 10px;
}

View File

@ -0,0 +1,83 @@
<template>
<div>
<div class="text-center">
<h1>Tic Tac Toe</h1>
<h3 id="turnText">Turn Of :{{this.game.currentTurn}}</h3>
<div class="score" v-if="game.players">
<span>
<u>Scores</u>
</span>
<br />
<span id="p1_score">Player One :{{this.game.players[0].score}}</span>
<span id="p2_score">Player Two :{{this.game.players[1].score}}</span>
<span id="draw_score">Draw :{{this.game.draw_score}}</span>
</div>
<button class="replay" id="restartBtn" @click="resetGame()">Restart</button>
</div>
<div class="gamebox" style="margin-top:20px">
<button id="00" @click="onClick($event)">{{btnText["00"]}}</button>
<button id="01" @click="onClick($event)">{{btnText["01"]}}</button>
<button id="02" @click="onClick($event)">{{btnText["02"]}}</button>
<button id="10" @click="onClick($event)">{{btnText["10"]}}</button>
<button id="11" @click="onClick($event)">{{btnText["11"]}}</button>
<button id="12" @click="onClick($event)">{{btnText["12"]}}</button>
<button id="20" @click="onClick($event)">{{btnText["20"]}}</button>
<button id="21" @click="onClick($event)">{{btnText["21"]}}</button>
<button id="22" @click="onClick($event)">{{btnText["22"]}}</button>
</div>
</div>
</template>
<script>
import Player from "../Player";
import Game from "../Game";
const game = new Game();
game.players.push(new Player("X"));
game.players.push(new Player("O"));
export default {
name: "GameUI",
data() {
return {
game: game,
buttons: [],
btnText: []
};
},
methods: {
onClick($event) {
let btn = $event.target;
//check if filled already
if (this.btnText[btn.id.toString()].length > 0) {
alert("Already filled");
return;
}
//fill X/O
this.btnText[btn.id] = this.game.currentTurn;
this.game.changeTurn();
//check if game won
setTimeout(() => {
if (this.game.checkWinner(btn) == true) {
this.resetGame();
}
}, 100);
},
resetGame() {
//UI and backend reset
this.game.reset();
[...this.buttons].forEach(btn => {
this.btnText[btn.id] = "";
});
}
},
mounted() {
//load all buttons and reset initially
this.$nextTick(() => {
this.buttons = this.$el.querySelectorAll(".gamebox button");
this.resetGame();
});
}
};
</script>