joeylib2/scripts/lib/joyGate.sh

145 lines
6.2 KiB
Bash

# shellcheck shell=bash
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
# See LICENSE.md for license information.
# SPDX-License-Identifier: MIT
#
# Shared by the joystick gates that read examples/joy's screen
# (scripts/verify-*-joy.sh): the steps, the check of each screenshot
# (scripts/lib/joyScreen.py) and the tally. Source after joeyEnv.sh. The gate
# defines three functions first:
#
# joyShot PNG save a screenshot of JOY as PNG; non-zero if none
# joyPress CONTROL... hold the controls (below)
# joyRelease CONTROL... let them go
#
# A control is a stick (A or B) and a line: A-left A-right A-up A-down
# A-button0 A-button1, and the same for B. A screenshot reads as
# "A=<state> B=<state>", each state five characters: connected (1/0), X and
# Y (- 0 +), button 0 and button 1 (1/0).
#
# joyCheck NAME WANT a screenshot, which must read WANT ("A=... B=...");
# an unreadable one ('?': JOY caught between
# erasing a dot and drawing it again) is taken
# once more before it counts
# joyRunSteps STEPS... for each "name|controls|A's state|B's state":
# hold the controls, check, let go, check at rest;
# REST in A's state stands for JOY_REST, in B's
# for JOY_REST_B (JOY_REST, or JOY_ABSENT where the
# machine has one stick)
# joyFail WHAT note a failure (after JOY_PASS, if the gate set
# it); the gate goes on to report the rest
# joyResult LABEL WHAT print LABEL's PASS (with WHAT) or FAIL line;
# exit 1 on a failure
#
# The gate sets JOY_LABEL (its name, for the lines it prints) and JOY_WORK
# (its scratch folder). JOY_SCALE (joyScreen.py --scale) is the stage's
# scale in the screenshot, empty to let joyScreen.py find it;
# JOY_SCREEN_ARGS are more joyScreen.py options (--st); JOY_SHOTS, if
# set, keeps every screenshot there; JOY_PRESS_SECONDS is how long a change
# takes to show; JOY_SHOT_TRIES is 1 for a gate whose screenshots were all
# taken before the checks (they cannot be taken again).
JOY_REST="10000" # a stick connected, in the middle, buttons up
JOY_ABSENT="00000" # no stick
JOY_REST_B="$JOY_REST" # stick B when nothing holds it
JOY_SCALE="${JOY_SCALE:-}"
JOY_SCREEN_ARGS=()
JOY_PRESS_SECONDS="${JOY_PRESS_SECONDS:-1}"
JOY_SHOT_TRIES="${JOY_SHOT_TRIES:-2}"
JOY_RETRY_SECONDS=0.5
JOY_FAILS=""
JOY_CHECKED=0
JOY_PASS="" # a gate that runs its steps more than once names each run
# Both sticks, every direction, two diagonals each, each button alone and
# together, and a step holding different lines on both sticks at once, so
# crosstalk between them shows up.
JOY_STEPS_TWO_STICKS=(
"A left|A-left|1-000|REST" "A right|A-right|1+000|REST"
"A up|A-up|10-00|REST" "A down|A-down|10+00|REST"
"A up-left|A-left A-up|1--00|REST" "A down-right|A-right A-down|1++00|REST"
"A button 0|A-button0|10010|REST" "A button 1|A-button1|10001|REST"
"A both buttons|A-button0 A-button1|10011|REST"
"B left|B-left|REST|1-000" "B right|B-right|REST|1+000"
"B up|B-up|REST|10-00" "B down|B-down|REST|10+00"
"B up-right|B-right B-up|REST|1+-00" "B down-left|B-left B-down|REST|1-+00"
"B button 0|B-button0|REST|10010" "B button 1|B-button1|REST|10001"
"B both buttons|B-button0 B-button1|REST|10011"
"A left+button 1, B right+button 0|A-left A-button1 B-right B-button0|1-001|1+010"
)
# The same for sticks with one button (the Atari's fire button). Held for the
# screenshot, the button is button 1: JOY_PRESS_SECONDS is past the half
# second that makes a press a hold. A tap is button 0 for one poll only,
# which no screenshot catches; tests/host/inputHost.c checks it.
JOY_STEPS_ONE_BUTTON=(
"A left|A-left|1-000|REST" "A right|A-right|1+000|REST"
"A up|A-up|10-00|REST" "A down|A-down|10+00|REST"
"A up-left|A-left A-up|1--00|REST" "A down-right|A-right A-down|1++00|REST"
"A button held: button 1|A-button0|10001|REST"
"B left|B-left|REST|1-000" "B right|B-right|REST|1+000"
"B up|B-up|REST|10-00" "B down|B-down|REST|10+00"
"B up-right|B-right B-up|REST|1+-00" "B down-left|B-left B-down|REST|1-+00"
"B button held: button 1|B-button0|REST|10001"
"A left+button held, B right+button held|A-left A-button0 B-right B-button0|1-001|1+001"
)
joyCheck() {
local png="$JOY_WORK/shot.png"
local got
local try
local args=("${JOY_SCREEN_ARGS[@]}")
JOY_CHECKED=$((JOY_CHECKED + 1))
[ -z "$JOY_SCALE" ] || args+=(--scale "$JOY_SCALE")
for ((try = 1; try <= JOY_SHOT_TRIES; try++)); do
[ "$try" = 1 ] || sleep "$JOY_RETRY_SECONDS"
rm -f "$png"
if ! joyShot "$png" || [ ! -s "$png" ]; then
joyFail "$1: no screenshot"
return
fi
got=$(python3 "$JOEY_REPO/scripts/lib/joyScreen.py" "$png" "${args[@]}")
[[ "${got% scale=*}" == *"?"* ]] || break
done
if [ -n "${JOY_SHOTS:-}" ]; then
mkdir -p "$JOY_SHOTS"
cp "$png" "$JOY_SHOTS/$(printf '%02d' "$JOY_CHECKED") $1.png"
fi
echo "$JOY_LABEL: $1: $got"
[ "${got% scale=*}" = "$2" ] || joyFail "$1: read ${got% scale=*}, want $2"
}
joyFail() {
JOY_FAILS="${JOY_FAILS:+$JOY_FAILS; }${JOY_PASS:+$JOY_PASS: }$1"
}
joyResult() {
if [ -n "$JOY_FAILS" ]; then
echo "$1: FAIL - $JOY_FAILS"
exit 1
fi
echo "$1: PASS ($JOY_CHECKED screenshots: $2)"
}
joyRunSteps() {
local step
local name
local held
local wantA
local wantB
local controls
for step in "$@"; do
IFS='|' read -r name held wantA wantB <<< "$step"
read -r -a controls <<< "$held"
joyPress "${controls[@]}"
sleep "$JOY_PRESS_SECONDS"
joyCheck "$name" "A=${wantA/REST/$JOY_REST} B=${wantB/REST/$JOY_REST_B}"
joyRelease "${controls[@]}"
sleep "$JOY_PRESS_SECONDS"
joyCheck "$name released" "A=$JOY_REST B=$JOY_REST_B"
done
}