Ein paar Worte vorabHome   Letzte MeldungenNews   Index der Kapitel und der besprochenen FunktionenIndex   Wer ich bin, warum ich diese Seiten mache, KontaktImpressum   Ich freue mich über jeden Eintrag im Gästebuch!Gästebuch   Einige Links zu anderen AutoLisp-SeitenLinks   Copyrights und DisclaimerRechts
Hier können die kompletten Seiten als ZIP-File heruntergeladen werden!

Funktionen für komfortables Arbeiten mit Zeichenketten String-Tango
Noch mehr Funktionen für komfortableres Arbeiten mit Zeichenketten Kettenhunde
strtok zerlegt Zeichenketten anhand eines Trennzeichens Tock-Tock
Arbeiten mit Datum und Zeit in AutoLisp Zeitlos...
Dotted pairs - wie man den Programmabbruch verhindert Gepunktet?
Neue Funktionen für die Listenbearbeitung Strukturtapete
Weitere neue Funktionen für die Listenbearbeitung Listen to me!
Lambda expressions - dasSalz in der Suppe Lambada
Lambda expressions anhand eines Praxisbeispiels Unter der Erde
Where und whereever erleichtern den Umgang mit Listen Quo vadis?
Rekursion - Funktionen, die sich selbst aufrufen Katzenschwanz
Ein äusserst wichtiger Prototyp für Funktionen Nix passiert
Wo das lineare mapcar am Ende ist Tiefer rein!
Über Effekte und Neben(Seiten-)Effekte von Funktionen Seitensprünge
Die Namensräume (Sichtbarkeit) von Variablen Raumwunder
Let dient zur Schaffung kleinerer Namensräume Lass mal...
Sukzessive Verarbeitung von Listenresten mit mapcdr Der Bruder
Was in AutoLisp einfach nicht machbar ist (Teil I) Beschränkt
Was in AutoLisp einfach nicht machbar ist (Teil II) Limited Edition
Nicht mit Effekten arbeiten, sondern direkter Daten-Änderung Destruktiv
Sequenzielles vs. paralleles Abarbeiten von Argumenten Parallelwelten
Über den Umgang mit Funktionsschablonen Erwachet!
Ein Praxiskapitel über Auswahlsätze, Attribute, wcmatch und mehr Durch die Brust
Die Farben des AutoCAD Color Index und ihre RGB-Werte Alles so schön
Hier laufen die Fäden zusammen: Viele Konzepte vereint Lapsus Lispuli
Ein Spiel als Beispiel für lernfähige Funktionen Zug um Zug
Errorhandling in AutoLisp - Teil 1 Alles valsch!
Errorhandling in AutoLisp - Teil 2 Und foll Feler!


Zum Einsteiger-Tutorial

Zu den ActiveX-Seiten

Meine Private HP mit Fotos, Gedichten, Musik und Postkartenversand

Mein Online-Lexikon der Fotografie

Mein völlig abgedrehtes Reisebüro










Eine Warnung vorweg: Dies ist ein sehr langes Kapitel! Hier geht es nicht darum, eine bestimmte Technik oder ähnliches vorzustellen - es ist einfach nur ein Bespiel, wie man in AutoLisp programmieren kann, und was man in AutoLisp programmieren kann. AutoCAD als Spieleplattform ist ja bisher nicht gerade als populär zu bezeichnen, aber das sollte uns nicht abhalten: Es geht also um ein Spiel, und wir fangen mal klein an und entscheiden uns für Tic Tac Toe.

Wenn man ein solches Brettspiel, bei dem ein Mensch gegen den Rechner antritt, programmieren will, muss man zunächst eine Vorstellung von der Strategie haben, die der Rechner bei seinen Zügen anwenden soll. Diese herauszufinden, ist aber oft recht schwierig - meist wird diejenige angewandt, bei der der Rechner möglichst viele Züge im Voraus durchrechnet und sich dann für den Zug mit der besten Gewinnchance entscheidet.

Ich möchte hier aber eine ganz andere Vorgehensweise vorstellen: Der Rechner plant überhaupt nichts, er zieht irgendwie und verliert dann auch fast immer. Aber: Er lernt aus Fehlern! Nach und nach wird er diese dummen Züge nicht mehr machen, sondern sich bessere einfallen lassen! Keine komplizierte Strategie also - das Programm wird wie ein Pawlowscher Hund mit Belohnung und Strafe konditioniert!

Den meisten Leuten wird bekannt sein, dass man bei Tic Tac Toe nicht wirklich gewinnen kann. Wenn beide Spieler keine Fehler machen, bleibt es immer bei einem Unentschieden! Um das Programm kurz und übersichtlich zu halten, habe ich es erst einmal so implementiert, dass der menschliche Spieler (im Programm immer durch H wie Human gekennzeichnet) immer den ersten Zug macht und der Rechner (M wie Machine) antwortet.

In diesem Kapitel werde ich das Programm in einer Form darstellen, bei der es seine 'Erfahrungen' in einer globalen Variable ablegt. Bei einem Neustart in einer neuen Zeichnung ist es also wieder strohdumm und muss von vorne anfangen zu lernen. Im nächsten Kapitel werde ich dann aber eine Variante vorstellen, bei der sich der Programmcode selber modifiziert und dann auf der Platte abspeichert: Ein Programm, das durch Benutzung wächst und immer schlauer wird.

Allerdings entsteht in diesem Zusammenhang ein kleines, aber lösbares Problem: Seit Einführung des neuen (Visual-) Interpreters werden benutzderdefinierte Funktionen nicht mehr als Listen, sondern als ein eigener Datentyp im Speicher abgelegt. Das bedeutet, dass man an den Code von mit (defun ...) erzeugten Funktionen nicht mehr herankommt und ihn natürlich erst recht nicht mehr modifizieren kann.

Die Implementation verzichtet daher vollständig auf die Verwendung von (defun ...), alle Programmteile werden mit (setq ...) als Datenlisten erzeugt. Dabei liegt folgende Äquivalenz zu Grunde:
(defun funktion1 (arg1 arg2 / var1 var2)
  ... ; macht irgendetwas
)

(setq funktion2 '((arg1 arg2 / var1 var2)
  ... ; macht auch irgendetwas
))
                  
Funktion 2 lässt sich genauso verwenden und ausführen wie Funktion 1, nur eben mit dem Unterschied, dass auf der AutoCAD-Kommandozeile mit !funktion2 der Lisp-Code ausgegeben wird und wir ihn natürlich auch im Programm verwenden oder modifizieren können. Dies ist sozusagen als Vorgriff auf das Folgekapitel schon eingebaut. Das Programm ist übrigens so angelegt, dass es nur eine einzige Funktion namens (play-game) gibt, alle anderen Funktionen sind als lokale Variablen darin 'versteckt'.

Allerdings brauchen wir einige solcher Unterfunktionen: (play-game) deckt nur die Rahmenhandlung ab, den Ablauf des Spiels mit dem Wechsel zwischen H und M, und es wird nach jedem Zug ein Test aufgerufen, ob einer der Spieler gewonnen hat oder das Spiel unentschieden ausgegangen ist. Die einzelnen Tests usw. finden aber in den Unterfunktionen statt, so dass die Hauptfunktion eigentlich für jedes beliebige ähnliche Spiel zu verwenden ist.

Ich werde jetzt das gesamte Programm Funktion für Funktion mit einigen Erläuterungen hier vorstellen. Am Ende des Kapitels findet sich das Programm dann noch einmal am Stück zum Kopieren und Ausprobieren. Und wie gesagt: es ist etwas länger!
(setq play-game '(( / size graphics gameover moves history
                      tmp draw-grid clear-grid draw-h-move
                      draw-m-move get-h-move win-test
                      h-only m-only tied? h-won? m-won?
                      ask-for-end find-m-move)

  ; Zeichnet das Gitter
  (setq draw-grid '((size / i ents)
    (setq i 0)
    (repeat (1+ size)
      (command"_line"(list i 0)(list i size)"")
      (setq ents(cons(entlast)ents))
      (command"_line"(list 0 i)(list size i)"")
      (setq ents(cons(entlast)ents))
      (setq i(1+ i))
    )
    (command"_zoom"
      (list(- size)(- size))
      (list(* 2 size)(* 2 size))
    )
    ents
  ))
                  
Wie gesagt, alle Unterfunktionen sind als lokale Variablen in der Hauptfunktion eingebettet. (draw-grid ...) zeichnet ein Gitter der Größe size, die Funktion kann also auch für andere Spiele verwendet werden, bei denen die Größe mehr als 3 beträgt. Die erzeugten Geometrieelemente werden zurückgegeben, damit sie nach dem Spiel gelöscht werden können. Die Variable graphics enthält immer die durch das laufende Spiel entstandenen Elemente.
  ; Löscht das letzte Spiel vom Bildschirm
  (setq clear-grid '((ents / )
    (foreach ent ents(entdel ent))
    nil
  ))
                  
Dieser Funktion wird nach einem Spiel der Inhalt von graphics zum Löschen übergeben.
  ; Zeichnet einen H-Zug
  (setq draw-h-move '((pos s / row col)
    (setq row(/ pos s)col(rem pos s))
    (command"_circle"(list(+ col 0.5)(+ row 0.5))0.35)
    (entlast)
  ))

  ; Zeichnet einen M-Zug
  (setq draw-m-move '((pos s / row col ents)
    (setq row(/ pos s)col(rem pos s))
    (command"_line"(list(+ col 0.8)(+ row 0.8))
            (list(+ col 0.2)(+ row 0.2))"")
    (setq ents(list(entlast)))
    (command"_line"(list(+ col 0.8)(+ row 0.2))
            (list(+ col 0.2)(+ row 0.8))"")
    (setq ents(cons(entlast)ents))
  ))
                  
Die beiden Funktionen zeichnen die Spielerzüge auf dem Bildschirm, für H einen Kreis, für M ein Kreuz. Auch diese Elemente werden zurückgegeben und von der Hauptfunktion in graphics gespeichert.
  (setq get-h-move '((moves s / tmp ok get-click)
    (setq get-click '((s / p row col)
      (setq p(getpoint "Click a field: \n"))
      (+(*(min(max(fix(cadr p))0)(1- s))s)
        (min(max(fix(car p))0)(1- s))
      )
    ))
    ;*******************
    (while(not ok)
      (initget 1)
      (setq tmp(get-click s))
        (if(member tmp moves)(princ "Invalid. ")(setq ok 1))
      )
      tmp
    )
  )
                  
Diese Funktion stellt sozusagen das 'Human Interface' dar - der Zug des menschlichen Spielers wird angefordert. In dieser Unterfunktion eingebettet findet man die Unter-Unterfunktion (get-click), die ermittelt, in welches Feld der Mensch geklickt hat. Auch diese Funktion ist wieder so ausgelegt, dass sie mit jedem quadratischen Spielfeld der Größe s umgehen kann.
  (setq win-test '((pl / )
    (apply'or
      (mapcar
      '(lambda(l)
         (apply'and(mapcar'(lambda(p)(member p pl))l))
       )
      '((0 1 2)(3 4 5)(6 7 8)(0 3 6)(1 4 7)
                        (2 5 8)(0 4 8)(2 4 6))
      )
    )
  ))
                  
Diese Funktion testet, ob einer der beiden Spieler eine Mühle zugemacht hat. Sie mag schon so etwas undurchsichtig aussehen - deshalb habe ich hier auf eine Funktion für beliebige Spielbretter verzichtet - hier wird nur Tic Tac Toe getestet! Die Zahlentripel sind natürlich die Gewinnkombinationen auf dem von 0 bis 8 durchnummerierten Spielfeld. Da ich hier auf die Riesenschüssel 'mapcar-lambda-Salat' verzichten wollte, habe ich sie fest verdrahtet!
  (setq h-only '((pl / )
    (setq pl(reverse pl))
    (if(null pl)
      nil
      (append(list(car pl))(h-only(cddr pl)))
    )
  ))
                  
Ein Spiel wird im Programm immer als Liste in der Form (0 4 7 5 ...) aufgezeichnet. Da H immer anfängt, hat H also zuerst 0 besetzt, dann antwortete M mit 4 usw. Der Korrektheit halber sollte allerdings noch erwähnt werden, dass die Listen tatsächlich auch noch rückwärts vorliegen, da sie mit (cons ...) erzeugt werden, also (... 5 7 4 0). Diese Hilfsfunktion extrahiert schlicht und einfach die H-Züge, d.h. jedes zweite Element, von hinten angefangen!
  (setq m-only '((pl / )
    (h-only(reverse(cdr(reverse pl))))
  ))
                  
Und diese Funktion extrahiert die M-Züge - das letze Element (das ja immer der Eröffnungszug von H ist, wird einfach abgeschnitten, und dann wird (h-only) im Sinnes des Code-Recycling verwendet!
  (setq tied? '((pl s / )
    (if(=(length pl)(* s s))
      (progn
        (princ "Game tied.\n")
        (princ)
      )
    )
  ))
                  
Diese Funktion testet, ob ein Unentschieden vorliegt. Der Test selbst ist einfach: Wenn soviel Züge gemacht wurden, wie das Spielfeld Felder hat, und keiner bisher gewonnen hat (das wird hier nicht mehr getestet!), dann muss es wohl so sein. Schwieriger ist wohl zu sehen, dass es sich hier um eine Prädikatfunktion handelt (deshalb auch das Fragezeichen im Namen). Dass (princ) als T gewertet wird, kann man durch die Eingabe von (not(not(princ))) verifizieren!
  (setq h-won? '((pl / )
    (if(win-test(h-only pl))
      (progn
        (princ"H is winner! Game over.\n")
        (princ)
      )
    )
  ))

  (setq m-won? '((pl / )
    (if(win-test(m-only pl))
      (progn
        (princ"M is winner! Game over.\n")
        (princ)
      )
    )
  ))

  (setq ask-for-end '(( / tmp)
    (initget 0 "Yes No")
    (=(getkword "Play again? <Yes>/No: ")"No")
  ))
                  
Diese beiden Funktion sind absolut simpel. Beide bedienen sich des selben Mechanismus: Die entsprechende H- oder M-Teilliste wird auf Sieg getestet, falls ja, wir eine entsprechende Meldung ausgegeben. Die dritte Funktion schliesslich fragt nur bei H an, ob er weiterspielen möchte.
  (setq reward '((amt his memory / )
    (foreach item his
      (setq memory
        (if(assoc item memory)
          (subst
            (cons item(+ amt(cdr(assoc item memory))))
            (assoc item memory)
            memory
          )
          (cons(cons item amt)memory)
        )
      )
    )
    memory
  ))
                  
Jetzt geht es ans Eingemachte: Die reward-Funktion führt die Pawlowsche Konditionierung durch! Zunächst die Erläuterung der Argumente: amt ist der Betrag (amount), für verlorene Spiele gibt es Abzug (-1), für Patt-Spiele und gewonnene Spiele einen Bonus (1 bzw. 2). Der Spielverlauf wird in der Variablen memory abgelegt und bewertet. memory ist eine Assoziationsliste in der Form ( ((1 0) . -4) ((4 0) . 3) ...).

Das Beispiel sagt uns: M hat mit der Antwort 4 auf die Eröffnung 0 bereits dreimal unentschieden gespielt (es kann aber auch ein Sieg und ein Unentschieden gewesen sein) - mit der Antwort 1 auf die Eröffnung 0 aber schon viermal verloren. Die reward-Funktion ist also der Buchhalter, der nach einem Spiel die Kontierung durchführt. Das Argument his bekommt hier den gesamten Spielverlauf in der Form ((... 2 7 4 0)(7 4 0)(4 0)...), und jeder dieser zwischenzeitlichen Spielstände erhält die Bewertung.

Ist ein Spielstand noch nicht in der Liste enthalten, wird er also mit amt bewertet eingetragen. Falls er in der Liste vorgefunden wird, kann er aktualisiert und mit verändertem Wert abgespeichert werden. Noch ein kleiner Hinweis: Eine Liste wie (7 4 0) kann durchaus als Schlüssel einer Assoziationsliste verwendet werden. Es müssen nicht immer Strings oder Zahlen sein!

Die Übergabe der Spiel-History könnte eigentlich einfach vonstatten gehen: Jeder vorhergehende Spielstand kann natürlich mit mapcdr aus dem letzten vorliegenden Stand abgeleitet werden. Ich möchte in diesem Kapitel aber auf das Einbinden anderer Funktionen völlig verzichten und habe daher diesen etwas primitiven Weg gewählt.

Ebenso verzichten wollte ich auf den Zugriff auf Variablen in übergeordneten Namensräumen, um keine irgendwie gearteten Undurchsichtigkeiten zu schaffen. Deshalb wird die globale Variable memory hier nicht dirket verwendet, sondern (etwas mühsam) als Argument übergeben, modifiziert und an den Aufrufer zurückgegeben. Dies hat den Vorteil, dass der Code unabhängig von den Namensraum-Verhältnissen bleibt (im Hinblick auf das Folgekapitel).
  (setq find-m-move'( (pl s memory / j rl mvs mn)
    (setq j 0)
    (repeat (* s s)
      (if(not(member j pl))
        (progn
          (if(not(assoc(cons j pl)memory))
            (progn
              (setq memory(cons(cons(cons j pl)0)memory))
              (setq mvs(cons(cons(cons j pl)0)mvs))
            )
            (setq mvs(cons(assoc(cons j pl)memory)mvs))
          )
        )
      )
      (setq j(1+ j))
    )
    (foreach item mvs
      (if
        (or
          (m-won?(car item))
          (h-won?(cons(caar item)(cons'x(cdar item))))
        )
        (setq mn item)
      )
    )
    (if(null mn)
      (progn
        (setq mn '((-1) . -1000000))
        (foreach item(reverse mvs)
          (if(>(cdr item)(cdr mn))
            (setq mn item)
          )
        )
      )
    )
    (list(caar mn)memory)
  ))
                  
Und nun zur letzten Unterfunktion - hier wird der nächste M-Zug festgelegt. In der hier vorliegenden Version wird folgendermassen vorgegangen: Zunächst wird eine Liste mvs mit allen möglichen Antworten erzeugt (je voller das Brett ist, umso kürzer fällt sie aus). Falls diese potenziellen Züge noch nicht in memory vorhanden sind, werden sie mit 0-Bewertung eingetragen.

Im zweiten Teil wird getestet, ob a) eine Mühle zugemacht werden kann, oder ob b) ein H-Sieg direkt verhindert werden kann. Falls es eine von diesen Möglichkeiten gibt, wird sie wahrgenommen. Dazu ist zu sagen: Dieser Teil ist verzichtbar! Er beschleunigt nur den Lernprozess - nimmt man das erste (foreach ...) aus dem Code heraus, wird der Rechner trotzdem seine Erfahrungen machen, nur eben etwas langsamer. Statt 'strohdumm' wird er eben 'saudumm' anfangen, aber trotzdem lernen.

Und hier beginnt nun das eigentliche Hauptprogramm (die Deklaration war schon ganz oben!):
  (setq size 3 stop nil)
  (while(not stop)
    (setq graphics(clear-grid graphics))
    (setq graphics(append graphics(draw-grid size)))
    (setq gameover nil moves nil history nil)
    (while(not gameover)
      (setq moves(cons(get-h-move moves size)moves))
      (setq graphics
        (cons(draw-h-move(car moves)size)graphics)
      )
      (if(h-won? moves)
        (progn
          (setq memory(reward -1 history memory))
          (setq gameover 1)
        )
        (progn
          (if(tied? moves size)
            (progn
              (setq memory(reward 1 history memory))
              (setq gameover 1)
            )
            (progn
              (setq tmp(find-m-move moves size memory))
              (setq moves(cons(car tmp)moves))
              (setq memory(cadr tmp))
              (setq history(append history(list moves)))
              (setq graphics
                (append graphics
                  (draw-m-move(car moves)size)
                )
              )
              (if(m-won? moves)
                (progn
                  (setq memory(reward 2 history memory))
                  (setq gameover 1)
                )
              )
            )
          )
        )
      )
    )
    (if(ask-for-end)(setq stop 'T))
  )
))

                  
Die Hauptfunktion selbst ist übersichtlich und in keinster Weise aufregend. Zwei ineinander geschachtelte Schleifen steuern zum einen (aussen) den Ablauf mehrerer Spiele nacheinander und zum anderen (innen) den Ablauf des jeweiligen aktuellen Spiels. In der Variablen moves ist der jeweils aktuelle Spielstand abgelegt, in history noch einmal der Verlauf (darauf wurde weiter oben bereits eingegangen).

Hier noch einmal der komplette Code zum Kopieren und ausprobieren (es sind keine zusätzlichen Funktionen nötig). Als Datei speichern, mit (load ...) und mit (play-game) starten. Ein Errorhandling ist nicht eingebaut, und der Ofang sollte vor dem Start ausgeschaltet werden!
(setq play-game '(( / size graphics gameover moves history
                      tmp draw-grid clear-grid draw-h-move
                      draw-m-move get-h-move win-test
                      h-only m-only tied? h-won? m-won?
                      ask-for-end find-m-move)

  ; Zeichnet das Gitter
  (setq draw-grid '((size / i x1 y1 ents)
    (setq i 0)
    (repeat (1+ size)
      (command"_line"(list i 0)(list i size)"")
      (setq ents(cons(entlast)ents))
      (command"_line"(list 0 i)(list size i)"")
      (setq ents(cons(entlast)ents))
      (setq i(1+ i))
    )
    (command"_zoom"
      (list(- size)(- size))
      (list(* 2 size)(* 2 size))
    )
    ents
  ))
  ;**************************************
  ; Löscht das letzte Spiel vom Bildschirm
  (setq clear-grid '((ents / )
    (foreach ent ents(entdel ent))
    nil
  ))
  ;**************************************
  ; Zeichnet einen H-Zug
  (setq draw-h-move '((pos s / row col)
    (setq row(/ pos s)col(rem pos s))
    (command"_circle"(list(+ col 0.5)(+ row 0.5))0.35)
    (entlast)
  ))
  ;**************************************
  ; Zeichnet einen M-Zug
  (setq draw-m-move '((pos s / row col ents)
    (setq row(/ pos s)col(rem pos s))
    (command"_line"(list(+ col 0.8)(+ row 0.8))
            (list(+ col 0.2)(+ row 0.2))"")
    (setq ents(list(entlast)))
    (command"_line"(list(+ col 0.8)(+ row 0.2))
            (list(+ col 0.2)(+ row 0.8))"")
    (setq ents(cons(entlast)ents))
  ))
  ;**************************************
  ; Fordert H zum Ziehen auf und ermittelt das Feld
  (setq get-h-move '((moves s / tmp ok get-click)
    ; Ermittelt das geklickte Feld
    (setq get-click '((s / p row col)
      (setq p(getpoint "Click a field: \n"))
      (+(*(min(max(fix(cadr p))0)(1- s))s)
        (min(max(fix(car p))0)(1- s))
      )
    ))
    ;*******************
    (while(not ok)
      (initget 1)
      (setq tmp(get-click s))
        (if(member tmp moves)(princ "Invalid. ")(setq ok 1))
      )
      tmp
    )
  )
  ;**************************************
  ; Testet, ob das Spiel gewonnen wurde
  (setq win-test '((pl / )
    (apply'or
      (mapcar
      '(lambda(l)
         (apply'and(mapcar'(lambda(p)(member p pl))l))
       )
      '((0 1 2)(3 4 5)(6 7 8)(0 3 6)(1 4 7)
                              (2 5 8)(0 4 8)(2 4 6))
      )
    )
  ))
  ;**************************************
  ; Extrahiert die H-Züge aus einem Spielverlauf
  (setq h-only '((pl / )
    (if(null pl)
      nil
      (append(list(car pl))(h-only(cddr pl)))
    )
  ))
  ;**************************************
  ; Extrahiert die M-Züge aus einem Spielverlauf
  (setq m-only '((pl / )
    (h-only(reverse(cdr(reverse pl))))
  ))
  ;**************************************
  ; Testet, ob ein Unentschieden vorliegt
  (setq tied? '((pl s / )
    (if(=(length pl)(* s s))
      (progn
        (princ "Game tied.\n")
        (princ)
      )
    )
  ))
  ;**************************************
  ; Testet, ob H gewonnen hat
  (setq h-won? '((pl / )
    (if(win-test(h-only pl))
      (progn
        (princ"H is winner! Game over.\n")
        (princ)
      )
    )
  ))
  ;**************************************
  ; Testet, ob M gewonnen hat
  (setq m-won? '((pl / )
    (if(win-test(m-only pl))
      (progn
        (princ"M is winner! Game over.\n")
        (princ)
      )
    )
  ))
  ;**************************************
  ; Fragt beim Benutzer an, ob weiteres Spiel
  (setq ask-for-end '(( / tmp)
    (initget 0 "Yes No")
    (=(getkword "Play again? <Yes>/No: ")"No")
  ))
  ;**************************************
  ; Trägt die Bewertung des Spielverlaufs ein
  (setq reward '((amt his memory / )
    (foreach item his
      (setq memory
        (if(assoc item memory)
          (subst
            (cons item(+ amt(cdr(assoc item memory))))
            (assoc item memory)
            memory
          )
          (cons(cons item amt)memory)
        )
      )
    )
    memory
  ))
  ;**************************************
  ; Ermittelt den nächsten M-Zug
  (setq find-m-move'( (pl s memory / j rl mvs mn)
    (setq j 0)
    (repeat (* s s)
      (if(not(member j pl))
        (progn
          (if(not(assoc(cons j pl)memory))
            (progn
              (setq memory(cons(cons(cons j pl)0)memory))
              (setq mvs(cons(cons(cons j pl)0)mvs))
            )
            (setq mvs(cons(assoc(cons j pl)memory)mvs))
          )
        )
      )
      (setq j(1+ j))
    )
    (foreach item mvs
      (if
        (or
          (m-won?(car item))
          (h-won?(cons(caar item)(cons'x(cdar item))))
        )
        (setq mn item)
      )
    )
    (if(null mn)
      (progn
        (setq mn '((-1) . -1000000))
        (foreach item(reverse mvs)
          (if(>(cdr item)(cdr mn))
            (setq mn item)
          )
        )
      )
    )
    (list(caar mn)memory)
  ))
  ;**************************************
  ; Hauptfunktion - Spielverlauf
  (setq size 3 stop nil)
  (while(not stop)
    (setq graphics(clear-grid graphics))
    (setq graphics(append graphics(draw-grid size)))
    (setq gameover nil moves nil history nil)
    (while(not gameover)
      (setq moves(cons(get-h-move moves size)moves))
      (setq graphics
        (cons(draw-h-move(car moves)size)graphics)
      )
      (if(h-won? moves)
        (progn
          (setq memory(reward -1 history memory))
          (setq gameover 1)
        )
        (progn
          (if(tied? moves size)
            (progn
              (setq memory(reward 1 history memory))
              (setq gameover 1)
            )
            (progn
              (setq tmp(find-m-move moves size memory))
              (setq moves(cons(car tmp)moves))
              (setq memory(cadr tmp))
              (setq history(append history(list moves)))
              (setq graphics
                (append graphics(draw-m-move(car moves)size))
              )
              (if(m-won? moves)
                (progn
                  (setq memory(reward 2 history memory))
                  (setq gameover 1)
                )
              )
            )
          )
        )
      )
    )
    (if(ask-for-end)(setq stop 'T))
  )
))