Teaser 3305: Things with scales and things to scale with
From The Sunday Times, 25th January 2026 [link] [link]
Rummaging through a cupboard I came across an old Snakes and Ladders set. The ladders (label them A to E) went from square 4 to 26, 18 to 28, 19 to 73, 22 to 60, and 41 to 98. The snakes (label them W to Z) went from square 53 to 24, 58 to 5, 72 to 33, and 90 to 66. For old times’ sake I couldn’t resist playing a game on my own. Starting from square 1, rolling the die and moving appropriately, travelling travelling up ladders and down snakes when I chanced to land on their starting squares, I happened to finish exactly on square 100. The total of all the numbers I had thrown on the die was 51.
What was the order of all my up and down jumps? (e.g. A, Y, D).
[teaser3305]





Jim Randell 8:56 am on 25 January 2026 Permalink |
Here is a program that attempts to constructively play games that finish exactly on square 100 and hve a throw total of 51.
We assume that once a possible game is found, all other possible games have the same sequence of jumps. (I’m thinking about better ways to show the answer to the puzzle is unique).
It runs in 77ms. (Internal runtime is 1.1ms).
from enigma import (tuples, seq2str, printf) # snakes and ladders jumps = { # ladders (lower -> higher) (4, 26): 'A', (18, 28): 'B', (19, 73): 'C', (22, 60): 'D', (41, 98): 'E', # snakes (higher -> lower) (53, 24): 'W', (58, 5): 'X', (72, 33): 'Y', (90, 66): 'Z', } # linked squares link = dict(jumps.keys()) # possible throws throws = [6, 5, 4, 3, 2, 1] # play the game # p = current position # t = target position # b = throw budget (must not be exceeded) # ds = dice throws # ps = positions def play(p, t, b, ds=[], ps=[]): ps = ps + [p] # are we done if p == t or p >= 100 or b <= 0: yield (ds, ps, b) elif b > 0: # make the next roll for d in throws: if not (d > b): # move the player p1 = p + d # perform any jump p2 = link.get(p1) # continue play if p2 is None: yield from play(p1, t, b - d, ds + [d], ps) else: yield from play(p2, t, b - d, ds + [d], ps + [p1]) # find a possible game for (ds, ps, r) in play(1, 100, 51): if r == 0 and ps[-1] == 100: printf("throws = {ds} (sum = {t}) -> positions = {ps}", t=sum(ds)) # find the jumps involved js = tuple(filter(None, (jumps.get(k) for k in tuples(ps, 2)))) printf("jumps = {js}", js=seq2str(js, enc="[]")) breakSolution: [To Be Revealed]
LikeLike
Jim Randell 1:28 pm on 25 January 2026 Permalink |
Here is a better solution. It is shorter, faster, can be used to experiment with different throw budgets, and finds all solutions (thereby showing the answer to the original puzzle is unique):
We don’t care about the actual dice throws, a game is made up of a sequence of contiguous blocks (that are moved with throws of the dice) with jumps (i.e. a snake or a ladder) between the blocks. The blocks themselves can usually be made by lots of dice throws (and the starts of the jumps are sufficiently fragmented that we can always avoid any unwanted jumps).
This Python program makes a collection of possible contiguous segments, and then joins a sequence of them together with jumps inbetween to make a game that starts at 1, ends at 100, and requires a total of 51 to be scored on the dice throws during the segments.
It runs in 71ms. (Internal runtime is 88µs).
from enigma import (defaultdict, tuples, seq2str, printf) # snakes and ladders jumps = { # ladders (lower -> higher) (4, 26): 'A', (18, 28): 'B', (19, 73): 'C', (22, 60): 'D', (41, 98): 'E', # snakes (higher -> lower) (53, 24): 'W', (58, 5): 'X', (72, 33): 'Y', (90, 66): 'Z', } # linked squares link = dict(jumps.keys()) # possible contiguous segments; segs: start -> ends segs = defaultdict(list) seg = lambda x, y: segs[x].append(y) seg(1, 100) # no jumps for (a, b) in jumps.keys(): # add in 1 to the start of a jump seg(1, a) # add in end of a jump to 100 seg(b, 100) # add in end of a jump to start of a jump for (x, y) in jumps.keys(): if b < x: seg(b, x) # find contiguous segments from <s> to <t> with throw budget <b> def solve(s, t, b, ss=[]): # are we done? if s == t and b == 0: yield ss elif s < 100 and b > 0: # add in the next segment for x in segs[s]: d = x - s if not (d > b): yield from solve(link.get(x, x), t, b - d, ss + [(s, x)]) # find games from 1 to 100 with a throw budget of B B = 51 for ss in solve(1, 100, B): # find jumps used js = tuple(jumps[(b, x)] for ((a, b), (x, y)) in tuples(ss, 2)) printf("{B}: {ss} -> {js}", js=seq2str(js, enc="[]"))LikeLike