C++ KI Erstellen

ZuseZ3

Lt. Commander
Registriert
Jan. 2014
Beiträge
1.666
Nachdem ich mir einige Berichte über das schreiben eigener Schach KI-s durchgelesen habe, bekam ich das Gefühl das Viergewinnt einen guten Einstieg darstellen dürfte. :lol:
Umsetzen möchte ich das ganze mit c++, weil es mehr möglichkeiten bietet als Java/Python (Die einzigen anderen Sprachen, welche ich genutzt habe). Die Geschwindigkeitsvorteile nehme ich gerne mit.
Sonderlich viel Programmiert habe ich bisher nicht, lediglich einfache Project Euler aufgaben gelöst und die Aufgaben an der Schule/Uni bearbeitet. Dadurch habe ich hoffentlich zuminindest Theoretisch die entsprechenden Grundlagen.
Um die Vererbung/Objektorierntierung mal praktisch anzuwenden, dachte ich hier daran das Spielfeld und die KI als eigene Klassen zu erstellen (Wer hättes blos erwartet :freaky:).
Dadurch könnte ich das ganze dann auch bei anderen Spielen wie Schach (bzw. erstmal einfacheren Varianten davon) weiternutzen.
Das Spielfeld habe ich als grundgerüst soweit fertig, demnächst währe dann die KI dran
Da vor aber erstmal ein paar Grundlegende Fragen, da ich c++ bisher nur privat genutzt habe.

1) Habe ich das ganze sinnvol in .cpp/.header aufgeteilt oder ist das kompletter murks?--> Erledigt
2) Habe ich irgendwelche anderen gravierenden Fehler drin?
3) Vor allem, wie muss ich die copy Methode überarbeiten, damit ich das spielfeld (2d vector) richtig kopiere? Mit Zeigern tu ich mich da realtiv schwer.
Ansonsten fehlt mir natürlich noch die gesamte Speicherverwaltung, für den fall, dass das Programm später mal nicht direkt nach dem ersten spiel beendet wird, sondern weitere folgen.

Hier das Spielfeld:

Hier ist meine Spielfeld.h datei
Code:
#pragma once

#include <iostream>
#include <vector>

using namespace::std;

class Spielfeld
{
public:
	Spielfeld(int, int, short, bool, bool); // breite, hoehe, benoetigte steine in einer reihe, ausgabe, stop nach sieg

	void cpy(Spielfeld); 

	bool setzen(short);

	short winner();

	void undo();
	
	short movenum();

	bool getplayer();

	void setplayer(bool);

	bool getactive();

	void setactive(bool);

	void nextplayer();

	short getbreite();

	short gethoehe();

	void ausgeben();

	void free(){

	}

private: 
	short move = 0; // Anzahl getaetigter züge
	std::vector<short> movesx; 
	std::vector<short> movesy;
	std::vector<std::vector<short>> feld; // Spielfeld
	int breite, hoehe; 
	bool player; // Spieler der aktuell am Zug  ist
	short chainlength; // Benoetigte Steine in einer Reihe um zu gewinnen
	bool active, ausgabe, stoppen; // Spiel noch aktiv?, zuege ausgeben?, nach Sieg spiel beenden? (zuruecknehmen nicht mgl)


	short winner(short b, short h){
		// effizienterer Algorithmus, der nur Steine in der Umgebung des zuletztgesetzten überprüft
		return 0;
	}

};

und hier ist meine Spielfeld.cpp datei:
Code:
#include "stdafx.h"
#include "Spielfeld.h"


Spielfeld::Spielfeld(int b, int h, short c, bool a, bool s)
{
	breite = b; hoehe = h; player = 0; chainlength = c, ausgabe = a, stoppen = s;
	feld.resize(breite, vector<short>(hoehe));
	movesx.resize(breite * hoehe);
	movesy.resize(breite * hoehe);
	active = true;
}

void Spielfeld::cpy(Spielfeld s)
{
	s.move = this->move;
	s.breite = this->breite;
	s.hoehe = this->hoehe;
	s.player = this->player;
	s.chainlength = this->chainlength;
	s.active = this->active;
	s.ausgabe = this->ausgabe;
	this->feld.resize(breite, vector<short>(hoehe));
	this->movesx.resize(breite * hoehe);
	this->movesx = movesx;
	this->movesy.resize(breite * hoehe);
	this->movesy = movesy;
}

bool Spielfeld::setzen(short b)
{
	if (getactive() == false || b < 0 || b >= breite || feld[b][hoehe - 1] != 0) 
		return false;
	int h = 0;
	for (; h < hoehe & feld[b][h] != 0; h++);
	if (feld[b][h] != 0) 
		return false;
	feld[b][h] = (getplayer() + 1);
	movesx[move] = b;
	movesy[move] = h;
	nextplayer();
	move++;
	if (ausgabe) 
	{
		system("cls");
		ausgeben();
	}
	winner();
	return true;
}

short Spielfeld::winner()
{
	for (int y = 0; y < hoehe; y++){
		for (int x = 0; x < breite; x++){
			//cout << x << "/" << y << " tested " << endl;
			if (x < breite - chainlength && feld[x][y] != 0 && feld[x][y] == feld[x + 1][y] &&
				feld[x + 2][y] == feld[x + 3][y] && feld[x + 1][y] == feld[x + 2][y]) {
				if (stoppen == true)setactive(false);
				cout << "GAME ENDED (horizontal win)" << endl;
				return feld[x][y];
			}
			if (y < hoehe - chainlength && feld[x][y] != 0 && feld[x][y] == feld[x][y + 1] &&
				feld[x][y + 2] == feld[x][y + 3] && feld[x][y + 1] == feld[x][y + 2]) {
				if (stoppen == true)setactive(false);
				cout << "GAME ENDED (vertical win)" << endl;
				//cout << "There is a row from " << x << "|" << y << " to " << x << "|" << (y+4) << endl;
				return feld[x][y];
			}
			if (y < hoehe - chainlength && x < breite - chainlength && feld[x][y] != 0 && feld[x][y] == feld[x + 1][y + 1] &&
				feld[x + 2][y + 2] == feld[x + 3][y + 3] && feld[x + 1][y + 1] == feld[x + 2][y + 2]) {
				if (stoppen == true)setactive(false);
				cout << "GAME ENDED (diagonal win)" << endl;
				//cout << "There is a row from " << x << "|" << y << " to " << x << "|" << (y+4) << endl;
				return feld[x][y];
			}
			if (y < hoehe - chainlength && x - (chainlength - 1) >= 0 && feld[x][y] != 0 && feld[x][y] == feld[x - 1][y + 1] &&
				feld[x - 2][y + 2] == feld[x - 3][y + 3] && feld[x - 1][y + 1] == feld[x - 2][y + 2]) {
				if (stoppen == true)setactive(false);
				cout << "GAME ENDED (diagonal win)" << endl;
				//cout << "There is a row from " << x << "|" << y << " to " << x << "|" << (y-4) << endl;
				return feld[x][y];
			}
		}

	}
	return 0;
}

void Spielfeld::undo()
{
	move--;
	feld[movesx[move]][movesy[move]] = 0;
	movesx[move] = 0;
	movesy[move] = 0;
	nextplayer();
}

short Spielfeld::movenum()
{
	return move;
}

bool Spielfeld::getplayer()
{
	return player;
}

void Spielfeld::setplayer(bool b)
{
	player = b;
}

bool Spielfeld::getactive()
{
	return active;
}

void Spielfeld::setactive(bool b)
{
	active = b;
}

void Spielfeld::nextplayer()
{
	player = !player;
}

short Spielfeld::getbreite()
{
	return breite;
}

short Spielfeld::gethoehe()
{
	return hoehe;
}

void Spielfeld::ausgeben()
{
	for (int h = hoehe - 1; h >= 0; h--){
		for (int b = 0; b < breite; b++)
			cout << feld[b][h] << " ";
		cout << endl;
	}
	cout << endl;
}

Die KI soll später ihre eigene Kopie des Spielfeldes erhalten, auf dem sie beliebig Züge ausführen und zurücknehmen kann, ohne dass die Züge ausgegeben werden. Entsprechende Funktionen stellt das Spielfeld.


Schonmal vielen dank für alle Hinweise :)
 
Zuletzt bearbeitet: (Überarbeiteten Code eingefügt)
Informier dich mal über Minimax, Alpha-Beta-Pruning, Quiescence Search und sowas. Überflieg einfach mal grob, so dass du ein ungefähres Verständnis hast, was das jeweils ist. Dann gib eine ehrliche Antwort auf die Frage, ob du das immer noch für ein gutes Einsteigerprojekt hältst. Ich habe meine Zweifel, aber mag mich täuschen. Denke, wenn du deine Einschätzung gemacht hast, kann man sich besser weiter unterhalten, welche nächsten Schritte für dich sinnvoll sein könnten.
 
Zuletzt bearbeitet:
Erstmal danke für die Hinweise.
Wahrscheinlich hätte ich genauer drauf eingehen sollen, dass ich erstmal keine Hinweise zur KI benötige, weil ich zuerst mein bisheriges Programm verbessern möchte.
Sobald die Basis halbwegs vernünftig läuft, würde ich weitergucken.

Dennoch mal meine Gedanken zur KI:
Durchgelesen habe ich mir einiges an Theorie weil ich vor einigen Monaten mit dem Gedanken gespielt habe, dass ganze als Facharbeitsthema zu nehmen. (Schach-KI). Letzten endes habe ich aber ein anderes Thema genommen.
Gewechselt bin ich jetzt vom Schach zu viergewinnt, weil ich mir dadurch eine menge Faktoren spare (Nur eine Figurenart->keine bitboards nötig, weniger Zugmöglichkeiten->höhere Rechentiefe,...).
Minimax haben wir bereits in der Schule programmiert und alpha-beta-prunning ist mir soweit klar. Quiescence Search schau ich mir gleich nochmal an.
Interessant fand ich vor allem den Hinweis in einem Forum: https://www.c-plusplus.net/forum/274328-full.
Je nachdem, ob man anfäng oder nicht muss man versuchen, auf einer geraden bzw. ungeraden Ebene 3 Steine in eine Reihe zu bekommen, wodurch man fast immer nach einfachem auffüllen gewinnt.

An sich schätze ich das als relativ gut machbar ein, schließlich kann ich ja mit dem Minimax algorithmus und random moves anfangen. Dies scheint ja bereits zu reichen um viele Spieler zu besiegen.
Dazu könnte ich dann Alpha-Beta-Prunning einfügen, um durch die gesteigerte Effizienz in vertretbarer Zeit noch ein, zwei Züge tiefer rechnen zu können.
Letzten endes könnte ich die von dir erwähnte Quiescencse Search oder den "Trick" aus dem Forum anwenden.
 
Zuletzt bearbeitet:
Der "Trick" den du nennst, scheint ein simpler Fall für die Zugbewertung zu sein. Hat mit Quiescence Search erstmal nicht so ganz viel zu tun. Aber das nur als Nebensache.

Fühl dich von meinem Post oben nicht angegriffen, aber hier kommt jeden zweiten Tag jemand mit "Ich habe eine gute Idee für ein Einsteigerprojekt! Ich schreibe jetzt Call of Battlefield: Modern Minecraft (Candycrush Edition)" an. Deshalb fand ich es wichtig, dass du einen Überblick darüber hast, was auf dich zukommt. Das scheint ja weitgehend der Fall zu sein, deshalb: Pluspunkt von mir :3

Habe den Grund deiner Anfrage dann auch verstanden und weil ich kein C++ kann, muss ich hier die Antwort Leuten überlassen, die sich damit auskennen. Hin und wieder mal ein Bericht, wie es läuft, würde ich mir sogar glatt durchlesen :e
 
So ganz unrecht hast du mit deiner Befürchtung nicht, für meine letzten Projekte habe ich deutlich länger benötigt als erwartet, weshalb ich hier erstmal versuche alles von anfang an schön sauber zu Programmieren.
Laufen tut das Programm soweit auch, dass ich Partien mit verschiedenen Parametern starten kann, züge beliebig oft zurücknehmen kann und Siege sauber erkannt werden.
Nur formell dürfte da noch einiges dran auszusetzen sein.
Aber da können mir dann hoffentlich ein paar weitere Leser auf die Sprünge helfen.
Dir aber schonmal vielen Dank
 
ZuseZ3 schrieb:
So ganz unrecht hast du mit deiner Befürchtung nicht, für meine letzten Projekte habe ich deutlich länger benötigt als erwartet, weshalb ich hier erstmal versuche alles von anfang an schön sauber zu Programmieren.
Länger für ein Projekt zu benötigen, als man vorher denkt, ist nicht ungewöhnlich und hat nicht unbedingt damit zu tun, sich das Ziel zu hoch zu stecken.
Zum Beispiel: Du sagst, du hast die Methoden, die ich genannt habe, verstanden. Aber sie in Code zu gießen ist eine Sache für sich. Es kommen Probleme, an die man nicht gedacht hat und die einem zurückwerfen und das Projekt in die Länge ziehen. Aber weil man es verstanden hat, kommt doch man ans Ziel. Nur eben später. Das ist kein persönliches Versagen, sondern einfach nur die Natur der Sache.

Programmiersprachenunabhängig mein allgemeiner Rat zu deiner Frage:
Es von Anfang an schön machen zu wollen ist richtig, aber du solltest nicht vom Hölzchen aufs Stöckchen kommen, bevor du überhaupt eine Grundlage hast. Da besteht die Gefahr, sich selbst in zu enge Vorgaben zu zwängen, von denen man irgendwann dann doch abweichen muss und dann bricht plötzlich alles zusammen. Da hilft Erfahrung und die bekommt man nur durch Praxis. Deshalb mach dir einen Plan, der flexibel ist und den du im Rahmen deiner Fähigkeiten einhalten kannst. Für das nächste Projekt hast du dann auf jeden Fall etwas gelernt und kannst dann besser einschätzen, wie du die Sache angehst.
 
Hallo,

nur erstmal ein paar Anmerkungen:

1) Hast du den Code in deinem Editor auch so eingerückt? So ist er nämlich ziemlich schwierig zu lesen. Falls du den Code noch nicht eingerückt hast, solltest du dich erst einmal damit beschäftigen und den Code hier richtig eingerückt posten. ;)

2) Die Trennung von Header- (.h) und Source-Dateien (.cpp) funktioniert zwar so, ist aber nicht im Sinne des Erfinders. In der Header-Datei nimmst du im Normalfall nur Forward-Declarations vor, d.h. du schreibst nur den Funktionskopf in den Header und die Funktionsdefinition in die Source-Datei. Dazu solltest du genügend Beispiele finden.

3) Meistens ist es so, dass man bei Klassenname (wie hier "Spielfeld") mit einem Großbuchstaben beginnt, um diese von Variablen und Attributen, die in der Regel mit einem kleinen Buchstaben beginnen, zu unterscheiden. Das solltest du dir von Anfang an angewöhnen.

Die drei Punkte würde ich zuerst einmal umsetzen und verinnerlichen, bevor du weiter machst.
 
Ich stimme Dekar zu. Es gab hier gerade erst vor kurzem einen Thread: Was in den Header, was nicht?

Beim Konstruktor machst du das schon, aber bei den Funktionen nicht. Bzgl des Konstruktors kannst du dir auch gleich noch Initialisierungslisten angucken.

ZuseZ3 schrieb:
Um die Vererbung/Objektorierntierung mal praktisch anzuwenden, dachte ich hier daran das Spielfeld und die KI als eigene Klassen zu erstellen (Wer hättes blos erwartet :freaky:).
Dadurch könnte ich das ganze dann auch bei anderen Spielen wie Schach (bzw. erstmal einfacheren Varianten davon) weiternutzen.

Wenn du hierüber schon nachdenkst, dann bedenke das die Klasse Spielfeld schon jetzt auf Vier Gewinnt ausgelegt ist (z.B. chainlength). Von der Theorie her würdest du eine Basisklasse machen die allgemeine Informationen über alle Spielbretter enthält, die du implementieren möchtest. Davon leitest du dann wieder ab und erstellst die nächste Klasse, die auch schon das Vier Gewinnt Spielbrett sein kann. Ob das nun bei deinem eher kleinen Projekt sinn macht ist jetzt eine ganz andere Frage. Nur der Sinn von weiterverwenden ist ja nicht den Code in eine neue Datei zu kopieren und dann anzupassen, sondern vorhandene Klassen als Basisklassen nutzen zu können und darauf aufzubauen.

Deine cpy Funktion funktioniert nicht weil, beim Aufruf der Funktion das Spielfeld kopiert wird. Alles was innerhalb dieser Funktion geändert wird ist lokal in der Funktion und nicht nach Außen sichtbar. Du könntest bei der Funktion call by reference verwenden. Such einfach mal danach, dass ist nicht schwer und dann hast du es auch verstanden. Der Resize des vectors in der cpy Funktion ist auch über, das macht die Zweisung schon. Um das zu verstehen würde ich das ruhig erst einmal testen, denn eigentlich kannst du dir die Funktion sparen ...

Code:
//statt dieser Zeile 
real.cpy(test);
//könntest du auch einfach folgendes schreiben
test = real;

Der Compiler generiert automatisch einen Zuweisungsoperator, wenn dieser nicht vorhanden ist. Dort weist er einfach jede Variable der anderen zu und genau das hast du in der cpy Funktion hingeschrieben. Je nachdem was für Member man hat, sollte man trotzdem aufpassen ob das Sinn macht, erlaubt ist usw.

Vielleicht noch was allgemeines, was Dekar auch schon angesprochen hat und die Arbeit auf lange Sicht gesehen erleichtert. Schau dir mal Namenskonventionen an. Hier gibt es viele und einige Teile hängen von der Sprache an sich ab, aber imho fördert es die Lesbarkeit und die Strukturierung. Eine Sache, die gerne Sprachunabhängig gemacht wird, ist z.B. Membervariablen einer Klasse immer mit einem "m" anfangen zu lassen. Das kann je nach Konvention dann ein einfaches "m" sein oder auch ein "m_" oder noch ganz anders. Der Vorteil hierbei ist, dass man in einer Memberfunktion direkt sieht, welche Variablen lokal in der Funktion deklariert wurden und welche Variablen Membervariablen sind. Bei so kleinen Projekten ist das nicht so wichtig und du erinnerst dich vermutlich immer noch an den Code, aber wenn du nach einer gewissen Zeit mal wieder drauf guckst oder gar Code von anderen irgendwann mal warten musst, dann erleichtern solche Dinge das Lesen. Wichtig ist eigentlich nur, gerade in größeren Projekten, dass man sich an die Vorhandenen Richtlinien hält.
 
So, jetzt mal der Reihe nach.
Dekar->1) So sehen eigentlich meine gesamten Programme aus, das einzige was mir bisher bei den Programmen von anderen Leuten aufgefallen ist, ist die { bei der Methodendeklaration in die nächste Zeile zu schreiben. Ansonsten hab ich mir halt bei manchen if abfragen die geschweifte Klammer gespart und das return statement direkt dahinter geschrieben, was ich jetzt mal geändert habe.
Sind sonst noch irgendwelche besonderheiten drin, die ich übersehen habe?

Dekar->2) Danke, ich schätz mal, dass hätte ich auch selbst im Internet finden können. :( Naja, ich habs auf jeden fall entsprechend geändert.

Dekar->3)Im Programm entsprechend geändert, lediglich den Dateinamen kann ich nicht mehr ändern, weil die Datei (großgeschrieben) angeblich bereits im selben Verzeichnis sei.
Anscheinend muss der Dateiname hier nicht wie bei java dem Klassennamen entsprechen? Würde ja außerdem bedeuten, dass bei c(++) nicht die groß und kleinschreibung von dateinamen beachtet wird, was ich anders in Errinerung hatte?-->Anscheinend ist c tatsächlich case-sensitiv, allerdings wird das ganze von den Dateisystemen Fat sowie hier NTFS ausgehebelt, welche die unterschiedliche groß/kleinschreibung von Dateien ignorieren? Stimmt das so weit?

So, was die Kopierfunktion angeht war ich mir nicht sicher ob das ganze bei c++ auch standardmäßig möglich ist. Wie du bereits angemerkt hast scheint dies aber allgemein nicht sonderlich vertrauenswürdig sein, weshalb ich es, auch zu übungszwecken, lieber selbst machen würde. Die ganze Call bye Value/Ref geschichte müsste ich mir dann nochmal angucken.
@Miuwa, naja, ich schätze einfach das gesamte Feld statt der einzelnen Züge übergeben bzw. vor allem zwischenspeichern zu können, dürfte mir später im Zusammenhang mit der KI etwas arbeit ersparen.
Außerdem scheint es sich ja bewährt zu haben--> http://de.wikipedia.org/wiki/Dreierregel

@antred: Hmm. naja, ich mag den Fragezeichenoperator, hällt meine Programme sonst immer so schön kurz. :D Ich habs mal geändert.
 
Zuletzt bearbeitet:
Was soll den das?

Code:
bool Spielfeld::getactive()
{
	return active ? true : false;
}

Könnte man doch auch als

Code:
bool Spielfeld::getactive()
{
	return active;
}

schreiben, oder? :p
 
Was genau denkst du eigentlich was cpy macht bzw. machen soll?
Ergänzung ()

Next player kannst du übrigens als

Code:
void Spielfeld::nextplayer()
{
	player = !player;
}
schreiben

Code:
if (ausgabe == true)
lässt sich durch
HTML:
if (ausgabe )
ersetzten.

Und den Konstruktor solltest du lieber so schreiben:
Code:
Spielfeld::Spielfeld(int b, int h, short c, bool a, bool s):
	move(0),
	movesx(b * h),
	movesy(b * h),
	feld(breite, vector<short>(h)),
	breite(b),
	hoehe(h),
	player(0),
	chainlength(c),
	active(true),
	ausgabe(a),
	stoppen(s)
{}
Das nennt sich initializer list und ist effizienter als alle werte im constructor body zu setzen.

Als abschließenden Hinweis würde ich noch vorschlagen, alle shorts durch ints zu ersetzten (weniger compiler Warnungen) und setter und getter etwas weniger exzessiv einzusetzen.
 
Danke, die kleinen Hinweise hab ich umgesetzt, die initializer list würde ich mir morgen genauer ansehen.
Wie sieht es denn mit den shorts genauer aus, warum könnte ich da überhaupt warnungen bekommen können? Short ist doch unabhängig von der konkreten Länge (bei c++ ja abhängig vom compiler, anders als bei java wo es festgelegt ist?!) sogar vollständig in int darstellbar, sodass es auch bei berechnungen mit Intwerten, nicht zu fehler kommen kann. (Anders als z.B. bei float-int umwandlungen?!)
Naja, ich leg mich erstmal hin :n8:
 
ZuseZ3 schrieb:
Danke, die kleinen Hinweise hab ich umgesetzt, die initializer list würde ich mir morgen genauer ansehen.
Wie sieht es denn mit den shorts genauer aus, warum könnte ich da überhaupt warnungen bekommen können? Short ist doch unabhängig von der konkreten Länge (bei c++ ja abhängig vom compiler, anders als bei java wo es festgelegt ist?!) sogar vollständig in int darstellbar, sodass es auch bei berechnungen mit Intwerten, nicht zu fehler kommen kann. (Anders als z.B. bei float-int umwandlungen?!)
Naja, ich leg mich erstmal hin :n8:

Der Standard verlangt (so weit ich mich erinnern kann) lediglich, daß ein short mindestens 2 Byte sind, und das ein int mindestens so lang wie ein short sein muß. In der Regel ist ein int natürlich breiter als ein short. Das heißt, nicht alle int-Werte sind auf ein short abbildbar. Aber dieses ganze Rumgeeiere mit shorts macht in C++ sowieso selten Sinn. Die Platzersparnis ist minimal, gemessen an dem, was dein Programm sonst so alles belegt. Einfach vergessen, daß es short und long überhaupt gibt und einfach mit int (bzw. unsigned int) glücklich werden.
Wenn du mal wirklich Typen mit einer garantierten Länge brauchst, dann greif auf die in <cstdint> definierten Typen zurück ( http://en.cppreference.com/w/cpp/header/cstdint ).
 
Das scheint ja offensichtlich vom Compiler abzuhängen und meiner gibt in diesem Fall keine Warnung aus, weswegen ich das erstmal so lassen würde.

Aktuell sieht es so aus, dass ich gegen eine Random ziehende KI spielen kann.
Zusätzlich arbeite ich an einer KI, welche das ganze brute force angehen soll und auf die random ziehende KI zurückgreift, sollte sie keinen Gewinnweg sehen.
Dafür habe ich die Klassen point (x und y koordinate) und row (startpunkt, endpunkt, länge) hinzugefügt, um das ganze zu vereinfachen.

Zusätzlich möchte ich alle Reihen von aneinanderhängenden steinen eines Spielers, welche in die selbe Richtung zeigen in einen Vector packen.
Dh. alle wagerechten, senkrechten sowie die beiden diagonalen reihen sollen in jeweils einem vector gespeichert werden.

Das ganze sieht dann so aus.
Code:
vector<line> Spielfeld::rowa()
{
	line nline;
	point a, b;
	vector<line> vec;
	int vecnum = 0;
	for (int y = 0; y < hoehe; y++)
	{
		for (int x = 0; x < breite; x++)
		{
			if (x < breite - chainlength && feld[x][y] != 0 && feld[x][y] == feld[x + 1][y])
			{
				if (nline.end().x() == x && nline.end().y() == y) // geht die reihe weiter?
				{
					b.set(x + 1, y);
					nline.set(a, b); // reihe verlängern
				}
				else
				{
					vec.push_back(nline); //alte reihe abspeichern
					vecnum++;
					a.set(x, y);
					b.set(x + 1, y);
					nline.set(a, b); // neue reihe erstellen
				}
			}
		}
	}
}

Aktuell hänge ich noch an folgendem Bug:
http://stackoverflow.com/questions/23281405/how-to-use-a-stdvectorunique-ptrt-as-default-parameter

Wie umgehe ich das am geschicktesten?
Hier ist die genaue Fehlermeldung:
1>------ Build started: Project: viergewinnt, Configuration: Debug Win32 ------
1> spielfeld.cpp
1>C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(593): error C2558: class 'line' : no copy constructor available or copy constructor is declared 'explicit'
1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(592) : while compiling class template member function 'void std::allocator<_Ty>::construct(_Ty *,const _Ty &)'
1> with
1> [
1> _Ty=line
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\xmemory0(723) : see reference to function template instantiation 'void std::allocator<_Ty>::construct(_Ty *,const _Ty &)' being compiled
1> with
1> [
1> _Ty=line
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\type_traits(572) : see reference to class template instantiation 'std::allocator<_Ty>' being compiled
1> with
1> [
1> _Ty=line
1> ]
1> C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include\vector(650) : see reference to class template instantiation 'std::is_empty<_Alloc>' being compiled
1> with
1> [
1> _Alloc=std::allocator<line>
1> ]
1> spielfeld.cpp(112) : see reference to class template instantiation 'std::vector<line,std::allocator<_Ty>>' being compiled
1> with
1> [
1> _Ty=line
1> ]
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Dies hier in meiner Spielfeld.h Datei einzufügen hat nur einen Fehler verursacht (illegal member initialization: 'line' is not a base or member):
Spielfeld() : line{}
{}
 
Zuletzt bearbeitet:
Sry, dass ich mich nochmal einmische. Aber wie klingt der Vorschlag, mal Code von Anderen zu lesen? Ich mein damit nicht Abkupferei, sondern einfach, um einen Eindruck zu bekommen, wie C++ im wahren Leben aussieht? Ich fand das immer sehr erkenntnisgewinnbringend.

Ich komme ja nicht aus der Richtung, aber vielleicht kann ja hier jemand ein vorbildliches C++-Projekt nennen, das ja nicht mal konkret mit KI zu tun haben muss. So lange niemand darauf besteht, dass Software genau so entwickelt werden muss, sondern es einfach als Beispiel zur weiterbildung vorbringt, sollte sich da auch ein Religionskrieg vermeiden lassen und OP hätte vielleicht wirklichen persönlichen Nutzen.
 
Zuletzt bearbeitet:
antred schrieb:
Der Standard verlangt (so weit ich mich erinnern kann) lediglich, daß ein short mindestens 2 Byte sind, und das ein int mindestens so lang wie ein short sein muß. In der Regel ist ein int natürlich breiter als ein short.http://en.cppreference.com/w/cpp/header/cstdint ).

Im C und C++ gilt laut Standard char <= short <= int/Long <= Long Long. Und weiterhin gilt char >= 1byte, short >= 2b, int/Long >= 4 Byte. Bei long Long bin ich mir nicht mehr ganz sicher.
Somit passt auch nach dem Standard ein short immer in ein int. Dennoch sollte man sowas nie einfach machen. Eigentlich castet man in c++ immer mit static_cast<Typ>(Variabel). Noch besser ist wenn dazu auch ein Kommentar vorhanden ist, warum der Cast vorgenommen werden kann.

ZuseZ3 schrieb:
Würde ja außerdem bedeuten, dass bei c(++) nicht die groß und kleinschreibung von dateinamen beachtet wird, was ich anders in Errinerung hatte?
Das ist wieder Abhängig vom verwendeten Compiler bzw. auch die IDE. Visual Studio ist da z.B. immer sehr großzügig und ignoriert das bzw. fixt das automatisch. Bei gcc/g++ wird aber die Groß- und Kleinschreibung beachtet.
 
Zuletzt bearbeitet:
TrebuTurbo schrieb:
Somit passt auch nach dem Standard ein short immer in ein int.

Hatte ich auch nicht anders behauptet. Ich hatte gesagt, nicht jeder int-Wert sei auch auf ein short abbildbar.
 
Könnte mir jemand, nachdem dass geklärt währe ( ;) ) noch einen Hinweis bezüglich des compiler bugs geben?
 
Poste doch mal die gesamte Datei, in der der Fehler auftritt, vielleicht kann man dann was sehen.

Was die Sache mit dem Kopieren angeht:
Wie du bereits angemerkt hast scheint dies aber allgemein nicht sonderlich vertrauenswürdig sein, weshalb ich es, auch zu übungszwecken, lieber selbst machen würde.
Das Gegenteil ist der Fall, der =-Operator ist der einzige vertrauenswürdige Weg, etwas zu kopieren, weil er eben auch implizit benutzt wird. Beispiel:

Code:
class Bla { ... };

class Blubb {
public:
  Blubb(const Bla& bla)
  : _bla(bla) { }
private:
  Bla _bla;
};

...

Blubb machWas() {
  Bla einBla = ... ;
  ...
  return Blubb(einBla); // Kopie von einBla im Konstruktor von Blubb
}

Eigentlich hat man ständig irgendwelche implizite Kopien oder Moves im Code, von denen man erstmal nicht viel sieht. Daher immer, wenn mehr passiert als einfach das Kopieren von Member-Variablen, den Copy-Assignment- und am besten auch den Move-Assignment-Operator überladen, sowie die entsprechenden Konstruktoren. Siehe auch Rule of Three.

Code:
class Bla {
public:
  Bla(const Bla& other); // Copy-Constructor
  Bla(Bla&& other);      // Move-Constructor
  Bla& operator = (const Bla& other); // Copy-Assignment
  Bla& operator = (Bla&& other);      // Move-Assignment
  ~Bla(); // ggf. Destruktor
};

Aber nicht mit irgendwelchen Funktionen wie "cpy(...)" herumhantieren. Sowas mag in Java funktionieren und nötig sein, in C++ fliegt einem sowas schnell um die Ohren.

Move-Semantik ist auch etwas, was ich noch in keiner anderen Sprache gesehen habe, und zugegebenerweise auch nicht besonders intuitiv zu verstehen - wenn man nicht weiß, was da passiert, lieber erstmal weglassen. Falls man keine Move-Konstruktoren und -Assignment-Operatoren implementiert, werden stattdessen Kopien genutzt, da kann also nicht viel schief gehen.
 
Zuletzt bearbeitet:
Puh, fang doch erst mal an lesbaren Code zu schreiben und werfe das Deutsche raus :)

Du musst auch nicht komplett alles scannen, nur an der Positon wo eingeworfen wurde 3 links, 3 rechts, 3 unten sowie 3 schräg. Oben drüber brauchst nicht testen, da es eh frei ist. Ich denke an eine Matrix mit Zahlen.

Die Tests solltest auch in Methoden aufdröseln statt alles in for und if zu verschachteln.

HorizontalRight(x,y,z)
for...i<=3
[x+i][y] == z

Spieler 1 hat in der Matrix dann die 1, Spieler 2 die 2 etc.
z = Letzter Spieler der eingeworfen hat
x/y = Einwurfposi

Damit kannst dann auch game modi machen, z.B. 5 gewinnt.
 
Zuletzt bearbeitet:
Zurück
Oben