Skip to content

Conversation

@Cyber-Mitch
Copy link

PR: Added on-chain ELO rating system to Tic-Tac-Toe

Summary

  • Feature: On-chain ELO rating system updates player ratings after each completed game.
  • Goal: Persistent ratings that reflect match quality using integer-only math suitable for Clarity.
  • Scope: Contract logic with a read-only API; minor frontend helper for fetching ratings.

Changes

  • Contract: contracts/tic-tac-toe.clar
    • Storage: ratings map principal => uint.
    • Constants: ELO_DEFAULT (u1200), ELO_K (u32), ELO_SCALE (u1000), ELO_TABLE (expected-score lookup).
    • Private functions:
      • elo-expected-high(diff): bucketed expected score for the higher-rated player.
      • update-elo(p1, p2, winner): computes and persists rating updates for both players.
    • Read-only:
      • get-rating(who) -> uint: stored rating or ELO_DEFAULT if unseen.
    • Integration:
      • play(...): after a winning move, pays out STX and calls update-elo with (player-one, player-two, winner).
  • Frontend: frontend/lib/contract.ts
    • Helper: getRating(address) calls the contract’s get-rating.

How it works

  • Default rating: New players start at 1200 via default-to in get-rating and update-elo.
  • Expected score:
    • Compute diff = |r1 - r2|, bucket by 100: bucket = diff / 100, clamp to [0, 8].
    • Lookup expected for higher-rated player from ELO_TABLE (scaled by 1000): [500, 640, 760, 850, 910, 947, 969, 983, 990].
    • Derive e1/e2: if r1 >= r2, e1 = e_high, else e1 = 1000 - e_high; e2 = 1000 - e1.
  • Update rule (ELO_K = 32, ELO_SCALE = 1000):
    • On win: rating += floor(K * (1 - e/1000)).
    • On loss: rating -= floor(K * (e/1000)).
    • Floors are applied via integer division; loser underflow guarded to 0.

Game flow interaction

  • When: Only on a winning move in play() once has-won(board) is true.
  • Order: STX payout → update-elo(...) → persist game → log print.
  • Access control: Elo updates occur within play() after turn validation using stored player-one and player-two.

Public API

  • New: get-rating(who principal) -> uint.
  • Unchanged: create-game, join-game, play signatures; play now updates Elo on wins.

Tests

  • File: tests/tic-tac-toe.test.ts
    • Default rating: get-rating returns 1200 for new players.
    • P1 win updates: From 1200/1200 to 1216/1184 (K=32, expected=0.5 ⇒ ±16).
    • P2 win updates: Symmetric case validated.
    • Loser decrement: Verified via get-rating before/after.

Frontend integration

  • Helper: frontend/lib/contract.ts#getRating(address) for UI display via read-only call.

Migration

  • None: ratings is lazily populated; unseen principals read as 1200.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant