# Checkers (English draughts): twelve men each on the dark squares, diagonal moves, mandatory
# captures by jumping, kings on the far row. Public-domain game, described in our own words.
game: checkers
label: Checkers
players: "2 players"
num_players: 2
mode: competitive
summary: >
  Each side has twelve men on the dark squares of an 8×8 board. A man moves one square diagonally
  forward; it captures by jumping over an adjacent enemy onto the empty square beyond, and may
  keep jumping with the same piece. Capturing is compulsory. A man that reaches the far row becomes
  a king, which moves and jumps backwards too. You lose when you have no legal move.
scope: >
  English draughts: men do not jump backwards, kings move one square (no flying kings). When
  several captures are possible you choose which; you need not take the longest sequence. White
  (player 1) moves first here, where the official rules let the dark side start. A piece jumped
  earlier in a multiple capture is removed at once, so it may be jumped over again (the official
  rules remove the pieces after the move). No draw by repetition; a game that goes on for 80 moves
  without a capture or a promotion is a draw.

boards:
  board: {kind: square, width: 8, height: 8, names: letters, neighbours: 8, label: board}

constants:
  SIDE: [w, b]
  FORWARD: {w: [nw, ne], b: [sw, se]}
  ALL_DIAG: [nw, ne, sw, se]
  START:
    a1: wm
    c1: wm
    e1: wm
    g1: wm
    b2: wm
    d2: wm
    f2: wm
    h2: wm
    a3: wm
    c3: wm
    e3: wm
    g3: wm
    b6: bm
    d6: bm
    f6: bm
    h6: bm
    a7: bm
    c7: bm
    e7: bm
    g7: bm
    b8: bm
    d8: bm
    f8: bm
    h8: bm
  DRAW_AFTER: 80

functions:
  enemy:
    params: [k]
    x: "'b' if k == 'w' else 'w'"
  dirs:
    text: "the directions a piece may go: forward for a man, all four for a king"
    params: [p]
    x: "ALL_DIAG if p[1] == 'k' else FORWARD[p[0]]"
  last_row:
    text: "is this cell on the far row for side k?"
    params: [c, k]
    x: "xy('board', c)[1] == (7 if k == 'w' else 0)"
  jumps_from:
    text: "the captures piece at c can make: over an adjacent enemy onto an empty square"
    params: [s, c]
    x: >-
      [{'label': c + 'x' + l, 'from': c, 'to': l, 'over': o}
       for d in dirs(s[c])
       for o in [step('board', c, d)] if o is not None and o in s and s[o][0] == enemy(s[c][0])
       for l in [step('board', o, d)] if l is not None and l not in s]
  steps_from:
    text: "the plain moves of the piece at c"
    params: [s, c]
    x: >-
      [{'label': c + '-' + l, 'from': c, 'to': l, 'over': None}
       for d in dirs(s[c]) for l in [step('board', c, d)] if l is not None and l not in s]
  all_jumps:
    params: [s, k]
    x: "[m for c, p in s.items() if p[0] == k for m in jumps_from(s, c)]"
  all_steps:
    params: [s, k]
    x: "[m for c, p in s.items() if p[0] == k for m in steps_from(s, c)]"
  legal:
    text: "capturing is compulsory: all jumps if there are any, else the plain moves"
    params: [s, k, j]
    x: "jumps_from(s, j) if j is not None else (all_jumps(s, k) or all_steps(s, k))"
  after:
    text: "the position after move mv (the captured piece removed, promotion on the far row)"
    params: [s, mv]
    x: >-
      {**{c: p for c, p in s.items() if c != mv['from'] and c != mv['over']},
       mv['to']: (s[mv['from']][0] + 'k') if last_row(mv['to'], s[mv['from']][0]) else s[mv['from']]}

state:
  vars:
    to_move: {scope: global, type: ref, init: 0, note: "0 = white (moves first), 1 = black"}
    pieces: {scope: global, type: dict, init_x: "dict(START)", note: "cell → wm, wk, bm or bk"}
    jumping: {scope: global, type: str, init: null, note: "the piece in the middle of a multiple capture, or none"}
    quiet: {scope: global, type: int, init: 0, note: "moves without a capture or promotion"}
    legal_moves: {scope: global, type: list, init_x: "legal(dict(START), 'w', None)"}
    last_move: {scope: global, type: str, init: null}
    men: {scope: player, type: int, init: 12, note: "pieces left"}

flow:
  id: game
  label: Game
  kind: loop
  note: "until an end condition fires"
  children:
    - id: turn
      label: The side to move makes a move (and keeps jumping while it can)
      kind: step
      actor_x: to_move
      do: move

actions:
  move:
    label: Move
    pre: "there is a legal move"
    pre_x: "len(legal_moves) > 0"
    effect:
      - {op: choose, options: "legal moves (captures first, when there are any)", options_x: legal_moves, bind: mv}
      - {op: let, bind: promoted, value_x: "last_row(mv['to'], SIDE[to_move]) and pieces[mv['from']][1] == 'm'"}
      - {op: set, var: pieces, value_x: "after(pieces, mv)"}
      - {op: set, var: last_move, value_x: "mv['label']"}
      - op: if
        cond: a capture
        cond_x: "mv['over'] is not None"
        then:
          - {op: add, var: "men[1 - to_move]", amount: -1}
          - {op: set, var: quiet, value: 0}
          - {op: emit, event: captured}
        else:
          - {op: set, var: quiet, value_x: "0 if promoted else quiet + 1"}
      - op: if
        cond: the capture can go on with the same piece (not after a promotion)
        cond_x: "mv['over'] is not None and not promoted and len(jumps_from(pieces, mv['to'])) > 0"
        then:
          - {op: set, var: jumping, value_x: "mv['to']"}
        else:
          - {op: set, var: jumping, value: null}
          - {op: set, var: to_move, value_x: "1 - to_move"}
      - {op: set, var: legal_moves, value_x: "legal(pieces, SIDE[to_move], jumping)"}
      - {op: emit, event: moved}

end:
  - id: no_moves
    check: immediate
    if: "the side to move has no legal move: the other side wins"
    if_x: "len(legal_moves) == 0"
    who_x: "1 - to_move"
    result: win
  - id: draw
    check: immediate
    if: "eighty moves without a capture or a promotion"
    if_x: "quiet >= DRAW_AFTER"
    result: draw

tutorial:
  - {at: move, text: "Men move one square diagonally forward. If you can jump an enemy piece, you must; after a jump you keep jumping with the same piece while you can. Reach the far row to crown a king, which also moves backwards."}
