[遊戲開發] 24點數學遊戲 - 網站製作篇(三)

更新於 發佈於 閱讀時間約 19 分鐘
raw-image


前言

大家好,上一篇介紹了網站的基本架構以及 socket.io 的加入房間與開始遊戲的事件,這篇主要會介紹遊戲主要核心事件,那就讓我們開始吧!


選牌事件

raw-image

上圖解說:遊戲一開始從牌庫抽取五張牌,每張牌只能用一次,為了讓算式等於 24 分別使用了數字牌 8 與 3 搭配乘法來完成。

// client​
socket.emit(SocketEvent.SelectCard, {
roomId: roomInfo?.roomId,
number,
symbol,
});

點選牌的時候由前端發送事件到後端,參數可帶數字或符號。

// server​
socket.on(SocketEvent.SelectCard, ({ roomId, number, symbol }) => {
const { room, msg } = selectCard(roomId, number, symbol);
if (room) {
io.sockets.to(roomId).emit(SocketEvent.RoomUpdate, {
room,
});
} else {
socket.emit(SocketEvent.ErrorMessage, msg);
}
});

後端接收到事件後呼叫 selectCard 方法。

function selectCard(
roomId: string,
number: NumberCard,
symbol: Symbol,
): Response {
try {
const roomIndex = _getCurrentRoomIndex(roomId);
if (roomIndex === -1) return { msg: '房間不存在' };
const selectedCards = _rooms[roomIndex].selectedCards;

if (selectedCards.length === 0 && symbol && symbol !== Symbol.LeftBracket) {
return {
msg: '第一個只能用左括號或數字',
};
}

if (number) {
const currentSelect = selectedCards[selectedCards.length - 1];
// 如果前一個是數字則不能選
if (currentSelect?.number && currentSelect?.number.id !== number.id) {
return {
msg: '數字牌不能連續使用',
};
}

const isExistIndex = selectedCards.findIndex(
s => s.number?.id === number.id,
);

if (isExistIndex !== -1) {
_rooms[roomIndex].selectedCards.splice(isExistIndex, 1);
} else {
_rooms[roomIndex].selectedCards.push({ number });
}
}

if (symbol) {
const lastCard = selectedCards[selectedCards.length - 1];
if (lastCard?.symbol === Symbol.Minus && symbol === Symbol.Minus) {
return {
msg: '減號不能連續用',
};
}

if (lastCard?.symbol === Symbol.Plus && symbol === Symbol.Plus) {
return {
msg: '加號不能連續用',
};
}

if (lastCard?.symbol === Symbol.Times && symbol === Symbol.Times) {
return {
msg: '乘號不能連續用',
};
}

if (lastCard?.symbol === Symbol.Divide && symbol === Symbol.Divide) {
return {
msg: '除號不能連續用',
};
}

if (
lastCard?.symbol === Symbol.LeftBracket &&
[Symbol.Plus, Symbol.Minus].includes(symbol)
) {
return {
msg: '左括號後面無法使用減號或加號',
};
}

if (symbol === Symbol.LeftBracket && lastCard?.number) {
_rooms[roomIndex].selectedCards.push({ symbol: Symbol.Times });
}
_rooms[roomIndex].selectedCards.push({ symbol });
}

return {
room: _rooms[roomIndex],
};

} catch (e) {
return { msg: '發生錯誤,請稍後再試 (select card)' };
}
}

選牌的需要判斷的邏輯主要分為兩個判斷,第一個是數字,數字比較簡單只要判斷前一個牌不是數字就可以,因為數字牌不能連續出,第 26 行那段是判斷如果已經選取了這張牌再次點選的時候要取消選取。

第二個是符號,符號需要判斷是否連續使用,當然上面的程式碼沒有寫到所有符號的交集,不過套件會幫我們判斷掉,差在判斷時機且會影響使用者體驗,是要在選牌的時候判斷還是出牌的時候判斷,因為跳 Toast(提示框) 的時間點不同,如果玩家在點選的時候跳提示訊息體驗會比較好。

第 63 行的判斷左括號前不能使用減號或加號是為了防止洗分數,同樣是 8 X 3 我可以使用 -(8) X -(3) 答案都是 24 這樣分數就會變高。

出牌事件

// client​
socket.emit(SocketEvent.PlayCard, {
roomId: roomInfo?.roomId,
});
// server
socket.on(SocketEvent.PlayCard, ({ roomId }) => {
const { room, msg, isCorrect } = playCard(roomId, playerId);
if (msg) {
socket.emit(SocketEvent.ErrorMessage, msg);
return;
}
if (isCorrect) {
// 答對
io.sockets.to(roomId).emit(SocketEvent.RoomUpdate, {
room,
extra: {
event: SocketEvent.PlayCardResponse,
data: true,
},
});
} else {
// 答錯
io.sockets.to(roomId).emit(SocketEvent.RoomUpdate, {
room,
extra: {
event: SocketEvent.PlayCardResponse,
data: false,
},
});
}
});

後端接收到事件後呼叫 playCard 方法。

function playCard(
roomId: string,
playerId: string,
): Response & { isCorrect?: boolean } {
try {
const roomIndex = _getCurrentRoomIndex(roomId);
if (roomIndex === -1) return { msg: '房間不存在' };

const playerIndex = _getCurrentPlayerIndex(
_rooms[roomIndex].players,
playerId,
);

if (playerIndex === -1) return { msg: '玩家不存在' };
const selectedCards = _rooms[roomIndex].selectedCards;
const answer = calculateAnswer(selectedCards);

if (answer === 24) {
// 使用的數字牌
const numberCards = selectedCards
.filter(c => c.number)
.map(c => c.number?.id);

// 移除數字牌
const newCards = _rooms[roomIndex].players[playerIndex].handCard.filter(
c => !numberCards.includes(c.id),
);

_rooms[roomIndex].players[playerIndex].handCard = newCards;

return {
isCorrect: true,
room: _rooms[roomIndex],
};
}

return {
isCorrect: false,
room: _rooms[roomIndex],
};
} catch (e) {
return { msg: '算式有誤 (play card)' };
}
}

第 16 行呼叫 calculateAnswer 方法計算算式結果。

function calculateAnswer(selectedCards: SelectedCard[]) {
const expression = selectedCards.map(s => {
if (s.number) {
return s.number.value;
}
if (s.symbol) {
return s.symbol;
}
});

try {
const answer = evaluate(expression.join(''));
return answer;
} catch (error) {
throw Error('算式有誤');
}
}

這個方法把使用到的牌組合成一個字串,例如:'(1+2)*8' 然後使用 mathjs 套件幫我們判斷算是合不合理及結果並回傳答案。

在上一段程式碼中第 25 行,我們需要把用到的數字牌從手牌中移除。

計算分數事件

當算式成立後,我們要計算分數,像下面圖片顯示,等動畫跑完後才去打 UpdateScore 的事件,詳細程式碼可以看 main-play-area.tsx。

raw-image
// client​
socket.emit(SocketEvent.UpdateScore, {
roomId: roomInfo?.roomId,
});
socket.on(SocketEvent.UpdateScore, ({ roomId }) => {
const { room, msg, winner } = updateScore(roomId, playerId);
if (room) {
io.sockets.to(roomId).emit(SocketEvent.RoomUpdate, {
room,
extra: {
event: SocketEvent.UpdateScore,
},
});

if (winner) {
io.sockets.to(roomId).emit(SocketEvent.GameOver, {
name: winner.name,
score: winner.score,
});
}
} else {
socket.emit(SocketEvent.ErrorMessage, msg);
}
});

後端接收到是事件後呼叫 updateScore 方法。

function updateScore(
roomId: string,
playerId: string,
): Response & { winner?: Player } {
try {
const roomIndex = _getCurrentRoomIndex(roomId);
if (roomIndex === -1) return { msg: '房間不存在' };
const playerIndex = _getCurrentPlayerIndex(
_rooms[roomIndex].players,
playerId,
);

if (playerIndex === -1) return { msg: '玩家不存在' };
const selectedCards = _rooms[roomIndex].selectedCards;

// 使用的數字牌
const numberCards = selectedCards
.filter(c => c.number)
.map(c => c.number?.id);

// 計算分數
let score = 0;
// +, - 各加一分
const plusAndMinusCount =
selectedCards.filter(
c => c.symbol && [Symbol.Plus, Symbol.Minus].includes(c.symbol),
).length || 0;
score += plusAndMinusCount;

// * 兩分, / 三分
const timesCount =
selectedCards.filter(c => c.symbol === Symbol.Times).length || 0;
const divideCount =
selectedCards.filter(c => c.symbol === Symbol.Divide).length || 0;
score += timesCount * 2;
score += divideCount * 3;

// 如果有兩個 * 額外加一分
if (timesCount >= 2) {
score += 1;
}

// 如果有兩個 / 額外加一分
if (divideCount >= 2) {
score += 1;
}

// 使用到的數字牌數量額外加分
const bonusNumberCardsScore = calculateNumbersScore(numberCards.length);
if (bonusNumberCardsScore) {
score += bonusNumberCardsScore;
}

// 寫入分數
_rooms[roomIndex].players[playerIndex].score += score;
_rooms[roomIndex].selectedCards = [];

const { room, winner, msg } = drawCard(
roomId,
playerId,
numberCards.length,
);

if (msg) {
return { msg };
}

return { room, winner };
} catch (e) {
return { msg: '發生錯誤,請稍後再試 (update score)' };
}
}

計算分數的邏輯很簡單,統計使用到的符號牌數量增加分數以及總共使用的牌數增加分數,依照遊戲規則來算分數。

結語

這篇文章介紹了選牌、出牌、計算分數的事件方法,是整個遊戲的核心,文章比較著重於事件邏輯的部份,關於前端 React UI 邏輯的部份比較沒有介紹,有興趣的朋友們可以看原始碼追一下 code,雖然不是寫得很完美,但他可以動 XD謝謝大家看到這邊,我們下篇見。

要來預告下一個系列的遊戲了🥁🥁🥁,記憶翻牌?

raw-image


留言
avatar-img
留言分享你的想法!
avatar-img
Johnson Huang的沙龍
7會員
10內容數
Johnson Huang的沙龍的其他內容
2025/01/09
這是一款休閒的記憶配對翻牌遊戲,遊戲總共超過 1000 個關卡等你來挑戰,玩家可以選擇各種不同的主題,例如可愛的動物、繽紛的顏色、超鬧的古人、路邊的樹木與草叢,不只是挑戰你的記憶力還要多一點觀察力才能闖關成功...
Thumbnail
2025/01/09
這是一款休閒的記憶配對翻牌遊戲,遊戲總共超過 1000 個關卡等你來挑戰,玩家可以選擇各種不同的主題,例如可愛的動物、繽紛的顏色、超鬧的古人、路邊的樹木與草叢,不只是挑戰你的記憶力還要多一點觀察力才能闖關成功...
Thumbnail
2024/08/28
大家好,要來介紹一款新開發的翻牌遊戲,可以訓練玩家的反應與記憶力。簡單易懂的遊戲規則以及有趣的題組設計。目前遊戲已經上線,歡迎玩家一起來挑戰!遊戲包含無限接關和排行榜機制,讓玩家能與朋友一較高下。下篇文章將探討遊戲開發技術及購買網域的流程。
Thumbnail
2024/08/28
大家好,要來介紹一款新開發的翻牌遊戲,可以訓練玩家的反應與記憶力。簡單易懂的遊戲規則以及有趣的題組設計。目前遊戲已經上線,歡迎玩家一起來挑戰!遊戲包含無限接關和排行榜機制,讓玩家能與朋友一較高下。下篇文章將探討遊戲開發技術及購買網域的流程。
Thumbnail
2024/08/12
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
Thumbnail
2024/08/12
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
Thumbnail
看更多
你可能也想看
Thumbnail
TOMICA第一波推出吉伊卡哇聯名小車車的時候馬上就被搶購一空,一直很扼腕當時沒有趕緊入手。前陣子閒來無事逛蝦皮,突然發現幾家商場都又開始重新上架,價格也都回到正常水準,估計是官方又再補了一批貨,想都沒想就立刻下單! 同文也跟大家分享近期蝦皮購物紀錄、好用推薦、蝦皮分潤計畫的聯盟行銷!
Thumbnail
TOMICA第一波推出吉伊卡哇聯名小車車的時候馬上就被搶購一空,一直很扼腕當時沒有趕緊入手。前陣子閒來無事逛蝦皮,突然發現幾家商場都又開始重新上架,價格也都回到正常水準,估計是官方又再補了一批貨,想都沒想就立刻下單! 同文也跟大家分享近期蝦皮購物紀錄、好用推薦、蝦皮分潤計畫的聯盟行銷!
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
Thumbnail
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
Thumbnail
24點數學遊戲是一款適合小朋友與想動動腦的朋友們的小遊戲,遊戲規則簡單易懂,可訓練邏輯能力。遊戲分為單人與多人模式,可以讓玩家自行挑戰高分或是與其他玩家競爭。算式中不同的數學符號會對應不同的加分機制。遊戲網站連結與專案 repo 也都提供在文章中。
Thumbnail
24點數學遊戲是一款適合小朋友與想動動腦的朋友們的小遊戲,遊戲規則簡單易懂,可訓練邏輯能力。遊戲分為單人與多人模式,可以讓玩家自行挑戰高分或是與其他玩家競爭。算式中不同的數學符號會對應不同的加分機制。遊戲網站連結與專案 repo 也都提供在文章中。
Thumbnail
賓果這款遊戲我想大家應該都不陌生才對,常常會出現在各種活動場合,看誰最快達到指定的連線數量,誰就能得到獎品 賓果遊戲基本概念 這種賓果遊戲使用的卡片由一個5x5的方格組成,每個方格中填入1到25之間的數字,每張卡片上的數字排列是隨機的。 🕹️遊戲規則 準備工作:每個玩家獲得一張或多
Thumbnail
賓果這款遊戲我想大家應該都不陌生才對,常常會出現在各種活動場合,看誰最快達到指定的連線數量,誰就能得到獎品 賓果遊戲基本概念 這種賓果遊戲使用的卡片由一個5x5的方格組成,每個方格中填入1到25之間的數字,每張卡片上的數字排列是隨機的。 🕹️遊戲規則 準備工作:每個玩家獲得一張或多
Thumbnail
原版的官方規則導入記分機制,但因為計算過於繁複,所以一般遊玩時較少採用。本變體規則旨在還原原規則的策略性,並保留平常的遊玩樂趣。 1. 配件準備 4枚不同顏色的棋子(紅、藍、黃、綠),以及一張標記0~15的場地。 2. 記分方式 一開始所有棋子都在0的位置。每一局結束時,贏家以外的所有人拿出
Thumbnail
原版的官方規則導入記分機制,但因為計算過於繁複,所以一般遊玩時較少採用。本變體規則旨在還原原規則的策略性,並保留平常的遊玩樂趣。 1. 配件準備 4枚不同顏色的棋子(紅、藍、黃、綠),以及一張標記0~15的場地。 2. 記分方式 一開始所有棋子都在0的位置。每一局結束時,贏家以外的所有人拿出
Thumbnail
Ae 小技巧:卡牌旋轉 動態後記系列會記錄一些我在製作中的記錄,可能是分解動畫、小技巧、發想、腳本......等等。 每篇都是小短篇,就是補充用的小筆記,沒有前後順序,可跳著閱讀。
Thumbnail
Ae 小技巧:卡牌旋轉 動態後記系列會記錄一些我在製作中的記錄,可能是分解動畫、小技巧、發想、腳本......等等。 每篇都是小短篇,就是補充用的小筆記,沒有前後順序,可跳著閱讀。
Thumbnail
從博弈觀點切入並結合數值盤點,觀察大富翁GO的運營模式。
Thumbnail
從博弈觀點切入並結合數值盤點,觀察大富翁GO的運營模式。
Thumbnail
建立幾個變數如下,最上面兩個變數值為清單值 接下來分別設定球1位置到左上角落、設定球2位置到右上角落、設定球3位置到左下角落、設定球4位置到右下角落 當螢幕初始化的時候,設定玩家球的X、Y座標和大小,並將玩家球的初始顏色,設定成(變數_顏色清單)中.....
Thumbnail
建立幾個變數如下,最上面兩個變數值為清單值 接下來分別設定球1位置到左上角落、設定球2位置到右上角落、設定球3位置到左下角落、設定球4位置到右下角落 當螢幕初始化的時候,設定玩家球的X、Y座標和大小,並將玩家球的初始顏色,設定成(變數_顏色清單)中.....
Thumbnail
麻將胡牌規則 麻將胡牌指的是贏下該局麻將遊戲,只要手上的16張牌+1張別人打出的牌可以組成「5個坎+1個對子」即算胡牌,由打出可以胡牌的玩家支付遊戲點數給胡牌的玩家;手上的16張牌+上1張自己摸進來的牌組成「5個坎+1個對子」即算自摸,自摸的玩家向其餘三位玩家收取遊戲點數。
Thumbnail
麻將胡牌規則 麻將胡牌指的是贏下該局麻將遊戲,只要手上的16張牌+1張別人打出的牌可以組成「5個坎+1個對子」即算胡牌,由打出可以胡牌的玩家支付遊戲點數給胡牌的玩家;手上的16張牌+上1張自己摸進來的牌組成「5個坎+1個對子」即算自摸,自摸的玩家向其餘三位玩家收取遊戲點數。
Thumbnail
這份教材由打醒卡塔羅、聊心卡作者Susu製作,禁止轉載內容避免引發爭議。 1. 看整體牌陣推測大方向。 抽牌後先看整體牌陣的位置出的是什麼牌,有沒有哪個部分訊息不足需補牌
Thumbnail
這份教材由打醒卡塔羅、聊心卡作者Susu製作,禁止轉載內容避免引發爭議。 1. 看整體牌陣推測大方向。 抽牌後先看整體牌陣的位置出的是什麼牌,有沒有哪個部分訊息不足需補牌
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News