labag 3.1.0 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/labag.js +1 -1
- package/dist/test.d.ts +3 -1
- package/dist/test.js +51 -30
- package/package.json +1 -1
- package/src/labag.ts +1 -1
- package/src/test.ts +66 -29
package/dist/labag.js
CHANGED
package/dist/test.d.ts
CHANGED
package/dist/test.js
CHANGED
|
@@ -1,55 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.payouts = exports.patterns = void 0;
|
|
3
4
|
const labag_1 = require("./labag");
|
|
4
5
|
// --- 設定 (Configuration) ---
|
|
5
|
-
const BET_AMOUNT =
|
|
6
|
-
const SIMULATION_COUNT =
|
|
6
|
+
const BET_AMOUNT = 100;
|
|
7
|
+
const SIMULATION_COUNT = 1000000;
|
|
7
8
|
// --- 資料定義 (Data Definitions) ---
|
|
8
|
-
|
|
9
|
+
exports.patterns = [
|
|
9
10
|
{
|
|
10
|
-
id: 1,
|
|
11
|
+
id: "1",
|
|
11
12
|
weight: 36,
|
|
12
13
|
image: "https://fanyu.vercel.app/api/album/item/1mDK1ewfLiV3fAB1HbjvwdSaDTdJdBGG3",
|
|
13
14
|
},
|
|
14
15
|
{
|
|
15
|
-
id: 2,
|
|
16
|
+
id: "2",
|
|
16
17
|
weight: 24,
|
|
17
18
|
image: "https://fanyu.vercel.app/api/album/item/1oB-uZhPPfjfTtG4CITnb3_E-Ops9JTA0",
|
|
18
19
|
},
|
|
19
20
|
{
|
|
20
|
-
id: 3,
|
|
21
|
+
id: "3",
|
|
21
22
|
weight: 17,
|
|
22
23
|
image: "https://fanyu.vercel.app/api/album/item/1bMJdRB8uerQZfGYINzBI9Vaw32bZljl2",
|
|
23
24
|
},
|
|
24
25
|
{
|
|
25
|
-
id: 4,
|
|
26
|
+
id: "4",
|
|
26
27
|
weight: 12,
|
|
27
28
|
image: "https://fanyu.vercel.app/api/album/item/1In8LF1wVfLXpPkp57a20zX84QgsAeLQx",
|
|
28
29
|
},
|
|
29
30
|
{
|
|
30
|
-
id: 5,
|
|
31
|
+
id: "5",
|
|
31
32
|
weight: 8,
|
|
32
33
|
image: "https://fanyu.vercel.app/api/album/item/1Zo_PjrXm-4TBrL2cLAeFkEl1el9kTR56",
|
|
33
34
|
},
|
|
34
35
|
{
|
|
35
|
-
id: 6,
|
|
36
|
+
id: "6",
|
|
36
37
|
weight: 3,
|
|
37
38
|
image: "https://fanyu.vercel.app/api/album/item/19NMnVgcb-9IsknNcfe9TpCyPBIcGnhQU",
|
|
38
39
|
},
|
|
39
40
|
];
|
|
40
|
-
|
|
41
|
-
{ id: 1, match_count: 2, pattern_id: 1,
|
|
42
|
-
{ id: 2, match_count: 3, pattern_id: 1,
|
|
43
|
-
{ id: 3, match_count: 2, pattern_id: 2,
|
|
44
|
-
{ id: 4, match_count: 3, pattern_id: 2,
|
|
45
|
-
{ id: 5, match_count: 2, pattern_id: 3,
|
|
46
|
-
{ id: 6, match_count: 3, pattern_id: 3,
|
|
47
|
-
{ id: 7, match_count: 2, pattern_id: 4,
|
|
48
|
-
{ id: 8, match_count: 3, pattern_id: 4,
|
|
49
|
-
{ id: 9, match_count: 2, pattern_id: 5,
|
|
50
|
-
{ id: 10, match_count: 3, pattern_id: 5,
|
|
51
|
-
{ id: 11, match_count: 2, pattern_id: 6,
|
|
52
|
-
{ id: 12, match_count: 3, pattern_id: 6,
|
|
41
|
+
exports.payouts = [
|
|
42
|
+
{ id: "1", match_count: 2, pattern_id: "1", multiplier: 0.56 },
|
|
43
|
+
{ id: "2", match_count: 3, pattern_id: "1", multiplier: 2.5 },
|
|
44
|
+
{ id: "3", match_count: 2, pattern_id: "2", multiplier: 1.05 },
|
|
45
|
+
{ id: "4", match_count: 3, pattern_id: "2", multiplier: 10.0 },
|
|
46
|
+
{ id: "5", match_count: 2, pattern_id: "3", multiplier: 1.3 },
|
|
47
|
+
{ id: "6", match_count: 3, pattern_id: "3", multiplier: 20.0 },
|
|
48
|
+
{ id: "7", match_count: 2, pattern_id: "4", multiplier: 2.1 },
|
|
49
|
+
{ id: "8", match_count: 3, pattern_id: "4", multiplier: 45.0 },
|
|
50
|
+
{ id: "9", match_count: 2, pattern_id: "5", multiplier: 2.6 },
|
|
51
|
+
{ id: "10", match_count: 3, pattern_id: "5", multiplier: 75.0 },
|
|
52
|
+
{ id: "11", match_count: 2, pattern_id: "6", multiplier: 7.0 },
|
|
53
|
+
{ id: "12", match_count: 3, pattern_id: "6", multiplier: 600.0 },
|
|
53
54
|
];
|
|
54
55
|
// --- 輔助函式 (Helpers) ---
|
|
55
56
|
/**
|
|
@@ -72,6 +73,7 @@ function calculateTheoreticalStats(patterns, payouts, bet) {
|
|
|
72
73
|
let ev = 0;
|
|
73
74
|
let varianceSum = 0; // E[X^2]
|
|
74
75
|
const hitProbabilities = {};
|
|
76
|
+
const payoutStats = [];
|
|
75
77
|
for (const payout of payouts) {
|
|
76
78
|
const pattern = patterns.find((p) => p.id === payout.pattern_id);
|
|
77
79
|
if (!pattern)
|
|
@@ -82,17 +84,26 @@ function calculateTheoreticalStats(patterns, payouts, bet) {
|
|
|
82
84
|
// k=3: p^3
|
|
83
85
|
// k=2: C(3,2) * p^2 * (1-p) = 3 * p^2 * (1-p)
|
|
84
86
|
const prob = payout.match_count === 3 ? Math.pow(p, 3) : 3 * Math.pow(p, 2) * (1 - p);
|
|
85
|
-
|
|
87
|
+
const reward = Math.floor(payout.multiplier * bet);
|
|
88
|
+
const rtpContribution = ((prob * reward) / bet) * 100; // RTP 貢獻 = (概率 * 獎金 / 投注) * 100
|
|
89
|
+
ev += prob * reward;
|
|
86
90
|
// 假設賠率互斥: E[X^2] = sum(prob * reward^2)
|
|
87
91
|
// Assuming disjoint payouts: E[X^2] = sum(prob * reward^2)
|
|
88
|
-
varianceSum += prob * Math.pow(
|
|
89
|
-
hitProbabilities[
|
|
90
|
-
|
|
92
|
+
varianceSum += prob * Math.pow(reward, 2);
|
|
93
|
+
hitProbabilities[reward] = (hitProbabilities[reward] || 0) + prob;
|
|
94
|
+
payoutStats.push({
|
|
95
|
+
id: payout.id,
|
|
96
|
+
patternId: payout.pattern_id,
|
|
97
|
+
matchCount: payout.match_count,
|
|
98
|
+
prob,
|
|
99
|
+
reward,
|
|
100
|
+
rtpContribution,
|
|
101
|
+
});
|
|
91
102
|
}
|
|
92
103
|
const variance = varianceSum - Math.pow(ev, 2);
|
|
93
104
|
const stdDev = Math.sqrt(variance);
|
|
94
105
|
const rtp = (ev / bet) * 100;
|
|
95
|
-
return { ev, rtp, stdDev, hitProbabilities };
|
|
106
|
+
return { ev, rtp, stdDev, hitProbabilities, payoutStats };
|
|
96
107
|
}
|
|
97
108
|
// --- 模擬邏輯 (Simulation Logic) ---
|
|
98
109
|
function runSimulation(game, count) {
|
|
@@ -101,7 +112,7 @@ function runSimulation(game, count) {
|
|
|
101
112
|
let maxWin = 0;
|
|
102
113
|
const hitFrequency = {};
|
|
103
114
|
for (let i = 0; i < count; i++) {
|
|
104
|
-
const result = game.spin();
|
|
115
|
+
const result = game.spin(BET_AMOUNT);
|
|
105
116
|
totalReward += result.reward;
|
|
106
117
|
if (result.reward > 0) {
|
|
107
118
|
winCount++;
|
|
@@ -120,7 +131,7 @@ function runSimulation(game, count) {
|
|
|
120
131
|
// --- 主要執行區塊 (Main Execution) ---
|
|
121
132
|
// 1. 理論分析 (Theoretical Analysis)
|
|
122
133
|
console.log("正在計算理論數值...");
|
|
123
|
-
const theo = calculateTheoreticalStats(patterns, payouts, BET_AMOUNT);
|
|
134
|
+
const theo = calculateTheoreticalStats(exports.patterns, exports.payouts, BET_AMOUNT);
|
|
124
135
|
console.log("==========================================");
|
|
125
136
|
console.log(" 理論分析 (Theoretical) ");
|
|
126
137
|
console.log("==========================================");
|
|
@@ -128,9 +139,19 @@ console.log(`理論期望值 (EV) : ${theo.ev.toFixed(2)}`);
|
|
|
128
139
|
console.log(`理論 RTP : ${theo.rtp.toFixed(2)}%`);
|
|
129
140
|
console.log(`標準差 (SD) : ${theo.stdDev.toFixed(2)}`);
|
|
130
141
|
console.log("------------------------------------------");
|
|
142
|
+
console.log("\n==========================================");
|
|
143
|
+
console.log(" RTP 貢獻分析 (RTP Contribution) ");
|
|
144
|
+
console.log("==========================================");
|
|
145
|
+
console.log(`| ID | Pattern | Count | Reward | Prob (%) | RTP Contrib (%) |`);
|
|
146
|
+
console.log(`|----|---------|-------|--------|----------|-----------------|`);
|
|
147
|
+
theo.payoutStats.forEach((stat) => {
|
|
148
|
+
console.log(`| ${stat.id.padEnd(2)} | ${stat.patternId.padEnd(7)} | ${stat.matchCount} | ${stat.reward.toString().padEnd(6)} | ${(stat.prob * 100).toFixed(4).padEnd(8)} | ${stat.rtpContribution.toFixed(4).padEnd(15)} |`);
|
|
149
|
+
});
|
|
150
|
+
console.log("------------------------------------------");
|
|
151
|
+
console.log(`總 RTP: ${theo.rtp.toFixed(2)}%`);
|
|
131
152
|
// 2. 模擬 (Simulation)
|
|
132
153
|
console.log(`\n正在執行模擬 (n=${formatCurrency(SIMULATION_COUNT)})...`);
|
|
133
|
-
const game = new labag_1.LaBaG(patterns, payouts);
|
|
154
|
+
const game = new labag_1.LaBaG(exports.patterns, exports.payouts);
|
|
134
155
|
const sim = runSimulation(game, SIMULATION_COUNT);
|
|
135
156
|
const simEV = sim.totalReward / SIMULATION_COUNT;
|
|
136
157
|
const simRTP = (simEV / BET_AMOUNT) * 100;
|
package/package.json
CHANGED
package/src/labag.ts
CHANGED
package/src/test.ts
CHANGED
|
@@ -2,62 +2,65 @@ import { LaBaG } from "./labag";
|
|
|
2
2
|
import { Pattern, Payout } from "./types";
|
|
3
3
|
|
|
4
4
|
// --- 設定 (Configuration) ---
|
|
5
|
-
const BET_AMOUNT =
|
|
6
|
-
const SIMULATION_COUNT =
|
|
5
|
+
const BET_AMOUNT = 100;
|
|
6
|
+
const SIMULATION_COUNT = 1000000;
|
|
7
7
|
|
|
8
8
|
// --- 資料定義 (Data Definitions) ---
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
export const patterns: Pattern[] = [
|
|
10
11
|
{
|
|
11
|
-
id: 1,
|
|
12
|
+
id: "1",
|
|
12
13
|
weight: 36,
|
|
13
14
|
image:
|
|
14
15
|
"https://fanyu.vercel.app/api/album/item/1mDK1ewfLiV3fAB1HbjvwdSaDTdJdBGG3",
|
|
15
16
|
},
|
|
16
17
|
{
|
|
17
|
-
id: 2,
|
|
18
|
+
id: "2",
|
|
18
19
|
weight: 24,
|
|
19
20
|
image:
|
|
20
21
|
"https://fanyu.vercel.app/api/album/item/1oB-uZhPPfjfTtG4CITnb3_E-Ops9JTA0",
|
|
21
22
|
},
|
|
23
|
+
|
|
22
24
|
{
|
|
23
|
-
id: 3,
|
|
25
|
+
id: "3",
|
|
24
26
|
weight: 17,
|
|
25
27
|
image:
|
|
26
28
|
"https://fanyu.vercel.app/api/album/item/1bMJdRB8uerQZfGYINzBI9Vaw32bZljl2",
|
|
27
29
|
},
|
|
28
30
|
{
|
|
29
|
-
id: 4,
|
|
31
|
+
id: "4",
|
|
30
32
|
weight: 12,
|
|
31
33
|
image:
|
|
32
34
|
"https://fanyu.vercel.app/api/album/item/1In8LF1wVfLXpPkp57a20zX84QgsAeLQx",
|
|
33
35
|
},
|
|
36
|
+
|
|
34
37
|
{
|
|
35
|
-
id: 5,
|
|
38
|
+
id: "5",
|
|
36
39
|
weight: 8,
|
|
37
40
|
image:
|
|
38
41
|
"https://fanyu.vercel.app/api/album/item/1Zo_PjrXm-4TBrL2cLAeFkEl1el9kTR56",
|
|
39
42
|
},
|
|
40
43
|
{
|
|
41
|
-
id: 6,
|
|
44
|
+
id: "6",
|
|
42
45
|
weight: 3,
|
|
43
46
|
image:
|
|
44
47
|
"https://fanyu.vercel.app/api/album/item/19NMnVgcb-9IsknNcfe9TpCyPBIcGnhQU",
|
|
45
48
|
},
|
|
46
49
|
];
|
|
47
50
|
|
|
48
|
-
const payouts: Payout[] = [
|
|
49
|
-
{ id: 1, match_count: 2, pattern_id: 1,
|
|
50
|
-
{ id: 2, match_count: 3, pattern_id: 1,
|
|
51
|
-
{ id: 3, match_count: 2, pattern_id: 2,
|
|
52
|
-
{ id: 4, match_count: 3, pattern_id: 2,
|
|
53
|
-
{ id: 5, match_count: 2, pattern_id: 3,
|
|
54
|
-
{ id: 6, match_count: 3, pattern_id: 3,
|
|
55
|
-
{ id: 7, match_count: 2, pattern_id: 4,
|
|
56
|
-
{ id: 8, match_count: 3, pattern_id: 4,
|
|
57
|
-
{ id: 9, match_count: 2, pattern_id: 5,
|
|
58
|
-
{ id: 10, match_count: 3, pattern_id: 5,
|
|
59
|
-
{ id: 11, match_count: 2, pattern_id: 6,
|
|
60
|
-
{ id: 12, match_count: 3, pattern_id: 6,
|
|
51
|
+
export const payouts: Payout[] = [
|
|
52
|
+
{ id: "1", match_count: 2, pattern_id: "1", multiplier: 0.56 },
|
|
53
|
+
{ id: "2", match_count: 3, pattern_id: "1", multiplier: 2.5 },
|
|
54
|
+
{ id: "3", match_count: 2, pattern_id: "2", multiplier: 1.05 },
|
|
55
|
+
{ id: "4", match_count: 3, pattern_id: "2", multiplier: 10.0 },
|
|
56
|
+
{ id: "5", match_count: 2, pattern_id: "3", multiplier: 1.3 },
|
|
57
|
+
{ id: "6", match_count: 3, pattern_id: "3", multiplier: 20.0 },
|
|
58
|
+
{ id: "7", match_count: 2, pattern_id: "4", multiplier: 2.1 },
|
|
59
|
+
{ id: "8", match_count: 3, pattern_id: "4", multiplier: 45.0 },
|
|
60
|
+
{ id: "9", match_count: 2, pattern_id: "5", multiplier: 2.6 },
|
|
61
|
+
{ id: "10", match_count: 3, pattern_id: "5", multiplier: 75.0 },
|
|
62
|
+
{ id: "11", match_count: 2, pattern_id: "6", multiplier: 7.0 },
|
|
63
|
+
{ id: "12", match_count: 3, pattern_id: "6", multiplier: 600.0 },
|
|
61
64
|
];
|
|
62
65
|
|
|
63
66
|
// --- 輔助函式 (Helpers) ---
|
|
@@ -90,6 +93,15 @@ function calculateTheoreticalStats(
|
|
|
90
93
|
let varianceSum = 0; // E[X^2]
|
|
91
94
|
const hitProbabilities: Record<number, number> = {};
|
|
92
95
|
|
|
96
|
+
const payoutStats: {
|
|
97
|
+
id: string;
|
|
98
|
+
patternId: string;
|
|
99
|
+
matchCount: number;
|
|
100
|
+
prob: number;
|
|
101
|
+
reward: number;
|
|
102
|
+
rtpContribution: number;
|
|
103
|
+
}[] = [];
|
|
104
|
+
|
|
93
105
|
for (const payout of payouts) {
|
|
94
106
|
const pattern = patterns.find((p) => p.id === payout.pattern_id);
|
|
95
107
|
if (!pattern) continue;
|
|
@@ -102,20 +114,31 @@ function calculateTheoreticalStats(
|
|
|
102
114
|
const prob =
|
|
103
115
|
payout.match_count === 3 ? Math.pow(p, 3) : 3 * Math.pow(p, 2) * (1 - p);
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
const reward = Math.floor(payout.multiplier * bet);
|
|
118
|
+
const rtpContribution = ((prob * reward) / bet) * 100; // RTP 貢獻 = (概率 * 獎金 / 投注) * 100
|
|
119
|
+
|
|
120
|
+
ev += prob * reward;
|
|
106
121
|
// 假設賠率互斥: E[X^2] = sum(prob * reward^2)
|
|
107
122
|
// Assuming disjoint payouts: E[X^2] = sum(prob * reward^2)
|
|
108
|
-
varianceSum += prob * Math.pow(
|
|
109
|
-
|
|
110
|
-
hitProbabilities[
|
|
111
|
-
|
|
123
|
+
varianceSum += prob * Math.pow(reward, 2);
|
|
124
|
+
|
|
125
|
+
hitProbabilities[reward] = (hitProbabilities[reward] || 0) + prob;
|
|
126
|
+
|
|
127
|
+
payoutStats.push({
|
|
128
|
+
id: payout.id,
|
|
129
|
+
patternId: payout.pattern_id,
|
|
130
|
+
matchCount: payout.match_count,
|
|
131
|
+
prob,
|
|
132
|
+
reward,
|
|
133
|
+
rtpContribution,
|
|
134
|
+
});
|
|
112
135
|
}
|
|
113
136
|
|
|
114
137
|
const variance = varianceSum - Math.pow(ev, 2);
|
|
115
138
|
const stdDev = Math.sqrt(variance);
|
|
116
139
|
const rtp = (ev / bet) * 100;
|
|
117
140
|
|
|
118
|
-
return { ev, rtp, stdDev, hitProbabilities };
|
|
141
|
+
return { ev, rtp, stdDev, hitProbabilities, payoutStats };
|
|
119
142
|
}
|
|
120
143
|
|
|
121
144
|
// --- 模擬邏輯 (Simulation Logic) ---
|
|
@@ -126,7 +149,7 @@ function runSimulation(game: LaBaG, count: number) {
|
|
|
126
149
|
const hitFrequency: Record<number, number> = {};
|
|
127
150
|
|
|
128
151
|
for (let i = 0; i < count; i++) {
|
|
129
|
-
const result = game.spin();
|
|
152
|
+
const result = game.spin(BET_AMOUNT);
|
|
130
153
|
totalReward += result.reward;
|
|
131
154
|
|
|
132
155
|
if (result.reward > 0) {
|
|
@@ -158,6 +181,20 @@ console.log(`理論 RTP : ${theo.rtp.toFixed(2)}%`);
|
|
|
158
181
|
console.log(`標準差 (SD) : ${theo.stdDev.toFixed(2)}`);
|
|
159
182
|
console.log("------------------------------------------");
|
|
160
183
|
|
|
184
|
+
console.log("\n==========================================");
|
|
185
|
+
console.log(" RTP 貢獻分析 (RTP Contribution) ");
|
|
186
|
+
console.log("==========================================");
|
|
187
|
+
console.log(`| ID | Pattern | Count | Reward | Prob (%) | RTP Contrib (%) |`);
|
|
188
|
+
console.log(`|----|---------|-------|--------|----------|-----------------|`);
|
|
189
|
+
|
|
190
|
+
theo.payoutStats.forEach((stat) => {
|
|
191
|
+
console.log(
|
|
192
|
+
`| ${stat.id.padEnd(2)} | ${stat.patternId.padEnd(7)} | ${stat.matchCount} | ${stat.reward.toString().padEnd(6)} | ${(stat.prob * 100).toFixed(4).padEnd(8)} | ${stat.rtpContribution.toFixed(4).padEnd(15)} |`,
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
console.log("------------------------------------------");
|
|
196
|
+
console.log(`總 RTP: ${theo.rtp.toFixed(2)}%`);
|
|
197
|
+
|
|
161
198
|
// 2. 模擬 (Simulation)
|
|
162
199
|
console.log(`\n正在執行模擬 (n=${formatCurrency(SIMULATION_COUNT)})...`);
|
|
163
200
|
const game = new LaBaG(patterns, payouts);
|