• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

Go-Statistik Programm

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
@theSplit
Ich sehe auf GitHub kein 1-dim. Array, sondern min/max in 2-dim (x und y). Das passt problemlos mit Coordinate zusammen.


Die Nachbar-Funktionalität ist aus meiner Sicht beim Board besser aufgehoben, weil nur gültige – im Rahmen des Spielbrett liegende – Nachbarn interessant sind. Aber von den Spielbrettdimensionen weiß Coordinate nichts. Wenn ihr’s so macht, wird Coordinate dann auch zu einem richtig hübschen, kleinen, schlanken, dummen, geradezu trivialen Datencontainer. :)

… Hm, eigentlich wollte ich auf GitHub nur mal kurz reinschauen, und dann stand plötzlich die dumme Variante von Coordinate im Texteditor, so wie ich sie bauen würde. Dann kann ich jetzt auch ein paar Worte dazu schreiben. :)

Der Übersicht halber alles direkt inline implementiert. Würde ich bei so einer Miniklasse vermutlich im Produktivcode genauso machen.

[src=cpp]
// file: Coordinate.hpp

class Coordinate {
public:
Coordinate() = default;
Coordinate(int x, int y): m_x(x), m_y(y) {}

int x() const { return m_x; }
int y() const { return m_y; }

private:
int m_x = 0;
int m_y = 0;
}

inline bool operator==(const Coordinate& lhs, const Coordinate& rhs)
{
return lhs.x() == rhs.x() && lhs.y() == rhs.y();
}

inline bool operator!=(const Coordinate& lhs, const Coordinate& rhs)
{
return not (lhs == rhs);
}
[/src]


  • Ich fand’s in der GitHub-Variante einen sehr feinen Ansatz, Coordinate als value type, also immutable, anzulegen. Eine Koordinate ist ja nicht besonders viel mehr als ein Integer. Dass sie sich verhält wie ein eigebauter Fundamentaltyp, passt perfekt ins Bild. Unschönheit auf GitHub in der Hinsicht: die Getter und Neighbour-Funktionen sind nicht const, was sie aber sowieso und beim value type erst recht sein könnten und sollten. Non-const-Methoden machen für einen immutable Typen keinen Sinn.
  • Warum stehen da oben genau diese Konstruktoren? Der Custom-Ctor mit den beiden ints verhindert, dass der Compiler automatisch den parameterlosen Default-Ctor generiert. Das =default sagt dem Compiler ausdrücklich, dass er den doch generieren soll. Copy-/Move-Ctors und Copy-/Move-Assignment sind compilergeneriert. D.h. den manuellen Copy-Ctor wie auf GitHub brauchts nicht. Schaut mal weiter runter für die kompletten Regeln, wann welche Ctors/Zuweisungsoperatoren automatisch erzeugt werden.
  • Standardoperatoren kommen selten allein. Es bietet sich immer an, alle einer Gruppe zu implementieren, also z.B. operator==() und operator!=(). Sonst kommt man in so seltsame Situationen, dass c1 == c2 compiliert, c1 != c2 aber nicht.
  • Die meisten Operatoren implementiert man besser als freie Funktionen. Als Member wie auf GitHub ist jetzt definitiv, dass der erste Parameter eines Vergleichs ein Coordinate, und *nur* ein Coordinate, sein muss. Die freie Variante wie oben erlaubt für beide Parameter auch andere Typen, die implizit zu einem Coordinate konvertierbar sind.

Code:
horizontal: if you write ...
vertical: the compiler generates ...

           none  dtor  copy ctor  copy op=  move ctor  move op=
dtor       yes         yes        yes       yes        yes
copy ctor  yes   yes              yes       no         no
copy op=   yes   yes   yes                  no         no
move ctor  yes   no    no         no                   no
move op=   yes   no    no         no        no

Was mir beim Drüberlesen auf GitHub sonst noch aufgefallen ist:

  • Da wird stdio.h includiert, obwohl weit und breit kein I/O zu sehen ist. Davon abgesehen ist’s ohne Not ein C-Header (in C++ heißt das Ding cstdio). Außerdem würde ich in einem C++-Programm tendenziell auch C++-I/O erwarten, also iostream.
  • Member mit __ anfangen zu lassen, ist potenziell gefährlich, weil solche Namen für die Implementierung (von Compiler und Standardlib) reserviert sind. Kann also, wenns blöd läuft, zu Namensclashes führen. Daumenregel: in Namen keine führenden Unterstriche und keine Mehrfachunterstriche.
  • Das *using std::vector;* im Header hat virale Wirkung auf alle Translation Units, die diesen Header einbinden. Sollte man vermeiden. Früher oder später clasht sowas mal böse.

So. Puh. Lang geworden. Sorry, ist halt C++. Wenn ich da einmal in Fahrt bin … Wenn das zu viel Review vom Außenseiter war, dann bremst mich. Oder wenn ihr mehr davon wollt, sagts.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
Ich will mehr, aber ich verstehe nur die Hälfte, ich warte auf die Fragen von "fachlichen" Kollegen :D
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #63
@Brother John:
Man das war mal Feedback :D

Zu dem ganzen "Wieso steht hier Construktor und wieso da nicht const" usw. Ganz einfach: Ich wusste es nicht besser :D Ich habe das nie professionell gelernt und mein Wissen mehr durch ausprobieren und googlen erweitert ;)

Mir gefällt das mit der kleinen Coordinate-Klasse recht gut. Probleme habe ich aber damit, dass beispielsweise eine Gruppe nicht ihre Nachbarn selbst ermitteln kann, da sie die Grenzen des Spielfeldes braucht. Daher dachte ich ja daran, dass die Coordinate die Grenzen lernt...
Dass das Spielfeld Nachbarn einer Gruppe errechnen kann ist zwar machbar, aber irgendwo auch wieder unschön. Ich finde da sind die Grenzen, welche Aufgabe in welche Klasse fällt etwas schwammig.
Ich implementiere aber deinen Vorschlag mal, da das alles gar nicht so schlecht klingt. Darfst da natürlich auch gerne selber was forken und commiten usw. Das muss ich nicht zwingend absegnen - also schon, aber erst wenn ich merge :D

Deine Übersicht, was der Compiler wann erzeugt ist für mich noch ein wenig verwirrend aber vermutlich auch, weil ich mich damit nie wirklich beschäftigt habe.
[src=cpp]Coordinate(int x, int y): m_x(x), m_y(y) {}[/src]
Diese Zeile finde ich übrigens ungewohnt. Ich wusste nicht, dass man Variablen auch so setzen kann...

Das mit den Unterstrichen passe ich dann auch direkt mal an...

Gerne mehr solche Reviews. Und auch gerne Commits :D

--- [2017-08-17 23:32 CEST] Automatisch zusammengeführter Beitrag ---

Kannst du vielleicht noch ein wenig kommentieren, wo ich genau überall Referenzen nutzen sollte und wo nicht?

--- [2017-08-18 00:15 CEST] Automatisch zusammengeführter Beitrag ---

Ich habe mal die meisten Änderungen eingepflegt, denke ich.
Wie gesagt gerne mehr davon oder auch einfach was ändern und Pull-Requests senden...

Wie vielleicht auffällt, habe ich für Board zum Beispiel auch die Construktoren angepasst, da dies ja deutlich kürzer ist und auch effizienter, denke ich.

Noch eine allgemeine Frage: Was macht es, wenn ich einen Construktor oder so schreibe, der dem entspricht, den der Compiler basteln würde? Mache ich damit irgendwas kaputt? Ist das bad practise? Oder ist das einfach persönliche Präferenz?
 

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Ich habe das nie professionell gelernt und mein Wissen mehr durch ausprobieren und googlen erweitert
Das ist das Gute an der IT: Man muss es nicht offiziell gelernt haben, um damit seine Brötchen zu verdienen. Ich mach C++ jetzt seit 7 Jahren. Irgendwas muss wohl hängengeblieben sein. :)

Mir gefällt das mit der kleinen Coordinate-Klasse recht gut. Probleme habe ich aber damit, dass beispielsweise eine Gruppe nicht ihre Nachbarn selbst ermitteln kann, da sie die Grenzen des Spielfeldes braucht. Daher dachte ich ja daran, dass die Coordinate die Grenzen lernt...
Finde ich gar nicht wild, weil sowieso das Board die Liste der Gruppen verwaltet. Alle Nachbarfunktionen würde ich intuitiv zuerst dort suchen, weil dort alle nötigen Infos dafür gehalten werden.

[src=cpp]Coordinate(int x, int y): m_x(x), m_y(y) {}[/src]
Diese Zeile finde ich übrigens ungewohnt. Ich wusste nicht, dass man Variablen auch so setzen kann...
Ging mir am Anfang auch so. Sieht so anders aus als das, was man von C++-Initialisierungssyntax gewohnt ist. Das ist die Initialisierungsliste: der normale, empfehlenswerte Weg, im Ctor Member zu initialisieren. Ansonsten macht das der Compiler implizit. Wenn der Body des Ctors anfängt, hast du die Garantie, dass sämtliche Member default-initialisiert[1] sind. D.h. wenn du sie nicht nutzt, rennst du schnell in Doppel-Initialisierungen. Z.B.:

[1] Vorsicht bei Fundamentaltypen (Integer & Co.) und einfachen, C-artigen Datenstructs: default-initialisiert heißt da: nicht initialisiert. Im Zweifel lieber die Initialisierung hinschreiben.

[src=cpp]class Bar { ... };

class Foo {
public:
Foo();
private:
Bar b;
};

Foo::Foo()
{
b = Bar();
}[/src]

Wenn du ein Foo instantiierst, wird ein Bar erzeugt, sofort wieder zerstört und nochmal erzeugt. Denn implizit steht da folgender Ctor:

[src=cpp]Foo::Foo() : b(Bar())
{
b = Bar();
}[/src]

Noch eine Konsequenz: Wenn du einen Typen als Member hast, der keinen parameterlosen Default-Ctor hat, dann *musst* du den Member in der Initliste abhandeln. Ansonsten versucht der Compiler implizit den Default-Ctor zu verwenden und fällt dabei natürlich böse auf die Nase.

Kannst du vielleicht noch ein wenig kommentieren, wo ich genau überall Referenzen nutzen sollte und wo nicht?
Das sieht im Moment ziemlich gut aus. Grundsätzlich: Erstmal immer const&, ausgenommen Fundamentaltypen und ähnlich kleine Objekte. Deswegen finde ich’s vollkommen ok, Coordinate zu kopieren.

Was macht es, wenn ich einen Construktor oder so schreibe, der dem entspricht, den der Compiler basteln würde? Mache ich damit irgendwas kaputt? Ist das bad practise? Oder ist das einfach persönliche Präferenz?
Für *den* einen Ctor/Dtor/op= ist es erstmal egal. Ob der entspr. Binärcode entsteht, weil ihn der Compiler implizit erzeugt oder weil du das entspr. C++ ausdrücklich hingeschrieben hast, macht keinen Unterschied. Es kann aber passieren – siehe Tabelle – dass dir damit andere Ctor/op= nicht mehr implizit erzeugt werden. Und das kann evtl. Performance kosten oder im blödesten Fall sogar nicht mehr compilieren. Schau dir zu dem ganzen Thema mal The rule of three/five/zero an.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #65
@Brother John:
Ich habe da geade noch zwei Fragen.
Zum Einen: In der Tabelle sind alle möglichen Zusatz-Construkte erwähnt. Aber was ist denn der normale Costum Constructor in dem Fall? Gehört der in eine der genannten Kategorien oder ist das ein normaler Konstruktor, der von den Compiler-Regeln, die du erwähnt hast ausgeschlossen ist und da "nichts" mit zu tun hat?
Und zum Anderen habe ich das gerade nicht mit der Doppelinitialisierung verstanden.

Wenn der Body des Ctors anfängt, hast du die Garantie, dass sämtliche Member default-initialisiert[1] sind. D.h. wenn du sie nicht nutzt, rennst du schnell in Doppel-Initialisierungen.

Also soll ich immer alles mögliche in dieser Initializer-Liste hinschreiben oder lieber im eigentlichen Body meines Construktors oder wie?

--- [2017-08-20 20:59 CEST] Automatisch zusammengeführter Beitrag ---

Alle Nachbarfunktionen würde ich intuitiv zuerst dort [im Board] suchen, weil dort alle nötigen Infos dafür gehalten werden.
Das habe ich ja jetzt auch erstmal so gemacht.
Findest du im aktuellen Repo denn noch mehr, was du als verbesserungswürdig erachtest?

Noch eine Konsequenz: Wenn du einen Typen als Member hast, der keinen parameterlosen Default-Ctor hat, dann *musst* du den Member in der Initliste abhandeln. Ansonsten versucht der Compiler implizit den Default-Ctor zu verwenden und fällt dabei natürlich böse auf die Nase.
Guter Tipp. Das sollte ich vielleicht in dem einen oder anderen Projekt noch anpassen. Da ist es zwar durchkompiliert, aber vermutlich nur, weil der Compiler da etwas mitgedacht hat und überall alles mögliche zurechtgeschustert hat :D

--- [2017-08-20 21:46 CEST] Automatisch zusammengeführter Beitrag ---

Grundsätzlich: Erstmal immer const&, ausgenommen Fundamentaltypen und ähnlich kleine Objekte.
Dazu gerade noch ein hilfreicher Link, der auch noch zu zwei weiteren Erklärungen führt: https://stackoverflow.com/a/3141107/6486348
Der hat mein Verständnis von den ganzen const-Konstellationen zumindest verbessert.

--- [2017-08-21 00:48 CEST] Automatisch zusammengeführter Beitrag ---

Leute ich habe ein Problem :D

Ich wollte von meinen Commits die Emailadresse ändern. Da stand jetzt immer drin, wie mein laptop hieß und sowas. Irgendwas, was Netbeans aus meinem Netzwerknamen generiert hat.
Also habe ich mit git rebase -i alle commits markiert, die ich editieren wollte und habe die dann bearbeitet und gepushed.

Jetzt würde ich aber gerne alle vorherigen dadurch ersetzen. Jetzt habe ich nämlich statt ~ 35 Commits etwa 71 da drinstehen und die sind ja jetzt etwas "Mist"...
Kann mir da jemand sagen, wie ich das sauber bearbeitet kriege? Ich steige durch git wohl nicht ausreichend durch :(
 

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
In der Tabelle sind alle möglichen Zusatz-Construkte erwähnt. Aber was ist denn der normale Costum Constructor in dem Fall? Gehört der in eine der genannten Kategorien oder ist das ein normaler Konstruktor, der von den Compiler-Regeln, die du erwähnt hast ausgeschlossen ist und da "nichts" mit zu tun hat?
Genau so ist es. Das kam da oben wirklich nicht richtig rüber. Du kannst beliebig viele Ctors haben. Die Regeln aus der Tabelle beeinflusst das überhaupt nicht. Es kommt nur eine Regel dazu: Sobald du einen Custom-Ctor anlegst, wird der Default-Ctor nicht mehr implizit erzeugt. … Oh, und mit »Default-Ctor« ist immer der ohne Parameter gemeint: egal ob handgeschrieben oder vom Compiler erzeugt.

Roin schrieb:
Und zum Anderen habe ich das gerade nicht mit der Doppelinitialisierung verstanden.
Bleiben wir bei dem Ctor von oben:
[src=cpp]
// Initliste könnte auch fehlen und wäre dann implizit.
// Macht keinen Unterschied
Foo::Foo() : b(Bar())
{
b = Bar();
}[/src]
Wenn du ein Objekt instantiierst, passiert der Reihe nach folgendes:
  1. Speicher für das Objekt wird zugewiesen, entweder vom Stack oder – wenn mit new erzeugt – vom Heap.
  2. Die Initliste des Ctors wird abgearbeitet.
  3. Der Body des Ctors wird abgearbeitet.
Bei Foo heißt das konkret:
  1. Los geht’s mit einem uninitialisierten Speicherblock, der für ein Foo groß genug ist.
  2. Im Rahmen der Initliste wird ein Bar-Objekt für den Member b erzeugt.
  3. Direkt anschließend kommt die Dopplung: Denn die Zeile im Body schmeißt das gerade erzeugte Bar-Objekt wieder weg, erzeugt ein neues und weist das dem Member b zu.
Deswegen sollte, soweit das irgendwie geht, sämtliche Instantiierung in der Initliste passieren. Wenn du den Ctor-Body brauchst, um anschließend die Member noch weiter zu konfigurieren, das ist fein. Beispiel mit einem halbwegs realistischen Qt-Miniwidget, das ein Label und einen Button enthält. In der Initliste werden das Label- und Button-Objekt instantiiert und dann im Body verwendet; genau so wie du es in jeder anderen Funktion der Klasse auch tun würdest.
[src=cpp]MyWidget::MyWidget(QWidget* parent)
: QWidget(parent)
, m_label(new QLabel(this))
, m_okButton(new QPushButton(this))
{
m_label->setAlignment(Qt::AlignRight);
m_okButton->setText("OK");
// ...
}[/src]

Roin schrieb:
Dazu gerade noch ein hilfreicher Link, der auch noch zu zwei weiteren Erklärungen führt
Feiner Link. Der bringt das echt gut auf den Punkt.


Roin schrieb:
Leute ich habe ein Problem :D
[...]
Kann mir da jemand sagen, wie ich das sauber bearbeitet kriege?
»Professionelle Anarchie mit Roin. Heute: Wir zerstören ein Git-Repo.« :D

Sobald Commits gepusht sind, ist normalerweise Ende mit editieren. Die sind jetzt in die Welt verteilt. Auch, wenn du sie bei dir gelöscht kriegst und pusht, können sie durch jemand anders wieder auftauchen, der sie gepullt hatte, von der Editieraktion nichts mitgekriegt hat und dann seinen Stand inkl. der ungewollten Commits wieder pusht.

Dass *das* nicht passiert, würden wir bei den paar Leuten hier ja sicher noch hinkriegen. Aber wie du das Repo wieder sauber und die ungewollten Commits von Github wegkriegst, da bin ich auch nicht Git-Experte genug. Die Frage hier sieht ganz brauchbar aus. Oder, wenn es nichts macht, die bisherige Historie zu verlieren, die »Second Method« hier. Dann muss nur noch jeder seine Forks killen und neu klonen. … Könnte klappen.
 
Zuletzt bearbeitet:

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #67
»Professionelle Anarchie mit Roin. Heute: Wir zerstören ein Git-Repo.« :D
Ich wollte halt eigentlich einfach nur meine Mailadresse ändern, damit da nicht so ein Schwachsinn drinsteht :D
Ich glaube das wieder zu fixen ist jetzt zu aufwändig. Ich lasse das einfach so stehen. Nach dem Hirarchie-Baum war der rebase auch eher ein Branch und dann ein merging... Seltsam, aber was soll man machen :D

---

Ich habe gerade wieder eine Menge programmiert.
Da darf gerne wer rübergucken. Ich mache sicherlich einige ...Missgeschicke (Ich mache ja keine Fehler. Bin ja König, wie oben erwähnt...). Vielleicht kann da ja jemand was zu sagen, was ich da so alles fabriziert habe :D
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
Ich frage mich gerade, wo der Talk hier hingeht, Code Qualität ist mit Sicherheit wichtig, aber mich als "Anfänger" schreckt es ein wenig ab, ich würde vielleicht gerne irgendwie behilflich sein, aber das kann ich mit Sicherheit nicht auf dem Level, das hier gerade besprochen wird.

Mit kleineren Sachen kann ich mit Sicherheit behilflich sein. Und ich bringe mich auch gern hier ein, wenn ich denn kann.

Auch wenn es ein wenig so aussieht, als wir @Roin das hier alles allein schreiben, bissle Futter für den Kopf geht bestimmt. Und wenn es nur Grundansätze sind wie man etwas umsetzen könnte. ;)

Ich werd mir Mühe geben!
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #69
@theSplit:
Ich will das keineswegs allein schreiben. Doch ich kam auf die Idee und bin aktuell sehr daran interessiert und stürze mich auf die Probleme, solange ich Zeit dafür habe. Leider hat sich bisher ja noch kein anderer gefunden, der Code erzeugen möchte (oder glaubt es zu können).
Ich wiederhole mich gerne: Ich bin auch für "schlechten" Code dankbar! Alles was das Projekt irgendwie voran bringt.
Die Qualität des Codes aufbessern kann man hinterher immernoch oder das wird on-the-fly gemacht ;-)

Gerade schreibe ich eine rudimentäre Ausgabe auf der Console des Spielfeldes.
Da meckert mich der Compiler noch etwas an - aber dann sieht man wenigstens ein bisschen was der Code bisher kann und dann hat man zumindest die meisten Grundlagen durch, wobei noch ein zwei Funktionen nicht implementiert sind und derzeit nichts tun (vgl Board::isValid(Turn)).

Ich habe nur die Vorschläge/Hinweise von Brother John gerne genutzt, da ich das bisher nicht konnte und sicherlich in näherer Zukunft noch nutzen kann.

Nochmal: Mein Code ist nicht perfekt und das erwarte ich auch von keinem anderen. Ich habe bestimmt einige Sachen umständlich gelöst und häufig auch nicht so auf die Performance geachtet. Das darf gerne jemand tun, der das alles besser kann als ich. Aber vielleicht fällt ja auch jemand anderem noch etwas auf wie "Da ist aber irgendwas nicht korrekt mit dem Atari" oder "Die Darstellung von xyz könnte man bestimmt auch hinkriegen. Zum Beispiel {Pseudocode}."

Ich bin für jede Beteiligung dankbar :)

--- [2017-08-22 21:30 CEST] Automatisch zusammengeführter Beitrag ---

Auf Github ist nun die erste Version online, bei der man eine rudimentäre Eingabe und Ausgabe zur Verfügung hat.
Man kann noch fehlerhafte Züge eingeben, da die Funktion Board::isValid(Turn) noch nicht implementiert ist.

Allerdings kann man immerhin schon mal Züge in eingeben und das Spielfeld mit "show" darstellen lassen. Immerhin ein Anfang.

Vielleicht ist das ja der Anreiz, dass sich noch jemand damit auseinandersetzt und auch sieht, was er da tut (?).
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
@Roin: Okay, wenn ich dir helfen kann, tue ich das gern. Ich verspreche aber auch nichts.
Wenn ich mal Klugscheißern will, tue ich das :D

Und dann mußt du damit leben ;)

--- [2017-08-22 21:41 CEST] Automatisch zusammengeführter Beitrag ---

PS: Was hast du denn als nächtes vor? - Fangen wir mal so an?!
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #71
Was hast du denn als nächtes vor? - Fangen wir mal so an?!

Vermutlich ist der nächste Schritt wirklich Board::isValid(Turn) zu schreiben.
Dann ist damit nämlich erstmal der allgemeine Teil der Logik abgearbeitet.

Wenn das geschehen ist, müsste man sich so langsam an eine Grafische Oberfläche machen. Da habe ich mich noch nicht erkundigt, was da so praktisch wäre. Soll ja im Endeffekt (erstmal) das Go-Spielfeld darstellen können. Mit ein paar Strichen und den Steinchen.
Daran vermutlich noch eine Anzeige der Zug-Historie und so nen Kram. Kann man dann ja alles erweitern. Ich hatte ja gehofft, dass mir da jemand unter die Arme greift, da Frontend wirklich nicht zu meinen Stärken zählt :unknown: .

Nebenbei natürlich ständiges Improvement des Codes. Alles was so auffällt wird nebenbei berichtigt und sowas. (Wo bleiben die Pull-Requests?) :D
Wenn man sich dann mit dem Frontend auseinandersetzt, kann man sicherlich auch schon irgendwie die Daten für die Statistiken aufbereiten oder auch erstmal berechnen. Das mit dem "vermutlichen Gebietseinfluss" und sowas wird da ja doch etwas schwieriger, denke ich. Da könnte man allerdings wirklich mal solche Algorithmen wie Kriging drauf werfen und schauen, ob man da sinnvoll Dinge hinbekommt.
Da dafür aber eine grafische Anzeige des Ergebnisses gut wäre, müsste das Spielfeld erstmal visualisiert sein...

Frage beantwortet oder habe ich dran vorbei geschwafelt?

--- [2017-08-22 21:54 CEST] Automatisch zusammengeführter Beitrag ---

Übrigens: Spielsteine fangen geht mit der aktuellen Version bereits auch. Könnten allerdings noch Bugs drin sein - da war ich mir mit allem nicht so ganz sicher.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #72
gtkmm oder Qt?
Was ist besser geeignet? Gibt es bei dem einen Vorteile, die das andere nicht hat?

Hat jemand mit einem von beidem vielleicht schon Erfahrungen gesammelt?
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.505
My HUMBLE Opinion, ohne signifikante Erfahrung, nur bisschen Recherche:
Pro Qt:
* Sehr schöne d.h. intuitive API
* Sehr viel Dokumentation
* Sehr große Userbase = Viel Hilfe bei Problemen

Contra Qt:
* Kein reines C++ mehr, sondern muss vorkompiliert(?) werden.

Artikel zum Thema, pro-gtkmm-biased: https://people.gnome.org/~jjongsma/temp/qt-notes.html


Hab vor Jahrem im Studium bisschen rumgespielt mit Qt, fand es damals ganz cool. Aber das wars dann auch. Wer braucht schon GUIs ;D.
Ansonsten finde ich persönlich sehr entspannend, wenn Frameworks nah an bestehenden Technologien bleiben.
Dinge wie: Layouts mit XML beschreiben. Styles mit CSS umsetzen. Solche Dinge.
a) Ist der Einstieg deutlich vereinfacht
b) Ist später ein guter Transfer für andersartige Projekte gegeben.
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #74
Ansonsten finde ich persönlich sehr entspannend, wenn Frameworks nah an bestehenden Technologien bleiben.
Dinge wie: Layouts mit XML beschreiben. Styles mit CSS umsetzen. Solche Dinge.
Und was unterstützt das?
Hast du Lust da was dran zu schreiben?

BTW: Ich versuche mich gerade an einer Version für isValid(Turn).
Was Performance angeht, sind meine Sachen aber sicherlich noch recht ineffizient. Da wären an der ein oder anderen Stelle Pointer oder temporäre Zwischenspeicher sinnvoll. Aber das ist Optimierung. Nur ich erzeuge da einiges an Overhead - weiß es aber auf Anhieb nicht besser zu lösen.

--- [2017-08-24 15:53 CEST] Automatisch zusammengeführter Beitrag ---

Ich habe allerdings noch ein kleines Problem dabei... Eine Prüfung kriege ich da nicht so recht hin leider...
Ich überlege noch ein wenig...

--- [2017-08-24 16:16 CEST] Automatisch zusammengeführter Beitrag ---

Neues Todo:
  • Funktionen oder Funktionsblöcke, die in Board::applyTurn(Turn) und Board::isValid(Turn) mehr oder minder gleich sind in entsprechende Funktionen auslagern.
  • Board::getGroups() mit default-Parametern ausstatten und vereinfachen. Dopplung von Code vermeiden.

--- [2017-08-24 16:27 CEST] Automatisch zusammengeführter Beitrag ---

Zudem habe ich noch einen Bug in isValid(Turn).
Undzwar genau die Atari regel.
Man darf auf ein Feld setzen, bei dem der eigene Stein gefangen werden würde, wenn man dadurch einen anderen Stein fängt und somit selber überlebt.
Das habe ich wohl noch nicht korrekt implementiert. Ich glaube das ist derzeit überhaupt nicht vorhanden.

--- [2017-08-24 16:43 CEST] Automatisch zusammengeführter Beitrag ---

Sollte jetzt passen.

Mag da jemand Code aufräumen? *hehe*
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
Also ich hab den Code für das Board jetzt mal überflogen, macht zum Teil Sinn für mich bzw. kann ich es etwas nachvollziehen.

Ich verstehe allerdings nicht 100% was die Aufgabe bzw. Nutzen der "Group" Klasse sein soll.
Oder was diese vereinfacht....

Wenn ein Atari gesetzt wird, werden alle "Coordinate" Member aus der Gruppe entfernt wenn die Gruppe in der anderen Spielerfarbe gehalten ist?
Das Attribut welchem Spieler die Gruppe gehört, hast du ja... so wie ich das aus der "Group.hpp" sehe.
Aber ist dem auch so?

Und im weiteren, ich verstehe den Aufbau der Funktionen, wie sie Brother John vorgeschlagen, also den "Initialisatorkopf" nicht... also was heißt das hier? (aus Coordinate.hpp)
[src=cpp]Coordinate(int x, int y) : m_x(x), m_y(y) {}[/src]

Ich verstehe das Coordinate mit zwei Int-Werten mit "x" und "y" initialisiert wird - aber was macht das ": : m_x(x), m_y(y)" ?
Ich weiß das war auch Thema hier, aber ich werde daraus nicht schlau, was sagt das dem Compiler genau? Das "m_x" auf "x" und "m_y" sicht auf "y" bezieht?

Vielleicht geht es auch nur mir so, aber da blicke ich absolut nicht durch ;)
 

BurnerR

Bot #0384479

Registriert
20 Juli 2013
Beiträge
5.505
Und im weiteren, ich verstehe den Aufbau der Funktionen, wie sie Brother John vorgeschlagen, also den "Initialisatorkopf" nicht... also was heißt das hier? (aus Coordinate.hpp)
[src=cpp]Coordinate(int x, int y) : m_x(x), m_y(y) {}[/src]

Ich verstehe das Coordinate mit zwei Int-Werten mit "x" und "y" initialisiert wird - aber was macht das ": : m_x(x), m_y(y)" ?
Ich weiß das war auch Thema hier, aber ich werde daraus nicht schlau, was sagt das dem Compiler genau? Das "m_x" auf "x" und "m_y" sicht auf "y" bezieht?

Vielleicht geht es auch nur mir so, aber da blicke ich absolut nicht durch ;)

Das heißt einfach, dass den Attributen mit den Namen "m_x" und "m_y" die Werte "x" bzw. "y" zugewiesen werden. Die Syntax ist an dieser Stelle einfach ein bisschen anders / ungewohnter.

Brother John hat ja schon ausgeführt, warum das an diese Stelle gehört: Die Attribute werden immer initialisiert, bevor in den Definitionsblock gesprungen wird und auf diese Weise kriegen sie direkt die korrekten Werte.
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
Das heißt einfach, dass den Attributen mit den Namen "m_x" und "m_y" die Werte "x" bzw. "y" zugewiesen werden. Die Syntax ist an dieser Stelle einfach ein bisschen anders / ungewohnter.

Also das gleiche, als ob ich das über die Initialisierungs-"Funktion" mache, bzw. den Umweg über Setter Funktionen?
Also eigentlich das gleiche. Aber direkt an die Klasse übergeben werden bei der Initialisierung, bei dem die Parameter der Funktion direkt die Attribute (Werte) der Klasse gesetzt.

Ähnlich als ob man schreibt:
"m_x = x;"
"m_y = y;"

Ja, das macht dann schon wieder mehr Sinn... :)

Oh Gott ist das komplex... aber logisch eigentlich :D
 

Roin

Freier Denker

Registriert
22 Juli 2013
Beiträge
581
  • Thread Starter Thread Starter
  • #78
Dann will ich doch mal ein bisschen deine Fragen beantworten.
Also ich hab den Code für das Board jetzt mal überflogen, macht zum Teil Sinn für mich bzw. kann ich es etwas nachvollziehen.
Das ist doch ein guter Anfang.

Ich verstehe allerdings nicht 100% was die Aufgabe bzw. Nutzen der "Group" Klasse sein soll.
In Go gibt es zwei Spieler. Schwarz und Weiß. Diese legen Steine auf das Spielfeld. liegen zwei gleichfarbige Steine vertikal oder horizontal nebeneinander, werden diese als eine "Einheit", ergo Gruppe, gewertet.
Diese Gruppe muss im Gesamten leben (2 Augen besitzen; derzeit nicht implementiert), ansonsten ist sie tot. Wenn eine Gruppe tot ist, wird sie am Spielende von dem Spielfeld entfernt (ohne Punkte an den Gegner zu geben! Nur die freien Felder geben dann Punkte).
Es kann allerdings auch eine Gruppe gefangen werden. Das bedeutet, dass alle freien Felder um die Gruppe (also die Freiheiten) von einem feindlichen Stein/Gruppe besetzt sind. Hat eine Gruppe keine Freiheiten mehr, wird diese vom Spielfeld entfernt und für jeden entfernten Stein bekommt der Jäger/Fänger einen Punkt.
Daher ist es sinnig auch solche Gruppen in dem Programm abzubilden. Man könnte auch wie in anderen Brettspielen üblichen die Felder darstellen und angeben, ob dort ein Spielstein liegt usw. Allerdings ist es imho komplizierter, dann diese Gruppen als Einheit darzustellen.

Das Attribut welchem Spieler die Gruppe gehört, hast du ja... so wie ich das aus der "Group.hpp" sehe.
Aber ist dem auch so?
Das verstehe ich nicht. Was fragst du genau? Eine Gruppe bekommt die Spielerfarbe/Zugehörigkeit des ersten Spielsteins, der dieser Gruppe hinzugefügt wird. Alle folgenden Spielsteine, die hinzugefügt werden, müssen von dem Board überprüft werden, ob diese auch dazugehören dürfen --> isValid(Group) und entsprechend group::getColor() == Turn::getColor()

EDIT: Da war ich wohl zu langsam...
Und im weiteren, ich verstehe den Aufbau der Funktionen, wie sie Brother John vorgeschlagen, also den "Initialisatorkopf" nicht... also was heißt das hier? (aus Coordinate.hpp)
[src=cpp]Coordinate(int x, int y) : m_x(x), m_y(y) {}[/src]
Der Doppelpunkt hinter dem eigentlichen Funktionskopf bedeutet, dass dieser vor dem "Eintreten" in den Funktionsbody (das in den geschweiften Klammern) erst die dahinterstehenden Funktionen aufrufen soll.
Also sind in diesem Fall m_x() und m_y() Funktionen.
Jetzt fragt man sich natürlich "Welche?". Da m_x und m_y Variablen der zugehörigen Klasse sind können diese mit dem passenden Typ aufgerufen werden. Das wären dann sozusagen Construktoren von dem entsprechenden Dateityp.
Ähnlich wie folgendes (Ich weiß nicht, ob man das so schreiben könnte):
[src=cpp]// Calls the constructor of MyClass with the Parameter classParameter1 and
// saves the object with variableName objectName
MyClass objectName(classParameter1);

// Same as above for Standard types
int integerVariable(5);[/src]
Da der Typ der Member-Variablen bereits bekannt ist, kann man den Construktor der Klasse auch ohne vorherige Angabe des Typs aufrufen.

@Brother John: Habe ich das richtig verstanden und erklärt?
@theSplit: Hast du verstanden, wie ich das meine? Hat dir das geholfen?
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.573
@Roin, danke für deinen Bemühungen :)

In Go gibt es zwei Spieler. Schwarz und Weiß. Diese legen Steine auf das Spielfeld. liegen zwei gleichfarbige Steine vertikal oder horizontal nebeneinander, werden diese als eine "Einheit", ergo Gruppe, gewertet.

Genau da war ich mir nicht sicher wie das Zustande kommt, aber du erklärt es ja auch weiter Anhand der Go Regeln:

Diese Gruppe muss im Gesamten leben (2 Augen besitzen; derzeit nicht implementiert), ansonsten ist sie tot. Wenn eine Gruppe tot ist, wird sie am Spielende von dem Spielfeld entfernt (ohne Punkte an den Gegner zu geben! Nur die freien Felder geben dann Punkte).
Es kann allerdings auch eine Gruppe gefangen werden. Das bedeutet, dass alle freien Felder um die Gruppe (also die Freiheiten) von einem feindlichen Stein/Gruppe besetzt sind. Hat eine Gruppe keine Freiheiten mehr, wird diese vom Spielfeld entfernt und für jeden entfernten Stein bekommt der Jäger/Fänger einen Punkt.
Daher ist es sinnig auch solche Gruppen in dem Programm abzubilden. Man könnte auch wie in anderen Brettspielen üblichen die Felder darstellen und angeben, ob dort ein Spielstein liegt usw. Allerdings ist es imho komplizierter, dann diese Gruppen als Einheit darzustellen.

Lange Rede, kurzer Sinn :D - eine Gruppe sind also Spielsteine die "aneinander anliegen", wohlgemerkt vom gleichen Spieler, also mindestens 2 Steine die horizontal oder vertikal verbunden sind bilden dann eine Gruppe.

Die Regeln, ob gegnerische Spielsteine "gefangen" sind macht dann das "Board".
Und ein "Spielstein" ist defacto über die Klasse "Coordinate.hpp" bzw. kann diese einen "Spielstein" haben ja/nein, deklariert.

Soweit korrekt? :unknown:

Das verstehe ich nicht. Was fragst du genau? Eine Gruppe bekommt die Spielerfarbe/Zugehörigkeit des ersten Spielsteins, der dieser Gruppe hinzugefügt wird.

Ergibt Sinn.

Alle folgenden Spielsteine, die hinzugefügt werden, müssen von dem Board überprüft werden, ob diese auch dazugehören dürfen --> isValid(Group) und entsprechend group::getColor() == Turn::getColor()

Also ohne jetzt den Code nochmal gelesen zu haben, würde ich aus deiner Beschreibung entnehmen das "isValid(Group)" überprüft, ob ein Spielstein "anliegt" und mit in die zu prüfende Gruppe dazu genommen werden kann?
Frage aber, kann ein Spielstein in zwei Gruppen parallel existieren? Oder fasst du Gruppen auch zusammen? (Ich hatte etwas von "Merge" in deinem Code gelesen).



Also Grundsätzlich denke ich diesen Construktur Aufbau, verstanden zu haben, was mich jetzt allerdings wieder stutzig macht, du sagst, das hatte @Burner nicht erwähnt:
Die Funktionen "m_x(Parameter)" und "m_y(Parameter)" werden "aufgerufen?

Ich dachte der Compiler sorgt nur dafür, so hatte ich es verstanden, das die Werte "m_x" mit "x" und "m_y" mit "y" initialisiert werden. Deshalb das "Beispiel":

Das Procedere gleicht in etwa dem Code beim Initialisieren der Klasse wie dieser "Pseudocode"?
"""
Klasse {
private:
int m_x = 0;
int m_y = 0;

initialisierungsMethode(int x, int y) {
// Das wird beides über den Konstruktor gemacht:
m_x = x; // Hier wird m_x gesetzt
m_y = y; // Hier m_y

}
}
"""

So hab ich es verstanden. :)
 
Zuletzt bearbeitet:

Brother John

(schein)heilig
Veteran

Registriert
1 Aug. 2013
Beiträge
235
Oh Gott ist das komplex... aber logisch eigentlich
Halbwegs logisch wenigstens. Initialisierung in C++ ist das, was man professionell als »a bloody mess« bezeichnet. ;) :( Der Ballast von C, historisch weitergewachsen und dann mit C++11 eine komplett neue Syntax drangebaut, die anschließend schon wieder weitergewachsen ist. Es ist ja nicht nur die Syntax, sondern auch die Regeln, wann wo was implizit initialisiert wird oder auch nicht.

Also sind in diesem Fall m_x() und m_y() Funktionen.
Nope. m_x und m_y bleiben schon *Variablen*. Was in der Initliste steht, ist eine Initialisierung, die nur dooferweise genauso aussieht wie ein Funktionsaufruf. Von der Bedeutung her könnte da auch stehen:
[src=cpp]// Pseudocode, kein valides C++
Coordinate(int x, int y)
: m_x = x
, m_y = y
{}[/src]
Mit deinen MyClass- und integerVariable-Beispielen bist du schon deutlich näher dran. Das sind auch Initialisierungen. Für den int ist es recht unübliche Syntax im Vergleich zum Normalfall
[src=cpp]int integerVariable = 5;[/src]
Ändert aber nichts dran, dass das absolut einwandfrei valides C++ ist.

MyClass passt auch. Wenn du eine Stackvariable brauchst und dein Ctor Parameter hat, dann:
Code:
Type variable_name(ctor_arguments)
Also:
[src=cpp]
struct MyClass {
MyClass(int foo): m_foo(foo) {};
int m_foo;
}

int main() {
MyClass objectName(5);
}[/src]

v.a. @theSplit, weil du C-Erfahrung hast:
Nicht von dem struct verwirren lassen. Es gibt in C++ genau einen – und *nur* diesen einen – Unterschied zwischen struct und class: Membervariablen/-funktionen/etc. sind im struct standardmäßig public, in der class standardmäßig private.

--- [2017-08-26 15:47 CEST] Automatisch zusammengeführter Beitrag ---

Ihr postet zu schnell für mich alten Mann … :)

Das hier sollte ich vielleicht noch nachschieben:
[src=cpp]struct Foo {
int member = 5;
}[/src]
ist seit C++11 eine Kurzform für
[src=cpp]struct Foo {
Foo() : member(5) {}
int member;
}[/src]
 
Oben