今日目標,「準備」功能。
今天我們要透過 WebSocket 更新房間內成員的準備狀態,聽起來很簡單,但還是有些細節要注意
再來就實作吧~~
對於房主的「開始」,小弟依然當作「準備」在處理,但比較不同的是,如果人數沒有滿或有人沒準備,那房主的準備狀態就會被取消掉,當所有人都準備(包含房主),才會開始遊戲。
package com.example.room;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class UserReadyMessage {
private boolean ready;
}
package com.example.room;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Map;
public class UserReadyResponse {
@Getter
private final Map<String, Boolean> userReadyStatus = new HashMap<>();
@Getter @Setter
private boolean allReady = false;
@Getter @Setter
private String message = "";
public void add(String username, boolean isReady) {
this.userReadyStatus.put(username, isReady);
}
}
userReadyStatus
:儲存使用者的準備狀態allReady
:是否全部人都準備message
:訊息回饋readyToPlay()
,加入的片段程式碼為:
@MessageMapping("/ready")
public void readyToPlay(UserReadyMessage readyMessage, Principal principal) {
// 設定使用者的準備狀態
String username = principal.getName();
this.userStatus.setUserReady(username, readyMessage.isReady());
// 取出所有房間內的成員
String roomId = this.userStatus.getUserRoomId(username);
Room room = roomList.getRoomById(roomId);
ArrayList<String> roomMembers = room.getAllMembers();
UserReadyResponse response = new UserReadyResponse();
// counter 用於確定是否全部都準備了,如果不是就會給予回饋
int counter = 0;
for (String member : roomMembers) {
boolean isReady = this.userStatus.isUserReady(member);
if (isReady) {
counter += 1;
}
response.add(member, isReady);
}
// 如果沒有全部都準備好,就把房主的準備狀態取消
String owner = room.getOwner();
if (counter == 4) {
response.setAllReady(true);
}
else {
this.userStatus.setUserReady(owner, false);
response.add(owner, false);
}
// 設定回饋給所有成員的訊息
for (String member : roomMembers) {
response.setMessage("");
if (counter != 4 && member.equals(username)) {
if (member.equals(owner)) {
if (room.count() == 4) {
response.setMessage("尚有人未準備好開始遊戲!");
}
else {
response.setMessage("人數不足!");
}
}
else {
if (!readyMessage.isReady()) {
response.setMessage("請快點準備!");
}
}
}
// 個別發送訊息
simpMessagingTemplate.convertAndSendToUser(member, "/queue/ready", response);
}
}
@MessageMapping("/ready")
:定義接收資料的 url 為 readyPrincipal
:其實是前面 SecurityContext 的 Authentication 的 Principal,用於保存登入資訊,所以在 RoomController 同樣可以使用這個參數來取得目前的使用者名稱/user/queue/ready
(因為要做單播),並定義 ready()
的行為,完整內容為:
var websocket = new WebSocket();
var myUsername = $("#my-username").val();
var roomId;
var owner;
websocket.connect('/connect', () => {
subscribeRoomInfo();
subscribeReady(); // 記得加入
websocket.send(`/room-info`, {});
});
function subscribeRoomInfo() {
websocket.subscribe("/user/queue/room-info", (response) => {
response = JSON.parse(response.body);
let roomInfo = response.roomInfo;
owner = roomInfo.owner;
roomId = roomInfo.roomId;
$("#room-id").text(roomId);
let roomMembers = [owner, ...roomInfo.guests]
if (!roomMembers.includes(myUsername)) {
window.location.href = `/rooms/`;
}
let userStatus = response.userStatus;
$("#room .card").each((index, element) => {
let name = roomMembers[index];
if (name === myUsername) {
$(element).addClass("shadow rounded");
}
let closeButton = "";
if (owner === myUsername && name !== myUsername) {
closeButton = `
<button type="button" class="close close-button" onclick="quitRoom('${name}')">
<i class="bi bi-x-circle-fill"></i>
</button>
`;
}
let readyText = "";
if (userStatus[name]) {
readyText = "準備";
}
if (name === owner) {
readyText = "房主";
}
$(element).prop("id", `user-${name}`);
$(element).empty();
if (index < roomMembers.length) {
$(element).html(`
${closeButton}
<img src="https://picsum.photos/200/200" class="card-img-top" alt="...">
<div class="card-body">
<h5 class="card-title">${name}</h5>
<h4 class="card-body user-ready">${readyText}</h4>
</div>
`);
}
else {
$(element).html(`
<div class="card-body">
<h5 class="card-title"></h5>
</div>
`);
}
})
$("#ready").text(owner === myUsername ? '開始' : '準備');
})
}
function subscribeReady() {
// 訂閱
websocket.subscribe("/user/queue/ready", (response) => {
response = JSON.parse(response.body);
let userReadyStatus = response.userReadyStatus;
// 顯示準備狀態的文字,房主額外處理
for (let user in userReadyStatus) {
let readyText = userReadyStatus[user] ? '準備' : '';
if (user === owner) {
readyText = "房主";
}
$(`#user-${user} .user-ready`).text(readyText);
}
if (response.allReady) {
// TODO: 開始遊戲
}
})
}
function ready() {
let myUsername = $("#my-username").val();
// 判斷依據: 看是不是「準備」
let status = $(`#user-${myUsername} .user-ready`).text() === '準備' ? true : false;
websocket.send(`/ready`, {
ready : !status,
});
}
準備功能也好了,整個遊戲已經完成一半了~~ 加油,只剩下一週!