Newer
Older
codenames / index.html
@Vinzenz Rosenkranz Vinzenz Rosenkranz on 13 Apr 2020 8 KB initial code commit
<!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>