Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion actions/play-shard/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,15 @@ inputs:
every batch replays the first one's openings.
default: "0"
time_control:
description: The time control, as fastchess reads it.
description: >
The time control, as fastchess reads it. With `candidate_time_control`
set it is the opponent's alone.
required: true
candidate_time_control:
description: >
A clock for the candidate alone, for a time odds match. Empty gives both
sides `time_control`, which is every other match.
default: ""
hash_mb:
description: >
The hash each engine is asked for. Asked rather than given: an engine
Expand Down Expand Up @@ -143,6 +150,7 @@ runs:
START: ${{ steps.slice.outputs.start }}
PAIRS: ${{ inputs.pairs }}
TIME_CONTROL: ${{ inputs.time_control }}
CANDIDATE_TIME_CONTROL: ${{ inputs.candidate_time_control }}
HASH: ${{ inputs.hash_mb }}
CONCURRENCY: ${{ inputs.concurrency }}
STARTUP_MS: ${{ inputs.startup_ms }}
Expand Down
20 changes: 17 additions & 3 deletions actions/play-shard/play.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# START the opening this shard starts at
# PAIRS how many rounds it plays
# TIME_CONTROL, HASH, CONCURRENCY, STARTUP_MS
# CANDIDATE_TIME_CONTROL optional, the candidate's own clock
# MAX_MATCH_MINUTES the wall clock cap
#
# stdout is fastchess's own, so the progress and the result block reach the
Expand All @@ -31,6 +32,19 @@ outputs=${1:?usage: play.sh <outputs-file>}
# find the last attempt's here.
rm -f games.pgn result.txt config.json

# A clock for the candidate alone is a time odds match: the same build both
# sides, one given more time, which prices the time rather than a change. Each
# side's tc then goes on its own -engine line and none in -each, because a tc
# in -each reaches both engines and takes precedence over theirs.
candidate_tc=()
opponent_tc=()
each_tc=("tc=$TIME_CONTROL")
if [ -n "${CANDIDATE_TIME_CONTROL:-}" ]; then
candidate_tc=("tc=$CANDIDATE_TIME_CONTROL")
opponent_tc=("tc=$TIME_CONTROL")
each_tc=()
fi

# No -sprt, sharded or not. A shard that stopped itself when its own games
# settled the question would be one look per shard at a bound meant for one
# look, and its games a slice chosen by what they said. The summary runs the
Expand All @@ -42,9 +56,9 @@ rm -f games.pgn result.txt config.json
# timeout says it sent it.
status=0
timeout -k 60 -s INT "${MAX_MATCH_MINUTES}m" ./fastchess \
-engine "name=$CANDIDATE" "cmd=$CANDIDATE_BINARY" \
-engine "name=$OPPONENT" "cmd=$OPPONENT_BINARY" \
-each proto=uci "tc=$TIME_CONTROL" "option.Hash=$HASH" \
-engine "name=$CANDIDATE" "cmd=$CANDIDATE_BINARY" ${candidate_tc[@]+"${candidate_tc[@]}"} \
-engine "name=$OPPONENT" "cmd=$OPPONENT_BINARY" ${opponent_tc[@]+"${opponent_tc[@]}"} \
-each proto=uci ${each_tc[@]+"${each_tc[@]}"} "option.Hash=$HASH" \
-startup-ms "$STARTUP_MS" \
-openings "file=$BOOK_FILE" "format=$BOOK_FORMAT" order=sequential "start=$START" \
-rounds "$PAIRS" -repeat -concurrency "$CONCURRENCY" -recover \
Expand Down
47 changes: 46 additions & 1 deletion tests/test_action_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def match(tmp_path):
return tools


def play(tools, outputs):
def play(tools, outputs, **extra):
return subprocess.run(
[str(PLAY), str(outputs)],
cwd=tools,
Expand All @@ -81,6 +81,7 @@ def play(tools, outputs):
"CONCURRENCY": "2",
"STARTUP_MS": "20000",
"MAX_MATCH_MINUTES": "150",
**extra,
},
)

Expand Down Expand Up @@ -123,6 +124,50 @@ def test_a_harness_that_died_fails_the_shard(self, match, tmp_path):
assert play(match, tmp_path / "outputs").returncode == 3


def arguments(tools, outputs, **extra):
"""What fastchess was given, one argument a line, the `-engine` and
`-each` groups split apart."""
executable(tools / "fastchess", 'printf "%s\\n" "$@" > args.txt')
result = play(tools, outputs, **extra)
assert result.returncode == 0, result.stderr
groups, current = [], None
for argument in (tools / "args.txt").read_text().splitlines():
if argument.startswith("-"):
current = [argument]
groups.append(current)
else:
current.append(argument)
return groups


class TestTheClocks:
# A tc in -each reaches both engines and wins over an -engine one, which
# is how a time odds match once played both sides at the shorter clock
# and read -3 for a clock that was worth +66.

def test_one_clock_is_given_to_both_sides_in_each(self, match, tmp_path):
groups = arguments(match, tmp_path / "outputs")
engines = [g for g in groups if g[0] == "-engine"]
each = next(g for g in groups if g[0] == "-each")
assert "tc=10+0.1" in each
assert not any(a.startswith("tc=") for g in engines for a in g)

def test_a_candidate_clock_goes_on_each_engine_line_and_not_in_each(
self, match, tmp_path
):
groups = arguments(match, tmp_path / "outputs", CANDIDATE_TIME_CONTROL="20+0.2")
candidate, opponent = [g for g in groups if g[0] == "-engine"]
each = next(g for g in groups if g[0] == "-each")
assert "name=new" in candidate and "tc=20+0.2" in candidate
assert "name=old" in opponent and "tc=10+0.1" in opponent
assert not any(a.startswith("tc=") for a in each)

def test_an_empty_candidate_clock_is_the_symmetric_match(self, match, tmp_path):
assert arguments(
match, tmp_path / "outputs", CANDIDATE_TIME_CONTROL=""
) == arguments(match, tmp_path / "outputs")


@pytest.fixture
def games(tmp_path):
"""One shard's pgn, and an estimator that reports a remark the way the
Expand Down
Loading