Member Avatar

I am in a pickle in trying to implement a turn based game where two players join the game and is assigned a role randomly where they alternate roles after each successive round, the problem with that for me personally lies in trying to figure out how to make it turn based when their roles are alternating each round. I am using nodejs (new to it) and socket.io.

The dealer moves first then the client selects their number is the game flow.

My code is as follows:

main.css

body {
    font-family: "Helvetica Neue", sans-serif;
    margin: 0;
    padding: 0;
      }

  input {
    outline: none;
    font-size: 1.0rem;
    padding: 0 5px;
  }

  button {
    text-transform: uppercase;
    font-size: 0.8rem;
    padding: 3px 15px;
    color: white;
    line-height: 25px;
    background-color: #00bcd4;
    border: none;
    border-radius: 2px;
    cursor: pointer;
    box-shadow: rgba(0, 0, 0, 0.12) 0 1px 6px, rgba(0, 0, 0, 0.12) 0 1px 4px;
    transition: all 150ms ease-in;
    outline: none;
  }

  button:hover {
      background-color: #79d7e4;
  }

  button.turn {
    color: rgb(255, 64, 129);
    background-color: white;
    box-shadow:none;
  }

  button.turn:hover {
    background-color: #eaeaea;
  }

  .findthequeen-wrapper {
    background-color: rgb(255, 255, 255);
    width: 400px;
    height: 400px;
    max-height: 500px;
    margin: 100px auto;
    justify-content: center;
    flex-direction: column;
    border: 1px solid lightgrey;
  }

  #events {
    flex-grow: 1;
    list-style: none;
    margin: 0;
    padding: 0;
    border-bottom: 1px solid lightgrey;
    overflow-y: scroll;
  }

  #events li {
    padding: 10px 10px 10px 10px;
  }

  .chat-wrapper {
    padding: 10px;
    height: 20px;
    max-height: 500px;
  }

  .chat-wrapper form {
    display: flex;
  }

  .chat-wrapper form input {
    font-size: 1.0rem;
    margin: 0 10px 0 0;
    padding: 0 5px;
    flex-grow: 1;
  }

  .chat-wrapper form button {
    width: 80px;
  }

  .button-wrapper {
    display: flex;
    padding: 10px 50px;
    justify-content: space-evenly;
  }

  .connect-wrapper {
    display: flex;
    padding: 10px 50px;
    justify-content: space-between;
  }

  .login-wrapper {
    display: flex;
    padding: 5px 40px;
    justify-content: center;
    width: min-content;

  }

  /* Login page style */
  .login-form {
    width: 300px;
    margin: 0 auto;
    font-family: Tahoma, Geneva, sans-serif;
  }
  .login-form h1 {
    text-align: center;
    color: #4d4d4d;
    font-size: 24px;
    padding: 20px 0 20px 0;
  }
  .login-form input[type="password"],
  .login-form input[type="text"] {
    width: 100%;
    padding: 15px;
    border: 1px solid #dddddd;
    margin-bottom: 15px;
    box-sizing:border-box;
  }
  .login-form input[type="submit"] {
    width: 100%;
    padding: 15px;
    background-color: #535b63;
    border: 0;
    box-sizing: border-box;
    cursor: pointer;
    font-weight: bold;
    color: #ffffff;
  }

index.html

<html lang="en">
<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>Find the Queen Multiplayer Game</title>
    <link rel="stylesheet" href="styles/main.css">
</head>
<body>
    <div id="loginDiv" class="login-form">
        <h1>Login Form</h1>
            <input id="loginDiv-username" type="text" name="username" placeholder="Username" required>
            <input id="loginDiv-password" type="password" name="password" placeholder="Password" required>
            <input id="loginDiv-login" type="submit" value="Login">
    </div>
    <div class="findthequeen-wrapper" id="gameDiv" style="display: none;">
        <ul id="events"></ul>
        <div id="scoreBoard"></div>
        <div class="controls">
            <div class="chat-wrapper">
                <form id="chat-form">
                    <input id="chat" autocomplete="off" title="chat"/>
                    <button id="say">Say</button>
                </form>
            </div>

            <div class="button-wrapper">
                <button id="1" class="turn">ONE</button>
                <button id="2" class="turn">TWO</button>
                <button id="3" class="turn">THREE</button>
            </div>
        </div>
    </div>
    <script src="/socket.io/socket.io.js"></script>
    <script src="src/client.js"></script>
</body>
</html>

client.js

// Writes the text in the chat box
const writeEvent = (text) => {
    // <ul> element 
    const parent = document.querySelector('#events');

    // <li> element
    const el = document.createElement('li');
    el.innerHTML = text;

    parent.appendChild(el);
    parent.scrollTop = parent.scrollHeight - parent.clientHeight;
};

// Takes keyboard input from user and broadcasts it to server
const onFormSubmitted  = (e) => {  
    e.preventDefault();

    const input = document.querySelector('#chat');
    const text = input.value;
    input.value = '';

    socket.emit('message', text);  
};

// Accepts the selected button input from user 
const addButtonListeners = () => {
    ['1', '2', '3'].forEach((id) => {
        const button = document.getElementById(id);
        button.addEventListener('click', () => {
            socket.emit('turn', id);
        });
    });
};

writeEvent('Welcome to Find the Queen!'); 

const socket = io('http://localhost:7621');
socket.on('message', writeEvent);

// login 
const loginDiv = document.getElementById('loginDiv');
const loginDivUsername = document.getElementById('loginDiv-username');
const loginDivPassword = document.getElementById('loginDiv-password');
const loginDivLogin = document.getElementById('loginDiv-login');

loginDivLogin.onclick = function() {
    socket.emit('login',{username: loginDivUsername.value, password: loginDivPassword.value})
}

socket.on('loginResponse', function(data) {
    if (data.success) {
        loginDiv.style.display = 'none';
        gameDiv.style.display = 'flex';
    } else {
        alert("Login unsuccessful");
    }
})

socket.on('dealerTurn', () => {
    writeEvent('Dealer Turn.');
    ['1', '2', '3'].forEach((id) => {
        document.getElementById(id).disabled = true;
    });
})

socket.on('spotterTurn', () => {
    ['1', '2', '3'].forEach((id) => {
        document.getElementById(id).disabled = false;
    });
})

document.querySelector('#chat-form').addEventListener('submit', onFormSubmitted)

addButtonListeners()

server.js

const http = require('http');
const express = require('express');
const socketio = require('socket.io');

const FtqGame = require('./FtqGame');

const app = express();

const clientPath = `${__dirname}/../client`;
console.log(`Serving static from ${clientPath}`);

app.use(express.static(clientPath));

const server = http.createServer(app);

const io = socketio(server);

let waitingPlayer = null;
let playerList = {};
let socketList = {};

let Player = function(id) {
    const self = {
        id: id
    }
    return self;
}

// List of users and corresponding passwords to access game
const USERS = {
    // username : password
    "one" : "12",
    "two"   : "12",
}

// Validates username with corresponding password to the input data
const isValidPassword = function(data) {
    return USERS[data.username] === data.password;
}

io.on('connection', (socket) => {

    socket.on('login', function(data) {
        if (isValidPassword(data)) {
            socket.emit('loginResponse', {success: true});

            socket.id = Math.random();
            socketList[socket.id] = socket;
            console.log('socket: ' + socket.id + ' connected.');
            let player = Player(socket.id);
            playerList[socket.id] = player;

            if (waitingPlayer) {
                // start a game
                socket.emit('message', 'You are Player Two.');
                new FtqGame(waitingPlayer, socket);
                waitingPlayer = null;
            } else {
                waitingPlayer = socket;
                waitingPlayer.emit('message', 'Waiting for an opponent.....');
                waitingPlayer.emit('message', 'You are Player One.');
            }

            socket.on('message', (text) => {
                io.emit('message', text);
            });

            socket.on('disconnect', () => {
                console.log('socket ID: ' + socket.id + ' disconnected.');
                delete socketList[socket.id];
                delete playerList[socket.id];
            });

        } else {
            socket.emit('loginResponse', {success: false});
        }
    });
});

server.on('error', (err) => {
    console.error('Server error:', err);
});

server.listen(7621, () => {
    console.log('Started on port 7621');
});

FtqGame.js (handles game logic )

class FtqGame {

    constructor(playerOne, playerTwo) {
        this._players = [playerOne, playerTwo];
        this._turns = [null, null];
        this._roles = ['Dealer', 'Spotter'];
        this._rounds = 1;
        this._scores = [0, 0];
        this._gameContinue = true;
        this._dealerTurn = false; 

        // randomizing the roles. 
        this._roles.sort(function(a, b) { return 0.5 - Math.random()});
        this._roleAssign = this._roles;
        this._sendToPlayers('Player ONE is assigned as the ' + this._roleAssign[0] + '. ' +
                            'Player TWO is assigned as the ' + this._roleAssign[1] + '.');

        this._sendToPlayers('Game Starts!');

        if (this._roleAssign[0] == 'Dealer') {
            this._players[1].emit('dealerTurn');
            this._sendToPlayer(0, 'Please choose where to hide the Queen.'); 
            if (this._turns[0] && !this._turns[1]) {
                this._players[1].emit('spotterTurn')
            }
        } else if (this._roleAssign[1] == 'Dealer') {
            this._players[0].emit('dealerTurn');
            this._sendToPlayer(1, 'Please choose where to hide the Queen.');
            if (this._turns[1] && !this._turns[0]) {
                this._players[1].emit('spotterTurn')
            }
        }

        // this._players.forEach((player, idx) => {
        //     player.emit('resetTurn');
        // });

        this._players.forEach((player, idx) => {
            player.on('turn', (turn) => {
                this._onTurn(idx, turn);
            });
        });
    }

    _sendToPlayer(playerIndex, msg) {
        this._players[playerIndex].emit('message', msg);
    }

    _sendToPlayers(msg) {
        this._players.forEach((player) => {
            player.emit('message', msg);
        });
    }

    // Main run function for each player.
    _onTurn(playerIndex, turn) {
        this._turns[playerIndex] = turn;
        this._sendToPlayer(playerIndex, `You selected ${turn}`);

        this._checkGameOver();
    }

    _checkGameOver() {
        const turns = this._turns;
        const gameContinue = this._gameContinue;
        const player = this._players;

        // if (this._roleAssign[0] == 'Dealer') {
        //     this._players[1].emit('dealerTurn');
        //     this._sendToPlayer(0, 'Please choose where to hide the Queen.'); 
        //     if (this._turns[0] && !this.turns[1]) {
        //         this._players[1].emit('spotterTurn')
        //     }
        // } else if (this._roleAssign[1] == 'Dealer') {
        //     this._players[0].emit('dealerTurn');
        //     this._sendToPlayer(1, 'Please choose where to hide the Queen.');
        //     if (this._turns[1]) {
        //         this._players[1].emit('spotterTurn')
        //     }
        // }

        if (turns[0] && turns[1] && gameContinue) {
            this._sendToPlayers('Result: ' + turns.join(' : '));
            this._getGameResult();
            this._turns = [null, null];
            this._rounds += 1;
            this._sendToPlayers('Next! Round ' + this._rounds);
            // Swap roles after each turn.
            this._switchRoles();
            this._sendToPlayers('Player ONE is assigned as the ' + this._roleAssign[0] + '. ' +
                                'Player TWO is assigned as the ' + this._roleAssign[1] + '.');
        }

        if (this._rounds >= 6) {
            this._sendToPlayers('Game Finished.');
            this._gameContinue = false;
            if (this._scores[0] > this._scores[1]) {
                this._sendToPlayer(0, 'Winner!');
                this._sendToPlayer(1, 'Loser!');
                this._sendToPlayers('Thanks for playing.');

            } else {
                this._sendToPlayer(1, 'Winner!');
                this._sendToPlayer(0, 'Loser!');
                this._sendToPlayers('Thanks for playing.');
            }
         }
    }

    _disconnectGame() {
        this._players.forEach((player, idx) => {
            player.emit('disconnect');
        });
        this._scores = [0, 0];
        this._rounds = 0;
    }

    _getGameResult() {

        const playerOneChoice = this._decodeTurn(this._turns[0]);
        const playerTwoChoice = this._decodeTurn(this._turns[1]);
        const playerO = this._roleAssign[0];
        const playerT = this._roleAssign[1];
        const scores = this._scores;

        if (playerO == 'Dealer') {
            if (playerOneChoice == playerTwoChoice) {
                // Dealer loses
                this._sendWinMessage(this._players[1], this._players[0]);
                scores[1] += 1;
            } else if (playerOneChoice != playerTwoChoice) {
                // Dealer wins
                this._sendWinMessage(this._players[0], this._players[1]);
                scores[0] += 1;
            }
        } else if (playerO == 'Spotter') {
            if (playerOneChoice == playerTwoChoice) {
                // Spotter wins
                this._sendWinMessage(this._players[0], this._players[1]);
                scores[0] += 1;
            } else if (playerOneChoice != playerTwoChoice) {
                // Spotter loses
                this._sendWinMessage(this._players[1], this._players[0]);
                scores[1] += 1;
            }
        }

        this._sendToPlayers('Score: ' + scores.join(' : '));
    }

    _switchRoles() {
        if (this._roleAssign[0] == 'Dealer') { 
            this._roleAssign[0] = 'Spotter';
            this._roleAssign[1] = 'Dealer';
        } else if (this._roleAssign[0] == 'Spotter') {
            this._roleAssign[0] = 'Dealer';
            this._roleAssign[1] = 'Spotter';
        }
    }

    _sendWinMessage(winner, loser) {
        winner.emit('message', 'You won!');
        loser.emit('message', 'You lost :(');
    }

    _decodeTurn(turn) {

        switch (turn) {
            case '1':
                return 1;
            case '2':
                return 2;
            case '3':
                return 3;
            default:
                throw new Error(`Could not decode move ${turn}`);
        }
    }
}

module.exports = FtqGame;

One method I am currently using is trying to disable the client's button if they are not the dealer to make the dealer move first, however, it is proving to be a disaster. Been at this for quite some time trying to figure this out.

Once i load the page and log in, then make the first move, sometimes it ends up failing and sometimes it doesn't.

Is it possible that once you disable the buttons in javascript, you're able to re-enable them? I have tried that as option but it doesn't seem to be effective as I thought it would.

I am open to suggestions, I just would like to figure out how to implement a turn based game then Ill refactor the code. (it is very atrocious I'm sorry).