kindwolf.org Git repositories moulti / master examples / moulti-scoreboard.bash
master

Tree @master (Download .tar.gz)

moulti-scoreboard.bash @masterraw · history · blame

#!/usr/bin/env bash

# This example script leverages MOULTI_CUSTOM_CSS to implement a scoreboard
# with two steps next to each other despite Moulti NOT offering this feature in
# the first place.

# Usage:
# 1 - in a first terminal, run this script: ./moulti-scoreboard.bash
# 2 - in a second terminal, source this script: source ./moulti-scoreboard.bash
# This provides a bunch of bash functions to control the scoreboard:
# - scoreboard_timer_pause, scoreboard_timer_resume and scoreboard_timer_reset
#   pause, resume and reset the timer, respectively.
# - scoreboard_score_inc <team> <toilet_args> increments the selected team's score:
#     scoreboard_score_inc 1  # increment the home team's score
#     scoreboard_score_inc 2 --metal # increment the guest team's score
# - scoreboard_score_set_val <team> <score> <toilet_args> sets the selected team's score:
#     scoreboard_score_set_val 1 12 --metal # set the home team's score to 12
# - scoreboard_score_set_val <team> <text> <toilet_args> shows any arbitrary
#   text inside the selected team's display:
#     scoreboard_score_set_val 1 WIN --metal # display "WIN" instead of home team's score

# Part 1: variables and functions users should source to control the scoreboard:

scoreboard_instance="${SCOREBOARD_INSTANCE:-default}"
export MOULTI_INSTANCE="scoreboard-${scoreboard_instance}"
export SCOREBOARD_CONTROL_PATH="${SCOREBOARD_CONTROL_PATH:-/tmp/scoreboard-${scoreboard_instance}-control}"
declare -a SCOREBOARD_SCORES=('' 0 0)

function scoreboard_score_inc {
	local team="${1}"; shift
	local score
	((score=++SCOREBOARD_SCORES[$team]))
	scoreboard_score_set_val "${team}" "${score}" "$@"
}

function scoreboard_score_set_val {
	local team="${1}"; shift
	local score="${1}"; shift
	SCOREBOARD_SCORES[$team]="${score}"
	scoreboard_score_set_text "${team}" "${score}" "$@"
}

function scoreboard_score_set_text {
	local team="team${1}"; shift
	toilet --font mono12 --filter=crop "$@" | moulti pass "${team}"
}

function scoreboard_timer_reset {
	touch "${SCOREBOARD_CONTROL_PATH}-timer-reset"
}

function scoreboard_timer_pause {
	touch "${SCOREBOARD_CONTROL_PATH}-timer-pause"
}

function scoreboard_timer_resume {
	rm -f "${SCOREBOARD_CONTROL_PATH}-timer-pause"
}

[ "$0" != "${BASH_SOURCE[0]}" ] && return 0

# Part 2: spawn a new Moulti instance:

if [ -z "${MOULTI_RUN}" ]; then
	export MOULTI_QUIT_POLICY='default=quit;running=terminate'
	export MOULTI_RUN_OUTPUT='harvest'

	# This script embeds a custom TCSS file for Moulti:
	MOULTI_CUSTOM_CSS=$(mktemp)
	sed '1,/<<SCOREBOARD_CSS$/ d' "$0" > "${MOULTI_CUSTOM_CSS}"
	export MOULTI_CUSTOM_CSS

	# Like all Textual applications, Moulti obeys $COLUMNS and $LINES:
	[ "${SCOREBOARD_COLUMNS}" ] && export COLUMNS="${SCOREBOARD_COLUMNS}"
	[ "${SCOREBOARD_LINES}" ] && export LINES="${SCOREBOARD_LINES}"

	exec moulti run --no-suffix -- "$0" "$@"
fi

# Part 3: driver script:

rm -f "${MOULTI_CUSTOM_CSS}"

source moulti-functions.bash
moulti_check_requirements 'toilet' 'stdbuf' ||\
	moulti buttonquestion delete moulti_check_requirements

moulti step add team1 --bottom-text=' ' --title='HOME' --collapsed
moulti step add team2 --bottom-text=' ' --title='GUEST' --collapsed
moulti step add timer --bottom-text=' ' --title='Time'
scoreboard_score_set_val 1 0 --metal
scoreboard_score_set_val 2 0 --metal
moulti step update team1 --no-collapsed
moulti step update team2 --no-collapsed

if [ $(uname) == 'OpenBSD' ]; then
	# On OpenBSD, "unbuffer simply exits when it encounters an EOF from [...] its input".
	# Therefore, the while-sleep loop below must exit when Moulti exits.
	function sleep {
		command sleep "$@" && moulti wait --max-attempts=1
	}
fi

{
	FORMAT="${SCOREBOARD_TIMER_FORMAT:-%03d:%02d\n}"
	# shellcheck disable=SC2059
	printf "${FORMAT}" 0 0
	prev_mode=''
	total=0
	elapsed=0
	start=$(date +%s)
	while sleep .5; do
		# Handle reset requests:
		if [ -e "${SCOREBOARD_CONTROL_PATH}-timer-reset" ]; then
			start=$(date +%s)
			total=0
			moulti step clear timer
			# shellcheck disable=SC2059
			printf "${FORMAT}" 0 0
			rm -f "${SCOREBOARD_CONTROL_PATH}-timer-reset"
		fi
		# Handle pause/resume requests:
		[ -e "${SCOREBOARD_CONTROL_PATH}-timer-pause" ] && mode='pause' || mode=''
		if [ "${prev_mode}" != "${mode}" ]; then
			prev_mode="${mode}"
			if [ "${mode}" == "pause" ]; then
				total="${elapsed}"
			else # resume
				start=$(date +%s)
			fi
		fi
		# Update the timer display:
		if [ "${mode}" != 'pause' ]; then
			now=$(date +%s)
			# Clear the timer step every ~5 minutes
			((elapsed=total+now-start,min=elapsed/60,sec=elapsed%60,clear=elapsed%300))
			[ "${clear}" == '0' ] && moulti step clear timer
			# shellcheck disable=SC2059
			printf "${FORMAT}" "${min}" "${sec}"
		fi
	done
} | stdbuf -i0 -o0 toilet --font mono12 --filter=crop | moulti pass timer

exit 0
# Part 4: custom Textual CSS:
# shellcheck disable=all
: <<SCOREBOARD_CSS
/* Remove header, footer and tooltips: */
Tooltip, Footer, #header {
	display: none;
}

StepContainer {
	align-horizontal: center;
	align-vertical: middle;
	layers: teams time;
}

MoultiLog {
	scrollbar-background: black;
	scrollbar-color: black;
}

#step_team1 {
	background: blue;
	layer: teams;
	offset: 0 0;
	position: absolute;
	width: 50%;
}

#step_team2 {
	background: red;
	layer: teams;
	offset: 50vw 0;
	position: absolute;
	width: 50%;
}

#step_timer {
	background: white;
	width: 67;
	layer: time;
	& MoultiLog { height: 7; }
}