Ein Thema wird in Insekta intern als "Szenario" bezeichnet. Dies ermöglicht die Abgrenzung zwischem dem Thema, das inhaltlich behandelt wird (das "Thema") und dem Thema, das die technische Einheit (als Bündel von mehreren Dateien) darstellt (das "Szenario").
Ein Szenario wird hauptsächlich durch HTML mit zusätzlichen Funktionen beschrieben. Dabei ist es auch möglich Bilder, JavaScript und andere statische Dateien einzubinden. Die zusätzlichen Funktionen ermöglichen es interaktive Elemente oder besondere Formatierungen in das Szenario einzubinden.
Diese Datei ist genau genommen ein Jinja-Template, das von Insekta beim Aufruf gerendert wird. Dieses Beispielszenario geht nur grob auf die Syntax ein, für genaueres sollte die Template-Designer-Dokumentation befragt werden.
{#
example
├── meta.json
├── scenario.html
└── static
└── insekta.png
Überschriften sollten mit der dritten Ebene (
<h3>) begonnen werden, da die erste bereits für die Projektüberschrift ("Insekta") und die zweite für den Titel des Themas
("Beispielszenario") vergeben ist. Generell wird empfohlen nicht zuviele Themen in einem Szenario unter verschiedenen
Überschriften unterzubringen. Stattdessen sollten einzelne Szenarien erstellt werden.
Es wird empfohlen sämtlichen Text in Absätze mit dem
<p>-Tag zu gliedern. Dies ermöglicht ein Kommentieren einzelner Absätze, sofern diese zum Kommentieren markiert sind. Das
Markieren erfolgt über das Hinzufügen eines Attributes
data-comment-id mit einem für das jeweilige Szenario eindeutigen Wert als Identifier (alphanumerisch und Bindestriche, max. 64 Zeichen).
Ein kommentierbarer Absatz wird durch eine kleine Sprechblase am Beginn des Absatzes gekennzeichnet. Es wird empfohlen
Absätze generell kommentierbar zu machen.
Folgendes Beispiel zeigt einen kommentierbaren Absatz:
{% call code(language='html') %}Dieser Abschnitt kann kommentiert werden.
{% endcall %}
Um das
data-comment-id-Attribut auf einfache Weise hinzuzufügen, kann das
annotate-comments.py-Script verwendet werden. Dieses sucht nach
<pc>-Tags und ersetzt sie durch einen
<p>-Tag mit dem
data-comment-id-Attribut.
Insekta bindet das
Bootstrap-CSS-Framework ein, entsprechende CSS-Klassen können daher zur Formatierung genutzt werden. Zum besonderen
Hervorheben wird neben
<em> und
<strong> auch die
alert-Klasse vorgeschlagen. Dazu zwei Beispiele:
Eine besonders wichtige Information soll an diesem Beispiel verdeutlicht werden.
Diese Hervorhebung eignet sich besonders gut, um auf häufige Fehler hinzuweisen.
Der dazugehörige Quelltext lautet:
{% call code(language='html') %}Eine besonders wichtige Information soll an diesem Beispiel hervorgehoben werden.
Diese Hervorhebung dient besonders gut, um auf häufige Fehler hinzuweisen.
{% endcall %}Jedes Szenario sollte mindestens eine Aufgabe enthalten. Dies ermöglicht dem Benutzer festzustellen, ob er das Szenario schonmal bearbeitet hat. Im Zweifel reicht auch eine einfache "Aufgabe" mit einer Checkbox, die das Verstehen des Inhalts bestätigt. Besser ist es jedoch, wenn möglichst viele kleine Aufgaben enthalten sind, die dem Leser zwischendurch zum Nachdenken anregen.
Insekta unterstützt integrierte Aufgaben, die vom Benutzer gelöst werden können und automatisiert geprüft werden. Dabei werden Multiple-Choice-Aufgaben mit und ohne Mehrfachnennung unterstützt sowie Freitextantworten.
Multiple-Choice-Aufgaben ohne Mehrfachnennung werden intern als
single_choice bezeichnet. Folgender Quelltext fügt eine solche Aufgabe ein:
Die Erde ist ...
{% call choice(name='flat') %}Eine Scheibe{% endcall %} {% call choice(name='sphere', correct=True) %}Eine Kugel{% endcall %} {% call choice(name='other') %}Etwas anderes{% endcall %} {% endcall %} {% endraw %} {% endcall %}
Jeder
task-Aufruf muss einen über das Szenario eindeutigen Identifier verfügen, der
type-Parameter wird auf
single_choice gesetzt. Optional kann noch der Parameter
title gesetzt werden, um der Aufgabe einen Titel zu geben.
Innerhalb des
task-Aufrufs kann wieder beliebiges HTML und Jinja-Code verwendet werden, um die Frage nach den eigenen Wünschen zu formatieren.
Um die Antwortmöglichkeiten zu spezifizieren, werden
choice-Aufrufe verwendet. Diese haben einen
name-Parameter, der eindeutig für die Frage sein muss und einen optionalen
correct-Parameter, der standardgemäß auf
False steht. Wenn die Antwortmöglichkeit als korrekt angenommen werden soll, muss
correct auf True gesetzt werden. Beim Typ
single_choice kann nur eine Antwortmöglichkeit als korrekt markiert werden.
Schauen wir uns das Ergebnis mal an:
{% call task(identifier='die_erde', type='single_choice', title='Astronomie') %}Die Erde ist ...
{% call choice(name='flat') %}Eine Scheibe{% endcall %} {% call choice(name='sphere', correct=True) %}Eine Kugel{% endcall %} {% call choice(name='other') %}Etwas anderes{% endcall %} {% endcall %}
Um eine Mehrfachnennung zu ermöglichen wird der Typ das
task-Aufrufs auf
multiple_choice gesetzt. Auch der
correct-Parameter der
choice-Aufrufe darf nun bei mehreren Antwortmöglichkeiten auf
True gesetzt werden.
Dazu wieder ein Beispiel: Folgender Code:
{% call code(language='html+jinja') %} {% raw %} {% call task(identifier='cookiemonster', type='multiple_choice') %}Welche Aussagen treffen auf das Cookiemonster zu?
{% call choice(name='cookies', correct=True) %}Er mag Kekse{% endcall %} {% call choice(name='nocookies', correct=False) %}Er mag keine Kekse{% endcall %} {% call choice(name='morecookies', correct=True) %}Er möchte mehr Kekse{% endcall %} {% endcall %} {% endraw %} {% endcall %}ergibt:
{% call task(identifier='cookiemonster', type='multiple_choice') %}Welche Aussagen treffen auf das Cookiemonster zu?
{% call choice(name='cookies', correct=True) %}Er mag Kekse{% endcall %} {% call choice(name='nocookies', correct=False) %}Er mag keine Kekse{% endcall %} {% call choice(name='morecookies', correct=True) %}Er möchte mehr Kekse{% endcall %} {% endcall %}
Als letzten Fragetyp gibt es noch Fragen mit Freitext. Zurzeit wird ein Freitextfeld für eine solche Frage unterstützt, ein
Lückentext mit mehreren Lücken ist daher leider nicht möglich. Der interne Name für diesen Typ lautet
question.. Am Besten eignet sich zum Verständnis wieder ein Beispiel:
Was gibt der Supercomputer "Deep Thought" auf die Frage als Antwort?
{{ answer(expected='42') }} {% endcall %} {% endraw %} {% endcall %}Ergebnis:
{% call task(identifier='hitchhiker', type='question') %}Was gibt der Supercomputer "Deep Thought" auf die Frage als Antwort?
{{ answer(expected='42') }} {% endcall %}
Das Antwortfeld und die gültige Antwort wird durch einen
answer-Aufruf gegeben. Bei obigen Beispiel ist
{% raw %}{{ answer(expected='42') }}{% endraw %} nur eine Kurzform von
{% raw %}{% call answer(expected='42') %}{% endcall %}{% endraw %}. Der
expected-Parameter definiert die zugelassene Antwort. Alternativ kann auch eine Liste von möglichen Antworten übergeben werden:
Daneben gibt es noch drei weitere Parameter:
case_sensitive gibt an, ob Groß- und Kleinschreibung beachtet werden soll (
True/
False), Standard ist
True. Der
strip-Parameter gibt an, ob Leerzeichen ab Anfang und Ende ignoriert werden sollen (auch ein Boolean, Standard:
True). Als letzten Parameter gibt es noch
label, ein String der den Text des Labels über dem Textfeld angibt.
Es gibt die Möglichkeit bestimmten Text nur anzeigen zu lassen, wenn eine gewisse Aufgabe bereits gelöst wurde. Dies kann
zum Beispiel dazu verwendet, um die Antwortmöglichkeiten einer Frage nach dem Lösen nochmal genauer zu erläutert. Der
dazu benötigte Aufruf heißt
require_task und hat einen Parameter
identifier, der den Identifier der dazugehörigen Aufgabe (
task-Aufruf) enthält.
Dazu ein Beispiel:
{% call code(language='html+jinja') %} {% raw %} {% call task(identifier='scientist', type='single_choice') %}Welcher Wissenschaftler war Biologe?
{% call choice(name='gauss') %}Carl Friedrich Gauß{% endcall %} {% call choice(name='curie') %}Marie Curie{% endcall %} {% call choice(name='darwin', correct=True) %}Charles Darwin{% endcall %} {% endcall %} {% call require_task(identifier='scientist') %}
Carl Friedrich Gauß war Mathematiker, Marie Curie beschäftigte sich mit Physik und Chemie. Wie richtig angegeben ist Charles
Darwin Biologe. Er ist für seine Evolutionstheorie bekannt. Dieser Absatz wurde aufgedeckt, weil du die Frage mit dem
Identifier
scientist gelöst hast.
Ergebnis:
{% call task(identifier='scientist', type='single_choice') %}Welcher Wissenschaftler war Biologe?
{% call choice(name='gauss') %}Carl Friedrich Gauß{% endcall %} {% call choice(name='curie') %}Marie Curie{% endcall %} {% call choice(name='darwin', correct=True) %}Charles Darwin{% endcall %} {% endcall %} {% call require_task(identifier='scientist') %}
Carl Friedrich Gauß war Mathematiker, Marie Curie beschäftigte sich mit Physik und Chemie. Wie richtig angegeben ist Charles
Darwin Biologe. Er ist für seine Evolutionstheorie bekannt. Dieser Absatz wurde aufgedeckt, weil du die Frage mit dem
Identifier
scientist gelöst hast.
Noch eine Anmerkung zum
require_task-Aufruf: Der
task-Aufruf und der
require_task-Aufruf müssen nicht nacheinander folgenden, sie sind völlig unabhängig voneinander. Einzig wichtig ist, dass der Identifier
übereinstimmt. Es kann auch mehrere
require_task-Aufrufe geben, die sich auf den gleichen Identifier beziehen.
Um bei Aufgaben Hilfestellungen zu geben, kann der
hint-Aufruf verwendet werden. Dieser erzeugt einen Button, der beim Betätigen den Hinweis anzeigt:
Den Beweis kann man über eine strukturelle Induktion führen.
{% endcall %} {% endraw %} {% endcall %}Das Ergebnis sieht wie folgt aus:
{% call hint() %}Den Beweis kann man über eine strukturelle Induktion führen.
{% endcall %}Im Szenario kann ganz normal MathJax verwendet werden, allerdings nur mit den \(\LaTeX\)-Delimitern, also \( ... \) für Inline-Umgebungen und \[ ... \] für Block-Umgebungen. Dazu wieder ein Beispiel:
{% call code(language='html') %}Der kleine Satz von Fermat kann für \( a \neq 0 \) in die folgende Form gebracht werden: \[ a^{p-1} = 1 \pmod p \]
{% endcall %}Der kleine Satz von Fermat kann für \( a \neq 0 \) in die folgende Form gebracht werden: \[ a^{p-1} = 1 \pmod p \]
Sollte in einem Bereich kein MathJax-Rendering erwünscht sein, so kann die CSS-Klasse
tex2jax_ignore hinzugefügt werden.
Mit einem
media-Aufruf können statische Dateien aus dem
static-Verzeichnis des Szenarios referenziert werden. Darüber können also Downloads bereitsgestellt werden oder Bilder eingebunden
werden.
bindet das folgende Bild ein:
Zum Hervorheben kann der
code-Aufruf verwendet werden. Über den Parameter
language stellt man den Lexer für das Syntax-Highlighting ein. Eine
Liste der verfügbaren Lexer findet sich in der
Pygments-Dokumentation (siehe "short names"). Nun aber erstmal ein einfaches Beispiel:
Der hervorgehobe Python-Quelltext sieht dann wie folgt aus:
{% call code(language='python') %} def fib(n): a, b = 0, 1 while n > 0: a, b = b, a + b n -= 1 return a {% endcall %}
Mit dem Parameter
linenos (Boolean, Standard:
True) kann die Zeilennummerierung ein- und ausgeschaltet werden.
Eine andere Möglichkeit zum Einbinden von Quelltext ist das Einbinden aus einer Datei. Dazu wird beim
code-Aufruf der Parameter
filename angegeben. Zusätzlich dazu kann mit den Parametern
linestart und
lineend noch spezifiziert werden, welche Zeilen der Datei angezeigt werden.
Die ersten zwei Zeilen der
meta.json werden wie folgt angezeigt:
Sie sind:
{{ code(filename='meta.json', language='json', linestart=1, lineend=2) }}
Das Einbinden von JavaScript und CSS läuft nicht über die
scenario.html, sondern übere die
meta.json. Das sieht dann wie folgt aus:
Dabei liegt die
example.css im
static-Verzeichnis dieses Szenarios, genauso wie die
paddingoracle.js. Die
prp.js liegt im
static-Verzeichnis des Szenarios "blockcipher". Die JavaScript-Dateien werden am Ende der Seite im
<body> eingebunden, es stehen
jQuery und
Bootstrap JavaScript zur Verfügung.
Optional können in einem Szenario auch virtuelle Maschinen angesprochen werden. Dabei wird in der
meta.json der Schlüssel
vm_resource auf den Namen der Resource gesetzt. Diese Resource muss vorher im Insekta-VM-System konfiguriert werden, entsprechende
Dokumentation dazu folgt noch.
Mit virtuellen Maschinen steht auch das
vm_ip-Aufruf zur Verfügung, er trägt die IP der entsprechenden virtuellen Maschine an die Stelle ein, wo er eingebunden wird.
Dabei muss der Name der virtuellen Maschine spezifizifiert werden (eine Resource kann mehrere virtuelle Maschinen haben):
Sollte die virtuelle Maschine nicht gestartet sein, so wird erstmal ein Platzhalter ($IP) eingesetzt.
Mit der Variable
vpn_ip kann außerdem die IP des Benutzers im VPN abgefragt werden:
Deine IP im VPN ist {{ vpn_ip }}
{% endraw %} {% endcall %}
Als letztes steht noch das Dictionary
vms als Variable zur Verfügung, das alle virtuellen Maschinen, die gestartet sind, enthält. Dies kannst du zum Beispiel
nutzen, um zu Testen, ob die virtuellen Maschinen gestartet wurden:
Deine virtuelle Maschine wurde gestartet, durch erreichst sie unter {{ vm_ip('main') }}.
{% else %}Bitte starte die virtuellen Maschinen, um die folgende Aufgabe zu lösen.
{% endif %} {% endraw %} {% endcall %}Du hast hoffentlich nun einen guten Überblick, wie ein Szenario gestaltet werden kann. Gerne kannst du dir auch den Quelltext dieses Szenarios anschauen. Viel Spaß beim Erstellen von Szenarien!