Skip to content

Instantly share code, notes, and snippets.

@jchros
Last active April 23, 2022 08:04
Show Gist options
  • Select an option

  • Save jchros/73ab7d6b5551639ab6f23c54e7ce5082 to your computer and use it in GitHub Desktop.

Select an option

Save jchros/73ab7d6b5551639ab6f23c54e7ce5082 to your computer and use it in GitHub Desktop.

Revisions

  1. jchros revised this gist Apr 23, 2022. 1 changed file with 8 additions and 6 deletions.
    14 changes: 8 additions & 6 deletions elo.lisp
    Original file line number Diff line number Diff line change
    @@ -46,12 +46,14 @@ otherwise it is ignored.
    The :MAGNITUDE argument is mandatory."
    (assert (shiftf magnitudep t)
    (magnitude) "The :MAGNITUDE argument was not provided.")
    (ccase keyword
    (:vs (assert (shiftf outcomep t)
    (outcome) "The :OUTCOME argument was not provided."))
    (:beats 1)
    (:draws 1/2)
    (:loses-to 0))
    (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)))
  2. jchros revised this gist Apr 23, 2022. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions elo.lisp
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,8 @@
    (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
  3. jchros created this gist Apr 23, 2022.
    56 changes: 56 additions & 0 deletions elo.lisp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,56 @@
    (defpackage :elo
    (:use :cl))

    (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.")
    (ccase keyword
    (:vs (assert (shiftf outcomep t)
    (outcome) "The :OUTCOME argument was not provided."))
    (: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))))