Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prevent potential errors with the SF process, and misc. #80

6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ You should install the stockfish engine in your operating system globally or spe
```python
from stockfish import Stockfish

stockfish = Stockfish("/Users/zhelyabuzhsky/Work/stockfish/stockfish-9-64")
stockfish = Stockfish(path="/Users/zhelyabuzhsky/Work/stockfish/stockfish-9-64")
```

There are some default engine's settings:
Expand All @@ -46,9 +46,9 @@ There are some default engine's settings:
}
```

You can change them during your Stockfish class initialization:
You can change them, as well as the default search depth, during your Stockfish class initialization:
```python
stockfish = Stockfish(parameters={"Threads": 2, "Minimum Thinking Time": 30})
stockfish = Stockfish(path="/Users/zhelyabuzhsky/Work/stockfish/stockfish-9-64", depth=18, parameters={"Threads": 2, "Minimum Thinking Time": 30})
```

### Set position by sequence of moves
Expand Down
26 changes: 17 additions & 9 deletions stockfish/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Stockfish:
"""Integrates the Stockfish chess engine with Python."""

def __init__(
self, path: str = "stockfish", depth: int = 2, parameters: dict = None
self, path: str = "stockfish", depth: int = 15, parameters: dict = None
) -> None:
self.default_stockfish_params = {
"Write Debug Log": "false",
Expand All @@ -35,14 +35,16 @@ def __init__(
"UCI_Elo": 1350,
"UCI_ShowWDL": "false",
}
self.stockfish = subprocess.Popen(
self._stockfish = subprocess.Popen(
path,
universal_newlines=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)

self._has_quit_command_been_sent = False

self._stockfish_major_version: int = int(
self._read_line().split(" ")[1].split(".")[0]
)
Expand Down Expand Up @@ -93,15 +95,18 @@ def _prepare_for_new_position(self, send_ucinewgame_token: bool = True) -> None:
self.info = ""

def _put(self, command: str) -> None:
if not self.stockfish.stdin:
if not self._stockfish.stdin:
raise BrokenPipeError()
self.stockfish.stdin.write(f"{command}\n")
self.stockfish.stdin.flush()
if self._stockfish.poll() is None and not self._has_quit_command_been_sent:
self._stockfish.stdin.write(f"{command}\n")
self._stockfish.stdin.flush()
if command == "quit":
self._has_quit_command_been_sent = True

def _read_line(self) -> str:
if not self.stockfish.stdout:
if not self._stockfish.stdout:
raise BrokenPipeError()
return self.stockfish.stdout.readline().strip()
return self._stockfish.stdout.readline().strip()

def _set_option(self, name: str, value: Any) -> None:
self._put(f"setoption name {name} value {value}")
Expand Down Expand Up @@ -543,5 +548,8 @@ def get_stockfish_major_version(self):
return self._stockfish_major_version

def __del__(self) -> None:
self._put("quit")
self.stockfish.kill()
if self._stockfish.poll() is None:
self._put("quit")
self._stockfish.kill()
while self._stockfish.poll() == None:
pass
28 changes: 28 additions & 0 deletions tests/stockfish/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ def test_set_skill_level(self, stockfish):
assert stockfish.get_parameters()["Skill Level"] == 20

def test_set_elo_rating(self, stockfish):
stockfish.set_depth(2)
stockfish.set_fen_position(
"rnbqkbnr/ppp2ppp/3pp3/8/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 1"
)
Expand Down Expand Up @@ -496,3 +497,30 @@ def test_benchmark_result_with_invalid_type(self, stockfish):
result = stockfish.benchmark(params)
# result should contain the last line of a successful method call
assert result.split(" ")[0] == "Nodes/second"

def test_multiple_calls_to_del(self, stockfish):
assert stockfish._stockfish.poll() is None
assert not stockfish._has_quit_command_been_sent
stockfish.__del__()
assert stockfish._stockfish.poll() is not None
assert stockfish._has_quit_command_been_sent
stockfish.__del__()
assert stockfish._stockfish.poll() is not None
assert stockfish._has_quit_command_been_sent

def test_multiple_quit_commands(self, stockfish):
# Test multiple quit commands, and include a call to del too. All of
# them should run without causing some Exception.
assert stockfish._stockfish.poll() is None
assert not stockfish._has_quit_command_been_sent
stockfish._put("quit")
assert stockfish._has_quit_command_been_sent
stockfish._put("quit")
assert stockfish._has_quit_command_been_sent
stockfish.__del__()
assert stockfish._stockfish.poll() is not None
assert stockfish._has_quit_command_been_sent
stockfish._put(f"go depth {10}")
# Should do nothing, and change neither of the values below.
assert stockfish._stockfish.poll() is not None
assert stockfish._has_quit_command_been_sent