Allgemeines

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.

{#

Dateistruktur

example
├── meta.json
├── scenario.html
└── static
    └── insekta.png
meta.json
Enthält die Metadaten zum Szenario.
scenario.html
Enthält die Szenariobeschreibung
#}

Konventionen

Überschriften

Ü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.

Absätze

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.

Bootstrap CSS und Hervorhebungen

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 %}

Aufgaben

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.

Aufgaben

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 ohne Mehrfachnennung

Multiple-Choice-Aufgaben ohne Mehrfachnennung werden intern als single_choice bezeichnet. Folgender Quelltext fügt eine solche Aufgabe ein:

{% call code(language='html+jinja') %} {% raw %} {% 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 %} {% 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 %}

Multiple Choice mit Mehrfachnennung

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 %}

Freitextfelder

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:

{% call code(language='html+jinja') %} {% raw %} {% call task(identifier='hitchhiker', type='question') %}

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:

{% call code(language='html+jinja') %} {% raw %} {% answer(expected=['42', 'zweiundvierzig', 'forty-two']) %} {% endraw %} {% endcall %}

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.

Verdeckter Text

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.

{% endcall %} {% endraw %} {% endcall %}

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.

{% endcall %}

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.

Hinweise

Um bei Aufgaben Hilfestellungen zu geben, kann der hint-Aufruf verwendet werden. Dieser erzeugt einen Button, der beim Betätigen den Hinweis anzeigt:

{% call code(language='html+jinja') %} {% raw %} {% call hint() %}

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 %}

Mathe-Schriftsatz mit MathJax

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.

Statische Dateien und Einbinden von Bildern

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.

{% call code(language='html+jinja') %} {% raw %}

Insekta-Logo

{% endraw %} {% endcall %}

bindet das folgende Bild ein:

Insekta-Logo

Quelltexte und Syntax-Highlighting

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:

{% call code(language='html+jinja') %} {% raw %} {% call code(language='python') %} def fib(n): a, b = 0, 1 while n > 0: a, b = b, a + b n -= 1 return a {% endcall %} {% endraw %} {% endcall %}

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:

{% call code(language='html+jinja') %} {% raw %} {{ code(filename='meta.json', language='json', linestart=1, lineend=2) }} {% endraw %} {% endcall %}

Sie sind:

{{ code(filename='meta.json', language='json', linestart=1, lineend=2) }}

Einbinden von JavaScript und CSS

Das Einbinden von JavaScript und CSS läuft nicht über die scenario.html, sondern übere die meta.json. Das sieht dann wie folgt aus:

{% call code(language='json') %} { "static": { "css": ["example.css"], "js": [["blockcipher", "prp.js"], "paddingoracle.js"] }, } {% endcall %}

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.

Virtuelle Maschinen

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):

{% call code(language='html+jinja') %} {% raw %} Webseite auf der VM {% endraw %} {% endcall %}

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:

{% call code(language='html+jinja') %} {% raw %}

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:

{% call code(language='html+jinja') %} {% raw %} {% if vms %}

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 %}

Schlusswort

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!