[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

 

  • Marcel Kneig

    schrieb am 2013-11-21 10:50:19

    Hallo könntest du eventuell mal den kompletten QuellCode von der MainForm reinkopieren? oder ggfs. das Projekt als Datei hochladen?

    Ich bekomme nämlich die Fehlermeldung "Error 1 'TicTacToe' is a 'namespace' but is used like a 'type'"

    Danke schön!

    Auf Kommentar antworten

    • Jonny132

      schrieb am 2013-11-22 12:45:47

      Hallo Marcel,

      diese Fehlermeldung kann eigentlich nur kommen, wenn du dein Projekt auch TicTacToe genannt hast und die Codestücke nicht richtig kopiert hast.

      Die Anleitung ist ziemlich Ausführlich also vielleicht liest du sie nochmals Schritt für Schritt durch.
      Eine fertige Solution zum herunterladen bringt dir auch nichts... Du willst ja schliesslich was lernen oder? ;)

      Falls du weiterhin Probleme hast - kann man dir hier im Forum auch weiterhelfen :)

      Auf Kommentar antworten

  • Üstünel

    schrieb am 2013-04-26 23:08:01

    Na ja es ist alle zu oberflächlich erklärt. Ich kann verstehen, das es zu schwer oder zuviel wäre, alle Schritte zu erklären aber ich denke es könnte gehen. Na ja auf jeden Fall muss ich mich überhaupt danken, dass du dich für so etwas anstrengst, also es machst. Aber bitte verständlich erklären und überhaupt die wichtigsten Schritte, was die alles machen.
    Danke :)

    Auf Kommentar antworten

    • Jonny132

      schrieb am 2013-04-29 11:34:54

      Hallo Üstünel,

      was genau verstehst du nicht?
      Das Tutorial ist schon sehr einfach gehalten. Wenn man da nicht mitkommt, sollte man sich die Grundlagen nochmals zugute führen...

      Ich kann nicht bei jedem Tutorial, alle Grundlagen (was sowieso nicht geht) erklären... Etwas Wissen wird schon vorausgesetzt.

      Du erwartest ja auch nicht ein Buch von Stephen Howking zu verstehen, nur weil du lesen kannst oder? ;) Und falls ja - dann versuche es mal.

      Ansonsten helfe ich dir natürlcih gerne im Forum weiter...

      Auf Kommentar antworten

  • Lisa

    schrieb am 2012-11-15 12:28:18

    Ich will ja nichts sagen aber da ist ein fetter fehler drinne in HatSpielerGewonnen wenn man nur die letzten beiden anklickt steht da kein gewonnen ;)

    Auf Kommentar antworten

    • Jonny132

      schrieb am 2012-11-15 15:09:42

      Hallo Lisa,

      was meinst du mit nur die letzten beiden? Ich kann das leider nicht nachvollziehen. Eventuell kannst du das Problem im Forum etwas genäuer beschreiben?

      Die definition von nem 'fetten' Fehler sieht wohl jeder etwas anders ;)

      Auf Kommentar antworten

  • öäkk

    schrieb am 2012-09-24 13:22:37

    ist das ne windows form anwendung oder was

    Auf Kommentar antworten

    • Jonny132

      schrieb am 2012-09-24 13:56:00

      ist das ne windows form anwendung oder was

      Hallo, In der vierten Zeile des Tutorials steht "Erstellen Sie zuerst ein neues Windows Forms Projekt"
      Im Grundgerüst steht "WindowsFormsApplication1"
      Die Screenshots sind alle von ner Form...

      Also ja - Das ist ne Windows Forms Anwendung ;)

      Auf Kommentar antworten

  • Gast

    schrieb am 2012-05-31 08:55:33

    Gesundheit ;D

    Auf Kommentar antworten

  • Destrusor

    schrieb am 2012-02-13 15:17:59

    Funktioniert Perfekt! Vielen Dank :D

    Auf Kommentar antworten

Veröffentlichen Sie ihre Kommentare ...