Skip to content

Commit

Permalink
Merge pull request #465 from hanshino:feature/number-gamble
Browse files Browse the repository at this point in the history
Add number gamble game history table, controller functionality, and anti-gambling addiction system
  • Loading branch information
hanshino authored Feb 5, 2024
2 parents 1f4520c + 114d23a commit b657a6d
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 47 deletions.
3 changes: 2 additions & 1 deletion app/locales/zh_tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@
"admin_now": "正在舉辦的遊戲:\n{{ id }}\n{{ name }}",
"sic_bo_rolled": "骰出了 {{ dice1 }}、{{ dice2 }}、{{ dice3 }},總和為 {{ sum }}",
"sic_bo_win": "你選擇的是 {{ option }},獲得 {{ chips }} * {{ payout }} = {{ total }} 顆女神石",
"sic_bo_lose": "你選擇的是 {{ option }},失去 {{ chips }} 顆女神石"
"sic_bo_lose": "你選擇的是 {{ option }},失去 {{ chips }} 顆女神石",
"reach_daily_limit": "防賭博沉迷系統,啟動!"
},
"trade": {
"no_mention": "請標記要交易的對象",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema.createTable("number_gamble_history", table => {
table.increments("id");
table.string("user_id", 64).notNullable();
table.string("option", 16).notNullable();
table.string("dices", 16).notNullable().comment("ex: 1,2,3");
table.integer("chips").notNullable();
table.integer("payout").notNullable().comment("1: big/small, 5: double, 24: triple");
table.tinyint("result").notNullable().comment("0: lose, 1: win");
table.integer("reward").notNullable();

table.index("user_id");
table.timestamps(true, true);
});
};

/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.dropTable("number_gamble_history");
};
68 changes: 61 additions & 7 deletions app/src/controller/application/NumberController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ const { text } = require("bottender/router");
const { sample, get } = require("lodash");
const NumberTemplate = require("../../templates/application/Number");
const i18n = require("../../util/i18n");
const redis = require("../../util/redis");
const { inventory } = require("../../model/application/Inventory");
const numberGambleHistory = require("../../model/application/NumberGambleHistory");
const moment = require("moment");

exports.router = [
text(/^[.#/](猜大小) (?<chips>\d{1,7})$/, privateSicBoHolding),
text(/^[.#/](猜) (?<option>\S+) (?<chips>\d{1,7})$/, userDecide),
text(/^[.#/](猜大小) (?<chips>\d{1,5})$/, privateSicBoHolding),
text(/^[.#/](猜) (?<option>\S+) (?<chips>\d{1,5})$/, userDecide),
];

const optionMapping = {
Expand Down Expand Up @@ -40,10 +44,16 @@ function searchOption(option) {
async function userDecide(context, props) {
const { option, chips } = props.match.groups;
const quoteToken = get(context, "event.message.quoteToken");
const { userId } = context.event.source;
const redisKey = `number:gamble:${userId}`;

if (!userId) {
return;
}

const key = searchOption(option);
if (!key) {
await context.replyText("請選擇正確的選項,例如:大、小、雙、三", { quoteToken });
await context.replyText("請選擇正確的選項,例如:大、小、兩顆、三顆", { quoteToken });
return;
}

Expand All @@ -52,6 +62,33 @@ async function userDecide(context, props) {
return;
}

const query = numberGambleHistory.knex
.where({ user_id: userId })
.where("created_at", ">=", moment().startOf("day").format("YYYY-MM-DD HH:mm:ss"))
.where("created_at", "<=", moment().endOf("day").format("YYYY-MM-DD HH:mm:ss"))
.count("* as count")
.first();

const { count: todayHistoryCount } = await query;

if (todayHistoryCount >= 10) {
await context.replyText(i18n.__("message.gamble.reach_daily_limit"));
return;
}

const isSuccess = await redis.set(redisKey, 1, { NX: true, EX: 10 });
if (!isSuccess) {
// 避免使用者快速下注造成 race condition issue
return;
}

const userMoney = await inventory.getUserMoney(userId);
if (userMoney.amount < parseInt(chips)) {
await unlockUser(userId);
await context.replyText(i18n.__("message.gamble.not_enough_coins"));
return;
}

const dice = rollDice(3);
const sum = dice.reduce((acc, cur) => acc + cur, 0);
const isSmall = sum >= 3 && sum <= 10;
Expand All @@ -75,24 +112,23 @@ async function userDecide(context, props) {
break;
}

let payout = 1;
let payout = 0;
if (result) {
switch (key) {
case "big":
case "small":
payout += 1;
break;
case "double":
payout += 8;
payout += 5;
break;
case "triple":
payout += 180;
payout += 24;
break;
}
}

const messages = [
"[公測] 不管輸贏都不會扣除籌碼,也不會贏得籌碼,僅供測試",
i18n.__("message.gamble.sic_bo_rolled", {
dice1: dice[0],
dice2: dice[1],
Expand All @@ -104,15 +140,28 @@ async function userDecide(context, props) {
const total = parseInt(chips) * payout;
if (result) {
messages.push(i18n.__("message.gamble.sic_bo_win", { option, chips, payout, total }));
await inventory.increaseGodStone({ userId, amount: total, note: "猜大小" });
} else {
messages.push(i18n.__("message.gamble.sic_bo_lose", { option, chips }));
await inventory.decreaseGodStone({ userId, amount: chips, note: "猜大小" });
}

await numberGambleHistory.create({
user_id: userId,
option,
dices: dice.join(","),
chips,
payout,
result: result ? 1 : 0,
reward: total,
});

const replyOption = {};
if (quoteToken) {
replyOption.quoteToken = quoteToken;
}
await context.replyText(messages.join("\n"), replyOption);
await unlockUser(userId);
}

/**
Expand Down Expand Up @@ -140,3 +189,8 @@ function rollDice(times) {
}
return result;
}

async function unlockUser(userId) {
const redisKey = `number:gamble:${userId}`;
return redis.del(redisKey);
}
8 changes: 8 additions & 0 deletions app/src/model/application/NumberGambleHistory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const base = require("../base");

class NumberGambleHistory extends base {}

module.exports = new NumberGambleHistory({
table: "number_gamble_history",
fillable: ["user_id", "option", "dices", "chips", "payout", "result", "reward"],
});
86 changes: 47 additions & 39 deletions app/src/templates/application/Number.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ exports.generatePanel = ({ chips }) => ({
},
{
type: "text",
text: "● 猜其中兩顆骰子同數字,賠率8",
text: "● 猜其中兩顆骰子同數字,賠率5",
size: "sm",
color: "#666666",
},
{
type: "text",
text: "● 猜其中三顆骰子同數字,賠率180",
text: "● 猜其中三顆骰子同數字,賠率24",
size: "sm",
color: "#666666",
},
Expand All @@ -65,7 +65,7 @@ exports.generatePanel = ({ chips }) => ({
{
type: "text",
align: "center",
size: "sm",
size: "xs",
color: "#FF0000",
contents: [
{
Expand All @@ -84,6 +84,14 @@ exports.generatePanel = ({ chips }) => ({
},
],
},
{
type: "text",
align: "center",
text: "為避免誤觸,目前僅限定自行輸入指令\n#猜 大/小/兩顆/三顆 金額",
size: "xs",
color: "#FF0000",
wrap: true,
},
{
type: "box",
layout: "horizontal",
Expand All @@ -105,15 +113,15 @@ exports.generatePanel = ({ chips }) => ({
cornerRadius: "md",
borderColor: "#009688",
paddingAll: "md",
action: {
type: "postback",
data: JSON.stringify({
action: "sicBoGuess",
option: "big",
chips,
}),
displayText: `#猜 大 ${chips}`,
},
// action: {
// type: "postback",
// data: JSON.stringify({
// action: "sicBoGuess",
// option: "big",
// chips,
// }),
// displayText: `#猜 大 ${chips}`,
// },
},
{
type: "box",
Expand All @@ -132,15 +140,15 @@ exports.generatePanel = ({ chips }) => ({
cornerRadius: "md",
borderColor: "#009688",
paddingAll: "md",
action: {
type: "postback",
data: JSON.stringify({
action: "sicBoGuess",
option: "small",
chips,
}),
displayText: `#猜 小 ${chips}`,
},
// action: {
// type: "postback",
// data: JSON.stringify({
// action: "sicBoGuess",
// option: "small",
// chips,
// }),
// displayText: `#猜 小 ${chips}`,
// },
},
{
type: "box",
Expand All @@ -159,15 +167,15 @@ exports.generatePanel = ({ chips }) => ({
cornerRadius: "md",
borderColor: "#009688",
paddingAll: "md",
action: {
type: "postback",
data: JSON.stringify({
action: "sicBoGuess",
option: "double",
chips,
}),
displayText: `#猜 兩顆 ${chips}`,
},
// action: {
// type: "postback",
// data: JSON.stringify({
// action: "sicBoGuess",
// option: "double",
// chips,
// }),
// displayText: `#猜 兩顆 ${chips}`,
// },
},
{
type: "box",
Expand All @@ -186,15 +194,15 @@ exports.generatePanel = ({ chips }) => ({
cornerRadius: "md",
borderColor: "#009688",
paddingAll: "md",
action: {
type: "postback",
data: JSON.stringify({
action: "sicBoGuess",
option: "triple",
chips,
}),
displayText: `#猜 三顆 ${chips}`,
},
// action: {
// type: "postback",
// data: JSON.stringify({
// action: "sicBoGuess",
// option: "triple",
// chips,
// }),
// displayText: `#猜 三顆 ${chips}`,
// },
},
],
spacing: "sm",
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ services:
"MYSQL_USER": "${DB_USER}"
"MYSQL_PASSWORD": "${DB_USER_PASSWORD}"
"MYSQL_ROOT_PASSWORD": "${DB_PASSWORD}"
"TZ": "Asia/Taipei"
healthcheck:
test:
[
Expand Down

0 comments on commit b657a6d

Please sign in to comment.