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' οnclick='selection(\"" + k + "\")' />投票</label>");
} else {
player.addProperty("operation", "<label><input type='radio' name='selection' οnclick='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' οnclick='getIdentity(\"" + k + "\")' />查詢</label>");
} else {
player.addProperty("operation", "<label><input type='radio' name='getIdentity' οnclick='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' οnclick='kill(\"" + k + "\")' />殺掉</label>");
} else {
player.addProperty("operation", "<label><input type='radio' name='kill' οnclick='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秒自動開始遊戲.
差不多就到這裏了,可以自行擴展.
源碼在頂部獲取.
關注瞭解更多.