<!DOCTYPE html>
<html lang="en" class="h-100">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="icon" type="image/png" href="icon.png" sizes="32x32">
<link rel="icon" type="image/png" href="icon.png" sizes="64x64">
<link rel="icon" type="image/png" href="icon.png" sizes="96x96">
<title>Codenames</title>
<link rel="stylesheet" type="text/css" href="./css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="./css/style.css" />
<script src="./js/jquery-3.5.0.min.js"></script>
<script src="./js/bootstrap.min.js"></script>
<script src="./js/vue.js"></script>
</head>
<body class="h-100">
<nav class="navbar navbar-light bg-light">
<a class="navbar-brand" href="#">
<img src="icon.png" width="30" height="30" class="d-inline-block align-top" alt="">
Codenames
</a>
</nav>
<div class="container" id="app">
<div class="d-flex flex-row justify-content-between">
<div>
<span :class="getPlayerColor('red')">
{{ openRedCards }}
</span>
-
<span :class="getPlayerColor('blue')">
{{ openBlueCards }}
</span>
</div>
<div :class="getPlayerColor()">
<h2 v-if="gameOver">
{{ playerName }} won!
</h2>
<span v-else>
{{ playerName }}'s turn
</span>
</div>
</div>
<div class="card-deck m-3" v-for="r, i in size.rows">
<div class="card py-3 border" v-for="c, j in size.cols" :data-id="`${i}, ${j}`" :class="getClasses(i, j)" @click="touchCard(i, j)">
<div class="card-body text-center">
{{ words[i*size.cols + j].value }}
<div class="rounded-circle marker-icon" :class="getClasses(i, j, true)" v-show="isLeader && !wordRevealed(i, j)"></div>
</div>
</div>
</div>
<div class="d-flex flex-row justify-content-between">
<button type="button" class="btn btn-primary" @click="isLeader = !isLeader">
Toggle Leadership
</button>
<button type="button" class="btn btn-primary" @click="nextPlayer()" :disabled="gameOver">
End {{ playerName }}'s turn
</button>
</div>
</div>
<script type="application/javascript">
var app = new Vue({
el: '#app',
mounted() {
try {
this.socket = new WebSocket(`ws://localhost:8000/codenames/api/event.php`);
this.socket.onopen = _ => {
console.log("successfully connected!");
};
this.socket.onmessage = msg => {
let res = JSON.parse(msg.data);
if(res.type === 'init') {
this.size.rows = res.data.rows;
this.size.cols = res.data.cols;
this.words.length = 0;
this.words = res.data.words;
} else if(res.type === 'next_player') {
this.redsTurn = res.data.player === 'red';
} else if(res.type === 'reveal') {
let word = this.getWord(res.data.row, res.data.col);
word.revealed = true;
}
};
this.socket.onclose = msg => {
console.log("Disconnected :(");
};
} catch(e) {
console.log(e);
}
},
methods: {
nextPlayer() {
if(this.gameOver) return;
this.socket.send(`{
"type": "turnend",
"who": ${this.playerName}
}`);
},
touchCard(row, col) {
if(this.gameOver) return;
if(this.isLeader) return;
if(this.getWord(row, col).revealed) return;
this.socket.send(`{
"type": "cardplayed",
"row_index": ${row},
"col_index": ${col},
"who": "${this.playerName}"
}`);
},
getPlayerColor(team) {
let red = team ? team === 'red' : this.redsTurn;
return {
'text-danger': red,
'text-primary': !red
};
},
getClasses(row, col, bgOnly) {
const word = this.getWord(row, col);
if(!!bgOnly) {
return {
'bg-danger': word.red,
'bg-primary': word.blue,
'bg-dark': word.mr_x,
'bg-warning': !word.red && !word.blue && !word.mr_x,
}
}
if(!word.revealed && !this.isLeader) {
return {
'border-secondary': true,
'clickable': true,
};
}
if(word.revealed) {
return {
'border-danger': word.red,
'border-primary': word.blue,
'border-dark': word.mr_x,
'border-warning': !word.red && !word.blue && !word.mr_x,
'bg-danger': word.red,
'bg-primary': word.blue,
'bg-dark': word.mr_x,
'bg-warning': !word.red && !word.blue && !word.mr_x,
'text-white': word.red || word.blue || word.mr_x,
'font-weight-bold': true
}
}
return {
'border-danger': word.red,
'border-primary': word.blue,
'border-dark': word.mr_x,
'border-warning': !word.red && !word.blue && !word.mr_x
};
},
getWord(row, col) {
return this.words[row * this.size.cols + col];
},
wordRevealed(row, col) {
return this.getWord(row, col).revealed;
}
},
data: {
socket: null,
isLeader: false,
redsTurn: true,
words: [],
size: {
rows: 0,
cols: 0,
}
},
computed: {
playerName() {
return this.redsTurn ? 'red' : 'blue';
},
openRedCards() {
return this.words.filter(w => w.red && !w.revealed).length;
},
openBlueCards() {
return this.words.filter(w => w.blue && !w.revealed).length;
},
gameOver() {
return this.openRedCards == 0 || this.openBlueCards == 0 || this.words.filter(w => w.mr_x && w.revealed).length > 0;
}
}
})
</script>
</body>
</html>