This was previously in another (other) thread but that thread morphed into a discussion about a possible new algorithm so this is now the official home of the official reference implementation of the official algorithm for the universal ping schedule. It’s really official, ok? Until we reach consensus in that other thread, this is the algorithm all TagTime clients should implement!
First some constants and global variables:
const GAP = 45*60 // Average gap between pings, in seconds
const URPING = 1184097393 // Ur-ping ie the birth of Timepie/TagTime! (unixtime)
const SEED = 11193462 // Initial state of the random number generator
const IA = 16807 // =7^5: Multiplier for LCG random number generator
const IM = 2147483647 // =2^31-1: Modulus used for the RNG
// Above URPING is in 2007 and it's fine to jump to any later URPING/SEED pair
// like this one in 2018 -- URPING = 1532992625, SEED = 75570 -- without
// deviating from the universal ping schedule.
let pung = URPING // Global var with unixtime (in seconds) of last computed ping
let state = SEED // Global variable that's the state of the RNG
Here are the functions for generating random numbers in general:
// Linear Congruential Generator, returns random integer in {1, ..., IM-1}.
// This is ran0 from Numerical Recipes and has a period of ~2 billion.
function lcg() { return state = IA * state % IM } // lcg()/IM is a U(0,1) R.V.
// Return a random number drawn from an exponential distribution with mean m
function exprand(m) { return -m * Math.log(lcg()/IM) }
Here’s the only other helper function we need:
// Every TagTime gap must be an integer number of seconds not less than 1
function gap() { return Math.max(1, Math.round(exprand(GAP))) }
And here are the functions to compute successive ping times:
// Return unixtime of the next ping. First call init(t) and then call this in
// succession to get all the pings starting with the first one after time t.
function nextping() { return pung += gap() }
// Start at the beginning of time and walk forward till we hit the first ping
// strictly after time t. Then scooch the state back a step and return the first
// ping *before* (or equal to) t. Then we're ready to call nextping().
function init(t) {
let p, s // keep track of the previous values of the global variables
[pung, state] = [URPING, SEED] // reset the global state
for (; pung <= t; nextping()) { [p, s] = [pung, state] } // walk forward
[pung, state] = [p, s] // rewind a step
return pung // return most recent ping time <= t
}
Protypical usage is like so:
p = init(now)
print "Welcome to TagTime! Last ping would've been at time {p}."
repeat forever:
p = nextping()
while now < p: wait
print "PING! What are you doing right now, at time {p}?"
Note that in Javascript you get current unixtime in seconds with Date.now()/1000
(just Date.now()
returns it in milliseconds).
I have the above code running in Glitch here:
Glitch :・゚✧ (see pub/client.js)