[C#] Spiel Tutorial: TicTacToe

Drucken
( 14 Votes )
Hauptkategorie: Programmieren Kategorie: C#
Erstellt am 23.12.2011 Zuletzt aktualisiert am 31.05.2012 Geschrieben von Jonny132
Heute zeige ich euch in einem Tutorial wie man das Spiel TicTacToe nachprogrammieren kann. Dieses kleine Spiel ist perfekt dazu geeignet einen Einstieg in die Spieleprogrammierung zu finden da die Logic dahinter noch Halbwegs überschaubar ist.
Erstellen Sie zuerst ein neues Windows Forms Projekt und erstellen Sie eine Neue Klasse (Datei) mit dem Namen TicTacToe.
Zuerst programmieren wir nähmlich die Logic des Spiels. Die Interaktion des Benutzers machen wir zu Schluss.
Um das Grundgerüst für die TicTacToe Logic zu erstellen, müssen wir und zunächst überlegen, welche Properties wir benötigen um dieses Spiel abzubilden.

Enum Spieler: Eine Enumeration ist im Prinzip nicht anderes als ein Status. Die Enum dient also dazu um herauszufinden was für ein Status ein Feld auf dem Spiel hat.
Spielfeldlänge: Gibt an wie groß das Spielfeld in Pixeln ist. Der Wert 300 gibt uns also ein Spielfeld von 300x300 Pixeln.
Spieler1Farbe: Gibt an welche Farbe Spieler1 haben soll.
Spieler2Farbe: Gibt an welche Farbe Spieler2 haben soll.
Delegate und Event OnSpielFertig: Wird geworfen sobald das Spiel zu Ende ist (Wenn ein Spieler gewonnen hat oder unentschieden) und gibt einen Text als Parameter mit.
SpielFeld: Ist das eigentliche Spielfeld. Sozusagen ist es ein Multidimensionales Array in der wir das 3x3 Spielfeld abbilden.
SpielerAmZug: Ist der Spieler der aktuell am Zug ist.
Construktor TicTacToe: Standard Konstruktor ruft erweiterten Konstruktor mit Standardwerten auf.   
Erweiterter Konstruktor: Initialisiert alle Variablen und ruft Reset auf in der das SpielFeld initialisiert wird. Wir trennen dies, damit wir am Ende des Spiels, das Spiel neu starten können in dem wir einfach Reset aufrufen.
InitSpielfeld: Initialisiert unser Spielfeld mit dem Standardwert None und einer 3x3 Matrix.

TicTacToe.cs - Grundgerüst

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
 
namespace WindowsFormsApplication1
{
   public class TicTacToe
   {
     enum Spieler
     {
        none, Spieler1, Spieler2
     }
     public int SpielFeldLaenge { get; set; }
     public Color Spieler1Farbe {get;set;}
     public Color Spieler2Farbe {get;set;}
     public delegate void OnSpielFertig(String Nachricht);
     public event OnSpielFertig SpielFertig;
 
     private Dictionary<int, Dictionary<int, Spieler>> SpielFeld;
     private Spieler SpielerAmZug;
 
     public TicTacToe() : this(300,Color.Green,Color.Red)
    {
    }
 
    public TicTacToe(int spielFeldLaenge, Color spieler1Farbe, 
                 Color spieler2Farbe)
    {
      this.SpielFeldLaenge = spielFeldLaenge;
      this.Spieler1Farbe = spieler1Farbe;
      this.Spieler2Farbe = spieler2Farbe; 
      this.Reset();
    }
 
    public void Reset()
    {
       this.SpielerAmZug = Spieler.Spieler1;
       InitSpielFeld();
    }
 
    private void InitSpielFeld()
    {
       this.SpielFeld = new Dictionary<int, Dictionary<int, Spieler>>();
       for (int x = 0; x < 3; x++)
       {
          this.SpielFeld.Add(x, new Dictionary<int, Spieler>());
          for (int y = 0; y < 3; y++)
          {
             this.SpielFeld[x].Add(y, Spieler.none);
           }
        }
      }
   }
}

So nun haben wir das Grundgerüst. Als nächstes benötigen wir 2 Hilfsfunktionen die uns zurückgeben ob das Feld noch freie Felder hat, ansonsten ist das Spiel vorbei und eine Hilfsfunktion die uns sagt Ob ein Spieler gewonnen hat.

In der Hilfsfunktion HatNochFreieFeldermachen wir nichts anderes als über das Spielfeld zu iterieren und wenn ein Freies Feld mit dem Status none gefunden wurde ein true zurückgibt.

Hilfsfunktion HatNochFreieFelder()

private bool HatNochFreieFelder()
 {
   for (int x = 0; x < 3; x++)
   {
      for (int y = 0; y < 3; y++)
      {
         if (SpielFeld[x][y] == Spieler.none)
           return true;
       }
     }
     return false;
 }

In der Hilfsfunktion HatSpielergewonnen überprüfen wir alle Möglichkeiten ob ein Spiler gewonnen hat. Dazu vergleichen wir zuerst ob Horizontal alle 3 Felder den gleichen Status haben und dann ob sie Vertikal den gleichen Status haben.
Zu Schluss überprüfen wir noch die 2 Sonderfälle der Diagonalen. Natürlich darf der Status den wir Prüfen nicht none sein, deshalb beziehen wir diesen Wert mitein.

Hilfsfunktion HatSpielerGewonnen()

 private bool HatSpielerGewonnen()
 {
   for (int x = 0; x < 3; x++)
   {
      if (SpielFeld[x][0]==SpielFeld[x][1]&&SpielFeld[x][0]==SpielFeld[x][2])
       return SpielFeld[x][0] != Spieler.none;
 
     if (SpielFeld[0][x]==SpielFeld[1][x] && SpielFeld[0][x]==SpielFeld[2][x])
       return SpielFeld[0][x] != Spieler.none;
   }
 
   if (SpielFeld[0][0]==SpielFeld[1][1] && SpielFeld[0][0]==SpielFeld[2][2])
     return SpielFeld[0][0] != Spieler.none;
 
   if (SpielFeld[2][0]==SpielFeld[1][1] && SpielFeld[2][0]==SpielFeld[0][2])
     return SpielFeld[2][0] != Spieler.none;
 
   return false;
 }
Nun haben wir schon einen Großteil der Logic geschafft. Jetzt brauchen wir nurnoch die Verarbeitung eines Klicks und das Zeichnen des Spielfelds.

In der MouseKlick Verarbeitung überprüfen wir ob der Spieler nicht none ist, wäre er das, ist das Spiel vorbei und die Klicks sollen keine Verarbeitung mehr ausführen.
Zur Hilfe erstellen wir zuerst ein paar Hilfsvariablen in der wir ausrechnen auf welchen Index geklickt wurde. Achtung eigentlich sollte man hier überprüfen ob der Index nicht zu Hoch ist da ansonsten eine Exception geworfen wird zB index 3 gibt es nicht. Ich habe jedoch die ganze Fehlerbehandlung weggelassen um den Code halbwegs schlank zu halten.
Wir überprüfen ob der geklickte Index noch keine Spielerzuweisung hat. Hat er das nicht weisen wir den aktuellen Spieler zu.
Nun kommt die Hilfsfunktion zum Einsatz in der wir Prüfen ob der Spieler gewonnen hat. Hat er das wir das Event geworfen mit der Meldung wer gewonnen hat.
Hat noch keiner gewonnen wir überprüft ob noch Lehre Felder zur Auswahl stehen. Falls nicht ist es ein Unentschieden. Am Schluss setzen wir nurnoch den aktuellen Spieler um.

MouseKlick Verarbeitung

public void MouseKlick(Point koordinaten)
 {
   if (SpielerAmZug != Spieler.none)
   {
     int feldLaenge = this.SpielFeldLaenge / 3;
     int xIndex = koordinaten.X / feldLaenge;
     int yIndex = koordinaten.Y / feldLaenge;
     if (SpielFeld[xIndex][yIndex] == Spieler.none)
     {
        SpielFeld[xIndex][yIndex] = SpielerAmZug;
        if (HatSpielerGewonnen())
       {
         if (SpielFertig != null)
           SpielFertig("Spieler "+SpielerAmZug.ToString()+" hat gewonnen!");
 
         SpielerAmZug = Spieler.none;
       }
       else if (!HatNochFreieFelder())
       {
         if (SpielFertig != null)
           SpielFertig("Unentschieden!");
 
        SpielerAmZug = Spieler.none;
       }
 
       if (SpielerAmZug == Spieler.Spieler1)
         SpielerAmZug = Spieler.Spieler2;
       else if (SpielerAmZug == Spieler.Spieler2)
         SpielerAmZug = Spieler.Spieler1;
    }
   }
 }
In der Draw - Funktion benötigen wir ein gültiges GraphicsObject. Zuerst werden die Raster des Spielfelds gezeichnet und danach noch 'X' für Spieler1 und 'O' für Spieler2. Diesen Code müsst Ihr euch selber anschauen da, das meiste nur Berechnungen sind, von wo bis wo eine Linie gezeichnet werden soll, wie groß etwas sein soll usw.

Draw Verarbeitung

 public void Draw(Graphics g)
 {
   int feldLaenge = this.SpielFeldLaenge / 3;
   for (int i = 1; i < 3; i++)
   {
     g.DrawLine(new Pen(Color.Black), new Point(0, i * feldLaenge),
                       new Point (SpielFeldLaenge, i * feldLaenge));
     g.DrawLine(new Pen(Color.Black), new Point(i * feldLaenge, 0),
                       new Point(i * feldLaenge, SpielFeldLaenge));
   }
   for (int x = 0; x < 3; x++)
   {
     for (int y = 0; y < 3; y++)
     {
       if (SpielFeld[x][y] == Spieler.Spieler1)
      {
         g.DrawLine(new Pen(Spieler1Farbe, 3),
                    new Point(x * feldLaenge, y*feldLaenge),
                    new Point((x * feldLaenge)+feldLaenge, 
                              (y*feldLaenge)+feldLaenge));
         g.DrawLine(new Pen(Spieler1Farbe, 3),
                    new Point(x * feldLaenge, (y*feldLaenge) + feldLaenge),
                    new Point((x*feldLaenge)+feldLaenge,(y*feldLaenge)));
      }
      else if (SpielFeld[x][y] == Spieler.Spieler2)
      {
         g.DrawEllipse(new Pen(Spieler2Farbe, 3), x*feldLaenge,
                       y*feldLaenge,feldLaenge, feldLaenge);
      }
     }
   }
 }
Mit Abschluss dieser Funktion haben wir eine fertige Logic die wir nun in die Form Main benützen können.
Öffnen Sie nun also die Codeansicht der MainForm und fügen Sie folgendes Code - Snippet ein. Der Code dürfte klar sein. Sollten trotzdem Fragen aufkommen können Sie jederzeit im Forum nachfragen.

MainForm Code

TicTacToe tictactoe;
 private void Form1_Load(object sender, EventArgs e)
 {
   this.DoubleBuffered = true;
   this.ResizeRedraw = true;
   this.Paint += new PaintEventHandler(Form1_Paint);
   this.MouseClick += new MouseEventHandler(Form1_MouseClick);
 
   tictactoe = new TicTacToe(this.Size.Width < this.Size.Height?
               this.Size.Width - 10:this.Size.Height - 30, 
               Color.Green, Color.Red);
   tictactoe.SpielFertig += new TicTacToe.OnSpielFertig(
               tictactoe_SpielerGewonnen);
 }
 
 void Form1_MouseClick(object sender, MouseEventArgs e)
 {
   tictactoe.MouseKlick(e.Location);
   this.Refresh();
 }
 
 void Form1_Paint(object sender, PaintEventArgs e)
 {
   tictactoe.SpielFeldLaenge = this.Size.Width < this.Size.Height ? 
                               this.Size.Width - 10 : this.Size.Height - 30;
   tictactoe.Draw(e.Graphics);
 }
 
 void tictactoe_SpielerGewonnen(string Nachricht)
 {
   MessageBox.Show(Nachricht);
 }
 
 private void button1_Click(object sender, EventArgs e)
 {
   tictactoe.Reset();
   this.Refresh();
 }

 

 

 

Das Ergebnis:
TicTacToe

Wie Sie sehen können Sie das Form auch Vergrößern und das Spielfeld wird angepasst:
TicTacToe Resize

 

Veröffentlichen Sie ihre Kommentare ...