Enigmatic Code

Programming Enigma Puzzles

Tantalizer 437: Miniatures

From New Scientist #988, 19th February 1976 [link]

When Pestle arrived at Mortar’s house last night for their weekly game of chess, he had forgotten to bring the pieces. Unsmilingly Mortar produces a board and a supply of Brandy, Gin, Kirsch, Rum, Vodka and Whisky in miniature bottles. Captures having been drunk, the game declined in quality, finally reaching this position. But Mortar had the harder head as well as the white pieces and delivered mate on the move.

The black circled pieces are black (white ones having had their tops removed at the start of play) and each kind of piece was represented by a different drink. Whenever a Vodka threatened a Gin, the Gin also threatened the Vodka. Whenever a Brandy threatened a Whisky, the Whiskey did not threaten the Brandy. Whenever a Kirsch did not threaten a Rum, the Rum did not threaten the Kirsch.

What was Mortar’s mating move?

[tantalizer437]

One response to “Tantalizer 437: Miniatures

  1. Jim Randell 23 January 2019 at 7:48 am

    This puzzle is reminiscent of Enigma 135 (also by Martin Hollis). I took a similar approach, although this time we don’t need to write a program to play chess backwards. Again I used [ https://en.wikipedia.org/wiki/Chess ] as my reference.

    I made some (reasonable) simplifications in my program. I don’t consider “castling” moves (but as both Kings have already been moved I don’t think it is possible), nor “en passant” captures for pawns. I don’t consider promotions in my code, but as we are only going to play a single move I don’t think this matters.

    The layout of the board given means some assignments of chess pieces to the drinks are not possible. The white gin and vodka are on row 1, so neither of them can be Pawns (Pawns can’t move backwards), and the white whisky and brandy are on row 8, so neither of those can be Pawns either (as they would have been promoted to something else). Also there are two black rums which are both on white squares, so without promotions rum cannot be King, Queen or Bishop, but with promotions there could be multiple Queens or Bishops that move on white squares.

    This Python 3 program determines possible assignments using the three statements about the behaviour of the pieces, and then looks for a move for white which will put black in check. It runs in 234ms.

    Run: [ @repl.it ]

    from itertools import product, permutations
    from enigma import irange, update, cached, printf
    
    # find value <v> in dictionary <d>
    def find(d, v):
      for (k, v1) in d.items():
        if v1 == v: return k
      return None
    
    # is position p = (x, y) a valid square on the chessboard
    def valid(p):
      (x, y) = p
      return 0 <= x <= 7 and 0 <= y <= 7
    
    # position in algebraic chess notation
    def pos(p):
      (x, y) = p
      return "abcdefgh"[x] + "12345678"[y]
    
    # swap sides (white <-> black)
    def swap(p):
      (x, y) = p
      return (x, 7 - y)
    
    # rotational symmetry order 4
    rot4 = lambda x, y: [(x, y), (-y, x), (-x, -y), (y, -x)]
    
    # adjacent delta moves (horizontal / vertical)
    h_v = rot4(0, 1)
    
    # diagonal delta moves
    dia = rot4(1, 1)
    
    # deltas in all directions
    adj = h_v + dia
    
    # knight move deltas
    knight = rot4(2, 1) + rot4(1, 2)
    
    # a chess board, white to move
    class Board(object):
    
      # white and black are dicts of <pos> -> <piece>
      def __init__(self, white, black):
        self.white = white
        self.black = black
    
      # find valid moves from <p> along a line delta <d>, max distance <k>
      def line(self, p, d, k=7):
        ((x, y), (dx, dy)) = (p, d)
        for i in irange(1, k):
          p = (x + i * dx, y + i * dy)
          # are we still on the board?
          if not valid(p): break
          # have we hit a white piece?
          if p in self.white: break
          # otherwise, this is a valid move
          yield p
          # have we hit a black piece?
          if p in self.black: break
    
      # moves along lines with deltas in <ds>
      def lines(self, p, ds, k=7):
        for d in ds:
          yield from self.line(p, d, k)
    
      # possible moves for the various pieces:
    
      # castle (rook)
      def R(self, p):
        return self.lines(p, h_v)
    
      # bishop
      def B(self, p):
        return self.lines(p, dia)
    
      # queen
      def Q(self, p):
        return self.lines(p, adj)
    
      # king (ignoring castling)
      def K(self, p):
        return self.lines(p, adj, 1)
    
      # knight
      def N(self, p):
        return self.lines(p, knight, 1)
    
      # pawn (ignoring: "en passant" and promotion)
      def P(self, p):
        (x, y) = p
        # pawns can move forward one (if not blocked)
        p1 = (x, y + 1)
        if valid(p1) and not(p1 in self.white or p1 in self.black):
          yield p1
          # or forward 2 if on home row (and not blocked)
          if y == 1:
            p2 = (x, y + 2)
            if not(p2 in self.white or p2 in self.black):
              yield p2
        # or can take a piece diagonally
        for p in [(x - 1, y), (x + 1, y)]:
          if p in self.black: yield p
    
      # generate moves for piece <n> @ position <s>
      def moves(self, s, n):
        return getattr(self, n)(s)
    
      # does white have black in check?
      def check(self):
        # find the black king
        k = find(self.black, 'K')
        # can white piece <n> @ <s> attack it?
        for (s, n) in white.items():
          if k in self.moves(s, n): return s
    
      # play a move from <s> to <p>
      def play(self, s, t):
        (white, black) = (self.white, self.black)
        # move the white piece
        n = white[s]
        white = update(white, [(t, n)])
        del white[s]
        # is there a capture?
        if t in black:
          black = update(black, [])
          del black[t]
        # return the new board
        return Board(white, black)
    
    # for each of the pairs of pieces can we find an example where:
    #
    #  TT: (A threatens B) and (B threatens A)
    #  TN: (A threatens B) and (B does not threaten A)
    
    @cached
    def T(A, B, f):
      # consider positions for white piece A
      xs = irange(0, 3) # there is left/right symmetry
      ys = irange(*((1, 6) if A == 'P' else (0, 7)))
      for p in product(xs, ys):
        # consider the squares that A potentially threatens
        b = Board({ p: A }, {})
        for q in b.moves(p, A):
          # check A threatens B
          b1 = Board({ p: A }, { q: B })
          if not(q in b1.moves(p, A)): continue
          # check if B threatens A
          (p2, q2) = (swap(p), swap(q))
          b2 = Board({ q2: B }, { p2: A })
          # is A threatened by B?
          if (p2 in b2.moves(q2, B)) == f:
            return (p, q)
    
    TT = lambda A, B: T(A, B, True)
    TN = lambda A, B: T(A, B, False)
    
    # consider possible assignments of drinks to chess pieces
    for (b, g, k, r, v, w) in permutations('KQRNBP'):
    
      # we can skip certain assigments:
      # - pawns cannot be on the end rows
      # - there is only one king
      if g == 'P' or v == 'P' or w == 'P' or b == 'P' or r == 'K': continue
    
      # "if v threatens g then g threatens v"
      # a counterexample is: (v threatens g) and (g does not threaten v) = TN(v, g)
      if TN(v, g): continue
      # "if b threatens w then w does not threaten b"
      # a counterexample is: (b threatens w) and (w threatens b) = TT(b, w)
      if TT(b, w): continue
      # "if k does not threaten r then r does not threaten k"
      # a counterexample is: (k does not threaten r) and (r threatens k) = TN(r, k)
      if TN(r, k): continue
      
      # the given board 
      white = { (0, 0): g, (7, 0): v, (1, 1): r, (3, 6): k, (0, 7): w, (7, 7): b }
      black = { (5, 5): k, (0, 4): g, (3, 4): w, (5, 4): r, (6, 4): v, (0, 3): r, (4, 2): b }
      b1 = Board(white, black)
    
      # black is not currently in check
      if b1.check(): continue
    
      # for each white piece...
      for (s, n) in white.items():
        # ... consider possible moves
        for t in b1.moves(s, n):
          # if we play this move...
          b2 = b1.play(s, t)
          # ... does it leave black in check?
          x = b2.check()
          if x:
            printf("brandy={b} gin={g} kirsch={k} rum={r} vodka={v} whisky={w}")
            printf("+ move {n} @ {s} -> {t}, check from {x}", s=pos(s), t=pos(t), x=pos(x))
    

    Solution: Mortar’s mating move was to move the white pawn (rum) at b2.

    This move puts the black king (kirsch) at f6 in check from the white queen (gin) at a1.

    It seems most likely that white would move the pawn at b2 to b4, as black will have to use his next move to get out of check, and so wouldn’t be able to capture the pawn “en passant”.

    I found there are three ways of assigning the drinks to the chess pieces. However:

    Kirsch is always a King, and rum is always a Pawn, so the third statement is:

    If a King does not threaten a Pawn, then the Pawn does not threaten the King.

    which seems reasonable.

    Gin is always a Queen, and vodka is either a Bishop or a Rook, so the first statement is one of:

    If a Bishop threatens a Queen, then the Queen threatens the Bishop.
    If a Rook threatens a Queen, then the Queen threatens the Rook.

    which also seem reasonable.

    Brandy and whisky are (Rook, kNight), (kNight, Bishop) or (kNight, Rook), so the second statement is one of:

    If a Rook threatens a kNight, then the kNight does not threaten the Rook.
    If a kNight threatens a Bishop, then the Bishop does not threaten the kNight.
    If a kNight threatens a Rook, then the Rook does not threaten the kNight.

    which also seem reasonable.

    The assignments I found that work are:

    Brandy = Rook; Gin = Queen; Kirsch = King; Rum = Pawn; Vodka = Bishop; Whisky = kNight
    Brandy = kNight; Gin = Queen; Kirsch = King; Rum = Pawn; Vodka = Rook; Whisky = Bishop
    Brandy = kNight; Gin = Queen; Kirsch = King; Rum = Pawn; Vodka = Bishop; Whisky = Rook

    Each of these assignments gives a move by white of b2 to b3 or b4 to put black in check.

    The published solution only gives the middle of these assignments. But maybe to a more experienced chess player there is a reason that the other two can be ruled out.

Leave a Comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: