Skip to content

Commit 096de4c

Browse files
author
Predrag Stojadinovic
committed
chore: cleanup
1 parent 20e5412 commit 096de4c

11 files changed

+461
-155
lines changed

README.md

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,184 @@
99
[![dependencies](https://david-dm.org/prefko/preferans-rating-js.svg)](https://www.npmjs.com/package/preferans-rating-js)
1010
[![npm](https://img.shields.io/npm/dt/preferans-rating-js.svg)](https://www.npmjs.com/package/preferans-rating-js) [![Greenkeeper badge](https://badges.greenkeeper.io/prefko/preferans-rating-js.svg)](https://greenkeeper.io/)
1111

12-
preferans user rating
12+
A TypeScript/JavaScript library for calculating player rating changes in Preferans card games. This library implements a sophisticated rating algorithm that considers player performance, existing ratings, and game-specific parameters (bula) to determine fair rating adjustments after each game.
1313

1414
### Documentation
1515

1616
[TypeDoc documentation](https://prefko.github.io/preferans-rating-js/docs/)
1717

18+
### Bula vs Final Score
19+
20+
It's important to understand the distinction between "bula" and "final score" in Preferans:
21+
22+
**Bula:**
23+
- **Starting Bula**: All players begin with an identical bula value, determining the length of the game.
24+
- **Game End**: The game finishes when the combined bulas of all three players equal zero.
25+
- **Bula Tracking**: Each player tracks their bula progression in the middle column of their scorecard.
26+
27+
**Final Score Calculation:**
28+
The final score used in rating calculations is much more complex than just the final bula. It involves:
29+
- **Middle Column**: Your bula progression (can be positive-bad or negative-good)
30+
- **Side Columns**: Points from tricks taken when other players were leading (multiplied by -10)
31+
- **Opponent Deductions**: Subtracting what opponents documented taking from you in games where you were leading
32+
33+
The `score` parameter in this library represents the **final calculated score** after all these complex scoring rules are applied, not the raw bula value.
34+
1835
### Usage
1936

20-
const PrefRating = require("preferans-rating-js");
21-
...
37+
```javascript
38+
const PrefRating = require("preferans-rating-js");
39+
40+
// Example: Three players with different ratings and final calculated scores
41+
const player1 = {
42+
username: 'Pedja',
43+
rating: 1200, // Current rating before the game
44+
score: -500 // Final calculated score (negative = good performance)
45+
};
46+
47+
const player2 = {
48+
username: 'Marko',
49+
rating: 1150,
50+
score: 40 // Final calculated score (positive = poor performance)
51+
};
52+
53+
const player3 = {
54+
username: 'Johnny',
55+
rating: 1100,
56+
score: 460 // Final calculated score (positive = poor performance)
57+
};
58+
59+
// Note: In Preferans scoring, final scores typically sum to zero: -500 + 40 + 460 = 0
60+
const startingBula = 30; // The initial bula value for all players
61+
62+
// Calculate rating changes
63+
const rating = new PrefRating(player1, player2, player3, startingBula);
64+
65+
// Get the results
66+
const result = rating.rating;
67+
console.log(result);
68+
/*
69+
Output:
70+
{
71+
bula: 30,
72+
p1: { username: 'Pedja', score: -500, oldRating: 1200, rating: 1173, change: -27 },
73+
p2: { username: 'Marko', score: 40, oldRating: 1150, rating: 1152, change: 2 },
74+
p3: { username: 'Johnny', score: 460, oldRating: 1100, rating: 1125, change: 25 }
75+
}
76+
*/
77+
```
78+
79+
### API
80+
81+
#### Constructor
82+
```typescript
83+
new PrefRating(player1, player2, player3, bula)
84+
```
85+
86+
**Parameters:**
87+
- `player1`, `player2`, `player3`: Objects with `username` (string), `rating` (number), and `score` (number - final calculated score)
88+
- `bula`: Number representing the starting bula value for all players
89+
90+
**Returns:** PrefRating instance
91+
92+
#### Properties
93+
- `rating`: Returns an object containing the bula and updated player information including rating changes
94+
95+
### Detailed Example
96+
97+
To grasp the complex scoring system, consider this detailed example with a starting bula of 30:
98+
99+
**Players**: Pedja, Marko, Johnny
100+
- **Order**: Pedja, Marko, Johnny
101+
102+
**Final Papers**:
103+
- **Pedja**:
104+
- Left (Johnny) = 320
105+
- Middle (Bula) = -20
106+
- Right (Marko) = 160
107+
- **Marko**:
108+
- Left (Pedja) = 120
109+
- Middle (Bula) = 4
110+
- Right (Johnny) = 60
111+
- **Johnny**:
112+
- Left (Marko) = 20
113+
- Middle (Bula) = 16
114+
- Right (Pedja) = 60
115+
116+
**Calculations**:
117+
- **Pedja**: -20 x 10 - 320 - 160 + 120 + 60 = -500
118+
- **Marko**: 4 x 10 - 120 - 60 + 160 + 20 = 40
119+
- **Johnny**: 16 x 10 - 20 - 60 + 320 + 60 = 460
120+
121+
**Result**:
122+
- **Pedja**: Good 500
123+
- **Marko**: Bad 40
124+
- **Johnny**: Bad 460
125+
126+
**Explanation**:
127+
- The formula: Middle (Bula) x 10 - Left - Right + Points Opponents Took
128+
- Negative scores are considered "good".
129+
130+
### Rating Algorithm
131+
132+
The rating system is inspired by chess ELO but adapted for 3-player games. The algorithm considers three main factors:
133+
134+
#### Step 1: Calculate D Values (Score Performance)
135+
```
136+
D₁₂ = (Player1.score - Player2.score) / bula
137+
D₁₃ = (Player1.score - Player3.score) / bula
138+
D₂₃ = (Player2.score - Player3.score) / bula
139+
```
140+
This measures actual performance differences, normalized by game length (bula).
141+
142+
#### Step 2: Calculate T Values (Rating Expectations)
143+
```
144+
T₁₂ = (Player2.rating - Player1.rating) × 2.8 / 100
145+
T₁₃ = (Player3.rating - Player1.rating) × 2.8 / 100
146+
T₂₃ = (Player3.rating - Player2.rating) × 2.8 / 100
147+
```
148+
This represents expected performance based on rating differences. The constant **2.8** is the "magic number" that scales rating differences.
149+
150+
#### Step 3: Calculate N Values (Performance Bonus)
151+
```
152+
N₁₂ = (Player1.score < Player2.score) ? -bula/100 : +bula/100
153+
N₁₃ = (Player1.score < Player3.score) ? -bula/100 : +bula/100
154+
N₂₃ = (Player2.score < Player3.score) ? -bula/100 : +bula/100
155+
```
156+
This gives a small bonus/penalty based on who performed better (remember: lower score = better in Preferans).
157+
158+
#### Step 4: Combine and Average
159+
```
160+
C₁₂ = D₁₂ + T₁₂ + N₁₂
161+
C₁₃ = D₁₃ + T₁₃ + N₁₃
162+
C₂₃ = D₂₃ + T₂₃ + N₂₃
163+
164+
Player1.change = round((C₁₂ + C₁₃) / 2)
165+
Player2.change = round((-C₁₂ + C₂₃) / 2)
166+
Player3.change = round((-C₁₃ + -C₂₃) / 2)
167+
```
168+
169+
#### Example Calculation (Pedja/Marko/Johnny):
170+
- **D Values**: -18, -32, -14 (Pedja dominated with -500 score)
171+
- **T Values**: -1.4, -2.8, -1.4 (Pedja had highest rating, so negative expectations)
172+
- **N Values**: -0.3, -0.3, -0.3 (All performed as expected relative to each other)
173+
- **Final Changes**: Pedja: -27, Marko: +2, Johnny: +25
174+
175+
**Key Insight**: Pedja had the best performance (-500 score) but **lost** rating points because his high initial rating (1200) created high expectations that even his excellent performance couldn't fully meet.
176+
177+
#### Differences from Chess ELO:
178+
1. **3 Players**: Each player's change considers performance against both opponents
179+
2. **No Draws**: Scores are always different (theoretically ties possible but extremely rare)
180+
3. **Base Rating**: Uses 1000 instead of chess's 1200
181+
4. **Score Normalization**: Performance scaled by bula (game length)
182+
183+
### Installation
184+
185+
```bash
186+
npm install preferans-rating-js
187+
```
188+
189+
### Requirements
190+
191+
- Node.js ≥ 16.0.0
192+
- TypeScript support included

eslint.config.cjs

Lines changed: 97 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -5,101 +5,101 @@ const prettier = require('eslint-plugin-prettier');
55
const prettierConfig = require('eslint-config-prettier');
66

77
module.exports = [
8-
js.configs.recommended,
9-
{
10-
files: ['src/**/*.ts'],
11-
languageOptions: {
12-
parser: tsparser,
13-
parserOptions: {
14-
ecmaVersion: 2020,
15-
sourceType: 'module',
16-
project: './tsconfig.eslint.json',
17-
},
18-
},
19-
plugins: {
20-
'@typescript-eslint': tseslint,
21-
prettier: prettier,
22-
},
23-
rules: {
24-
// TypeScript ESLint recommended rules
25-
...tseslint.configs.recommended.rules,
26-
27-
// Prettier integration
28-
'prettier/prettier': 'error',
29-
30-
// Converted from TSLint rules
31-
'curly': ['error', 'multi-line', 'consistent'],
32-
'max-classes-per-file': ['error', 5],
33-
'@typescript-eslint/naming-convention': [
34-
'error',
35-
{
36-
selector: 'variable',
37-
format: ['camelCase', 'PascalCase'],
38-
leadingUnderscore: 'allow',
39-
},
40-
],
41-
'sort-imports': 'off', // equivalent to ordered-imports: false
42-
'sort-keys': 'off', // equivalent to object-literal-sort-keys: false
43-
44-
// Additional TypeScript-specific rules
45-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
46-
'@typescript-eslint/explicit-function-return-type': 'off',
47-
'@typescript-eslint/explicit-module-boundary-types': 'off',
48-
'@typescript-eslint/no-explicit-any': 'warn',
49-
50-
// Allow multiple variable declarations per statement (from TSLint config)
51-
'one-var': 'off',
52-
53-
// Disable some rules that might be too strict for this project
54-
'@typescript-eslint/no-inferrable-types': 'off',
55-
},
56-
},
57-
// Configuration for test files
58-
{
59-
files: ['test/**/*.ts'],
60-
languageOptions: {
61-
parser: tsparser,
62-
parserOptions: {
63-
ecmaVersion: 2020,
64-
sourceType: 'module',
65-
project: './tsconfig.eslint.json',
66-
},
67-
globals: {
68-
describe: 'readonly',
69-
it: 'readonly',
70-
test: 'readonly',
71-
expect: 'readonly',
72-
beforeAll: 'readonly',
73-
afterAll: 'readonly',
74-
beforeEach: 'readonly',
75-
afterEach: 'readonly',
76-
jest: 'readonly',
77-
},
78-
},
79-
plugins: {
80-
'@typescript-eslint': tseslint,
81-
prettier: prettier,
82-
},
83-
rules: {
84-
// TypeScript ESLint recommended rules
85-
...tseslint.configs.recommended.rules,
86-
87-
// Prettier integration
88-
'prettier/prettier': 'error',
89-
90-
// Test-specific rule overrides
91-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
92-
'@typescript-eslint/explicit-function-return-type': 'off',
93-
'@typescript-eslint/explicit-module-boundary-types': 'off',
94-
'@typescript-eslint/no-explicit-any': 'warn',
95-
'@typescript-eslint/no-unused-expressions': 'off', // Allow for Chai expressions
96-
97-
// Allow multiple variable declarations per statement
98-
'one-var': 'off',
99-
100-
// Disable some rules that might be too strict for test files
101-
'@typescript-eslint/no-inferrable-types': 'off',
102-
},
103-
},
104-
prettierConfig, // This should be last to override other formatting rules
8+
js.configs.recommended,
9+
{
10+
files: ['src/**/*.ts'],
11+
languageOptions: {
12+
parser: tsparser,
13+
parserOptions: {
14+
ecmaVersion: 2020,
15+
sourceType: 'module',
16+
project: './tsconfig.eslint.json'
17+
}
18+
},
19+
plugins: {
20+
'@typescript-eslint': tseslint,
21+
prettier: prettier
22+
},
23+
rules: {
24+
// TypeScript ESLint recommended rules
25+
...tseslint.configs.recommended.rules,
26+
27+
// Prettier integration
28+
'prettier/prettier': 'error',
29+
30+
// Converted from TSLint rules
31+
'curly': ['error', 'multi-line', 'consistent'],
32+
'max-classes-per-file': ['error', 5],
33+
'@typescript-eslint/naming-convention': [
34+
'error',
35+
{
36+
selector: 'variable',
37+
format: ['camelCase', 'PascalCase'],
38+
leadingUnderscore: 'allow'
39+
}
40+
],
41+
'sort-imports': 'off', // equivalent to ordered-imports: false
42+
'sort-keys': 'off', // equivalent to object-literal-sort-keys: false
43+
44+
// Additional TypeScript-specific rules
45+
'@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
46+
'@typescript-eslint/explicit-function-return-type': 'off',
47+
'@typescript-eslint/explicit-module-boundary-types': 'off',
48+
'@typescript-eslint/no-explicit-any': 'warn',
49+
50+
// Allow multiple variable declarations per statement (from TSLint config)
51+
'one-var': 'off',
52+
53+
// Disable some rules that might be too strict for this project
54+
'@typescript-eslint/no-inferrable-types': 'off'
55+
}
56+
},
57+
// Configuration for test files
58+
{
59+
files: ['test/**/*.ts'],
60+
languageOptions: {
61+
parser: tsparser,
62+
parserOptions: {
63+
ecmaVersion: 2020,
64+
sourceType: 'module',
65+
project: './tsconfig.eslint.json'
66+
},
67+
globals: {
68+
describe: 'readonly',
69+
it: 'readonly',
70+
test: 'readonly',
71+
expect: 'readonly',
72+
beforeAll: 'readonly',
73+
afterAll: 'readonly',
74+
beforeEach: 'readonly',
75+
afterEach: 'readonly',
76+
jest: 'readonly'
77+
}
78+
},
79+
plugins: {
80+
'@typescript-eslint': tseslint,
81+
prettier: prettier
82+
},
83+
rules: {
84+
// TypeScript ESLint recommended rules
85+
...tseslint.configs.recommended.rules,
86+
87+
// Prettier integration
88+
'prettier/prettier': 'error',
89+
90+
// Test-specific rule overrides
91+
'@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_'}],
92+
'@typescript-eslint/explicit-function-return-type': 'off',
93+
'@typescript-eslint/explicit-module-boundary-types': 'off',
94+
'@typescript-eslint/no-explicit-any': 'warn',
95+
'@typescript-eslint/no-unused-expressions': 'off', // Allow for Chai expressions
96+
97+
// Allow multiple variable declarations per statement
98+
'one-var': 'off',
99+
100+
// Disable some rules that might be too strict for test files
101+
'@typescript-eslint/no-inferrable-types': 'off'
102+
}
103+
},
104+
prettierConfig // This should be last to override other formatting rules
105105
];

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "preferans-rating-js",
3-
"version": "2.4.4",
3+
"version": "2.4.5",
44
"description": "nodejs rating for preferans",
55
"engines": {
66
"node": ">=16.0.0"

0 commit comments

Comments
 (0)