從0開始 3.真正的聊天室 (singleton) - MUD Game

Charlie avatar
By Charlie
at 2019-12-05T15:53

Table of Contents

大家好! 歡迎收看從0開始的 MUD 開發第 3 集

在上一集我們已經實作了多執行緒的 telnet echo server
雖然我們的 server 可以支援複數使用者同時連線
但是使用者之間並沒有辦法互相溝通
所以這一集我們要講的是如何透過 java 內建的資料結構(HashMap)收集, 管理使用者
同時建立一個 PlayerManager, 藉由 PlayerManager 提供一個統一的操作介面

// PlayerManager.class
// ✂--------------請沿虛線剪下--------------
package core;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;

public class PlayerManager {
// 宣告 static 的變數儲存 PlayerManager 的 instance
// 這個寫法可以讓 PlayerManager 這個 class 被載入時, 立刻執行自己的建構子
private static PlayerManager instance = new PlayerManager();
// 用來儲存所有使用者的 Map
private static Map<String, Player> players;

// 重要! 一個 private 的建構子, 確保沒有其他程式可以 new PlayerManager()
private PlayerManager() {
players = new ConcurrentHashMap<>();
}

// 一個 static 的 getInstance() 方法取代原有的建構子
// 原本常見的 PlayerManager pm = new PlayerManager();
// 寫法改為 PlayerManager pm = PlayerManager.getInstance();
public static PlayerManager getInstance() {
return instance;
}

// 以下是真正提供給其他程式使用的方法
// 包括了新增、移除使用者的 addPlayer, removePlayer
// 檢查使用者是否上線的 isPlayerOnline
// 對所有使用者下指令的 forEach
public void addPlayer(String name, Player player) {
players.put(name, player);
}

public void removePlayer(String name) {
players.remove(name);
}

public boolean isPlayerOnline(String name) {
return players.containsKey(name);
}

public void forEach(BiConsumer<? super String, ? super Player> biConsumer) {
players.forEach(biConsumer);
}

}
// ✂--------------請沿虛線剪下--------------


有了 PlayerManager 的支援, 原本的 Player.run() 功能也得到了進一步的擴充


// 在原有的 Player class 中新增一個 String playerName 儲存使用者的 ID
private String playerName;

public void run() {
try {
// 剛寫好的 PlayerManager 在這邊派上用場, 讓他來負責處理關於使用者的事務
PlayerManager pm = PlayerManager.getInstance();
write("歡迎來到 telnet chat server! (多執行緒版本)");
write("請問您的大名?");

// 一個簡易判斷線上是否有同名使用者的小程式
// 如果有同名使用者則一直執行無限迴圈, 直到使用者輸入了不衝突的新 ID
String name;
while (pm.isPlayerOnline(name = read())) {
write("線上已經有一個叫做" + name + "的使用者了! 請重新輸入");
}

// 當使用者輸入了獨一無二的 ID 後, 先對所有線上使用者廣播
// 再將目前的使用者加入 players
playerName = name;
pm.forEach((k, v) -> v.write(playerName + "上線囉"));
pm.addPlayer(playerName, this);
write(playerName + "你好! 歡迎進入聊天室");

while (true) {
String input = read();
if ("quit".equalsIgnoreCase(input)) {
socket.close();
// 別忘了使用者下線後必須把他移除
pm.removePlayer(playerName);
pm.forEach((k, v) -> v.write(playerName + "離開了"));
break;
}
// 原本只是呼叫個別使用者的 write()
// 現在改為使用 forEach 呼叫所有使用者的 write() 達到 chat 的效果
pm.forEach((k, v) -> v.write("【閒聊】" + playerName + "說:" + input));
}
} catch (IOException ignored) {}
}

以下是三開連線的畫面
如何? MUD 的味道是不是開始慢慢出現了? XD
https://i.imgur.com/9l27OV9.png

--
╔═ ═╦╦═════╦═════╗
◤◤◤ ╠╣飛鳥ももこ╠═╗ ║
║╚═════╝ ╚═╦═╣
║╔══════╗╔═╩═╣
█◤ ╠╣Momoko Asuka╠╝ ║
◣◢◣◢╩╩══════╩════╝

--
Tags: 線上

All Comments

從0開始 2.多執行緒

Gary avatar
By Gary
at 2019-12-05T13:36
今天我們要來正式進入 multi-thread (多執行緒)的部分了 觀察之前的程式片段可以發現到有兩個 while 迴圈 一個負責建立連線, 另一個負責 echo // 建立連線的迴圈 while (true) { socket = server.accept(); BufferedReader ...

請問該如何賺寶石最划算

Daph Bay avatar
By Daph Bay
at 2019-12-05T13:08
最近去買了30張底紫去合 雖然沒紫保 但都有配寶石合 爆了26張QQ 想說484人品問題 又買4張底黑 配寶石合也全爆 這樣噴了4000多QQ 寶石算一算才2萬顆 想要寶石真的一定要有保存卷嗎 還是一定要買包卡划算QQ 看到一堆本隊的雷公黑 都拉不到嗚嗚 - ...

OWL 2020 隊伍不精確總移動里程數

Dorothy avatar
By Dorothy
at 2019-12-04T23:16
看到明年OWL要全球開戰,好奇心起來想要看看哪隊最爽哪隊最累,於是用Excel跟Great Circle Mapper手工算了一下距離。 http://i.imgur.com/y4XFSJT.jpg 結論:東區不是很爽就是很累,歐洲隊伍會飛機坐到升天,亞洲四隊因為有特別安排反而沒想像中移動那麼多。 不過 ...

馬爾杜克道具店

Dorothy avatar
By Dorothy
at 2019-12-04T22:18
https://www.youtube.com/watch?v=zXPa32fVrKo https://i.imgur.com/5k7XUpi.png 一個over kill的概念,如果一般的道具店是差不多斬20點, 馬爾杜克道具店的斬殺的上限應該是你的牌堆總數量+回合上限時間 前置作業是用索羅門抽個0費馬爾 ...

這次修正控球

Hamiltion avatar
By Hamiltion
at 2019-12-04T19:41
這次控球修正到底是在搞啥? 有些卡有上修;有些卡沒動;有些卡還下修 稍微花了點時間整理,拜託辣椒看一下, 或是有大戶跟小幫手回報一下,非常感恩 (上修紅色;沒動或持平不上色;下修綠色) 中信兄弟 萊福力 BB/9 2.5,這次沒動維持82 (對比羅里奇 整個問號?) 紐維拉 BB/9 2.1,這次沒動維持 ...