[遊戲開發] 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


留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Johnson Huang的沙龍 的其他內容
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
這篇文章介紹了網站的整體架構以及開發時所使用的工具和套件,包括 Next.js、Tailwind CSS 和 socket.io 等。文章回顧了程式碼的重構與優化,幫助開發者提高工作效率,適合希望深入瞭解前端開發和網站架構的讀者。
24點數學遊戲是一款適合小朋友與想動動腦的朋友們的小遊戲,遊戲規則簡單易懂,可訓練邏輯能力。遊戲分為單人與多人模式,可以讓玩家自行挑戰高分或是與其他玩家競爭。算式中不同的數學符號會對應不同的加分機制。遊戲網站連結與專案 repo 也都提供在文章中。
由於最近比較忙碌停更了好一陣子,關於 Flutter 系列目前難產中XD,在開發 Flutter 遊戲之前,我正在進行一個桌遊專案的開發,因為我很喜歡玩桌遊,如果在外面無聊的時候打開手機就可以與朋友一同連線遊玩那該有多好(雖然現在手遊很多),所以我就開始了這個專案,目標是上架各式各樣的桌遊.....
前一篇我們介紹了遊戲模板的基本架構,在這篇文章中會記錄目前的開發進度,主要先把遊戲核心邏輯寫完,再慢慢完成其他功能,在這系列的文章中,我不會太聚焦於寫程式的部分,如果想看程式內容的話,可以到我的 github 上看喔,讓我們開始吧!
前一篇說到遊戲開發的契機以及介紹了開發工具與官方提供的遊戲模板,在這一篇文章中,會開始進行遊戲的設計與製作,目標是上架到 Google Play 與 App Store 平台上,讓我們開始吧!
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
這篇文章介紹了網站的整體架構以及開發時所使用的工具和套件,包括 Next.js、Tailwind CSS 和 socket.io 等。文章回顧了程式碼的重構與優化,幫助開發者提高工作效率,適合希望深入瞭解前端開發和網站架構的讀者。
24點數學遊戲是一款適合小朋友與想動動腦的朋友們的小遊戲,遊戲規則簡單易懂,可訓練邏輯能力。遊戲分為單人與多人模式,可以讓玩家自行挑戰高分或是與其他玩家競爭。算式中不同的數學符號會對應不同的加分機制。遊戲網站連結與專案 repo 也都提供在文章中。
由於最近比較忙碌停更了好一陣子,關於 Flutter 系列目前難產中XD,在開發 Flutter 遊戲之前,我正在進行一個桌遊專案的開發,因為我很喜歡玩桌遊,如果在外面無聊的時候打開手機就可以與朋友一同連線遊玩那該有多好(雖然現在手遊很多),所以我就開始了這個專案,目標是上架各式各樣的桌遊.....
前一篇我們介紹了遊戲模板的基本架構,在這篇文章中會記錄目前的開發進度,主要先把遊戲核心邏輯寫完,再慢慢完成其他功能,在這系列的文章中,我不會太聚焦於寫程式的部分,如果想看程式內容的話,可以到我的 github 上看喔,讓我們開始吧!
前一篇說到遊戲開發的契機以及介紹了開發工具與官方提供的遊戲模板,在這一篇文章中,會開始進行遊戲的設計與製作,目標是上架到 Google Play 與 App Store 平台上,讓我們開始吧!
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
Thumbnail
這篇文章介紹了網站的整體架構以及開發時所使用的工具和套件,包括 Next.js、Tailwind CSS 和 socket.io 等。文章回顧了程式碼的重構與優化,幫助開發者提高工作效率,適合希望深入瞭解前端開發和網站架構的讀者。
Thumbnail
winbet小編今天來跟大家介紹骰寶這個遊戲,骰寶規則相當簡單3分鐘就能學會,再要做到骰寶攻略,學會了以上技巧玩家已經離骰寶破解不遠了,大家聽完可能會好奇哪裡可以去玩骰寶線上,想要體驗好玩有趣的骰寶遊戲不妨來winbet試試看!
Thumbnail
分享一個猜數字的遊戲題目,給予提示讓玩家找出正確的四位數密碼。
Thumbnail
winbet小編將介紹21點遊戲規則,並分享親身實測得出的21點教學,包括21點莊家優勢、21點算牌技巧和21點術語。此外,也會提供玩家在實戰中累積經驗的建議。另外,玩家也會得知21點線上遊戲的推薦。歡迎來winbet試試看!
Thumbnail
介紹一個優質的遊戲知識部落格:遊戲設計中藥鋪,其中「Game Design 資源分享表」十分推薦遊戲開發者閱讀。另外提到Gamker攻壳是一個專業的遊戲評鑑頻道,其深入的評論幫助作者入坑《健身環大冒險》。作者在後記也分享了自己在遊戲開發上的經歷和挑戰。
Thumbnail
24點數學遊戲是一款適合小朋友與想動動腦的朋友們的小遊戲,遊戲規則簡單易懂,可訓練邏輯能力。遊戲分為單人與多人模式,可以讓玩家自行挑戰高分或是與其他玩家競爭。算式中不同的數學符號會對應不同的加分機制。遊戲網站連結與專案 repo 也都提供在文章中。
Thumbnail
一、了解思維 二、大量體驗 三、拆解架構 四、找出樂趣
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
本篇介紹單人遊戲的核心架構與邏輯,涵蓋發牌、抽牌、出牌及遊戲結算等重要步驟。文章也詳細介紹了使用 socket.io 建立連線的過程,並說明如何利用 React Hooks 管理遊戲狀態,提及後端伺服器如何處理玩家加入房間的事件,並簡要介紹了房間資訊的管理,此文將分為多篇進一步介紹遊戲事件部分。
Thumbnail
這篇文章介紹了網站的整體架構以及開發時所使用的工具和套件,包括 Next.js、Tailwind CSS 和 socket.io 等。文章回顧了程式碼的重構與優化,幫助開發者提高工作效率,適合希望深入瞭解前端開發和網站架構的讀者。
Thumbnail
winbet小編今天來跟大家介紹骰寶這個遊戲,骰寶規則相當簡單3分鐘就能學會,再要做到骰寶攻略,學會了以上技巧玩家已經離骰寶破解不遠了,大家聽完可能會好奇哪裡可以去玩骰寶線上,想要體驗好玩有趣的骰寶遊戲不妨來winbet試試看!
Thumbnail
分享一個猜數字的遊戲題目,給予提示讓玩家找出正確的四位數密碼。
Thumbnail
winbet小編將介紹21點遊戲規則,並分享親身實測得出的21點教學,包括21點莊家優勢、21點算牌技巧和21點術語。此外,也會提供玩家在實戰中累積經驗的建議。另外,玩家也會得知21點線上遊戲的推薦。歡迎來winbet試試看!
Thumbnail
介紹一個優質的遊戲知識部落格:遊戲設計中藥鋪,其中「Game Design 資源分享表」十分推薦遊戲開發者閱讀。另外提到Gamker攻壳是一個專業的遊戲評鑑頻道,其深入的評論幫助作者入坑《健身環大冒險》。作者在後記也分享了自己在遊戲開發上的經歷和挑戰。
Thumbnail
24點數學遊戲是一款適合小朋友與想動動腦的朋友們的小遊戲,遊戲規則簡單易懂,可訓練邏輯能力。遊戲分為單人與多人模式,可以讓玩家自行挑戰高分或是與其他玩家競爭。算式中不同的數學符號會對應不同的加分機制。遊戲網站連結與專案 repo 也都提供在文章中。
Thumbnail
一、了解思維 二、大量體驗 三、拆解架構 四、找出樂趣