[C++] 4 Gewinnt mit KI auf Konsolenbasis

In diesem Artikel möchte ich das Spiel, das ich als AIT-Projekt für die Schule programmiert habe, vorstellen.
Es ist das Spiel „4 Gewinnt“, das sowohl mit einer „KI“ (Künstliche Intelligenz) als auch mit einem „dummen“ PC-Gegner ausgestattet ist. Dafür, dass es mein erstes richtiges Programm war, finde ich es ziemlich gelungen.

Mein Spiel kann per Maus bzw. per Tastatur gesteuert werden und sieht so aus:
Das Einstellungsmenü
Das Menü des Spiels

Das Spielfeld
Das Spielfeld des Spiels

Die Gewinnermeldung
Meldung

Download
Das kompilierte Spiel als exe: viergewinnt.exe
Das kompilierte Spiel als zip: viergewinnt.zip
Der Quelltext und die Borland-Projektdatei als zip: viergewinnt_pr.zip
Der Quelltext, die Projektdatei und die exe als zip: viergewinnt_exe_pr.zip (alles komplett)

Programmierung
Wie schon gesagt, habe ich das Spiel in C++ programmiert, es kam aber trotzdem keine Objektorientierte Programmierung zum Einsatz. (Obwohl das ja C++ eigentlich ausmacht) Das ganze Spiel besteht aus 18 Funktionen, von denen 3 nichts direkt mit dem Spiel zu tun haben:

  • Initialisierung der Maus
  • Einfabe von Strings
  • Eingabe von Integers

Sie werden später (klick!) ausführlich erklärt. Hier ist erstmal der komplette Code:

/******************************************************************************
  Programmname: 4 Gewinnt
  Von:          Simon H.
  Stand:        16.6.2008 22:32
  Version:      8.0
******************************************************************************/
#include
#include
#include
#include

#include
#include 

/**
   Konstantendefinitionen
   ----------------------
   Diese Konstanten sollen die einfache Änderung der Programmeigenschaften
   ermöglichen. Sie werden im Programm anstelle der eigentlichen Werte
   angegeben.
*/
// FARBEN BEGINN
const int FARBE_WEISS      = 15;
const int FARBE_SCHWARZ    = 0;
const int FARBE_BLAU       = 9;
const int FARBE_MAGENTA    = 5;
const int FARBE_GELB       = 14;
const int FARBE_BRAUN      = 6;
const int FARBE_ROT        = 12;
const int FARBE_BLINK      = 128;
const int FARBE_GRUEN      = 10;
const int FARBE_CYAN       = 3;
// FARBEN ENDE

const int LEERES_FELD      = 0;                                                  // Konstante für die Markierung eines leeren Feldes
const int STEIN_SPIELER1   = 1;                                                  // Konstante für die Markierung eines Feldes von Spieler 1
const int STEIN_SPIELER2   = 2;                                                  // Konstante für die Markierung eines Feldes von Spieler 2
const int SPIELFELD_HOEHE  = 8;                                                  // Konstante für Spielfeldhöhe
const int SPIELFELD_BREITE = 8;                                                  // Konstante für Spielfeldbreite
const int TASTE_ENTER      = 13;                                                 // ASCII-Code der Enter-Taste
const int TASTE_RECHTS     = 77;                                                 // ASCII-Code der Rechts-Taste
const int TASTE_LINKS      = 75;                                                 // ASCII-Code der Links-Taste
const int TASTE_HOCH       = 72;                                                 // ASCII-Code der Rechts-Taste
const int TASTE_RUNTER     = 80;                                                 // ASCII-Code der Links-Taste
const int TASTE_PFEILTASTE = 0;                                                  // wird von getch() geliefert, wenn eine
                                                                                 // Sondertaste gedrückt wird, wie z.B.
                                                                                 // eine Pfeiltaste oder Enter.
                                                                                 // der 2. Aufruf von getch() liefert dann
                                                                                 // den entsprechenenden ASCII-Code der
                                                                                 // Taste

/**
   Struktur des Spielfelds
   -----------------------
   Die Struktur des Spielfelds ergibt sich aus den folgenden Konstanten:
   - SPIELFELD_HOEHE
   - SPIELFELD_BREITE
*/
struct spielfeld  // Neuer Datentyp "spielfeld"
{                 // Enthält eine Eigenschaft ("array"), die das Spielfeld speichert
  int array[SPIELFELD_HOEHE+1][SPIELFELD_BREITE+1];
};

/**
  Struktur der Einstellungen
  --------------------------
  In diesem Datentyp können die Spieleinstellungen gespeichert werden.
  (Spielernamen, Schwierigkeit, usw.)
*/
struct settings
{
  string spieler1;   //Name vom ersten Spieler
  string spieler2;   //Name vom zweiten Spieler bzw. Computer
  int  difficulty;   //Schwierigkeitsstufe (Entscheidet, ob die KI verwendet wird oder nicht)
  int  against_who;  //PC als Gegner / Anderer Spieler als Gegner
  bool use_mouse;    //Speichert die Verwendung der Maus (true/false)
};

/**
   Initialisierung der Maus
   mouseInit initialisiert die Maus und gibt die Anzahl der Buttons zurück.
   (Das wird in diesem Prgramm aber nie benötigt, nur die Initialisierung ist
   wichtig)
*/
DWORD mouseInit ()
{
    DWORD mode;
    GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),&mode);                    //bisherigen Modus in mode speichern
    SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),mode|ENABLE_MOUSE_INPUT);  //Modus um ENABLE_MOUSE_INPUT erweitern,
                                                                             //Dies aktiviert die Maus.
    DWORD buttons;
    GetNumberOfConsoleMouseButtons(&buttons);                                //Anzahl der Maustasten ermitteln

    return buttons;
}

/**
   Textfarbe ändern
   textfarbe() benötigt als Parameter eine Int-Zahl farbe und gibt nichts zurück.
   Die Funktion führt eine Funktion der Konsole aus, die den nachfolgenden Text in der
   Farbe ausgibt, die in farbe steht.
*/
void textfarbe(int farbe)
{ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), farbe); }

/**
  void header():
  Diese Funktion zeigt die Überschrift auf dem Bildschirm an und leert davor das
  Fenster!
*/
void header(void)
{
clrscr();                                                //Fenster leeren
textfarbe(FARBE_ROT);
cout< <"\t\t*********************************"<<<"\t\t***";
textfarbe(FARBE_GRUEN);                                 //Text grün ausgeben
cout<<" Willkommen bei 4 Gewinnt!";
textfarbe(FARBE_ROT);                                   //Text rot ausgeben
cout<<" ***"<<<"\t\t*********************************"<<<<<"------------------------ HILFE ------------------------"<<<"| Wenn sie die Einstellungen veraendern wollen, nutzen |"<<<"| Sie bitte die Pfeiltasten. Die Hoch-Taste waehlt     |"<<<"| den jeweils darueberliegenden Menuepunkt aus.        |"<<<"| Die Runter-Taste macht genau das Gegenteil.          |"<<<"| -----------------------------------------------------|"<<<"| Wenn Sie alle Einstellungen vorgenommen haben,       |"<<<"| koennen Sie das Spiel starten, indem sie den letzten |"<<<"| Menuepunkt auswaehlen und die Enter-Taste druecken.  |"<<<"| -----------------------------------------------------|"<<<"| Viel Spass!                                          |"<<<"--------------------------------------------------------"<<<<"Auf der naechsten Seite sind die Regeln zu sehen."<<<<<"------------------------ REGELN ------------------------"<<<"| Im Spiel 'Vier Gewinnt' geht es darum, als erster    |"<<<"| Spieler 4 Spielsteine nebeneinander oder ueber-      |"<<<"| einander zu haben.                                   |"<<<"| Dabei sind auch Diagonale Kombinationen moeglich.    |"<<<"| -----------------------------------------------------|"<<<"| Sie koennen wahlweise gegen einen anderen Spieler    |"<<<"| oder gegen den PC spielen.                           |"<<<"| Wenn Sie gegen den PC spielen, koennen sie zwischen  |"<<<"| den Schwierigkeitsstufen 'einfach' und 'schwierig'   |"<<<"| waehlen.                                             |"<<<"| -----------------------------------------------------|"<<<"| Das Spiel wird mit der Maus / der Tastatur gesteuert.|"<<<"--------------------------------------------------------"<<<<"Mit einem Tastendruck kommen Sie wieder zu den Einstellungen."<< Anderer Spieler
                                                             //Nicht immer wieder "Spieler 2" im Feld.

  bool schleife_durchlaufen = true;                          //true, wenn die Schleife ganz unten durchlaufen werden soll
  int taste = -1;                                            //Zwischenspeicher für die vom Benutzer gedrückte Taste

cout< <"Hinweis: Die Hilfe/Regeln kann mit der h-Taste aufgerufen werden"<<<<"Steuerung: \t"; textfarbe(FARBE_WEISS);
  }
  else                                                      //wenn der erste Menüpunkt nicht gewählt ist
  {
    cout<<"Steuerung: \t";
  }

  if(einstellungen.use_mouse)                               //wenn die Maus eingeschaltet ist
  {
   cout<<"[";
   textfarbe(FARBE_BLINK);
   cout<<" Maus ";
   textfarbe(FARBE_WEISS);
   cout<<"]";
   cout<<" Tastatur (Pfeiltasten) ";
  }
  else                                                       //wenn die Maus ausgeschaltet ist
  {
    cout<<" Maus ";
    cout<<"[";
    textfarbe(FARBE_BLINK);
    cout<<" Tastatur (Pfeiltasten) ";
    textfarbe(FARBE_WEISS);
    cout<<"]";
  }

  cout<<<"Spielgegner: \t"; textfarbe(FARBE_WEISS);
  }
  else                                                      //wenn der zweite Menüpunkt nicht gewählt ist
  {
    cout<<"Spielgegner: \t";
  }

  if(einstellungen.against_who == 1)                       //wenn der PC als Gegner eingestellt ist
  {
   cout<<"[";
   textfarbe(FARBE_BLINK);
   cout<<" PC ";
   textfarbe(FARBE_WEISS);
   cout<<"]";
   cout<<" Anderer Spieler ";
  }
  else                                                     //wenn ein anderer Spieler als Gegner eingestellt ist
  {
    einstellungen.spieler2 = temp_spieler2;
    cout<<" PC ";
    cout<<"[";
    textfarbe(FARBE_BLINK);
    cout<<" Anderer Spieler ";
    textfarbe(FARBE_WEISS);
    cout<<"]";
  }

  cout<<<"Schwierigkeit: \t"; textfarbe(FARBE_WEISS);
  }
  else
  {                                                       //wenn der dritte Menüpunkt nicht gewählt ist
    cout<<"Schwierigkeit: \t";
  }

  if(einstellungen.against_who == 2)                      //wenn ein anderer Spieler als Gegner eingestellt ist
  {
    textfarbe(FARBE_ROT);
    cout<<"Diese Einstellung ist nur beim Spielen gegen den PC verfuegbar";
    textfarbe(FARBE_WEISS);
  }
  else                                                    //wenn der PC als Gegner eingestellt ist
  {
  if(einstellungen.difficulty == 1)                       //wenn KI aus ist (Schwierigkeit einfach)
  {
   cout<<"[";
   textfarbe(FARBE_BLINK);
   cout<<" Einfach ";
   textfarbe(FARBE_WEISS);
   cout<<"]";
   cout<<" Schwierig";
  }
  else                                                    //wenn KI an ist (Schwierigkeit schwierig)
  {
    cout<<" Einfach ";
    cout<<"[";
    textfarbe(FARBE_BLINK);
    cout<<" Schwierig ";
    textfarbe(FARBE_WEISS);
    cout<<"]";
  }
  }

  cout<<<"Spielername 1: \t"; textfarbe(FARBE_WEISS);
  }
  else                                                   //wenn der vierte Menüpunkt nicht gewählt ist
  {
    cout<<"Spielername 1: \t";
  }

  if(change_name1)                                       //wenn der erste Name geändert werden soll
  {
    getline(cin, einstellungen.spieler1);                //Name mithilfe von getline erhalten und speichern
    pos = 1;                                             //wieder oben anfangen
    change_name1 = false;                                //Der Name wurde geändert, muss also nich nochmal geändert werden
    schleife_durchlaufen = false;                        //Schleife soll NICHT durchlaufen werden, sondern
    menu(einstellungen);                                 //das Menü soll erneut ausgegeben werden
  }

   cout<<"[";
   cout<<<"]";

  cout<<<"Spielername 2: \t"; textfarbe(FARBE_WEISS);
  }
  else                                                  //wenn der fünfte Menüpunkt nicht gewählt ist
  {
    cout<<"Spielername 2: \t";
  }

  if(einstellungen.against_who == 2)                    //wenn der Gegner ein anderer Spieler ist
  {
   if(change_name2)                                     //wenn der zweite Name geändert werden soll
   {
    getline(cin, einstellungen.spieler2);               //Name mithilfe von getline erhalten und speichern
    temp_spieler2 = einstellungen.spieler2;             //Name zwischenspeichern, weil er sonst verloren gehen kann
    pos = 1;                                            //wieder oben anfangen
    change_name2 = false;                               //Der Name wurde geändert, muss also nich nochmal geändert werden
    schleife_durchlaufen = false;                       //Schleife soll NICHT durchlaufen werden, sondern
    menu(einstellungen);                                //das Menü soll erneut ausgegeben werden
   }
   cout<<"[";
   cout<<<"]";
  }
  else                                                  //wenn der Gegner der PC ist,
  {                                                     //lautet der Name immer Computer und
   textfarbe(FARBE_ROT);                                //kann deshalb auch nicht geändert werden
   einstellungen.spieler2 = "Computer";
   cout<<<<<"-> Spiel starten (mit ENTER) < -";
    textfarbe(FARBE_WEISS);
  }
  else                                                 //wenn Position 6 nicht gewählt ist
  {
   cout<<"-> Spiel starten < -";
  }
  cout<< 6)           //Wenn sie die Runter-taste war und nicht schon der letzte Punkt
        { pos++; }                                     // wird der nächsteMenüpunkt markiert

        if(taste == TASTE_HOCH && pos > 1)             //Wenn sie die Runter-Taste war und nicht schon der letzte Punkt
        { pos--; }                                     //aktiv ist, wird der darüberliegende Menüpunkt aktiviert

        if(taste == TASTE_RECHTS || taste == TASTE_LINKS)  //bei rechts/links wird die jeweilige Einstellung geändert
        {
         switch(pos)                                       //je nach dem, was gerade aktiv ist.
         {
          case 1: if(einstellungen.use_mouse)              //Maus aktivieren / deaktivieren
                  { einstellungen.use_mouse = false; }     //deaktivieren
                  else
                  { einstellungen.use_mouse = true; }      //aktivieren
                  break;
          case 2: if(einstellungen.against_who == 1)       //Gegner ändern
                  {
                    einstellungen.against_who = 2;         //Anderer Spieler
                  }
                  else
                  {
                     einstellungen.against_who = 1;        //PC
                  }
                  break;
          case 3: if(einstellungen.against_who == 1)       //Schwierigkeit ändern
                  {
                   if(einstellungen.difficulty == 1)
                   { einstellungen.difficulty = 2; }       //Schwierig (KI)
                   else
                   { einstellungen.difficulty = 1; }       //Einfach (random)
                  }
                  break;
          case 4: change_name1 = true;                     //Ersten Namen ändern
                  break;
          case 5: if(einstellungen.against_who == 2)       //Zweiten namen ändern, wenn nicht der
                  {                                        //PC der Gegner ist.
                    change_name2 = true;
                  }
                  break;                                   //Sonst nichts tun
          default: break;
         }
        }
       Sleep(10); //CPU entlasten
      }
      menu(einstellungen);                                //Menü neu aufbauen
      break;                                              //Schleife abbrechen
    }
  }
}

/**
  start() initialisiert die Einstellungen und stellt den Startbildschirm dar.
  Danach wird das menu() angezeigt, wo die Einstellungen verändert werden können.
  Die Funktion start() verlangt als Parameter eine Referenz auf die Einstellungsstruktur.
*/
void start(settings &einstellungen)
{
/**
  Initialisierung der Einstellungen
  ---------------------------------
  Die Referenz auf die Struktur &einstellungen wird mit Standartwerten beschrieben.
  Diese Werte sind in der Funktion menu() änderbar.
*/
einstellungen.against_who = 1;                 //Gegner ist der PC
einstellungen.use_mouse = true;                //Maus ist an
einstellungen.difficulty = 1;                  //Schwierigkeit ist einfach
einstellungen.spieler1 = "Spieler 1";          //Spieler 1 heißt Spieler 1
einstellungen.spieler2 = "Spieler 2";          //Spieler 2 heißt Spieler 2

/**
  Als nächstes wird das Logo ausgegeben.
  Mit textfarbe() wird die Farbe des Textes so lange geändert, bis textfarbe() wieder aufgerufen wird.
*/
     system("title 4 Gewinnt");                //Titel des Fensters auf "4 Gewinnt setzen"
     textfarbe(FARBE_GELB);
     cout< <"°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°"<<<"............................................................................."<<<".##............######...########.##......##.####.##....##.##....##.########.."<<<".##....##.....##....##..##.......##..##..##..##..###...##.###...##....##....."<<<".##....##.....##........##.......##..##..##..##..####..##.####..##....##....."<<<".##....##.....##...####.######...##..##..##..##..##.##.##.##.##.##....##....."<<<".#########....##....##..##.......##..##..##..##..##..####.##..####....##....."<<<".......##.....##....##..##.......##..##..##..##..##...###.##...###....##....."<<<".......##......######...########..###..###..####.##....##.##....##....##....."<<<"............................................................................."<<<"°°°°°°°°°°°°°°°°°°°°°°°°°°";
     textfarbe(FARBE_GRUEN);
     cout<<" von Simon H.";
     textfarbe(FARBE_GELB);
     cout<<"°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°"<<< 0; i--)        //zähler dekrementieren
     {
      cout< <"\rSpiel startet in "<<<" Sekunden!";  // "\r" überschreibt die Zeile
      Sleep(1000);   //1 Sekunde warten
     }
     // ------------ COUNTDOWN ENDE ----------------------------

menu(einstellungen);    // Aufruf der Funktion menu() mit der Übergabe der Einstellungsstruktur
header();               // Ausgabe des Headers
textfarbe(FARBE_GELB);
cout<<"Die Einstellungen wurden gespeichert!"<<<"Das Spiel startet jetzt!"<<= SPIELFELD_BREITE &&
     zeile <= SPIELFELD_HOEHE &&
     spalte >= 0 &&
     zeile >= 0)
  {
    return true;               //Punkt ist gültig
  }
  else
  {
    return false;             //Punkt ist nicht gültig
  }
}

/**
   show_spielfeld ist eine der wichtigsten Funktionen in meinem Spiel, da sie
   für die Ausgabe der Oberfläche sorgt.
   Es wird nur eine Referenz auf das Spielfeld benötigt und nichts zurückgegeben
*/
void show_field(spielfeld &mein_spielfeld)  //mit & kann die variable in der Funktion verändert werden
{
     clrscr();                              //Inhalt des Bildschirms löschen
     int i, j;

     textfarbe(FARBE_BRAUN);
     for(i = 0; i < SPIELFELD_BREITE*4; i++) //linie für Spielfeld
     { cout<<"-"; }
     cout<< SPIELFELD_BREITE; i++)
     {
           textfarbe(FARBE_CYAN);
           cout<<<" | ";
           textfarbe(FARBE_WEISS);
     }
     cout<< SPIELFELD_BREITE*4; i++) //linie für Spielfeld
     { cout<<"-"; }
     cout<< SPIELFELD_HOEHE; i++)            //Erste Dimension
     {
           for(j = 0; j < SPIELFELD_BREITE; j++)     //Zweite Dimension
           {

             switch(mein_spielfeld.array[i][j])
             {
                //Die folgenden Konstanten wurden ganz oben im Programm deklariert
                case  LEERES_FELD:    cout<<" "; break;
                case  STEIN_SPIELER1: textfarbe(FARBE_GRUEN);
                                      cout<<"X";
                                      textfarbe(FARBE_WEISS);
                                      break;
                case  STEIN_SPIELER2: textfarbe(FARBE_ROT);
                                      cout<<"O";
                                      textfarbe(FARBE_WEISS);
                                      break;
                default:              cout<<" "; break;
             }
             textfarbe(FARBE_BRAUN);
             cout<<" | ";             //Trennstrich zwischen den Feldern
             textfarbe(FARBE_WEISS);
           }
           cout<< SPIELFELD_BREITE*4; i++) //linie für Spielfeld
     { cout<<"-"; }
     cout<= 0)
  {
   aktuelle_zeile--;
  }

  if(aktuelle_zeile < 0)
  {
    return -1; //spalte voll!
  }

  return aktuelle_zeile;
}

/**
  Diese Funktion hat 7 Parameter:
  - Spieler: Von welchem Spieler soll eine Reihe gesucht werden?
  - laenge: Wie lange soll die Reihe sein? (normalerweise 4, bei der KI aber 2/3)
  - richtung_spalte: in welche Richtung soll in der Breite gesucht werden?
    - 1:  rechts
    - 0:  stehen bleiben   (wird bei Suche nach Stapeln benötigt)
    - -1: links
  - richtung_zeile: in welche Richtung soll in der Höhe gesucht werden?
    - 1:  nach unten
    - 0:  stehen bleiben   (wird bei Suche nach nebeneinanderliegenden benötigt)
    - -1: nach oben
  - start_spalte: Wo wird mit der Suche angefangen? (spalte)
  - start_zeile:  Wo wird mit der Suche angefangen? (zeile)
  - feld: Das Spielfeld

  Der Rückgabewert ist je nach dem, ob eine Reihe existiert true oder false.
*/
bool ist_reihe(int spieler, int laenge, int richtung_spalte, int richtung_zeile, int start_spalte, int start_zeile, spielfeld feld)
{
 //Wenn ungültige Positionen erhalten wurde, wird gleich abgebrochen und false zurückgegeben
 if (!gueltig( start_spalte, start_zeile ) ||
     !gueltig( start_spalte + richtung_spalte * laenge, start_zeile + richtung_zeile * laenge ))
     {
       return false;
     }

 //ansonsten wird im Spielfeld nach den Reihen (Kombinationen) gesucht.
 for(int i = 0; i < laenge; i++)
 {
   if(feld.array[start_zeile + (richtung_zeile*i)][start_spalte + (richtung_spalte *i)] != spieler)
   {  //Sobald ein Stein nicht von spieler ist, wird abgebrochen und false zurückgegeben
   return false;
   }
 }
 return true;   //Wenn das Programm bis hier kommt, wurde eine Reihe gefunden.
}

/**
   check() ist die Funktion, die nach jedem Spielzug aufgerufen wird.
   Sie prüft, ob einer der Spieler gewonnen hat.
*/
int check(spielfeld &feld)
{
for(int zeile = 0; zeile < SPIELFELD_HOEHE; zeile++)        //1. Dimension
{
  for(int spalte = 0; spalte < SPIELFELD_BREITE; spalte++)  //2. Dimension
  {
          //Hat der erste Spieler gewonnen?
          if(ist_reihe(STEIN_SPIELER1, 4, -1,  1, spalte, zeile, feld) ||       //diagonal
             ist_reihe(STEIN_SPIELER1, 4, -1,  0, spalte, zeile, feld) ||       //nebeneinander
             ist_reihe(STEIN_SPIELER1, 4, -1, -1, spalte, zeile, feld) ||       //diagonal
             ist_reihe(STEIN_SPIELER1, 4,  0, -1, spalte, zeile, feld) ||       //übereinander
             ist_reihe(STEIN_SPIELER1, 4,  1, -1, spalte, zeile, feld) ||       //diagonal
             ist_reihe(STEIN_SPIELER1, 4,  1,  0, spalte, zeile, feld) ||       //nebeneinander
             ist_reihe(STEIN_SPIELER1, 4,  1,  1, spalte, zeile, feld))         //diagonal
          {
           return STEIN_SPIELER1;
          }

          //Hat der zweite Spieler gewonnen?
          if(ist_reihe(STEIN_SPIELER2, 4, -1,  1, spalte, zeile, feld) ||       //diagonal
             ist_reihe(STEIN_SPIELER2, 4, -1,  0, spalte, zeile, feld) ||       //nebeneinander
             ist_reihe(STEIN_SPIELER2, 4, -1, -1, spalte, zeile, feld) ||       //diagonal
             ist_reihe(STEIN_SPIELER2, 4,  0, -1, spalte, zeile, feld) ||       //übereinander
             ist_reihe(STEIN_SPIELER2, 4,  1, -1, spalte, zeile, feld) ||       //diagonal
             ist_reihe(STEIN_SPIELER2, 4,  1,  0, spalte, zeile, feld) ||       //nebeneinander
             ist_reihe(STEIN_SPIELER2, 4,  1,  1, spalte, zeile, feld))         //diagonal
          {
           return STEIN_SPIELER2;
          }
  }
}
return 0;  //Keiner hat gewonnen, aber es muss etwas returned werden.

}

/**
   Spieler gegen PC
   Schwierigkeitsstufe 1 (einfach)
   In dieser Stufe spielt der PC nach Zufall...

   Die Funktion benötigt das Spielfeld als Parameter und gibt eine Ganzzahl zurück.
*/
int pc_easy(spielfeld &feld)
{
     randomize();                               //Zufallsgenerator starten
     int aktuelle_position = SPIELFELD_HOEHE;
     int durchlauf = 0;
     int spalte = random(SPIELFELD_BREITE+1);    //zufallszahl für die spalte! +1, weil sonst nie die letzte spalte beschrieben wird

     //solange aktuelle spalte voll ist und noch nicht alle spalten kontrolliert wurden
     while(gueltig(spalte, 0) &&
           feld.array[0][spalte] != LEERES_FELD &&
           choose_free_position(feld, spalte) == -1 &&
           durchlauf < 3)
     {
        if(spalte < SPIELFELD_BREITE)             //wenn nicht die letzte spalte die aktuelle ist
        {
           spalte++;                              //nächste spalte
        }
        else                                      //wenn sie die aktuelle ist
        {
          spalte = 0;                             //dann bei erster spalte anfangen
          durchlauf++;                            //jetzt fängt ein neuer durchlauf an, also counter ++
        }
     }

      aktuelle_position = choose_free_position(feld, spalte);
      if(aktuelle_position != -1)   //wenn die Spalte nicht voll ist,
      {                             //steht in aktuelle_position die nächste freie stelle
         feld.array[aktuelle_position][spalte]  = STEIN_SPIELER2;
      }
      else
      {
         return -2; //keiner hat gewonnen ---> unentschieden
      }

return 0;
}

/**

   -----------------------
   ---------- KI ---------
   -----------------------

   Allgemein
   ---------
   pc_difficult ist die Funktion, die ausgeführt wird, wenn der PC am Zug ist und
   die Schwierigkeitsstufe 2 eingestellt wurde.

   Funktionsweise
   ---------------
   Die Funktion sucht im Array der struktur (spielfeld) zuerst nach 3er-kombinationen
   vom Spieler bzw. vom PC.

   Wenn eine Kombination gefunden wurde und die Kombination dem PC gehört, wird
   sie mit einem Stein vom PC (Spieler 2) vervollständigt.
   ---> PC gewinnt

   Wenn eine Kombination gefunden wurde und die Kombination dem Spieler 2 gehört,
   wird sie mit einem Stein vom PC (Spieler 2) vervollständigt.
   ---> PC verhindert, dass der Spieler die Kombination vervollständigt

   Wenn keine 3er-Kombination gefunden wurde, wird das Ganze mit 2er-Kombinationen
   wiederholt. Das wird aber nur mit den nebeneinander / übereinanderliegenden
   Steinen gemacht. Diagonale werden nur in 3er-Varianten ergänzt / zerstört.

   Wenn überhaupt nichts gefunden wurde (keine 3er UND keine 2er), wird der Stein mithilfe von
   pc_easy() an eine zufällige Stelle gesetzt.

   Parameter
   ---------
   Die Funktion verlangt als Parameter die Struktur feld (Referenz)

   Returnwert
   ----------
   Die Funktion gibt immer 0 zurück. Diese 0 wird vom restlichen Spiel nicht weiterverarbeitet.
   Sie dient nur dazu, die Funktion abzubrechen, wenn eine Kombination gefunden
   und ergänzt (PC) / zerstört (Spieler) wurde.

   Reihenfolge
   -----------
   1. Diagonale 3er-Kombinationen (links unten -> rechts oben)
   2. Diagonale 3er-Kombinationen (links oben -> rechts unten)
   3. Nebeneinanderliegende 3er-Kombinationen
      1. Versuch, einen Stein links davon zu setzen
      2. Versuch, einen Stein rechts davon zu setzen
   4. Übereinanderliegende 3er-Kombination
   5. Nebeneinanderliegende 2er-Kombinationen
      1. Versuch, einen Stein links davon zu setzen
      2. Versuch, einen Stein rechts davon zu setzen
   6. Übereinanderliegende 2er-Kombinationen
   7. Zufallszug, falls nichts geklappt hat.
*/
int pc_difficult(spielfeld &feld)
{
int zeile = 0, spalte = 0;           //Initialisierung der Zählvariablen
int temp_zeile = 0;                  //Speicher für die Zeile, die zeile, die
                                     //choose_free_position liefert
                                     // -------------------------------------
                                     // Mit dieser Variablen wird geprüft, ob ein
                                     // Zug sinnvoll und möglich ist.

for(spalte = 0; spalte < SPIELFELD_BREITE; spalte++)    //1. Dimension
{
  for(zeile = 0; zeile < SPIELFELD_HOEHE; zeile++)      //2. Dimensoion
  {
          //von links unten nach rechts oben
          if(ist_reihe(STEIN_SPIELER1, 3, -1,  1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER1, 3,  1, -1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3, -1,  1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3,  1, -1, spalte, zeile, feld))
          {
            //nur weitermachen, wenn das "benötigte" feld im Spielfeld liegt und frei ist
            if(gueltig(spalte+3, zeile-3) && feld.array[zeile-3][spalte+3] == LEERES_FELD)
            {
             temp_zeile = choose_free_position(feld, spalte+3);
             if(temp_zeile != -1 && temp_zeile == zeile-3)       //wenn die spalte nicht voll ist
             {                                                   //und die nächste freie zeile der zeile entspricht, die
              feld.array[zeile-3][spalte+3] = STEIN_SPIELER2;    //als "Ergänzer" für eine Kombination ermittelt wurde,
              return 0;                                          //wird der Stein gesetzt
             }
            }
          }

          //von links oben nach rechts unten
          if(ist_reihe(STEIN_SPIELER1, 3,  1,  1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER1, 3, -1, -1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3,  1,  1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3, -1, -1, spalte, zeile, feld))
          {
            //nur weitermachen, wenn das "benötigte" feld im Spielfeld liegt und frei ist
            if(gueltig(spalte-1, zeile-1) && feld.array[zeile-1][spalte-1] == LEERES_FELD)
            {
             temp_zeile = choose_free_position(feld, spalte-1);
             if(temp_zeile != -1 && temp_zeile == zeile-1)      //wenn die spalte nicht voll ist
             {                                                  //und die nächste freie zeile der zeile entspricht, die
              feld.array[zeile-1][spalte-1] = STEIN_SPIELER2;   //als "Ergänzer" für eine Kombination ermittelt wurde,
              return 0;                                         //wird der Stein gesetzt
             }
            }
          }

          //Steine liegen nebeneinander
          //Es wird geprüft, ob einer der beiden Spieler 3 Steine nebeneinander hat.
          //Der PC sucht dann die Stelle, an der er den Stein setzten muss, um zu gewinnen oder um den Gegner zu schwächen
          if(ist_reihe(STEIN_SPIELER1, 3, -1,  0, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER1, 3,  1,  0, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3, -1,  0, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3,  1,  0, spalte, zeile, feld))
          {
             //nur weitermachen, wenn das "benötigte" feld im Spielfeld liegt und frei ist
             if(gueltig(spalte-1, zeile) && feld.array[zeile][spalte-1] == LEERES_FELD)
             {
               temp_zeile = choose_free_position(feld, spalte-1);
               if(temp_zeile != -1 && temp_zeile == zeile)
               {
                feld.array[zeile][spalte-1] = STEIN_SPIELER2;
                return 0;
               }
             }  //nur weitermachen, wenn das "benötigte" feld im Spielfeld liegt und frei ist
             else if(gueltig(spalte+3, zeile) && feld.array[zeile][spalte+3] == LEERES_FELD)
             {
               temp_zeile = choose_free_position(feld, spalte+3);
               if(temp_zeile != -1 && temp_zeile == zeile)              //.... siehe oben
               {                                                        //.... siehe oben
                feld.array[zeile][spalte+3] = STEIN_SPIELER2;           //.... siehe oben
                return 0;                                               //.... siehe oben
               }
             }
          }

          //3 Steine liegen übereinander
          //Der PC sucht legt seinen Stein über die 3 vorhandenen, wenn die Stelle noch frei ist.
          if(ist_reihe(STEIN_SPIELER1, 3,  0, 1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 3,  0, 1, spalte, zeile, feld))
          {
            if(gueltig(spalte, zeile-1) && feld.array[zeile-1][spalte] == LEERES_FELD)
            {
               feld.array[zeile-1][spalte] = STEIN_SPIELER2;
               return 0;
            }
          }

          //2 Steine liegen nebeneinander
          //Es wird geprüft, ob einer der beiden Spieler 3 Steine nebeneinander hat.
          //Der PC sucht dann die Stelle, an der er den Stein setzten muss, um zu gewinnen oder um den Gegner zu schwächen
          if(ist_reihe(STEIN_SPIELER1, 2, -1,  0, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER1, 2,  1,  0, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 2, -1,  0, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 2,  1,  0, spalte, zeile, feld))
          {
             if(gueltig(spalte-1, zeile) && feld.array[zeile][spalte-1] == LEERES_FELD)
             {
               temp_zeile = choose_free_position(feld, spalte-1);
               if(temp_zeile != -1 && temp_zeile == zeile)
               {
                feld.array[zeile][spalte-1] = STEIN_SPIELER2;
                return 0;
               }
             }
             else if(gueltig(spalte+2, zeile) && feld.array[zeile][spalte+2] == LEERES_FELD)
             {
               temp_zeile = choose_free_position(feld, spalte+2);
               if(temp_zeile != -1 && temp_zeile == zeile)
               {
                feld.array[zeile][spalte+2] = STEIN_SPIELER2;
                return 0;
               }
             }
          }

          //2 Steine liegen übereinander
          //Der PC sucht legt seinen Stein über die 3 vorhandenen, wenn die Stelle noch frei ist.
          if(ist_reihe(STEIN_SPIELER1, 2,  0, 1, spalte, zeile, feld) ||
             ist_reihe(STEIN_SPIELER2, 2,  0, 1, spalte, zeile, feld))
          {
            if(gueltig(spalte, zeile-1) && feld.array[zeile-1][spalte] == LEERES_FELD)
            {
               feld.array[zeile-1][spalte] = STEIN_SPIELER2;
               return 0;
            }
          }
  }
}
return pc_easy(feld); //Falls die KI nicht zum EInsatz kam, muss der PC wieder einen Zufallszug machen

}

/**
  Diese Funktion entscheidet nur, ob die KI verwendet werden soll oder nicht
  Je nach dem ruft sie die Entsprechenden Funktionen auf und gibt die Rückgabewerte zurück

  Die Parameter sind das Spielfeld und die Einstellungen
*/
int spielzug_pc(spielfeld &feld, settings &einstellungen)
{
  show_field(feld);      //Spielfeld anzeigen
  cout<<"Computer (O) ist jetzt dran!"<<<<" (";
             textfarbe(FARBE_GRUEN);
             cout<<"X";
             textfarbe(FARBE_WEISS);
             cout<<") ist jetzt dran! "; break;
     case 2: cout<<<" (";
             textfarbe(FARBE_ROT);
             cout<<"O";
             textfarbe(FARBE_WEISS);
             cout<<") ist jetzt dran! "; break;
     default: break;
    }

    if(einstellungen.use_mouse != true)
    {
     //STEUERUNG ÜBER DIE TASTATUR
     bool gesetzt = false;
     int taste = -1;
     cout<<"Bitte mit den Pfeiltasten steuern (Links/Rechts)!"<< SPIELFELD_BREITE-1) // Zeiger nach rechts verschieben
             {
               spalte++;
             }
          }

          if(taste == TASTE_LINKS)         // Links-Taste
          {
            if(spalte > 0)                 // Zeiger nach links verschieben
            {
             spalte--;
            }
          }
        }

        if(taste == TASTE_ENTER)          // Enter-Taste
        {
          gesetzt = true;                 // Stein wurde abgelegt
        }

        cout< <"\r";
        for(i = 0; i < 79; i++)      //komplette zeile

         {                            // mit
               cout<<" ";             // " " überschreiben
         }
         cout<<"\r";                  //dann zum Zeilenanfang springen
         for(i = 0; i < spalte*4; i++)  //und dann an die entsprechende Stelle die Zahl ausgeben lassen
         {
               cout<<" ";
         }
         cout<<"-^"<<<"Bewegen sie die Maus zur gewünschten Spalte und legen sie den Stein per Mausklick ab!"<<<"\r";                  // zuerst
         for(i = 0; i < 79; i++)      //komplette zeile

         {                            // mit
               cout<<" ";             // " " überschreiben
         }
         cout<<"\r";                  //dann zum Zeilenanfang springen
         for(i = 0; i < posX-2; i++)  //und dann an die entsprechende Stelle die Zahl ausgeben lassen
         {
               cout<<" ";             //dazu posX-2 Leerzeichen voranstellen
         }

         if(spalte < SPIELFELD_BREITE)         // wenn maus im gültigen Bereich ist
         {
            cout<<"-^"<< SPIELFELD_BREITE))  //nur anzeigen, wenn die maus im gültigen Bereich ist
                {
                    cout << " - Stein wird gesetzt!";
                    Sleep(500);      //1/2 Sekunde warten. (Dem Benutzer Zeit zum Lesen lassen)
                    running = false;  //abbruch, da Stein gesetzt wurde!
                }
        }
       lastPosX = posX;
       Sleep(50);            //Pause wegen Flimmern
    }
    while(running);
    }

  aktuelle_position = choose_free_position(feld, spalte);      //erste freie zeile auswählen
  if(aktuelle_position == -1)                                  //falls die spalte voll ist
  {
     clrscr();
     textfarbe(FARBE_ROT);
     cout<<"Spalte "<<<" ist leider schon voll."<<<"Bitte neu setzen!"<< SPIELFELD_HOEHE; i++)           //1. Dimension
     {
           for(j = 0; j < SPIELFELD_BREITE; j++)    //2. Dimension
           {
             mein_spielfeld.array[i][j] = LEERES_FELD;     //konstante ganz oben definiert!
           }
     }

    while(spiel_beendet == false)
    {
        runde++;
        if(einstellungen.against_who == 1)  //gegen den PC)
        {
          if(runde%2 == 0)
          {
           if(spielzug_pc(mein_spielfeld, einstellungen) == -2)
           {
            spiel_beendet = true;
            show_field(mein_spielfeld);
            textfarbe(FARBE_ROT);
            cout<<"Alle Felder sind belegt, d.h. das Spiel endet untentschieden!"<<<<"Es ist ein Fehler aufgetreten!!\n"<<<<" hat gewonnen!"<<<<" hat gewonnen!"<<<"Alle Felder sind belegt, d.h. das Spiel endet untentschieden!"<<<"Das Spiel wurde beendet."<<<"Wollen Sie nochmal mit den gleichen Einstellungen spielen? \n((j)a / (n)ein)";
    nochmal1 = char_eingabe();
  }

 /**
   Abfrage, ob der Benutzer nochmal neue Einstellungen vornehmen möchte.
   nochmal1 wird auf 'j' gesetzt, weil sonst die erste Schleife ("wiederholung 1") nicht mehr ausgeführt wird
   und das Spiel gleich beendet wäre.

   Man hätte auch oben in der Schleife ("wiederholung 1") abfragen können, ob nochmal1 ODER nochmal2 'j' sind.
 */
 clrscr();
 header();
 cout<<"Wollen sie ein neues Spiel starten und auch neue Einstellungen vornehmen? \n((j)a / (n)ein)";
 nochmal2 = char_eingabe();
 if(nochmal2 == 'j')
 { nochmal1 = 'j'; }

}
}

// ENDE

Code-Erklärung
Globale Variablen
Ich habe nicht eine einzige globale Variable verwendet, was den Code viel leichter wartbar macht. Die Variablen wurden meistens per Referenz (&) übergeben, so dass sie auch in Funktionen bearbeitet werden können.

Konstanten
Wichtig war mir, dass man Einstellungen wie die Breite des Spielfelds sehr schnell ändern kann. Deshalb habe ich entsprechende Konstanten eingeführt:

const int SPIELFELD_HOEHE  = 8;                                                  // Konstante für Spielfeldhöhe
const int SPIELFELD_BREITE = 8;                                                  // Konstante für Spielfeldbreite

Auch die Farben sind als Konstanten deklariert:

// FARBEN BEGINN
const int FARBE_WEISS      = 15;
const int FARBE_SCHWARZ    = 0;
const int FARBE_BLAU       = 9;
const int FARBE_MAGENTA    = 5;
const int FARBE_GELB       = 14;
const int FARBE_BRAUN      = 6;
const int FARBE_ROT        = 12;
const int FARBE_BLINK      = 128;
const int FARBE_GRUEN      = 10;
const int FARBE_CYAN       = 3;
// FARBEN ENDE

Funktionen
Ich gebe zu, dass ich mit den Funktionen ein bisschen übertrieben habe. Man hätte sich z.B. die header() oder die start()-Funktion komplett sparen können, aber ich achte halt sehr auf Übersichtlichkeit 😉

Benotung
Mein Spiel wurde wie erwartet mit der 1 bewertet… Wenn man den zeitlichen Aufwand von mir mit dem von meinen Kameraden vergleicht, ist das auch gerecht 😉
Allerdings ist mir klar, dass mein Spiel noch sehr umständlich programmiert ist..

Die KI
Die KI ist keine wirkliche KI, da sie nicht berücksichtigt, was der Mensch als nächstes machen wird. Das wäre für meinen jetzigen Stand auch eindeutig zu viel gewesen.
Die KI arbeitet zur Zeit so, dass die einfach nach 2er und 3er Reihen sucht und diese ergänzt, wenn sie zum Sieg des PCs führen würden.
Wenn die Reihen aber dem Gegner (also dem richtigen Spieler) gehören, setzt die KI den Stein so, dass der Sieg verhindert wird.

Sollte mir mal langweilig sein, programmiere ich vielleicht noch eine richtige KI auf Basis des Minimax-Algorithmus. Das wird dann aber mit Sicherheit ein OOP-Programm mit grafischer Oberflächte (sprich Windowsprogramm – GUI)


Die Erklärung von int_eingabe, char_eingabe und mouseInit
Wie oben schon angekündigt wurde, folgt hier die Erklärung der 3 Funktionen!
char_eingabe

/**
   Char_eingabe() benötigt keine Parameter und gibt ein Zeichen als char zurück.
   Die Funktion führt solange eine Endlosschleife aus, bis eine Taste gedrückt wird.
*/
char char_eingabe()
{
 while(1)                //Endlosschleife
 {
   if(kbhit())          //wenn eine Taste gedrückt wurde
   {
   return getch();      //Endlosschleife abbrechen
   }
 Sleep(10);   //CPU entlasten, indem 10ms gewartet wird.
 }
}

char_eingabe() führt so lange eine Endlosschleife aus, bis eine Taste gedrückt wurde. Danach wird die Eingabe des Benutzers als char zurückgegeben und die Funktion verlassen.

int_eingabe (Baut auf char_eingabe() auf)

/**
  int_eingabe erwartet wie char_eingabe keine Parameter und gibt eine Ganzzahl (int)
  zurück.
  int_eingabe ist eine kleine Funktion zum Einlesen von Zahlen (int)
  Die Funktion verhindert Fehler (Exceptions) bei der Eingabe von Buchstaben, wenn eine Zahl erwartet wird.
*/
int int_eingabe()
{
 char speicher = ' ';      //zwischenspeicher ist char, weil char alles enthalten darf
 while(!isdigit(speicher)) // solange die Eingabe keine Zahl war, wird die Schleife ausgeführt
 {
  speicher = char_eingabe();  //aufruf des programms, das char vom benutzer verlangt
 }
  //in speicher steht der ASCI-Code von der Eingabe. Da '0' vor allen
  //anderen Zahlen kommt und zu '1' den Abstand 1, zu '2' den Abstand 2 usw. hat, kann man durch abziehen von
  //'0' die richtige int-Zahl erhalten.
  /**
    ASCI-Tabelle:
    zahl  | ASCI-Code
    -----------------
    0     | 48
    1     | 49
    2     | 50
    3     | 51
    4     | 52
    5     | 53
    6     | 54
    7     | 55
    8     | 56
    9     | 57
    -----------------
  */
  // wenn jetzt z.B. eine 3 eingegeben wird, steht in char speicher '51'.
  // jetzt wird die '0' (ASCI 48) abgezogen. Also steht dann '3' in speicher.
  //(int) konvertiert dann noch den datentyp, da die funktion int zurückgibt.
  return (int)speicher-'0';
}

Diese kleine Funktion verlangt solange eine Eingabe, bis isdigit() true liefert. (Was es bei Zahlen tut)
Zurückgegeben wird dann aus der Funktion die Zahl, die gedrückt wurde.

mouseInit
Hier muss gesagt werden, dass ich die Funktion so im Internet gefunden habe und meinen Zwecken angepasst habe.

/**
   Initialisierung der Maus
   mouseInit initialisiert die Maus und gibt die Anzahl der Buttons zurück.
   (Das wird in diesem Prgramm aber nie benötigt, nur die Initialisierung ist
   wichtig)
*/
DWORD mouseInit ()
{
    DWORD mode;
    GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),&mode);                    //bisherigen Modus in mode speichern
    SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE),mode|ENABLE_MOUSE_INPUT);  //Modus um ENABLE_MOUSE_INPUT erweitern,
                                                                             //Dies aktiviert die Maus.
    DWORD buttons;
    GetNumberOfConsoleMouseButtons(&buttons);                                //Anzahl der Maustasten ermitteln

    return buttons;
}

Als erstes wird mode deklariert (DWORD). Dann wird der Modus, auf dem die Konsole läuft in diese Varaible gespeichert und danach SetConsoleMode ausgeführt. SetConsoleMode setzt die Konsole auf einen anderen Modus. Mit „|ENABLE_MOUSE_INPUT“ wird mode um die Mauseingabe erweitert.
Der Rest der Funktion ist unwichtig!

Ich hoffe, euch hat der Artikel gefallen!
Tut mir aber den Gefallen und kopiert den Code nicht nur, sondern verlinkt auch auf diese Seite.
Und vor allem: Gebt ihn nicht als euer eigenes Werk aus!

1 Star2 Stars3 Stars4 Stars5 Stars (1 Stimme, durchschnittlich 5,00 / 5)
Loading...


7 Kommentare zu “[C++] 4 Gewinnt mit KI auf Konsolenbasis”

  1. Pointer in C++: http://www.c-plusplus.de/forum/viewtopic-var-t-is-124532.html

  2. […] habe. Sie war ein Konsolenspiel und konnte Wahlweise mit Maus und Tastatur gesteuert werden. Hier könnt ihr alles nochmal […]

  3. ki: ungenügend

  4. Jo, da hast du Recht!
    Aber ich habe auch nie behauptet, dass die KI perfekt ist. Der Algorithmus für eine gute 4-Gewinnt-KI ist auch sehr kompliziert und wird evlt irgendwann man implementiert

  5. Wir machen das gerade in der Uni! Vielen Dank für deinen Source Code!

    Greetz

    Mario
    .-= Mario Kaller´s last blog ..Canon Xeed SX7 Mark II =-.

  6. Echt, sowas macht man an der Uni?
    Dann aber bestimmt mit echter KI, oder?

    MfG
    Simon

  7. WOW 🙂 ich hab so was einfach nicht drauf, aber hut ab vor dir

Hinterlasse einen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.

»Informationen zum Artikel

Autor: Simon
Datum: 23.06.2008
Zeit: 15:00 Uhr
Kategorien: C/C++
Gelesen: 21351x heute: 3x

Kommentare: RSS 2.0.
Diesen Artikel kommentieren oder einen Trackback senden.

»Meta