微信答題PK類遊戲嘗試實現

前言

前陣子頭腦王者類的微信答題PK遊戲很火,本身也是很癡迷,玩了一陣子以後,想本身嘗試來模仿下答題這一部分的實現。服務端打算在下swoole和socket.io之間選擇,由於socket.io能夠不借助redis直接緩存一些變量,因此選擇了socket.io。簡單實現了對戰這一部分,實現的並不完善,只是一種思路javascript

客戶端實現

index.htmlphp

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width, initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>答題</title>

    <script type="text/javascript" src="vue.js"></script>
    
    <style>
    .player {
        display: flex;
        color: #fff;
        text-align: center;
    }

    .player .item {
        height: 80px;
    }

    .player .item.select {
        border: 4px solid #ff9933;
        box-sizing: border-box;
    }

    .player .item:first-child {
        background-color: #6dcffb;
        flex: 3;
    }

    .player .item:nth-child(2) {
        background-color: #5247a4;
        flex: 1;
        line-height: 80px;
    }

    .player .item:last-child {
        background-color: #d64e8c;
        flex: 3;
    }

    #app {
        background-color: #5a8ced
    }

    .question {
        color: #fff;
        text-align: center;
    }

    .options {
        padding-bottom: 40px
    }

    .options li {
        width: 280px;
        height: 60px;
        background-color: #fff;
        margin-top: 40px;
        display: block;
        border-radius: 50px;
        text-align: center;
        line-height: 60px;
    }

    .options li.correct {
        background-color: #00e2bc;
        color: #fff;
    }

    .options li.wrong {
        background-color: #fe6f5f;
        color: #fff;
    }

    .tips {
        text-align: center;
        color: #fff
    }
</style>
</head>

<body>
    <div id="app">
        <div class="player">
            <div :class="[playerIndex===0?'item select':'item']">
                <p class="name">選手1</p>
                <p class="score">{{score[0]}}</p>
            </div>
            <div class="item">{{remain_time}}</div>
            <div :class="[playerIndex===1?'item select':'item']">
                <p class="name">選手2</p>
                <p class="score">{{score[1]}}</p>
            </div>
        </div>
        <p class="question">{{question}}</p>
        <ul class="options">
            <li v-for="(item, key, index) in options" :class='item.class' @click="sendAnswer(key)">{{item.index}}</li>
        </ul>
        <p class="tips">{{msg}}</p>
    </div>
</body>
</html>
<script>

</script>
<script src="./socket.io.js"></script>
<script>
    var header = new Vue({
        el: "#app",
        data: {
            socket: null, //socket.io對象
            playerIndex: null, //當前選手索引
            question: '', //問題內容
            msg: '', //下方提示語
            options: [], //選項
            canAnswer: true, //是否能夠回答
            remainTime: 10, //倒計時剩餘時間
            score: [0, 0] //兩名選手的分數
        },

        created: function () {
            let that = this;
            this.socket = io.connect('http://localhost:1024');

            //獲取選手索引
            this.socket.emit('getPlayerIndex', '')
            this.socket.on('getPlayerIndex', (data) => {
                that.playerIndex = data;
                if (that.playerIndex == 1) {
                    that.socket.emit('getQuestion', that.playerIndex)
                }
            });
            
            //服務端發送問題,更新問題、選項和提示信息
            this.socket.on('sendQueston', (data) => {
                that.canAnswer = true; //拿到問題後,容許回答
                let res = JSON.parse(data);
                that.question = res.content;
                that.options = res.options;
                that.msg = res.msg;
            });

            //服務端發送更新倒計時時間,更新倒計時
            this.socket.on('updateTime', (time) => {
                that.remain_time = time;
            });

            //遊戲結束,直接alert
            this.socket.on('gameOver', (text) => {
                alert('遊戲結束,' + text);
            });

            //服務端發出獲取問題
            this.socket.on('getQuestion', (time) => {
                if (this.playerIndex == 0) {
                    let that = this;
                    //3秒後獲取下一題
                    setTimeout(function () {
                        that.socket.emit('getQuestion', this.playerIndex)
                    }, 3000)
                }
            });

            //服務端發送答題結果
            this.socket.on('sendResult', (data) => {
                that.canAnswer = false; //回答後,禁止回答,防止另外一名選手點擊
                let res = JSON.parse(data);
                that.msg = res.msg;
                that.options = res.options; 
                that.score = res.score;
            });
        },

        methods: {
            //點擊選項後的事件
            sendAnswer(index) {
                if (!this.canAnswer) {
                    return false;
                }
                this.socket.emit('sendAnswer', index, this.playerIndex)
            },
        }
    })
</script>

大概長這樣,後端的審美,比較很差看
image.pnghtml

問題數據的存儲方式

我用了redis來保存問題,內容格式以下前端

{
    "id": 9,
    "content": "9.一年幾天",//問題內容
    "options": [  //選項
        "1",
        "2",
        "3",
        "365"
    ],
    "answer_index": 3 //答案的索引
}

選用redis的list格式,順手本身編幾個問題,用php寫進去,多編幾條,多寫幾回vue

$redis = new \Redis();
$redis->connect('127.0.0.1');
$content = ['id' => 1, 'content' => '1.一天有幾小時', 'options' => ['1', '2', '3', '48'], 'answer_index' => 3];
$redis->lPush('questions', json_encode($content));

image.png

服務端實現

answerserver.jsjava

const app = require('express')();
const server = require('http').Server(app);
const io = require('socket.io')(server);
const redisModule = require('redis');
const redis = redisModule.createClient(6379, '127.0.0.1');

let playerIndex = 0; //選手索引
let options = []; //選項
let answerIndex = 0; //答案索引
let timer = null; //定時器
let remainTime = 10; //剩餘時間
let score = [0, 0]; //得分 ['選手1分數','選手2分數']
let questionCount = 0; //問題數
let startGame = false; //是否已經開始遊戲
const maxRemainTime = 10; //最大倒計時秒數

server.listen(1024);

io.on('connection', (socket) => {
    //客戶端發送 獲取問題
    socket.on('getQuestion', (playerIndex) => {
        if (startGame == false) {
            //首次發送 倒計時開始
            timer = setInterval(() => {
                remainTime--;
                if (remainTime <= 0) {
                    socket.emit('getQuestion', remainTime);
                    remainTime = maxRemainTime;
                }
                socket.emit('updateTime', remainTime);
                socket.broadcast.emit('updateTime', remainTime);

            }, 1000);
            startGame = true;
        }
        //初始化倒計時
        remainTime = maxRemainTime;
        questionCount++;
        redis.lpop('questions', function (err, data) {
            let res = JSON.parse(data);
            let new_options = [];
            answerIndex = res.answer_index;
            res.options.forEach(function (v, k) {
                let o = new Object();
                o.index = v;
                o.class = '' //這裏的class供前端使用,爲空時,選項是白色背景
                new_options.push(o)
            })
            res.options = new_options;
            options = new_options;
            socket.emit('sendQueston', JSON.stringify(res))
            socket.broadcast.emit('sendQueston', JSON.stringify(res))
        })
    });

    //發送答案結果
    socket.on('sendAnswer', (userSelectIndex, playerIndex) => {
        let result = { msg: '' };
        options.forEach(function (v, k) {
            if (answerIndex == k) {
                //正確的選項 背景改爲綠色
                options[k].class = 'correct'
            } else if (k == userSelectIndex && userSelectIndex != answerIndex) {
                //正確的選項 背景改爲紅色
                result.msg = '選手' + (playerIndex + 1) + '答錯了';
                options[k].class = 'wrong'
            }
        })
        //答對的選手+10分
        if (userSelectIndex == answerIndex) {
            score[playerIndex] += 10;
            result.msg = '選手' + (playerIndex + 1) + '答對了';
        }
        result.score = score;
        result.options = options;

        socket.emit('sendResult', JSON.stringify(result));
        socket.emit('getQuestion');
        socket.broadcast.emit('getQuestion');
        socket.broadcast.emit('sendResult', JSON.stringify(result));
        if (questionCount >= 5) {
            let winText = '平局'; //結束時的提示信息
            if (score[0] > score[1]) {
                winText = '選手1獲勝'
            } else if (score[0] < score[1]) {
                winText = '選手2獲勝'
            }
            socket.emit('gameOver', winText);
            socket.broadcast.emit('gameOver', winText);
            clearInterval(timer);
        }
    });

    //獲取選手號數
    socket.on('getPlayerIndex', (data) => {
        socket.emit('getPlayerIndex', playerIndex);
        playerIndex++;
        if (playerIndex >= 2) {
            playerIndex = 0;
        }
    });
});

最終效果

GIF.gif

相關文章
相關標籤/搜索