Skip to content

Commit

Permalink
Merge pull request #42 from miguel-ambrona/ambrona@sherlock
Browse files Browse the repository at this point in the history
Sherlock
  • Loading branch information
miguel-ambrona authored Feb 24, 2024
2 parents 62796e2 + d5d8b5e commit d3c2d01
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 113 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/c-cpp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ jobs:
run: sudo make install-cha
- name: Enable shared libraries
run: sudo /sbin/ldconfig -v
- name: Download and compile Retractor
- name: Download and compile Sherlock
working-directory: ./src/
run: make get-retractor
run: make get-sherlock
- name: Compile Deadpos
working-directory: ./src
run: make
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ dead reckoning.

## Installation

Make sure you have `curl`, `opam` and `python-chess`, which you can
Make sure you have `opam` and `python-chess`, which you can
install with:
- `sudo apt install curl`
- `sudo apt install opam && opam init -y`
- `pip3 install python-chess`.

Expand All @@ -23,7 +22,8 @@ Then, after cloning the repository and from the `src/` directory:
[CHA](https://github.com/miguel-ambrona/D3-Chess).
Then install CHA with `sudo make install-cha`.

3. Run `make get-retractor` to download and build our retraction engine.
3. Run `make get-sherlock` to download and build our retraction and legality
engine.

4. Compile the tool with `make`. (You may need to enable shared libraries
with `sudo /sbin/ldconfig -v` first.)
Expand Down Expand Up @@ -216,6 +216,11 @@ nsols 1
- Use `--uci` to display moves in UCI notation (the default is
[LAN](https://en.wikipedia.org/wiki/Algebraic_notation_(chess)#Long_algebraic_notation)).

- Use `--sherlock` to enable futher (and computationally more expensive)
legality checks, via [Sherlock](https://github.com/miguel-ambrona/sherlock),
when using the `legal` command.


## Feedback

Please, open an issue if you have any suggestions.
Expand Down
19 changes: 12 additions & 7 deletions src/Makefile
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
SHERLOCK_VERSION = 9f7faa81d5c6f01d9cce1c4eff8c0821614f92c8

solver:
g++ -o solver.exe solver.cpp main.cpp -I/usr/local/include/stockfish -lstockfish -I/usr/local/include/cha -lcha

Expand All @@ -16,13 +18,16 @@ get-cha:
install-cha:
cd ../lib/cha/ && make install

get-retractor:
if [ -d ../lib/retractor ]; then rm -Rf ../lib/retractor; fi
cd ../lib/ && curl https://chasolver.org/retractor.zip --output retractor.zip
cd ../lib/ && unzip retractor.zip && rm retractor.zip
cd ../lib/retractor && ./scripts/install_build_deps.sh
cd ../lib/retractor && eval `(opam env)` && opam install -y ocamlgraph
cd ../lib/retractor && eval `(opam env)` && dune build
get-sherlock:
if [ -d ../lib/sherlock ]; then rm -Rf ../lib/sherlock; fi
cd ../lib/ && git clone https://github.com/miguel-ambrona/sherlock.git
cd ../lib/sherlock && git checkout $(SHERLOCK_VERSION)
cd ../lib/sherlock && ./scripts/install_build_deps.sh
cd ../lib/sherlock && eval `(opam env)` && dune build

update-sherlock:
cd ../lib/sherlock && git fetch && git checkout $(SHERLOCK_VERSION)
cd ../lib/sherlock && eval `(opam env)` && dune build

test:
cat ../test/test-vectors.txt | python3 deadpos.py --no-progress-bar > /tmp/test-vectors.out
Expand Down
25 changes: 18 additions & 7 deletions src/deadpos.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
import os
import sys
import time
from solver import is_dead, is_legal, is_zombie, explain_dead, explain_alive, retract
from solver import is_dead, is_legal, is_zombie, explain_dead, \
explain_alive, retract, is_illegal_sherlock

PROGRESS_BAR = not "--no-progress-bar" in sys.argv
SOLVER_ARGS = ["--progress-bar"] if PROGRESS_BAR else []
UCI_NOTATION = "--uci" in sys.argv
SHERLOCK = "--sherlock" in sys.argv

CPP_SOLVER = Popen(["./solver.exe"] + SOLVER_ARGS, stdout=PIPE, stdin=PIPE, stderr=STDOUT)
CPP_SOLVER.stdout.readline().strip().decode("utf-8")
Expand Down Expand Up @@ -160,7 +162,7 @@ def set_ep(fen, ep):

return new_fens

def legal(pos):
def legal(pos, flush):
if not pos.is_valid:
return [pos]

Expand All @@ -170,7 +172,11 @@ def legal(pos):
(m, fen) = history[i]
if fen:
if not is_legal(fen, depth = 2):
flag += [("illegal", None)]
flag += [("illegal (unretractable)", None)]
elif SHERLOCK:
(illegal, reason) = is_illegal_sherlock(fen)
if illegal:
flag += [("illegal " + reason, None)]

if is_zombie(fen, depth = 2):
flag += [("zombie", None)]
Expand All @@ -181,6 +187,9 @@ def legal(pos):
pos.is_valid = False
break
pos.history = pos.history[:i+1] + flag + pos.history[i+1:]
if flush != None:
print(pos)

return [pos]

def solver_call(cmd, pos, progress_bar, flush):
Expand Down Expand Up @@ -238,7 +247,7 @@ def solver_call(cmd, pos, progress_bar, flush):
new_pos = Position(b.fen(), history = pos.history + history_token)
if flush != None:
if "legal" in flush:
print(legal(new_pos)[0])
print(legal(new_pos, None)[0])
else:
print(new_pos)
solutions.append(new_pos)
Expand Down Expand Up @@ -401,15 +410,15 @@ def process_cmd(positions, cmd, flush = None):
return (positions, len(positions))

elif cmd == "legal":
positions = bind(positions, legal)
positions = [r for pos in positions for r in legal(pos, flush)]
return (positions, len([pos for pos in positions if pos.is_valid]))

else:
return solve(cmd, positions, flush)


def main():
print("Deadpos Analyzer version 2.3")
print("Deadpos Analyzer version 2.4.1")

while True:
try:
Expand Down Expand Up @@ -455,8 +464,10 @@ def is_solve_cmd(cmd):
return "#" in cmd or cmd[0] == "h"

flush = "plain" if len(cmds) > 0 and is_solve_cmd(cmds[-1]) else None
if len(cmds) > 1 and cmds[-1] == "legal" and is_solve_cmd(cmds[-2]):
if len(cmds) > 1 and cmds[-1] == "legal":
flush = "with-legal"
if is_solve_cmd(cmds[-2]):
cmds = cmds[:-1]

for cmd in cmds:
positions, n = process_cmd(positions, cmd, flush)
Expand Down
52 changes: 43 additions & 9 deletions src/solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,32 @@
CHA.stdout.readline()

RETRACT_TABLE = {}
SHERLOCK_TABLE = {}
DEAD_TABLE = {}
LEGAL_TABLE = {}
ZOMBIE_TABLE = {}

RETRACTOR = Popen(["../lib/retractor/_build/default/retractor/retractor.exe"], \
SHERLOCK = Popen(["../lib/sherlock/_build/default/retractor/retractor.exe"], \
stdout=PIPE, stdin=PIPE, stderr=STDOUT)

def retract(fen):
'''
Returns a fen of all possible pseudo-legal retractions of the
given position.
'''
global RETRACTOR
global SHERLOCk
global RETRACT_TABLE

retracted = RETRACT_TABLE.get(fen)
if retracted != None:
return retracted

inp = ("retract " + fen + "\n").encode("utf-8")
RETRACTOR.stdin.write(inp)
RETRACTOR.stdin.flush()
SHERLOCK.stdin.write(inp)
SHERLOCK.stdin.flush()
fens = []
while True:
output = RETRACTOR.stdout.readline().strip().decode("utf-8")
output = SHERLOCK.stdout.readline().strip().decode("utf-8")
if "nsols" in output or len(output) <= 1:
break
retracted_fen, retraction = output.split('retraction')
Expand All @@ -43,6 +44,35 @@ def retract(fen):

return fens

def is_illegal_sherlock(fen):
'''
Returns True if the position is illegal and False if Sherlock cannot
determine the illegality of the position.
'''
global SHERLOCK
global SHERLOCK_TABLE

found = SHERLOCK_TABLE.get(fen)
if found != None:
return found

inp = ("legal " + fen + "\n").encode("utf-8")
SHERLOCK.stdin.write(inp)
SHERLOCK.stdin.flush()
illegal = False
reason = ""
while True:
output = SHERLOCK.stdout.readline().strip().decode("utf-8")
if "nsols" in output or len(output) <= 1:
break
illegal = illegal or "illegal" in output
if "illegal"in output:
reason += " ".join(output.split(" ")[1:]).strip()

SHERLOCK_TABLE[fen] = (illegal, reason)

return (illegal, reason)

def is_dead(fen):
'''
Returns true iff the given position is dead.
Expand All @@ -57,8 +87,14 @@ def is_dead(fen):
return dead

inp = (fen + " white\n").encode("utf-8")
CHA.stdin.write(inp)
CHA.stdin.flush()
try:
CHA.stdin.write(inp)
CHA.stdin.flush()
except:
CHA = Popen(["../lib/cha/D3-Chess/src/cha"], stdout=PIPE, stdin=PIPE, stderr=STDOUT)
CHA.stdout.readline()
return is_dead(fen)

output = CHA.stdout.readline().strip().decode("utf-8")
white_undetermined = "undetermined" in output

Expand Down Expand Up @@ -176,8 +212,6 @@ def explain_dead(board, depth = 10):
return explanation

def explain_alive(fen, depth = 10):
global CHA_MIN

UCI_NOTATION = "--uci" in sys.argv

inp = (fen + " white\n").encode("utf-8")
Expand Down
Loading

0 comments on commit d3c2d01

Please sign in to comment.