lorchess/game-add

279 lines
8.2 KiB
Bash
Executable File

#!/usr/bin/env bash
# Copyright 2014 Vladimir Ivanov <ivvl82@gmail.com>
# Distributed under the terms of the GNU General Public License v2
REPO_DIR=
# Specify the tournament here
TOURNAMENT=
# Version information
VERSION="0.3"
# Firefox User Agent
FIREFOX_UA="Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0"
argv0=${0##*/}
function usage {
cat <<EOF
Store chess games played on lichess.org
Usage:
$argv0 -t <num> [-u] [<url>]
$argv0 -h
$argv0 -v
Put the script under the root directory of your repository or set
inner variable REPO_DIR to it. If the tournament is not the last one
(default), store its sub-directory in inner variable TOURNAMENT.
The first form fills the results of chess games and stores its PGN
files, assuming that all the games were played by the same pair of
players on tour <num> of the tournament. The PGN files are sorted by
their timestamps, unless '-u' is set. Each game should be available
at the corresponding <url> (lichess.org).
The second form shows this help output. The third form shows version
information.
Options:
-u Don't sort games by their timestamps
EOF
exit "${1:-0}"
}
function version {
exec echo "${argv0}-${VERSION}"
}
function game_setup {
[[ -z $REPO_DIR ]] && REPO_DIR=`dirname "$0"`
# Convert REPO_DIR to an absolute path
[[ ! $REPO_DIR =~ ^/ ]] && REPO_DIR=$(cd ${REPO_DIR}; pwd)
# If no tournament given, set it to the last one
if [[ -z $TOURNAMENT ]]; then
local year_dir=$(ls -1 -d ${REPO_DIR}/[0-9][0-9][0-9][0-9]/ | tail -1)
TOURNAMENT=$(ls -1 -d ${year_dir}[0-9]-*/ | tail -1 \
| sed -E "s|${REPO_DIR}/(.*)|\1|")
# Remove the trailing slash
TOURNAMENT=${TOURNAMENT%/}
fi
# Configuration file for players
ply_ini=${REPO_DIR}/${TOURNAMENT}/players.ini
}
function game_mktemp {
# Don't sort just one game
[[ $# == 1 ]] && SORT_GAMES=false
for url in $@; do
local pgn_template="pgn"
[[ $url =~ ^(http://[^/]*)/([^/]*) ]]
# Link to annotated game PGN
local game_url=${BASH_REMATCH[1]}/game/export/${BASH_REMATCH[2]::8}.pgn
# Append the timestamp of game for sorting
if $SORT_GAMES; then
local game_api=${BASH_REMATCH[1]}/api/game/${BASH_REMATCH[2]::8}
local api_response=$(curl -q --fail --location --silent "$game_api")
[[ -z $api_response ]] && die "Unreachable game API ${game_api}"
local game_time=$(sed -En "s/.*\"timestamp\":([0-9]+).*/\1/p" <<< "$api_response")
pgn_template+="-${game_time}"
fi
# Store PGN file in a temporal location
local tmp_pgn=$(mktemp -t ${pgn_template}.XXXXXX)
TMP_PGN_FILES+=" $tmp_pgn"
trap "rm $TMP_INI_FILES $TMP_PGN_FILES" EXIT
wget -q -U "$FIREFOX_UA" -O $tmp_pgn "$game_url" \
|| die "Unreachable game PGN ${game_url}"
done
$SORT_GAMES && TMP_PGN_FILES=$(echo "$TMP_PGN_FILES" | xargs -n1 | sort | xargs)
}
function game_get_players {
# Extract players on Lichess
local wt_lichess=$(sed -En "s/\[White \"([^\"]*)\"\]/\1/p" $tmp_pgn)
local bk_lichess=$(sed -En "s/\[Black \"([^\"]*)\"\]/\1/p" $tmp_pgn)
# Get names of white and black players
local counter=1 players=()
game_parse_config
while eval "config_section_player${counter}" 2>/dev/null; do
players+=("$name")
[[ $lichess == $wt_lichess ]] && white=$name
[[ $lichess == $bk_lichess ]] && black=$name
((counter += 1))
done
[[ -z $white ]] && fix_white_player
[[ -z $black ]] && fix_black_player
}
function game_parse_config {
# Copy player INI file to the temporary location
# NOTE: an empty line is added to the file beginning in order to
# match the only first occurrence for non-GNU sed
echo > $tmp_ini
cat "$ply_ini" >> $tmp_ini
# Remove tabs or spaces around the `='
sed -E -i.prev "s/[[:blank:]]*=[[:blank:]]*/=/" "$tmp_ini"
# Transform section labels into function declaration
sed -E -i.prev "1,/^\[.*\]/s/^\[([^]]*)\]/config_section_\1() {/" "$tmp_ini"
sed -E -i.prev "s/^\[([^]]*)\]/}\\"$'\n'"config_section_\1() {/" "$tmp_ini"
echo -e "\n}" >> $tmp_ini
# Source the file
source "$tmp_ini"
}
function fix_white_player {
local number
echo "Lichess player '${wt_lichess}' not found."
choose_player
white=${players[$number]}
}
function fix_black_player {
local number
echo "Lichess player '${bk_lichess}' not found."
choose_player
black=${players[$number]}
}
function choose_player {
echo "Please choose a proper name from the list below:"
for ((i=0; i<${#players[@]}; ++i)); do
echo "$((i+1)) ${players[$i]}"
done \
| column -t \
| sed -E "s/^([0-9]*)/$(tput setaf 6)\1$(tput sgr0)/" # highlight number
echo -n "Put number> "
read number
if (( $number < 1 || $number > ${#players[@]} )); then
die "Incorrect player number."
fi
((number += -1))
}
function game_add_to_repo {
local date_re="[0-9?]{2}\.[0-9?]{2}\.[0-9?]{4}"
local correct=false length_max=0
# Change field separator to read a file line by line
old_IFS=$IFS IFS=$'\n'
# Check if the tour number is correct
for line in $(cat "$tour_info"); do
if [[ $line =~ ^(${date_re}\ +([^\ ]+)\ +-\ +([^\ ]+)) ]]; then
local length=${#BASH_REMATCH[1]}
local fst_ply=${BASH_REMATCH[2]} snd_ply=${BASH_REMATCH[3]}
if [[ $fst_ply == $white && $snd_ply == $black ]]; then
local length_rec=${#BASH_REMATCH[1]}
correct=true
fi
if [[ $fst_ply == $black && $snd_ply == $white ]]; then
local length_rec=${#BASH_REMATCH[1]}
local answer
echo -n "Approve game with wrong players' sides? (Y/n)> "
read answer
[[ ! $answer =~ ^(Y|y|Yes|yes)$ ]] && exit 1
white=$fst_ply black=$snd_ply correct=true
fi
# Determine the maximal length of game records
(( $length > $length_max )) && length_max=$length
fi
done
IFS=$old_IFS
if ! $correct; then
die "Game '${white} vs. ${black}' not found in ${tour_info}."
fi
local pgn_date=$(sed -En "s/\[Date \"([^\"]*)\"\]/\1/p" "$tmp_pgn")
local pgn_date=$(tr "." "-" <<< "$pgn_date")
pgn_dir=${REPO_DIR}/${TOURNAMENT}/tours/${TOUR}/${pgn_date}-${white}-vs-${black}
[[ -d $pgn_dir ]] && die "Directory ${pgn_dir} already exist."
echo "Creating directory ${pgn_dir}..."
mkdir -p "$pgn_dir"
echo "Storing PGN file..."
cp "$tmp_pgn" "${pgn_dir}/1.pgn"
echo "Updating tour_info..."
local game_date=${pgn_date:8:2}.${pgn_date:5:2}.${pgn_date::4}
local result=$(sed -En "s/\[Result \"([^\"]*)\"\]/\1/p" $tmp_pgn)
local spaces=$((length_max - length_rec + 1))
local sep=$(printf "%${spaces}s" " ")
# Change the representation of draw
[[ $result == 1/2-1/2 ]] && result=1/2
sed -E -i.orig \
"s|${date_re}( +${white} +- +${black})|${game_date}\1${sep}${result}|" "$tour_info"
rm "${tour_info}.orig"
}
function game_git_commit {
cd $REPO_DIR
git add "${pgn_dir}/1.pgn" "$tour_info"
git commit -m "Tour ${TOUR#0}: ${white} vs. ${black}."
git push
}
function die {
echo "$@" >&2
exit 1
}
function checkargs {
if [[ $opt == t ]]; then
[[ ! $OPTARG =~ ^[0-9]+$ ]] && die "Incorrect tour number."
TOUR=$(printf "%02g" $OPTARG)
fi
}
SORT_GAMES=true
while getopts t:uhv opt; do
case $opt in
t) checkargs ;;
u) SORT_GAMES=false ;;
h) usage ;;
v) version ;;
*) usage 1 ;;
esac
done
shift $(($OPTIND - 1))
# For now, tour number should be given explicitly
[[ -z $TOUR || $# == 0 ]] && usage 1
game_setup
tour_info=${REPO_DIR}/${TOURNAMENT}/tours/${TOUR}/tour_info
[[ ! -f $tour_info ]] && die "File ${tour_info} not found."
# Ensure that the repository is up-to-date
git pull
# Temporary files
tmp_ini=$(mktemp -t `basename $ply_ini`.XXXXXX)
TMP_INI_FILES="${tmp_ini} ${tmp_ini}.prev"
TMP_PGN_FILES=
game_mktemp $@
game_get_players
game_add_to_repo
game_git_commit
exit 0