4Gewinnt - Strategieproblem

oldsql.Triso

Volt-Modder(in)
Hi Com,

ich hoffe ihr könnt mir helfen und zwar sollen wir 4Gewinnt via C programmieren und bei der Strategie sind wir nach dem Negamax-Algorithmus vorgegangen. Nun erkennt er aber gerade nicht, das sich leere Felder auch als Möglichkeit erschließen bzw. wenn der Gegner schon 2 nebeneinander hat, das man dort verteidigen müsste. Vllt. findet jemand den Fehler. Habe hoffentlich ausreichend kommentiert. Die "KI" baut gerne Türmchen und sieht seine eigenen Gewinnmöglichkeiten nicht.

#include "gameField.h"
#include <stdio.h>
Code:
	    struct MoveValue
        {
            double Score;
            int Column;
        };
        
		void countFieldTypes(int **field, int p, int posX, int posY, int modX, int modY, int &occupiedByPlayer, int &free)
        {

            posX = posX + modX;
            posY = posY + modY;

            // Wenn die Grenzen überschritten wurden, dann ist die Prüfung für den Weg beendet
            if (posX < 0 || posX >= 7) return;
            if (posY < 0 || posY >= 6) return;

            if (field[posX][posY]== 0) //Wenn das Feld leer ist
            {
                // wird free erhöht
                free++;
            }
            else if (field[posX][posY]== p) // Feld ist von "mir/aktuellen" belegt
            {
                occupiedByPlayer++; // Hochzählen von cbp
                //index = 3 + Convert.ToInt32(Math.Pow(coinsInRow + 1, 2));
            }
            else
            {
                // Feld ist durch Gegner belegt
                return;
            }

            // Es wird solange geprüft, bis die Spielgrenzen erreicht sind, 
            // da somit Felder, welche noch viele Möglichkeiten bieten, besser bewertet werden.
            countFieldTypes(field, p, posX, posY, modX, modY,occupiedByPlayer, free);
        }

		 int faculty(int n)
        {
            return n == 0 ? 1 : n * faculty(n - 1);
        }

        int getLineIndex(int **field, int p, int posX, int posY, int modX, int modY)
        {
            int occupiedByPlayerPart1 = 0;
            int free1 = 0;
            int occupiedByPlayerPart2 = 0;
            int free2 = 0;
            countFieldTypes(field, p, posX, posY, modX, modY, occupiedByPlayerPart1,free1);
            countFieldTypes(field, p, posX, posY, modX * (-1), modY * (-1), occupiedByPlayerPart2, free2); // für die gegenüberliegende Seiten
																											// Siehe Zeile 75 - 
            if (occupiedByPlayerPart1 > 0)
                occupiedByPlayerPart1 += 2;

            if (occupiedByPlayerPart2 > 0)
                occupiedByPlayerPart2 += 2;

            // Die leeren Zeilen werden verdoppelt
            // Die Anzahl der besetzen Steine mit Fakultät berechnet
            return ((free1 + free2) * 3) + faculty(occupiedByPlayerPart1) + faculty(occupiedByPlayerPart2); 
        } 	// Freie Felder werden mit 3 multipliziert da es beim Überwiegen noch viele Chancen gibt; ansonsten unterbewertet

        
		 int calculateChoiseIndex(int **field, int p, int posX, int posY)
        {

            //
            // 6  1  2  3
            // 5   X X X
            // 4    XXX
            // 3  4XX+XX4
            // 2    XXX
            // 1   X X X
            // 0  3  2  1
            //    0123456
            //

            int indexP1 = 0;

            // Die diagonalen müssen als eine Zeile betrachtet werden.
            indexP1 += getLineIndex(field, p, posX, posY, -1, 1);
            indexP1 += getLineIndex(field, p, posX, posY, 0, 1);
            indexP1 += getLineIndex(field, p, posX, posY, 1, 1);
            indexP1 += getLineIndex(field, p, posX, posY, -1, 0);

            int indexP2 = 0;

            // Die diagonalen müssen als eine Zeile betrachtet werden.
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, -1, 1);
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, 0, 1);
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, 1, 1);
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, -1, 0);

            //field[posX, posY].ChoiseIndexP1 = indexP1;
            //field[posX, posY].ChoiseIndexP2 = (-1) * indexP2;

            return indexP1 > (indexP2 * (-1)) ? indexP1 : indexP2; // das gleiche wie if(index P1 > (indexP2*(-1))) return indexP1; else indexP2;

        }



		struct MoveValue bestMove(int **field, int sideToMove, int depth, int maxDepth) //Züge im vorraus berechnen
        { // über maxDepth wird die "cleverness" gesetzt, quasi die Züge im vorraus
            // Speichert die beste Score
            struct MoveValue best_score;
			best_score.Score = -100000; // Default-Wert vorgeben
			best_score.Column = 0;
            if (depth % 2 == 1)
                best_score.Score *= -1;

            struct MoveValue mv;
			mv.Score = 0;
			mv.Column = 0;
            //int directWinningChance = 0;
            for (int x = 0; x < 7; x++)
            {
                // Versuche einen Stein in jede der 7 Spalten
                mv.Column = x;
                if (field[x][6 - 1] != 0)
                    continue; // Wenn die Spalte voll ist, ignoriere sie

                // Die Spalte finden wo man einen Stein setzen kann - y ist die Reihe
                int y = 0;
                while (y < 6 && field[x][y] != 0)
                    y++;

                field[x][y] = sideToMove;

                struct MoveValue cur;
				cur.Score = 0;
				cur.Column = 0;
                cur.Score = calculateChoiseIndex(field, sideToMove, x, y);

                 if (depth == maxDepth)
                {
                    mv.Score = cur.Score;
                    //directWinningChance = 1;
                }
                else // Weiter suchen (Rekursiver Aufruf)
                    mv.Score = bestMove(field, 3 - sideToMove, depth + 1, maxDepth).Score;

                // wenn dieser move besser ist, nutzen diesen anstatt des anderen
                if (mv.Score > best_score.Score && depth % 2 == 0)
                {
                    best_score.Score = mv.Score;
                    best_score.Column = mv.Column;
                }
                else if (mv.Score < best_score.Score && depth % 2 == 1)
                {
                    best_score.Score = mv.Score;
                    best_score.Column = mv.Column;
                }

                // Feld nullen und Zug zurück setzen
                field[x][y] = 0;
            }

            return best_score; // beste Wahl/Score
        }
 
int cpuChoice(int **field, int player, int difficulty) {

	 return bestMove(field, player, 0, difficulty + 4).Column;
}

Ihr könnt mir auch nur sagen wo der Fehler ist, das würde mir ausreichen. Soweit funktioniert alles, also die anderen Funktionen wie Visualisierung, Felderstellung, Prüfung auf 4 in einer Reihe/Diagonale/Spalte in alle Richtungen passt. Meiner Meinung nach kann es nur die Feldwertberechnung sein, aber ich sehe da einfach nicht den Fehler.

Gruß
 
Zuletzt bearbeitet:
1) Also ich habe jetzt etwas drübergeschaut, aber war vielleicht auch ein bisschen viel für das bisschen Zeit, was ich grad habe.
und ich muss sagen, ich weiß jetztn nich nicht so genau wie das funktionieren soll. Vielleicht könntest du nochmal kurz zusammenfassen, was welche funktion erreichen soll.


2) Kannst du deinen Code oben mal mit den "[ CODE ] und [ /CODE ]" Labels formatieren? ich glaube dann wird automatisch etwas eingerückt und das ganze wird etwas leserlicher.


3)
Code:
int faculty(int n)
        {
            return n == 0 ? 1 : n * faculty(n - 1);
        }
könntest du zu
Code:
int faculty(int n)
        {
            return n < 2 ? 1 : n * faculty(n - 1);
        }
ändern. Somit verhinderst du auch Exceptions, wenn du z.B. ne Zahl kleiner 0 eingibst ;)


okay, Formatierung:
Code:
#include "gameField.h"
#include <stdio.h>
 
 
        struct MoveValue
        {
            double Score;
            int Column;
        };
 
        void countFieldTypes(int **field, int p, int posX, int posY, int modX, int modY, int &occupiedByPlayer, int &free)
        {
 
            posX = posX + modX;
            posY = posY + modY;
 
            // Wenn die Grenzen überschritten wurden, dann ist die Prüfung für den Weg beendet
            if (posX < 0 || posX >= 7) return;
            if (posY < 0 || posY >= 6) return;
 
            if (field[posX][posY]== 0) //Wenn das Feld leer ist
            {
                // wird free erhöht
                free++;
            }
            else if (field[posX][posY]== p) // Feld ist von "mir/aktuellen" belegt
            {
                occupiedByPlayer++; // Hochzählen von cbp
                //index = 3 + Convert.ToInt32(Math.Pow(coinsInRow + 1, 2));
            }
            else
            {
                // Feld ist durch Gegner belegt
                return;
            }
 
            // Es wird solange geprüft, bis die Spielgrenzen erreicht sind, 
            // da somit Felder, welche noch viele Möglichkeiten bieten, besser bewertet werden.
            countFieldTypes(field, p, posX, posY, modX, modY,occupiedByPlayer, free);
        }
 
         int faculty(int n)
        {
            return n == 0 ? 1 : n * faculty(n - 1);
        }
 
        int getLineIndex(int **field, int p, int posX, int posY, int modX, int modY)
        {
            int occupiedByPlayerPart1 = 0;
            int free1 = 0;
            int occupiedByPlayerPart2 = 0;
            int free2 = 0;
            countFieldTypes(field, p, posX, posY, modX, modY, occupiedByPlayerPart1,free1);
            countFieldTypes(field, p, posX, posY, modX * (-1), modY *  (-1), occupiedByPlayerPart2, free2); // für die gegenüberliegende Seiten
                                                                                                            // Siehe Zeile 75 - 
            if (occupiedByPlayerPart1 > 0)
                occupiedByPlayerPart1 += 2;
 
            if (occupiedByPlayerPart2 > 0)
                occupiedByPlayerPart2 += 2;
 
            // Die leeren Zeilen werden verdoppelt
            // Die Anzahl der besetzen Steine mit Fakultät berechnet
            return ((free1 + free2) * 3) + faculty(occupiedByPlayerPart1) + faculty(occupiedByPlayerPart2); 
        }     // Freie Felder werden mit 3 multipliziert da es beim Überwiegen noch viele Chancen gibt; ansonsten unterbewertet
 
 
         int calculateChoiseIndex(int **field, int p, int posX, int posY)
        {
 
            //
            // 6  1  2  3
            // 5   X X X
            // 4    XXX
            // 3  4XX+XX4
            // 2    XXX
            // 1   X X X
            // 0  3  2  1
            //    0123456
            //
 
            int indexP1 = 0;
 
            // Die diagonalen müssen als eine Zeile betrachtet werden.
            indexP1 += getLineIndex(field, p, posX, posY, -1, 1);
            indexP1 += getLineIndex(field, p, posX, posY, 0, 1);
            indexP1 += getLineIndex(field, p, posX, posY, 1, 1);
            indexP1 += getLineIndex(field, p, posX, posY, -1, 0);
 
            int indexP2 = 0;
 
            // Die diagonalen müssen als eine Zeile betrachtet werden.
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, -1, 1);
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, 0, 1);
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, 1, 1);
            indexP2 -= getLineIndex(field, 3 - p, posX, posY, -1, 0);
 
            //field[posX, posY].ChoiseIndexP1 = indexP1;
            //field[posX, posY].ChoiseIndexP2 = (-1) * indexP2;
 
            return indexP1 > (indexP2 * (-1)) ? indexP1 : indexP2; //  das gleiche wie if(index P1 > (indexP2*(-1))) return indexP1; else  indexP2;
 
        }
 
 
 
        struct MoveValue bestMove(int **field, int sideToMove, int depth, int maxDepth) //Züge im vorraus berechnen
        { // über maxDepth wird die "cleverness" gesetzt, quasi die Züge im vorraus
            // Speichert die beste Score
            struct MoveValue best_score;
            best_score.Score = -100000; // Default-Wert vorgeben
            best_score.Column = 0;
            if (depth % 2 == 1)
                best_score.Score *= -1;
 
            struct MoveValue mv;
            mv.Score = 0;
            mv.Column = 0;
            //int directWinningChance = 0;
            for (int x = 0; x < 7; x++)
            {
                // Versuche einen Stein in jede der 7 Spalten
                mv.Column = x;
                if (field[x][6 - 1] != 0)
                    continue; // Wenn die Spalte voll ist, ignoriere sie
 
                // Die Spalte finden wo man einen Stein setzen kann - y ist die Reihe
                int y = 0;
                while (y < 6 && field[x][y] != 0)
                    y++;
 
                field[x][y] = sideToMove;
 
                struct MoveValue cur;
                cur.Score = 0;
                cur.Column = 0;
                cur.Score = calculateChoiseIndex(field, sideToMove, x, y);
 
                 if (depth == maxDepth)
                {
                    mv.Score = cur.Score;
                    //directWinningChance = 1;
                }
                else // Weiter suchen (Rekursiver Aufruf)
                    mv.Score = bestMove(field, 3 - sideToMove, depth + 1, maxDepth).Score;
 
                // wenn dieser move besser ist, nutzen diesen anstatt des anderen
                if (mv.Score > best_score.Score && depth % 2 == 0)
                {
                    best_score.Score = mv.Score;
                    best_score.Column = mv.Column;
                }
                else if (mv.Score < best_score.Score && depth % 2 == 1)
                {
                    best_score.Score = mv.Score;
                    best_score.Column = mv.Column;
                }
 
                // Feld nullen und Zug zurück setzen
                field[x][y] = 0;
            }
 
            return best_score; // beste Wahl/Score
        }
 
int cpuChoice(int **field, int player, int difficulty) {
 
     return bestMove(field, player, 0, difficulty + 4).Column;
}
 
Sag mir mal bitte welche Stelle dir nicht klar ist. Bestimmt unten der rekursive Aufruf, in der die beste Score für die beste Spalte anhand der maximalen Tiefe errechnet wird, oder? Wir sind aber jetzt auf einen anderen Dampfer wahrscheinlich umgestiegen ^^ - min-max-Methode.
 
Zurück