之前的 client 端處理的是建立/加入房間/開始遊戲的部分,今天我們要實作的部分是遊戲開始之後的頁面和一些玩家會有的操作:玩家抽牌和玩家出牌。因為處理連線部分比較複雜,所以那個部分會推遲到明天完成。
以下就是今天的成果:
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="style.css">
  <title>簡易網頁遊戲</title>
</head>
<body>
  <div class="game-container">
    <div class="player-info">
      <h2>對手資訊</h2>
      <p>對手血量: <span id="opponent-health"></span></p>
    </div>
    <div class="player-info">
      <h2>玩家資訊</h2>
      <p>玩家血量: <span id="player-health"></span></p>
      <p>玩家手牌: <span id="player-hand"></span></p>
    </div>
    <div class="commands">
      <button onclick="updateGameInfo(gameData1)">模擬開始</button>
      <button onclick="updateGameInfo(gameData2)">模擬抽牌</button>
      <button onclick="updateGameInfo(gameData3)">模擬出牌</button>
    </div>
  </div>
  <div class="chat-container">
    <h2>聊天</h2>
    <div id="messages" class="chat-messages">
    </div>
    <div class="chat-input">
      <input type="text" id="message-input" placeholder="輸入訊息...">
      <button id="send-button">送出</button>
    </div>
  </div>
  <script src="script.js"></script>
</body>
</html>
// 初始化之後
const gameData1 = {
  playerHealth: 50,
  playerHand: [2],
  opponentHealth: 50
}
// 抽牌
const gameData2 = {
  playerHand: [2,5],
}
// 出牌
const gameData3 = {
  playerHand: [2],
  opponentHealth: 45
}
// 將遊戲資訊的 JSON 資料更新到頁面上
function updateGameInfo(gameData) {
  // 更新玩家血量
  if (gameData.playerHealth) document.getElementById("player-health").textContent = gameData.playerHealth
    
  // 更新玩家手牌數
  if (gameData.playerHand) document.getElementById("player-hand").textContent = gameData.playerHand.join(', ')
    
  // 更新對手血量
  if (gameData.opponentHealth) document.getElementById("opponent-health").textContent = gameData.opponentHealth
}
const sendMessage = () => {
  const messageInput = document.getElementById("message-input");
  const message = messageInput.value;
  if (message.trim() !== "") {
    // 顯示訊息在聊天訊息容器中
    const chatMessages = document.querySelector(".chat-messages");
    const messageElement = document.createElement("div");
    messageElement.textContent = message;
    chatMessages.appendChild(messageElement);
    chatMessages.scrollTop = chatMessages.scrollHeight;
    // 清空輸入欄位
    messageInput.value = "";
  }
}
// 處理送出按鈕的點擊事件
document.getElementById("send-button").addEventListener("click", sendMessage)
document.getElementById("message-input").addEventListener('keydown', (e) => {
  // console.log(e.code)
  if (e.code === 'Enter' || e.code === 'NumpadEnter') sendMessage()
}, false);
body {
  font-family: Arial, sans-serif;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;
  height: 100vh;
}
.game-container {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 800px;
  border: 1px solid #ccc;
  border-radius: 5px;
  padding: 20px;
}
.player-info {
  width: 50%;
  margin-right: 20px;
}
.chat-container {
  padding: 1rem 2rem;
  width: 50%;
}
.chat-messages {
  height: 80vh;
  overflow-y: auto;
}
.chat-input {
  width: 100%;
  display: flex;
  justify-content: space-between;
}
input[type="text"] {
  flex-grow: 1;
  padding: 5px;
  font-size: 16px;
}
button {
  padding: 5px 10px;
  font-size: 16px;
}
今天實作的部分主要著重在幾個部分:
const gameData = {
  playerHealth: 50,
  playerHand: [2],
  opponentHealth: 50
}
在前端頁面中主要使用 gameData 呈現整個遊戲狀態,這裡已經簡化到只有三個資訊
會設計成這樣子是為了符合「玩家的第一人稱視角」,這樣子前端的操作邏輯會比較直觀一點,相對來說,後端送過來的資料就要進行一次轉換,但這樣的成本很小是可以接受的。
另外這邊也會發現 gameData 不包含玩家牌庫、對手牌庫、對手手牌的資訊,這個是為了遊戲公平性考量。當然專案初期開發可以不用考慮到這麼多,但因為前面已經有做資料轉換的動作,就順便一起把這部分也做掉了。