Skip to content

Instantly share code, notes, and snippets.

@jchros
Last active April 23, 2022 08:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jchros/73ab7d6b5551639ab6f23c54e7ce5082 to your computer and use it in GitHub Desktop.
Save jchros/73ab7d6b5551639ab6f23c54e7ce5082 to your computer and use it in GitHub Desktop.
A Common Lisp library for the Elo ranking system.
(defpackage :elo
(:use :cl))
(in-package :elo)
(defvar *expected-outcome-fun* nil
"This is used to supply an alternative function for
calculating the expected outcome of a game (whose
arguments are the Elo scores of the two players, and
returns a number between 0 and 1).
You shouldn't change this unless you know what you're
doing.")
(declaim (type (or null (function (real real) (real 0 1)))
*expected-outcome-fun*))
(defun expected-outcome (player-elo opponent-elo)
"The default function used for determining the expected
outcome of a game."
(check-type player-elo real)
(check-type opponent-elo real)
(the (real 0 1)
(/ (1+ (expt 10 (/ (- opponent-elo player-elo) 400))))))
(defun elo-change (player opponent outcome magnitude)
"Calculates the number of Elo points gained or lost by
PLAYER after a game against OPPONENT with the given
OUTCOME."
(check-type player real)
(check-type opponent real)
(check-type outcome (real 0 1))
(check-type magnitude (real (0)))
(* magnitude
(- outcome (funcall (or *expected-outcome-fun* #'expected-outcome)
player opponent))))
(defun game
(player keyword opponent &key (outcome nil outcomep) (magnitude nil magnitudep))
"Returns the Elo scores of PLAYER and OPPONENT after a game.
The KEYWORD argument can be any of :BEATS, :DRAWS,
:LOSES-TO, or :VS.
When the KEYWORD argument is :VS, the :OUTCOME is used to
specify the outcome of the game and must be provided,
otherwise it is ignored.
The :MAGNITUDE argument is mandatory."
(assert (shiftf magnitudep t)
(magnitude) "The :MAGNITUDE argument was not provided.")
(setf outcome
(ccase keyword
(:vs (assert (shiftf outcomep t)
(outcome) "The :OUTCOME argument was not provided.")
outcome)
(:beats 1)
(:draws 1/2)
(:loses-to 0)))
(when (and (not (eql keyword :vs)) outcomep)
(warn "The :OUTCOME argument has been ignored."))
(let ((elo-change (elo-change player opponent outcome magnitude)))
(values (+ player elo-change) (- opponent elo-change))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment