Patience - und das Kartenspiel ist immer vollständig!

Da hat man mal wieder Lust auf ein Ründchen Patience, holt die Karten aus dem Schrank, mischt sie gründlich, teilt sie erwartungsvoll aus, holt sich ein Likörchen oder lecker Weinchen und einige Kekse, legt los, und dann kommt eine bestimmte Karte nicht, die man dringend braucht!

Sie kommt überhaupt nicht, denn das Söhnchen hat sie verschleppt, sie fiel beim letzten Canasta-Treffen unter den Tisch und schimmelt hinter der Heizung vor sich hin, oder beim letzten Umzug verdünnisierte sie sich auf die Straße, als ein Karton auf die Straße fiel und seinen Inhalt in den Rinnstein ergoß.

Vielleicht hat man aber auch gar kein Kartenspiel, aber einen Computer mit einem Browser. - Na, prima, bringen wir dem Browser bei, mit uns Patience zu spielen!

Inhalt

  1. Was ist eine Patience?
  2. Planung der Patience
  3. Spielkarten - ein bisschen OOP
  4. Probleme mit verschiedenen Browsern
  5. Platzhalter als unverrückbare Divs
  6. Vorladen der Bilder
  7. Talon, Reihen und Ablagen als Listen
  8. Spielanfang
  9. Crossbrowser-Bibliothek, die zweite
  10. Spielverlauf
  11. Noch einige Annehmlichkeiten für den Benutzer

Entwicklung der Patience "Belagerung"

1. Was ist eine Patience?

Patience ist ein Kartenlegespiel, bei dem Karten in der Reihenfolge ihres Wertes nach Farben sortiert auf Ablagen gesammelt werden sollen. Wie das zu geschehen hat, ist in den jeweiligen Regeln festgelegt.

Die "Belagerung" ist zwar eine Patience, die nicht sehr oft aufgeht, aber sie ist schnell zu "Freecell" erweitert. Der Quelltext ist nicht allzu lang, so dass er sich als Beispiel gut eignet.



zum Seitenanfang
zum Seitenende




2. Planung der Patience

Das zu erstellende Dokument soll den Tisch nachbilden, auf der die Patience gespielt wird. Im Fall der "Belagerung" muss man 4 Ablagen unterbringen sowie 8 Reihen, in denen die 52 Karten offen ausgeteilt werden. Da die Karten fast beliebig verschiebbar sind, kann es sein, dass in einer Reihe über 40 Karten auftauchen, in anderen gar keine.

Will man in diesen Fällen mit fest positionierten Grafikelementen arbeiten, muss man also 40x8+4=324 verweissensitive Grafiken unterbringen. Und arbeitet man mit einem doppelten Kartenspiel wie z.B. für die "Neuner" oder "Große Harfe", sind es mehr als 10*100+10+2=1012 zu erstellende, gut gefüllte Objekte.

Das Laden der Seite stellt den Anwender auf eine Geduldsprobe, also verbietet sich dieser Weg. Was dann? - OOP ist angesagt!



zum Seitenanfang
zum Seitenende




3. Spielkarten - ein bisschen OOP

Ein Informatiker erklärte mir die objekt-orientierte Programmierweise folgendermaßen:

Wir wollen was nachbilden. Sagen wir: ein Haus. Das heisst dann mein_haus=new Haus().
Dieses Haus hat ein paar Zimmer: with(mein_haus) { Wohnzimmer=new Zimmer(); Kueche=new Zimmer(); }
In der Küche steht ein Stuhl und ein Tisch: with(mein_haus.kueche) { Tisch=new Moebel(); Stuhl=new Moebel(); }
Auf dem Stuhl kann man sitzen: mein_Haus.Kueche.Stuhl.sitzen();
Der Stuhl ist braun: mein_Haus.Kueche.Stuhl.Farbe=braun;
(Hier driftete das Gespräch dann etwas ab.)

Übertragen auf unser Kartenspiel heisst das: Wir haben 52 Karten, diese haben je ein Bild (und eine Rückseite, die hier aber eher uninteressant ist). Wenn sie auf dem Tisch liegen, haben sie Koordinaten. An den Koordinaten erkennt man, ob sie im Talon, in einer der Reihen oder auf einer der Ablagen liegen. Das Bild enthält Informationen zu Farbe und Wert.

Na, dann bilden wir die Karten doch mal nach. Schicke Bilder, 52 Stück an der Zahl in doppelter Ausführung für Merkeffekte sind ein Fall für Paintshop oder ein anderes Grafikprogramm. Die Bilder benennen wir geordnet nach 1.Farbe und 2.Wert cxx.gif und cxxa.gif, wobei xx von 0 bis 51 geht. Die Farbe ist dann Math.floor(xx/13), der Wert xx%13.
So, und nun noch schöne Platzhalter für die Reihen und die Ablagen, vielleicht noch ein Kartenrücken, das war's dann auch schon mit den Vorarbeiten mit einem Grafikprogramm.

Den Zugriff auf Koordinaten liefert uns das Objekt div im Objekt document des Objektes window. Sobald ein Fenster aufgerufen wird, ist das window-Objekt schon da, das document entsteht spätestens durch die body-Tags, nach DOM2 schon direkt nach Bereitstellen des Fensters. Die benötigten div-Objekte erzeugt man durch durch div-Tags, und damit man sie auseinanderhalten kann, verpasst man ihnen eine id. Die Raum-Koordinaten bekommen sie durch die style-Attribute position:absolute; top:12px; left:8px; z-index:4: 8.Pix von links, 12.Pix von oben, 4.Schicht von unten. Das Bild bekommen sie, indem zwischen den div-Tags ein Image-Objekt <img ... > geschrieben wird. Ab dann lassen sie sich hin- und herschieben. Dann noch die Grafik verweissensitiv gemacht, fertig sind die Spielkarten!



zum Seitenanfang
zum Seitenende




4. Probleme mit verschiedenen Browsern

Kartenobjekte schon fertig? Wirklich? Nein, da ist ein kleines Dorf, äh, ein alter Browser, der die Schieberei nicht mitmacht! Und dies ist der Einstieg in das traurige Kapitel der Browserinkompatibilitäten! -

Für Netscape 4.xx werden die Karten also nicht in divs verpackt, sondern in layers. Und wenn man sich das DocumentObjectModel, kurz:DOM anschaut und etwas herumprobiert, so stellt man fest, dass bei layers styles ziemlich fehl am Platze sind. Die Karten bekommen auf folgende Weise ihre Raumkoordinaten: <layer name="k_name" left="yy" top="xx" z-index="zz"> xx,yy und zz stehen für die Koordinaten, k_name für das id-Äquivalent. Am besten schreiben wir uns eine Funktion, die uns die Karten erstellt.

Als Vorarbeit müssen wir erst mal bestimmen, mit welchem Browser (DOM) gearbeitet wird. Wir unterscheiden hier zwischen nn4, nn5, ie4, ie5 und sonstige:


var xyz=new Array(3); // temp.Wert fuer Koordinaten der Objekte
var my_browser=get_browser();  Kennung des Browsers fuer DOM

function get_browser()
{ var t=navigator.appName; 
  if (t=='Netscape') t='nn';
  else if (t.search(/Internet.+/)>-1)  t='ie';
  if (navigator.appVersion.substring(0,1) == "4") t=t+4; 
  else if (navigator.appVersion.substring(0,1) == "5") t=t+5;
  if (t=='ie4') if (document.documentElement) t='ie5';
  return t;
}

Warum fragen wir nicht einfach auf Objekte ab? - Es gibt Browser wie z.B. Opera, die meinen, ein Objekt zu kennen, aber dann kennen sie nicht mehr als den Namen dieses Objektes. Sagt ihm der Anwender aber, er soll das DOM von IE oder Mozilla/Netscape benutzen, dann klappt die Zusammenarbeit auf einmal.
Desweiteren sollte man die "crossbrowser"-Funktionen immer so anlegen, dass im letzen Fall (sonstige) das neueste DOM (nn5) herangezogen wird. Somit werden erst die "bewährten" Modelle durchprobiert, bevor es an etwas noch nicht ganz Ausgegorenes geht.

Die Karten werden also folgendermaßen erzeugt und erscheinen erst mal alle oben links (Talon):

zmax=52;              // 52 Karten
dx=56; dy=80; dy2=12; // Kartengroesse und der Teil,
                      // der bei den Reihen nicht überlappt 
rmax=8; smax=4;       // 8 Reihen, 4 Stacks
var gewaehlt=-1;      // aktuell gewaehlte Karten 

function gen_karten()
{ var i,t,t1;
  for (i=0; i<zmax; i++) 
  { // -- verweissensitives Bild:
    t1='<a href="javascript:klick('+i+')">';
    t1=t1+'<img src="'+ima_c[i].src+'" width=56 height=80 ';
    t1=t1+'alt="" name="k'+i+'" border=0></a>';

    // - Verpackung in div/layer wg. Koordinaten-Zugriff:
    if (my_browser=='nn4')
    { t='<layer name="k_"'+i+'" z-index="'+(i+1)
      t=t+'"top="12" left="12" width="'+dx+'" height="'+dy+'">';
      t=t+t1+'</layer>'; 
    }
    else
    { t='<div id="k_'+i+'" style="position:absolute; top:12px; ';
      t=t+'left:12px; width:'+dx+'px; height:'+dy+'px; ';
      t=t+'z-index:'+(i+1)+'">'+t1+'<div>';
    }
    document.writeln(t);
  }
}

Da die Objekte als Unterobjekte des document-Objektes geplant sind, rufen wir die Funktion direkt nach dem öffnenden body-Tag auf, bevor die nicht verrückbaren divs erzeugt werden, auch als Unterobjekte von document/body.

<body>

<script type="text/javascript">
gen_karten();
</script>

<div id="kopf" style="position:absolute; 
  top:16px; left:60px; width:588px; height:32px;
  font-size:16pt; font-weight:bold;">
 Belagerung <br> uja Vers. 1.0
</div>


<div id="knoepfe" style="position:absolute; top:96px; left:480px; width:96">

<div class="button"><a href="javascript:neu(true)">neues Spiel</a></div>
<div class="button"><a href="javascript:neu(false)">von vorne</a></div>
<div class="button"><a href="javascript:abraeumen()">abräumen</a></div>
<div class="button"><a href="javascript:hilfe()">Hilfe</a></div>

</div>

...(weiterer Text)

Jetzt haben wir Karten mit der Id "k_0" bis "k_51", diese enthalten die Bilder "k0" bis "k51", deren Aussehen von "c0.gif" bis "c51.gif" bestimmt werden und die bei Bedarf auch das Aussehen von "c0a.gif" bis "c51a.gif" annehmen können, und die bei Anwählen mit klick(0) bis klick(51) reagieren.



zum Seitenanfang
zum Seitenende




5. Platzhalter als unverrückbare Divs

Die Platzhalter geben an, wohin eine Karte hochgelegt werden kann. Sie markieren die erlaubten Reihen. Wir schreiben sie als divs in den Body-Teil, damit sind sie direkte Unterobjekte von document. Und weil ich bequem bin, lasse ich den Text mit Javascript erzeugen, vgl. Making of Logelei.

<div id="reihen" style="position:absolute; top:108px; left:8px; z-index:0;">

<script type="text/javascript">
t='';
for (var i=0; i<rmax; i++)
t=t+'<a href="javascript:reihe_hoch('+i+')">';
t=t+'<img src="null.gif" width=56 height=80 alt="" border=0></a>';
document.writeln(t);
</script>

</div>

Die Karten werden derzeit links oben generiert, die Function neu(schalter) mischt (oder auch nicht, je nach Wert des Schalters), verteilt die Asse auf die Ablagen, die restlichen Karten auf die Reihen. Damit man gleich ein hübsches Bild geboten bekommt, wird im Body-Tag onload="neu(true)" direkt nach Erstellen der Seite aufgerufen. Danach ist der Body-Teil fertig.



zum Seitenanfang
zum Seitenende




6. Vorladen der Bilder

Damit der Bildwechsel bei der Auswahl der Karte flüssig abläuft, sollten die Bilder schon mal über die Internet-Leitung gekommen sein. Dies geschieht mit:

// ----- 52 Kartenbilder vorladen: --------------------
var leer=new Image;    leer.src='leer.gif'; 
var nulli=new Image(); nulli.src='null.gif';    
var ima_b=new Image;   ima_b.src='bg.gif'; 
var ima_c =new Array();
var ima_ca=new Array();
for (i=0; i<52; i++)
{ ima_c[i] =new Image(); ima_c[i].src='c'+i+'.gif'; 
  ima_ca[i]=new Image(); ima_ca[i].src='c'+i+'a'+'.gif';
}


zum Seitenanfang
zum Seitenende




7. Talon, Reihen und Ablagen als Listen

Bei einem genauen Blick auf die ausgeteilten Karten auf dem Tisch fällt auf, dass Talon, Ablagen und Kartenreihen je eine Einheit bilden, die Karten werden zwischen diesen Einheiten hin- und hergeschoben. Alle diese Einheiten lassen sich durch Listen nachbauen. In dieser Patience wechselt jeweils die letzte Karte in der jeweiligen Liste den Platz.

Bauen wir erst mal den Talon: eine Liste der Karten von 0 bis 52. Wir packen nicht die ganzen 52 Kartenobjekte in diese Liste, sondern *nur* deren Kennziffern! Mischen, Austauschen und Sortieren geht damit erheblich schneller!

Da die javascript-eigenen Funktionen zur Listenverwaltung mir im IE4 mal eine böse Überraschung bei push serviert hat, und ich so von Pascal her so meine eigenen Gedanken habe, was mit Listen alles anzustellen ist, baue ich mir lieber selbst eine "Listenverwaltung". Ich benutze dafür ein Array, dessen erster Wert die Listenlänge angibt (counted array).

  1. etwas, was die Listen initialisiert: var talon=new Array()
  2. Listen löschen: talon[0]=0() (statt etlichen pops)
  3. Check, ob ein Element in der Liste ist, dann dessen Index. Es wird zwar nicht für dieses Spiel gebraucht, aber für (fast) alle Spiele der Kategorie "Chips"
  4. ein push-Äquivalent: erst wird gecheckt, ob das Element schon drin ist, wenn nicht, wird es angefügt. Dazu wird das 0.Element eins hochgezählt, das neue Element an dem Platz mit dem Index dex 0.Elementes eingefügt. Es existiert ein weiteres push-Äquivalent, welches nicht vorher checkt, es wird hier aber nicht gebraucht.
  5. ein pop-Äquivalent: der Index des Elementes in der Liste wird bestimmt (Punkt 3), das letzte Element tritt an dessen Stelle, Element 0 wird eins runtergezählt, fertig ist die Laube. Es gibt ein zweites pop-Äquivalent, bei dem die restliche Reihenfolge erhalten bleibt. Bei Patience werden jedoch nur End-Blöcke verschoben, so dass diese Funktion ausreichend ist. Es würde sogar reichen, einfach den Counter (0.Element) runterzusetzen.
  6. Ich sortiere auch lieber selbst, dazu in einem andern Kapitel (Spiele mit HighScore), es wird hier nicht gebraucht.
// -- Talon mit 52 Karten generieren:
zmax=52; // Patience mit 52 Karten

var talon=new Array();
tliste[0]=zmax;
for (i=0; i<tliste[0]; i++) tliste[i+1]=i;

function in_liste(nr,liste) 
{ var i,k=0; 
  if (liste[0]>0) for (i=1; i<=liste[0]; i++) if (liste[i]==nr) k=i;
  return k;
}

function add_liste(nr,liste)
{ if (in_liste(nr,liste)<1) 
  { liste[0]++; 
    liste[liste[0]]=nr;
  }
}

function sub_liste(nr,liste)
{ var k=in_liste(nr,liste);
  if (k>0)
  { liste[k]=liste[liste[0]]; 
    liste[0]--;
  }
}

Als weiteres brauchen wir 4 Ablagen und 8 Kartenreihen, jede Ablage und Reihe wieder eine Liste mit den Kennziffern der darin befindlichen Karten, aufgebaut nach demselben Schema wie der Talon. Diesmal wird die Liste nicht "händisch" erzeugt, sondern wir basteln eine Funktion, die so ein Listen-Array erstellt und den ersten Wert auf 0 setzt. Diese Function lassen wir auf alle 4 Stacks los und alle 8 Reihen:

smax=4; // 4 Ablagen
rmax=8; // 8 Reihen

var rliste=new Array(); 
for (i=0; i<rmax; i++) rliste[i]=gen_liste(); // Kartenreihen

var sliste=new Array(); 
for (i=0; i<smax; i++) sliste[i]=gen_liste(); // Ablagen

function gen_liste() { var f=new Array(); f[0]=0; }

Mit diesen Listen "tliste", "rliste[0]" bis "rliste[7]" und "sliste[0]" bis "sliste[3]" können wir zusammen mit den Kartenobjekten von oben den Talon, alle Reihen und Ablagen nachbauen.



zum Seitenanfang
zum Seitenende




8. Spielanfang

Zu Beginn des Spiels werden eventuelle Reihen und Ablagen auf 0 Karten gesetzt, der Talon gemischt, die 4 Asse auf die 4 Ablagen gelegt und der Rest gleichmäßig auf die 8 Reihen verteilt. Im Ernst? Nein, nur im wirklichen Leben. Bei der virtuellen Patience ist es günstiger, den Talon, so wie er nach dem Mischen ist, beizubehalten, um eventuell das Spiel zu wiederholen. Ausgesucht haben wir noch keine Karte: gewaehlt wird auf -1 gesetzt, ein Wert, der bei der Kartenkennziffer, die von 0 bis 51 geht, nicht vorkommt.
Man beachte: das Leeren der Ablagen geschieht schlicht und ergreifend mit sliste[i][0]=0. Die Werte, die danach irgendwann mal in der Liste landeten, interessieren ab jetzt nicht die Bohne.

Bei Bedarf wird gemischt und dann ausgeteilt. - Und so sieht das Ganze aus:

function neu(flag)
{ var i,j,k;
  // Spielfeld leeren:
  for (i=0; i<smax; i++) sliste[i][0]=0;
  for (i=0; i<rmax; i++) rliste[i][0]=0;
  if (flag)  // --- Karten mischen: ---
  { for (i=1; i<zmax; i++)
    { k=Math.floor(zmax*Math.random())+1;
      j=tliste[i]; 
      tliste[i]=tliste[k];
      tliste[k]=j;
    }
  }

  // --- alle Karten austeilen, Asse auf Stacks, Rest in Reihen
  j=0;
  for (i=1; i<=zmax; i++)
  { normal(tliste[i]);
    if (tliste[i]%13==0)                // Abfrage auf As
    { k=Math.floor(tliste[i]/13);      	// Abfrage auf Farbe
      add_liste(tliste[i],sliste[k]);   // ab auf die Ablage 
    }
    else 
    { add_liste(tliste[i],rliste[j%rmax]); // ab auf die Reihe 0-7
      j++;
    }
  }
  // -- Listen sind jetzt belegt, wie der Spielanfang vorgibt.
  // -- Jetzt alle Listen (ausser Talon diesmal) darstellen:
  for (i=0; i<smax; i++) zeige_stack(i);  
  for (i=0; i<rmax; i++) zeige_reihe(i);

  // -- gewaehlt haben wir noch nichts
  gewaehlt=-1;
}

function zeige_reihe(nr)
{ if (rliste[nr][0]>0)
  for (var i=1; i<=rliste[nr][0]; i++)
  set_koords('k_'+rliste[nr][i],(xoff+nr*dx),(yoff+dy2*i),i);
}

function zeige_stack(nr)
{ for (var i=1; i<=sliste[nr][0]; i++)
  set_koords('k_'+sliste[nr][i],(xoff_s+nr*dx),yoff_s,i);
}

Was soll die Zeile set_koords('k_'+rliste[nr][i],(xoff+nr*dx),(yoff+dy2*i),i)?
Die Karte mit der Id ":k_xx" soll auf die Koordinate xoff plus irgendwas, yoff plus irgendwas, i.-te Schicht von unten geschoben werden.
Ja und? set_koords() gibt es in Javascript nicht, in keinem der DOMs.
Na und? Der Informatiker sagte damals noch was: "Wenn es was nicht gibt, muss man es sich programmieren!" - Recht hat er, der Mann. Und schon sind wir in Teil 2 des traurigen Kapitels der Browser-Kompatibilität!



zum Seitenanfang
zum Seitenende




9. Crossbrowser-Bibliothek, die zweite

Wir brauchen eine Funktion, die die Karten verschiebt, eine weitere, die deren Koordinaten ausliest, und eine dritte, die das Bild im Layer ändert. Und da sage ich nur: 3 Browser, 4 DOMs :-(

DOM-TypeZugriff auf KoordinatenZugriff auf Bilddarstellung
ie4 document.all[Karten-Id].style.pixelLeft
document.all[Karten-Id].style.pixelTop
document.all[Karten-Id].style.zIndex
document.images[Bild].src
nn4 document.layers[Karten-Id].left
document.layers[Karten-Id].top
document.layers[Karten-Id].zIndex
document.layers[Karten-Id].
   document.images[Bild].src;
(alles in 1 Zeile)
nn5 document.getElementById(Karten-Id).style.left
document.getElementById(Karten-Id).style.top
document.getElementById(Karten-Id).style.zIndex
document.images[Bild].src
neuestes
W3C:
document.getElementById(Karten-Id).style.left
document.getElementById(Karten-Id).style.top
document.getElementById(Karten-Id).style.zIndex
document.getElementById(Bild).
   setAttribute('src',bilddaten)
(alles in 1 Zeile)

Und so sieht dann der 2.Teil der Crossbrowser-Bibliothek aus:

var xyz=new Array();

function set_bild(lay,bild,datei)
{ if (my_browser=='ie4')      document.images[bild].src=datei;
  else if (my_browser=='nn4') document.layers[lay].document.images[bild].src=datei; 
  else if (my_browser=='nn5') document.images[bild].src=datei; // Mozilla >0.9
  else document.getElementById(bild).setAttribute('src',datei);
}

function set_koords(lay,x,y,z)
{ if (my_browser=='ie4')
  { with (document.all[lay].style) 
    { pixelLeft=x;
      pixelTop=y;
      zIndex=z;
    }
  }
  else if (my_browser=='nn4') 
  { document.layers[lay].left=x;
    document.layers[lay].top=y;
    document.layers[lay].zIndex=z;
  } 
  else 
  { with (document.getElementById(lay).style) 
    { left=x; 
      top=y;
      zIndex=z;
    }
  }
}

function get_koords(lay)
{ var x,y,z;
  if (my_browser=='ie4')
  { with (document.all[lay].style)
    { x=pixelLeft;
      y=pixelTop;
      z=zIndex;
    }
  }
  else if (my_browser=='nn4') 
  { x=document.layers[lay].left;
    y=document.layers[lay].top;
    z=document.layers[lay].zIndex;
  } 
  else
  { with (document.getElementById(lay).style)
    { x=left;
      y=top;
      z=zIndex;
    }
  }
  xyz[0]=parseInt(x);
  xyz[1]=parseInt(y);
  xyz[2]=parseInt(z);
}


zum Seitenanfang
zum Seitenende




10. Spielverlauf

Die Karten sollten jetzt ausgebreitet auf dem Bildschirm liegen, jetzt kann es (fast) losgehen: erster Klick auf eine Karte in den Reihen: Karte zum Bewegen markieren, zweiter Klick auf eine der Listen oder auf einen Reihen-Platzhalter: Karte dort ablegen.

Beim ersten Klick ist die globale Variable gewaehlt<0, ist der Zug erlaubt, d.h. es wurde die letzte Karte in einer der Reihen gewählt, erhält sie die Kennziffer der Karte. Zweckmäßigerweise merkt man sich auch die Reihennummer. Nun kann man aber auch auf irgendeine Karte in den Reihen klicken, es wird die letzte Karte der Reihe ermittelt und gewählt.

function klick(nr)
{ var i,j,k;
  get_koords('k_'+nr); // Koordinaten ermitteln
  if (gewaehlt<0)
  { if (xyz[1]>=yoff)  // Karte stammt aus Reihe 
    { x_alt=Math.floor((xyz[0]-xoff)/dx); // Reihennr.
      gewaehlt=rliste[x_alt][rliste[x_alt][0]]; // letzte Karte in Reihe 
      hilite(gewaehlt); // Merker setzen
    }
  }
  else 
  {
// ... der Teil für den 2.Klick
  }
}

Dank der Crossbrowser-Bibliothek sind hilite(nr) und normal(nr) nur Einzeiler und werden hier zwischengeschoben:

function hilite(nr) { set_bild('k_'+nr,'k'+nr,ima_ca[nr].src); }
function normal(nr) { set_bild('k_'+nr,'k'+nr,ima_c[nr].src); }

Beim 2.Klick, also, wenn bereits eine Karte gewählt ist, wird das Ziel ermittelt, entweder eine Ablage oder eine Reihe. Die Marke wird wieder gelöscht, es wird gecheckt, ob die Karte passt, dann der Zug ausgeführt, gewaehlt wieder auf -1 gesetzt, das war's! Beim Ausführen des Zuges wird die gewählte Karte ihrer Reihe entnommen und der neuen Reihe oder Ablage zugefügt, wie im richtigen Leben!

// ... der Teil für den 2.Klick
    if (xyz[1]<yoff)             // Reihe x_alt auf Stack (y<yoff)
    { j=Math.floor(gewaehlt/13); // Ermitteln des Stacks = Farbe der Karte
      k=gewaehlt%13;             // Kartenwert ermitteln
      i=sliste[j][sliste[j][0]]%13;  // letzter Kartenwert im Stack
      if ((k-i)==1)             // Kartenwert um 1 größer als letzte Stackkarte
      { sub_liste(gewaehlt,rliste[x_alt]); // Karte verschwindet aus der Reihe
        add_liste(gewaehlt,sliste[j]);     // und taucht auf der Ablage wieder auf
        // Koordinaten der Karte aktualisieren:
        set_koords('k_'+gewaehlt,(xoff_s+j*dx),yoff_s,sliste[j][0]); 
      }
    } 
    else // von Reihe x_alt nach neuer Reihe (y>=yoff)
    { j=Math.floor((xyz[0]-xoff)/dx); // neue Reihe bestimmen
      k=gewaehlt%13;                  // Wert der gewählten Karte
      i=rliste[j][rliste[j][0]]%13;   // Wert der letzten Karte der neien Reihe
      if ((i-k)==1)         // Kartenwert der anzulegenden Karte muss 1 kleiner sein
      { sub_liste(gewaehlt,rliste[x_alt]); // Karte verschwindet aus der alten Reihe
        add_liste(gewaehlt,rliste[j]);     // und taucht in der neuen wieder auf
        // Koordinaten der Karte aktualisieren:
        set_koords('k_'+gewaehlt,(xoff+j*dx),(yoff+dy2*rliste[j][0]),rliste[j][0]);
      }
    }
    // Auswahl zurücksetzen:
    normal(gewaehlt);
    gewaehlt=-1; 

Was bleibt noch zu tun? Gewählte Karte hochlegen, ausgelöst durch reihe_hoch(nr), die Funktion, mit der wir die Platzhalter ausgestattet haben:

function reihe_hoch(nr)
{ if (gewaehlt>-1)
  { sub_liste(gewaehlt,rliste[x_alt]); // Karte verschwindet aus der alten Reihe
    add_liste(gewaehlt,rliste[nr]);     // und taucht in der neuen wieder auf
    // Koordinaten der Karte aktualisieren:
    set_koords('k_'+gewaehlt,(xoff+nr*dx),(yoff+dy2),rliste[nr][0]);
    // Auswahl zurücksetzen:
    normal(gewaehlt);
    gewaehlt=-1;
  }
}

Damit kann man die Patience "Belagerung" jetzt spielen. Man sollte dem Benutzer aber noch ein paar nette Kleinigkeiten gönnen. Da wäre erstens ein Hilfe-Bildschirm, in dem die Patience erklärt wird, und zweitens eine Automatik, die zu Ende spielt, wenn klar ist, dass die Patience aufgeht.



zum Seitenanfang
zum Seitenende




11. Noch einige Annehmlichkeiten für den Benutzer

Die Function zum Einlesen des Hilfetextes sieht folgendermaßen aus:

function hilfe()
{ var win2=window.open('hilfe.htm','popup','width=560,height=360,scrollbars=auto'); }

Dies öffnet das Dokument hilfe.htm im Zielfenster namens popup, welches mit der Größe 560x360 initialisiert wird. Das Hilfedokument oder andere Popup-Fenster wie Bestenliste sollten im Body-Tag onload="this.focus()" enthalten, denn wenn das Zielfenster schon existiert, bleibt es sonst da, wo es ist, oft verborgen im Hintergrund. Denn Zocker zocken nicht nur eine Runde!

Wichtig an diesem Teil ist, dass es sklavisch genauso hingeschrieben wird, wie es da steht. Ein Leerzeichen vor den Kommata oder danach, und die Größenangabe wird ignoriert.

Warum hier der Weg über Javascript? Warum kein ordinärer Link?
Das Hilfefenster sieht ohne Toolbar einfach schicker aus, verdeckt nicht den ganzen Schirm, und durch Hin- und Herschieben hat man Hilfetext und Spielfeldlayout zusammen im Blickfeld.

Und hier noch die "Abräum-Automatik": sie checkt die untersten Karten der Reihen, ob sie auf eine Ablage passen. Falls ja, wird die Karte dahin geschoben und die Reihen nochmal durchsucht.

function abraeumen()
{ var i,j,k,weiter=true;
  if (gewaehlt>-1) { normal(gewaehlt); gewaehlt=-1; } // Marke löschen
  while(weiter)
  { weiter=false;
    for (i=0; i<rmax; i++) if (rliste[i][0]>0) // von allen 8 Reihen 
    { k=rliste[i][rliste[i][0]];               // die unterste Karte checken
      j=Math.floor(k/13);                      // deren Farbe
      if ((k-sliste[j][sliste[j][0]])==1) // Kenn muss 1 höher sein als auf Ablage
      { weiter=true;
        sub_liste(k,rliste[i]);           // das Verschiebe-Triplett
        add_liste(k,sliste[j]);
        set_koords('k_'+k,(xoff_s+j*dx),yoff_s,sliste[j][0]);
      }
    }        
  }
}


zum Seitenanfang
zum Seitenende




damit ist die Patience fertig!


zur Textauswahl
Gamecraft nachladen

©uja 2002 für www.gamecraft.de