De Olympische Spelen zijn de vierjaarlijkse hoogmis van de topsport. Maar niet alleen de atleten zetten er hun beste beentje voor. Het is ook telkens weer uitkijken naar nieuwe technische hoogstandjes op het vlak van sportverslaggeving. Mediaplatformen verdringen zich om de miljoenen volgers wereldwijd op de hoogte te houden van alle sportieve prestaties in de verschillende disciplines.
Voor de Olympische Spelen van Tokyo 2020 — die omwille van de coronacrisis in 2021 georganiseerd werden — reiken wij alvast de gouden medaille voor sportjournalistiek uit aan The New York Times1. Met hun verbluffende animaties wanen online kijkers zich eventjes live toeschouwer van zwem- en loopnummers, zelfs voor fictieve wedstrijden tussen atleten uit verschillende olympiaden.
Tijdens loopnummers worden de posities van de atleten gemeten op een aantal opeenvolgende tijdstippen, en die metingen worden frames genoemd. De resultaten daarvan worden opgeslagen in een tekstbestand. De opeenvolgende regels van het bestand beschrijven de opeenvolgende frames en bestaan uit een aantal informatievelden die van elkaar gescheiden worden door tabs. Het eerste veld bevat het volgnummer van het frame (een natuurlijk getal), waarbij frames genummerd worden vanaf 0. Daarna volgt voor elke atleet een veld met een natuurlijk getal dat de afstand aanduidt die de atleet reeds heeft afgelegd (uitgedrukt in meter). Het aantal atleten kan dus afgeleid worden uit het aantal velden van een frame. Het eerste frame wordt genomen bij de start, en uiteraard hebben alle atleten dan nog 0 meter afgelegd. We gaan er ook van uit dat alle atleten in het laatste frame over de finish gelopen zijn. De totale afstand van het loopnummer kan dus afgeleid worden uit het laatste frame in het bestand, en die regel geeft meteen ook aan hoeveel frames er zijn. Dit is bijvoorbeeld een bestand frames_1500m.tsv2 dat in 51 frames de toestand vastlegt van een loopnummer over 1500 meter met 3 atleten:
0 0 0 0 1 2 48 1 2 6 95 1 3 14 142 2 4 24 188 4 … 31 1027 1241 1171 32 1070 1267 1221 33 1112 1292 1265 … 48 1495 1498 1500 49 1499 1500 1500 50 1500 1500 1500
Schrijf een bash shell script track dat kan gebruikt worden om de toestand van een loopnummer grafisch weer te geven op de terminal. Aan het shell script moeten verplicht twee argumenten doorgegeven worden: i) de padnaam van een bestand met frames van een loopnummer en ii) het volgnummer van het frame waarop de toestand van het loopnummer moet weergegeven worden. Dit is bijvoorbeeld hoe frame 32 van het voorbeeldbestand wordt weergegeven:
$ track frames_1500m.tsv3 32
Runners: 3, Length: 1500, Frame: 32
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | | | | |\@ | | | | |###|
|###| | | | | | | | | | | | | | |\| | | | |###|
|###| | | | | | | | | | | | | |/ \| | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | | | | | | |\@ | | |###|
|###| | | | | | | | | | | | | | | | |\| | |###|
|###| | | | | | | | | | | | | | | |/ \| | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | | | | | |\@ | | | |###|
|###| | | | | | | | | | | | | | | |\| | | |###|
|###| | | | | | | | | | | | | | |/ \| | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
De eerste regel die het shell script naar standaard uitvoer (stdout) schrijft, geeft aan hoeveel atleten er zijn, wat de totale afstand van het loopnummer is, en voor welk framenummer de toestand wordt uitgeschreven. Daarna wordt een grafische voorstelling uitgeschreven van de verschillende banen: één baan per atleet. De bovenste baan geeft de positie aan van de eerste atleet (tweede veld van het frame), de baan daaronder van de tweede atleet (derde veld van het frame), enzoverder.
Elke baan is onderverdeeld in een vast aantal blokken, die van elkaar gescheiden worden door verticale strepen. Standaard zijn er 20 blokken. Elk blok wordt opgevuld met drie karakters tussen de verticale strepen: hekjes (#) voor het eerste en laatste blok, en spaties voor de andere blokken. Eén van de blokken wordt echter opgevuld met een symbolische voorstelling van de atleet, om aan te geven hoe ver de atleet in die baan reeds gelopen is. Als we de blokken van links naar rechts nummeren vanaf 0, elke baan ingedeeld is in $$b$$ blokken, de totale afstand van het loopnummer $$t$$ is, en de atleet reeds afstand $$a$$ afgelegd heeft, dan wordt de atleet in blok \[ (b - 1) \times \frac{a}{t} \] geplaatst, waarbij deze waarde wordt afgerond naar het dichtsbijzijnde natuurlijk getal. Dit zijn bijvoorbeeld de posities van de drie atleten in frame 32 van het voorbeeldbestand:
atleet | aantal blokken ($$b$$) | afgelegde afstand ($$a$$) | totale afstand ($$t$$) | positie (blok) |
---|---|---|---|---|
1 | 20 | 1070 | 1500 | $$(20 - 1) \times \frac{1070}{1500} = 13.55 \approx 14$$ |
2 | 20 | 1241 | 1500 | $$(20 - 1) \times \frac{1267}{1500} = 15.72 \approx 16$$ |
3 | 20 | 1171 | 1500 | $$(20 - 1) \times \frac{1221}{1500} = 15.47 \approx 15$$ |
Bash heeft niet direct ondersteuning om te werken met reële getallen. Om een reëel getal af te ronden naar het dichtstbijzijnde natuurlijk getal, moet je in deze opgave gebruikmaken van deze shell functie.
round() {
printf "%.0f" "$(bc <<< "($1+0.5)/1")"
}
Aan de functie moet je een reëel getal als argument doorgeven. De functie schrijft het dichtstbijzijnde natuurlijk getal naar standaard uitvoer (stdout).
Om te doen alsof de atleten echt aan het lopen zijn, worden ze symbolisch voorgesteld in drie verschillende houdingen. De houding die een atleet aanneemt, hangt af van het framenummer. Meer bepaald, de rest na deling van het framenummer door drie geeft aan welke van de volgende drie houdingen moet geselecteerd worden:
0 1 2 @/ @ \@ /| -|- |\ / \ | / \
Het shell script moet ook de volgende opties ondersteunen:
optie -b <int>: aan deze optie moet verplicht een natuurlijk getal $$b$$ doorgegeven worden dat aangeeft hoeveel blokken er tussen het eerste en het laatste blok van een baan liggen; er zijn dus altijd minstens twee blokken en het totaal aantal blokken (inclusief blokken aan de start en de finish) is gelijk aan $$b + 2$$; standaard telt een baan 20 blokken (inclusief start en finish)
optie -h <char>: aan deze optie moet verplicht een karakter doorgegeven worden dat als hoofd gebruikt wordt van de atleten; standaard wordt het hoofd van de atleten voorgesteld door een apenstaartje (@)
optie -n: met deze optie moeten de banen van boven naar onder genummerd worden vanaf 1 in hun start- en finishblok; bekijk het formaat daarvan in onderstaande voorbeelden; daarbij mag je ervan uitgaan dat er maximaal negen banen zijn
Het shell script moet voor de verwerking van de opties de flexibiliteit aan de dag leggen die gebruikelijk is bij Unix commando's: volgorde van opties speelt geen rol, opties kunnen eventueel samengenomen worden, argument bij een optie moet niet noodzakelijk van de optieletter gescheiden worden door witruimte, …. Daarnaast moet het shell script de volgende foutafhandeling voorzien:
als niet de gepaste opties doorgegeven worden (enkel ondersteuning voor de opties -b, -h en -n, waarbij verplicht een argument aan de opties -b en -h moet doorgegeven worden) of er worden niet exact twee argumenten aan het shell script doorgegeven, dan moet het shell script een gepaste boodschap uitschrijven naar stderr en eindigen met exit status 1
als het argument dat aan de optie -b wordt doorgegeven geen natuurlijk getal is, dan moet het shell script een gepaste boodschap uitschrijven naar stderr en eindigen met exit status 2
als het argument dat aan de optie -h wordt doorgegeven geen karakter is, dan moet het shell script een gepaste boodschap uitschrijven naar stderr en eindigen met exit status 3
als het eerste argument niet de padnaam is van een gewoon leesbaar bestand, dan moet het shell script een gepaste boodschap uitschrijven naar stderr en eindigen met exit status 4
als het tweede argument geen natuurlijk getal is dat overeenkomt met het volgnummer van een frame uit het gegeven bestand (eerste argument), dan moet het shell script een gepaste boodschap uitschrijven naar stderr en eindigen met exit status 5
Zie onderstaande voorbeeldsessie voor wat we telkens bedoelen met de gepaste boodschap die moet uitgeschreven worden naar stderr.
Onderstaande voorbeeldsessie toont aan hoe het shell script track moet kunnen gebruikt worden. Hierbij gaan we ervan uit dat het bestand frames_1500m.tsv4 zich in de huidige directory bevindt.
$ track frames_1500m.tsv5 25
Runners: 3, Length: 1500, Frame: 25
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | @ | | | | | | | | |###|
|###| | | | | | | | | |-|-| | | | | | | | |###|
|###| | | | | | | | | | | | | | | | | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | | | | @ | | | | | |###|
|###| | | | | | | | | | | | |-|-| | | | | |###|
|###| | | | | | | | | | | | | | | | | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | @ | | | | | | | | |###|
|###| | | | | | | | | |-|-| | | | | | | | |###|
|###| | | | | | | | | | | | | | | | | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
$ track -n frames_1500m.tsv6 26
Runners: 3, Length: 1500, Frame: 26
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | |\@ | | | | | | | | |###|
|#1#| | | | | | | | | | |\| | | | | | | | |#1#|
|###| | | | | | | | | |/ \| | | | | | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | | | | |\@ | | | | |###|
|#2#| | | | | | | | | | | | | | |\| | | | |#2#|
|###| | | | | | | | | | | | | |/ \| | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | | |\@ | | | | | | | |###|
|#3#| | | | | | | | | | | |\| | | | | | | |#3#|
|###| | | | | | | | | | |/ \| | | | | | | |###|
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
$ track -b8 -h§ frames_5_runners.tsv7 21
Runners: 5, Length: 1500, Frame: 21
+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | §/| | |###|
|###| | | | | |/| | | |###|
|###| | | | | |/ \| | |###|
+---+---+---+---+---+---+---+---+---+---+
| §/| | | | | | | | |###|
|/| | | | | | | | | |###|
|/ \| | | | | | | | |###|
+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | §/|
|###| | | | | | | | |/| |
|###| | | | | | | | |/ \|
+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | §/| | |###|
|###| | | | | |/| | | |###|
|###| | | | | |/ \| | |###|
+---+---+---+---+---+---+---+---+---+---+
|###| | | | | | | | | §/|
|###| | | | | | | | |/| |
|###| | | | | | | | |/ \|
+---+---+---+---+---+---+---+---+---+---+
$ track frames_1500m.tsv8
Syntax: track [-b <int>] [-h <char>] [-n] FILE FRAME
$ echo$?
1
$ track -x frames_1500m.tsv9 25
Syntax: track [-b <int>] [-h <char>] [-n] FILE FRAME
$ echo$?
1
$ track -b XYZ frames_1500m.tsv10 25
Syntax: track [-b <int>] [-h <char>] [-n] FILE FRAME
$ echo$?
2
$ track -h XYZ frames_1500m.tsv11 25
Syntax: track [-b <int>] [-h <char>] [-n] FILE FRAME
$ echo$?
3
$ track unknown.tsv 25
Syntax: track [-b <int>] [-h <char>] [-n] FILE FRAME
$ echo$?
4
$ track frames_1500m.tsv12 999
Syntax: track [-b <int>] [-h <char>] [-n] FILE FRAME
$ echo$?
5
Als je het shell script track correct geïmplementeerd hebt, dan is het maar een kleine moeite meer om een volledige animatie van een loopnummer op je terminal te toveren. Aan het shell script animated_track13 kan je als argument hetzelfde bestand meegeven dat je ook als eerste argument aan het shell script track meegeeft. Voor het voorbeeldbestand frames_1500m.tsv14 krijg je dan de volgende animatie te zien: