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.
It's important to understand the distinction between "bula" and "final score" in Preferans:
Bula:
- Starting Bula: All players begin with an identical bula value, determining the length of the game.
- Game End: The game finishes when the combined bulas of all three players equal zero.
- Bula Tracking: Each player tracks their bula progression in the middle column of their scorecard.
Final Score Calculation: The final score used in rating calculations is much more complex than just the final bula. It involves:
- Middle Column: Your bula progression (can be positive-bad or negative-good)
- Side Columns: Points from tricks taken when other players were leading (multiplied by -10)
- Opponent Deductions: Subtracting what opponents documented taking from you in games where you were leading
The score
parameter in this library represents the final calculated score after all these complex scoring rules are applied, not the raw bula value.
const PrefRating = require("preferans-rating-js");
// Example: Three players with different ratings and final calculated scores
const player1 = {
username: 'Pedja',
rating: 1200, // Current rating before the game
score: -500 // Final calculated score (negative = good performance)
};
const player2 = {
username: 'Marko',
rating: 1150,
score: 40 // Final calculated score (positive = poor performance)
};
const player3 = {
username: 'Johnny',
rating: 1100,
score: 460 // Final calculated score (positive = poor performance)
};
// Note: In Preferans scoring, final scores typically sum to zero: -500 + 40 + 460 = 0
const startingBula = 30; // The initial bula value for all players
// Calculate rating changes
const rating = new PrefRating(player1, player2, player3, startingBula);
// Get the results
const result = rating.rating;
console.log(result);
/*
Output:
{
bula: 30,
p1: { username: 'Pedja', score: -500, oldRating: 1200, rating: 1173, change: -27 },
p2: { username: 'Marko', score: 40, oldRating: 1150, rating: 1152, change: 2 },
p3: { username: 'Johnny', score: 460, oldRating: 1100, rating: 1125, change: 25 }
}
*/
new PrefRating(player1, player2, player3, bula)
Parameters:
-
player1
,player2
,player3
: Objects withusername
(string),rating
(number), andscore
(number - final calculated score) -
bula
: Number representing the starting bula value for all players
Returns: PrefRating instance
-
rating
: Returns an object containing the bula and updated player information including rating changes
To grasp the complex scoring system, consider this detailed example with a starting bula of 30:
Players: Pedja, Marko, Johnny
- Order: Pedja, Marko, Johnny
Final Papers:
-
Pedja:
- Left (Johnny) = 320
- Middle (Bula) = -20
- Right (Marko) = 160
-
Marko:
- Left (Pedja) = 120
- Middle (Bula) = 4
- Right (Johnny) = 60
-
Johnny:
- Left (Marko) = 20
- Middle (Bula) = 16
- Right (Pedja) = 60
Calculations:
- Pedja: -20 x 10 - 320 - 160 + 120 + 60 = -500
- Marko: 4 x 10 - 120 - 60 + 160 + 20 = 40
- Johnny: 16 x 10 - 20 - 60 + 320 + 60 = 460
Result:
- Pedja: Good 500
- Marko: Bad 40
- Johnny: Bad 460
Explanation:
- The formula: Middle (Bula) x 10 - Left - Right + Points Opponents Took
- Negative scores are considered "good".
The rating system is inspired by chess ELO but adapted for 3-player games. The algorithm considers three main factors:
D₁₂ = (Player1.score - Player2.score) / bula
D₁₃ = (Player1.score - Player3.score) / bula
D₂₃ = (Player2.score - Player3.score) / bula
This measures actual performance differences, normalized by game length (bula).
T₁₂ = (Player2.rating - Player1.rating) × 2.8 / 100
T₁₃ = (Player3.rating - Player1.rating) × 2.8 / 100
T₂₃ = (Player3.rating - Player2.rating) × 2.8 / 100
This represents expected performance based on rating differences. The constant 2.8 is the "magic number" that scales rating differences.
N₁₂ = (Player1.score < Player2.score) ? -bula/100 : +bula/100
N₁₃ = (Player1.score < Player3.score) ? -bula/100 : +bula/100
N₂₃ = (Player2.score < Player3.score) ? -bula/100 : +bula/100
This gives a small bonus/penalty based on who performed better (remember: lower score = better in Preferans).
C₁₂ = D₁₂ + T₁₂ + N₁₂
C₁₃ = D₁₃ + T₁₃ + N₁₃
C₂₃ = D₂₃ + T₂₃ + N₂₃
Player1.change = round((C₁₂ + C₁₃) / 2)
Player2.change = round((-C₁₂ + C₂₃) / 2)
Player3.change = round((-C₁₃ + -C₂₃) / 2)
- D Values: -18, -32, -14 (Pedja dominated with -500 score)
- T Values: -1.4, -2.8, -1.4 (Pedja had highest rating, so negative expectations)
- N Values: -0.3, -0.3, -0.3 (All performed as expected relative to each other)
- Final Changes: Pedja: -27, Marko: +2, Johnny: +25
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.
- 3 Players: Each player's change considers performance against both opponents
- No Draws: Scores are always different (theoretically ties possible but extremely rare)
- Base Rating: Uses 1000 instead of chess's 1200
- Score Normalization: Performance scaled by bula (game length)
npm install preferans-rating-js
- Node.js ≥ 16.0.0
- TypeScript support included