rootan-probability 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rootan (https://rootan.pro)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,148 @@
1
- # Temporary Holding Version
1
+ # rootan-probability — คำนวณ RTP และค่าคาดหมายเพื่อการศึกษา
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ไลบรารี JavaScript/TypeScript ขนาดเล็กจาก [รู้ทัน (rootan.pro)](https://rootan.pro/) สำหรับคำนวณ **ผลขาดทุนคาดหมายจาก RTP** และ **ค่าคาดหมาย (expected value)** เพื่อการศึกษาเรื่องความน่าจะเป็นและความเสี่ยงจากการพนัน
4
+
5
+ เหมาะกับนักพัฒนาที่ทำเนื้อหาหรือเครื่องมือสอนเรื่องความน่าจะเป็น ครูหรือผู้ทำสื่อการเรียนรู้ และผู้ที่อยากเห็นว่าค่าเฉลี่ยทางคณิตศาสตร์ทำงานอย่างไร เช่น RTP 96% หมายถึงอะไร หรือทำไมสูตรทบเงินจึงไม่เปลี่ยนค่าคาดหมาย
6
+
7
+ - ไม่มี runtime dependencies
8
+ - ESM พร้อม TypeScript declarations
9
+ - ใช้ได้ทั้ง Node.js 18+ และเบราว์เซอร์ ไม่เรียกเครือข่ายและไม่เก็บข้อมูลผู้ใช้
10
+ - ตรวจข้อมูลเข้าอย่างเข้มงวด ข้อมูลผิดจะ throw แทนการคืนตัวเลขที่ทำให้เข้าใจผิด
11
+
12
+ > แพ็กเกจนี้ไม่ทำนายผล ไม่แนะนำเกมหรือเว็บใด และไม่ใช่สูตรทำกำไร ค่าคาดหมายคือค่าเฉลี่ยทางทฤษฎีในระยะยาว ผลจริงในระยะสั้นอาจต่างออกไปมาก
13
+
14
+ ## ติดตั้ง
15
+
16
+ ```sh
17
+ npm install rootan-probability
18
+ ```
19
+
20
+ ## ตัวอย่างการใช้งาน
21
+
22
+ ตัวเลขด้านล่างเป็นตัวอย่างสมมติ
23
+
24
+ ```js
25
+ import { expectedLoss, expectedValue } from 'rootan-probability';
26
+
27
+ // ยอดเดิมพันสะสม 10,000 บาท ที่ RTP สมมติ 96%
28
+ expectedLoss(10_000, 96);
29
+ // → 400 (โดยประมาณ เพราะเป็นทศนิยมของคอมพิวเตอร์)
30
+
31
+ // ได้ −1 หรือ +1 ด้วยโอกาสเท่ากัน
32
+ expectedValue([
33
+ { value: -1, probability: 0.5 },
34
+ { value: 1, probability: 0.5 },
35
+ ]);
36
+ // → 0
37
+
38
+ // เดิมพัน 1 ได้ +35 ถ้าถูก 1 ใน 37 ช่อง และเสีย 1 ถ้าไม่ถูก
39
+ expectedValue([
40
+ { value: 35, probability: 1 / 37 },
41
+ { value: -1, probability: 36 / 37 },
42
+ ]);
43
+ // → ประมาณ −0.027 ต่อการเดิมพัน 1 หน่วย
44
+ ```
45
+
46
+ TypeScript ใช้ type `Outcome` ได้โดยตรง:
47
+
48
+ ```ts
49
+ import { expectedValue, type Outcome } from 'rootan-probability';
50
+
51
+ const outcomes: Outcome[] = [{ value: 2, probability: 1 }];
52
+ const ev: number = expectedValue(outcomes); // 2
53
+ ```
54
+
55
+ ### ตัวอย่าง: สูตรทบเงินเปลี่ยนค่าคาดหมายได้ไหม
56
+
57
+ เกมสมมติที่โอกาสชนะแต่ละตา 48% ได้หรือเสียเท่าเงินที่วาง ถ้าใช้สูตรทบเงิน (เพิ่มเดิมพันเท่าตัวหลังเสีย) สูงสุด 3 ตา โดยเริ่มที่ 1 หน่วย
58
+
59
+ ```js
60
+ const lose = 0.52; // โอกาสแพ้แต่ละตา
61
+
62
+ expectedValue([
63
+ { value: 1, probability: 0.48 }, // ชนะตาแรก
64
+ { value: 1, probability: lose * 0.48 }, // แพ้ 1 ตาแล้วชนะ: −1 + 2
65
+ { value: 1, probability: lose ** 2 * 0.48 }, // แพ้ 2 ตาแล้วชนะ: −1 − 2 + 4
66
+ { value: -7, probability: lose ** 3 }, // แพ้รวด 3 ตา: −1 − 2 − 4
67
+ ]);
68
+ // → ประมาณ −0.125
69
+ ```
70
+
71
+ สูตรนี้ได้ +1 บ่อยถึงราว 86% ของรอบ จึงดูเหมือนได้ผล แต่รอบที่แพ้รวดเสีย 7 หน่วย ค่าคาดหมายรวมจึงยังติดลบ ทบเงินเปลี่ยนแค่รูปแบบการได้เสีย ไม่ได้ทำให้เกมที่ติดลบกลายเป็นบวก อ่านตัวอย่างตัวเลขเพิ่มเติมใน[สูตรทบเงินได้ผลจริงไหม](https://rootan.pro/articles/baccarat-doubling/)
72
+
73
+ ### ใช้ในเบราว์เซอร์
74
+
75
+ ใช้ผ่าน CDN ที่รองรับ ESM ได้ เช่น
76
+
77
+ ```html
78
+ <script type="module">
79
+ import { expectedLoss } from 'https://esm.sh/rootan-probability@0.1.0';
80
+ console.log(expectedLoss(10000, 96));
81
+ </script>
82
+ ```
83
+
84
+ ## API
85
+
86
+ ### `expectedLoss(totalStaked, rtpPercent)`
87
+
88
+ คืนค่า `totalStaked × (1 − rtpPercent ÷ 100)`
89
+
90
+ RTP (Return to Player) คือสัดส่วนการจ่ายคืนเฉลี่ยในระยะยาว RTP 96% จึงหมายถึงผลขาดทุนคาดหมายประมาณ 4% ของยอดเดิมพันสะสม ไม่ได้แปลว่าเดิมพัน 100 บาทแล้วจะได้คืน 96 บาททุกครั้ง
91
+
92
+ | พารามิเตอร์ | ชนิด | ขอบเขต | ความหมาย |
93
+ |---|---|---|---|
94
+ | `totalStaked` | `number` | 0 ถึง 100,000,000 | ยอดเดิมพันสะสม นับทุกครั้งที่เดิมพัน ไม่ใช่ยอดฝาก |
95
+ | `rtpPercent` | `number` | 0 ถึง 100 | RTP เป็นเปอร์เซ็นต์ |
96
+
97
+ ผลลัพธ์: `number` ผลขาดทุนคาดหมายตามทฤษฎี
98
+
99
+ ### `expectedValue(outcomes)`
100
+
101
+ คืนผลรวมของ `value × probability` ของทุกผลลัพธ์
102
+
103
+ | พารามิเตอร์ | ชนิด | ขอบเขต | ความหมาย |
104
+ |---|---|---|---|
105
+ | `outcomes` | `Outcome[]` | ไม่ว่าง | รายการผลลัพธ์ที่เป็นไปได้ทั้งหมด |
106
+ | `outcomes[i].value` | `number` | จำนวนจำกัด | ได้ (บวก) หรือเสีย (ลบ) เมื่อเกิดผลลัพธ์นี้ |
107
+ | `outcomes[i].probability` | `number` | 0 ถึง 1 | โอกาสเกิดผลลัพธ์นี้ ทุกรายการรวมกันต้องเป็น 1 |
108
+
109
+ ผลลัพธ์: `number` ค่าบวกหมายถึงได้เฉลี่ย ค่าลบหมายถึงเสียเฉลี่ย ต่อหนึ่งครั้งในระยะยาว
110
+
111
+ ผลรวม probability ต่างจาก 1 ได้ไม่เกิน `1e-12` เพื่อรองรับทศนิยมของคอมพิวเตอร์ ฟังก์ชันจะไม่ปรับสัดส่วนให้เองเงียบ ๆ
112
+
113
+ ### ข้อผิดพลาด
114
+
115
+ | กรณี | Error |
116
+ |---|---|
117
+ | อาร์กิวเมนต์ไม่ใช่ `number` เช่น string, `undefined`, BigInt | `TypeError` |
118
+ | `outcomes` ไม่ใช่ array หรือรายการไม่ใช่ object | `TypeError` |
119
+ | ค่าเป็น `NaN` หรือ `Infinity` | `RangeError` |
120
+ | ค่าอยู่นอกขอบเขตในตาราง | `RangeError` |
121
+ | `outcomes` ว่าง หรือ probability รวมไม่เป็น 1 | `RangeError` |
122
+ | ผลคำนวณล้นเป็น `Infinity` | `RangeError` |
123
+
124
+ ### ค่าคงที่
125
+
126
+ - `MAX_TOTAL_STAKED` = `100_000_000`
127
+ - `PROBABILITY_TOLERANCE` = `1e-12`
128
+
129
+ ## ข้อจำกัด
130
+
131
+ - **ค่าคาดหมายไม่ใช่ผลที่รับประกัน** เป็นค่าเฉลี่ยเมื่อทำซ้ำจำนวนมาก ผลจริงในช่วงสั้นอาจได้หรือเสียมากกว่านี้มาก
132
+ - **ไม่บอกโอกาสหมดเงิน** และไม่ได้จำลองการกระจายรางวัลหรือความผันผวน RTP ค่าเดียวไม่พอสำหรับจำลองผลจริง
133
+ - **ยอดเดิมพันสะสมต่างจากยอดฝาก** ถ้าเงินเดิมถูกนำไปเดิมพันซ้ำ ยอดสะสม (เทิร์นโอเวอร์) จะสูงกว่ายอดฝากหลายเท่า ดูวิธีคิดใน[เทิร์นโอเวอร์คืออะไร](https://rootan.pro/articles/turnover-explained/)
134
+ - **ตัวเลขเป็น floating point** ผลลัพธ์ไม่ได้ปัดเป็นสกุลเงิน ให้ชั้นแสดงผลจัดรูปแบบเอง เช่น `toLocaleString` และเปรียบเทียบผลด้วย tolerance แทนการเท่ากันทุกบิต
135
+
136
+ ## อ่านต่อบนรู้ทัน
137
+
138
+ - [ทดลองเครื่องมือคำนวณบนรู้ทัน](https://rootan.pro/tools/) — กรอกยอดเดิมพันสะสมและ RTP แล้วดูผลขาดทุนคาดหมายทันที
139
+ - [RTP คืออะไร? RTP 96% ไม่ได้แปลว่าจะได้เงินคืน 96 บาท](https://rootan.pro/articles/rtp-explained/) — ความต่างระหว่างค่าเฉลี่ยระยะยาวกับผลการเล่นจริง
140
+ - [บทความหมวดความน่าจะเป็นทั้งหมด](https://rootan.pro/topics/probability/) — ตัวเลขเบื้องหลังเกมและความเข้าใจผิดที่พบบ่อย
141
+
142
+ หากการพนันเริ่มกระทบเงิน งาน หรือความสัมพันธ์ ดู[ช่องทางขอความช่วยเหลือ](https://rootan.pro/help/) หรือโทรสายด่วนสุขภาพจิต 1323
143
+
144
+ ## รายงานปัญหาและใบอนุญาต
145
+
146
+ พบปัญหาหรือมีข้อเสนอ แจ้งได้ที่ [หน้าติดต่อรู้ทัน](https://rootan.pro/contact/)
147
+
148
+ เผยแพร่ภายใต้ [MIT License](./LICENSE)
@@ -0,0 +1,39 @@
1
+ /**
2
+ * rootan-probability — คำนวณค่าคาดหมายเพื่อการศึกษา
3
+ * ไม่ทำนายผลจริง ไม่มี dependencies ไม่เรียกเครือข่าย
4
+ * https://rootan.pro/tools/
5
+ */
6
+ /** ยอดเดิมพันสะสมสูงสุดที่รับได้ (เท่ากับขอบเขตเครื่องมือบน rootan.pro) */
7
+ export declare const MAX_TOTAL_STAKED = 100000000;
8
+ /** ผลรวม probability ต่างจาก 1 ได้ไม่เกินค่านี้ เพื่อรองรับทศนิยมของคอมพิวเตอร์ */
9
+ export declare const PROBABILITY_TOLERANCE = 1e-12;
10
+ /** ผลลัพธ์หนึ่งแบบ: ได้/เสีย `value` ด้วยโอกาส `probability` (0–1) */
11
+ export interface Outcome {
12
+ readonly value: number;
13
+ readonly probability: number;
14
+ }
15
+ /**
16
+ * ผลขาดทุนคาดหมายจาก RTP: `totalStaked × (1 − rtpPercent ÷ 100)`
17
+ *
18
+ * `totalStaked` คือยอดเดิมพันสะสม (นับทุกครั้งที่เดิมพัน) ไม่ใช่ยอดฝาก
19
+ * ผลที่ได้เป็นค่าเฉลี่ยทางทฤษฎี ไม่ใช่ยอดที่จะเสียจริง และไม่บอกโอกาสหมดเงิน
20
+ *
21
+ * @param totalStaked ยอดเดิมพันสะสม 0 ถึง 100,000,000
22
+ * @param rtpPercent RTP เป็นเปอร์เซ็นต์ 0 ถึง 100
23
+ * @throws {TypeError} เมื่ออาร์กิวเมนต์ไม่ใช่ number
24
+ * @throws {RangeError} เมื่อเป็น NaN, Infinity หรืออยู่นอกขอบเขต
25
+ * @example expectedLoss(10_000, 96) // ≈ 400
26
+ */
27
+ export declare function expectedLoss(totalStaked: number, rtpPercent: number): number;
28
+ /**
29
+ * ค่าคาดหมาย: ผลรวมของ `value × probability` ของทุกผลลัพธ์
30
+ *
31
+ * probability ต้องรวมเป็น 1 (คลาดเคลื่อนได้ไม่เกิน 1e-12) ฟังก์ชันไม่ปรับสัดส่วนให้เอง
32
+ * ค่าบวกหมายถึงได้เฉลี่ย ค่าลบหมายถึงเสียเฉลี่ย ต่อการเล่นหนึ่งครั้งในระยะยาวตามทฤษฎี
33
+ *
34
+ * @param outcomes รายการผลลัพธ์ที่ไม่ว่าง
35
+ * @throws {TypeError} เมื่อ outcomes ไม่ใช่ array หรือรายการมีชนิดข้อมูลผิด
36
+ * @throws {RangeError} เมื่อรายการว่าง ค่าไม่จำกัด probability นอก 0–1 หรือรวมไม่เป็น 1
37
+ * @example expectedValue([{ value: -1, probability: 0.5 }, { value: 1, probability: 0.5 }]) // 0
38
+ */
39
+ export declare function expectedValue(outcomes: readonly Outcome[]): number;
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
1
+ /**
2
+ * rootan-probability — คำนวณค่าคาดหมายเพื่อการศึกษา
3
+ * ไม่ทำนายผลจริง ไม่มี dependencies ไม่เรียกเครือข่าย
4
+ * https://rootan.pro/tools/
5
+ */
6
+ /** ยอดเดิมพันสะสมสูงสุดที่รับได้ (เท่ากับขอบเขตเครื่องมือบน rootan.pro) */
7
+ export const MAX_TOTAL_STAKED = 100000000;
8
+ /** ผลรวม probability ต่างจาก 1 ได้ไม่เกินค่านี้ เพื่อรองรับทศนิยมของคอมพิวเตอร์ */
9
+ export const PROBABILITY_TOLERANCE = 1e-12;
10
+ function assertFiniteNumber(value, name) {
11
+ if (typeof value !== 'number') {
12
+ throw new TypeError(`${name} must be a number, received ${typeof value}`);
13
+ }
14
+ if (!Number.isFinite(value)) {
15
+ throw new RangeError(`${name} must be a finite number, received ${value}`);
16
+ }
17
+ }
18
+ function assertInRange(value, min, max, name) {
19
+ if (value < min || value > max) {
20
+ throw new RangeError(`${name} must be between ${min} and ${max}, received ${value}`);
21
+ }
22
+ }
23
+ function assertFiniteResult(result) {
24
+ if (!Number.isFinite(result)) {
25
+ throw new RangeError('result is not a finite number');
26
+ }
27
+ return result;
28
+ }
29
+ /**
30
+ * ผลขาดทุนคาดหมายจาก RTP: `totalStaked × (1 − rtpPercent ÷ 100)`
31
+ *
32
+ * `totalStaked` คือยอดเดิมพันสะสม (นับทุกครั้งที่เดิมพัน) ไม่ใช่ยอดฝาก
33
+ * ผลที่ได้เป็นค่าเฉลี่ยทางทฤษฎี ไม่ใช่ยอดที่จะเสียจริง และไม่บอกโอกาสหมดเงิน
34
+ *
35
+ * @param totalStaked ยอดเดิมพันสะสม 0 ถึง 100,000,000
36
+ * @param rtpPercent RTP เป็นเปอร์เซ็นต์ 0 ถึง 100
37
+ * @throws {TypeError} เมื่ออาร์กิวเมนต์ไม่ใช่ number
38
+ * @throws {RangeError} เมื่อเป็น NaN, Infinity หรืออยู่นอกขอบเขต
39
+ * @example expectedLoss(10_000, 96) // ≈ 400
40
+ */
41
+ export function expectedLoss(totalStaked, rtpPercent) {
42
+ assertFiniteNumber(totalStaked, 'totalStaked');
43
+ assertFiniteNumber(rtpPercent, 'rtpPercent');
44
+ assertInRange(totalStaked, 0, MAX_TOTAL_STAKED, 'totalStaked');
45
+ assertInRange(rtpPercent, 0, 100, 'rtpPercent');
46
+ return assertFiniteResult(totalStaked * (1 - rtpPercent / 100));
47
+ }
48
+ /**
49
+ * ค่าคาดหมาย: ผลรวมของ `value × probability` ของทุกผลลัพธ์
50
+ *
51
+ * probability ต้องรวมเป็น 1 (คลาดเคลื่อนได้ไม่เกิน 1e-12) ฟังก์ชันไม่ปรับสัดส่วนให้เอง
52
+ * ค่าบวกหมายถึงได้เฉลี่ย ค่าลบหมายถึงเสียเฉลี่ย ต่อการเล่นหนึ่งครั้งในระยะยาวตามทฤษฎี
53
+ *
54
+ * @param outcomes รายการผลลัพธ์ที่ไม่ว่าง
55
+ * @throws {TypeError} เมื่อ outcomes ไม่ใช่ array หรือรายการมีชนิดข้อมูลผิด
56
+ * @throws {RangeError} เมื่อรายการว่าง ค่าไม่จำกัด probability นอก 0–1 หรือรวมไม่เป็น 1
57
+ * @example expectedValue([{ value: -1, probability: 0.5 }, { value: 1, probability: 0.5 }]) // 0
58
+ */
59
+ export function expectedValue(outcomes) {
60
+ if (!Array.isArray(outcomes)) {
61
+ throw new TypeError('outcomes must be an array');
62
+ }
63
+ if (outcomes.length === 0) {
64
+ throw new RangeError('outcomes must not be empty');
65
+ }
66
+ let total = 0;
67
+ let probabilitySum = 0;
68
+ outcomes.forEach((outcome, index) => {
69
+ if (typeof outcome !== 'object' || outcome === null) {
70
+ throw new TypeError(`outcomes[${index}] must be an object with value and probability`);
71
+ }
72
+ const { value, probability } = outcome;
73
+ assertFiniteNumber(value, `outcomes[${index}].value`);
74
+ assertFiniteNumber(probability, `outcomes[${index}].probability`);
75
+ assertInRange(probability, 0, 1, `outcomes[${index}].probability`);
76
+ total += value * probability;
77
+ probabilitySum += probability;
78
+ });
79
+ if (Math.abs(probabilitySum - 1) > PROBABILITY_TOLERANCE) {
80
+ throw new RangeError(`probabilities must sum to 1, received ${probabilitySum}`);
81
+ }
82
+ return assertFiniteResult(total);
83
+ }
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "rootan-probability",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "คำนวณผลขาดทุนคาดหมายจาก RTP และค่าคาดหมาย (expected value) เพื่อการศึกษา ไม่มี dependencies ใช้ได้ทั้ง Node.js และเบราว์เซอร์",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "default": "./dist/index.js"
10
+ }
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "sideEffects": false,
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "test": "node --test test/index.test.mjs",
21
+ "prepack": "npm run build && npm test"
22
+ },
23
+ "keywords": [
24
+ "probability",
25
+ "expected-value",
26
+ "rtp",
27
+ "return-to-player",
28
+ "expected-loss",
29
+ "house-edge",
30
+ "martingale",
31
+ "education",
32
+ "math",
33
+ "thai"
34
+ ],
35
+ "homepage": "https://rootan.pro/tools/",
36
+ "bugs": {
37
+ "url": "https://rootan.pro/contact/"
38
+ },
39
+ "license": "MIT",
40
+ "author": "รู้ทัน (https://rootan.pro)",
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "devDependencies": {
48
+ "typescript": "^6.0.3"
49
+ }
50
+ }