前言
前阵子头脑王者类的微信答题PK游戏很火,自己也是很痴迷,玩了一阵子之后,想自己尝试来模仿下答题这一部分的实现。服务端打算在下swoole和socket.io之间选择,因为socket.io可以不借助redis直接缓存一些变量,所以选择了socket.io。简单实现了对战这一部分,实现的并不完善,只是一种思路
客户端实现
index.html
<!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>
大概长这样,后端的审美,比较不好看
问题数据的存储方式
我用了redis来保存问题,内容格式如下
{
"id": 9,
"content": "9.一年几天",//问题内容
"options": [ //选项
"1",
"2",
"3",
"365"
],
"answer_index": 3 //答案的索引
}
选用redis的list格式,顺手自己编几个问题,用php写进去,多编几条,多写几次
$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));
服务端实现
answerserver.js
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;
}
});
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。