%erstmals modularisiert.
%hier die module für die einzelnen level einbinden.
% als nächstes: spielbare versionen erstellen von:
% 1,3,4,6,8,15,17,18,20,21,22,23.

:-consult(zeitreisegraphikv16a).

%                           TIMEBALLS

% ERSTE SCHRITTE RICHTUNG INTERFACE. FUNKTIONIERT NOCH NICHT GANZ.
% NÄCHSTER SCHRITT: VERBINDUNGEN VON SCHALTERN ZU OBJEKTEN SOLLTEN AUCH
% IN DER EINGABE SICHTBAR SEIN.
% ZUR EINGABE FÜR DEN SPIELER AUTOMATISCHER FORTSCHRITT DURCH DIE LEVEL
% SOUND/MUSIK MEHR LEVEL BESSERE DARSTELLUNG VON ZEITMASCHINEN, START-
% UND ZIELZEIT?

%WARNUNG: ZU TESTZWECKEN SIND IM PRÄDIKAT 'ZEIGE_VERLAUF' ZWEI ZEILEN
%AUSKOMMENTIERT. ZEIGE_BILD WIRD ERST FUNKTIONIEREN, WENN DIE WIEDER
%AKTIVIERT (ALSO DIE %-ZEICHEN DAVOR ENTFERNT) WERDEN.
% DAS GENERISCHE TESTPRÄDIKAT IST SHOWTEST12(X,Y,Z), WOBEI X DIE
% KENNZIFFER DES TESTFALLES UND Y DIE ZEITSCHRANKE, Z DIE ERFOLGSANZEIGE
% IST. Hier: Erste interaktive Elemente in der Graphiksteuerung.
% Fehlermeldungen müssen noch in die Graphikdarstellung integriert
% werden.

%Objekte:

%Zeitmaschine: [zm,Id,Startzeit,Zielzeit,Pos,Ball]

%Schalter: [sw,Id,Pos,Zustand,Bezugsobjektliste]
%Bezugsobjektliste enthält die Gegenstände, die von dem Schalter bedient werden.

%Gitter: [gr,Id,Pos,Zustand,Schalterliste]
%Zustand:offen/geschlossen; die Schalterliste enthält die Ids der Schalter,
%die das gr bedienen.

%Barriere: [barr,Id,Pos,Zustand,Schalterliste]
%Alles wie beim Gitter. Eine Barriere ist wie eine variable Wand. Jedesmal,
%wenn ein Schalter aus der Liste bedient wird, wird der Zustand alterniert.

%Dynamischer Winkel: [dwi,Id,Pos,Ausrichtung,Zustand,Schalterliste]
%Wie ein statischer Winkel, aber durch Schalter im Zustand modifizierbar.

%Zielfeld: [goal,Id,Pos,Zustand,Ballliste,Schalterliste]
%Die Balliste enthält die Bälle, die das Zielfeld erreicht haben. Daran bemißt
% sich der Erfolg. Zielfeld kann von Gitter bedeckt sein. Das wird von
% Elementen der Schalterliste bedient. Es hat einen Zustand (auf oder
% zu)

%Wand: [wa,Pos]

%Loch: [loch
%,Id,Pos, Zustand, Ballliste, Schalterliste]
%Ein Loch ist eine Art Falle. Bälle, die in ein Loch fallen, verschwinden aus dem
% Spiel. Ein Loch kann von einem Gitter bedeckt sein. Dieses wird von
% den Schaltern aus der Schalterliste bedient und ist entweder auf oder
% zu

%Winkel: [wi,Id,Pos,Ausrichtung]
%Ausrichtung: die Schräge kann nach (links/rechts)-(oben/unten) weisen.

%Ball: [ball,Id,Pos,Richtung]

%Feldzustand: Eine Liste von Objektlisten.
% [Bälle,Schalter,GitterUndBarrieren,WändeundWinkel,LöcherUndZiel,Zeitmaschinen]
% Im Prinzip muss auf Konsistenz geachtet werden - die meisten
% Gegenstände schließen einander an einer Position aus - aber das
% erreichen wir, indem wir Verlauf und Anfangszustände entsprechend
% konstruieren einfacher.

%Dynamischer Feldzustand: Zur Abarbeitung des Verlaufes. Enthält den aktuellen
%Feldzustand, das noch nicht bearbeitete Endstück, sowie eine Liste der betätigten
%Schalter.

% Zusätzlich speichern wir die zuletzt vollzogene zeitreise in der Id
% eines Objektes. Das erleichtert hinterher erheblich die Prüfung auf
% virtuelle Objekte.

%switch_list entfernt. Wurde nicht benötigt.

%next_state([I,Zustand1],[I+1,Zustand2]) berechnet zu einem Zustand den jeweils folgenden.

%next_state_balls berechnet die Ballliste für den nächsten Zustand.

%next_field(Ko,Ri,Koneu) berechnet zu den derzeitigen Koordinaten Ko
% und der Richtung Ri (rechts, links, oben, unten) die neuen Koordinaten
% Koneu, falls der Ball frei rollt.

next_field([K1,K2],rechts,[K1neu,K2]):-K1neu is K1+1.
next_field([K1,K2],links,[K1neu,K2]):-K1neu is K1-1.
next_field([K1,K2],oben,[K1,K2neu]):-K2neu is K2+1.
next_field([K1,K2],unten,[K1,K2neu]):-K2neu is K2-1.

% Bälle und Zeitmaschinen:
% [Bälle,Schalter,GitterUndBarrieren,WändeundWinkel,LöcherUndZiel,Zm]
% [zm,Id,Startzeit,Zielzeit,Pos,Balldaten]

% Tritt eine Paradoxie auf, werden keine weiteren Zustände berechnet,
% nur das "fail" wird weiter vererbt.
next_state_balls([_,[fail,_,_,_,_,_]],fail).

%Paradoxieroutine für Bälle, die sich nach rechts bewegen:
next_state_balls([I,[Balls,_,_,_,_,Zm]],fail):-
	J is I+1,
	member([zm,_,J,_,[Ko1,Ko2],[ball,Id,[Ko1,Ko2],rechts]],Zm),
	N is Ko1-1,
	not(member([ball,[Id,_],[N,Ko2],rechts],Balls)),
	not(member([ball,Id,[N,Ko2],rechts],Balls)).

%Paradoxieroutine für Bälle, die sich nach links bewegen:
next_state_balls([I,[Balls,_,_,_,_,Zm]],fail):-
	J is I+1,
	member([zm,_,J,_,[Ko1,Ko2],[ball,Id,[Ko1,Ko2],links]],Zm),
	N is Ko1+1,
	not(member([ball,[Id,_],[N,Ko2],links],Balls)),
	not(member([ball,Id,[N,Ko2],links],Balls)).

%Paradoxieroutine für Bälle, die sich nach oben bewegen:
next_state_balls([I,[Balls,_,_,_,_,Zm]],fail):-
	J is I+1,
	member([zm,_,J,_,[Ko1,Ko2],[ball,Id,[Ko1,Ko2],oben]],Zm),
	N is Ko2-1,
	not(member([ball,[Id,_],[Ko1,N],oben],Balls)),
	not(member([ball,Id,[Ko1,N],oben],Balls)).

%Paradoxieroutine für Bälle, die sich nach unten bewegen:
next_state_balls([I,[Balls,_,_,_,_,Zm]],fail):-
	J is I+1,
	member([zm,_,J,_,[Ko1,Ko2],[ball,Id,[Ko1,Ko2],unten]],Zm),
	N is Ko2+1,
	not(member([ball,[Id,_],[Ko1,N],unten],Balls)),
	not(member([ball,Id,[Ko1,N],unten],Balls)).
% Die Paradoxieroutine koennte noch melden, was genau schief gegangen
% ist - also Reise- und Balldaten.


% Ist eine Maschine entsprechend
% eingestellt, muss der reisende Ball zur Zielzeit auftauchen. Da wir
% jetzt der Id des Balles die der Zeitmaschine, aus der erkommt,
% hinzufügen, steht das hier entsprechend: Die neue Id ist also
% [Idball,A]. Die Zeitmaschine ist gleichwohl auf [ball,Idball,Ko,Ri]
% eingestellt - der Spieler muss also nicht wissen, woher der Ball kam.

next_state_balls([I,[Ba,Sw,Barr,Wa,Lo,Zm]],[[ball,[Idball,A],Ko,Ri]|BallRest]):-
	member([zm,A,B,I,C,[ball,Idball,Ko,Ri]],Zm),
	delete_from([zm,A,B,I,C,[ball,Idball,Ko,Ri]],Zm,Rest),
	next_state_balls([I,[Ba,Sw,Barr,Wa,Lo,Rest]],BallRest).

% Ist die Maschine entsprechend eingestellt, muss der reisende Ball
% zur Startzeit da sein und verschwinden, sonst entsteht ein Paradoxon.
% Auch hier müssen wir nun die neue Syntax beachten: Statt Idball mag
% die Ballid [Idball,_] sein. Diese modifizierte BallId nennen wir
% ModBallId.

% Im folgenden sind die Verschwindebedingungen nicht einheitlich: die
% ersten beiden benutzen zwei Klauseln, die anderen den ModBallId-Trick.
% Das könnte man später bereinigen, beides funktioniert offenbar.

%Verschwindebedingung für Bälle, die sich nach rechts bewegen:

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],Sw,Barr,Wa,Lo,Zm]],Rest1):-
	N is Ko1+1,
	J is I+1,
	member([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],rechts]],Zm),
	delete_from([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],rechts]],Zm,Zm1),
	next_state_balls([I,[Rest,Sw,Barr,Wa,Lo,Zm1]],Rest1).

next_state_balls([I,[[[ball,[Id,_],[Ko1,Ko2],rechts]|Rest],Sw,Barr,Wa,Lo,Zm]],Rest1):-
	N is Ko1+1,
	J is I+1,
	member([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],rechts]],Zm),
	delete_from([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],rechts]],Zm,Zm1),
	next_state_balls([I,[Rest,Sw,Barr,Wa,Lo,Zm1]],Rest1).

%Verschwindebedingung für Bälle, die sich nach links bewegen:
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],Sw,Barr,Wa,Lo,Zm]],Rest1):-
	N is Ko1-1,
	J is I+1,
	member([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],links]],Zm),
	delete_from([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],links]],Zm,Zm1),
	next_state_balls([I,[Rest,Sw,Barr,Wa,Lo,Zm1]],Rest1).

next_state_balls([I,[[[ball,[Id,_],[Ko1,Ko2],links]|Rest],Sw,Barr,Wa,Lo,Zm]],Rest1):-
	N is Ko1-1,
	J is I+1,
	member([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],links]],Zm),
	delete_from([zm,_,J,_,[N,Ko2],[ball,Id,[N,Ko2],links]],Zm,Zm1),
	next_state_balls([I,[Rest,Sw,Barr,Wa,Lo,Zm1]],Rest1).

%Verschwindebedingung für Bälle, die sich nach oben bewegen:
next_state_balls([I,[[[ball,ModBallId,[Ko1,Ko2],oben]|Rest],Sw,Barr,Wa,Lo,Zm]],Rest1):-
	N is Ko2+1,
	J is I+1,
	member(ModBallId,[Id,[Id,_]]),
	member([zm,_,J,_,[Ko1,N],[ball,Id,[Ko1,N],oben]],Zm),
	delete_from([zm,_,J,_,[Ko1,N],[ball,Id,[Ko1,N],oben]],Zm,Zm1),
	next_state_balls([I,[Rest,Sw,Barr,Wa,Lo,Zm1]],Rest1).

%Verschwindebedingung für Bälle, die sich nach unten bewegen:
next_state_balls([I,[[[ball,ModBallId,[Ko1,Ko2],unten]|Rest],Sw,Barr,Wa,Lo,Zm]],Rest1):-
	N is Ko2-1,
	J is I+1,
	member(ModBallId,[Id,[Id,_]]),
	member([zm,_,J,_,[Ko1,N],[ball,Id,[Ko1,N],unten]],Zm),
	delete_from([zm,_,J,_,[Ko1,N],[ball,Id,[Ko1,N],unten]],Zm,Zm1),
	next_state_balls([I,[Rest,Sw,Barr,Wa,Lo,Zm1]],Rest1).


%Ziel und Löcher:
%[loch,Id,Pos, Zustand, Ballliste, Schalterliste]
%[goal,Id,Pos,Zustand,Ballliste,Schalterliste]

% Fällt ein Ball in ein offenes Loch oder ins Ziel, ist er weg. (Dass er
% in der Ballliste des Loches/Zieles auftaucht, wird beim Loch/Ziel
% geregelt.)
% Im folgenden allgemein: Gr->Dyn und Barr->Stat. Das ist die
% kanonischere Bezeichnung: Erst dynamische, dann statische Objekte.
% Diese Bedingung wurde deutlich vereinfacht, in früheren Versionen war
% sie antizipierend.

next_state_balls([I,[[[ball,_,Pos,_]|Rest],Sw,Dyn,Stat,Lo,Zm]],Rest1):-
	next_state_balls([I,[Rest,Sw,Dyn,Stat,Lo,Zm]],Rest1),
        member([_,_,Pos,auf,_,_],Lo).

%Wände:
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,Wa,C,D]],[[ball,Id,[Ko1,Ko2],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,Wa,C,D]],Rest1),
	N is Ko1+1,
	member([wa,[N,Ko2]],Wa).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,Wa,C,D]],[[ball,Id,[Ko1,Ko2],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,Wa,C,D]],Rest1),
	N is Ko1-1,
	member([wa,[N,Ko2]],Wa).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,Wa,C,D]],[[ball,Id,[Ko1,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,Wa,C,D]],Rest1),
	N is Ko2+1,
	member([wa,[Ko1,N]],Wa).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,Wa,C,D]],[[ball,Id,[Ko1,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,Wa,C,D]],Rest1),
	N is Ko2-1,
	member([wa,[Ko1,N]],Wa).


%Dynamische Barrieren:
%[barr,Id,Pos,Zustand,Schalterliste]
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,Barr,B,C,D]],[[ball,Id,[Ko1,Ko2],links]|Rest1]):-
	next_state_balls([I,[Rest,A,Barr,B,C,D]],Rest1),
	N is Ko1+1,
	member([barr,_,[N,Ko2],zu,_],Barr).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,Barr,B,C,D]],[[ball,Id,[Ko1,Ko2],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,Barr,B,C,D]],Rest1),
	N is Ko1-1,
	member([barr,_,[N,Ko2],zu,_],Barr).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,Barr,B,C,D]],[[ball,Id,[Ko1,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,Barr,B,C,D]],Rest1),
	N is Ko2+1,
	member([barr,_,[Ko1,N],zu,_],Barr).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,Barr,B,C,D]],[[ball,Id,[Ko1,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,Barr,B,C,D]],Rest1),
	N is Ko2-1,
	member([barr,_,[Ko1,N],zu,_],Barr).


%Winkel und dynamische Winkel
% Viele Fälle, je nach Orientierung: lu,lo,ru,ro. Je nachdem wird er
% reflektiert oder um 90° reflektiert. Dynamische Winkel müssen zu sein,
% um zu wirken.
%
%[wi,Id,Pos,Ausrichtung]
%[dwi,Id,Pos,Ausrichtung,Zustand,Schalterliste]
% [Bälle,Schalter,GitterUndBarrierenUndDynWi,WändeundWinkel,LöcherUndZiel,Zm]
%

%Winkel:
%Reflektion, falls Ball auf eine gerade Seite des Winkels stößt:
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1+1,
	member([wi,_,[N,Ko2],X],C),
	member(X,[ru,ro]).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1-1,
	member([wi,_,[N,Ko2],X],C),
	member(X,[lu,lo]).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2+1,
	member([wi,_,[Ko1,N],X],C),
	member(X,[ro,lo]).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2-1,
	member([wi,_,[Ko1,N],X],C),
	member(X,[ru,lu]).

%Umlenkung, falls Ball auf eine Schrägseite des Winkels trifft:
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1+1,
	member([wi,_,[N,Ko2],lo],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1+1,
	member([wi,_,[N,Ko2],lu],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1-1,
	member([wi,_,[N,Ko2],ro],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1-1,
	member([wi,_,[N,Ko2],ru],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2+1,
	member([wi,_,[Ko1,N],ru],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2+1,
	member([wi,_,[Ko1,N],lu],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2-1,
	member([wi,_,[Ko1,N],ro],C).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2-1,
	member([wi,_,[Ko1,N],lo],C).


%Dynamische Winkel
%[dwi,Id,Pos,Ausrichtung,Zustand,Schalterliste]
%[Bälle,Schalter,GitterUndBarrierenUndDynWi,WändeundWinkel,LöcherUndZiel,Zm]

%Reflektion, falls Ball auf eine gerade Seite des Winkels stößt:
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1+1,
	member([dwi,_,[N,Ko2],X,zu,_],B),
	member(X,[ru,ro]).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1-1,
	member([dwi,_,[N,Ko2],X,zu,_],B),
	member(X,[lu,lo]).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2+1,
	member([dwi,_,[Ko1,N],X,zu,_],B),
	member(X,[ro,lo]).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2-1,
	member([dwi,_,[Ko1,N],X,zu,_],B),
	member(X,[ru,lu]).

%Umlenkung, falls Ball auf eine Schrägseite des Winkels trifft:
next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1+1,
	member([dwi,_,[N,Ko2],lo,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1+1,
	member([dwi,_,[N,Ko2],lu,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],oben]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1-1,
	member([dwi,_,[N,Ko2],ro,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],A,B,C,D,E]],[[ball,Id,[N,Ko2],unten]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko1-1,
	member([dwi,_,[N,Ko2],ru,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2+1,
	member([dwi,_,[Ko1,N],ru,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2+1,
	member([dwi,_,[Ko1,N],lu,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],rechts]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2-1,
	member([dwi,_,[Ko1,N],ro,zu,_],B).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],A,B,C,D,E]],[[ball,Id,[Ko1,N],links]|Rest1]):-
	next_state_balls([I,[Rest,A,B,C,D,E]],Rest1),
	N is Ko2-1,
	member([dwi,_,[Ko1,N],lo,zu,_],B).


%Bälle und Bälle
%Bedenke: Immer nur zwei Dinge dürfen zur Zeit kollidieren!
%Bei Kollision zweier Bälle tauschen sie ihre Richtung.
% Etwas nervig hier ist, dass beide Bälle zugleich behandelt werden
% müssen. Dazu durchsuchen wir die Ballliste nach einem geeigneten
% Kollisionspartner für den ersten Eintrag. Ein Kollisionspartner
% ist dabei ein Ball, der im nächsten Schritt auf dem gleichen
% Feld sein wird. Finden wir einen, ist der nächste Zustand, dass beide
% sich auf dem Kollisionsfeld befinden, aber mit geänderten
% Richtungen. Generell: Kollidieren Bälle B1 und B2 mit Richtungen R1
% und R2, sind die Richtungen anschließend vertauscht. Das ist nicht
% sehr physikalisch und könnte später verbessert werden. Evtl. kann man
% ja auch diagonale Bewegungsrichtungen einführen. [K1,K2] ist das
% Kollisionsfeld
%DIESE KLAUSEL FUNKTIONIERT NUR BEI BÄLLEN MIT GERADEM ABSTAND!
%SIE MUSS DAHER ERGÄNZT WERDEN UM DEN FALL, DASS ZWEI BÄLLE DIE
%PLÄTZE GETAUSCHT HABEN!

next_state_balls([I,[[[ball,Id1,[Ko1alt,Ko2alt],Ri1]|BallRest],Sw,Gr,Wa,Go,Zm]],
		 [[ball,Id1,[K1,K2],Ri2]|[[ball,Id2,[K1,K2],Ri1]|Rest]]):-
		 next_field([Ko1alt,Ko2alt],Ri1,[K1,K2]),
		 member([ball,Id2,[Ko21alt,Ko22alt],Ri2],BallRest),
		 next_field([Ko21alt,Ko22alt],Ri2,[K1,K2]),
		 delete_from([ball,Id2,_,_],BallRest,BallRest2),
		 next_state_balls([I,[BallRest2,Sw,Gr,Wa,Go,Zm]],Rest).

next_state_balls([I,[[[ball,Id1,[Ko11,Ko12],Ri1]|BallRest],Sw,Dyn,Stat,Lo,Zm]],
		 [[ball,Id1,[Ko11,Ko12],Ri2]|[[ball,Id2,[Ko21,Ko22],Ri1]|Rest]]):-
	next_field([Ko11,Ko12],Ri1,[Ko21,Ko22]),
	member([ball,Id2,[Ko21,Ko22],Ri2],BallRest),
	next_field([Ko21,Ko22],Ri2,[Ko11,Ko12]),
	delete_from([ball,Id2,_,_],BallRest,BallRest2),
	next_state_balls([I,[BallRest2,Sw,Dyn,Stat,Lo,Zm]],Rest).

%next_state-Regeln für Bälle, falls sonst nichts passiert: Falls nichts im Weg ist, rollen sie einfach weiter.
% [Bälle,Schalter,GitterUndBarrieren,WändeundWinkel,LöcherUndZiel,Zeitmaschinen]
% Sind keine Bälle da, ändert sich daran nichts (Zeitmaschinen werden ja
% weiter oben behandelt.)

next_state_balls([_,[[],_,_,_,_,_]],[]).


next_state_balls([I,[[[ball,Id,[Ko1,Ko2],rechts]|Rest],Sw,Gr,Wa,Go,Zm]],[[ball,Id,[Ko1neu,Ko2],rechts]|Rest1]):-
	Ko1neu is Ko1+1,
next_state_balls([I,[Rest,Sw,Gr,Wa,Go,Zm]],Rest1).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],links]|Rest],Sw,Gr,Wa,Go,Zm]],[[ball,Id,[Ko1neu,Ko2],links]|Rest1]):-
	Ko1neu is Ko1-1,
next_state_balls([I,[Rest,Sw,Gr,Wa,Go,Zm]],Rest1).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],oben]|Rest],Sw,Gr,Wa,Go,Zm]],[[ball,Id,[Ko1,Ko2neu],oben]|Rest1]):-
	Ko2neu is Ko2+1,
next_state_balls([I,[Rest,Sw,Gr,Wa,Go,Zm]],Rest1).

next_state_balls([I,[[[ball,Id,[Ko1,Ko2],unten]|Rest],Sw,Gr,Wa,Go,Zm]],[[ball,Id,[Ko1,Ko2neu],unten]|Rest1]):-
	Ko2neu is Ko2-1,
next_state_balls([I,[Rest,Sw,Gr,Wa,Go,Zm]],Rest1).

% next_state_switches: Sagt uns, wie im nächsten Zustand die Schalter
% stehen werden
% [Bälle,Schalter,GitterUndBarrierenUndDynWi,WändeundWinkel,LöcherUndZiel,Zm]
% [sw,Id,Pos,Zustand,Bezugsobjektliste]
% Regel: Befindet sich ein Ball auf dem Feld des Schalters, hat er im
% nächsten Zustand seinen Zustand geändert.
% Die Zustände heißen "an" und "aus".
% Bemerkung: Wir übergeben den Zeitindex hier mir, obwohl er für die
% Schalter nicht benötigt wird. Das vereinheitlicht aber später
% das Hauptprädikat, weil die Berechnung der Ballverteilung eben
% doch den Zeitindex benötigt.

next_state_switches([_,[_,[],_,_,_,_]],[]).

% ist der schalter "an" und liegt ein ball darauf, ist er im nächsten
% zustand "aus":
next_state_switches([I,[Ba,[[sw,Id,Pos,an,List]|Rest],_,_,_,_]],[[sw,Id,Pos,aus,List]|Rest1]):-
	member([ball,_,Pos,_],Ba),
	next_state_switches([I,[Ba,Rest,_,_,_,_]],Rest1).

% ist der schalter "aus" und liegt ein ball darauf, ist er im nächsten
% zustand "an"
next_state_switches([I,[Ba,[[sw,Id,Pos,aus,List]|Rest],_,_,_,_]],[[sw,Id,Pos,an,List]|Rest1]):-
	member([ball,_,Pos,_],Ba),
	next_state_switches([I,[Ba,Rest,_,_,_,_]],Rest1).

%andernfalls bleibt der zustand unverändert.
% Diese Bedingung muss unbedingt hier stehen, sonst bräuchte man den
% Zusatz, dass sich kein Ball auf dem Schalterfeld befindet.
next_state_switches([_,[Ba,[Sw|Rest],_,_,_,_]],[Sw|Rest1]):-
	next_state_switches([_,[Ba,Rest,_,_,_,_]],Rest1).


% next_state_dyn: Nächster Zustand von Barrieren, Löchern, Zielen und
% dyn. Winkeln. Hängt vom derzeitigen Zustand der Schalter ab.
% Zu Anfang eines Levels sind alle Schalter auf "aus".
% BEACHTE: DER RELEVANTE SCHALTERZUSTAND IST DER BEREITS AKTUALISIERTE!
% ALSO ERST SCHALTER, DANN DYNAMISCHE OBJEKTE UPDATEN!

%Barrieren:
%Generell gilt: "an" gehört zu "zu" und "aus" gehört zu "auf".
% Entsprechend hängt der Zustand der Barriere im nächsten Schritt davon
% ab, wie viele Schalter, die sie bedienen, aktuell in welchem Zustand
% sind: Eine ungerade Zahl von "an"-Schaltern öffnet, sonst ist sie
% geschlossen.
%[barr,Id,Pos,Zustand,Schalterliste]
%[sw,Id,Pos,Zustand,Bezugsobjektliste]
%[Bälle,Schalter,GitterUndBarrierenUndDynWi,WändeundWinkel,LöcherUndZiel,Zm]


%next_barrier_state(BarriereAlt,Switches,BarriereNeu) berechnet zu
% den derzeitigen Schalterstellungen und dem derzeitigen Barrierezustand
% den neuen Barrierezustand. Dazu wird die Schalterliste durchlaufen,
% bei jedem relevanten Schalter im "an"-Zustand wird der Zustand
% alterniert. Ist also eine ungerade Zahl von Schalter "an", so ist
% die Barriere "zu", sonst "auf". Dazu benutzen wir "parity"

next_barrier_state([barr,Id,Pos,_,SwList],Sw,[barr,Id,Pos,zu,SwList]):-
	findall(Id1,(member(Id1,SwList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,odd).

next_barrier_state([barr,Id,Pos,_,SwList],Sw,[barr,Id,Pos,auf,SwList]):-
	findall(Id1,(member(Id1,SwList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,even).

%next_state_barriers berechnet nun den neuen zustand aller barrieren.
next_state_barriers([_,[_,_,[],_,_,_]],[]).
next_state_barriers([I,[Ba,Sw,[BarrAlt|Rest],_,_,_]],[BarrNeu|Rest1]):-
	%next_state_switches([I,[Ba,Sw,_,_,_]],SwNeu),
	next_barrier_state(BarrAlt,Sw,BarrNeu),
	next_state_barriers([I,[Ba,Sw,Rest,_,_,_]],Rest1).

% Falls keine Barriere, sondern ein Gitter bzw. dynamischer Winkel an
% erster Stelle steht, wird es übersprungen.
% Sollte next_barrier_state anwendbar sein, greifen die obigen klauseln,
% sonst dieser default-fall.
next_state_barriers([I,[Ba,Sw,[X|Rest],_,_,_]],[X|Rest1]):-next_state_barriers([I,[Ba,Sw,Rest,_,_,_]],Rest1).

%Das gleiche jetzt noch für dynamische Winkel, Löcher und Ziele.

%Dynamische Winkel:
%[dwi,Id,Pos,Ausrichtung,Zustand,Schalterliste]
next_dynangle_state([dwi,Id,Pos,Ausr,_,SwList],Sw,[dwi,Id,Pos,Ausr,zu,SwList]):-
	findall(Id1,(member(Id1,SwList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,odd).

next_dynangle_state([dwi,Id,Pos,Ausr,_,SwList],Sw,[dwi,Id,Pos,Ausr,auf,SwList]):-
	findall(Id1,(member(Id1,SwList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,even).

% next_state_dynangles berechnet nun den neuen zustand aller dynamischen
% winkel
next_state_dynangles([_,[_,_,[],_,_,_]],[]).
next_state_dynangles([I,[Ba,Sw,[DynangleAlt|Rest],_,_,_]],[DynangleNeu|Rest1]):-
	next_state_switches([I,[Ba,Sw,_,_,_,_]],SwNeu),
	next_dynangle_state(DynangleAlt,SwNeu,DynangleNeu),
	next_state_dynangles([I,[Ba,Sw,Rest,_,_,_]],Rest1).
next_state_dynangles([I,[Ba,Sw,[X|Rest],_,_,_]],[X|Rest1]):-next_state_dynangles([I,[Ba,Sw,Rest,_,_,_]],Rest1).

%Gitter:
%[gr,Id,Pos,Zustand,Schalterliste]
%[Bälle,Schalter,GitterUndBarrierenUndDynWi,WändeundWinkel,LöcherUndZiel,Zm]

next_grid_state([gr,Id,Pos,_,SwList],Sw,[gr,Id,Pos,zu,SwList]):-
	findall(Id1,(member(Id1,SwList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,odd).

next_grid_state([gr,Id,Pos,_,SwList],Sw,[gr,Id,Pos,auf,SwList]):-
	findall(Id1,(member(Id1,SwList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,even).


% next_state_grids berechnet nun den neuen zustand aller barrieren.
% Achtung: Wenn hier schon die neuen Schalterzustände verwendet werden,
% so sollte man das bei next_state vielleicht nicht mehr tun?
% ausprobieren...

next_state_grids([_,[_,_,[],_,_,_]],[]).

next_state_grids([I,[Ba,Sw,[GridAlt|Rest],_,_,_]],[GridNeu|Rest1]):-
	next_state_switches([I,[Ba,Sw,_,_,_]],SwNeu),
	next_grid_state(GridAlt,SwNeu,GridNeu),
	next_state_grids([I,[Ba,Sw,Rest,_,_,_]],Rest1).

% und wieder der default-fall.
next_state_grids([I,[Ba,Sw,[X|Rest],_,_,_]],[X|Rest1]):-next_state_barriers([I,[Ba,Sw,Rest,_,_,_]],Rest1).



%Ziele und Löcher:
%Repräsentation: [goal/lo,Id,Pos,auf/zu,Ballliste,Schalterliste]
next_state_holes([_,[_,_,_,_,[],_]],[]).

%Ball fällt herein+Zustand ändert sich:
next_state_holes([I,[Ba,Sw,Dyn,Stat,[[Typ,Id,Pos,auf,BList,SList]|Rest],TM]],[[Typ,Id,Pos,zu,[IdBall|BList],SList]|Rest1]):-
	next_state_holes([I,[Ba,Sw,Dyn,Stat,Rest,TM]],Rest1),
	member([ball,IdBall,Pos,_],Ba),
	findall(Id1,(member(Id1,SList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,odd).

%Ball fällt herein+keine Änderung
next_state_holes([I,[Ba,Sw,Dyn,Stat,[[Typ,Id,Pos,auf,BList,SList]|Rest],TM]],[[Typ,Id,Pos,auf,[IdBall|BList],SList]|Rest1]):-
	next_state_holes([I,[Ba,Sw,Dyn,Stat,Rest,TM]],Rest1),
	member([ball,IdBall,Pos,_],Ba),
	findall(Id1,(member(Id1,SList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,even).

%Kein Ball fällt, aber der Zustand ändert sich von "auf" nach "zu":
next_state_holes([I,[Ba,Sw,Dyn,Stat,[[Typ,Id,Pos,auf,BList,SList]|Rest],TM]],[[Typ,Id,Pos,zu,BList,SList]|Rest1]):-
	next_state_holes([I,[Ba,Sw,Dyn,Stat,Rest,TM]],Rest1),
	findall(Id1,(member(Id1,SList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,odd).

%Kein Ball fällt, aber der Zustand ändert sich von "zu" nach "auf":
next_state_holes([I,[Ba,Sw,Dyn,Stat,[[Typ,Id,Pos,zu,BList,SList]|Rest],TM]],[[Typ,Id,Pos,auf,BList,SList]|Rest1]):-
	next_state_holes([I,[Ba,Sw,Dyn,Stat,Rest,TM]],Rest1),
	findall(Id1,(member(Id1,SList),member([sw,Id1,_,an,_],Sw)),AnSw),
	parity(AnSw,even).

%Generische Bedingung (Weder Balliste noch Zustand ändern sich):
next_state_holes([I,[Ba,Sw,Dyn,Stat,[X|Rest],TM]],[X|Rest1]):-
	next_state_holes([I,[Ba,Sw,Dyn,Stat,Rest,TM]],Rest1).


% Wir fassen diese Operationen jetzt noch zusammen in eine zur
% Manipulation dynamischer Objekte.
next_state_dynobjects([_,[_,_,[],_,_,_]],[]).
next_state_dynobjects([I,[Ba,Sw,DynObj,_,_,_]],DynObjNeu):-
	next_state_grids([I,[Ba,Sw,DynObj,_,_,_]],X1),
	next_state_dynangles([I,[Ba,Sw,X1,_,_,_]],X2),
	next_state_barriers([I,[Ba,Sw,X2,_,_,_]],DynObjNeu).

% Test auf virtuelle Objekte: ist der Verlauf aufgebaut, wird hier
% geprüft, ob ein Objekt eine Kette durchläuft, die in der gleichen
% Zeitmaschine endet, in der sie beginnt. Die sind auszuschließen.

%Als erstes erstellen wir eine Liste aller Paare [zm1,zm2] von
%ZeitmaschinenIds derart, dass ein Ball B existiert, der aus zm1 heraus
% kommt, sich dann klassisch bewegt und anschliessend in zm2
% verschwindet. Das tun wir für jeden Ball gesondert, anschliessend
% wenden wir auf das Ergebnis any_loop an.
%Das Prädikat zeitpfadliste(Verlauf,Liste) nimmt den Verlauf
%in der Form [[1,Zustand1],[2,Zustand2],...] entgegen,
%wobei wie üblich Zustand=[Bälle,Schalter,GitterUndBarrieren,WändeundWinkel,LöcherUndZiel,Zeitmaschinen]
%und erzeugt eine Liste von Listen der Form [BallId,zm1,zm2], so dass
%BallId aus zm1 kommt und in zm2 verschwindet.
%Die durchsuchen wir dann nach Schleifen.
%Zeitmaschinensyntax: [zm,Id,Startzeit,Zielzeit,Pos,Ball]


zeitpfadliste([],[]).

zeitpfadliste([[_,[[],_,_,_,_,_]]|Rest],Rest1):-
	zeitpfadliste(Rest,Rest1).

zeitpfadliste([[I,[[[ball,Id,_,_]|BRest],Sw,Dyn,Stat,Lo,Zm]]|Rest],Rest1):-
	not(Id=[_,_]),
	zeitpfadliste([[I,[BRest,Sw,Dyn,Stat,Lo,Zm]]|Rest],Rest1).

zeitpfadliste([[I,[[Ball1|BRest],Sw,Dyn,Stat,Lo,Zm]]|Rest],[[Id,Zm1,Zm2]|Rest1]):-
	Ball1=[ball,[Id,Zm1],_,Ri],
	next_state_balls([I,[[Ball1|BRest],Sw,Dyn,Stat,Lo,[]]],NeuBall),
	member([ball,[Id,Zm1],KoNeu,Ri],NeuBall),
	J is I+1,
	member([zm,Zm2,J,_,KoNeu,[ball,Id,KoNeu,Ri]],Zm),
	zeitpfadliste([[I,[BRest,Sw,Dyn,Stat,Lo,Zm]]|Rest],Rest1).

zeitpfadliste([[I,[[_|BRest],Sw,Dyn,Stat,Lo,Zm]]|Rest],Rest1):-
	zeitpfadliste([[I,[BRest,Sw,Dyn,Stat,Lo,Zm]]|Rest],Rest1).

%Jetzt können wir mit index_schleife auf virtuelle objekte testen.
%Das Prädikat virtual_objects nimmt den Verlauf entgegen und sieht nach,
%ob darin virtuelle Objekte vorkommen.
virtual_objects(Verlauf):-
	zeitpfadliste(Verlauf,Reiseliste),
	!,
	index_schleife(Reiseliste).


% Das Hauptprädikat nimmt den Grundzustand, die Nebenbedingungen (wie
% viele Exemplare von jedem Gegenstand etc.) und die Benutzereingaben
% entgegen, testet, ob die Eingaben den Bedinungen entsprechen,
% berechnet den Verlauf und testet ihn auf virtuelle Objekte. Dann
% erfolgt eine entsprechende Meldung oder der Verlauf wird ausgegeben.

berechne_verlauf(Feld,Input,Bdg,TMax,Result):-
	konsistent(Feld,Input,Bdg),
	grundzustand(Feld,Input,Grundzustand),
	verlauf(Grundzustand,TMax,Result),
	not(virtual_objects(Result)).

% konsistent: Keine zwei Gegenstände auf dem gleichen Feld, nur
% Gegenstände verwendet, die zur Verfügung standen...
% Grundzustand: Berechnet den Feldzustand gemäß den Benutzereingaben,
% mit denen das Spiel gestartet wird.
% verlauf berechnet vom grundzustand aus den verlauf.
% dabei wird im fall, dass next_state_balls im zeitpunkt I 'fail'
% produziert, [fail,I] zurückgegeben.

%HIER KOMMEN JETZT NOCH SONDERREGELN FÜR FEHLERMELDUNGEN, FALLS
%VIRTUELLE OBJEKTE GEFUNDEN WERDEN ODER INKONSISTENZEN
berechne_verlauf(Feld,Input,Bdg,TMax,[fehler,inkonsistenz,in,I]):-
		konsistent(Feld,Input,Bdg),
	grundzustand(Feld,Input,Grundzustand),
	verlauf(Grundzustand,TMax,[fail,I]).

berechne_verlauf(Feld,Input,Bdg,TMax,[fehler,virtuelle,objekte]):-
	konsistent(Feld,Input,Bdg),
	grundzustand(Feld,Input,Grundzustand),
	verlauf(Grundzustand,TMax,Verlauf),
	virtual_objects(Verlauf).


% next_state fasst nun alle obigen Prädikate zusammen, um den nächsten
% Feldzustand zu berechnen.
% Wichtig: dynobj verwendet Sw1, damit die objekte nicht erst im
% übernächsten schritt nach betätigung eines schalters reagieren.
% (allgemein kann man hier den ablauf ändern, indem man in verschiedenen
% prädikaten die neuen statt der alten werte als ausgang nimmt.)

next_state([I,[Ba,Sw,Dyn,Stat,Lo,TM]],[J,[Ba1,Sw1,Dyn1,Stat,Lo1,TM]]):-
	J is I+1,
	next_state_balls([I,[Ba,Sw,Dyn,Stat,Lo,TM]],Ba1),
	next_state_switches([I,[Ba,Sw,Dyn,Stat,Lo,TM]],Sw1),
	next_state_dynobjects([I,[Ba,Sw1,Dyn,Stat,Lo,TM]],Dyn1),
	next_state_holes([I,[Ba,Sw1,Dyn,Stat,Lo,TM]],Lo1).


% Verlauf berechnet nun mit next_state ausgehend von einem Grundzustand
% den ablauf bis hin zu einer vorgeschriebenen Zeitgrenze TMax
% I: Startzeit; Start: Startzustand

verlauf([I,Start],TMax,[[I,Start]|Rest]):-
	I<TMax,
	next_state([I,Start],[J,StateNew]),
	Rest=[[J,StateNew]|Rest1],
	verlauf([J,StateNew],TMax,[[J,StateNew]|Rest1]).

verlauf([I,Start],I,[[I,Start]]).


% fail_check überprüft den Verlauf auf Inkonsistenzen, d.h. darauf, ob
% die Balliste an irgendeiner Stelle "fail" ist.
% fail_check(Verlauf,X) ist wahr, wenn der Verlauf konsistent und X=con
% ist oder wenn der Verlauf inkonsistent und X der Index der ersten
% Inkonsistenz ist.
fail_check([],con).
fail_check(Verlauf,I):-
	member([I,[fail|_]],Verlauf).
fail_check([_|Rest],Y):-
	fail_check(Rest,Y).

% achievement_check überprüft, ob am Ende der Zeitfrist die Zielvorgabe
% erreicht ist, d.h. alle Bälle sich im Ziel befinden, die es sollten.
achievement_check(Verlauf,TMax,Zielvorgabe):-
	member([TMax,[_,_,_,_,Lo,_]],Verlauf),
	member([goal,_,_,_,Balls,_],Lo),
	first_elements(Balls,PureIds),
	set_eq(PureIds,Zielvorgabe).

achievement_check(Verlauf,TMax,_):-
	member([TMax,[_,_,_,_,Lo,_]],Verlauf),
	not(member([goal,_,_,_,_,_],Lo)).

% evaluate(Start,TMax,Zielvorgabe,Resultat) berechnet den Verlauf ab
% der Startsituation Start bis zum Zeitpunkt TMax und gibt ihn zurück,
% falls er konsistent ist, frei von virtuellen Objekten und die
% Zielvorgabe erreicht ist. Andernfalls erfolgt eine Fehlermeldung.
% Hier ist einiges zu optimieren: In jeder Klausel den Verlauf einzeln
% zu berechnen, ist mehr als überflüssig...
evaluate(Start,TMax,Zielvorgabe,[I,Verlauf]):-
	!,
	verlauf(Start,TMax,Verlauf),
	!,
	fail_check(Verlauf,I),
	!,
	not(virtual_objects(Verlauf)),
	!,
	achievement_check(Verlauf,TMax,Zielvorgabe).

evaluate(_,_,_,trouble).

% Konstruktion der Ausgangssituation aus den gegebenen Daten und dem
% Benutzerinput.
% Startplätze: Liste von Koordinaten, auf die Bälle gelegt werden dürfen
% Vorrat: [bälle,schalter,dyn,stat,lo,zm], jeweils unterspezifiziert.
% bälle:[ball,Id,_Ko,_Ri] Koordinaten und Richtung sind wählbar
% schalter:[sw,Id,_Pos,_Zustand,Bezugsobjektliste]
% barrieren und winkel:
% [barr,Id,_Pos,_Zustand,Schalterliste]
%Dynamischer Winkel: [dwi,Id,_Pos,Ausrichtung,_Zustand,Schalterliste]
%wände und winkel:
%[wa,_Pos] bzw. [wi,Id,_Pos,_Ri]
%Bälle:
% Zeitmaschinen:[zm,Id,_Startzeit,_Zielzeit,_Pos,_Ball] Einschränkung:
% Startzeit und Zielzeit müssen zwischen 1 und TMax liegen.
%Gitter stellen wir einstweilen nicht frei.
%Löcher: [loch,Id,_Pos,[],_Zustand,Schalterliste]

% raumkonsistenz(Situation,Abgrenzung) ist erfüllt, wenn keine zwei
% Objekte auf dem gleichen Feld und alle innerhalb der Abgrenzung
% liegen.
% get_coordinates(RaumSituation,Koordinaten) ist ein Prädikat, das alle
% Koordinaten von Objekten in der Situation zurückgibt.
% Eine Raumsituation ist eine Situation ohne Zeitkomponente
get_coordinates([],[]).
get_coordinates([[]|Rest],Rest1):-
	get_coordinates(Rest,Rest1).
get_coordinates([[[ball,_,Pos,_]|BRest]|Rest],[Pos|Rest1]):-
	get_coordinates([BRest|Rest],Rest1).
get_coordinates([[[sw,_,Pos,_,_]|SwRest]|Rest],[Pos|Rest1]):-
	get_coordinates([SwRest|Rest],Rest1).
get_coordinates([[[barr,_,Pos,_,_]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).
get_coordinates([[[dwi,_,Pos,_,_,_]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).
get_coordinates([[[wi,_,Pos,_]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).
get_coordinates([[[wa,Pos]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).
get_coordinates([[[loch,_,Pos,_,_,_]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).
get_coordinates([[[goal,_,Pos,_,_,_]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).
get_coordinates([[[zm,_,_,_,Pos,_]|DynRest]|Rest],[Pos|Rest1]):-
	get_coordinates([DynRest|Rest],Rest1).

% im_limit(Koordinaten,[X,Y]) testet, ob alle Koordinatenpaare in der
% ersten Liste eine kleinere x-Koordinate als X haben, entsprechend eine
% y-Koordinate kleiner als Y.
im_limit([],_).
im_limit([[X,Y]|Rest],[XMax,YMax]):-
	X<XMax,
	Y<YMax,
	im_limit(Rest,[XMax,YMax]).

% Das Limit besteht aus der maximalen x-Koordinate und der maximalen
% y-Koordinate: [XMax,YMax]
raumkonsistenz(Raumsituation,Limit):-
	!,
	get_coordinates(Raumsituation,Koordinaten),
	!,
	vereinfache(Koordinaten,Koordinaten),
	!,
	im_limit(Koordinaten,Limit).

%valid_input(Input,Vorgaben) überprüft, ob das in Input eingegebene
%in den Vorgaben erlaubt war.
valid_input([Ba,Sw,Dyn,Stat,Lo,TM],[Ba1,Sw1,Dyn1,Stat1,Lo1,TM1]):-
	schnitt(Ba,Ba1,Ba),
	schnitt(Sw,Sw1,Sw),
	schnitt(Dyn,Dyn1,Dyn),
	schnitt(Stat,Stat1,Stat),
	schnitt(Lo,Lo1,Lo),
	schnitt(TM,TM1,TM).

% merge_fields(X,Y,Z) vereinigt die entsprechenden komponenten von X und
% Y zu Z.
merge_fields([X1,X2,X3,X4,X5,X6],[Y1,Y2,Y3,Y4,Y5,Y6],[Z1,Z2,Z3,Z4,Z5,Z6]):-
	union(X1,Y1,Z1),
	union(X2,Y2,Z2),
	union(X3,Y3,Z3),
	union(X4,Y4,Z4),
	union(X5,Y5,Z5),
	union(X6,Y6,Z6).

% create_situation(Feld,Limit,Vorgaben,Input,Startsituation) stellt aus
% dem bereits fertigen Feld, den Raumgrenzen Limit, den Vorgaben und dem
% Benutzer-Input die Startsituation her und testet, ob alle
% Beschränkungen eingehalten sind.

create_situation(Feld,Limit,Vorgaben,Input,Startsituation):-
	!,
	valid_input(Input,Vorgaben),
	!,
	merge_fields(Feld,Input,Startsituation),
	!,
	raumkonsistenz(Startsituation,Limit).

% play(Feld,Limit,Vorgaben,Zielvorgabe,Input,Verlauf) ist jetzt das
% Hauptprädikat: Es stellt zuerst die Startsituation her, berechnet dann
% den Verlauf und testet ihn auf Konsistenz und Erreichen der
% Zielvorgabe.
play(Feld,Limit,Vorgaben,Zielvorgabe,TMax,Input,[I,Verlauf]):-
	!,
	create_situation(Feld,Limit,Vorgaben,Input,Start),
	!,
	evaluate([1,Start],TMax,Zielvorgabe,[I,Verlauf]).












%Nützliche Idioten:

% set_eq(X,Y) vergleicht die Listen X und Y darauf, ob sie aus den
% gleichen Elementen bestehen.
set_eq([],[]).
set_eq(X,Y):-
	vereinfache(X,[A|Rest]),
	vereinfache(Y,Y1),
	member(A,Y1),
	delete_from(A,Y1,Y2),
	set_eq(Rest,Y2).

% delete_from(X,Y,Z) löscht alle vorkommen von X aus Y und nennt das
% ergebnis Z

delete_from(_,[],[]).
delete_from(X,[X|Rest],Y):-
	delete_from(X,Rest,Y).
delete_from(X,[Z|Rest],[Z|Y]):-
	delete_from(X,Rest,Y).

% restrict_to(X,I,Y) nimmt eine Liste X der Form [[A1,X1],[A2,X2],...],
% wobei Ai eine natürliche Zahl ist und gibt eine Liste Y zurück, in der
% alle einträge entfernt ist, für die Ai größer oder gleich I.
restrict_to([],_,[]).
restrict_to([[I,X]|Rest],J,[[I,X]|Rest1]):-
	I<J,
	restrict_to(Rest,J,Rest1).
restrict_to([_|Rest],J,Rest1):-
	restrict_to(Rest,J,Rest1).

% vereinfache(X,Y) ist erfüllt, wenn Y aus X dadurch entsteht, dass man
% alle Elemente von X nur einmal aufführt.
vereinfache([],[]).
vereinfache([X|Rest],[X|Rest1]):-
	delete_from(X,Rest,Y),
	vereinfache(Y,Rest1).

% union(X,Y) ist erfüllt, wenn Y die vereinfache liste der elemente von
% elementen von X ist.
% mult_union vereinigt zunächst mit mehrfachen elementen, union löscht
% dann wieder mit vereinfache

mult_union([],[]).
mult_union([[]|Rest],Y):-mult_union(Rest,Y).
mult_union([[X|Rest1]|Rest2],[X|Rest3]):-mult_union([Rest1|Rest2],Rest3).

union(X,Y):-mult_union(X,Z),vereinfache(Z,Y).

% schnitt(X,Y,Z) ist erfüllt, wenn Z aus den Elementen besteht, die in X
% und in Y liegen.
member_of_both(X,Y,Z):-member(Z,X),member(Z,Y).
schnitt(X,Y,Schnitt):-findall(Z,member_of_both(X,Y,Z),Schnitt).

% parity(List,Par) ist erfüllt, wenn List ungerade viele Elemente
% enthält und Par=odd oder List gerade viele Elemente enthält und
% Par=even.
parity([],even).
parity([_|Rest],even):-parity(Rest,odd).
parity([_|Rest],odd):-parity(Rest,even).

% schleife(X) ist erfüllt, wenn X eine liste von paaren ist, die, als
% graph aufgefasst, einen kreis enthält.
% dazu benutzen wir als hilfe path_to(X,Y,Z), was heisst, dass in Z ein
% Pfad von X nach Y existiert.
path_to(X,Y,Z):-member([X,Y],Z).

path_to(X,Y,Z):-
	member([K1,Y],Z),
	delete_from([K1,Y],Z,Zneu),
	path_to(X,K1,Zneu).

schleife(Z):-
	union(Z,Knoten),
	member(K1,Knoten),
	path_to(K1,K1,Z).

% index_schleife sucht in einer Liste von Graphen der Form
% [[Id1,K1,K2],[Id2,K4,K5],[Id1,K3,K2],...], in der die Kanten indiziert
% sind, ansonsten aber vermischt sein können, nach Schleifen in einem
% der Graphen. Dazu finden wir zunächst alle vorkommenden Id's und
% suchen dann einzeln.

% first_elements(X,Y) ist erfüllt, wenn Y die Liste der ersten Elemente
% der Elemente von X ist. Auch hier: erst mit Wiederholungen
% (mult_first_elements), dann ohne (anwendung von vereinfache).
mult_first_elements([],[]).
mult_first_elements([[X|_]|Rest],[X|Rest1]):-
	mult_first_elements(Rest,Rest1).
first_elements(X,Y):-
	mult_first_elements(X,Z),
	vereinfache(Z,Y).


%mit erstes_ist identifizieren wir ein element X einer liste
%List mit anfangselement Y.
erstes_ist(List,X,Y):-member(Y,List),Y=[X|_].

% delete_first_elements löscht das erste element aus jedem element einer
% liste.
delete_first_elements([],[]).
delete_first_elements([[_|X]|Rest1],[X|Rest2]):-
	delete_first_elements(Rest1,Rest2).

index_schleife(X):-
	first_elements(X,Indizes),
	member(Index,Indizes),
	findall(Y,erstes_ist(X,Index,Y),Graph),
	delete_first_elements(Graph,Graph1),
	schleife(Graph1).

%Prioritätsregeln: Gegenstandskollisionen gehen vor Ballkollisionen, d.h.
%Gegenstände werden zuerst bearbeitet.
%Treffen sich zwei Bälle auf einem Schalter, wird dieser nicht betätigt. Das passiert automatisch, da er doppelt betätigt wird.
%Kollidieren zwei Bälle, nimmt jeder die Richtung des anderen an.
%(Das ist ein Problem mit einem der Level, aber sicher kein unlösbares.)
%Was ist bei Kollision von drei oder 4 Bällen? Das lassen wir einstweilen unbehandelt und konzentrieren uns auf Rätsel mit einem
%oder zwei Bällen. Das gleiche gilt für alle Mehrfachkollisionen.
% Zeitportale werden widerstandslos durchdrungen, wenn sie nicht zu
% Reisen benutzt werden. Insbesondere reflektieren sie nicht.














%LEVEL:
% BEACHTE: Wenn zu einem Zeitpunkt kein Ball auf dem Feld ist, später
% aber wieder einer auftaucht, spinnt das Interpolationsprädikat. Das
% läßt sich am leichtesten beheben, indem man einen zusätzlichen Ball
% einführt, der sich außerhalb des sichtbaren Feldes in eine Richtung
% bewegt, z.B. über dem oberen Bildschirmrand entlang. Wir nennen ihn
% helpful fritz: [ball,fritz,[1,-1],rechts]. Fritz ist immer weiß.
%
% verlauf([1,[[[ball,3,[1,1],rechts]],[[sw,0,[2,1],aus,[1]]],[[barr,1,[6,1],auf,[0]]],[],[[goal,4,[7,1],auf,[],[]]],[[zm,2,5,1,[5,1],[ball,3,[5,1],rechts]]]]],10,X).
%

testfall(1,[1,[[[ball,3,[0,1],rechts]],[[sw,0,[4,1],aus,[1]]],[[barr,1,[7,1],auf,[0]]],[],[[goal,4,[9,1],auf,[],[]]],[[zm,2,7,1,[6,1],[ball,3,[6,1],rechts]]]]]).

% Das ist das einfachste Level: "B-Sw-Zm-Barr-Goal" in dieser
% Reihenfolge, wobei Sw die Barriere schließt. Also muss der
% zeitgereiste B an Barr vorbei sein, ehe der originale B den Sw
% erreicht.

testfall(2,[1,[[[ball,5,[-1,1],unten]],[],[],[[wa,[1,9]],[wa,[1,1]]],[],[[zm,0,18,3,[1,5],[ball,1,[1,5],oben]]]]]).

% [1,[[],[],[],[[wa,[1,5]],[wa,[1,1]]],[],[[zm,0,8,1,[1,3],[ball,1,[1,3],oben]]]]]
% Klassisches Beispiel für ein virtuelles Objekt: Ball 1 kommt aus der
% zeitmaschine, prallt hin und her und verschwindet wieder in der
% zeitmaschine.
% Eine schönere Version ist dies hier:
%  zeige_bild([1,[[[ball,5,[-1,1],unten]],[],[],[[wa,[1,9]],[wa,[1,1]]],[],[[zm,0,18,3,[1,5],[ball,1,[1,5],oben]]]]],20).
%
% verlauf([1,[[[ball,0,[1,1],rechts]],[[sw,1,[5,1],aus,[3]],[sw,2,[9,1],aus,[4]]],[[barr,3,[7,1],auf,[1]],[barr,4,[11,1],auf,[2]]],[],[[goal,7,[12,1],auf,[],[]]],[[zm,5,6,1,[6,1],[ball,0,[6,1],rechts]],[zm,6,6,1,[10,1],[ball,0,[10,1],rechts]]]]],12,X).
% das ist eine hintereinanderausführung von zwei fassungen des ersten
% levels oben.

testfall(3,[1,[[[ball,0,[1,1],rechts]],[[sw,1,[5,1],aus,[3]],[sw,2,[9,1],aus,[4]]],[[barr,3,[7,1],auf,[1]],[barr,4,[11,1],auf,[2]]],[],[[goal,7,[15,1],auf,[],[]]],[[zm,5,6,1,[6,1],[ball,0,[6,1],rechts]],[zm,6,6,1,[10,1],[ball,0,[10,1],rechts]]]]]).

testfall(4,[1,[[[ball,0,[1,5],rechts]],[[sw,1,[3,1],aus,[2,3]]],[[dwi,2,[6,1],lo,auf,[1]],[dwi,3,[6,5],lu,auf,[1]]],[[wa,[1,3]]],[[goal,6,[1,1],auf,[],[]]],[[zm,7,12,1,[4,1],[ball,0,[4,1],links]]]]]).

%evaluate([1,[[[ball,0,[1,5],rechts]],[[sw,1,[3,1],aus,[2,3]]],[[dwi,2,[6,1],lo,auf,[1]],[dwi,3,[6,5],lu,auf,[1]]],[[wa,[1,3]]],[[goal,6,[1,1],auf,[],[]]],[[zm,7,12,1,[4,1],[ball,0,[4,1],links]]]]],20,[0],X).
% zeitgereister ball aktiviert die winkel, durch die erst die zeitreise
% stattfindet. zusätzliche wollte: originalball deaktiviert eine
% barriere zwischen zeitgereistem ball und ziel, das wäre noch
% einzubauen. allerdings ist dann genau auf die richtigen strecken zu
% achten.
% zeige_bild([1,[[[ball,0,[1,1],oben],[ball,2,[10,1],links]],[],[],[[wi,1,[1,4],ru],[wi,2,[4,4],lu],[wi,3,[4,1],ro]],[],[]]],15).
% Ein einfacher Testfall mit zwei Bällen und drei Winkeln.

testfall(5,[1,[[[ball,0,[1,1],oben],[ball,2,[10,1],links]],[],[],[[wi,1,[1,4],ru],[wi,2,[4,4],lu],[wi,3,[4,1],ro],[wi,3,[12,1],lo]],[],[]]]).

testfall(6,[1,[[[ball,7,[5,1],rechts]],[[sw,5,[14,3],aus,[4]],[sw,6,[11,5],an,[4]]],[[dwi,4,[14,1],lo,zu,[5,6]]],[[wi,0,[1,1],ro],[wi,1,[1,5],ru],[wi,2,[14,5],lu]],[[goal,3,[17,1],auf,[],[]]],[[zm,8,42,13,[12,1],[ball,7,[12,1],rechts]]]]]).

%abreisezeit von 8 war vorher 42.
testfall(7,[1,[[[ball,7,[5,1],rechts],[ball,9,[2,1],rechts]],[[sw,5,[14,3],aus,[4]],[sw,6,[11,5],an,[4]]],[[dwi,4,[14,1],lo,zu,[5,6]]],[[wi,0,[1,1],ro],[wi,1,[1,5],ru],[wi,2,[14,5],lu]],[[goal,3,[17,1],auf,[],[]]],[[zm,10,42,11,[9,1],[ball,9,[9,1],rechts]],[zm,8,76,13,[12,1],[ball,7,[12,1],rechts]]]]]).

testfall(8,[1,[[[ball,7,[8,1],rechts],[ball,9,[1,3],unten]],[[sw,5,[14,3],aus,[4]],[sw,6,[13,5],an,[4]]],[[dwi,4,[14,1],lo,zu,[5,6]]],[[wi,0,[1,1],ro],[wi,1,[1,5],ru],[wi,2,[14,5],lu]],[[goal,3,[17,1],auf,[],[]]],[[zm,10,41,11,[5,1],[ball,9,[5,1],rechts]],[zm,8,38,9,[11,1],[ball,7,[11,1],rechts]]]]]).

%einige dynamische winkel

testfall(9,[1,[[[ball,0,[1,1],oben],[ball,2,[10,1],links]],[[sw,9,[12,7],an,[1,2,3,4]]],[[dwi,1,[1,4],ru,zu,[9]],[dwi,2,[4,4],lu,zu,[9]],[dwi,3,[4,1],ro,zu,[9]],[dwi,4,[12,1],lo,zu,[9]]],[],[],[]]]).

%ein inkonsistenter verlauf.

testfall(10,[1,[[[ball,3,[0,1],rechts]],[[sw,0,[4,1],aus,[1]]],[[barr,1,[7,1],auf,[0]]],[],[[goal,4,[9,1],auf,[],[]]],[[zm,2,6,1,[6,1],[ball,3,[6,1],rechts]]]]]).

%testfall 1 ohne ball - zur überprüfung der eingabe.
testfall(11,[1,[[],[[sw,0,[4,1],aus,[1]]],[[barr,1,[7,1],auf,[0]]],[],[[goal,4,[15,1],auf,[],[]]],[[zm,2,7,1,[6,1],[ball,3,[6,1],rechts]]]]]).

%nettes level mit selbstkollision

% zeige_bild([1,[[[ball,1,[18,1],oben]],[[sw,5,[7,6],an,[4]]],[[barr,4,[5,2],zu,[5]]],[[wi,2,[5,10],ru],[wi,7,[12,6],lo],[wi,8,[12,12],lu],[wi,9,[3,12],ru],[wi,10,[3,6],ro],[wi,11,[18,10],lu]],[[goal,3,[5,1],auf,[],[]]],[[zm,6,32,1,[10,6],[ball,1,[10,6],rechts]]]]],35).
% (problem: paßt bei showtest12 nicht ins spielfeld. entsprechend
% anpassen.

testfall(13,[1,[[[ball,1,[18,1],oben]],[[sw,5,[7,6],an,[4]]],[[barr,4,[5,2],zu,[5]]],[[wi,2,[5,10],ru],[wi,7,[12,6],lo],[wi,8,[12,12],lu],[wi,9,[3,12],ru],[wi,10,[3,6],ro],[wi,11,[18,10],lu]],[[goal,3,[5,1],auf,[],[]]],[[zm,6,32,1,[10,6],[ball,1,[10,6],rechts]]]]]).

testfall(14,[1,[[[ball,1,[20,1],oben]],[[sw,5,[7,6],an,[4]]],[[barr,4,[5,2],zu,[5]]],[[wi,2,[5,10],ru],[wi,7,[12,6],lo],[wi,8,[12,12],lu],[wi,9,[3,12],ru],[wi,10,[3,6],ro],[wi,11,[20,10],lu]],[[goal,3,[5,1],auf,[],[]]],[[zm,6,34,3,[10,6],[ball,1,[10,6],rechts]]]]]
).

% Diese Variante ist etwas verschoben, um noch ins Spielfenster zu
% passen - etwas unelegant, später modifizieren.
% zusatzidee hierzu: alle winkel dynamisch, die schalter sind fest
% positioniert, die positionen der winkel frei.

testfall(15,[1,[[[ball,1,[20,4],unten]],[[sw,5,[6,4],an,[4]]],[[barr,4,[5,1],zu,[5]]],[[wi,2,[5,8],ru],[wi,7,[12,4],lo],[wi,8,[12,10],lu],[wi,9,[3,10],ru],[wi,10,[3,4],ro],[wi,11,[20,8],lu],[wa,[20,1]],[wa,[4,0]],[wa,[6,0]]],[[goal,3,[5,0],auf,[],[]]],[[zm,6,34,3,[10,4],[ball,1,[10,4],rechts]]]]]).
% im spiel sollten hier außer dem ball, dem ziel, den wänden am ziel und
% dem schalter auch diejenigen winkel fixiert sein, die den ball nach
% oben leiten könnten. der rest kann zur wahl überlassen sein.
% vermutlich keine leichte aufgabe.

% Eine Variante von Level 1, bei der die Zeitmaschine links vom Schalter
% steht und daher der Ball von sich selbst abprallen muss.
% frei: wand und einstellungen der zeitmaschine.

testfall(16,[1,[[[ball,1,[2,5],links]],[[sw,2,[10,5],aus,[3]]],[[barr,3,[19,5],auf,[2]]],[[wa,[0,5]]],[[goal,5,[21,5],auf,[],[]]],[[zm,4,38,1,[9,5],[ball,1,[9,5],rechts]]]]]).

%hübschere variante.

testfall(17,[1,[[[ball,1,[2,5],links]],[[sw,2,[10,5],aus,[3]]],[[barr,3,[20,5],auf,[2]]],[[wa,[0,5]]],[[goal,5,[22,5],auf,[],[]]],[[zm,4,40,1,[9,5],[ball,1,[9,5],rechts]]]]]).

%ein einfaches erstes level, um sich an die physik zu gewöhnen.

testfall(18,[1,[[[ball,1,[8,8],links]],[],[],[[wa,[1,8]],[wi,2,[12,8],lu]],[[goal,3,[12,4],auf,[],[]]],[]]]).

% eine erste kombination aus reise in die vergangenheit und reise in die
% zukunft.

testfall(19,[1,[[[ball,0,[0,-5],rechts],[ball,1,[2,12],rechts]],[[sw,2,[8,5],aus,[4]],[sw,3,[8,9],an,[5]]],[[dwi,4,[8,12],lu,auf,[2]],[barr,5,[12,3],zu,[3]]],[[wi,6,[8,3],ro]],[[goal,7,[17,3],auf,[],[]]],[[zm,9,8,13,[10,3],[ball,1,[10,3],rechts]],[zm,8,12,1,[8,7],[ball,1,[8,7],unten]]]]]).

% gleiches level, nur um 3 nach oben verschoben zu besserer
% sichtbarkeit. problem: der zusatzball, der die zeitreise in die
% zukunft überbrückt, muss noch verschwinden, damit das als erfolg
% zählt. dazu vorzugsweise die erfolgsroutine ändern.

testfall(20,[1,[[[ball,0,[0,-5],rechts],[ball,1,[2,10],rechts]],[[sw,2,[8,3],aus,[4]],[sw,3,[8,7],an,[5]]],[[dwi,4,[8,10],lu,auf,[2]],[barr,5,[12,1],zu,[3]]],[[wi,6,[8,1],ro]],[[goal,7,[17,1],auf,[],[]]],[[zm,9,8,13,[10,1],[ball,1,[10,1],rechts]],[zm,8,12,1,[8,5],[ball,1,[8,5],unten]]]]]).

%einfacher grundfall für reisen in die zukunft

testfall(21,[1,[[[ball,1,[0,10],rechts],[ball,2,[15,0],oben]],[[sw,3,[15,8],an,[5]]],[[barr,5,[9,10],zu,[3]]],[[wi,4,[15,10],lu]],[[goal,6,[11,10],auf,[],[]]],[[zm,7,8,10,[7,10],[ball,1,[7,10],rechts]]]]]).

%nettes ineinander aus zukunft und vergangenheit mit zwei bällen.

testfall(22,[1,[[[ball,1,[0,2],rechts],[ball,2,[0,5],rechts]],[[sw,7,[12,3],an,[5]],[sw,8,[7,5],aus,[6]]],[[barr,5,[5,5],zu,[7]],[barr,6,[8,2],auf,[8]]],[[wi,4,[12,2],lo]],[[goal,3,[12,5],auf,[],[]]],[[zm,10,7,10,[6,5],[ball,2,[6,5],rechts]],[zm,9,12,1,[11,2],[ball,1,[11,2],rechts]]]]]).

%einfache vorbereitung auf level 1

testfall(23,[1,[[[ball,1,[2,2],rechts],[ball,2,[2,6],rechts]],[[sw,6,[8,2],aus,[5]]],[[barr,5,[10,6],auf,[6]]],[[wi,4,[12,2],lo]],[[goal,3,[12,6],auf,[],[]]],[[zm,7,7,1,[8,6],[ball,2,[8,6],rechts]]]]]).

% Diverse partielle Testprädikate. Unerheblich für den eigentliche
% Programmablauf, zur Entwicklung aber nützlich.

check_verlauf(X,TMax):-
	!,
	verlauf(X,TMax,Y),
	!,
	not(virtual_objects(Y)).

zeitreisen(X,TMax,Z):-
	verlauf(X,TMax,Y),
	zeitpfadliste(Y,Z).
















%Ecke für verschiedene Experimente mit Interaktivität.
%Ein Testprädikat.
zeige_bild(Start,Zeit):-
	verlauf(Start,Zeit,X),
	!,
	preprocess_all(X,X1),
	!,
	refine(X1,X2),
	!,
	free(@p),new(@p,picture),
	verlauf_anzeigen(X2).

% HIER HÄTTE ICH GERNE EIN TESTPRÄDIKAT, DAS ÜBER EINE BEFEHLSZEILE DAS
% LEVEL UND DIE MAXIMALZEIT EINLIEST UND DANN SHOWTEST1 AKTIVIERT.

%Ein Testprädikat, das die einzelnen Level zusammenfaßt.
test(I,Max):-
	I<12,
	showtest12(I,Max,_).

test(12,Max):-showtest13(Max).

%spiellauf([[Level1,Max1],[Level2,Max2],[Level3,Max3],...]) führt die
% mit den Zahlen Level1, Level2, Level3 indizierten Level bis zum
% Zeitpunkt Max1, Max2, Max3,... aus und geht nur dann zum nächsten,
% wenn das vorige erfolgreich war.

spiellauf([]).
spiellauf([[Ind,Max]|Rest]):-
	!,
	showtest12(Ind,Max,1),
	send(timer(5),delay),
	spiellauf(Rest).


% showtest12 gibt zusätzlich auch noch eine erfolgs/mißerfolgsmeldung
% aus. Das letzte Argument ist dabei 1, falls das Level erfolgreich
% war, sonst 0.

showtest12(X,Max,1):-
	!,
	free(@p),
	free(@dialog),
	free(@button),
	new(@dialog,dialog('Spielfeld')),
	send(@dialog,display,new(@p,picture('Spielfeld'))),
	send(@p,width(1200)),
	send(@p,height(600)),
	%send(@dialog,append,new(_T,menu(destination,marked))),
	!,
	send(@dialog,append,button(start,message(@prolog,
					      v8,
					      X,Max))),

	send(@dialog,open),
	!,
	testfall(X,A),
	!,
	verlauf(A,Max,Verl),
	!,
	member([Max,[[]|_]],Verl),
	!,
	not(virtual_objects(Verl)).

showtest12(X,Max,0):-
	!,
	free(@p),
	free(@dialog),
	free(@button),
	new(@dialog,dialog('Spielfeld')),
	send(@dialog,display,new(@p,picture('Spielfeld'))),
	send(@p,width(1200)),
	send(@p,height(600)),
	%send(@dialog,append,new(_T,menu(destination,marked))),
	!,
	send(@dialog,append,button(start,message(@prolog,
					      v8,
					      X,Max))),

	send(@dialog,open).

%die zu showtest12 gehörigen hilfsprädikate

v8(Z,Max):-
	testfall(Z,Y),
	catch_virtual_objects(Y,Max,X),
	send(@p,display,new(_,text('Virtuelle Objekte gefunden',center,bold)),point(250,250)),
	!,
	preprocess_all(X,X1),
	!,
	refine(X1,X2),
	!,
	verlauf_anzeigen(X2),
	!,
	send(timer(1.0),delay),
	!,
	misserfolgsmeldung,
	send(@p,open).


v8(Z,Max):-
	testfall(Z,Y),
	!,
	verlauf(Y,Max,X),
	!,
	preprocess_all(X,X1),
	!,
	refine(X1,X2),
	!,
	verlauf_anzeigen(X2),
	!,
	send(timer(1.0),delay),
	!,
	success(X,Max).



%showtest13 testet interaktive eingabe anhand von level 1.
%es ist möglich, den blauen ball in level 1 an
%beliebiger stelle zu plazieren. nächster schritt: level 1
%ausprogrammieren
spiele(1,Max):-showtest13(1,Max).
spiele(spielplatz,Max):-spielplatz(Max).






richtungswahl(rechts):-
	write_ln(rechts),
	free(@selected_arrow),
	send(@rarrow,display,new(@selected_arrow,line(0,8,30,8))),
	send(@upbox,fill_pattern,colour(yellow)),
	send(@dbox,fill_pattern,colour(yellow)),
	send(@lbox,fill_pattern,colour(yellow)),
	send(@rbox,fill_pattern,colour(red)).

richtungswahl(links):-
	write_ln(links),
	free(@selected_arrow),
	send(@larrow,display,new(@selected_arrow,line(0,6,30,6))),
	send(@upbox,fill_pattern,colour(yellow)),
	send(@dbox,fill_pattern,colour(yellow)),
	send(@lbox,fill_pattern,colour(red)),
	send(@rbox,fill_pattern,colour(yellow)).

richtungswahl(oben):-
	write_ln(oben),
	free(@selected_arrow),
	send(@uparrow,display,new(@selected_arrow,line(7,1,7,31))),
	send(@upbox,fill_pattern,colour(red)),
	send(@dbox,fill_pattern,colour(yellow)),
	send(@lbox,fill_pattern,colour(yellow)),
	send(@rbox,fill_pattern,colour(yellow)).

richtungswahl(unten):-
	write_ln(unten),
	free(@selected_arrow),
	send(@darrow,display,new(@selected_arrow,line(7,0,7,30))),
	send(@upbox,fill_pattern,colour(yellow)),
	send(@dbox,fill_pattern,colour(red)),
	send(@lbox,fill_pattern,colour(yellow)),
	send(@rbox,fill_pattern,colour(yellow)).

richtung(0,unten). richtung(1,oben). richtung(6,links). richtung(8,rechts).

v10(Max,Ba1,Ba2,Ba3,Ba,Wi1,Zm1,Wa1,Wa2,Wa3):-
	get(@selected_arrow,y,Ri),
	richtung(Ri,Dir),
	get(Ba1,x,Xba1),
	get(Ba1,y,Yba1),
	get(Ba2,x,Xba2),
	get(Ba2,y,Yba2),
	get(Ba3,x,Xba3),
	get(Ba3,y,Yba3),
	get(Ba,x,Xba),
	get(Ba,y,Yba),
	get(Wi1,x,Xwi1),
	get(Wi1,y,Ywi1),
	get(Zm1,x,Xzm1),
	get(Zm1,y,Yzm1),
	get(Wa1,x,Xwa1),
	get(Wa1,y,Ywa1),
	get(Wa2,x,Xwa2),
	get(Wa2,y,Ywa2),
	get(Wa3,x,Xwa3),
	get(Wa3,y,Ywa3),
	free(Ba1),free(Ba2),free(Ba3),free(Ba),free(Wi1),free(Zm1),free(Wa1),free(Wa2),free(Wa3),
	round(Xba1,Xba11),
	round(Yba1,Yba11),
	round(Xba2,Xba21),
	round(Yba2,Yba21),
	round(Xba3,Xba31),
	round(Yba3,Yba31),
	round(Xba,Xbaneu),
	round(Yba,Ybaneu),
	round(Xwi1,Xwi11),
	round(Ywi1,Ywi11),
	round(Xzm1,Xzm11),
	round(Yzm1,Yzm11),
	round(Xwa1,Xwa11),
	round(Ywa1,Ywa11),
	round(Xwa2,Xwa21),
	round(Ywa2,Ywa21),
	round(Xwa3,Xwa31),
	round(Ywa3,Ywa31),
	Xba12 is Xba11+1, Yba12 is Yba11+1,
	Xba22 is Xba21+1, Yba22 is Yba21+2,
	Xba32 is Xba31+1, Yba32 is Yba31+3,
	Xbaneu1 is Xbaneu+2, Ybaneu1 is Ybaneu+2,
	Xwa12 is Xwa11+1, Ywa12 is Ywa11+1,
	Ywa22 is Ywa21+2,
	Xwa32 is Xwa31+2, Ywa32 is Ywa31+1,
	Xwi12 is Xwi11+1, Ywi12 is Ywi11+1,
	Xzm12 is Xzm11+2, Yzm12 is Yzm11+2,
	Start=[1,[[[ball,1,[Xba12,Yba12],rechts],[ball,2,[Xba22,Yba22],links],[ball,3,[Xba32,Yba32],unten],[ball,6,[Xbaneu1,Ybaneu1],Dir]],[],[],
	[[wa,[Xwa12,Ywa12]],[wa,[Xwa21,Ywa22]],[wa,[Xwa32,Ywa32]],[wi,4,[Xwi12,Ywi12],lu]],[],[[zm,5,10,3,[Xzm12,Yzm12],[ball,1,[Xzm12,Yzm12],rechts]]]]],
	write_ln(Start),
	!,
	verlauf(Start,Max,P0),
	!,
	preprocess_all(P0,P1),
	!,
	refine(P1,P2),
	!,
	verlauf_anzeigen(P2).

draw_triangle_lu(_,0).
draw_triangle_lu([X,Y],A):-
	Endpoint is X-A,
	send(@wi1,display,line(X,Y,Endpoint,Y)),
	Yneu is Y-1,
	Aneu is A-1,
	draw_triangle_lu([X,Yneu],Aneu).

round(X,Y):-
	X1 is X mod 50,
	X2 is X-X1,
	Y is X2//50.


















