Tôi đã viết game Bầu Cua chơi qua Livestream như thế nào!

Nếu các bạn hay theo dõi mình qua Fanpage Tôi Đi Code Dạo hoặc Youtube Channel, các bạn sẽ thấy lâu lâu mình có hay làm 1 số minigame trao quà bằng cách đặt sòng bầu cua thông qua kênh chat.

Game bầu cua đặt cược qua comment – Made by Code Dạo

 

Game này chơi khá là vui, tăng tương tác nhiều vì bà con phải comment, lại rất dễ tham gia, chỉ có đang xem stream là được.

Do vậy, mình chia sẻ cho các bạn biết cách để làm 1 game như thế này. Các bạn có thể dựa vào đó để độ chế ra thành game tương tự nhé!

Kiến trúc hệ thống

Trước khi đi sâu vào code, chúng ta có thể tìm hiểu kiến trúc hệ thống trước, để biết cần code những phần nào, code như thế nào nha!

Hệ thống có 1 kiến trúc vô cùng đơn giản đến mức không thể đơn giản hơn, như 1 trang web thông thường:

  • Front-end (VueJS): Hiển thị bàn bầu cua, số lượng đặt, bảng xếp hạng người dùng
  • Back-end  Facebook (NodeJS): Ban đầu, mình làm cái này chơi trên Facebook. Do đó back-end là 1 webhook, nhận thông báo từ Facebook khi có 1 người chơi comment. Sau đó đọc comment và thông báo cho front-end biết là user đó là ai, đã đặt những gì
  • Back-end Youtube (NodeJS): Về sau, mình muốn mang trò này qua Youtube. Bản thân Youtube không có Webhook, nhưng có Livestream API, cho phép mình lấy danh sách các comment trong 1 livestream. Do vậy mình chạy code gọi API này mỗi vài giây để lấy comment và xử lý.
  • Socket.io: Để hệ thống hoạt động real-time, back-end và front-end giao tiếp với nhau thông qua WebSocket (Thật ra chỉ có 1 chiều là back-end gửi thông tin về front-end thôi). Mình dùng socket.io luôn cho lẹ!

Xử lý phía back-end

Như đã nói, với Facebook, mình có chạy 1 web app nho nhỏ, làm webhook. (Bạn nào quên webhook là gì có thể xem lại bài Làm Facebook ChatBot nha).

Ta sẽ nhận thông tin từ Facebook qua webhook và xử lý.


app.post('/webhook', async(req, res) => {
const hookObject = req.body;
console.log(JSON.stringify(hookObject, null, 2));
await process.processHook(hookObject);
res.status(200).send("OK");
});

view raw

fb-hook.js

hosted with ❤ by GitHub

Sau đó, ta tiếp tục lấy avatar của người dùng, lấy comment và  gửi cho client qua WebSocket.


async processHook(hookObject) {
for (const entry of hookObject.entry) {
for (const change of entry.changes) {
this.processEntryChange(change);
}
}
}
async processEntryChange(change) {
if (change.field !== 'feed') return;
// New comment only
const changeValue = change.value;
if (changeValue.post_id !== this.postId) return;
if (changeValue.item !== 'comment' || changeValue.verb !== 'add') return;
var { sender_id, sender_name, message } = changeValue;
const bets = this.getBetFromComment(message);
console.log(bets);
if (bets.length === 0) return;
const avatar = await api.getAvatar(sender_id);
for (const bet of bets) {
const playerAndBet = {
id: sender_id,
name: sender_name,
avatar,
bet: bet.bet,
choice: bet.choice
};
console.log(playerAndBet);
this.emitter.emit('newBet', playerAndBet);
}
}

view raw

process-hook.js

hosted with ❤ by GitHub

Ở cuối, các bạn sẽ thấy emitter gửi thông tin tới cho client, event là newBet. Ở client, khi nhận được event là newBet, mình sẽ lấy thông tin đó ra và đặt cược cho người chơi.


import io from 'socket.io-client';
const socket = io('http://localhost:3002');
socket.on('connect', () => { console.log('connected') });
socket.on('newBet', function(newBet) {
const { id, name, avatar, bet, choice } = newBet;
const player = new Player(id, name, avatar);
store.dispatch('placeBet', { player, bet, choice });
});

 

Về cơ chế là như vậy, còn 1 đoạn khá hay ho đó là nhận và lọc input từ người chơi. Giả sử người chơi nói “em muốn đặt 10 gà“, mình dùng regex để lọc số lượng đặt là 10, con đặt là gà như sau.


getBetFromComment(comment) {
const cleanedComment = textParser.charToNumber(
textParser.removeUnicode(comment.toLowerCase()));
const regex = /(\d+)( |)?(cop|bau|ga|tom|ca|cua)/;
const matches = cleanedComment.match(regex);
if (!matches) return [];
const bets = matches.map(match => {
const execMatch = regex.exec(match);
const bet = parseInt(execMatch[1], 10);
const choice = choiceToNumberMap[execMatch[3]];
return { bet, choice };
});
return bets;
}

view raw

get-bet.js

hosted with ❤ by GitHub

Front-end

Ở front-end thì không có quá nhiều thứ phức tạp cần xử lý. Do đợt đấy mình học VueJS nên thử dùng Vue + Vuex để viết hệ thống cho vui luôn.

Phần mình tưởng là khó nhất – hiển thị bàn bầu cua thì hoá ra lại …khá dễ. Chắc do hình ảnh gốc ban đầu đã chia khá đều rồi, chỉ việc code lại xíu thôi~

6 ô trong bàn bầu cua thật ra chỉ là 6 cell trong 1 cái css-grid thôi, dùng vừa khít luôn


<div>
<div class="bc-table">
<!– Ảnh bầu cua –>
<img src="./../assets/baucua.jpg" alt class="image bc-image" />
<div class="bc-overlay">
<!– 6 cells tương ứng với 6 con –>
<div class="boards" :key="key" v-for="(cell, key) in board">
<transition-group
tag="div"
enter-active-class="animated bounceInDown"
leave-active-class="animated fadeOutDown"
>
<!– Mỗi hình tròn là 1 token của user đã đặt –>
<token v-for="(token, userId) in cell" :key="userId" v-bind="token"></token>
</transition-group>
</div>
</div>
</div>
</div>

view raw

table.html

hosted with ❤ by GitHub

Ngoài ra, do bản chất game có khá nhiều trạng thái, nhiều state nên cần xử lý, lưu trữ nhiều. May mắn là Vuex giúp mình làm chuyện này khá tốt, viết code bằng tay để quản lý state chắc phê lòi luôn!

State của hệ thống cũng khá đơn giản. Mình lưu danh sách người chơi và điểm để làm bảng xếp hạng, số người đã đặt cược, trạng thái của 3 con xí ngầu


const store = new Vuex.Store({
state: {
players: {},
status: WAITING_FOR_BET,
dices: [1, 2, 3],
board: {
1: {},
2: {},
3: {},
4: {},
5: {},
6: {}
}
}
})

view raw

state.js

hosted with ❤ by GitHub

Sau khi lắc xí ngầu xong, mình tổng kết số điểm, upload lên Firebase để mọi người tham gia có thể vào tra điểm.


finishGame({ commit, state }) {
commit('removeLosers');
// Đếm những quân xí ngầu thắng
var diceDic = countBy(state.dices, dice => dice);
for (const key in diceDic) {
const multiplier = diceDic[key] + 1;
const winners = Object.values(state.board[key]);
// Cộng điềm cho những người thắng
for (const winner of winners) {
commit('updatePlayerPoint', { playerId: winner.id, changedValue: (winner.bet * multiplier) });
}
}
commit('changeStatus', FINISHED);
// Upload danh sách người chơi lên Firebase đến tra điểm
const syncPlayer = Object.values(state.players)
.filter(p => p.id && p.name && p.avatar)
.sort((p1, p2) => p2.point p1.point);
firebase.set(syncPlayer);
}

view raw

finish-game.js

hosted with ❤ by GitHub

Trang tra điểm Bầu Cua

Để làm trang tra điểm này, các bạn muốn dùng gì cũng được. Mình lười nên viết Angular 1, bỏ thành 1 file HTML thuần rồi upload lên Github là xong. Đỡ phải lo build lung tung mệt.

Tạm kết

Đấy, hệ thống trông hay hay, lạ lạ nhưng viết không quá phức tạp như bạn tưởng đâu. Bản thân mình viết tầm 3 ngày nên các bạn viết chắc cỡ 2 ngày là xong ấy mà.

Nếu còn thắc mắc, các bạn có thể xem source code của mình tại đây: github.com/conanak99/baucua. Các bạn có thể Fork hay làm trò gì tuỳ thích, hoặc để lại 1 star cho mình nha.

Nếu có hứng thú về chủ đề thiết kế/code ra 1 hệ thống chuyên sâu thế này thì các bạn cứ để lại comment nha. Nếu nhiều bạn quan tâm mình sẽ làm nhiều hơn ahihi!

 

Bonus: Clip có khuôn mặt đập trai + demo của mình

3 thoughts on “Tôi đã viết game Bầu Cua chơi qua Livestream như thế nào!”

  1. Mình rất thích những series có tính chuyên môn và ứng dụng cao thế này, mong bạn tiếp tục phát huy trong thời gian tới nhé!

    Like

  2. Để nhận cái event bắn tới webhook khi user comment cần App review đúng ko bạn? Mình subscibe ở phần web hook setting mà backend vẫn ko nhận đc request gì

    Like

  3. Cho mình hỏi bạn dùng webhook nào của fb để get được comment vậy? hình như không phải Messenger webhook thì phải. tks bạn

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s