Hello,I'm Shendi,这次我制作了谁是卧底游戏(制作周期三天).
这里我写了一篇关于这个制作的教程,并附带了源码
下面是运行效果.
目录
当我们点击快速开始游戏按钮的时候,请求了 JoinServlet 接口
主要技术
服务器: Tomcat,Servlet
前端: html,css,js
项目已放 Github(对应于WhoIsTheSpy项目):https://github.com/1711680493/Application
后面我会制作更多有意思的东西来分享,如果您感兴趣,点个关注呗~...
此文章对应实战专栏(一些实战案例都在此): https://blog.csdn.net/qq_41806966/category_9656338.html
整理思路
在开始之前,我们需要了解一下这个游戏的玩法,机制.
游戏机制: 房间功能(用户的加入退出,游戏状态等),聊天功能(不同频道处理),投票功能等
玩法(我参考谁是卧底定义的): 警杀模式
六个玩家一组,四个平民,一个警察一个杀手
杀手每晚可以投票杀一个人,警察每晚可以获取一个人的身份
白天所有玩家进行投票,票数最多的将会被处决
杀手杀掉所有平民或者警察则胜利
警察和平民则处决杀手胜利
了解到游戏的玩法后,我们就可以一步一步来实现了.(后面的实战例子我会绘制UML图来讲解)
下面是我这个项目所有的前端界面
开始界面实现
其中 index.html 是第一个界面 界面效果如下
界面包含一个文字和一个a标签...对应index.css文件
a标签点击后跳转到 main.html 界面
房间列表界面(快速开始,进入房间)
界面效果如下
这个界面有一个div(用于显示现有的所有房间)
一个快速开始按钮(从现有的房间中选一个未满的进入,如果没有房间则新建一个,自己为1号)
以及顶部的 logo(没有啥作用,这篇博客主要讲后端实现...)
房间号的加载对应了roomList.js(请求服务器,拿到数据)和main.js(将数据放到div里显示)
刚开启服务器是没有任何房间的,当我们点击快速开始游戏的时候,会请求 Servlet 来进行房间的进入和创建
快速开始游戏的点击事件在 main.js 中绑定了.使用了 ajax 请求 join接口(对应 JoinServlet)
房间架构(对应 Room 类)
Room类代表一个房间,是一个抽象类,用于处理用户的加入退出,游戏逻辑调用,拥有保存玩家状态的属性
有以下属性
Room类的方法
Room类有一个子类,Default类,实现了 start(),stop()方法
以及RoomManager(房间管理),用于创建,销毁房间.
当我们点击快速开始游戏按钮的时候,请求了 JoinServlet 接口
主要获取用户sessionId,并通过房间管理器(RoomManager)来加入房间
joinRoom方法有两个参数,一个是sessionId(String),和roomId(int)
接下来我们点击快速开始游戏按钮(没有房间,创建了一个 id 为 1),在跳转的时候url上加上了id参数(main.js跳转room.js获取)
接下来就进入了这样一个界面 room.html页面.
房间界面实现
room.js 刚开始获取到url上的 id(当前房间号)
最上方的文字不再只是单纯的logo,可以用来退出房间(请求ExitRoomServlet->Room的exit方法)
带着房间id访问了ExitRoomServlet接口(不管用户在不在房间里,都会重定向到 main.html 中)
房间的进入,退出功能都有了,接下来就是玩家加入房间内(不是旁观者了)
加入房间
当点击加入房间按钮,就会带着房间 id 去请求JoinRoomServlet 接口(加入房间接口)
JoinRoomServlet 最终调用指定 Room 的 join 方法(也就是加入房间) 代码如下
join方法有sessionId参数,以及加了同步锁(避免人数超出限制),加入人数不能超过最大人数
返回值我这里提供了,没有处理(可以自行扩展...)
现在我们的房间进入,退出,加入房间功能已经实现了
目前我们的程序看不到什么效果,就算点击加入房间...也只是用户的信息在Map里存储了.
所以,接下来我们就需要让我们房间里的信息在页面显示
(我制作的时候是先制作聊天功能,然后在制作用户的显示,因为要确定用户类里需要有什么功能)
在房间没有开始的时候.用户存储为 座位号=sessionId 的形式
在开始游戏后,用户存储为 sessionId=Player类的形式
用户类 Player
Player类有以下几个属性
其中 objects 是用于存储此用户对应的一些特别的数据,比如警察晚上查询的玩家,杀手晚上选择执行的玩家
每一个玩家都对应一个 Player.
两个枚举
Player在Room中
房间内聊天功能
接下来我们把聊天功能先制作,在房间中有存所有聊天信息的map,以及对应的频道常量
我们发送/接收就很简单了直接从map里取
在 room.js 中,有一个一直运行的代码(一直请求聊天信息,根据不同频道,然后放在div显示)
代码比较长,请求的接口为 InfoServlet,是专门处理消息的接口
//获取聊天框
var infoContent = document.getElementById("info_content");
//当前的频道
var infoType = "all";
//房间消息的线程 默认等于全部
{
var infoUrl;
if (window.XMLHttpRequest) infoUrl = new XMLHttpRequest();
else infoUrl = new ActiveXObject("Microsoft.XMLHTTP");
infoUrl.onreadystatechange = function () {
if (infoUrl.readyState == 4) {
//获取到消息
let infos = infoUrl.responseText;
if (infoUrl.status != 200) {
//请求出错处理
infoContent.innerHTML = infos;
clearInterval(getInfo);
return;
}
//替换内容
let infoArray = eval(infos);
infoContent.innerHTML = "";
for (let i = 0;i < infoArray.length;i++) {
let obj = infoArray[i];
infoContent.innerHTML += obj.info + "<br />";
}
}
}
var getInfo = setInterval(function () {
infoUrl.open("POST","/WhoIsTheSpy/info",true);
infoUrl.setRequestHeader("Content-type","application/x-www-form-urlencoded");
infoUrl.send("roomId=" + roomId + "&infoType=" + infoType);
},800);
}
InfoServlet代码,一些基础的判断,获取指定房间的指定频道的消息.(数据传递使用了 json)
在 getInfo 方法中会判断频道类型是否为阵容,如果是阵容(游戏没开始)则没有任何消息.
对于阵容的消息,我定义的规则是,不同阵容用,阵容 + 职业类型 来区分
接下来我们可以接收到频道消息,则需要切换频道
切换聊天频道
通过这几个按钮,点击的时候我们会把js里频道的值改变.也就是
对于系统频道是不允许发送消息的.
接收消息+切换频道弄好了,剩下的就是发送消息,点击发送按钮会带着输入框内的值去请求 SendInfoServlet.
发送聊天信息
SendInfoServlet 是用于发送聊天信息的接口,需要 房间id 聊天频道 和 消息内容 三个参数
sendInfo方法是 Room 类的方法,用于将消息添加进map.(因为有 js 一直获取 map里的值,所以就实现了发送消息的功能)
sendInfo代码比较长,有很多过滤的东西
- 游戏开始,并且频道为公共则不能发送消息(return)
- 如果频道是系统频道(这个只有代码里自己调用,而不是用户),则直接添加进map,不做一些判断,并且把消息也添加到公共消息中.
- 参数 sessionId 为发送者,(房间内部调用则为 系统)
- 如果房间内有此玩家,那么就以玩家座位号命名,否则直接用sessionId命名
/**
* 发送消息到指定频道.
* @author Shendi <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>QQ</a>
* @param sessionId 是谁发送的消息.
* @param infoType 频道类型
* @param info 消息内容
*/
public void sendInfo(String sessionId,String infoType,String info) {
//游戏开始 类型公共 并且是晚上则不执行操作
if (state && infoType.equals(INFO_ALL) && gameState == GameState.夜晚) {
return;
}
//系统的则直接发送
if (INFO_SYSTEM.equals(infoType)) {
if (infos.containsKey(infoType)) {
List<String> list = infos.get(infoType);
list.add(sessionId + ":" + info);
} else {
List<String> list = new ArrayList<>();
list.add(sessionId + ":" + info);
infos.put(infoType,list);
}
if (infos.containsKey(INFO_ALL)) {
List<String> list = infos.get(INFO_ALL);
list.add(sessionId + ":" + info);
} else {
List<String> list = new ArrayList<>();
list.add(sessionId + ":" + info);
infos.put(INFO_ALL,list);
}
return;
}
//获取sessionId在房间里的位置 如果没有,则以sessionId命名
if (players.containsValue(sessionId)) {
Set<Integer> set = players.keySet();
for (int k : set) {
if (sessionId.equals(players.get(k))) {
sessionId = String.valueOf(k);
break;
}
}
}
if (infos.containsKey(infoType)) {
List<String> list = infos.get(infoType);
list.add(sessionId + ":" + info);
} else {
List<String> list = new ArrayList<>();
list.add(sessionId + ":" + info);
infos.put(infoType,list);
}
}
除了所有人的消息之外,有时候系统需要给用户私发一些消息,所以弄一个私人消息接收框
系统发给自己的专属消息接收
也就是这个,是一个Div元素,一次只显示一条数据(最后一条),如果点击,则会变大,并显示所有的数据
设置这个div为只读(不可选),点击的时候将css样式切换一下达到这种效果.
我们获取私信的系统消息与获取信息的方式一致,
这里请求的是RoomEventServlet接口,有两种类型 通过 isLast判断获取一条数据或者所有数据
当游戏开始的时候才会有数据返回.
我们前端获取游戏结果也是通过这个获取
现在消息系统弄好了,接下来就是显示房间的用户
房间用户以及状态显示
在 room.js 里有一个一直执行获取当前房间其余玩家信息的ajax
通过获取 RoomInfoServlet 接口中拿到数据并进行显示
js代码
//获取房间用户状态的线程
var userInfo = "<table border='1'>" +
"<tr>" +
"<th>编号</th>" +
"<th>当前玩家</th>" +
"<th>身份</th>" +
"<th>操作</th>" +
"<th>操作数量</th>" +
"<th>游戏状态</th>" +
"</tr>";
var players = document.querySelector(".players");
var userUrl;
if (window.XMLHttpRequest) userUrl = new XMLHttpRequest();
else userUrl = new ActiveXObject("Microsoft.XMLHTTP");
userUrl.onreadystatechange = function () {
if (userUrl.readyState == 4) {
//获取到消息
let infos = userUrl.responseText;
if (userUrl.status != 200) {
//请求出错处理
clearInterval(getUser);
return;
}
//替换内容
let infoArray = eval(infos);
let text = userInfo;
for (let i = 0;i < infoArray.length;i++) {
let obj = infoArray[i];
text += "<tr><td>";
text += obj.id + "</td>";
text += "<td>" + obj.user + "</td>";
//标识
let identity = obj.identity;
if (identity == undefined) {
text += "<td>无</td>";
} else {
text += "<td>" + identity + "</td>";
}
//操作
let operation = obj.operation;
if (operation == undefined) {
text += "<td>无</td>";
} else {
text += "<td>" + operation + "</td>";
}
//操作数量
let operationNum = obj.operationNum;
if (operation == undefined) {
text += "<td>无</td>";
} else {
text += "<td>" + operationNum + "</td>";
}
//游戏状态
let gameState = obj.gameState;
if (gameState == undefined) {
text += "<td>无</td>";
} else {
text += "<td>" + gameState + "</td>";
}
text += "</tr>";
}
text += "</table>";
players.innerHTML = text;
}
}
//获取房间里的用户
var getUser = setInterval(function () {
userUrl.open("POST","/WhoIsTheSpy/roomInfo",true);
userUrl.setRequestHeader("Content-type","application/x-www-form-urlencoded");
userUrl.send("roomId=" + roomId);
},800);
RoomInfoServlet
用于获取当前房间内用户的信息(有很多判断)
- 房间不存在则返回-500
- 游戏未开始(准备中)则除了用户的座位号 用户名 其余的信息都为无
- 游戏开始则判断请求的用户是什么职业
- 警察: 获取已经查询了的其余职业的身份,白天,晚上可以投票
- 杀手: 白天,晚上可以投票.
- 平民: 白天可以投票.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取当前session
String sessionId = req.getSession().getId();
//获取房间id
String roomId = req.getParameter("roomId");
//获取房间信息
Room room = RoomManager.getRooms().get(Integer.parseInt(roomId));
if (room == null) {
//返回房间号 -500为无房间
resp.getOutputStream().write(String.valueOf(room).getBytes("UTF-8"));
return;
}
//获取房间状态 游戏未开启输出的 操作什么都为无
JsonArray array = new JsonArray();
if (!room.state) {
room.getPlayers().forEach((k,v) -> {
JsonObject player = new JsonObject();
player.addProperty("id", k);
if (v.equals(sessionId)) player.addProperty("user", "我");
else player.addProperty("user", v);
array.add(player);
});
} else {
//获取自己的信息
Player my = room.startPlayers.get(sessionId);
room.startPlayers.forEach((k,v) -> {
JsonObject player = new JsonObject();
//id
player.addProperty("id", v.getId());
//名称标识
if (k.equals(sessionId)) {
player.addProperty("user", "我");
//获取自己的职业
player.addProperty("identity",v.getType().toString());
} else {
player.addProperty("user", k);
//其余人的职业一律未知 (警察可以获取到查询了的职业),已死亡的角色职业将暴露
if (my != null && my.getType() == PlayerType.警察) {
@SuppressWarnings("unchecked")
List<String> identitys = (List<String>) my.getObjects().get("identitys");
if (identitys != null && identitys.contains(k)) {
player.addProperty("identity",v.getType().toString());
} else {
player.addProperty("identity","未知");
}
} else if (v.getState() == PlayerState.死亡) {
player.addProperty("identity",v.getType().toString());
} else {
player.addProperty("identity","未知");
}
}
//当前状态
player.addProperty("gameState", v.getState().toString());
//操作,和操作数量 用户死亡 或者为自己 则无操作选项和数量
if (v.getState() == PlayerState.死亡 || k.equals(sessionId)) {
player.addProperty("operation", "无");
if (k.equals(sessionId)) {
player.addProperty("operationNum",getOperationNum(k, room,PlayerType.平民));
} else {
player.addProperty("operationNum", "无");
}
} else {
//旁观不能投票
if (my == null) {
player.addProperty("operation", "无");
player.addProperty("operationNum",getOperationNum(k, room,PlayerType.平民));
} else {
//白天都有投票操作 并且只有投票操作
if (room.gameState == GameState.白天) {
//获取自己选中的是什么
if (k.equals(my.getObjects().get("selection"))) {
player.addProperty("operation", "<label><input type='radio' name='selection' checked='checked' onclick='selection(\"" + k + "\")' />投票</label>");
} else {
player.addProperty("operation", "<label><input type='radio' name='selection' onclick='selection(\"" + k + "\")' />投票</label>");
}
//获取此用户的投票数量
player.addProperty("operationNum",getOperationNum(k, room,PlayerType.平民));
} else {
//晚上 自己的身份是警察则是查操作 杀手则是杀操作 平民则无
switch (my.getType()) {
case 平民:
player.addProperty("operation", "无");
player.addProperty("operationNum", "无");
break;
case 警察:
//获取自己选中的是什么
if (k.equals(my.getObjects().get("getIdentity"))) {
player.addProperty("operation", "<label><input type='radio' name='getIdentity' checked='checked' onclick='getIdentity(\"" + k + "\")' />查询</label>");
} else {
player.addProperty("operation", "<label><input type='radio' name='getIdentity' onclick='getIdentity(\"" + k + "\")' />查询</label>");
}
//获取此用户的投票数量
player.addProperty("operationNum",getOperationNum(k, room,PlayerType.警察));
break;
case 杀手:
//获取自己选中的是什么
if (k.equals(my.getObjects().get("kill"))) {
player.addProperty("operation", "<label><input type='radio' name='kill' checked='checked' onclick='kill(\"" + k + "\")' />杀掉</label>");
} else {
player.addProperty("operation", "<label><input type='radio' name='kill' onclick='kill(\"" + k + "\")' />杀掉</label>");
}
//获取此用户的投票数量
player.addProperty("operationNum",getOperationNum(k, room,PlayerType.杀手));
break;
}
}
}
}
array.add(player);
});
}
resp.getOutputStream().write(array.toString().getBytes("UTF-8"));
}
/**
* 获取指定用户的操作数
* @author Shendi <a href='tencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=1711680493'>QQ</a>
* @param sessionId 用户标识,获取此用户的操作数.
* @param room 房间
* @param type 代表获取的类型 平民则获取投票数,警察则获取用户的警投票数,杀手则获取杀投票数.
* @return 操作数量
*/
private int getOperationNum(String sessionId,Room room,PlayerType type) {
Set<String> set = room.startPlayers.keySet();
int num = 0;
for (String session : set) {
switch (type) {
case 平民:
if (sessionId.equals(room.startPlayers.get(session).getObjects().get("selection"))) num++;
break;
case 警察:
if (sessionId.equals(room.startPlayers.get(session).getObjects().get("getIdentity"))) num++;
break;
case 杀手:
if (sessionId.equals(room.startPlayers.get(session).getObjects().get("kill"))) num++;
break;
}
}
return num;
}
有了用户的显示后,就是核心部分玩法了.
游戏开始
当房间最后一个用户加入后就会执行开始游戏操作.
switchState() 是 Room的切换房间状态方法,这里开线程的原因是游戏逻辑要在后台执行,而不是使用最后一个用户的线程,
state默认是false,所以上述代码会执行 start(); 方法(子类实现的 start)
职业分配
在DefaultRoom中的Start运行进行职业分配.(分配一个警察和一个杀手,其余的都是平民)
switchGameState方法
调用的是 GameBlockState 中的 switchState方法 传递了一个 room.
GameWhiteState执行完就会调用GameBlockState的方法,GameBlockState执行完就会调用GameWhiteState的方法.
直至游戏结束条件成立...
投票机制
游戏开始时,刚开始时夜晚,警察和杀手进行行动,
点击单选框其实会调用 js 请求接口
这里的三个接口一个是警察投票,杀手投票,白天投票的接口.
主要功能就是获取投票的用户,给他的objects(存数据的HashMap)加投票数据(获取房间玩家信息的时候就可以获取到,并且游戏后台可以获取到)
- 当投票成功后,执行对应的操作,先判断结果(比如杀手可以杀死一个玩家,杀死后如果没有平民/警察则胜利).
- 如果没有胜利,则切换状态(夜晚变成白天).
- 白天所有玩家都可以投票,进行处决一名玩家,投票结果会取票数最多的玩家进行处决
- (上述警察和杀手也是,只不过目前只有一个警察和一个杀手)
- 处决完后判断游戏结果是否胜利/失败
- 如果警察/平民/杀手都没有全灭亡 则切换状态(白天变夜晚)
晚上代码
public class GameBlockState implements RoomGameState {
private static GameWhiteState whiteState = new GameWhiteState();
public GameBlockState() {
GameWhiteState.setBlockState(this);
}
@SuppressWarnings("unchecked")
@Override
public void switchState(Room room) {
//休息一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//提示晚上来临
room.sendInfo("系统", Room.INFO_SYSTEM, "夜晚来临.");
//睡眠一秒 提示进行操作
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
room.startPlayers.forEach((k,v) -> {
if (PlayerType.警察 == v.getType()) {
room.sendInfoByUser(k, "请选择你要搜查的目标");
} else if (PlayerType.杀手 == v.getType()) {
room.sendInfoByUser(k, "请选择你要击杀的目标");
}
});
//倒计时20秒 执行结果
room.sendInfo("系统", Room.INFO_SYSTEM, "距离白天还有20秒.");
try {
Thread.sleep(20000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
room.gameState = GameState.白天;
try {
Thread.sleep(1000);;
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//执行夜晚的操作
{
//获取警察 杀手选择的目标 并执行操作
HashMap<String,Integer> officeSelection = new HashMap<>();
HashMap<String,Integer> killSelection = new HashMap<>();
room.startPlayers.forEach((k,v) -> {
if (v.getType() == PlayerType.警察) {
if (v.getObjects().containsKey("getIdentity")) {
String sessionId = (String) v.getObjects().get("getIdentity");
if (officeSelection.containsKey(sessionId)) {
officeSelection.put(sessionId,officeSelection.get(sessionId) + 1);
} else {
officeSelection.put(sessionId,1);
}
v.getObjects().remove("getIdentity");
}
} else if (v.getType() == PlayerType.杀手) {
if (v.getObjects().containsKey("kill")) {
String sessionId = (String) v.getObjects().get("kill");
if (killSelection.containsKey(sessionId)) {
killSelection.put(sessionId,killSelection.get(sessionId) + 1);
} else {
killSelection.put(sessionId,1);
}
v.getObjects().remove("kill");
}
}
});
//通知警察被查询的身份
{
String officeSelect = null;
int officeMax = -1;
//获取投票最多的用户
{
Set<String> keys = officeSelection.keySet();
for (String key : keys) {
int value = officeSelection.get(key);
if (value > officeMax) {
officeSelect = key;
officeMax = value;
}
}
}
Player officeSelectPlayer = room.startPlayers.get(officeSelect);
if (officeSelect != null) {
//所有警察都获取此用户身份
room.sendInfo("系统", Room.INFO_TOGETHER + PlayerType.警察,officeSelectPlayer.getId() + " 已经被查,身份是:" + officeSelectPlayer.getType());
Set<String> keys = room.startPlayers.keySet();
for (String key : keys) {
Player player = room.startPlayers.get(key);
if (player.getType() == PlayerType.警察) {
room.sendInfoByUser(key, officeSelectPlayer.getId() + " 已经被查,身份是:" + officeSelectPlayer.getType());
if (player.getObjects().containsKey("identitys")) {
((List<String>)(player.getObjects().get("identitys"))).add(officeSelect);
} else {
List<String> list = new ArrayList<>();
list.add(officeSelect);
player.getObjects().put("identitys",list);
}
}
}
}
}
//执行杀手操作
{
String killSelect = null;
int killMax = -1;
//获取投票最多的用户
{
Set<String> keys = killSelection.keySet();
for (String key : keys) {
int value = killSelection.get(key);
if (value > killMax) {
killSelect = key;
killMax = value;
}
}
}
//击杀玩家
if (killSelect != null) {
Player killPlayer = room.startPlayers.get(killSelect);
killPlayer.setState(PlayerState.死亡);
room.sendInfo("系统",Room.INFO_SYSTEM,killPlayer.getId() + " 被杀害,身份是“" + killPlayer.getType() + "”");
//判断游戏结果 如果场上没有警察 平民则胜利
boolean officeExists = false;
boolean peopleExists = false;
Collection<Player> values = room.startPlayers.values();
for (Player player : values) {
if (player.getType() == PlayerType.警察 && player.getState() == PlayerState.存活) {
officeExists = true;
} else if (player.getType() == PlayerType.平民 && player.getState() == PlayerState.存活) {
peopleExists = true;
}
}
if (!officeExists || !peopleExists) {
//提示游戏结束 一秒后跳出结果界面并停止游戏
room.startPlayers.forEach((k,v) -> {
if (v.getType() == PlayerType.杀手) {
room.sendInfoByUser(k, "|stop|杀手胜利,警察或平民全灭.");
} else {
room.sendInfoByUser(k, "|stop|失败,警察或平民全灭.");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
room.switchState();
return;
}
}
}
}
room.roomGameState = whiteState;
room.switchGameState();
}
}
白天代码
public class GameWhiteState implements RoomGameState {
private static GameBlockState blockState;
public static void setBlockState(GameBlockState blockState) {
GameWhiteState.blockState = blockState;
}
@Override
public void switchState(Room room) {
//休息一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//提示早上来临
room.sendInfo("系统", Room.INFO_SYSTEM, "早上了,请开始投票.");
//睡眠一秒 提示进行操作
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
room.startPlayers.forEach((k,v) -> {
room.sendInfoByUser(k, "请投票.");
});
//倒计时50秒 执行结果
room.sendInfo("系统", Room.INFO_SYSTEM, "距离夜晚还有50秒.");
try {
Thread.sleep(50000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
room.gameState = GameState.夜晚;
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
//执行白天的操作
{
//获取所有的投票
HashMap<String,Integer> selection = new HashMap<>();
room.startPlayers.forEach((k,v) -> {
if (v.getObjects().containsKey("selection")) {
String sessionId = (String) v.getObjects().get("selection");
if (selection.containsKey(sessionId)) {
selection.put(sessionId,selection.get(sessionId) + 1);
} else {
selection.put(sessionId,1);
}
v.getObjects().remove("selection");
}
});
String select = null;
int selectMax = -1;
//获取投票最多的用户
{
Set<String> keys = selection.keySet();
for (String key : keys) {
int value = selection.get(key);
if (value > selectMax) {
select = key;
selectMax = value;
}
}
}
//处决用户
if (select != null) {
Player player = room.startPlayers.get(select);
//将用户状态改为死亡
player.setState(PlayerState.死亡);
//通知其他用户
room.sendInfo("系统", Room.INFO_SYSTEM, "玩家 " + player.getId() + " 被处决,身份是 “" + player.getType() + "”");
//休息两秒
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取职业状态(是否全灭)
boolean officeExists = false;
boolean killExists = false;
boolean peopleExists = false;
Collection<Player> values = room.startPlayers.values();
for (Player p : values) {
if (p.getType() == PlayerType.警察 && p.getState() == PlayerState.存活) {
officeExists = true;
} else if (p.getType() == PlayerType.杀手 && p.getState() == PlayerState.存活) {
killExists = true;
} else if (p.getType() == PlayerType.平民 && p.getState() == PlayerState.存活) {
peopleExists = true;
}
}
//判断结果
if (!officeExists || !peopleExists) {
//提示游戏结束 一秒后跳出结果界面并停止游戏
room.startPlayers.forEach((k,v) -> {
if (v.getType() == PlayerType.杀手) {
room.sendInfoByUser(k, "|stop|杀手胜利,警察或平民全灭.");
} else {
room.sendInfoByUser(k, "|stop|失败,警察或平民全灭.");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
room.switchState();
return;
} else if (!killExists) {
//提示游戏结束 一秒后跳出结果界面并停止游戏
room.startPlayers.forEach((k,v) -> {
if (v.getType() == PlayerType.杀手) {
room.sendInfoByUser(k, "|stop|杀手失败,杀手全灭.");
} else {
room.sendInfoByUser(k, "|stop|胜利,杀手全灭.");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
room.switchState();
return;
}
}
}
room.roomGameState = blockState;
room.switchGameState();
}
}
发送结果,展示胜利界面
根据之前说到的私人消息,如果我们发送的消息包含结束 -- 在js中判断手为结束的数据,是则弹出结果面板
游戏状态切换(状态变为等待) 几秒后,如果房间内还是满人则等待20秒自动开始游戏.
差不多就到这里了,可以自行扩展.
源码在顶部获取.
关注了解更多.