Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

05.03.2014, 22:32

Mein erstes Tic Tac Toe

Hallo Community,

ich bin das M und versuche mich gerade in der Spieleprogrammierung. :D

Meine bisherigen Programmierkenntnisse beschränken sich lediglich auf kleinere Websachen (php, js, etc).

Da ich überlege eine Ausbilung in die Game-Programming-Richtung zu machen habe ich angefangen mich mal mit zuerst C und dann relativ schnell mit C++ auseinander zu setzen.
Was würde sich da besser anbieten als ein kleines erstes Spiel zu Programmieren?

Genau deswegen habe ich mich auch mal, wie viele vor mir, an ein Tic Tac Toe gewagt.

Als nächster Schritt folgt dann eine Umsetzung auf eine grafische Oberfläche - mal schauen was es da wird. Evtl. habt ihr ja ein paar Vorschläge? (ich habe mich noch nicht wirklich mit den Möglichkeiten beschäftigt)

Anbei erstmal der Code:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
#include <iostream>
#include <stdlib.h>
#include <time.h>

using namespace std;
const int maxX = 3;
const int maxY = 3;
int field[maxX][maxY], menu, checkField[maxX][maxY], bestX, bestY;

const int winCasePartA[8][3] = {
    { 0, 0, 0 },
    { 1, 1, 1 },
    { 2, 2, 2 },
    { 0, 1, 2 },
    { 0, 1, 2 },
    { 0, 1, 2 },
    { 0, 1, 2 },
    { 2, 1, 0 }
};
const int winCasePartB[8][3] = {
    { 0, 1, 2 },
    { 0, 1, 2 },
    { 0, 1, 2 },
    { 0, 0, 0 },
    { 1, 1, 1 },
    { 2, 2, 2 },
    { 0, 1, 2 },
    { 0, 1, 2 }
};
const int heuristic_Array[4][4] = {
    {     0,   -10,  -100, -1000 },
    {    10,     0,     0,     0 },
    {   100,     0,     0,     0 },
    {  1000,     0,     0,     0 }
};

void deleteScreen();
void deleteField();
void drawField();
void shortPause(int seconds);
void startGame(int character, int firstPlayer);
void makeMove(int character, int bestX, int bestY);
void pickGame();
int pickCharacter();
int pickFirstPlayer();
int evaluateBestScore(int character);
void checkGameStatus(int character);
bool checkGameEnd(char translateCharacter);

int main()
{
    deleteScreen();
    do {
        cout << "[1] Spieler gegen Spieler" << endl;
        cout << "[2] Spieler gegen Computer" << endl;
        cout << "[3] Regeln" << endl;
        cout << "[4] Spiel beenden" << endl;
        cout << endl << "Bitte triff eine Auswahl: ";
        cin >> menu;
        switch(menu){
            case 1: {           //Startet das Spiel gegen Spieler
                deleteField();
                pickGame();
            } break;
            case 2: {           //Startet das Spiel gegen Computer
                deleteField();
                pickGame();
            } break;
            case 3: {           //Zeigt die Regeln
                deleteScreen();
                cout << "Ziel des Spieles ist es auf einem 3x3 grossen Spielfeld\nals erster Spieler drei gleiche Symbole [X oder O]\nin einer Reihe zu haben." << endl << endl;
                system("PAUSE");
                deleteScreen();
            } break;
            case 4: {           //Bendet das Spiel
                deleteScreen();
                cout << "Spiel beendet." << endl << endl;
            } break;
            default: {           //Bei falscher eingabe
                cout << endl << "Bitte triff eine gueltige Auswahl!" << endl;
            }
        }
    }while(menu != 4);

    return 0;
}

void deleteScreen() {
    system("cls");
    cout << endl << "---------- Tic Tac Toe V 0.9 ----------" << endl << endl;
}

void shortPause(int seconds) {
    clock_t start;
    seconds *= CLOCKS_PER_SEC;
    start = clock();
    while( clock() < (start+seconds));
}

void deleteField() {
    for(int y=0; y<maxY; y++) {
        for(int x=0; x<maxX; x++){
            field[x][y] = 0;
        }
    }
}

void drawField() {
    char fieldCharacter;
    cout << endl << "    1  2  3" << endl;
    for(int y=0; y<maxY; y++) {
        cout << (char)('A'+y) << " "; //Setzt einen Buchstaben angefangen bei A vor jede Zeile
        for(int x=0; x<maxX; x++){
            if(field[x][y] == 0) { fieldCharacter = '.'; }
            else if(field[x][y] == 1) { fieldCharacter = 'X'; }
            else if(field[x][y] == 2) { fieldCharacter = 'O'; }
            cout << "  " << fieldCharacter;
        }
        cout << endl; //Fügt einen Zeilenumbruch nach dem letzem X-Wert hinzu
    }
}

void pickGame() {
    int character=0, firstPlayer=0;
    character = pickCharacter();
    switch(menu) {
        case 1: {
            startGame(character, firstPlayer);
            break;
        }
        case 2: {
            firstPlayer = pickFirstPlayer(); //je nachdem welcher Spieler (S oder C) anfängt wechselt der vom Spieler gewählte Buchstabe
            if(firstPlayer == 2 && character == 1) { character = 2; }
            else if(firstPlayer == 2 && character == 2) { character = 1; }
            startGame(character, firstPlayer);
            break;
        }
     }
}

int pickCharacter() {
    char character;
    bool checkCharacter = false;
    deleteScreen();
    do {
        cout << "Spieler 1 waehle deinen Buchstaben: [X oder O] ";
        cin >> character;
        switch(character) {     //sowohl bei Groß- als auch bei Kleinbuchstaben
            case 'X': character = 1; checkCharacter = true; break;
            case 'x': character = 1; checkCharacter = true; break;
            case 'O': character = 2; checkCharacter = true; break;
            case 'o': character = 2; checkCharacter = true; break;
            default: deleteScreen(); cout << "Bitte gib nur X oder O ein!" << endl << endl;
        }
    }while(checkCharacter != true);
    return character;
}

int pickFirstPlayer() {
    char firstPlayer;
    bool checkFirstPlayer = false;
    deleteScreen();
    do {
        cout << "Wer beginnt das Spiel: [S oder C] ";
        cin >> firstPlayer;
        switch(firstPlayer) {       //sowohl bei Groß- als auch bei Kleinbuchstaben
            case 'S': firstPlayer = 1; checkFirstPlayer = true; break;
            case 's': firstPlayer = 1; checkFirstPlayer = true; break;
            case 'C': firstPlayer = 2; checkFirstPlayer = true; break;
            case 'c': firstPlayer = 2; checkFirstPlayer = true; break;
            default: deleteScreen(); cout << "Bitte gib nur S oder C ein!" << endl << endl;
        }
    }while(checkFirstPlayer != true);
    return firstPlayer;
}

void startGame(int character, int firstPlayer) {
    bool gameEnd = false;
    char playerY;
    int playerX, checkY, checkX;
    char translateCharacter;
    do{
        deleteScreen();
        drawField();
        if(character == 1) { translateCharacter = 'X'; } else { translateCharacter = 'O';}
        switch(firstPlayer) {
            case 0: case 1: {  //Wenn kein firstPlayer festgelegt wurde oder Spieler gegen den Computer spielt
                checkGameStatus(character);
                cout << endl << "Spieler " << translateCharacter << " du bist am Zug: [z.B. A1] ";
                cin >> playerY >> playerX;
                switch (playerY) { //Wenn Großschreibung - A __ Wenn Kleinschreibung - a
                    case 'A': case 'B': case 'C': {
                        checkY = playerY - 'A'; //Konvertiert eingabe von A,B,C auf 0, 1, 2 für das Field-Array
                        break;
                    }
                    case 'a': case 'b': case 'c': {
                        checkY = playerY - 'a'; //Konvertiert eingabe von a,b,c auf 0, 1, 2 für das Field-Array
                        break;
                    }
                }
                checkX = playerX - 1;  //Konvertiert eingabe von 1,2,3 auf 0, 1, 2 für das Field-Array
                if(checkX >= 0 && checkX < maxX && checkY >= 0 && checkY < maxY) { //Überprüft ob eine valide Spielfeldeingabe vorliegt
                    if(field[checkX][checkY] != 1 && field[checkX][checkY] != 2) { //Überprüft ob das Feld bereits besetzt ist
                        field[checkX][checkY] = character;
                        gameEnd = checkGameEnd(translateCharacter);
                        if(character == 1) { character = 2; } else { character = 1; } //ändert den aktuellen Buchstaben für den Spieler
                        if(firstPlayer == 1) { firstPlayer = 2; } //ändert den als nächstes ziehenden Spieler wenn es einen Wechsel gibt (firstPlayer != 0)
                    }
                    else {
                        cout << endl << "Das Feld ist bereits besetzt!" << endl;
                        shortPause(3);
                    }
                }
                else {
                    cout << endl << "Bitte gib nur werte von A1 - C3 ein!" << endl;
                    shortPause(3);
                }
                break;
            }
            case 2: { //Computer zieht
                checkGameStatus(character);
                cout << endl << "Spieler " << translateCharacter << " ist am Zug: ";
                shortPause(2);
                makeMove(character, bestX, bestY);
                shortPause(2);
                gameEnd = checkGameEnd(translateCharacter);
                if(character == 1) { character = 2; } else { character = 1; }
                firstPlayer = 1; //ändert den aktuellen Buchstaben für den Spieler
                break; //ändert den als nächstes ziehenden Spieler
            }
        }
    }while(gameEnd != true);
}

void makeMove(int character, int bestX, int bestY) {
    int random, randomX, randomY, moveX, maxRandX=1, maxRandY=1;
    char moveY;
    bool emptyField = false;

    srand(time(NULL)); //wird benötigt um immer unterschiedliche Zufallszahlen zu erhalten
    random = (rand() % 10); //Zufall von 0 - 9

    if(random < 8) { //von 0-7 -> 80%
        field[bestX][bestY] = character;
        moveX = bestX + 1;
        moveY = bestY + 'A';
        cout << moveY << moveX;
    }
    else{
        for(int y=0; y<maxY; y++) {
            for(int x=0; x<maxX; x++){
                if(field[x][y] == 0 && (x+1)>maxRandX){ //grenzt die Zufallsberechnung zur Positionsfindung ein -> die Position wird schneller gefunden
                    maxRandX = x;
                }
                if(field[x][y] == 0 && (y+1)>maxRandY){ //grenzt die Zufallsberechnung zur Positionsfindung ein -> die Position wird schneller gefunden
                    maxRandY = y;
                }
            }
        }
        do{
            srand(time(NULL)); //wird benötigt um immer unterschiedliche Zufallszahlen zu erhalten
            randomX = rand() % maxRandX; //Zufall der maximalen X Position um schneller zu einem Validem X zu gelangen
            srand(time(NULL)); //wird benötigt um immer unterschiedliche Zufallszahlen zu erhalten
            randomY = rand() % maxRandY; //Zufall der maximalen Y Position um schneller zu einem Validem Y zu gelangen
            if(field[randomX][randomY] == 0) {
                emptyField = true;
                field[randomX][randomY] = character;
                moveX = randomX + 1;
                moveY = randomY + 'A';
                cout << moveY << moveX;
            }
        }while(emptyField != true);
    }
}

void checkGameStatus(int character) {
    int getScore, bestScore;
//Aktuelles Spielfeld wird kopiert um besser damit arbeiten zu können
    for(int y=0; y<maxY; y++) {
        for(int x=0; x<maxX; x++){
            checkField[x][y] = field[x][y];
        }
    }

    bestScore = evaluateBestScore(character); //aktuelle Bewertung des Spielfeldes

    for(int y=0; y<maxY; y++) {
        for(int x=0; x<maxX; x++){
            if(field[x][y] == 0) {
                checkField[x][y] = character; //Setzt den aktuellen Buchstaben an jede leere Position
                getScore = evaluateBestScore(character); //holt sich die bewertung des gemachten Zuges
                checkField[x][y] = 0; // setzt den vorher gesetzten Buchstaben wieder auf 0
                if(getScore == bestScore){
                    bestX = x;
                    bestY = y;
                }
                else if(getScore > bestScore) {
                    bestScore = getScore;
                    bestX = x;
                    bestY = y;
                }
            }
        }
    }
}

int evaluateBestScore(int character)    {
    int opponent, position;
    if(character == 1) { opponent=2; } else { opponent=1; }; //Legt den Gegnerbuchstaben fest
    int players, others, score = 0, i, j;
    for(i=0; i<8; i++)  {
        players = others = 0;
        for(j=0; j<3; j++)  {
            position = checkField[winCasePartA[i][j]][winCasePartB[i][j]]; //Zeigt das Aktuelle Feld an z.B. [0][0] .. [0][1] .. etc....
            if (position == character) {
                players++;
            }
            else if (position == opponent) {
                others++;
            }
        }
        score += heuristic_Array[players][others]; //holt sich die Score aus dem vordefiniertem Array
    }
    return score;
}

bool checkGameEnd (char translateCharacter) {
    int i, j=0, checkField[maxX][maxY], countEmpty = 3;
    bool victory = false;
//zählt die Leerfelder beginnend bei 3, da das Spielfeld aus 0, 1, 2 besteht und kopiert alles in ein Platzhalter-Array
    for(int y=0; y<maxY; y++) {
        for(int x=0; x<maxX; x++){
            if(field[x][y] == 0) {
                checkField[x][y] = countEmpty;
                countEmpty++;
            }
            else {
                checkField[x][y] = field[x][y];
            }
        }
    }
//Überprüft die horizontale und vertikale Übereinstimmung
    for(i=0; i<3; i++) {
        if((checkField[i][j] == checkField[i][j+1] && checkField[i][j+1] == checkField[i][j+2]) ||
           (checkField[j][i] == checkField[j+1][i] && checkField[j+1][i] == checkField[j+2][i])) {
                victory = true;
                deleteScreen();
                drawField();
                cout << endl << "Herzlichen Glueckwunsch Spieler " << translateCharacter << " - Du hast das Spiel gewonnen!" << endl << endl;
                system("PAUSE");
                deleteScreen();
        }
    }
//Überprüft die diagonale Übereinstimmung
    if((checkField[0][0] == checkField[1][1] && checkField[1][1] == checkField[2][2]) ||
       (checkField[2][0] == checkField[1][1] && checkField[1][1] == checkField[0][2])) {
            victory = true;
            deleteScreen();
            drawField();
            cout << endl << "Herzlichen Glueckwunsch Spieler " << translateCharacter << " - Du hast das Spiel gewonnen!" << endl << endl;
            system("PAUSE");
            deleteScreen();
    }
//Wenn nix mehr frei ist und bisher nicht gewonnen wurde
    if(countEmpty == 3 && victory != true) {
        victory = true;
        deleteScreen();
        drawField();
        cout << endl << "Das Spiel endet unentschieden!" << endl << endl;
        system("PAUSE");
        deleteScreen();
    }
    return victory;
}


Mich würde es freuen wenn Ihr euch kurz Zeit nehmen würdet um da mal drüber zu schauen und mir evtl. Ratschläge zur Verbesserung des ganzen geben könntet.

Ein Problem, was ich bisher noch nicht bewältigen konnte ist, dass die "KI" fast immer in der gleichen Ecke anfängt.

z.B.
Der Spieler zieht zuerst in der Mitte:

0 0 0
0 1 0
0 0 0

Dann zieht der Computer fast immer unten rechts:

0 0 0
0 1 0
0 0 2

Vielleicht habt Ihr ja noch eine Idee um das ganze etwas variabler zu gestalten?

Die Position für den Computerspieler wird hier rausgesucht:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
void checkGameStatus(int character) {
    int getScore, bestScore;
//Aktuelles Spielfeld wird kopiert um besser damit arbeiten zu können
    for(int y=0; y<maxY; y++) {
        for(int x=0; x<maxX; x++){
            checkField[x][y] = field[x][y];
        }
    }

    bestScore = evaluateBestScore(character); //aktuelle Bewertung des Spielfeldes

    for(int y=0; y<maxY; y++) {
        for(int x=0; x<maxX; x++){
            if(field[x][y] == 0) {
                checkField[x][y] = character; //Setzt den aktuellen Buchstaben an jede leere Position
                getScore = evaluateBestScore(character); //holt sich die bewertung des gemachten Zuges
                checkField[x][y] = 0; // setzt den vorher gesetzten Buchstaben wieder auf 0
                if(getScore == bestScore){
                    bestX = x;
                    bestY = y;
                }
                else if(getScore > bestScore) {
                    bestScore = getScore;
                    bestX = x;
                    bestY = y;
                }
            }
        }
    }
}

int evaluateBestScore(int character)    {
    int opponent, position;
    if(character == 1) { opponent=2; } else { opponent=1; }; //Legt den Gegnerbuchstaben fest
    int players, others, score = 0, i, j;
    for(i=0; i<8; i++)  {
        players = others = 0;
        for(j=0; j<3; j++)  {
            position = checkField[winCasePartA[i][j]][winCasePartB[i][j]]; //Zeigt das Aktuelle Feld an z.B. [0][0] .. [0][1] .. etc....
            if (position == character) {
                players++;
            }
            else if (position == opponent) {
                others++;
            }
        }
        score += heuristic_Array[players][others]; //holt sich die Score aus dem vordefiniertem Array
    }
    return score;
}


Vielen Dank für eure Bemühungen!

M

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

2

06.03.2014, 01:04

C-/C++-Quelltext

1
case 'X':  case 'x': character = 1; checkCharacter = true; break;

sollte das selbe machen wie

C-/C++-Quelltext

1
2
case 'X': character = 1; checkCharacter = true; break;
case 'x': character = 1; checkCharacter = true; break;


Wieso zählst du nicht einfach die gemachten Züge anstatt jede Runde die leeren Felder abzuzählen?
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

3

06.03.2014, 09:43

Hi NachoMan,

danke für den Case-Hinweis. Ich hab das lustiger Weise bei der Abfrage der Eingabe in startGame() auch so gelöst nur bei der Spieler und Buchstabenwahl nicht - wer weiß was mich da geritten hat. :D

Da mein field-Array ja nur aus 0, 1, 2 besteht muss ich um die Win-Situation zu vergleichen sowieso jedes mal das Array in ein zweites Kopieren - also statt Nullen dann Zahlen aufsteigend von 3.
Da kann ich mir die Leerfelder auch gleich mit auszählen.

Ich fand den Weg mit der Schleifenüberprüfung schmaler als alle Win-Situationen per Hand einzutragen.

C-/C++-Quelltext

1
2
3
    for(i=0; i<3; i++) {
        if((checkField[i][j] == checkField[i][j+1] && checkField[i][j+1] == checkField[i][j+2]) ||
           (checkField[j][i] == checkField[j+1][i] && checkField[j+1][i] == checkField[j+2][i])) {


Sicherlich könnte man das field-Array auch von Anfang an mit 1, 2, und dann 3++ gestalten und sich das ganze kopiere sparen aber ich lern ja noch und bin so erstmal recht zufrieden mit meiner Lösung.

M

Werbeanzeige