Skip to Content
Development Guide

Last Updated: 3/10/2026


Development Guide

This guide will help you contribute to the TFT Rolling Odds Calculator, modify it for new TFT sets, or adapt it for your own purposes.

Getting Started

Prerequisites

  • A modern web browser (Chrome, Firefox, Safari, or Edge)
  • A text editor (VS Code, Sublime Text, Atom, etc.)
  • Basic knowledge of HTML, CSS, and JavaScript
  • Understanding of TFT game mechanics
  • (Optional) Git for version control

Local Development Setup

  1. Clone the repository
git clone https://github.com/enteigss/TFT-Rolling-Odds-Calculator.git cd TFT-Rolling-Odds-Calculator
  1. Open in browser

Simply open index.html in your web browser. No build process required!

# On macOS open index.html # On Linux xdg-open index.html # On Windows start index.html
  1. Make changes

Edit the files in your text editor and refresh the browser to see changes.

Project Structure

TFT-Rolling-Odds-Calculator/ ├── index.html # HTML structure and UI ├── style.css # Styling and layout ├── script.js # All JavaScript logic ├── markov.png # Diagram for documentation └── README.md # Project documentation

Updating for New TFT Sets

When a new TFT set is released, you’ll need to update the game data constants.

Step 1: Update Pool Sizes

Find the totalUnits constant in script.js:

const totalUnits = [22, 20, 17, 10, 9];

Update with the new set’s pool sizes for costs 1-5.

Example for Set 11:

// If Set 11 has different pool sizes const totalUnits = [29, 22, 18, 12, 10];

Step 2: Update Champion Counts

Find the distinctChamps constant:

const distinctChamps = [13, 13, 13, 13, 8];

Update with the number of unique champions at each cost.

How to count:

  1. Go to the TFT wiki or patch notes
  2. Count unique champions at each cost tier
  3. Update the array

Step 3: Update Shop Odds

Find the costProbs array:

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 ];

Each row represents a level (1-11) Each column represents a cost tier (1-5)

Example: At level 7, the odds are:

  • 19% for 1-cost
  • 35% for 2-cost
  • 35% for 3-cost
  • 10% for 4-cost
  • 1% for 5-cost
[0.19, 0.35, 0.35, 0.10, 0.01], // Level 7

Where to find shop odds:

  • Official Riot patch notes
  • TFT wiki
  • Community resources like lolchess.gg 

Step 4: Update Headliner Probabilities

If the set uses headliners (or similar mechanic), update:

const headlinerProbs = [ [0.077, 0, 0, 0, 0], // Level 2 [0.077, 0, 0, 0, 0], // Level 3 // ... continues ];

If the set doesn’t have headliners:

Set all values to 0:

const headlinerProbs = [ [0, 0, 0, 0, 0], // Level 2 [0, 0, 0, 0, 0], // Level 3 // ... all zeros ];

Step 5: Update the UI

Update the changelog in index.html:

<section id="changelog"> <ul> <li><strong>[Update as of DD/MM/YY]</strong>: Set 11 bag sizes and shop odds. XX/XX/XX/XX/XX for each cost, XX/XX/XX/XX/XX distinct champions for each cost </li> </ul> </section>

Step 6: Test

Verify your changes with known scenarios:

Test 1: Single shop probability

Level 8, 4-cost unit, full pool, 2 gold Manual calculation: 0.18 × (12/144) = 0.015 = 1.5% Calculator should show ~1.5% for "at least 1"

Test 2: Edge cases

  • All copies taken (should show 0%)
  • Impossible cost at low level (should show 0%)
  • Full pool at high level (should show high %)

Code Modification Guide

Adding Input Validation

Current code lacks validation. Here’s how to add it:

function validateInputs() { const cost = parseInt(document.getElementById("costRange").value); const lvl = parseInt(document.getElementById("lvlRange").value); const copies = parseInt(document.getElementById("copiesText").value); const pool = parseInt(document.getElementById("poolText").value); const gold = parseInt(document.getElementById("goldText").value); // Validate ranges if (copies < 0 || copies > totalUnits[cost - 1]) { alert("Copies out must be between 0 and " + totalUnits[cost - 1]); return false; } if (pool < 0) { alert("Pool size cannot be negative"); return false; } if (gold < 0 || gold % 2 !== 0) { alert("Gold must be a positive even number"); return false; } return true; } // Call before updating graph function updateGraph() { if (!validateInputs()) return; // ... rest of function }

Changing the Matrix Size

Currently tracks up to 9 copies. To track more:

  1. Change matrix size in all functions:
// Change from 10 to desired size (e.g., 20) const MATRIX_SIZE = 20; function getTransitionMatrix(cost, lvl, a, b){ const mat = []; for (let i = 0; i < MATRIX_SIZE; i++) { // ... update loop } }
  1. Update chart labels:
const labels = Array.from({length: MATRIX_SIZE - 1}, (_, i) => String(i + 1));
  1. Update headliner transition matrix size

Adding Exact Probability Display

Currently shows only cumulative. To add exact probabilities:

function updateGraph() { const probs = getProbs( parseInt(cost.value), parseInt(lvl.value), parseInt(copies.value), parseInt(pool.value), parseInt(gold.value) ); chart.data.datasets = [ { label: 'Probability of getting at least x units', data: probs[1].slice(1), backgroundColor: 'rgba(54, 162, 235, 0.5)' }, { label: 'Probability of getting exactly x units', data: probs[0].slice(1), backgroundColor: 'rgba(255, 99, 132, 0.5)' } ]; chart.update(); }

Optimizing Matrix Power Calculation

Current implementation is O(n). Use exponentiation by squaring for O(log n):

function powerOptimized(matrix, n) { if (n === 1) return matrix; if (n === 0) return identityMatrix(matrix.length); if (n % 2 === 0) { const half = powerOptimized(matrix, n / 2); return multiply(half, half); } else { return multiply(matrix, powerOptimized(matrix, n - 1)); } } function identityMatrix(size) { const mat = []; for (let i = 0; i < size; i++) { mat[i] = []; for (let j = 0; j < size; j++) { mat[i][j] = (i === j) ? 1 : 0; } } return mat; }

Styling Customization

Changing Color Scheme

Edit style.css:

/* Change background color */ body { background-color: #1a1a2e; /* Dark blue */ } /* Change slider color */ .slider::-webkit-slider-thumb { background: #ff6b6b; /* Red accent */ }

Responsive Design

Add media queries for mobile:

@media (max-width: 768px) { .slidecontainer { width: 95%; } h1 { font-size: 1.5em; } canvas { height: 40vh !important; } }

Testing

Manual Testing Checklist

  • All sliders move smoothly
  • Text inputs accept numbers
  • Chart updates on every input change
  • Probabilities sum to reasonable values
  • Edge cases handled (0 gold, full pool, etc.)
  • Works on mobile browsers
  • Works offline after initial load

Automated Testing Setup

To add unit tests (requires Node.js):

  1. Install Jest:
npm init -y npm install --save-dev jest
  1. Create test file (script.test.js):
// Import functions (need to export them first) const { getTransitionProb, multiply } = require('./script'); test('transition probability at level 1, 1-cost, full pool', () => { const prob = getTransitionProb(1, 1, 0, 0); expect(prob).toBeCloseTo(1.0 / 13, 3); }); test('matrix multiplication identity', () => { const A = [[1, 2], [3, 4]]; const I = [[1, 0], [0, 1]]; const result = multiply(A, I); expect(result).toEqual(A); });
  1. Run tests:
npm test

Contributing

Workflow

  1. Fork the repository on GitHub
  2. Clone your fork locally
  3. Create a feature branch
    git checkout -b feature/your-feature-name
  4. Make your changes
  5. Test thoroughly
  6. Commit with clear messages
    git commit -m "Add: Feature description"
  7. Push to your fork
    git push origin feature/your-feature-name
  8. Create a Pull Request on GitHub

Commit Message Guidelines

Add: New feature Fix: Bug fix Update: Data update (e.g., new set) Refactor: Code restructuring Docs: Documentation changes Style: Formatting, no code change

Code Style

  • Use 2-space indentation
  • Use const for constants, let for variables
  • Use descriptive function names
  • Add comments for complex logic
  • Keep functions small and focused

Common Issues

Issue: Chart not displaying

Cause: Chart.js CDN not loaded

Solution: Check internet connection or use local Chart.js:

<script src="./chart.min.js"></script>

Issue: Incorrect probabilities

Cause: Outdated game data

Solution: Verify totalUnits, distinctChamps, and costProbs match current patch

Issue: Slow performance with high gold

Cause: Many matrix multiplications

Solution: Implement optimized matrix power (see above)

Issue: Mobile display issues

Cause: Fixed dimensions

Solution: Add responsive CSS (see Styling section)

Deployment

GitHub Pages

  1. Push changes to main branch
  2. Go to repository Settings → Pages
  3. Select source: main branch, root directory
  4. Site will be live at https://username.github.io/repo-name/

Custom Domain

  1. Add CNAME file with your domain:
    yourdomain.com
  2. Configure DNS:
    A record: 185.199.108.153 A record: 185.199.109.153 A record: 185.199.110.153 A record: 185.199.111.153

Other Hosting Options

  • Netlify: Drag and drop folder
  • Vercel: Connect GitHub repo
  • Any static host: Upload files via FTP

Advanced Modifications

Adding Multiple Unit Tracking

To track probabilities for multiple units simultaneously:

  1. Expand state space (currently 10 states, would need 10^n for n units)
  2. Modify transition matrix to multi-dimensional
  3. Update UI to handle multiple inputs

Note: Computational complexity increases exponentially

Adding Expected Value Calculation

Show expected number of copies:

function getExpectedValue(cost, lvl, a, b, gold) { const probs = getProbs(cost, lvl, a, b, gold)[0]; // Exact probabilities let expected = 0; for (let i = 0; i < probs.length; i++) { expected += i * probs[i]; } return expected.toFixed(2); }

Adding Scenario Saving

Use localStorage to save/load scenarios:

function saveScenario(name) { const scenario = { cost: document.getElementById("costRange").value, lvl: document.getElementById("lvlRange").value, copies: document.getElementById("copiesText").value, pool: document.getElementById("poolText").value, gold: document.getElementById("goldText").value }; localStorage.setItem(name, JSON.stringify(scenario)); } function loadScenario(name) { const scenario = JSON.parse(localStorage.getItem(name)); if (scenario) { document.getElementById("costRange").value = scenario.cost; // ... set other inputs updateGraph(); } }

Resources

TFT Data Sources

Mathematics

Web Development

Getting Help

  • GitHub Issues: Report bugs or request features
  • Pull Requests: Submit improvements
  • Community: TFT subreddit, Discord servers

License

This project is open source. Check the repository for license details.