Last Updated: 3/10/2026
Technical Implementation
This article provides a comprehensive technical overview of how the TFT Rolling Odds Calculator is implemented, covering the architecture, key algorithms, and code structure.
Architecture Overview
The calculator is a client-side web application with no backend dependencies:
┌─────────────────────────────────────────┐
│ index.html │
│ (UI Structure & Input Controls) │
└──────────────┬──────────────────────────┘
│
├──> style.css (Styling)
│
├──> Chart.js (Visualization)
│
└──> script.js (Core Logic)
├─ Event Handlers
├─ Probability Calculations
├─ Matrix Operations
└─ Chart UpdatesTechnology Stack
- HTML5: Structure and input controls
- CSS3: Styling and layout
- Vanilla JavaScript: All logic (no frameworks)
- Chart.js: Bar chart visualization
- Mathematics: Linear algebra (matrix operations)
File Structure
TFT-Rolling-Odds-Calculator/
├── index.html # Main HTML structure
├── style.css # Styling
├── script.js # All JavaScript logic
├── markov.png # Diagram for README
└── README.md # Project documentationCore Data Structures
Game Configuration Constants
const totalUnits = [22, 20, 17, 10, 9];
const distinctChamps = [13, 13, 13, 13, 8];totalUnits[i]: Number of copies of each champion at cost tier i+1distinctChamps[i]: Number of unique champions at cost tier i+1
Shop Odds Table
const costProbs = [
[1, 0, 0, 0, 0], // Level 1
[1, 0, 0, 0, 0], // Level 2
[0.75, 0.25, 0, 0, 0], // Level 3
// ... (continues for all 11 levels)
];A 2D array where costProbs[level-1][cost-1] gives the probability of rolling a unit of that cost at that level.
Headliner Probabilities
const headlinerProbs = [
[0.077, 0, 0, 0, 0], // Level 2
[0.077, 0, 0, 0, 0], // Level 3
// ... (continues through level 10)
];Probability of getting a headliner of each cost at each level.
Key Algorithms
1. Main Probability Calculation
function getProbs(cost, lvl, a, b, gold) {
var mat = getCombOfMatrices(cost, lvl, a, b, gold)
const pprob = mat[0] // Exact probabilities
// Convert to cumulative probabilities
let cprob = [1];
for (let i = 1; i < 10; i++) {
let p = 1;
for (let j = 0; j < i; j++) {
p -= pprob[j];
}
cprob.push(p.toFixed(2));
}
return [pprob, cprob];
}Flow:
- Generate combined transition matrix for all possible scenarios
- Extract probability distribution from first row
- Convert exact probabilities to cumulative (“at least X”)
- Return both formats
2. Transition Matrix Generation
function getTransitionMatrix(cost, lvl, a, b){
const mat = [];
for (let i = 0; i < 10; i++) {
const newRow = [];
const p = getTransitionProb(cost, lvl, a+i, b+i);
for (let j = 0; j < 10; j++) {
if (i == 9 && j == 9) {
newRow.push(1); // Absorbing state
continue;
}
if (j == i) {
newRow.push(1 - p); // Stay in same state
} else if (j == i + 1) {
newRow.push(p); // Transition to next state
} else {
newRow.push(0); // No other transitions
}
}
mat.push(newRow);
}
return mat;
}Matrix Structure:
- 10×10 matrix representing states (0-9 units found)
- Row
i= currently haveiunits - Column
j= end withjunits after one shop mat[i][j]= probability of transitioning from state i to state j
Key Properties:
- Tri-diagonal structure (can only stay or advance by 1)
- State 9 is absorbing (represents 9+ units)
- Transition probability decreases as pool depletes
3. Single Shop Transition Probability
function getTransitionProb(cost, lvl, a, b){
const howManyLeft = Math.max(0, totalUnits[cost - 1] - a);
const poolSize = totalUnits[cost - 1] * distinctChamps[cost - 1] - b;
return getCostProb(lvl, cost) * (howManyLeft / poolSize)
}Formula:
P(hit unit) = P(roll this cost tier) × (copies left / total units of this cost left)4. Headliner Integration
function getCombOfMatrices(cost, lvl, a, b, gold){
const rolls = Math.floor(gold/2);
var p = [10×10 zero matrix];
const allTransitionMatrices = getAllTransitionMatrices(cost, lvl, a, b, gold);
const headlinerWhichShopProbs = getHeadlinerWhichShopProbs(cost, lvl, gold);
// Weighted sum over all possible headliner scenarios
for (let i = 0; i < rolls; i++){
const combined = multiplyScalar(allTransitionMatrices[i], headlinerWhichShopProbs[i]);
p = addMatrices(p, combined);
}
// Add scenario where no headliner appears
const noHeadlinerM = power(getTransitionMatrix(cost, lvl, a, b), 5*rolls);
const prNoHeadliner = (1-headlinerProbs[lvl-2][cost-1])**(rolls);
const c = multiplyScalar(noHeadlinerM, prNoHeadliner);
p = addMatrices(p, c);
return p;
}Logic:
- For each possible roll where headliner appears:
- Calculate transition matrix up to that point
- Apply headliner transition (instant +3 copies)
- Continue normal rolling after
- Weight by probability of headliner appearing at that roll
- Add the scenario where no headliner appears at all
- Sum all weighted scenarios using law of total probability
5. Headliner Transition Matrix
const headlinerTransitionMatrix = [
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0], // 0 → 3 units
[0, 0, 0, 0, 1, 0, 0, 0, 0, 0], // 1 → 4 units
[0, 0, 0, 0, 0, 1, 0, 0, 0, 0], // 2 → 5 units
// ... (each row shifts right by 3)
];When a headliner appears, you instantly get +3 copies, so this matrix jumps 3 states forward.
6. Matrix Operations
Matrix Multiplication
function multiply(a, b){
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows);
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols);
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0;
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}Standard matrix multiplication: O(n³) complexity.
Matrix Power
function power(a, n){
var newmat = a
for (let i = 0; i < n-1; i++) {
newmat = multiply(newmat, a);
}
return newmat;
}Usage: M^n represents n consecutive shops.
Note: This is a naive implementation. For large n, could be optimized with exponentiation by squaring.
UI and Event Handling
Input Controls
document.getElementById("costRange").oninput = function(){
updateCost();
updateGraph();
}
document.getElementById("lvlRange").oninput = function(){
updateLvl();
updateGraph();
}
document.getElementById("copiesText").oninput = function(){
updateGraph();
}
// ... (similar for other inputs)All inputs trigger immediate recalculation and chart update.
Chart Update
function updateGraph() {
chart.data.datasets = [{
label: 'Probability of getting at least x units',
data: getProbs(
parseInt(cost.value),
parseInt(lvl.value),
parseInt(copies.value),
parseInt(pool.value),
parseInt(gold.value)
)[1].slice(1) // Use cumulative probs, skip "at least 0"
}]
chart.update();
}Chart Configuration
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ['1','2','3','4','5','6','7','8','9'],
datasets: [{
label: 'Probability of getting at least x units',
data: [0, 0, 0, 0, 0, 0, 0, 0, 0]
}]
},
options: {
scales: {
y: {
suggestedMin: 0,
suggestedMax: 1,
beginAtZero: true
}
},
plugins: {
tooltip: {
displayColors: false,
bodyFont: { size: 20 },
// Custom callbacks for clean display
}
}
}
});Performance Considerations
Computational Complexity
For gold amount g (number of rolls = g/2):
- Matrix size: 10×10 (fixed)
- Matrix multiplications: O(g) operations
- Each multiplication: O(10³) = O(1000) operations
- Total: O(g) - linear in gold amount
Optimization Opportunities
-
Matrix Exponentiation by Squaring
- Current: O(n) multiplications for M^n
- Optimized: O(log n) multiplications
- Benefit: Significant for large gold amounts (100+)
-
Sparse Matrix Operations
- Transition matrices are tri-diagonal
- Could use specialized sparse matrix algorithms
- Benefit: ~3x speedup
-
Memoization
- Cache transition matrices for common states
- Benefit: Faster UI updates when changing only gold amount
-
Web Workers
- Move heavy calculations off main thread
- Benefit: Smoother UI during computation
Current Performance
On modern hardware:
- Typical calculation (50 gold): < 10ms
- Large calculation (200 gold): < 100ms
- UI remains responsive throughout
Testing and Validation
Manual Test Cases
Test 1: Level 1, 1-cost unit, full pool
Input: cost=1, lvl=1, copies=0, pool=0, gold=10
Expected: High probability (100% shop odds × full pool)Test 2: Impossible scenario
Input: cost=5, lvl=1, copies=0, pool=0, gold=100
Expected: 0% (can't roll 5-costs at level 1)Test 3: Depleted pool
Input: cost=1, lvl=8, copies=22, pool=X, gold=50
Expected: 0% (all copies already taken)Validation Against Known Probabilities
The mathematics can be validated against:
- Single-shop probabilities (simple formula)
- Binomial approximations (for large pools)
- Community-verified edge cases
Code Quality
Strengths
- ✅ Clear function names
- ✅ Modular design
- ✅ No external dependencies (except Chart.js)
- ✅ Works offline after initial load
Areas for Improvement
- ⚠️ No input validation (can enter negative numbers)
- ⚠️ No unit tests
- ⚠️ Magic numbers could be constants
- ⚠️ Could benefit from JSDoc comments
Extending the Calculator
Adding New TFT Sets
- Update
totalUnitsarray - Update
distinctChampsarray - Update
costProbstable if shop odds changed - Update
headlinerProbsif mechanic changed - Test with known scenarios
Adding New Features
Possible enhancements:
- Multiple unit tracking
- Expected value calculations
- Save/load scenarios
- Probability heatmaps
- Mobile-optimized UI
See the Development Guide for detailed instructions.
Dependencies
Chart.js
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>- Version: Latest from CDN
- Purpose: Bar chart visualization
- License: MIT
- Docs: chartjs.org
No Build Process
The application requires no build step:
- No npm/yarn
- No webpack/bundler
- No transpilation
- Just open
index.htmlin a browser!
Browser Compatibility
Required Features
- ES6 JavaScript (arrow functions, const/let)
- HTML5
- CSS3
- Canvas API (for Chart.js)
Supported Browsers
- Chrome 60+
- Firefox 55+
- Safari 11+
- Edge 79+
- Mobile browsers (iOS Safari, Chrome Mobile)
Deployment
The application is deployed using GitHub Pages:
- Code pushed to
mainbranch - GitHub Pages serves from root directory
- Accessible at:
https://wongkj12.github.io/TFT-Rolling-Odds-Calculator/
No server required - purely static hosting.
Related Articles
- Mathematical Background - Deep dive into the Markov Chain theory
- Development Guide - How to contribute and modify
- Game Mechanics Reference - TFT-specific data