From bc71e758cfb0fb9cb8f59fca5869f4e88227279d Mon Sep 17 00:00:00 2001 From: Noa Aarts Date: Thu, 27 Nov 2025 23:56:32 +0100 Subject: [PATCH] initial commit --- .envrc | 1 + .gitignore | 11 ++++ .python-version | 1 + README.md | 0 blokus.py | 165 ++++++++++++++++++++++++++++++++++++++++++++++++ flake.lock | 43 +++++++++++++ flake.nix | 26 ++++++++ 7 files changed, 247 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100755 blokus.py create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2302b9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv +.direnv diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/blokus.py b/blokus.py new file mode 100755 index 0000000..e92ec4e --- /dev/null +++ b/blokus.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +import numpy as np +import random + +BOARD_SIZE = 14 + +def make_board(): + a = np.array([[0 for i in range(BOARD_SIZE)] for j in range(BOARD_SIZE)]) + a[4,4] = -1 + a[9,9] = -1 + return a + +tiles = [ + np.array([[1]]), + np.array([[1],[1]]), + np.array([[1],[1],[1]]), + np.array([[1,0],[1,1]]), + np.array([[1],[1],[1],[1]]), + np.array([[1,0],[1,0],[1,1]]), + np.array([[1,0],[1,1],[1,0]]), + np.array([[1,1],[1,1]]), + np.array([[1,1,0],[0,1,1]]), + np.array([[1],[1],[1],[1],[1]]), + np.array([[1,0],[1,0],[1,0],[1,1]]), + np.array([[1,0],[1,0],[1,1],[0,1]]), + np.array([[1,0],[1,1],[1,1]]), + np.array([[1,1],[1,0],[1,1]]), + np.array([[1,0],[1,1],[1,0],[1,0]]), + np.array([[0,1,0],[0,1,0],[1,1,1]]), + np.array([[1,0,0],[1,0,0],[1,1,1]]), + np.array([[1,1,0],[0,1,1],[0,0,1]]), + np.array([[1,0,0],[1,1,1],[0,0,1]]), + np.array([[1,0,0],[1,1,1],[0,1,0]]), + np.array([[0,1,0],[1,1,1],[0,1,0]]), +] + + +def get_permutations(which_tiles: list[tuple[int,int]]): + permutations = [] + for i,tile in enumerate(tiles): + if i not in which_tiles: + continue + permutations.append((i,tile)) + permutations.append((i,np.rot90(tile))) + permutations.append((i,np.rot90(np.rot90(tile)))) + permutations.append((i,np.rot90(np.rot90(np.rot90(tile))))) + permutations.append((i,np.flip(tile))) + permutations.append((i,np.flip(np.rot90(tile)))) + permutations.append((i,np.flip(np.rot90(np.rot90(tile))))) + permutations.append((i,np.flip(np.rot90(np.rot90(np.rot90(tile)))))) + + unique_arrays = [] + for arr in permutations: + if not any(np.array_equal(arr, u) for u in unique_arrays): + unique_arrays.append(arr) + + return unique_arrays + +def can_place(board, tile, player): + placements = [] + has_minus_one = False + for x in range(BOARD_SIZE): + for y in range(BOARD_SIZE): + if board[x,y] == -1: + has_minus_one = True + with np.nditer(tile, flags=['multi_index']) as it: + for v in it: + if v == 1: + (i,j) = it.multi_index + if x + i >= BOARD_SIZE: + break + if y + j >= BOARD_SIZE: + break + if board[x + i][y + j] > 0: + break + if x + i - 1 >= 0 and board[x + i - 1][y+j] == player: + break + if y + j - 1 >= 0 and board[x + i][y + j - 1] == player: + break + if x + i + 1 < BOARD_SIZE and board[x + i + 1][y+j] == player: + break + if y + j + 1 < BOARD_SIZE and board[x + i][y+j+1] == player: + break + else: + placements.append((x,y)) + final = [] + if has_minus_one: + for (x,y) in placements: + with np.nditer(tile, flags=['multi_index']) as it: + for v in it: + (i,j) = it.multi_index + if v == 1 and board[x+i,y+j] == -1: + final.append((x,y)) + break + else: + for (x,y) in placements: + with np.nditer(tile, flags=['multi_index']) as it: + for v in it: + (i,j) = it.multi_index + if x+i+1 < BOARD_SIZE and y+j+1 < BOARD_SIZE and board[x+i+1][y+j+1] == player: + final.append((x,y)) + break + if x+i+1 < BOARD_SIZE and y+j-1 >= 0 and board[x+i+1][y+j-1] == player: + final.append((x,y)) + break + if x+i-1 >= 0 and y+j+1 < BOARD_SIZE and board[x+i-1][y+j+1] == player: + final.append((x,y)) + break + if x+i-1 >= 0 and y+j-1 >= 0 and board[x+i-1][y+j-1] == player: + final.append((x,y)) + break + return final + +def do_placement(tidx, tile, placement, game_state, player): + (x,y) = placement + with np.nditer(tile, flags=['multi_index']) as it: + for v in it: + (i,j) = it.multi_index + if v == 1: + game_state[0][x+i,y+j] = player + game_state[player].remove(tidx) + +def print_game_state(game_state): + (board, p1tiles, p2tiles) = game_state + + for row in board: + print("".join([" " if x == 0 else "X" if x == 1 else "O" if x == 2 else "S" for x in row])) + + print("") + print(f"Player 1 tiles left: {p1tiles}") + print(f"Player 2 tiles left: {p2tiles}") + +game_state = ( + make_board(), + [i for i in range(21)], + [i for i in range(21)], +) + + +playing = True +player = 1 +while playing: + moves = [] + for (tidx, tile) in get_permutations(game_state[player]): + for placement in can_place(game_state[0], tile, player): + moves.append((tidx, tile, placement)) + + print_game_state(game_state) + print(f"player {player} has {len(moves)} options") + + if len(moves) == 0: + print(f"No moves left, player {player} lost") + playing = False + continue + + (tidx, tile, placement) = random.choice(moves) + do_placement(tidx, tile, placement, game_state, player) + + + if player == 1: + player = 2 + elif player == 2: + player = 1 + + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e5c2c44 --- /dev/null +++ b/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1764167966, + "narHash": "sha256-nXv6xb7cq+XpjBYIjWEGTLCqQetxJu6zvVlrqHMsCOA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "5c46f3bd98147c8d82366df95bbef2cab3a967ea", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "systems": "systems" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..66a3e35 --- /dev/null +++ b/flake.nix @@ -0,0 +1,26 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + systems.url = "github:nix-systems/default"; + }; + + outputs = + { nixpkgs, ... }: + let + eachSystem = + f: + nixpkgs.lib.genAttrs nixpkgs.lib.systems.flakeExposed (system: f nixpkgs.legacyPackages.${system}); + in + { + devShells = eachSystem (pkgs: { + default = pkgs.mkShell { + buildInputs = [ + (pkgs.python3.withPackages (ppkgs: [ + ppkgs.numpy + ppkgs.torch + ])) + ]; + }; + }); + }; +}