Spielerstellung mit SpoookyJS - Ein Tutorial

In diesem Tutorial wird erläutert, wie sich eigene Spiele mit dem JavaScript-Framework SpoookyJS entwickeln lassen. Umgesetzt wird eine Schachvariante, die auf einem Spielbrett mit 5x6 Spielfeldern gespielt wird.

Download und _template-Ordner

SpoookyJS wird über GitHub bereitgestellt. Nachdem das Framework in seiner aktuellen Version als Zip-Archiv von https://github.com/janwieners/SpoookyJS/archive/master.zip heruntergeladen und extrahiert wurde, zeigt sich die folgende Ordnerstruktur:
Ordnerstruktur SpoookyJS Im Ordner games finden sich einige Spiele, die mit dem Framework erstellt wurden; ein guter Startpunkt für die Erstellung eigener Spiele ist der Vorlageordner games/_template.

Vorbereitung einer Schachvariante

In diesem Tutorial wird eine Schachvariante erstellt, die auf einem Spielbrett mit 5x6 Spielfeldern von zwei Spielern gespielt wird. Beide Spieler verfügen zu Beginn des Spieles über zehn Spielfiguren, die zu Beginn des Spieles wie folgend auf dem Spielbrett abgelegt werden: Startkonfiguration der Schachvariante Um die Schachvariante vorzubereiten, wird der Ordner games/_template kopiert und unter dem Namen chessvariant in den Ordner games eingefügt. Der Ordner chessvariant enthält nun die folgenden Dateien und Ordner: Struktur des Ordners chessvariant Im Ordner assets finden sich sowohl Vorlagegrafiken für die Spielfiguren des Schachspieles als auch Grafiken zur Darstellung von Würfelwerten. Mit den JSON-Dateien agentmemory_1.json und agentmemory_2.json wird das Verhalten der Meta Agenten und ihrer assoziierten Agentenensembles agesteuert. In der HTML-Datei index.htm werden die einzelnen Seitenbereiche realisiert und HTML-Dateien aus dem übergeordneten Ordner templates eingebunden.
Weil im Webbrowser das Sicherheitskonzept der Same-Origin-Policy greift, müssen die Spiele auf einem (lokalen oder externen) Webserver ausgeführt werden oder muss der Sicherheitsmechanismus deaktiviert werden - im Browser Firefox z.B. durch Eingabe von about:config in der Adressleiste und Änderung der security.fileuri.strict_origin_policy auf false.

Das zentrale Spielskript game.js

Zentral für die Erstellung eigener Spiele ist die JavaScript-Datei game.js im neu erstellten Ordner chessvariant. Hier werden Spielerinnen und Spieler dem Spiel hinzugefügt, wird die Spielwelt generiert, Spielfiguren erstellt und Spielregeln definiert.

Spielinitialisierung

Um die Schachvariante umzusetzen, wird die leere Skriptdatei game.js zuerst um die folgenden Anweisungen ergänzt:
// Ein neues SpoookyJS Spiel erstellen
var game = new Spoooky.Game;

// Bootstrapping-Funktion: Instanzen aller benötigten Controller generieren
game.initialize("Schachvariante");

// Beschreibung der Schachvariante
game.setDescription("Die Schachvariante wird gespielt auf einem Spielbrett " +
    "mit 5x8 Spielfeldern und einer reduzierten Anzahl von Spielfiguren. " +
    "Gespielt wird nach den klassischen Spielregeln des Schachspieles." +
    "Ausgenommen ist die Fähigkeit der Spielfiguren, zu rochieren.");
                

Spieler erstellen mit game.createPlayer

Nach der grundlegenden Initialisierung des neuen Spieles mit den Memberfunktionen initialize und setdescription werden dem Spiel anhand der Methode createPlayer ein menschlicher Spieler und eine artifizielle Spielerin hinzugefügt:
// Menschlichen Spieler erstellen
var player1 = game.createPlayer({
    name: "Jan",
    type: "HUMAN"
});

// Artifiziellen Gegner erstellen
var player2 = game.createPlayer({
    name: "Scully",
    type: "ARTIFICIAL"
});

// Startspieler festlegen: Spieler 1 (weiß) startet das Spiel
game.setPlayer(player1);

Spielbrettsetup

Das Spielbrett wird mit der Funktion setupGridWorld initialisiert. Als ersten Parameter erwartet die Funktion die Anzahl der Felder auf der X-Achse, der zweite Parameter dient dazu, die Anzahl der Felder auf der Y-Achse anzugeben und mit dem dritten Parameter wird die Darstellung der Spielfelder festgelegt. Zurückgreifen lässt sich hierbei auf CSS-Klassen, die im Stylesheet css/spoookystyle.css vordefiniert sind.
// CSS-Klassennamen (vordefiniert in css/spoookystyle.css)
// zur vereinfachten Verwendung in Variablen speichern
var b = "gridCellBlack",
    w = "gridCellWhite";

// Spielbrett mit 5x6 Spielfeldzellen erstellen
game.setupGridWorld(5, 6, [
    w, b, w, b, w,
    b, w, b, w, b,
    w, b, w, b, w,
    b, w, b, w, b,
    w, b, w, b, w,
    b, w, b, w, b
]);
Die Darstellung des Spielbrettes wird geleistet, indem
var SpoookyGame = new Spoooky.AngularWrapper({
    game : game,
    cellWidth : 100,
    cellHeight : 100 });

Spielfiguren hinzufügen

Jede Spieleseite ist mit einer entsprechenden Blaupausendatei verknüpft, so dass sich auf einen Fundus vordefinierter Spielfiguren zugreifen lässt (vgl. die JavaScript-Dateien im Ordner js/blueprints). Für die Schachvariante werden zunächst die entsprechenden Spielfigurenblaupausen mit der Funktion addBlueprint dem Spiel hinzugefügt. Anschließend werden die Aktionsmöglichkeiten der Spielfiguren und ihre Auswirkungen auf den Spielverlauf anhand der Funktion connectConsequences mit dem Spiel verknüpft:
// Entitätenblaupausen dem Spiel hinzufügen
var black_bishop = game.addBlueprint(player2,
        Spoooky.Blueprints.CHESS.entities.black_bishop),
    black_king = game.addBlueprint(player2,
        Spoooky.Blueprints.CHESS.entities.black_king),
    black_knight = game.addBlueprint(player2,
        Spoooky.Blueprints.CHESS.entities.black_knight),
    black_pawn = game.addBlueprint(player2,
        Spoooky.Blueprints.CHESS.entities.black_pawn),
    black_queen = game.addBlueprint(player2,
        Spoooky.Blueprints.CHESS.entities.black_queen),
    black_rook = game.addBlueprint(player2,
        Spoooky.Blueprints.CHESS.entities.black_rook),

    white_bishop = game.addBlueprint(player1,
        Spoooky.Blueprints.CHESS.entities.white_bishop),
    white_king = game.addBlueprint(player1,
        Spoooky.Blueprints.CHESS.entities.white_king),
    white_knight = game.addBlueprint(player1,
        Spoooky.Blueprints.CHESS.entities.white_knight),
    white_pawn = game.addBlueprint(player1,
        Spoooky.Blueprints.CHESS.entities.white_pawn),
    white_queen = game.addBlueprint(player1,
        Spoooky.Blueprints.CHESS.entities.white_queen),
    white_rook = game.addBlueprint(player1,
        Spoooky.Blueprints.CHESS.entities.white_rook);

// Entitätenziele mit Folgen für das Spiel und die Spielwelt verknüpfen
game.connectConsequences(Spoooky.Blueprints.CHESS.consequences.blackPlayer);
game.connectConsequences(Spoooky.Blueprints.CHESS.consequences.whitePlayer);
Die Memberfunktion addEntitiesToGameBoard sorgt schließlich dafür, dass die Spielfiguren auf dem Spielbrett abgelegt werden:
// Spielfiguren auf dem Spielbrett ablegen
game.addEntitiesToGameBoard([
    black_rook, black_queen, black_king, black_knight, black_bishop,
    black_pawn, black_pawn, black_pawn, black_pawn, black_pawn,
    0, 0, 0, 0, 0,
    0, 0, 0, 0, 0,
    white_pawn, white_pawn, white_pawn, white_pawn, white_pawn,
    white_rook, white_queen, white_king, white_knight, white_bishop
]);

Aufbau von Spielfigurenblaupausen

Die Eigenschaften der einzelnen Spielfiguren werden anhand von Spielfigurenblaupausen definiert, die mit der Funktion addBlueprint verarbeitet und dem Spiel hinzugefügt werden. Wie der folgende Quelltextausschnit aus dem Skript js/spoooky.Blueprints.js veranschaulicht, wird in jeder Blaupause zunächst der Typ der Spielfigur zur späteren Verwendung angegeben und wird die Darstellung bestimmt:
black_pawn : {

    typeID : "AA",
    entityType : "Black Pawn",
    associatedWithMetaAgent : null,
    representation : { type : "image",
        texture : "assets/black_pawn.png" },
Die Zugmöglichkeiten des schwarzen Bauern werden im moves-Bereich vorgesehen - für den Bauern sind die Bewegungsmöglichkeiten schnell umgesetzt. So kann sich der Bauer auf ein leeres Feld bewegen, das sich südlich von ihm befindet:
    // Zugmöglichkeiten der Spielfigur
    moves : [{
        name : "Zug in Richtung des unteren Spielfeldrandes",
        type : "Default",
        direction : "south",
        frequency : 1,
        // Zugbedingungen: Das Zielfeld ist nicht besetzt
        // und befindet sich nicht am unteren Rand des Spielbrettes
        // (hier wird der Bauer eingetauscht in eine andere Spielfigur)
        conditions : [
            { condition : "Is Empty",
                state : true },
            { condition : "Is Not The Last Row",
                state : true }]
    },
Zu Beginn des Spieles ist's dem schwarzen Bauern möglich, sich um zwei Felder nach unten zu bewegen:
    {
        name : "Startzug: Zwei Felder nach unten",
        type : "Default",
        direction : [ 0, +2 ],
        frequency : 1,
        conditions : [
            { condition : "Is Empty",
                state : true },

            // Andere Spielfiguren dürfen nicht übersprungen werden.
            // Bedingung: Leeres Feld südlich des Bauern
            { condition : "Is Empty At",
                relativeCoordinate : [ 0, +1 ],
                state : true },
            { condition : "yPosition",
                value : 1, state : true }
        ]
    }],
Um festzulegen, dass sich der schwarze Bauer nicht bewegen darf, wenn der eigene König nach dem Zug des Bauern von einer gegnerischen Spielfigur bedroht wird, werden die Zugmöglichkeiten des schwarzen Bauern an die entsprechende Bedingung geknüpft:
    // Zugbedingung: Prüfen, ob der eigene König nach ausgeführter
    // Zugmöglichkeit des Bauern angreifbar ist
    postMoveCheck : [{
        condition : "Entity Is Attackable After Move",
        state : false, entity : "Black King" }],

Spielfigurenziele

Nachdem die Zugmöglichkeiten des Bauern formuliert wurden, werden die Ziele der Spielfigur angegeben: Der Bauer verfolgt das Ziel, gegnerische Sielfiguren zu schlagen und die unterste Reihe des Spielbrettes zu erreichen. Spielfigurenziele setzen sich aus Zielatomen (goalAtoms) und zusammengesetzten Zielen (goals) zusammen. Mit dem folgenden Quelltext werden zuerst die einzelnen Bestandteile der Spielfigurenziele definiert - Bestandteile wie die Abfrage nach gegnerischen Spielfiguren oder das Erreichen der untersten Reihe des Spielbrettes:
    // Definition von Unterzielen, die anschließend
    // zu Zielen der Spielfigur zusammengesetzt werden
    goalAtoms : [{
        atomName : "Gegner auf suedoestlich angrenzendem Feld",
        atomFunction : "Is Opponent",
        atomArguments : "southeast"
    },{
        atomName : "Gegner auf suedwestlich angrenzendem Feld",
        atomFunction : "Is Opponent",
        atomArguments : "southwest"
    }, {
        atomName : "Leeres Feld Suedlich",
        atomFunction : "Is Empty Cell",
        atomArguments : "south"
    }, {
        atomName : "Figur steht auf einem Spielfeld in Zeile vier",
        atomFunction : "Current Y Position Is",
        atomArguments : 4
    },{
        atomName : "Weißer Bauer westlich",
        atomFunction : "Entity At Cell Is Of Type",
        atomArguments : [ -1, 0, "White Pawn" ]
    },{
        atomName : "Weißer Bauer oestlich",
        atomFunction : "Entity At Cell Is Of Type",
        atomArguments : [ +1, 0, "White Pawn" ]
    },{
        atomName : "Weißer Bauer westlich wurde nur einmal bewegt",
        atomFunction : "Entity At Cell Has Been Moved n Times",
        atomArguments : [ -1, 0, 1 ]
    },{
        atomName : "Weißer Bauer oestlich wurde nur einmal bewegt",
        atomFunction : "Entity At Cell Has Been Moved n Times",
        atomArguments : [ +1, 0, 1 ]
    },{
        atomName : "Weißer Bauer westlich wurde zuletzt bewegt",
        atomFunction : "Entity At Cell Has Been Moved In Last Game Round",
        atomArguments : [ -1, 0 ]
    },{
        atomName : "Weißer Bauer oestlich wurde zuletzt bewegt",
        atomFunction : "Entity At Cell Has Been Moved In Last Game Round",
        atomArguments : [ +1, 0 ]
    }, {
        atomName : "Spielfigur kann die unterste Reihe erreichen",
        atomFunction : "Entity Is Able To Reach A Specific Row",
        atomArguments : [ "last", "south" ]
    }],
Abschließend werden die Zielatome zu Spielfigurenzielen zusammengesetzt:
    // Zielatome zu Spielsteinzielen zusammensetzen
    goals : [{
        type     : "CAPTURE",
        name     : "Schlage Spielfigur auf Feld suedost",
        atoms    : ["Gegner auf suedoestlich angrenzendem Feld"],
        move     : "southeast"
    },{
        type     : "CAPTURE",
        name     : "Schlage Spielfigur auf Feld suedwest",
        atoms    : ["Gegner auf suedwestlich angrenzendem Feld"],
        move     : [ -1, +1 ]
    },{
        type     : "CAPTURE",
        name     : "Schlage Gegner en passant suedwestlich",
        atoms    : ["Figur steht auf einem Spielfeld in Zeile vier",
            "Weißer Bauer westlich",
            "Weißer Bauer westlich wurde nur einmal bewegt",
            "Weißer Bauer westlich wurde zuletzt bewegt"],
        move     : "southwest"
    },{
        type     : "CAPTURE",
        name     : "Schlage Gegner en passant suedoestlich",
        atoms    : ["Figur steht auf einem Spielfeld in Zeile vier",
            "Weißer Bauer oestlich",
            "Weißer Bauer oestlich wurde nur einmal bewegt",
            "Weißer Bauer oestlich wurde zuletzt bewegt"],
        move     : "southeast"
    }, {
        type     : "GOALMOVE",
        name     : "Erreiche die letzte Reihe des Spielbrettes",
        atoms    : ["Leeres Feld Suedlich",
            "Spielfigur kann die unterste Reihe erreichen"],
        move     : "south"
    }]
}
Im consequences-Bereich der Spielfigurenblaupausen werden die Folgen ausgeführter Spielfigurenziele bestimmt, um anhand der Funktion connectConsequences mit dem Spiel verknüpft zu werden. Folgen, die sich mit Erreichen der letzten Reihe des Spielbrettes verbinden:
black_pawn_reach_last_row_south : {
    goalName     : "Erreiche die letzte Reihe des Spielbrettes",
    entityType : "Black Pawn",
    consequences : [
        {
            jobName: "Markiere das Zielfeld",
            jobFunction: "Highlight Cell",
            jobArguments: [ 0, +1, "move_goal",
                "RELATIVE", "Black Pawn" ],
            execute: "immediately"
        }, {
            jobName: "Entferne gegnerischen Spielstein",
            jobFunction: "Move Entity Relative To",
            jobArguments: [ 0, +1, "Black Pawn", "captureMove" ]
        }, {
            jobName: "Transformiere Bauer in Koenigin",
            jobFunction: "Transform Entity",
            jobArguments: "Black Queen"
        }]
},
Was geschieht, wenn der schwarze Bauer eine gegnerische Spielfigur schlägt, ist mit den folgenden Anweisungen definiert:
black_pawn_capture_opponent_southwest : {
    goalName     : "Schlage Spielfigur auf Feld suedwest",
    entityType : "Black Pawn",
    consequences : [
        {
            jobName: "Markiere das Zielfeld",
            jobFunction: "Highlight Cell",
            jobArguments: [ -1, +1, "move_goal",
                "RELATIVE", "Black Pawn" ],
            execute: "immediately"
        },
        {
            jobName: "Entferne gegnerischen Spielstein",
            jobFunction: "Capture Opponent At",
            jobArguments: [ -1, +1, "RELATIVE",
                "Black Pawn" ]
        },
        {
            jobName: "Bewege Spielfigur",
            jobFunction: "Move Entity Relative To",
            jobArguments: [ -1, +1, "Black Pawn",
                "captureMove" ]
        }, {
            jobName: "Transformiere Spielfigur, wenn letzte Reihe erreicht",
            jobFunction: "Transform Entity If Row Reached",
            jobArguments: { row : "last",
                entityType : "Black Queen" }
        }]
},

black_pawn_capture_opponent_southwest_enpassant : {
    goalName     : "Schlage Gegner en passant suedwestlich",
    entityType : "Black Pawn",
    consequences : [
        {
            jobName: "Markiere das Zielfeld",
            jobFunction: "Highlight Cell",
            jobArguments: [ -1, +1, "move_goal",
                "RELATIVE", "Black Pawn" ],
            execute: "immediately"
        },
        {
            jobName: "Entferne gegnerischen Spielstein",
            jobFunction: "Capture Opponent At",
            jobArguments: [ -1, 0, "RELATIVE",
                "Black Pawn" ]
        },
        {
            jobName: "Bewege Spielfigur",
            jobFunction: "Move Entity Relative To",
            jobArguments: [ -1, +1, "Black Pawn",
                "captureMove" ]
        }]
},

black_pawn_capture_opponent_southeast : {
    goalName     : "Schlage Spielfigur auf Feld suedost",
    entityType : "Black Pawn",
    consequences : [
        {
            jobName: "Highlight Target Cell",
            jobFunction: "Highlight Cell",
            jobArguments: [ +1, +1, "move_goal", "RELATIVE", "Black Pawn" ],
            execute: "immediately"
        },
        {
            jobName: "Delete Opponent Entity",
            jobFunction: "Capture Opponent At",
            jobArguments: [ +1, +1, "RELATIVE", "Black Pawn" ]
        },
        {
            jobName: "Move Game Entity",
            jobFunction: "Move Entity Relative To",
            jobArguments: [ +1, +1, "Black Pawn", "captureMove" ]
        }, {
            jobName: "Transform Entity If It Has Reached The First Row",
            jobFunction: "Transform Entity If Row Reached",
            jobArguments: { row : "last", entityType : "Black Queen" }
        }]
},

black_pawn_capture_opponent_southeast_enpassant : {
    goalName     : "Schlage Gegner en passant suedoestlich",
    entityType : "Black Pawn",
    consequences : [
        {
            jobName: "Highlight Target Cell",
            jobFunction: "Highlight Cell",
            jobArguments: [ +1, +1, "move_goal", "RELATIVE", "White Pawn" ],
            execute: "immediately"
        },
        {
            jobName: "Delete Opponent Entity",
            jobFunction: "Capture Opponent At",
            jobArguments: [ +1, 0, "RELATIVE", "White Pawn" ]
        },
        {
            jobName: "Move Game Entity",
            jobFunction: "Move Entity Relative To",
            jobArguments: [ +1, +1, "White Pawn", "captureMove" ]
        }]
},

Spielregeln definieren

Die Definition von Spielregeln verläuft dreischrittig: Mit der Funktion addGameRuleAtom werden zunächst Spielregelatome definiert, mit denen sich bestimmte Zustände des Spieles abfragen lassen, wie sie in Spoooky.Game.gameRuleAtoms definiert sind:
// ***** Spielregeln des Schachspieles *****
// *** Unentschieden: König gegen König ***
game.addGameRuleAtom({
    atomName : "Spieler 1 hat nur noch eine Spielfigur",
    atomFunction : "Player Has Number Of Entities",
    atomArguments : [ player1.getID(), 1 ]
});

game.addGameRuleAtom({
    atomName : "Spielerin 2 hat nur noch eine Spielfigur",
    atomFunction : "Player Has Number Of Entities",
    atomArguments : [ player2.getID(), 1 ]
});
Anschließend wird die Spielregel "Unentschieden, wenn sich nur noch die Könige der beiden Spieler auf dem Spielbrett befinden" durch Verknüpfung der beiden Spielregelatome "Spieler 1 hat nur noch eine Spielfigur" und "Spielerin 2 hat nur noch eine Spielfigur" definiert:
game.assembleGameRule({
    name     : "Unentschieden: König gegen König",
    atoms    : ["Spieler 1 hat nur noch eine Spielfigur",
        "Spielerin 2 hat nur noch eine Spielfigur"]
});
...und durch Funktion connectGameRuleConsequences mit Folgen für die Spielwelt und den Spielverlauf verknüpft:
game.connectGameRuleConsequences({
    ruleName     : "Unentschieden: König gegen König",
    consequences : [{
        jobName: "Spiel anhalten",
        jobFunction: "Stop Game"
    }, {
        jobName: "Unentschieden-Nachricht ausgeben",
        jobFunction: "Print Game Process",
        jobArguments: "Unentschieden."
    }]});

Spielregel "Der König von Spieler 1 steht im Schach"

Steht der König von Spieler 1 im Schach, so wird eine entsprechende Nachricht im Spielverlauf ausgegeben, der aktuelle Spieler gewechselt und der Spielzustand auf "SCHACH" bzw. "CHECK" gesetzt (der Quell:
game.addGameRuleAtom({
    atomName : "König von Spieler 1 steht im Schach",
    atomFunction : "Entity Is Under Attack",
    atomArguments : white_king
});

game.assembleGameRule({
    name     : "Schach (Spieler 1)",
    atoms    : ["König von Spieler 1 steht im Schach"]
});

game.connectGameRuleConsequences({
    ruleName     : "Schach (Spieler 1)",
    consequences : [{
        jobName: "Ausgabe, dass der König von Spieler 1 im Schach steht",
        jobFunction: "Print Game Process",
        jobArguments: "Der König von Spieler 1 steht im Schach."
    },{
        jobName: "Spielerwechsel",
        jobFunction: "Next Player"
    },{
        jobName: "Spielzustand auf CHECK setzen",
        jobFunction: "Set Game State",
        jobArguments: "CHECK"
    }]});

Spielregel "Spieler 1 ist Schachmatt gesetzt"

Wurdee der König von Spieler 1 Schachmatt gesetzt, wird das Spiel beendet und eine entsprechende Nachricht im Spielverlaufsdialog ausgegeben:
game.addGameRuleAtom({
    atomName : "Keine Spielfigur von Spieler 1 kann dem eigenen König helfen.",
    atomFunction : "Got No Protecting Entities",
    atomArguments : white_king
});

game.addGameRuleAtom({
    atomName : "Das Spiel ist im Zustand CHECK",
    atomFunction : "Game State Is",
    atomArguments : "CHECK"
});
game.assembleGameRule({
    name     : "Schachmatt (Spieler 1)",
    atoms    : ["König von Spieler 1 steht im Schach",
        "Keine Spielfigur von Spieler 1 kann dem eigenen König helfen.",
        "Das Spiel ist im Zustand CHECK"]
});
game.connectGameRuleConsequences({
    ruleName     : "Schachmatt (Spieler 1)",
    consequences : [{
        jobName: "Spiel beenden",
        jobFunction: "Stop Game"
    }, {
        jobName: "Ausgabe im Spielverlaufsdialog, dass Spieler 1 das Spiel verloren hat.",
        jobFunction: "Print Game Process",
        jobArguments: "Spieler 1 ist schachmatt."
    },{
        jobName: "Ausgabe im Spielverlaufsdialog, dass Spielerin 2 das Spiel gewonnen hat.",
        jobFunction: "Print Game Process",
        jobArguments: "Spieler 2 gewinnt das Spiel."
    },{
        jobName: "ID der Spielerin speichern, die das Spiel gewonnen hat.",
        jobFunction: "Set Winner",
        jobArguments: player2.getID()
    }]});

Spielregel "Unentschieden (Stalemate)"

Verfügt keine Spielfigur von Spieler 1 über die Möglichkeit, sich zu bewegen, endet das Spiel unentschieden:
game.addGameRuleAtom({
    atomName : "Keine Spielfigur von Spieler 1 kann sich bewegen.",
    atomFunction : "Player Has No Movable Entities",
    atomArguments : player1.getID()
});

game.addGameRuleAtom({
    atomName : "Das Spiel ist im Zustand INGAME",
    atomFunction : "Game State Is",
    atomArguments : "INGAME"
});

game.assembleGameRule({
    name     : "Patt (Spieler 1)",
    atoms    : ["Keine Spielfigur von Spieler 1 kann sich bewegen.",
        "Das Spiel ist im Zustand INGAME"]
});

game.connectGameRuleConsequences({
    ruleName     : "Patt (Spieler 1)",
    consequences : [{
        jobName: "Spiel beenden",
        jobFunction: "Stop Game"
    }, {
        jobName: "Ausgabe im Spielverlaufsdialog, dass Spieler 1 " +
            "ueber keine gueltige Zugmoeglichkeit verfuegt.",
        jobFunction: "Print Game Process",
        jobArguments: "Patt: Spieler 1 verfuegt ueber keine gueltige Zugmoeglichkeit."
    }]});