<script type="text/x-template" id="markerSelect">
<transition name="modal">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-body">
<slot name="body">
<div class='row'>
<div class="col-xs-2 select_marker"
@click='set("X")'><span>X</span></div>
<div class="col-xs-8 select_title">
SELECT<span style='color:#00B16A'>MARKER</span>
</div>
<div class="col-xs-2 select_marker"
@click='set("O")'>O</div>
</div>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
<div id='app'>
<transition enter-active-class='animated fadeIn'
leave-active-class='animated fadeOut'
mode='out-in'>
<modal v-if='showModal'>
</modal>
<div id='welcome' v-if='!started'>
<div class='titleBlock'>
<h1>tic<span>tac</span>toe</h1>
<div class='row mode'
@click='init("player")'
@mouseover='tick.play()'>
[versus<span>player</span>]
</div>
<div class='row mode'
@click='showModal = true'
@mouseover='tick.play()'>
[versus<span>computer</span>]
</div>
</div>
</div>
<div id='main' v-if='started && !game.over'>
<table id='board'>
<tr v-for='row in board'>
<td v-for='box in row'>
<div class='square'
@click='router(box)'
:class='{"square active": box.bg}'>
{{box.val}}
</div>
</td>
</tr>
</table>
</div>
<div id='reset' v-if='started && game.over'>
<div class='titleBlock'>
<h1 v-if='!game.draw'><span>{{game.winner}}</span> wins!</h1>
<h1 v-if='game.draw'><span>draw</span></h1>
<h2>play again?</h2>
<div class='row mode'
@click='init("player")'
@mouseover='tick.play()'>
[versus<span>player</span>]
</div>
<div class='row mode'
@click='showModal = true'
@mouseover='tick.play()'>
[versus<span>computer</span>]
</div>
</div>
</div>
<!--//-->
</transition>
</div>
@import 'https://fonts.googleapis.com/css?family=Oswald';
$primary: #6C7A89;
$secondary: #00B16A;
$main: 'Oswald', sans-serif;
* {
user-select: none
}
.center {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%,-50%);
}
span {
color: $secondary;
}
//<----Modal---->//
.modal-mask {
position: absolute;
z-index: 9998;
top: 0; left: 0;
width: 100%;
height: 100%;
background: rgba($primary,.5);
transition: opacity .3s ease;
}
.modal-container {
@extend .center;
width: 80%;
background: white;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
transition: all .3s ease;
}
.modal-body {
font-family: $main;
color: black;
font-size: 8vw;
margin: 0;
width: 100%;
text-align: center;
background: $primary;
color: white;
}
.select_marker {
cursor: pointer;
}
//<----/Modal---->//
#welcome {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background: $primary;
}
.titleBlock {
@extend .center;
color: white;
font-family: $main;
text-align: center;
text-transform: uppercase;
letter-spacing: 4px;
h1 {
font-size: 100px;
width: 100%;
}
.mode {
font-size: 50px;
cursor: pointer;
}
}
#board {
@extend .center;
border-collapse: separate;
border-spacing: 2px;
}
td {
vertical-align: middle;
}
.square {
width: 15vw;
height: 15vw;
background: $primary;
cursor: pointer;
font-family: $main;
font-size: 15vw;
line-height: 15vw;
text-align: center;
color: white;
&:hover {
opacity: .8;
}
&.active {
background: $secondary;
}
}
#reset {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background: $primary;
}
View Compiled
/*
AI is half smart/half stupid by design. Originally I used the minimax algorithm, but that made for a pretty boring game, always ending in either a tie or a loss. So I made my own AI algorithm. The decision tree looks like this:
1. Winning move available? --->
2. Will lose next turn without blocking move? --->
3. Choose a move at random
*/
const vm = new Vue({
el: '#app',
components: {
modal: {
template: '#markerSelect',
methods: {
set(marker){
vm.markers.player = marker;
vm.markers.computer = marker == 'X' ? 'O' : 'X';
vm.init('computer');
}
}
}
},
data: {
started: false,
showModal: false,
mode: '',
run: false,
turn: 'X',
markers: {
player: '',
computer: ''
},
game: {
over: false,
winner: '',
draw: false,
},
board: [
[{val:'',bg:''}, {val:'',bg:''}, {val:'',bg:''}],
[{val:'',bg:''}, {val:'',bg:''}, {val:'',bg:''}],
[{val:'',bg:''}, {val:'',bg:''}, {val:'',bg:''}]
],
windex: [
[[0,0], [0,1], [0,2]],
[[1,0], [1,1], [1,2]],
[[2,0], [2,1], [2,2]],
[[0,0], [1,0], [2,0]],
[[0,1], [1,1], [2,1]],
[[0,2], [1,2], [2,2]],
[[0,0], [1,1], [2,2]],
[[0,2], [1,1], [2,0]]
],
tick: new Audio('https://dl.getdropbox.com/s/kgqqnei0yhv3r8n/219069__annabloom__click1.wav')
},//DATA
computed: {
arr() {
return this.board.map(x => x.map( y => y.val));
},
winArr() {
return this.windex.map(x => x.map( y => this.board[y[0]][y[1]].val));
},
check(){
const board = this.arr;
const windex = this.winArr;
const draw = this.arr.every(x => x.every(y => y != ''));
const checkWin = windex.forEach((x,ind) => {
const vector = this.windex[ind];
let over = false;
if(x.every(y => y == 'X')){
this.game.winner = 'X';
over = true;
}
if(x.every(y => y == 'O')){
this.game.winner = 'O';
over = true;
}
if(draw && this.game.winner == ''){
this.game.draw = true;
over = true;
}
if(over){
this.run = false;
this.setWin(vector);
}
})
},
AIWinMove(){
let move = '';
const board = this.board;
const combos = this.winArr;
const AI = this.markers.computer;
const AIStr = AI.repeat(3);
combos.forEach((x,ind) => {
if(x.join('') + AI === AIStr){
let row = combos[ind];
let col = row.indexOf('');
let index = this.windex[ind][col];
let box = board[index[0]][index[1]];
move = box;
}
});
return move;
},
AIBlockMove(){
let move = '';
const board = this.board;
const combos = this.winArr;
const player = this.markers.player;
const playStr = player.repeat(3);
combos.forEach((x,ind) => {
if(x.join('') + player === playStr){
let row = combos[ind];
let col = row.indexOf('');
let index = this.windex[ind][col];
let box = board[index[0]][index[1]];
move = box;
}
});
return move;
},
randomMove(){
const board = this.board;
let counter = 0;
const recurse = () => {
if(counter == 8) return;
counter++;
let row = Math.floor(Math.random() * 2);
let col = Math.floor(Math.random() * 2);
if(board[row][col].val == ''){
return board[row][col];
} else
return recurse();
};
return recurse();
},
AI(){
const win = this.AIWinMove;
const block = this.AIBlockMove;
const random = this.randomMove;
if(win != '')
return win
else if(win == '' && block != '')
return block
else
return random
}
},//COMPUTED
methods: {
init(mode){
this.reset();
this.mode = mode;
this.showModal = false;
if(mode == 'computer')
this.turn = this.markers.player;
this.start();
},
start(){
this.run = true;
this.started = true;
},
reset(){
this.mode = '';
this.run = false;
this.game.over = false;
this.game.winner = '';
this.game.draw = false;
this.board.forEach(x => {
x.map(y => {y.val = ''; y.bg = ''})
})
},
router(box){
if(this.mode == 'player')
this.mark(box);
if(this.mode == 'computer' && box.val == ''){
this.mark(box);
setTimeout(() => {
this.mark(this.AI);
}, 300)
}
},
mark(box){
const run = this.run;
if(box.val == '' && run){
box.val = this.turn;
this.turn = this.turn == 'X' ? 'O' : 'X';
this.check;
}
},
setWin(vector){
if(!this.game.draw){
vector.forEach( x => {
this.board[x[0]][x[1]].bg = 'active'
})
}
setTimeout(() => {
this.game.over = true;
}, 500)
}
}//METHODS
})