## TinyChess — the board and the way the pieces move. ## ## Four files by four ranks, kings and pawns and a queen. Everything in ## this file is COMPUTABLE and has no holes: squares are numbers, the ## step rules are Bools, and the checker can simply run them. That is ## deliberate, because it is what lets the counterexample search in the ## editor walk a claim and hand you the two squares that break it. ## ## What is NOT here is the game: occupancy, captures against a real ## position, check, and mate. Those live in game.bend; the laws about them ## are stated in LAWS.bend and proved, where they are proved, in PROOF.bend. ## ## Squares are 0..15, counting along the files first: ## ## a b c d ## 4 12 13 14 15 rank 4, black's back rank ## 3 8 9 10 11 ## 2 4 5 6 7 ## 1 0 1 2 3 rank 1, white's back rank ## ## so file = s mod 4 and rank = s div 4, and both are written out by ## structural recursion rather than by arithmetic, because the checker ## evaluates a Nat one successor at a time and these stay under sixteen. import Base # ---- the small arithmetic these rules need ------------------------------- def nat_eq(a: Nat, b: Nat) -> Bool: match a: case 0n: match b: case 0n: True{} case 1n+q: False{} case 1n+p: match b: case 0n: False{} case 1n+q: nat_eq(p, q) def nat_lt(a: Nat, b: Nat) -> Bool: match a: case 0n: match b: case 0n: False{} case 1n+q: True{} case 1n+p: match b: case 0n: False{} case 1n+q: nat_lt(p, q) # the distance between two numbers, whichever way round they are def gap(a: Nat, b: Nat) -> Nat: match a: case 0n: b case 1n+p: match b: case 0n: 1n+p case 1n+q: gap(p, q) # ---- the board's geometry ------------------------------------------------ # which file a square is on: 0 is the a-file, 3 is the d-file def file(s: Nat) -> Nat: match s: case 0n: 0n case 1n+a: match a: case 0n: 1n case 1n+b: match b: case 0n: 2n case 1n+c: match c: case 0n: 3n case 1n+d: file(d) # which rank a square is on: 0 is white's back rank, 3 is black's def rank(s: Nat) -> Nat: match s: case 0n: 0n case 1n+a: match a: case 0n: 0n case 1n+b: match b: case 0n: 0n case 1n+c: match c: case 0n: 0n case 1n+d: r = rank(d) 1n+r # a square is on the board when it is one of the sixteen def on_board(s: Nat) -> Bool: nat_lt(s, 16n) # ---- how each piece steps, on an empty board ----------------------------- # A king steps to a touching square: at most one file across, at most one # rank up or down, and never onto the square it is already on. def king_step(+a: Nat, +b: Nat) -> Bool: Bool.and( Bool.and(on_board(a), on_board(b)), Bool.and( Bool.not(nat_eq(a, b)), Bool.and(nat_lt(gap(file(a), file(b)), 2n), nat_lt(gap(rank(a), rank(b)), 2n)))) # A queen steps along a rank, a file or a diagonal. The path being clear # is a question about a position, not about geometry, so it is not asked # here — it is one of the open claims. def queen_line(+a: Nat, +b: Nat) -> Bool: Bool.and( Bool.and(on_board(a), on_board(b)), Bool.and( Bool.not(nat_eq(a, b)), Bool.or( Bool.or(nat_eq(file(a), file(b)), nat_eq(rank(a), rank(b))), nat_eq(gap(file(a), file(b)), gap(rank(a), rank(b)))))) # White pawns walk up the board, black pawns walk down. `up` says whether # b is exactly one rank ahead of a for the side given. def ahead(+white: Bool, +a: Nat, +b: Nat) -> Bool: match white: case True{}: ra = rank(a) nat_eq(rank(b), 1n+ra) case False{}: rb = rank(b) nat_eq(rank(a), 1n+rb) # A pawn's quiet move: one rank forward, same file. There is no double # step on a board this short, and nowhere to promote to. def pawn_push(+white: Bool, +a: Nat, +b: Nat) -> Bool: Bool.and( Bool.and(on_board(a), on_board(b)), Bool.and(ahead(white, a, b), nat_eq(file(a), file(b)))) # A pawn's capture: one rank forward, one file sideways. A pawn can only # take this way, and can never take the piece directly in front of it. def pawn_capture(+white: Bool, +a: Nat, +b: Nat) -> Bool: Bool.and( Bool.and(on_board(a), on_board(b)), Bool.and(ahead(white, a, b), nat_eq(gap(file(a), file(b)), 1n))) # every way a pawn may move, capture or not def pawn_step(+white: Bool, +a: Nat, +b: Nat) -> Bool: Bool.or(pawn_push(white, a, b), pawn_capture(white, a, b)) # A knight's leap: two squares one way and one the other, which is the # only move on the board that does not care what stands between. def knight_step(+a: Nat, +b: Nat) -> Bool: +df = gap(file(a), file(b)) +dr = gap(rank(a), rank(b)) Bool.and( Bool.and(on_board(a), on_board(b)), Bool.or( Bool.and(nat_eq(df, 1n), nat_eq(dr, 2n)), Bool.and(nat_eq(df, 2n), nat_eq(dr, 1n))))