Tic-Tac-Toe cmd-Fenster

Schnelle objektorientierte, kompilierende Programmiersprache.
FritziFoppel
Beiträge: 101
Registriert: Sa Mär 02, 2013 6:53 pm
Wohnort: Göppingen

Tic-Tac-Toe cmd-Fenster

Beitrag von FritziFoppel » Mi Apr 17, 2013 6:29 pm

Hi,

Ich hab mir am WE überlegt mal was "nützlicheres" zu programmieren als die Größe eines Textes, oder eine Textdatei in der "Hallo Peter!" steht, um vielleicht ein bisschen selbst kreativ zu werden. Ich bin dann zu dem Ergebnis gekommen, dass ich mal Tic-Tac-Toe programmieren kann (gar nicht so leicht während der Schulzeit zum programmieren zu kommen, da leidet auch das Tutorail darunter :cry: ). Jedenfalls hab ich mir ein simples Konzept gemacht wie das Spiel ablaufen soll.
Nachdem ich zuerst mit C++ ein Projekt über mehrere Files gemacht hatte, was dann nicht so funktionierte, hab ich ganz "ordinär" den gesamten Code als C-Datei geschrieben. Der Code ist dementsprechend "lang".

Und nun wie immer zu meinem eigentlichen Problem: ;)
Da die Abfrage (für 2 Spieler [0, 1]) ständig wiederholt wird, hab ich alles in eine do..while-Schleife gepackt. Bisher funktioniert die Eingabe einwandfrei. Bei einem unbesetztem Feld (ich habe ein zweidimensionales char Array benutzt) wird das Feld mit 0 bzw 1 überschrieben. Ist ein Feld doppelt besetzt, wird der Spieler, der an der Reihe ist erneut aufgefordert ein Feld einzugeben, und ist die Eingabe ungleich 1-9, wird das Spiel beendet.
Mir stellt sich nun die Frage, warum sich die Schleife nicht unterbricht, wenn ich eine Mögliche Siegkombination in das Fenster eingebe.
Mit der Überlegung (s.u): 3 gleiche in einer Reihe (horizontal), daselbe nur vertikal und 2 mal schräg durch das 5. Feld, habe ich eine Funktion definiert, die bei besetzten Feldern entweder 0 oder 1, je nach Spieler, zurückgibt, bei unbesetzten 0(und noch eine extra Funktion full(), die, wenn alle Felder besetzt sind und es keinen Sieger gibt, 0 zurückgibt, mithilfe eines int Arrays, 0 leer, 1 besetzt: hier aber das gleiche Prinzip).

Code: Alles auswählen

int win()
{
    if(felder mit Spieler 1 besetzt und Überlegungerfüllt)
         return 0;

     else if(felder mit Spieler 2 besetzt und Überlegungerfüllt)
        return 1;

    else
        return 2;
}

//Spielablauf
do
{
...
}
while(win() != 1 || win() != 2);
Wenn Spieler 1 abgefragt wird eine Zahl einzugeben und er das macht hat er gewonnen, egal welche Zahl zwischen 1-9.
Überprüft die Schleife nun nicht die Ähnlichkeit zwischen Tatsächlichen Werten und Siegwerten, sondern nimmt er die Werte der Funktion an?

Danke für die Hilfe, vielleicht versteht mich ja einer.
Gruß Chris ;)

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von Glocke » Mi Apr 17, 2013 6:51 pm

FritziFoppel hat geschrieben:

Code: Alles auswählen

int win()
{
    if(felder mit Spieler 1 besetzt und Überlegungerfüllt)
         return 0;

     else if(felder mit Spieler 2 besetzt und Überlegungerfüllt)
        return 1;

    else
        return 2;
}

//Spielablauf
do
{
...
}
while(win() != 1 || win() != 2);
Ich würde für das Resultat der Siegprüfung ein Enum verwenden:

Code: Alles auswählen

enum Winner { UNDEFINED, PLAYER1, PLAYER2 }
dann beim prüfen mit win() den entsprechenden Wert zurückliefern, und in der Spielablauf-Schleife in einer Variable speichern. Du rufst bei jedem Durchlauf am Ende win() 2x auf. Das ist hier nicht weiter tragisch (vermute ich), kann dir aber bei größeren Sachen das Genick brechen. Eine schönere Lösung wäre z.B.

Code: Alles auswählen

Winner state = UNDEFINED;
do {
    // ... Zeug machen usw.

    // Siegbedingungen prüfen
    state = check_for_winner();
while (state == UNDEFINED);
Deine Siegbedingung hab' ich bisher noch nicht verstanden... Vom Prinzip her könnte man schon so vorgehen wie du formuliert hast: alle möglichen Siegbedingungen prüfen und schauen, welcher Spieler diese erfüllt hat.

Der Ansatz mit der Schleife ist schon super! In der Regel ist ein Spiel eine große Endlosschleife mit sinnvollen Abbruchbedingungen - in deinem Fall dem Sieg eines Spielers.

LG Chris ;D

FritziFoppel
Beiträge: 101
Registriert: Sa Mär 02, 2013 6:53 pm
Wohnort: Göppingen

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von FritziFoppel » Mi Apr 17, 2013 7:38 pm

Danke für die antwort, werd ich gleich mal ausprobieren. Evtl. dann morgen.

Mit den Siegesbedingungen meinste ich das übliche "3 in einer Reihe" Muster.
1|2|3
4|5|6
7|8|9

Sieg bei: 1-2-3, 4-5-6, 7-8-9, 1-4-7, 2-5-8, 3-6-9, 1-5-9, 3-5-9

Ziemlich umständlich das ganze dann immer in die Funktion eingeben, aber für meinen aktuellen Wissensstand sehe ich da keine andere Möglichkeit als;
if((feld [0] [0] = 1) &&.....)
Mein Onkel der vor 20 Jahren mal programmiert hat und nicht C, meinte man könnte das mithilfe einer Matrix irgendwie aufrufen, aber ja noch nicht ganz mein Wissensstand. ;)

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von Glocke » Mi Apr 17, 2013 8:05 pm

FritziFoppel hat geschrieben:Ziemlich umständlich das ganze dann immer in die Funktion eingeben, aber für meinen aktuellen Wissensstand sehe ich da keine andere Möglichkeit als;
if((feld [0] [0] = 1) &&.....)
naja du könntest dir weitere Funktionen schreiben (ausgehend vom Enum-Vorschlag oben), z.B.
  • Winner check_row(int row);
  • Winner check_col(int col);
  • WInner check_diag();
Dann würde check_row / check_col entsprechend die drei Felder der gegebenen Zeile / Spalte auf Gleichheit (also alle ==1 oder ==2, nicht ==0 ^^) prüfen und UNDEFINED, PLAYER1 oder PLAYER2 zurückliefern. Wie check_diag arbeiten könnte, ahnst du sicher. Anschließend brauchst du beim eigentlichen Prüfen der Siegbedingungen nur noch alle Fälle durchgehen, die entsprechenden Funktionen aufrufen und die Rückgabewerte entsprechend interpretieren (z.B. die Prüfung abbrechen, wenn ein Gewinner feststeht).

Damit zerlegst du ersteinmal deine Siegbedingungen in einzelne Teile - du musst ja auch nicht alles sofort implementieren, d.h. du kannst mit Zeilen anfangen und dann später um Spalten und die beiden Diagonalen erweitern. Mi der Zerlegung bekommst du hauptsächlich eine bessere Übersicht in die Sache rein - anstatt gefühlter 1.000 "&&"- bzw. "||"-Operatoren.

LG Glocke

FritziFoppel
Beiträge: 101
Registriert: Sa Mär 02, 2013 6:53 pm
Wohnort: Göppingen

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von FritziFoppel » Do Apr 18, 2013 2:33 pm

Hab mich heute nochmal hingesetzt und deine Idee ins Programm geschrieben.
Ich erhalte aber immer noch den gleichen Fehler: Wird Spieler 1 aufgefordert eine Zahl einzugeben, gewinnt er immer automatisch und die ersten 3 Felder werden mit einer 1 belegt.
Kann es vielleicht sein, das ich mich durch die Funktion vllt. falsch ausdrücke?

Code: Alles auswählen

//jeweils 3 Felder belegt mit 1
if(  ((surface [0] [0] = 1) && (surface [0] [1] = 1) && (surface [0] [2] = 1)) || /*erste Reihe 1-3*/
     ((surface [1] [0] = 1) && (surface [1] [1] = 1) && (surface [1] [2] = 1)) || /*zweite Reihe 4-6*/
     ((surface [2] [0] = 1) && (surface [2] [1] = 1) && (surface [2] [2] = 1)) || /*dritte Reihe 7-9*/
     ((surface [0] [0] = 1) && (surface [1] [0] = 1) && (surface [2] [0] = 1)) || /*erste Spalte*/
     ((surface [0] [1] = 1) && (surface [1] [1] = 1) && (surface [2] [1] = 1)) || /*zweite Spalte*/
     ((surface [0] [2] = 1) && (surface [1] [2] = 1) && (surface [2] [2] = 1)) || /*dritte Spalte*/
     ((surface [0] [0] = 1) && (surface [1] [1] = 1) && (surface [2] [2] = 1)) || /*diagonal lo nach ru*/
     ((surface [0] [2] = 1) && (surface [1] [1] = 1) && (surface [2] [0] = 1))  )  /*diagonal ro nach lu*/

    {
        printf("Spieler 1 gewinnt.\n");
        return PLAYER1;
    }
surface ist das bereits erwähnte, zweidimensionale char Array
Hab ich in der Bedingung vllt. etwas übersehen oder werden die Felder einfach so verändert, wenn die Funktion aufgerufen wird?

Code: Alles auswählen

enum Winner {UNDEFINED, PLAYER1, PLAYER2};

int state = UNDEFINED;

    do
    {
        //Spieler wird nach Abgabe gefragt, diese wird dann geprüft auf Gültigkeit
        win();
        state = win();

    }while(state == UNDEFINED);
Ich hoff ich hab deine Idee richtig umgesetzt?

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von Glocke » Do Apr 18, 2013 3:33 pm

FritziFoppel hat geschrieben:Hab ich in der Bedingung vllt. etwas übersehen oder werden die Felder einfach so verändert, wenn die Funktion aufgerufen wird?
Also die Bedingungen erscheinen mir nachvollziehbar. Was gibst du im else-Fall zurück?
FritziFoppel hat geschrieben:

Code: Alles auswählen

enum Winner {UNDEFINED, PLAYER1, PLAYER2};

int state = UNDEFINED;

    do
    {
        //Spieler wird nach Abgabe gefragt, diese wird dann geprüft auf Gültigkeit
        win();
        state = win();

    }while(state == UNDEFINED);
Ich hoff ich hab deine Idee richtig umgesetzt?
Erstmal solltest du Winner state = UNDEFINED; schreiben: state soll vom Typ "Winner" sein - der Typ Winner hat die Werte UNDEFINED, PLAYER1 und PLAYER2.

Beim Game-Loop haben wir uns glaube ich missverstanden. Er könnte so aussehen:

Code: Alles auswählen

Winner state = UNDEFINED;

do {
    // führt zug auch direkt durch und gibt das neue spielfeld aus
    handle_user_input();
    // prüft, ob sieger feststeht
    state = check_for_winner();
    if (state != UNDEFINED) {
        /* der sieger steht fest - rein logisch kann es an dieser
        stelle nur spieler 1 sein; das spiel endet */
        break;
    }
    /* führt zug auch direkt aus und gibt das neue spielfeld aus;
    ggf. könnte hier auch die Eingabe durch Spieler 2 erfolgen */
    handle_cpu_turn();
    // prüft, ob sieger feststeht
    state = check_for_winner();
    /* wenn spieler 2 jetzt gewonnen hat, ist das spiel
    ebenfalls vorbei, da der sieger dann feststeht ^^ */
} while(state == UNDEFINED);
Das ganze kann man noch weiter verschönern - ist aber erstmal nicht nötig.

LG Glocke

/EDIT: Ich habe mal nen Schere-Stein-Papier / Rock-Paper-Scissors-Spiel gebastelt, was genau das verfolgt, was ich zu erklären versuche. Es sollte einfach genug sein, um das Prinzip anschaulich zu zeigen ... und es ist nicht genau dein Projekt, um dir nichts "wegzunehmen" (du möchtest ja Lernen :) )

Code: Alles auswählen

#include <iostream>

// für Zufallszahlen
#include <cstdlib>
#include <ctime>

// Enumerationen für Zustände
enum Choice { ROCK = 0, PAPER = 1, SCISSORS = 2 };
enum Winner { UNDEFINED, PLAYER1, PLAYER2 };

// Ermittelt Sieger
Winner check_for_winner(Choice human, Choice bot) {
    if (human == ROCK) {
        if (bot == PAPER) {
            // Papier schlägt Stein
            return PLAYER2;
        } else if (bot == SCISSORS) {
            // Schere schlägt Papier
            return PLAYER1;
        } else {
            // Unentschieden
            return UNDEFINED;
        }
    } else if (human == PAPER) {
        if (bot == ROCK) {
            // Papier schlägt Stein
            return PLAYER1;
        } else if (bot == SCISSORS) {
            // Schere schlägt Papier
            return PLAYER2;
        } else {
            // Unentschieden
            return UNDEFINED;
        }
    } else {
        // d.h. player == SCISSORS
        if (bot == ROCK) {
            // Stein schlägt Schere
            return PLAYER2;
        } else if (bot == PAPER) {
            // Schere schlägt Papier
            return PLAYER1;
        } else {
            // Unentschieden
            return UNDEFINED;
        }
    }
}

Choice read_input() {
    std::string input;
    int i = -1;
    while (i < ROCK || i > SCISSORS) {
        // Eingabe
        std::cout << "Wähle: [0] Stein, [1] Papier, [2] Schere\n";
        std::cin >> input;
        // Konvertieren und prüfen
        i = atoi(input.c_str());
    }
    return (Choice)i;
}

Choice pick_choice() {
    // Wählt Zufallszahl aus {0,1,2} aus (vgl. enum Choice)
    return (Choice)(std::rand() % 3);
}

std::string choice2string(Choice c) {
    switch (c) {
        case ROCK:
            return "Stein";
        case PAPER:
            return "Papier";
        case SCISSORS:
            return "Schere";
    }
    return "Unbekannt O_o";
}

void print(Choice human, Choice bot, Winner champion) {
    std::cout << choice2string(human) << " vs. " << choice2string(bot) << "\n";
    switch (champion) {
        case UNDEFINED:
            std::cout << "Unentschieden!\n";
            break;
        case PLAYER1:
            std::cout << "Spieler 1 gewinnt!\n";
            break;
        case PLAYER2:
            std::cout << "Spieler 2 gewinnt!\n";
            break;
    }
}

int main() {
    // Gewählte Aktionen
    Choice human, bot;
    // Ermittelter Sieger
    Winner champion = UNDEFINED;
    // Zufallsgenerator initialisieren
    std::srand(std::time(0));

    do {
        human = read_input();
        bot = pick_choice();
        champion = check_for_winner(human, bot);
        print(human, bot, champion);
    } while (champion == UNDEFINED);
}
sieht dann bei mir so aus:

Code: Alles auswählen

christian@glocke:/tmp$ ./rps 
Wähle: [0] Stein, [1] Papier, [2] Schere
0
Stein vs. Papier
Spieler 2 gewinnt!
christian@glocke:/tmp$ ./rps 
Wähle: [0] Stein, [1] Papier, [2] Schere
1
Papier vs. Papier
Unentschieden!
Wähle: [0] Stein, [1] Papier, [2] Schere
3
Wähle: [0] Stein, [1] Papier, [2] Schere
2
Schere vs. Schere
Unentschieden!
Wähle: [0] Stein, [1] Papier, [2] Schere
0
Stein vs. Stein
Unentschieden!
Wähle: [0] Stein, [1] Papier, [2] Schere
1
Papier vs. Schere
Spieler 2 gewinnt!
christian@glocke:/tmp$ 
ggf. kann ich dazu auch noch etwas posten.

FritziFoppel
Beiträge: 101
Registriert: Sa Mär 02, 2013 6:53 pm
Wohnort: Göppingen

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von FritziFoppel » Do Apr 18, 2013 4:27 pm

Im else if() wird noch die Möglichkeit beschrieben, dass 3 von Spieler 2 gesetzten Feldern, den Bedingungen entsprechen (return PLAYER 2;)
Im else-Fall wird UNDEFINED zurückgegeben.

Das Programm bricht aber immer nach der ersten Eingabe ab und nicht erst, wenn eine Bedingung erfüllt ist :|

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von Glocke » Do Apr 18, 2013 4:29 pm

FritziFoppel hat geschrieben:Im else if() wird noch die Möglichkeit beschrieben, dass 3 von Spieler 2 gesetzten Feldern, den Bedingungen entsprechen (return PLAYER 2;)
Im else-Fall wird UNDEFINED zurückgegeben.

Das Programm bricht aber immer nach der ersten Eingabe ab und nicht erst, wenn eine Bedingung erfüllt ist :|
kannst du es hier mal hochladen, d.h. den quellcode (ggf. aus zip-archiv) anhängen? ich denke da werde ich dir noch am besten helfen können.

FritziFoppel
Beiträge: 101
Registriert: Sa Mär 02, 2013 6:53 pm
Wohnort: Göppingen

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von FritziFoppel » Do Apr 18, 2013 5:25 pm

Ich hab ein paar Kommentare dazugeschrieben. Falls du Fragen und Skype hast: chris-4ever1
Du hast keine ausreichende Berechtigung, um die Dateianhänge dieses Beitrags anzusehen.

Glocke
Beiträge: 332
Registriert: Fr Okt 26, 2012 8:39 am

Re: Tic-Tac-Toe cmd-Fenster

Beitrag von Glocke » Do Apr 18, 2013 5:42 pm

Erstmal rein stilistisch: Zeilen wie

Code: Alles auswählen

if(     ((surface [0] [0] = 1) && (surface [0] [1] = 1) && (surface [0] [2]= 1) ) || ((surface [1] [0] = 1) && (surface [1] [1] = 1) && (surface [1] [2] = 1)) || ((surface [2] [0] = 1) && (surface [2] [1] = 1) && (surface [2] [2]= 1) ) ||
            ((surface [0] [0] = 1) && (surface [1] [0] = 1) && (surface [2] [0]= 1) ) || ((surface [0] [1] = 1) && (surface [1] [1] = 1) && (surface [2] [1] = 1)) || ((surface [0] [2] = 1) && (surface [1] [2] = 1) && (surface [2] [2]= 1) ) ||
            ((surface [0] [0] = 1) && (surface [1] [1] = 1) && (surface [2] [2]= 1) ) || ((surface [0] [2] = 1) && (surface [1] [1] = 1) && (surface [2] [0] = 1)) )
würde ich übersichtlicher formatieren und dabei auf eine maximale Zeilenlänge achten, z.B.

Code: Alles auswählen

if (((surface [0] [0] == 1) && (surface [0] [1] == 1) && (surface [0] [2] == 1) )
    || ((surface [1] [0] == 1) && (surface [1] [1] == 1) && (surface [1] [2] == 1))
    || ((surface [2] [0] == 1) && (surface [2] [1] == 1) && (surface [2] [2] == 1) )
    || ((surface [0] [0] == 1) && (surface [1] [0] == 1) && (surface [2] [0] == 1) )
    || ((surface [0] [1] == 1) && (surface [1] [1] == 1) && (surface [2] [1] == 1))
    || ((surface [0] [2] == 1) && (surface [1] [2] == 1) && (surface [2] [2] == 1) )
    || ((surface [0] [0] == 1) && (surface [1] [1] == 1) && (surface [2] [2] == 1) )
    || ((surface [0] [2] == 1) && (surface [1] [1] == 1) && (surface [2] [0] == 1)) ) 
Plattformabhängige Dinge wie

Code: Alles auswählen

system("cls");
sind nicht sooo schön. z.B. läuft es dadurch bei mir (Linux Ubuntu) nicht ^^

HAH gefunden xDD

Vergleich ist der == Operator, = ist die Zuweisung ;-) D.h. nach dem ersten Prüfen tauchen 1en auf: die Zeile

Code: Alles auswählen

((surface [0] [0] = 1) && (surface [0] [1] = 1) && (surface [0] [2] = 1)
fällt immer true aus, da Zuweisungen der Art klappen. D.h. er führt die Zuweisung aus und merkt "klappt, also true"... that's C ^^

Ansonsten verstehe ich

Code: Alles auswählen

char surface [3] [3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
nicht ganz: die Surface ist dein Spielbrett, in das du die Eingaben reinschreibst. Warum char und warum die Zahlen von 1 bis 9? Klar, dass sollen deine Indizes sein! Aber schöner wäre dort die Spielernummer (1 für Spieler 1, 2 für Spieler 2, 0 für "Leer") einzutragen. Dann sparst du dir auch das, was du vermutlich mit fieldSet erreichen möchtest. Dann wäre int naheliegender - char sollte eigentlich für Zeichen benutzt werden. Alternativ kannst du auch ein enum verwenden ^^

LG Glocke

/EDIT: Das hätte mir echt früher auffallen sollen :D (s.o.^^)

/EDIT2: für das cls kannst du vllt mit dem Präprozessor arbeiten, falls du weißt was das ist, z.B.

Code: Alles auswählen

void clear_screen() {
#ifdef WIN32
    // für windows
    system("cls");
#else
    // für zb Linux, ob es bei Mac geht, weiß ich nicht
    system("clear");
#endif
}
Dann sollte beim Compilieren entsprechend der Zielplattform der entsprechende Code im entsprechenden Zweig compiliert und der andere verworfen werden. Ohne Gewähr - hab kein Windows :D Von solchen Feinheiten würde ich ersteinmal abraten :)

Antworten