Skip to content

Commit

Permalink
Additional debugging and translating utilities
Browse files Browse the repository at this point in the history
  • Loading branch information
Casper-Guo committed Jun 28, 2024
1 parent 6298d21 commit 5fc3b3b
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 69 deletions.
65 changes: 65 additions & 0 deletions dev_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Display board from input seeds."""

import logging
from pathlib import Path
from typing import Optional

import click

from royal_game._exceptions import BoardError
from royal_game.modules.board import Board

logging.basicConfig(format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)


def process_input(seed: str, decimal: bool) -> Board | None:
"""Produce board from input seed string."""
try:
seed = int(seed, 10 if decimal else 2)
except ValueError as e:
logger.warning(e)
return None

try:
board = Board(seed)
except BoardError as e:
logger.warning(e)
board = Board(seed, no_verify=True)
finally:
return board


@click.command()
@click.option("--decimal/--binary", "-d/-b", default=True)
@click.argument("in_file", required=False, type=click.Path(exists=True, path_type=Path))
def main(decimal: bool, in_file: Optional[Path]):
"""
Accept seed input from user or file and display the corresponding boards.
If an input file is provided, decimal encoding is assumed. The file should
contain one seed per line.
Warnings are shown for invalid boards but the program will not terminate.
"""
if in_file is not None:
decimal = True
with open(in_file, "r") as fin:
seeds = [line.strip() for line in fin.readlines()]

for seed in seeds:
board = process_input(seed, decimal)
if board is not None:
print(f"{seed}:\n{board}")

else:
user_input = ""
while user_input != "quit":
user_input = input("Enter a board seed in the chosen encoding: ")
board = process_input(user_input, decimal)
if board is not None:
print(board)


if __name__ == "__main__":
main()
63 changes: 0 additions & 63 deletions rust_translate.py

This file was deleted.

30 changes: 25 additions & 5 deletions test_translate.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
import pytest

from royal_game._exceptions import BoardError
from rust_translate import translate
from translate import rust_to_python, python_to_rust


def test_translate():
def test_rust_to_python():
assert (
translate(0b0010111000000000000010000100000000100000000000001000010000000000)
rust_to_python(0b0010111000000000000010000100000000100000000000001000010000000000)
== 87510499392
)

assert (
translate(0b0110000000000000000000000010100000100000000000000000001000000000)
rust_to_python(0b0110000000000000000000000010100000100000000000000000001000000000)
== 83751871040
)

with pytest.raises(BoardError):
# too many black pieces
translate(0b0000000000001010101010101010000000000000101010101010101000000000)
rust_to_python(0b0000000000001010101010101010000000000000101010101010101000000000)


def test_python_to_rust():
assert (
python_to_rust(87510499392)
== 0b1110111000000000000010000100000000100000000000001000010000000000
)

assert (
python_to_rust(83751871040)
== 0b0110000000000000000000000010100000100000000000000000001000000000
)

assert (
python_to_rust(966988398624)
== 0b1000001000000101000000000000000000000100010100000000000000000000
)

with pytest.raises(BoardError):
python_to_rust(155055118456)
2 changes: 1 addition & 1 deletion tournament.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def output_results(num_wins: defaultdict, num_games: int, self_play: bool) -> No
help="Enable saving full debug output to games.log in addition to summary statistics",
)
def main(
players: Iterable[click.Path],
players: Iterable[Path],
num_games: int,
board_seed: int,
binary_seed: bool,
Expand Down
129 changes: 129 additions & 0 deletions translate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
"""Translate Rust board seed to Python board seed."""

import logging
import click
from pathlib import Path

from royal_game._exceptions import BoardError
from royal_game._constants import white_order_iter, black_order_iter
from royal_game.modules.board import Board

logging.basicConfig(format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)


def rust_to_python(rust_seed: int, verify: bool = True) -> int:
"""
Rust board encoding schema copied below.
Two bits each for board grids to achieve consistency in the order of
W1...W4, 5...12, W13, W14, B1...B4, 5...12, B13, B14 (from least to most significant).
Note the more significant bit of private grids are unused (56 total)
Three bits each for WS and BS from the least to the most significant (6 total)
Bits 62-63 indicate the endgame state.
This is useful for stopping search and assigning rewards.
00: not set/calculated
01: white win
10: black win
11: in progress
"""
py_seed = 0
white_total, black_total = 0, 0

for py_offset, rust_offset in enumerate((0, 2, 4, 6, 24, 26)):
if ((rust_seed & (0b11 << rust_offset)) >> rust_offset) == 0b01:
white_total += 1
py_seed += 1 << py_offset

for py_offset, rust_offset in enumerate((28, 30, 32, 34, 52, 54)):
if ((rust_seed & (0b11 << rust_offset)) >> rust_offset) == 0b10:
black_total += 1
py_seed += 1 << (py_offset + 6)

for py_offset, rust_offset in zip(range(12, 28, 2), range(8, 24, 2)):
grid_status = (rust_seed & (0b11 << rust_offset)) >> rust_offset
py_seed += grid_status << py_offset

if grid_status == 0b01:
white_total += 1
elif grid_status == 0b10:
black_total += 1

num_ws = (rust_seed & (0b111 << 56)) >> 56
py_seed += num_ws << 28
num_bs = (rust_seed & (0b111 << 59)) >> 59
py_seed += num_bs << 34

# WE
py_seed += (7 - white_total - num_ws) << 31
# BE
py_seed += (7 - black_total - num_bs) << 37

if verify:
try:
_ = Board(py_seed)
except BoardError as e:
logger.warning("Provided rust seed %d is invalid.", rust_seed)
raise e

return py_seed


def python_to_rust(py_seed: int, verify: bool = True) -> int:
"""
Python board encoding schema copied below.
bit 0-5: one bit each for W1...W14 indicating
whether the grid is occupied
bit 6-11: one bit each for B1...B14 indicating
whether the grid is occupied
bit 12-27: two bit each for 5...12 indicating
whether the grid is empty, white, or black
bit 28-39: three bit each for WS, WE, BS, BE
indicating number of pieces at start/end
The endgame state bits will always be set. Thus the following assumption
is not valid:
python_to_rust(rust_to_python(rust_seed)) == rust_seed
Only compare the lower 62 bits if needed.
"""
if verify:
try:
_ = Board(py_seed)
except BoardError as e:
logger.warning("Provided python seed %d is invalid.", py_seed)
raise e

board = Board(py_seed, no_verify=True)
rust_seed = 0

if board.board["WE"].num_pieces == 7:
rust_seed += 0b01 << 62
elif board.board["BE"].num_pieces == 7:
rust_seed += 0b10 << 62
else:
rust_seed += 0b11 << 62

rust_seed += int(board.board["WS"]) << 56
rust_seed += int(board.board["BS"]) << 59

for grid, offset in zip(list(white_order_iter()), range(0, 28, 2)):
# we can take this shortcut because GridStatus(1) indicates white
# which match the status bit for an occupied white private grid
rust_seed += int(board.board[grid]) << offset

for grid, offset in zip(list(black_order_iter()), range(28, 56, 2)):
rust_seed += int(board.board[grid]) << offset

return rust_seed

def main():
pass


if __name__ == "__main__":
main()

0 comments on commit 5fc3b3b

Please sign in to comment.