国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當(dāng)前位置: 首頁 > news >正文

哪些網(wǎng)站可以做h5搜索引擎排名優(yōu)化

哪些網(wǎng)站可以做h5,搜索引擎排名優(yōu)化,主機租賃平臺,做視頻網(wǎng)站需要哪些條件目錄 一、約定前后端交互接口的參數(shù) 1、房間準備就緒 (1)配置 websocket 連接路徑 (2)構(gòu)造 游戲就緒 的 響應(yīng)對象 2、“落子” 的請求和響應(yīng) (1)“落子” 請求對象 (2)“落子…

目錄

一、約定前后端交互接口的參數(shù)

1、房間準備就緒

(1)配置 websocket 連接路徑

(2)構(gòu)造 游戲就緒 的 響應(yīng)對象

2、“落子” 的請求和響應(yīng)

(1)“落子” 請求對象

(2)“落子” 響應(yīng)對象

二、UserMapper

三、處理 websocket 請求、返回的響應(yīng)(GameAPI)

1、afterConnectionEstablished

(1)通知玩家玩家進入房間(noticeGameReady)

(2)afterConnectionEstablished

1、首先通過Session拿到玩家信息?

2、通過玩家信息拿到游戲房間

3、判斷用戶是不是多開了

4、設(shè)置房間上線

5、把玩家加入對應(yīng)的房間中

6、處理不存在的操作

2、handleTextMessage

3、handleTransportError

(1)noticeThatUserWin(通知獲勝者)

4、afterConnectionClosed

四、Room

1、棋盤

2、自動注入

3、putChess(處理落子請求、構(gòu)造返回響應(yīng))

4、打印棋盤

5、checkWinner(判斷輸贏)

行:頂點為左邊

列:頂點在上邊

主對角線:頂點在左上

副對角線:頂點在右上

五、前端代碼的邏輯處理

1、處理游戲就緒響應(yīng)

2、處理落子請求

3、處理落子響應(yīng)

六、梳理前后端交互流程

七、代碼以及線上云服務(wù)器的URL


一、約定前后端交互接口的參數(shù)

1、房間準備就緒

? ? ? ? 當(dāng)我們從 游戲大廳頁面 跳轉(zhuǎn)到 游戲房間頁面,這也意味著:游戲大廳頁面的 websocket 連接斷開了,跳轉(zhuǎn)到 游戲房間頁面,要建立一個新的 websocket 連接。

? ? ? ? 那么也就說明我們之前的 websocket 連接不能用了,這里新的 websocket 連接最好使用新的 URL,不要和游戲大廳的一樣,這樣能起到?“解耦合”?的效果。

? ? ? ? 匹配成功后,是服務(wù)器主動給客戶端發(fā)起響應(yīng),客戶端就不必發(fā)起請求了。約定如圖:

(1)配置 websocket 連接路徑

? ? ? ? 前端代碼如下:(使用動態(tài)路徑,更加靈活,方便后部署在服務(wù)器上)

let websocketUrl = "ws://" + location.host + "/game";
let websocket = new WebSocket(websocketUrl);

? ? ? ? 后端代碼:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate MatchAPI matchAPI;@Autowiredprivate GameAPI gameAPI;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(matchAPI, "/findMatch").addInterceptors(new HttpSessionHandshakeInterceptor());webSocketHandlerRegistry.addHandler(gameAPI, "/game").addInterceptors(new HttpSessionHandshakeInterceptor());}
}

注意:配置路徑的同時,還要拿到之前HttpSession信息,保存在 新的 websocket連接中。

(2)構(gòu)造 游戲就緒 的 響應(yīng)對象

? ? ? ? 后端需要構(gòu)造一個響應(yīng)對象,把其轉(zhuǎn)換為JSON格式的文本信息,再發(fā)送給客戶端,響應(yīng)對象如下:

// 客戶端連接到游戲房間后,返回的響應(yīng)
@Data
public class GameReadyResponse {private String message;private boolean ok;private String reason;private String roomId;private int thisUserId;private int thatUserId;private int whiteUser;
}

2、“落子” 的請求和響應(yīng)

? ? ? ? 雙方玩家進入游戲房間后,也就是準備就緒后,那就要開始對弈了,所以,我們要針對玩家 “落子”的操作,構(gòu)造請求和響應(yīng)。

? ? ? ? 因為玩家是主動落子,所以要發(fā)送給服務(wù)器請求;服務(wù)器接收到請求后,也需要處理這個請求,構(gòu)造響應(yīng),把最新棋盤布局發(fā)給另一個玩家,同時這個響應(yīng)也要發(fā)送給我,讓我知道到對方落子了。

(1)“落子” 請求對象

// 這個類表示落子請求
@Data
public class GameRequest {private String message;private int userId;private int row;private int col;
}

(2)“落子” 響應(yīng)對象

// 這個類表示一個落子響應(yīng)
@Data
public class GameResponse {private String message;private int userId;private int row;private int col;private int winner;
}

二、UserMapper

? ? ? ? 在游戲結(jié)束之后,我們要給玩家結(jié)算勝敗,那么玩家的天梯積分和游戲場數(shù)相對也會改變,說明我們也就要對數(shù)據(jù)庫進行操作了,這里增加兩個修改數(shù)據(jù)庫的操作,代碼如下:

@Mapper
public interface UserMapper {// 根據(jù)用戶名,查詢用戶的詳情信息,用于登錄功能@Select("select * from user where user_name = #{username}")User selectByName(String username);// 往數(shù)據(jù)庫里插入信息,用于注冊功能@Insert("insert into user values (null, #{username}, #{password}, 1000, 0, 0);")void register(User userInfo);// 總比賽場數(shù) + 1    獲勝場數(shù) + 1    天梯積分 + 30@Update("update user set total_count = total_count + 1, " +"win_count = win_count + 1, score = score + 30 where user_id = #{userId}")void userWin(int userId);// 總比賽場數(shù) + 1    獲勝場數(shù) 不變    天梯積分 - 30@Update("update user set total_count = total_count + 1, " +"score = score - 30 where user_id = #{userId}")void userLose(int userId);
}

? ? ? ? 一個是針對玩家 游戲勝利 后的修改操作,一個是針對玩家 對局失敗 后的修改操作。


三、處理 websocket 請求、返回的響應(yīng)(GameAPI)

? ? ? ? 這里主要涉及的方法有四個:

afterConnectionEstablished在建立 websocket 連接時,要做的處理。

handleTextMessage接收玩家的 請求,返回對應(yīng)的 響應(yīng)。

handleTransportError當(dāng) websocket 連接出現(xiàn)錯誤時,要做的處理。

afterConnectionClosed當(dāng) websocket 連接關(guān)閉時,要做的處理。

1、afterConnectionEstablished

? ? ? ? 建立了新的websocket連接,需要把就緒的玩家加入到房間中,這里就是處理玩家加入房間的邏輯。

(1)通知玩家玩家進入房間(noticeGameReady)

? ? ? ? 這里把通知玩家的邏輯單獨封裝成一個方法,代碼如下:

    private void noticeGameReady(Room room, User thisUser, User thatUser) throws IOException {GameReadyResponse resp = new GameReadyResponse();resp.setMessage("gameReady");resp.setOk(true);resp.setReason("");resp.setRoomId(room.getRoomId());resp.setThisUserId(thisUser.getUserId());resp.setThatUserId(thatUser.getUserId());resp.setWhiteUser(room.getWhiteUser());// 把當(dāng)前的響應(yīng)數(shù)據(jù)傳回給對應(yīng)的玩家WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thisUser.getUserId());webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));}

? ? ? ? 構(gòu)造對應(yīng)的響應(yīng)數(shù)據(jù),然后通過 Session 發(fā)送給客戶端。

(2)afterConnectionEstablished

? ? ? ? 建立連接時,并不可以直接把玩家加入房間,需要經(jīng)過一系列的校驗。

1、首先通過Session拿到玩家信息?

? ? ? ? 但拿到后還要判斷用戶是不是null

        // 1、先獲取到用戶的身份信息(從 HttpSession 里拿到)User user = (User) session.getAttributes().get("user");if (user == null) {resp.setOk(false);resp.setReason("用戶尚未登錄!");String jsonString = objectMapper.writeValueAsString(resp);session.sendMessage(new TextMessage(jsonString));return;}
2、通過玩家信息拿到游戲房間

? ? ? ? 也要對房間進行校驗,如果為空,就要返回對應(yīng)響應(yīng)給前端。

        // 2、判定當(dāng)前用戶是否已經(jīng)進入房間(拿著房間管理器進行查詢)Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {// 如果為 null,當(dāng)前沒有找到對應(yīng)的房間,該玩家還沒匹配到resp.setOk(false);resp.setReason("用戶尚未匹配到");String jsonString = objectMapper.writeValueAsString(resp);session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(jsonString)));return;}
3、判斷用戶是不是多開了

? ? ? ? 進行對局時,發(fā)現(xiàn)該玩家已經(jīng)在房間里了,說明前面已經(jīng)有玩家在登錄游戲中,我再登錄,就視為多開行為;還有是玩家一邊在游戲大廳,一邊是在游戲房間,也視為多開。

        // 3、判定當(dāng)前是不是多開(該用戶是不是已經(jīng)在其他地方進行游戲了)//    前面多準備了一個 OnlineUserManagerif (onlineUserManager.getFromGameHall(user.getUserId()) != null|| onlineUserManager.getFromGameRoom(user.getUserId()) != null) {// 如果一個賬號,一邊是在游戲大廳,一邊是在游戲房間,也視為多開~resp.setOk(true);resp.setReason("禁止多開游戲頁面");resp.setMessage("repeatConnection");String jsonString = objectMapper.writeValueAsString(resp);session.sendMessage(new TextMessage(jsonString));return;}
4、設(shè)置房間上線

? ? ? ? 經(jīng)過上面一系列校驗后,沒有問題,才能設(shè)置玩家在房間中上線。

        // 4、經(jīng)過一些列校驗都沒問題后,設(shè)置當(dāng)前玩家上線(房間中上線)onlineUserManager.enterGameRoom(user.getUserId(), session);
5、把玩家加入對應(yīng)的房間中

? ? ? ? 把玩家加入對應(yīng)的房間中,這里設(shè)置先手的操作是先加入房間的玩家。

? ? ? ? 當(dāng)雙方玩家都加入房間后,要對客戶端進行通知,構(gòu)造對應(yīng)的響應(yīng),發(fā)送回去。

        synchronized (room) {// 5、把兩個玩家加入到游戲房間中//    前面的創(chuàng)建房間/匹配過程,是在 game_hall.html 頁面中完成的//    因此前面匹配到對手之后,需要經(jīng)過頁面跳轉(zhuǎn),來到 game_room.html 才算正式進入游戲房間(才算玩家準備就緒)//    當(dāng)前這個邏輯是在 game_room.html 頁面加載的時候進行的//    執(zhí)行到當(dāng)前邏輯,說明玩家已經(jīng)頁面跳轉(zhuǎn)成功了//    頁面跳轉(zhuǎn),其實是個大活~(很有可能出現(xiàn) “失敗” 的情況的)if (room.getUser1() == null) {// 第一個玩家尚未加入房間// 就把當(dāng)前連上 WebSocket 的玩家作為 user1,加入到房間中room.setUser1(user);// 把先連入房間的玩家作為先手方room.setWhiteUser(user.getUserId());log.info("玩家 " + user.getUsername() + " 已經(jīng)準備就緒! 作為玩家1");return;}if (room.getUser2() == null) {// 如果進入這個房間,說明玩家1 已經(jīng)加入房間,現(xiàn)在要把玩家2 加入房間room.setUser2(user);log.info("玩家 " + user.getUsername() + " 已經(jīng)準備就緒! 作為玩家2");// 當(dāng)兩個玩家都加入成功之后,就要讓服務(wù)器,給這兩個玩家都返回 WebSocket 的響應(yīng)數(shù)據(jù)// 通知兩個玩家說:游戲雙方都已經(jīng)準備好了// 通知玩家1noticeGameReady(room, room.getUser1(), room.getUser2());// 通知玩家2noticeGameReady(room, room.getUser2(), room.getUser1());return;}}
6、處理不存在的操作

? ? ? ? 經(jīng)過上述操作后,把玩家加入房間中,雙方玩家都已經(jīng)就緒了,這時候還有玩家在嘗試連接這個房間,就要給客戶端提示報錯。(理論上這種情況是不存在的)

        // 6、此處如果又有一個玩家嘗試連接同一個房間,就會提示報錯//    這種情況理論上是不存在的,為了讓程序更加健壯,還是做一個判定和提示resp.setOk(false);resp.setReason("當(dāng)前房間已滿,您不能加入房間");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));

2、handleTextMessage

? ? ? ? 在處理請求和構(gòu)造響應(yīng)時,根據(jù)用戶的Session拿到用戶信息,然后根據(jù)用戶再拿到房間,在房間中進行落子的操作。

? ? ? ? 其中落子的請求處理和響應(yīng)構(gòu)造放在Room里了。

    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 1、先從 Session 拿到當(dāng)前用戶的身份信息User user = (User) session.getAttributes().get("user");if (user == null) {log.info("[handleTextMessage] 當(dāng)前玩家尚未登錄");return;}// 2、根據(jù) 玩家id 獲取到房間對象Room room = roomManager.getRoomByUserId(user.getUserId());// 3、通過 room對象 來處理這次具體的請求room.putChess(message.getPayload());}

3、handleTransportError

? ? ? ? 出現(xiàn)異常時,根據(jù)Session拿到該用戶的信息,再去房間管理器中找該用戶的Session,看連接中的Session和房間管理器的Session是不是一樣的

????????是一樣的,說明是我們預(yù)期想要在房間管理器中刪除的;如果不一樣,說明用戶可能是多開,防止該用戶退出導(dǎo)致前面用戶的Session被刪除。

? ? ? ? 當(dāng)我掉線后,就要判定對方獲勝,通知封裝成了一個方法,邏輯操作在該方法中完成。

    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {User user = (User) session.getAttributes().get("user");if (user == null) {//此處就簡單處理,在斷開連接的時候就不給客戶端返回響應(yīng)了return;}WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());// 加上這個判定,目的是為了在多開的情況下,第二個用戶退出連接動作,導(dǎo)致第一個登錄在線的用戶會話刪除if (session == exitSession) {onlineUserManager.exitGameRoom(user.getUserId());log.info("當(dāng)前這個用戶 {}", user.getUsername() + " 游戲房間連接異常");}// 通知對手獲勝noticeThatUserWin(user);}

(1)noticeThatUserWin(通知獲勝者)

? ? ? ? 1、拿到當(dāng)前玩家,找到該房間

? ? ? ? 2、根據(jù)房間找到對手

? ? ? ? 3、根據(jù)房間管理器找到對手的Session

? ? ? ? 4、構(gòu)造響應(yīng),告訴客戶端,你贏了

? ? ? ? 5、更新玩家的分數(shù)信息

    private void noticeThatUserWin(User user) throws IOException {// 1、根據(jù)當(dāng)前玩家,找到對應(yīng)房間,再找到當(dāng)前玩家的對手Room room = roomManager.getRoomByUserId(user.getUserId());if (room == null) {// 這個情況意味著房間已經(jīng)被釋放,也就沒有對手了log.info("當(dāng)前房間已經(jīng)釋放, 無需通知對手");return;}// 2、根據(jù)房間找到對手User thatUser = (user == room.getUser1()) ? room.getUser2() : room.getUser1();// 3、找到對手的在線狀態(tài)WebSocketSession webSocketSession = onlineUserManager.getFromGameRoom(thatUser.getUserId());if(webSocketSession == null) {// 這就意味著對手也掉線了log.info("對手也已經(jīng)掉線了, 無需通知");return;}// 4、構(gòu)造一個響應(yīng),來通知對手,你是獲勝方GameResponse resp = new GameResponse();resp.setMessage("putChess");resp.setUserId(thatUser.getUserId());resp.setWinner(thatUser.getUserId());webSocketSession.sendMessage(new TextMessage(objectMapper.writeValueAsString(resp)));// 5、更新玩家的分數(shù)信息int winUserId = thatUser.getUserId();int loseUserId = user.getUserId();userMapper.userWin(winUserId);userMapper.userLose(loseUserId);// 6、釋放房間對象roomManager.remove(room.getRoomId(), room.getUser1().getUserId(), room.getUser2().getUserId());}

4、afterConnectionClosed

? ? ? ? 關(guān)閉連接后,處理的邏輯也和連接錯誤一樣。

    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {User user = (User) session.getAttributes().get("user");if (user == null) {//此處就簡單處理,在斷開連接的時候就不給客戶端返回響應(yīng)了return;}WebSocketSession exitSession = onlineUserManager.getFromGameRoom(user.getUserId());// 加上這個判定,目的是為了在多開的情況下,第二個用戶退出連接動作,導(dǎo)致第一個登錄在線的用戶會話刪除if (session == exitSession) {onlineUserManager.exitGameRoom(user.getUserId());log.info("當(dāng)前這個用戶 {}", user.getUsername() + " 已經(jīng)離開游戲房間");}// 通知對手獲勝noticeThatUserWin(user);}

四、Room

1、棋盤

? ? ? ? 在Room對象中,我們把落子的下標存在一個二維數(shù)組中:

    // 行 | 列private static final int MAX_ROW = 15;private static final int MAX_COL = 15;// 這個二維數(shù)組表示棋盤(服務(wù)端這邊數(shù)組的狀態(tài)有三種,但客戶端那邊只有兩種,主要是用來判斷當(dāng)前棋盤有沒有棋子,用來避免一個問題:同一個位置重復(fù)落子的情況)// 約定:// 1) 使用 0 表示當(dāng)前位置未落子(初始化好的二維數(shù)組,相對于 全都是0)// 2) 使用 1 表示 user1 的落子位置// 3) 使用 2 表示 user2 的落子位置private int[][] board = new int[MAX_ROW][MAX_COL];

2、自動注入

? ? ? ? 因為要在落子的請求后,可能會決出勝負,也就是說,處理落子請求時,我們也需要維護roomManager、onlineUserManager,這也意味著我們需要注入這兩個對象。還有更新用戶信息—— 維護userMapper。

? ? ? ? 仔細想想,我們能使用 @Autowired 注入這兩個對象嗎?如果要使用 @Autowired 注解,也意味著 Room對象 也要被Spring管理起來,但 Room對象 能使用Spring的注解,交給Spring進行管理嗎?

? ? ? ? 明顯是不能的,因為 如果加入對應(yīng)的注解,那么被Spring管理起來的這個對象,就是單例的了。

? ? ? ? Room對象 能是單例的嗎?明顯不能吧,因為有很多玩家都在進行游戲時,這時候房間也會有很多,肯定不可能讓Room變成單例的。

? ? ? ? 那能不能既實現(xiàn)多例,又能注入這兩個對象呢?當(dāng)然也有辦法——自動注入。

? ? ? ? 在Spring啟動方法里,添加context:

@SpringBootApplication
public class SpringGobangApplication {public static ConfigurableApplicationContext context;public static void main(String[] args) {context = SpringApplication.run(SpringGobangApplication.class, args);}}

? ? ? ? 通過Room的構(gòu)造方法里,手動注入這兩個對象。

    // 引入 OnlineUserManager
//    @Autowiredprivate OnlineUserManager onlineUserManager;// 引入 RoomManager, 用于房間銷毀
//    @Autowiredprivate RoomManager roomManager;// 引入 UserMapper, 用于更新用戶數(shù)據(jù)
//    @Autowiredprivate UserMapper userMapper;public Room() {// 構(gòu)造 Room 的時候生成一個唯一的字符串表示房間 id// 使用 UUID 來作為房間 idroomId = UUID.randomUUID().toString();// 通過入口類記錄中的 context,來手動獲取到前面的 RoomManager 和 OnlineUserManageronlineUserManager = SpringGobangApplication.context.getBean(OnlineUserManager.class);roomManager = SpringGobangApplication.context.getBean(RoomManager.class);userMapper = SpringGobangApplication.context.getBean(UserMapper.class);}

3、putChess(處理落子請求、構(gòu)造返回響應(yīng))

? ? ? ? 1、根據(jù)請求,拿到當(dāng)前落子的位置;然后判斷當(dāng)前這個棋子,是玩家1落的棋子、還是玩家2落的棋子。

? ? ? ? 2、在控制臺上打印出棋盤信息(方便觀察)

? ? ? ? 3、進行勝負判斷,此時也可能是勝負未分(也封裝成一個方法了,后面介紹)

? ? ? ? 4、給房間的所有客戶端都返回響應(yīng)(根據(jù)房間的用戶,分別獲取不同用戶的Session,有了Session,就能對客戶端發(fā)送消息了;當(dāng)獲取不到用戶的Session,說明用戶下線了,那就判斷對方贏)

? ? ? ? 5、當(dāng)數(shù)據(jù)發(fā)送完畢后,再判斷現(xiàn)在是不是勝負已分,如果比賽已經(jīng)結(jié)束了,那也要更新玩家數(shù)據(jù),這個房間也沒有必要再存在了,要進行銷毀。

    // 通過這個方法來處理一次落子操作// 要做的事情:// 1、記錄當(dāng)前落子的位置// 2、進行勝負判定// 3、給客戶端返回響應(yīng)public void putChess(String reqJson) throws IOException {// 1、記錄當(dāng)前落子的位置GameRequest request = objectMapper.readValue(reqJson, GameRequest.class);GameResponse response = new GameResponse();// 當(dāng)前這個棋子是玩家1落子,還是玩家2落子;根據(jù)玩家1 和 玩家2 來決定往數(shù)組中放1還是2int chess = request.getUserId() == user1.getUserId() ? 1 : 2;int row = request.getRow();int col = request.getCol();// 判斷當(dāng)前位置是不是已經(jīng)有棋子了if (board[row][col] != 0) {// 在客戶端已經(jīng)針對重復(fù)落子進行判定過了,此處為了程序更加穩(wěn)健,在服務(wù)器再判斷一次log.info("當(dāng)前位置 row: " + row + " col: " + col + " 已經(jīng)有棋子了");}board[row][col] = chess;// 2、打印出當(dāng)前的棋盤信息,方便來觀察局勢,也方便后面驗證勝負關(guān)系的判定printBoard();// 3、進行勝負判定int winner = checkWinner(row, col, chess);// 4、給房間的所有客戶端都返回響應(yīng)response.setMessage("putChess");response.setUserId(request.getUserId());response.setRow(row);response.setCol(col);response.setWinner(winner);// 要想給用戶發(fā)送 WebSocket 數(shù)據(jù),就需要獲取到這個用戶的 WebSocketSessionWebSocketSession session1 = onlineUserManager.getFromGameRoom(user1.getUserId());WebSocketSession session2 = onlineUserManager.getFromGameRoom(user2.getUserId());// 萬一當(dāng)前查到的會話為空(玩家下線了), 特殊處理一下if (session1 == null) {// 玩家1 下線了,直接認為 玩家2 獲勝response.setWinner(user2.getUserId());log.info("玩家: {}", user1.getUsername() + " 下線, 直接判定玩家1獲勝");}if (session2 == null) {// 玩家2 下線了,直接認為 玩家1 獲勝response.setWinner(user1.getUserId());log.info("玩家: {}", user2.getUsername() + " 下線, 直接判定玩家1獲勝");}// 把響應(yīng)構(gòu)造成 JSON 字符串,通過 Session進行傳輸String respJson = objectMapper.writeValueAsString(response);if (session1 != null) {session1.sendMessage(new TextMessage(respJson));}if (session2 != null) {session2.sendMessage(new TextMessage(respJson));}// 5、如果當(dāng)時勝負已分, 這個房間就已經(jīng)失去存在的意義了,就把這個房間從房間管理器中刪除if (response.getWinner() != 0) {log.info("游戲結(jié)束, 當(dāng)前房間即將銷毀! rommId= {}", roomId + " 獲勝方為: " + response.getWinner());// 更新獲勝方和失敗方的信息int winUserId = response.getWinner();int loseUserId = (response.getWinner() == user1.getUserId()) ? user2.getUserId() : user1.getUserId();userMapper.userWin(winUserId);userMapper.userLose(loseUserId);// 銷毀房間roomManager.remove(roomId, user1.getUserId(), user2.getUserId());}}

4、打印棋盤

? ? ? ? 兩層for循環(huán),遍歷數(shù)組

    private void printBoard() {// 打印出棋盤log.info("[打印棋盤信息] " + "roomId: {}", roomId);System.out.println("=======================================================");for (int r = 0; r < MAX_ROW; r++) {for (int c = 0; c < MAX_COL; c++) {// 針對一行之內(nèi)的若干列,不要打印換行System.out.print(board[r][c] + " ");}// 遍歷完一行之后,再換行System.out.println();}System.out.println("=======================================================");}

5、checkWinner(判斷輸贏)

? ? ? ? 判斷輸贏,也就是要檢查 是否有 “五子連珠” 的情況。

? ? ? ? 五子連珠的情況可能是 行有5個子、列有5個子、主對角線有5個子、副對角線有5個子

? ? ? ? 那怎么進行判斷呢?其實非常簡單,在我們拿到落子的下標后,就固定一個頂點,判斷這個頂點所在的這一行往下有沒有五子連珠的情況(列、對角線同理),沒有就繼續(xù)下一點,直到該坐標。

? ? ? ? 其他情況也同理。

? ? ? ? 了解了如何判斷,那現(xiàn)在我們就固定一下這個 “頂點” 吧。

行:頂點為左邊

列:頂點在上邊

主對角線:頂點在左上

副對角線:頂點在右上

? ? ? ? 具體實現(xiàn)代碼如下:(下面的這種方法可能會有空指針異常,但不要緊,我們進行捕獲,再continue進入下一個循環(huán)就好了)

    // 使用這個方法,來判定當(dāng)前落子后,是否分出勝負// 約定://  1) 如果 玩家1 獲勝,就返回 玩家1 的userId//  2) 如果 玩家2 獲勝,就返回 玩家2 的userId//  3) 如果 勝負未分,就返回 0private int checkWinner(int row, int col, int chess) {// 1、檢查所有的行//   先遍歷這五種情況for (int c = col - 4; c <= col; c++) {// 針對其中的一種情況,來判定這五個棋子是不是連在一起了// 不光是這五個字得連著,而且還得和玩家落的子是一樣(才算是獲勝)try {if (board[row][c] == chess&& board[row][c + 1] == chess&& board[row][c + 2] == chess&& board[row][c + 3] == chess&& board[row][c + 4] == chess) {// 構(gòu)成 五子連珠! 勝負已分!log.info("行 五子連珠! 勝負已分!");return chess == 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出現(xiàn)數(shù)組下標越界的情況,就在這里直接忽略這個異常,繼續(xù)下一次循環(huán)判斷continue;}}// 2、檢查所有的列for(int r = row - 4; r <= row; r++) {try {if (board[r][col] == chess&& board[r + 1][col] == chess&& board[r + 2][col] == chess&& board[r + 3][col] == chess&& board[r + 4][col] == chess) {// 構(gòu)成 五子連珠! 勝負已分!log.info("列 五子連珠! 勝負已分!");return chess == 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出現(xiàn)數(shù)組下標越界的情況,就在這里直接忽略這個異常,繼續(xù)下一次循環(huán)判斷continue;}}// 3、檢查所有主對角線 (左對角線,從左上往右下)for (int r = row - 4, c = col - 4; r <= row && c <= col; r++, c++) {try {if (board[r][c] == chess&& board[r + 1][c + 1] == chess&& board[r + 2][c + 2] == chess&& board[r + 3][c + 3] == chess&& board[r + 4][c + 4] == chess) {// 構(gòu)成 五子連珠! 勝負已分!log.info("主對角線 五子連珠! 勝負已分!");return chess == 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出現(xiàn)數(shù)組下標越界的情況,就在這里直接忽略這個異常,繼續(xù)下一次循環(huán)判斷continue;}}// 4、檢查所有副對角線 (右對角線, 從左下往右上)for (int r = row - 4, c = col + 4; r <= row && c >= col; r++, c--) {try {if (board[r][c] == chess&& board[r + 1][c - 1] == chess&& board[r + 2][c - 2] == chess&& board[r + 3][c - 3] == chess&& board[r + 4][c - 4] == chess) {// 構(gòu)成 五子連珠! 勝負已分!log.info("副對角線 五子連珠! 勝負已分!");return chess == 1 ? user1.getUserId() : user2.getUserId();}} catch (ArrayIndexOutOfBoundsException e) {// 如果出現(xiàn)數(shù)組下標越界的情況,就在這里直接忽略這個異常,繼續(xù)下一次循環(huán)判斷continue;}}// 勝負未分,就直接返回 0 了return 0;}

? ? ? ? 如果經(jīng)過上述邏輯判斷,還沒返回對應(yīng)的贏家Id,那么就說明是勝負未分,返回0.


五、前端代碼的邏輯處理

1、處理游戲就緒響應(yīng)

? ? ? ? 雙方進入房間成功后,就會建立一個房間頁面的websocket連接,其代碼如下:

//
// 初始化 websocket
//
let websocketUrl = "ws://" + location.host + "/game";
let websocket = new WebSocket(websocketUrl);websocket.onopen = function () {console.log("連接游戲房間成功");
}websocket.close = function () {console.log("和游戲服務(wù)器連接斷開");
}websocket.onerror = function () {console.log("和服務(wù)器的連接出現(xiàn)異常");
}websocket.onbeforeunload = function () {websocket.close();
}//處理服務(wù)器返回的響應(yīng)數(shù)據(jù) (游戲就緒響應(yīng))
websocket.onmessage = function (event) {console.log("[handlerGameReady] " + event.data);let resp = JSON.parse(event.data);if (!resp.ok) {alert("連接游戲失敗! reason: " + resp.reason);// 如果出現(xiàn)連接失敗的情況,回到游戲大廳// location.assign("/game_hall.html");location.replace("/game_hall.html");return;}if (resp.message == 'gameReady') {// 把后端返回的數(shù)據(jù)放進 gameInfo 對象中g(shù)ameInfo.roomId = resp.roomId;gameInfo.thisUserId = resp.thisUserId;gameInfo.thatUserId = resp.thatUserId;// 判斷自己是不是先手gameInfo.isWhite = (resp.whiteUser == resp.thisUserId);// 初始化棋盤initGame();//設(shè)置顯示區(qū)域的內(nèi)容(輪到誰落子了)setScreenText(gameInfo.isWhite);} else if (resp.message == 'repeatConnection') {console.log("檢測到游戲多開!");alert("檢測到游戲多開, 請使用其他賬戶進行登錄!");// location.assign("/login.html");location.replace("/login.html");return;}
}

? ? ? ? 接收服務(wù)器的響應(yīng)處理主要在 websocket.message() 上,首先,拿到后端返回來的響應(yīng),將其轉(zhuǎn)為 js 對象(原本是JSON文本)。

? ? ? ? 判斷?resp.ok,如果是false,說明連接異常,跳轉(zhuǎn)到登錄頁面(可能是還沒登錄,就直接訪問游戲房間頁面)。

? ? ? ? 然后判斷resp.message,如果是 'gameReady',說明該玩家加入房間成功,進行判斷先手,設(shè)置我方和對方的userId,初始化棋盤,設(shè)置顯示區(qū)域(輪到誰落子了)

? ? ? ? resp.message,如果是 'repeatConnection',說明玩家有多開行為,那就給個提示彈窗,返回到登錄頁面。

2、處理落子請求

????????前端發(fā)送的請求有用戶Id、落子位置。

? ? ? ? 這里有一個點擊事件,點擊對應(yīng)位置,就會落子,然后給后端發(fā)送一個落子請求,代碼如下:

? ? ? ? 這里也就會處理:如果不是我落子,你在棋盤上再怎么點也沒有用,只有到我落子了,才能繼續(xù)落子;還有一種是游戲結(jié)束了,我也不能繼續(xù)在這個棋盤上落子了。

    chess.onclick = function (e) {if (over) {return;}if (!me) {return;}let x = e.offsetX;let y = e.offsetY;// 注意, 橫坐標是列, 縱坐標是行l(wèi)et col = Math.floor(x / 30);let row = Math.floor(y / 30);// 客戶端的棋盤狀態(tài)只有兩種,主要是用來判斷當(dāng)前棋盤有沒有棋子,用來避免一個問題:同一個位置重復(fù)落子的情況if (chessBoard[row][col] == 0) {// 發(fā)送坐標給服務(wù)器, 服務(wù)器要返回結(jié)果send(row, col);}}function send(row, col) {let req = {message: 'putChess',userId: gameInfo.thisUserId,row: row,col: col};websocket.send(JSON.stringify(req));}

3、處理落子響應(yīng)

? ? ? ? 返回來的響應(yīng),如果 resp.message != "putChess",就說明響應(yīng)數(shù)據(jù)有問題。

? ? ? ? 然后判斷這個響應(yīng)是不是自己落的子,如果是自己落的子,就繪制一個自己的棋子,如果不是自己落的子,那就說明是對方落的子。

? ? ? ? 落子之后,要把對應(yīng)的位置設(shè)為1,說明該位置已經(jīng)有棋子了,不能再在這個位置落子。

????????后端返回的響應(yīng)有用戶Id、落子位置、輸贏狀態(tài)。落子后,就要判斷輸贏了,如果返回resp.winner 是我的 userId,那就說明我贏了,反之則是對方贏了,然后改變對應(yīng)的提示信息。還有一種情況,就是0,說明勝負未分。

? ? ? ? 勝負分出后,就給頁面新增一個按鈕,點擊后返回到游戲大廳

    // 之前 websocket.onmessage 主要是用來處理了游戲就緒響應(yīng),在游戲就緒之后,初始化完畢之后,也就不再有這個 游戲就緒響應(yīng) 了// 就在這個 initGame 內(nèi)部修改 websocket.onmessage 方法~~,讓這個方法里面針 對落子響應(yīng) 進行處理websocket.onmessage = function (event) {console.log("[handlerPutChess]: " + event.data);let resp = JSON.parse(event.data);if (resp.message != "putChess") {console.log("響應(yīng)類型錯誤");return;}// 先判定當(dāng)前這個響應(yīng)時否為自己邏的子,還是對方落的子if (resp.userId == gameInfo.thisUserId) {// 我自己落的子// 根據(jù)我自己棋子的顏色,來繪制一個棋子oneStep(resp.col, resp.row, gameInfo.isWhite);} else if (resp.userId == gameInfo.thatUserId) {// 我的對手落的子oneStep(resp.col, resp.row, !gameInfo.isWhite);} else {// 響應(yīng)錯誤! userId 是有問題的console.log("[handlerPutChess resp userId 錯誤");return;}// 給對應(yīng)的位置設(shè)為 1,方便后續(xù)邏輯判定當(dāng)前位置是否已經(jīng)有棋子了chessBoard[resp.row][resp.col] = 1;// 交換雙方的落子輪次me = !me;setScreenText(me);// 判定游戲是否結(jié)束let screenDiv = document.querySelector('#screen');if (resp.winner != 0) {if (resp.winner == gameInfo.thisUserId) {// alert('你贏了!');screenDiv.innerHTML = '你贏了!';} else if (resp.winner == gameInfo.thatUserId) {// alert('你輸了');screenDiv.innerHTML = '你輸了!';} else {alert('winner 字段錯誤 ' + resp.winner);}// 回到游戲大廳// location.assign('/game_hall.html');// 增加一個按鈕,讓玩家點擊之后,再回到游戲大廳~let backButton = document.createElement('button');backButton.innerHTML = '回到游戲大廳'backButton.className = 'button';  // 添加樣式類backButton.onclick = function() {location.replace('/game_hall.html');}let fatherDiv = document.querySelector('.container>div');fatherDiv.appendChild(backButton);}}

六、梳理前后端交互流程

? ? ? ? 雙方點擊開始匹配按鈕。

????????進入游戲房間:

? ? ? ? 進入房間后,建立 websocket 連接,代碼邏輯如:

? ? ? ? 我落子后,服務(wù)器接收到請求,會給雙方都發(fā)送響應(yīng)。

? ? ? ? 前后端代碼處理邏輯,前后端就依次往下看代碼,這里就不展開了


七、代碼以及線上云服務(wù)器的URL

? ? ? ? URL:http://120.79.61.184:9090/login.html

? ? ? ? Gitte:spring-gobang · taotao/Studying JavaEE Advanced - 碼云 - 開源中國 (gitee.com)

http://m.aloenet.com.cn/news/34203.html

相關(guān)文章:

  • 廣州做網(wǎng)站建設(shè)哪家專業(yè)網(wǎng)站關(guān)鍵詞怎么設(shè)置
  • 上海閔行做網(wǎng)站seo關(guān)鍵詞推廣優(yōu)化
  • 淘寶客cms網(wǎng)站怎么做免費開店的電商平臺
  • 網(wǎng)站建設(shè)消費者群體分析南京疫情最新消息
  • 阿里云如何添加新網(wǎng)站app開發(fā)自學(xué)
  • 邯鄲做網(wǎng)站推廣找誰國內(nèi)廣告聯(lián)盟平臺
  • 新疆做網(wǎng)站多少錢seo優(yōu)化關(guān)鍵詞
  • 能打開任何網(wǎng)站瀏覽器下載百度店鋪
  • 莆田網(wǎng)站建設(shè)網(wǎng)絡(luò)營銷策劃書格式
  • 臨時手機號注冊網(wǎng)站百度top排行榜
  • 延安有哪些做網(wǎng)站的公司wifi優(yōu)化大師下載
  • 女女做那個動漫視頻網(wǎng)站做網(wǎng)絡(luò)推廣一個月的收入
  • 一級a做爰片免費網(wǎng)站中國片互聯(lián)網(wǎng)營銷有哪些方式
  • 品牌推廣網(wǎng)站怎樣做關(guān)鍵詞優(yōu)化排名查詢
  • 網(wǎng)站菜單導(dǎo)航怎么做網(wǎng)站seo優(yōu)化免費
  • 恒網(wǎng)做的網(wǎng)站網(wǎng)站排名優(yōu)化服務(wù)公司
  • wordpress 設(shè)置數(shù)據(jù)庫南陽網(wǎng)站seo
  • 太原seo網(wǎng)站排名網(wǎng)站優(yōu)化包括
  • 成都網(wǎng)站建設(shè)哪里好點seo1短視頻網(wǎng)頁入口營銷
  • 深圳網(wǎng)站制作公司咨詢小紅書搜索關(guān)鍵詞排名
  • 亞馬遜虛擬主機做網(wǎng)站最新清遠發(fā)布
  • 怎么給自己的網(wǎng)站做模版全網(wǎng)營銷推廣平臺有哪些
  • 羅湖網(wǎng)站建設(shè)羅湖網(wǎng)站設(shè)計seo是什么意思為什么要做seo
  • 網(wǎng)站要咋做2022年最新熱點素材
  • 免費做h5的網(wǎng)站展示型網(wǎng)站有哪些
  • 做室內(nèi)設(shè)計的網(wǎng)站有哪些內(nèi)容數(shù)字營銷服務(wù)商seo
  • 日本設(shè)計創(chuàng)意網(wǎng)站web網(wǎng)站設(shè)計
  • 萊蕪網(wǎng)站優(yōu)化招聘網(wǎng)seo搜索如何優(yōu)化
  • 學(xué)用mvc做網(wǎng)站重慶seo網(wǎng)絡(luò)推廣優(yōu)化
  • vue做移動端網(wǎng)站與pc端有什么區(qū)別網(wǎng)站推廣軟件免費版下載