Python script for estimating tinyhouse config...
- """
- Count (an upper bound on) the number of tinyhouse chess positions.
- Board: 4x4 = 16 squares. Ranks 2 and 3 are the 8 "middle" squares.
- Piece inventory (conserved totals, both colours + banks combined):
- 1 white king, 1 black king
- 2 "pawn-ish" pieces : each is a pawn, or a promoted pawn (pawn-wazir /
- pawn-ferz / pawn-horse)
- 2 true wazirs, 2 true ferzes, 2 true horses
- Placement domains:
- king, true wazir/ferz/horse ....... any of the 16 squares (or bank, except kings)
- plain pawn ........................ one of the 8 middle squares, or the bank
- promoted pawn (pawn-X) ............ any of the 16 squares, never the bank
- at most one piece per square
- Method
- ------
- Every piece is described by a "slot option": (label, place) where
- label = (colour, kind) -- what the piece is and whose it is
- place = 'M' (middle 8 squares) | 'F' (free: any of 16) | 'bank'
- Pieces sharing the same label+place are indistinguishable, so for each group
- we enumerate *multisets* of options (combinations_with_replacement).
- Given a whole configuration with `a` on-board pieces restricted to the middle
- squares and `b` on-board pieces free to use all 16, the number of ways to place
- them as *distinguishable* pieces is
- P(8, a) * P(16 - a, b)
- (place the middle-restricted ones first; each one eats a square from the 16).
- Dividing by k! for every group of k identical on-board pieces turns that into a
- count of distinct positions. Identical pieces sitting in the same bank are
- already handled by the multiset enumeration, so they need no divisor.
- Finally multiply by 2 for whose turn it is.
- """
- from itertools import combinations_with_replacement, product
- from collections import Counter
- from math import perm, factorial
- COLOURS = ("W", "B")
- # ---- option lists for each conserved group -------------------------------
- # the two pawn-ish pieces
- PAWN_OPTIONS = (
- [((c, "pawn"), "M") for c in COLOURS] # pawn on board
- + [((c, "pawn"), "bank") for c in COLOURS] # pawn in hand
- + [((c, "pawn-" + p), "F") # promoted pawn
- for c in COLOURS for p in ("wazir", "ferz", "horse")]
- )
- def true_piece_options(kind):
- return ([((c, kind), "F") for c in COLOURS]
- + [((c, kind), "bank") for c in COLOURS])
- WAZIR_OPTIONS = true_piece_options("wazir")
- FERZ_OPTIONS = true_piece_options("ferz")
- HORSE_OPTIONS = true_piece_options("horse")
- KINGS = [(("W", "king"), "F"), (("B", "king"), "F")]
- # ---- enumeration ---------------------------------------------------------
- def count_positions(verbose=False):
- groups = [
- list(combinations_with_replacement(PAWN_OPTIONS, 2)),
- list(combinations_with_replacement(WAZIR_OPTIONS, 2)),
- list(combinations_with_replacement(FERZ_OPTIONS, 2)),
- list(combinations_with_replacement(HORSE_OPTIONS, 2)),
- ]
- total = 0
- by_pieces_on_board = Counter() # how many pieces stand on the board
- configs = 0
- for combo in product(*groups):
- pieces = KINGS + [item for grp in combo for item in grp]
- a = sum(1 for _, place in pieces if place == "M")
- b = sum(1 for _, place in pieces if place == "F")
- placements = perm(8, a) * perm(16 - a, b)
- if placements == 0:
- continue
- # divide out permutations of identical on-board pieces
- board_counts = Counter(p for p in pieces if p[1] != "bank")
- divisor = 1
- for k in board_counts.values():
- divisor *= factorial(k)
- n = placements // divisor
- total += n
- configs += 1
- by_pieces_on_board[a + b] += n
- total *= 2 # side to move
- if verbose:
- print(f"piece configurations considered : {configs}")
- print("positions by number of pieces standing on the board "
- "(before the x2 for side to move):")
- for k in sorted(by_pieces_on_board):
- print(f" {k:2d} pieces on board : {by_pieces_on_board[k]:>14,}")
- print()
- return total
- if __name__ == "__main__":
- t = count_positions(verbose=True)
- print(f"TOTAL tinyhouse positions (upper bound): {t:,}")
- print(f" ~ {t:.4g}")
Pasted 2026-08-24 07:10:41
Short link:
Short link:
Recent Public Pastes