trucoshi 4.0.3 → 4.0.4

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.
@@ -0,0 +1,11 @@
1
+ import { ICard, IDeck, IPlayedCard, IPlayer, IPublicPlayer } from "../../types";
2
+ import { ITable } from "./Table";
3
+ export declare function Deck(): IDeck;
4
+ export declare const getAllCards: () => ("rb" | "1e" | "1b" | "7e" | "7o" | "3e" | "3o" | "3b" | "3c" | "2e" | "2o" | "2b" | "2c" | "1o" | "1c" | "re" | "ro" | "rc" | "ce" | "co" | "cb" | "cc" | "pe" | "po" | "pb" | "pc" | "7b" | "7c" | "6e" | "6o" | "6b" | "6c" | "5e" | "5o" | "5b" | "5c" | "4e" | "4o" | "4b" | "4c")[];
5
+ export declare function dealCards<TPlayer extends {
6
+ key: string;
7
+ idx: number;
8
+ setHand(h: Array<ICard>): void;
9
+ } = IPlayer>(table: ITable<TPlayer>, deck: IDeck): void;
10
+ export declare function shuffleArray<T = unknown>(array: Array<T>, getRandom?: (max: number) => number): T[];
11
+ export declare function PlayedCard(player: IPlayer | IPublicPlayer, card: ICard, burn?: boolean): IPlayedCard;
@@ -0,0 +1,61 @@
1
+ import { BURNT_CARD, CARDS } from "../constants";
2
+ import { Random } from "./Random";
3
+ export function Deck() {
4
+ const deck = {
5
+ cards: getAllCards(),
6
+ random: Random(),
7
+ usedCards: [],
8
+ takeCard() {
9
+ const card = deck.cards.shift();
10
+ deck.usedCards.push(card);
11
+ return card;
12
+ },
13
+ takeThree() {
14
+ return [deck.takeCard(), deck.takeCard(), deck.takeCard()];
15
+ },
16
+ shuffle(dealer) {
17
+ deck.cards = getAllCards();
18
+ deck.usedCards = [];
19
+ deck.cards = shuffleArray(deck.cards, (max) => deck.random.pick(dealer, max - 1));
20
+ if (deck.cards.length !== 40) {
21
+ throw new Error("This is not good");
22
+ }
23
+ return deck;
24
+ },
25
+ };
26
+ return deck;
27
+ }
28
+ export const getAllCards = () => Object.keys(CARDS);
29
+ export function dealCards(table, deck) {
30
+ const playerHands = [];
31
+ for (let i = 0; i < 3; i++) {
32
+ for (const player of table.getPlayersForehandFirst()) {
33
+ playerHands[player.idx] = [...(playerHands[player.idx] || []), deck.takeCard()];
34
+ }
35
+ }
36
+ for (const [key, player] of table.players.entries()) {
37
+ player.setHand(playerHands[key]);
38
+ }
39
+ }
40
+ const defaultGetRandom = (max) => Math.floor(Math.random() * max);
41
+ export function shuffleArray(array, getRandom = defaultGetRandom) {
42
+ let currentIndex = array.length, randomIndex;
43
+ while (currentIndex != 0) {
44
+ randomIndex = getRandom(currentIndex);
45
+ currentIndex--;
46
+ [array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]];
47
+ }
48
+ return array;
49
+ }
50
+ export function PlayedCard(player, card, burn) {
51
+ const pc = {
52
+ player,
53
+ card,
54
+ key: card + player.idx,
55
+ };
56
+ if (burn) {
57
+ pc.card = BURNT_CARD;
58
+ pc.key = Math.floor(Math.random() * 100).toString();
59
+ }
60
+ return pc;
61
+ }
@@ -0,0 +1,5 @@
1
+ export interface IQueue {
2
+ promise: Promise<void>;
3
+ queue(operation: () => any): Promise<void>;
4
+ }
5
+ export declare const Queue: () => IQueue;
@@ -0,0 +1,18 @@
1
+ import logger from "../../utils/logger";
2
+ export const Queue = () => {
3
+ const queue = {
4
+ promise: Promise.resolve(),
5
+ queue(operation) {
6
+ return new Promise((resolve) => {
7
+ queue.promise = queue.promise
8
+ .then(operation)
9
+ .then(resolve)
10
+ .catch((e) => {
11
+ logger.error(e, "Error in queue operation %o", operation);
12
+ resolve();
13
+ });
14
+ });
15
+ },
16
+ };
17
+ return queue;
18
+ };
@@ -0,0 +1,18 @@
1
+ import { IRandom } from "../../types";
2
+ export declare const Random: () => IRandom;
3
+ export interface IRng {
4
+ combine(client: string, server: string, nonce: number): string;
5
+ sha512(string: string): string;
6
+ generateServerSeed(): string;
7
+ hexToBytes(hex: string): Uint8Array;
8
+ byteGenerator(clientseed: string, serverseed: string, nonce: number): Uint8Array;
9
+ generateInteger(clientSeed: string, serverSeed: string, nonce: number, min: number, max: number): number;
10
+ generateFloat(clientSeed: string, serverSeed: string, nonce: number, precision?: number): number;
11
+ generateBool(clientSeed: string, serverSeed: string, nonce: number): boolean;
12
+ selectRandomObject<T extends {
13
+ probability: number;
14
+ } = {
15
+ probability: number;
16
+ [x: string]: any;
17
+ }>(clientSeed: string, serverSeed: string, nonce: number, objects: Array<T>): T | null;
18
+ }
@@ -0,0 +1,140 @@
1
+ import forge from "node-forge";
2
+ const rnd = Rng();
3
+ export const Random = () => {
4
+ const random = {
5
+ secret: rnd.generateServerSeed(),
6
+ clients: [],
7
+ nonce: 0,
8
+ pick(key, max) {
9
+ return rnd.generateInteger(random.clients[key], random.secret, random.nonce, 0, max);
10
+ },
11
+ next() {
12
+ random.nonce++;
13
+ },
14
+ reveal() {
15
+ return { secret: random.secret, clients: [...random.clients] };
16
+ },
17
+ };
18
+ return random;
19
+ };
20
+ function Rng() {
21
+ return {
22
+ /**
23
+ * Generates a random 256 long hex hash
24
+ *
25
+ * @param {string} clientSeed - the clientSeed
26
+ * @param {string} serverSeed - the serverSeed
27
+ * @param {number} nonce - the nonce
28
+ * @returns {string} combined string
29
+ */
30
+ combine: (client, server, nonce) => client + server + nonce,
31
+ /**
32
+ * Generates a sha512 hash from a string
33
+ *
34
+ * @param {string} string - input string
35
+ * @returns {string} random 256 long string
36
+ */
37
+ sha512: (string) => forge.md.sha512.create().update(string).digest().toHex(),
38
+ /**
39
+ * Generates a random 256 long hex hash
40
+ *
41
+ * @returns {string} random 256 long string
42
+ */
43
+ generateServerSeed: function () {
44
+ return forge.util.bytesToHex(forge.random.getBytesSync(256));
45
+ },
46
+ /**
47
+ * Converts a hex string to a Uint8Array of bytes.
48
+ * @param {string} hex - The hex string to convert.
49
+ * @returns {Uint8Array} The resulting byte array.
50
+ */
51
+ hexToBytes: function (hex) {
52
+ const byteCount = hex.length / 2;
53
+ const bytes = new Uint8Array(byteCount);
54
+ for (let i = 0; i < byteCount; i++) {
55
+ const byteHex = hex.substring(i * 2, 2);
56
+ bytes[i] = parseInt(byteHex, 16);
57
+ }
58
+ return bytes;
59
+ },
60
+ /**
61
+ * Generates an array of 32 random bytes using the given serverseed, clientseed, and nonce.
62
+ *
63
+ * @param {string} clientseed - The clientseed to use.
64
+ * @param {string} serverseed - The serverseed to use.
65
+ * @param {number} nonce - The nonce to use.
66
+ * @returns {Uint8Array} - The generated bytes as a Uint8Array.
67
+ */
68
+ byteGenerator: function (clientseed, serverseed, nonce) {
69
+ const preHash = this.combine(clientseed, serverseed, nonce);
70
+ const hash = this.sha512(preHash);
71
+ return this.hexToBytes(hash.slice(0, 64));
72
+ },
73
+ /**
74
+ * Generates a random integer between min and max using the specified client seed, server seed, and nonce.
75
+ *
76
+ * @param {string} clientSeed - The client seed.
77
+ * @param {string} serverSeed - The server seed.
78
+ * @param {number} nonce - The nonce.
79
+ * @param {number} min - The minimum value of the range.
80
+ * @param {number} max - The maximum value of the range.
81
+ * @returns {number} A random integer between min and max (inclusive).
82
+ */
83
+ generateInteger: function (clientSeed, serverSeed, nonce, min, max) {
84
+ const preHash = this.combine(clientSeed, serverSeed, nonce);
85
+ const hash = this.sha512(preHash);
86
+ const range = max - min + 1;
87
+ return (parseInt(hash.slice(0, 8), 16) % range) + min;
88
+ },
89
+ /**
90
+ * Generates a random float between 0 and 1 using the fairjs library.
91
+ * @param {string} clientSeed - The client seed to use for the random number generation.
92
+ * @param {string} serverSeed - The server seed to use for the random number generation.
93
+ * @param {number} nonce - The nonce to use for the random number generation.
94
+ * @param {number} [precision=2] - The number of decimal places to include in the result.
95
+ * @returns {number} - The random float between 0 and 1.
96
+ */
97
+ generateFloat: function (clientSeed, serverSeed, nonce, precision = 2) {
98
+ const bytes = this.byteGenerator(clientSeed, serverSeed, nonce);
99
+ const float = parseFloat("0." + bytes.join(""));
100
+ return parseFloat(float.toFixed(precision));
101
+ },
102
+ /**
103
+ * Generates a random boolean using the specified client seed, server seed, and nonce.
104
+ *
105
+ * @param {string} clientSeed - The client seed.
106
+ * @param {string} serverSeed - The server seed.
107
+ * @param {number} nonce - The nonce.
108
+ * @returns {float, boolean} random boolean true/false
109
+ */
110
+ generateBool: function (clientSeed, serverSeed, nonce) {
111
+ return this.generateFloat(clientSeed, serverSeed, nonce, 10) <= 0.5 ? true : false;
112
+ },
113
+ /**
114
+ * Selects a random object from an array of objects based on their probabilities.
115
+ *
116
+ * @param {string} clientSeed - The client seed.
117
+ * @param {string} serverSeed - The server seed.
118
+ * @param {number} nonce - The nonce.
119
+ * @param {Array} objects - An array of objects with an ID and a probability property.
120
+ * @returns {String} The ID of the randomly selected object.
121
+ */
122
+ selectRandomObject: function (clientSeed, serverSeed, nonce, objects) {
123
+ let totalProbability = 0;
124
+ for (const obj of objects) {
125
+ totalProbability += obj.probability;
126
+ }
127
+ const normalizedProbabilities = objects.map((obj) => obj.probability / totalProbability);
128
+ const randomFloat = this.generateFloat(clientSeed, serverSeed, nonce, 10);
129
+ let index = 0;
130
+ for (let i = 0; i < normalizedProbabilities.length; i++) {
131
+ index += normalizedProbabilities[i];
132
+ if (randomFloat < index) {
133
+ return objects[i];
134
+ }
135
+ }
136
+ // If no object is selected, return null
137
+ return null;
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,15 @@
1
+ import { IPlayedCard, IPlayer } from "../../types";
2
+ export interface ITable<TPlayer extends {
3
+ key: string;
4
+ } = IPlayer> {
5
+ forehandIdx: number;
6
+ cards: Array<Array<IPlayedCard>>;
7
+ players: Array<TPlayer>;
8
+ nextHand(): TPlayer;
9
+ getPlayerByPosition(idx?: number, forehandFirst?: boolean): TPlayer;
10
+ getPlayerPosition(key: string, forehandFirst?: boolean): number;
11
+ getPlayersForehandFirst(forehandIdx?: number): Array<TPlayer>;
12
+ }
13
+ export declare function Table<TPlayer extends {
14
+ key: string;
15
+ } = IPlayer>(players: Array<TPlayer>): ITable<TPlayer>;
@@ -0,0 +1,34 @@
1
+ export function Table(players) {
2
+ const table = {
3
+ players,
4
+ cards: [],
5
+ forehandIdx: 0,
6
+ nextHand() {
7
+ if (table.forehandIdx < table.players.length - 1) {
8
+ table.forehandIdx++;
9
+ }
10
+ else {
11
+ table.forehandIdx = 0;
12
+ }
13
+ return table.getPlayerByPosition();
14
+ },
15
+ getPlayerPosition(key, forehandFirst = false) {
16
+ const array = forehandFirst ? table.getPlayersForehandFirst() : table.players;
17
+ return array.findIndex((p) => p.key === key);
18
+ },
19
+ getPlayersForehandFirst(forehand) {
20
+ const idx = forehand !== undefined ? forehand : table.forehandIdx;
21
+ const cut = players.slice(idx, table.players.length);
22
+ const end = players.slice(0, idx);
23
+ return cut.concat(end);
24
+ },
25
+ getPlayerByPosition(idx, forehandFirst = false) {
26
+ const array = forehandFirst ? table.getPlayersForehandFirst() : table.players;
27
+ if (idx !== undefined) {
28
+ return array[idx];
29
+ }
30
+ return array[0];
31
+ },
32
+ };
33
+ return table;
34
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./Deck";
2
+ export * from "./Table";
3
+ export * from "./Random";
@@ -0,0 +1,3 @@
1
+ export * from "./Deck";
2
+ export * from "./Table";
3
+ export * from "./Random";
@@ -0,0 +1,5 @@
1
+ import { IRound } from "../truco";
2
+ import { ICard } from "../types";
3
+ export declare function getMaxNumberIndex<T = number>(array: Array<T>): number;
4
+ export declare function getCardValue(card: ICard): number;
5
+ export declare function checkHandWinner(rounds: Array<IRound>, forehandTeamIdx: 0 | 1): null | 0 | 1;
@@ -0,0 +1,45 @@
1
+ import { CARDS } from "./constants";
2
+ export function getMaxNumberIndex(array) {
3
+ return array.reduce((accumulator, current, index) => {
4
+ return current > array[accumulator] ? index : accumulator;
5
+ }, 0);
6
+ }
7
+ export function getCardValue(card) {
8
+ return CARDS[card] !== undefined ? CARDS[card] : -2;
9
+ }
10
+ export function checkHandWinner(rounds, forehandTeamIdx) {
11
+ var _a, _b, _c;
12
+ const roundsWon = {
13
+ 0: 0,
14
+ 1: 0,
15
+ ties: 0,
16
+ };
17
+ for (let i = 0; i < rounds.length; i++) {
18
+ const round = rounds[i];
19
+ if (round.tie) {
20
+ roundsWon[0] += 1;
21
+ roundsWon[1] += 1;
22
+ roundsWon.ties = roundsWon.ties + 1;
23
+ continue;
24
+ }
25
+ if (((_a = round.winner) === null || _a === void 0 ? void 0 : _a.teamIdx) === 0) {
26
+ roundsWon[0] += 1;
27
+ }
28
+ if (((_b = round.winner) === null || _b === void 0 ? void 0 : _b.teamIdx) === 1) {
29
+ roundsWon[1] += 1;
30
+ }
31
+ }
32
+ if (roundsWon[0] > 2 && roundsWon[1] > 2) {
33
+ return forehandTeamIdx;
34
+ }
35
+ if (rounds.length > 2 && roundsWon.ties > 0 && ((_c = rounds[0]) === null || _c === void 0 ? void 0 : _c.winner)) {
36
+ return rounds[0].winner.teamIdx;
37
+ }
38
+ if (roundsWon[0] >= 2 && roundsWon[1] < 2) {
39
+ return 0;
40
+ }
41
+ if (roundsWon[1] >= 2 && roundsWon[0] < 2) {
42
+ return 1;
43
+ }
44
+ return null;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trucoshi",
3
- "version": "4.0.3",
3
+ "version": "4.0.4",
4
4
  "description": "Lightning Truco Server",
5
5
  "main": "dist/types.js",
6
6
  "license": "GPL-3.0",
@@ -47,10 +47,7 @@
47
47
  "ts-mocha": "^10.0.0"
48
48
  },
49
49
  "files": [
50
- "dist/lib/index.js",
51
- "dist/lib/index.d.ts",
52
- "dist/lib/constants.js",
53
- "dist/lib/constants.d.ts",
50
+ "dist/lib/*",
54
51
  "dist/types.js",
55
52
  "dist/types.d.ts",
56
53
  "dist/events.js",