Käsekästchen

Auf dieser Seite sehen Sie ein Programmierbeispiel für das Käsekästchen-Spiel mit C++11 und Qt 5.

Kennen Sie noch das Spiel Käsekästchen aus der Schulzeit oder von langen Bahnreisen? Damit können sich zwei Spieler wunderbar die Zeit vertreiben. Sie benötigen nur kariertes Papier und zwei Stifte. Ein Spieler malt ein großes Rechteck oder ein Polygon auf, und abwechselnd markieren die Spieler eine Linie. Wenn ein Spieler ein Kästchen schließt, gehört es ihm: er malt sein Symbol hinein und darf eine weitere Linie markieren. Das Spiel ist zuende, wenn alle Kästchen geschlossen sind, und der Spieler mit den meisten Kästchen gewinnt.

Wenn kein Mitspieler vorhanden ist, können Sie das Spiel gegen den Computer spielen.
Dabei können Sie

Download

Sie können die ausführbare Datei mit den benötigten Bibliotheken (Dlls) als Zip-Datei herunterladen, irgendwohin entpacken und das Programm mit einem Doppelklick auf Kaesekaestchen.exe starten.

Kaesekaestchen.zip (Windows, 14 MB)

Weiterhin können Sie den Quellcode des Programms herunterladen, mit dem Qt Creator öffnen und ausführen. Das Programm wurde mit dem Qt Creator 2.7.0 und Qt 5.0.2 (32 bit) erstellt.

Kaesekaestchen-sources.zip

Quellcode

Hier befinden sich die Qt Projektdatei und der C++ Quellcode. Die Formular-, Ressourcen- und Icondatei finden Sie im gezippten Quellcode.

kaestchen.h
#ifndef KAESTCHEN_H
#define KAESTCHEN_H

#include <spiellabel.h>
#include <seite.h>
#include <vector>

class SpielLabel;
class Seite;
enum class Position;

class Kaestchen
{
    SpielLabel* spielLabel;
    int x, y;
    Seite* seiteOben;
    Seite* seiteRechts;
    Seite* seiteUnten;
    Seite* seiteLinks;
    Kaestchen* obererNachbar;
    Kaestchen* rechterNachbar {nullptr};
    Kaestchen* untererNachbar {nullptr};
    Kaestchen* linkerNachbar;
    bool ausgegraut {false};
    bool vomMenschenGeschlossen {false};
public:
    //(x, y) ist die linke obere Ecke des Kästchens
    Kaestchen(SpielLabel* spielLabel, int x, int y,
              Kaestchen* linkerNachbar, Kaestchen* obererNachbar);
    ~Kaestchen();
    Kaestchen* getNachbar(Position pos) const;
    bool markiereSeite(int x, int y, Position *seitenPosition);
    void markiereSeite(Position pos);
    Seite* getSeite(Position pos) const;
    std::vector<Position> getOffenePositionen() const;
    bool geschlossen() const;
    void zeichneGeschlossen(bool mensch);
    void setzeZurueck(bool mensch);
    void graueAus();
    bool istAusgegraut() const;
    int anzahlMarkierteSeiten() const;
    std::vector<Seite*> getMarkierteSeiten() const;
    char status() const;
    void setzeVomStatus(char status);
    static const int breite {40};
    static const Qt::GlobalColor grau {Qt::GlobalColor::gray};
};

#endif // KAESTCHEN_H

mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <spiellabel.h>
#include <QLabel>

class SpielLabel;
enum class Groesse;

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT
    
public:
    explicit MainWindow(QWidget *mainwindow = 0);
    ~MainWindow();
    void aktualisiereStatusBar(const QString& s);
    void zeigeTemporaereStatusNachricht(const QString& s);
    void erlaubeRueckgaengig(bool wert);
    void erlaubeWiederherstellen(bool wert);
    void setzeGroesse(Groesse groesse, bool graueAus = true,
                      bool erzeugeNeuesSpiel = false);
    void setzeLevel(int level);

private slots:
    void on_actionNeues_Spiel_triggered();
    void on_actionKlein_triggered();
    void on_actionMittel_triggered();
    void on_actionGross_triggered();
    void on_actionLevel5_triggered();
    void on_actionLevel4_triggered();
    void on_actionLevel3_triggered();
    void on_actionLevel2_triggered();
    void on_actionLevel1_triggered();
    void on_actionRueckgaengig_triggered();
    void on_actionWiederherstellen_triggered();
    void on_actionSpiel_oeffnen_triggered();
    void on_actionSpiel_speichern_triggered();
    void on_actionSpiel_speichern_unter_triggered();
    void on_actionMensch_startet_toggled(bool menschStarted);

private:
    Ui::MainWindow *ui;
    SpielLabel* spielLabel;
    QLabel statusLabel;
    QFont font;
    QFont fontBold;
    Groesse groesse;
    void resize();
};

#endif // MAINWINDOW_H

matrix.h
#ifndef MATRIX_H
#define MATRIX_H

#include <vector>

class Kaestchen;

class Matrix
{
    int anzahlZeilen;
    int anzahlSpalten;
    std::vector<Kaestchen*> matrix;
public:
    Matrix();
    Matrix(int anzahlZeilen, int anzahlSpalten);
    Matrix& operator=(Matrix&& m);
    Kaestchen** operator[](int zeile);
    Kaestchen** begin();
    Kaestchen** end();
};

#endif // MATRIX_H

seite.h
#ifndef SEITE_H
#define SEITE_H

#include <spiellabel.h>
#include <qnamespace.h>
#include <vector>

class SpielLabel;

enum class Position {OBEN, RECHTS, UNTEN, LINKS};

class Seite
{
    SpielLabel* spielLabel;
    int x;
    int y;
    Position pos;
    bool klickbar {false};
    bool markiert {false};
    static const int duenneBreite {1};
    static const int dickeBreite {2};
    int dicke {duenneBreite};
    static const Qt::GlobalColor schwacheFarbe {Qt::GlobalColor::lightGray};
    static const Qt::GlobalColor starkeFarbe {Qt::GlobalColor::black};
    Qt::GlobalColor farbe {starkeFarbe};
    void zeichneLinie(Qt::GlobalColor farbe, int breite = duenneBreite);
    std::vector<Seite*> nachbarn;
public:
    //(x, y) ist die linke obere Ecke des Kästchens
    Seite(SpielLabel* spielLabel, int x, int y, Position pos);
    //die Seite wird mit einem anderen Kästchen geteilt
    void setzeGeteilt();
    bool setzeMarkiert();
    void blinke(bool warnung);
    void setzeNormal();
    void setzeZurueck();
    void setzeNichtKlickbar();
    bool istKlickbar() const;
    bool istMarkiert() const;
    Position getPosition() const;
    void addNachbar(Seite* nachbar);
};

#endif // SEITE_H

spiellabel.h
#ifndef SPIELLABEL_H
#define SPIELLABEL_H

#include "mainwindow.h"
#include "kaestchen.h"
#include "seite.h"
#include "matrix.h"
#include <QLabel>
#include <QMouseEvent>
#include <QTimer>
#include <QPixmap>
#include <vector>
#include <climits>

class Kaestchen;
class MainWindow;
struct Zug;

enum class Groesse {KLEIN, MITTEL, GROSS};

class SpielLabel : public QLabel
{
    Q_OBJECT

    MainWindow* mainWindow;
    QPixmap* spielflaeche {nullptr};
    int level {5};
    bool menschStarted {true};
    int anzahlKaestchen;
    bool fertig;
    Matrix matrix;
    static const int abstandLinks {1};
    static const int abstandOben {2};
    int b;
    int h;
    QTimer timerBlinke {this};
    QTimer timerComputerIstDran {this};
    Seite* blinkSeite;
    std::vector<Kaestchen*> kaestchenZumSchliessen;
    int blinkZaehler {0};
    bool blinkeMitWarnung;
    bool menschIstDran;
    int standMensch, standComputer;
    void aktualisiereStand();
    void wechsleSpieler(bool menschIstDran);
    bool inKonstruktion;
    std::vector<Kaestchen*> kaestchenMit3MarkiertenSeiten;
    std::vector<Kaestchen*> kaestchenMitMax1MarkierterSeite;
    std::vector<Kaestchen*> kaestchenMit2MarkiertenSeiten;
    void untersucheKaestchen(Kaestchen* kaestchen,
                             std::vector<Kaestchen*>& schliessbareKaestchen);
    struct Zug
    {
        bool mensch;
        std::vector<Seite*> seiten;
        std::vector<Kaestchen*> geschlosseneKaestchen;
        Zug(bool mensch, Seite* seite):
            mensch {mensch}, seiten {seite} {}
    };
    std::list<Zug*> zuege;
    std::list<Zug*>::iterator zugIt;
    void neuerZug(Zug* zug);
    enum class Ecke {LINKS_OBEN, RECHTS_OBEN,
                     RECHTS_UNTEN, LINKS_UNTEN};
    int zeichneWinkel(Ecke aussparung,
                      int minZeile, int minSpalte,
                      int maxZeile, int maxSpalte);
    int zeichneDiagonale(bool vonLinksObenNachRechtsUnten,
                         int minZeile, int minSpalte,
                         int maxZeile, int maxSpalte);
    QString dateiName;

private slots:
    void blinkeMitSeite();
    void computerZug();

public:
    SpielLabel(MainWindow *mainwindow);
    QPixmap* getSpielflaeche() const;
    void zeichne();
    int breite() const;
    int hoehe() const;
    void erzeugeSpielflaeche(Groesse groesse, bool graueAus);
    void setzeLevel(int level);
    void setzeMenschStarted(bool menschStarted);
    void incStandMensch(bool inc);
    void incStandComputer(bool inc);
    void rueckgaengig();
    void wiederherstellen();
    void speichern(Groesse groesse, bool speichernUnter);
    void oeffnen();
    static const Qt::GlobalColor hintergrundfarbe {Qt::GlobalColor::yellow};

protected:
    void mousePressEvent(QMouseEvent*);
};

#endif // SPIELLABEL_H

kaestchen.cpp
#include "kaestchen.h"
#include "spiellabel.h"
#include <QPainter>

using namespace std;

Kaestchen::Kaestchen(SpielLabel *spielLabel, int x, int y,
                     Kaestchen *linkerNachbar, Kaestchen *obererNachbar):
    spielLabel {spielLabel}, x {x}, y {y}
{
    this->linkerNachbar = linkerNachbar;
    this->obererNachbar = obererNachbar;
    if (obererNachbar) {
        seiteOben = obererNachbar->seiteUnten;
        seiteOben->setzeGeteilt();
        obererNachbar->untererNachbar = this;
    } else {
        seiteOben = {new Seite{spielLabel, x, y, Position::OBEN}};
    }
    seiteRechts = {new Seite{spielLabel, x, y, Position::RECHTS}};
    seiteUnten = {new Seite{spielLabel, x, y, Position::UNTEN}};
    if (linkerNachbar) {
        seiteLinks = linkerNachbar->seiteRechts;
        seiteLinks->setzeGeteilt();
        linkerNachbar->rechterNachbar = this;
    } else {
        seiteLinks = {new Seite{spielLabel, x, y, Position::LINKS}};
    }
    seiteOben->addNachbar(seiteRechts);
    seiteOben->addNachbar(seiteLinks);
    seiteUnten->addNachbar(seiteRechts);
    seiteUnten->addNachbar(seiteLinks);
}

Kaestchen::~Kaestchen()
{
    delete seiteOben;
    delete seiteRechts;
    delete seiteUnten;
    delete seiteLinks;
}

Kaestchen* Kaestchen::getNachbar(Position pos) const
{
    switch (pos) {
    case Position::OBEN: return obererNachbar;
    case Position::RECHTS: return rechterNachbar;
    case Position::UNTEN: return untererNachbar;
    default: return linkerNachbar;
    }
}

/*
 *Falls sich die Seite nicht markieren lässt,
 *weil sie z. B. zum Rand gehört oder schon markiert ist,
 *wird die Seite zurückgegeben. Ansonsten wird nullptr zurückgegeben.
 */
bool Kaestchen::markiereSeite(int x, int y, Position* seitenPosition)
{
    Seite* seite;
    if (x < y) {
        if (breite - x < y) {
            seite = seiteUnten;
            *seitenPosition = Position::UNTEN;
        } else {
            seite = seiteLinks;
            *seitenPosition = Position::LINKS;
        }
    } else {
        if (breite - x < y) {
            seite = seiteRechts;
            *seitenPosition = Position::RECHTS;
        } else {
            seite = seiteOben;
            *seitenPosition = Position::OBEN;
        }
    }
    return seite->setzeMarkiert();
}

void Kaestchen::markiereSeite(Position pos)
{
    Seite* seite;
    switch (pos) {
    case Position::OBEN: seite = seiteOben; break;
    case Position::RECHTS: seite = seiteRechts; break;
    case Position::UNTEN: seite = seiteUnten; break;
    default: seite = seiteLinks;
    }
    seite->setzeMarkiert();
}

Seite* Kaestchen::getSeite(Position pos) const
{
    switch (pos) {
    case Position::OBEN: return seiteOben;
    case Position::RECHTS: return seiteRechts;
    case Position::UNTEN: return seiteUnten;
    default: return seiteLinks;
    }
}

vector<Position> Kaestchen::getOffenePositionen() const
{
    vector<Position> positionen;
    if (seiteOben->istKlickbar())
        positionen.push_back(Position::OBEN);
    if (seiteRechts->istKlickbar())
        positionen.push_back(Position::RECHTS);
    if (seiteUnten->istKlickbar())
        positionen.push_back(Position::UNTEN);
    if (seiteLinks->istKlickbar())
        positionen.push_back(Position::LINKS);
    return positionen;
}

vector<Seite*> Kaestchen::getMarkierteSeiten() const
{
    vector<Seite*> seiten;
    if (!seiteOben->istKlickbar())
        seiten.push_back(seiteOben);
    if (!seiteRechts->istKlickbar())
        seiten.push_back(seiteRechts);
    if (!seiteUnten->istKlickbar())
        seiten.push_back(seiteUnten);
    if (!seiteLinks->istKlickbar())
        seiten.push_back(seiteLinks);
    return seiten;
}

bool Kaestchen::geschlossen() const
{
    return !seiteOben->istKlickbar() && !seiteRechts->istKlickbar()
            && !seiteUnten->istKlickbar() && !seiteLinks->istKlickbar();
}

void Kaestchen::zeichneGeschlossen(bool mensch)
{
    QPainter painter {spielLabel->getSpielflaeche()};
    QPen pen;
    pen.setWidth(2);
    painter.setPen(pen);
    int d = 0.1*breite;
    if (mensch) {
        painter.drawEllipse(x + d, y + d, breite - 2*d, breite - 2*d);
        //Mund
        pen.setColor(Qt::GlobalColor::red);
        painter.setPen(pen);
        painter.drawArc(x + 3*d, y + 5*d, 4*d, 2*d, -30*16, -120*16);
        //Augen
        pen.setWidth(3);
        pen.setColor(Qt::GlobalColor::blue);
        painter.setPen(pen);
        painter.drawEllipse(x + 3*d, y + 3*d, d, d);
        painter.drawEllipse(x + 6*d, y + 3*d, d, d);
        vomMenschenGeschlossen = true;
    } else {
        painter.drawRoundedRect(x + d, y + 2*d, breite - 2*d, breite - 4*d, 3, 3);
    }
    spielLabel->zeichne();
    if (mensch) spielLabel->incStandMensch(true);
    else spielLabel->incStandComputer(true);
}

void Kaestchen::graueAus()
{
    QPainter painter {spielLabel->getSpielflaeche()};
    painter.fillRect(x, y, breite, breite, grau);
    for (Seite* seite: {seiteOben, seiteRechts, seiteUnten, seiteLinks})
        seite->setzeNichtKlickbar();
    ausgegraut = true;
}

bool Kaestchen::istAusgegraut() const
{
    return ausgegraut;
}

void Kaestchen::setzeZurueck(bool mensch)
{
    QPainter painter {spielLabel->getSpielflaeche()};
    painter.fillRect(x + 2, y + 2, breite - 4, breite - 4,
                     SpielLabel::hintergrundfarbe);
    spielLabel->zeichne();
    if (mensch) spielLabel->incStandMensch(false);
    else spielLabel->incStandComputer(false);
    vomMenschenGeschlossen = false;
}

int Kaestchen::anzahlMarkierteSeiten() const
{
    return (seiteOben->istKlickbar() ? 0 : 1) +
            (seiteRechts->istKlickbar() ? 0 : 1) +
            (seiteUnten->istKlickbar() ? 0 : 1) +
            (seiteLinks->istKlickbar() ? 0 : 1);
}

char Kaestchen::status() const
{
    return seiteOben->istMarkiert()
            | (seiteRechts->istMarkiert() << 1)
            | (seiteUnten->istMarkiert() << 2)
            | (seiteLinks->istMarkiert() << 3)
            | (geschlossen() << 4)
            | (vomMenschenGeschlossen << 5)
            | (ausgegraut << 6);
}

void Kaestchen::setzeVomStatus(char status)
{
    if (status & 1)
        markiereSeite(Position::OBEN);
    if (status & 1 << 1)
        markiereSeite(Position::RECHTS);
    if (status & 1 << 2)
        markiereSeite(Position::UNTEN);
    if (status & 1 << 3)
        markiereSeite(Position::LINKS);
    if (static_cast<bool>(status & 1 << 4)) {
        vomMenschenGeschlossen = static_cast<bool>(status & 1 << 5);
        zeichneGeschlossen(vomMenschenGeschlossen);
    }
    if (status & 1 << 6)
        graueAus();
}

main.cpp
#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    
    return a.exec();
}

mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <string>
#include <fstream>

using namespace std;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow {parent},
    ui {new Ui::MainWindow},
    groesse {Groesse::KLEIN}
{
    ui->setupUi(this);
    spielLabel = new SpielLabel{this};
    resize();
    setCentralWidget(spielLabel);
    fontBold.setBold(true);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::aktualisiereStatusBar(const QString &s)
{
    statusLabel.setText(s);
    ui->statusBar->addWidget(&statusLabel);
}

void MainWindow::zeigeTemporaereStatusNachricht(const QString &s)
{
    ui->statusBar->showMessage(s, 2000);
}

void MainWindow::on_actionNeues_Spiel_triggered()
{
    spielLabel->erzeugeSpielflaeche(groesse, true);
}

void MainWindow::on_actionKlein_triggered()
{
    setzeGroesse(Groesse::KLEIN);
}

void MainWindow::on_actionMittel_triggered()
{
    setzeGroesse(Groesse::MITTEL);
}

void MainWindow::on_actionGross_triggered()
{
    setzeGroesse(Groesse::GROSS);
}

void MainWindow::setzeGroesse(Groesse groesse, bool graueAus, bool erzeugeNeuesSpiel)
{
    QAction* action;
    switch (groesse) {
    case Groesse::KLEIN:
        action = ui->actionKlein; break;
    case Groesse::MITTEL:
        action = ui->actionMittel; break;
    default:
        action = ui->actionGross; break;
    }
    if (!erzeugeNeuesSpiel && action->font().bold()) return;
    for (QAction* a : {ui->actionKlein, ui->actionMittel, ui->actionGross})
        a->setFont(a == action ? fontBold : font);
    this->groesse = groesse;
    spielLabel->erzeugeSpielflaeche(groesse, graueAus);
    resize();
}

void MainWindow::resize()
{
    setFixedSize(spielLabel->breite(), spielLabel->hoehe() + 40);
}

void MainWindow::on_actionLevel5_triggered()
{
    setzeLevel(5);
}

void MainWindow::on_actionLevel4_triggered()
{
    setzeLevel(4);
}

void MainWindow::on_actionLevel3_triggered()
{
    setzeLevel(3);
}

void MainWindow::on_actionLevel2_triggered()
{
    setzeLevel(2);
}

void MainWindow::on_actionLevel1_triggered()
{
    setzeLevel(1);
}

void MainWindow::setzeLevel(int level)
{
    QAction* action;
    switch (level) {
    case 5:
        action = ui->actionLevel5; break;
    case 4:
        action = ui->actionLevel4; break;
    case 3:
        action = ui->actionLevel3; break;
    case 2:
        action = ui->actionLevel2; break;
    default:
        action = ui->actionLevel1; break;
    }
    for (QAction* a : {ui->actionLevel5, ui->actionLevel4, ui->actionLevel3,
         ui->actionLevel2, ui->actionLevel1})
        a->setFont(a == action ? fontBold : font);
    spielLabel->setzeLevel(level);
}

void MainWindow::on_actionRueckgaengig_triggered()
{
    spielLabel->rueckgaengig();
}

void MainWindow::on_actionWiederherstellen_triggered()
{
    spielLabel->wiederherstellen();
}

void MainWindow::erlaubeRueckgaengig(bool wert)
{
    ui->actionRueckgaengig->setEnabled(wert);
}

void MainWindow::erlaubeWiederherstellen(bool wert)
{
    ui->actionWiederherstellen->setEnabled(wert);
}

void MainWindow::on_actionSpiel_oeffnen_triggered()
{
    spielLabel->oeffnen();
}

void MainWindow::on_actionSpiel_speichern_triggered()
{
    spielLabel->speichern(groesse, false);
}

void MainWindow::on_actionSpiel_speichern_unter_triggered()
{
    spielLabel->speichern(groesse, true);
}

void MainWindow::on_actionMensch_startet_toggled(bool menschStarted)
{
    spielLabel->setzeMenschStarted(menschStarted);
}

matrix.cpp
#include "matrix.h"
#include "kaestchen.h"

using namespace std;

Matrix::Matrix(): Matrix(0, 0) {}

Matrix::Matrix(int anzahlZeilen, int anzahlSpalten):
    anzahlZeilen {anzahlZeilen}, anzahlSpalten {anzahlSpalten}
{
    matrix.resize(anzahlZeilen*anzahlSpalten);
}

Matrix& Matrix::operator=(Matrix&& m)
{
    //lösche alte Werte
    for (Kaestchen* kaestchen: matrix)
        delete kaestchen;
    matrix.clear();

    matrix = std::move(m.matrix);
    anzahlZeilen = m.anzahlZeilen;
    anzahlSpalten = m.anzahlSpalten;
    m.anzahlZeilen = 0;
    m.anzahlSpalten = 0;
    return *this;
}

Kaestchen** Matrix::operator[](int zeile)
{
    return &matrix[zeile*anzahlSpalten];
}

Kaestchen** Matrix::begin()
{
    return &matrix[0];
}

Kaestchen** Matrix::end()
{
    return &matrix[anzahlZeilen*anzahlSpalten];
}

seite.cpp
#include "seite.h"
#include <spiellabel.h>
#include <QPainter>

using namespace std;

Seite::Seite(SpielLabel *spielLabel, int x, int y, Position pos):
    spielLabel {spielLabel}, x {x}, y {y}, pos {pos}
{
    zeichneLinie(farbe);
}

void Seite::setzeGeteilt()
{
    zeichneLinie(schwacheFarbe);
    farbe = schwacheFarbe;
    klickbar = true;
}

bool Seite::setzeMarkiert()
{
    if (!klickbar) return false;
    zeichneLinie(starkeFarbe, dickeBreite);
    dicke = dickeBreite;
    farbe = starkeFarbe;
    klickbar = false;
    markiert = true;
    return true;
}

void Seite::zeichneLinie(Qt::GlobalColor farbe, int breite)
{
    QPainter painter {spielLabel->getSpielflaeche()};
    QPen pen {farbe};
    pen.setWidth(breite);
    painter.setPen(pen);
    painter.drawLine(x
                     + (pos == Position::RECHTS ? Kaestchen::breite : 0),

                     y
                     + (pos == Position::RECHTS && farbe == schwacheFarbe ? 1 : 0)
                     + (pos == Position::UNTEN ? Kaestchen::breite : 0),

                     x
                     + (pos == Position::LINKS ? 0 : Kaestchen::breite),

                     y
                     - (pos == Position::RECHTS && farbe == schwacheFarbe ? 1 : 0)
                     + (pos == Position::OBEN ? 0 : Kaestchen::breite));
    spielLabel->zeichne();
}

void Seite::blinke(bool warnung)
{
    zeichneLinie(warnung ? Qt::GlobalColor::red: Qt::GlobalColor::cyan, dicke);
}

void Seite::setzeNormal()
{
    zeichneLinie(farbe, dicke);
}

void Seite::setzeZurueck()
{
    klickbar = true;
    markiert = false;
    zeichneLinie(SpielLabel::hintergrundfarbe, dicke);
    farbe = schwacheFarbe;
    dicke = duenneBreite;
    setzeNormal();
    for (Seite* nachbar: nachbarn)
        nachbar->setzeNormal();
}

void Seite::setzeNichtKlickbar()
{
    klickbar = false;
    farbe = Kaestchen::grau;
}

bool Seite::istKlickbar() const
{
    return klickbar;
}

bool Seite::istMarkiert() const
{
    return markiert;
}

Position Seite::getPosition() const
{
    return pos;
}

static bool enthaelt(vector<Seite*>& v, Seite* kaestchen)
{
    return find(v.begin(), v.end(), kaestchen) != v.end();
}

void Seite::addNachbar(Seite *nachbar)
{
    if (!enthaelt(nachbarn, nachbar))
        nachbarn.push_back(nachbar);
    if (!enthaelt(nachbar->nachbarn, this))
        nachbar->addNachbar(this);
}

spiellabel.cpp
#include "spiellabel.h"
#include "kaestchen.h"
#include <ctime>
#include <QFileDialog>
#include <QMessageBox>

using namespace std;

SpielLabel::SpielLabel(MainWindow* mainWindow)
    : QLabel {mainWindow},
      mainWindow {mainWindow}
{
    erzeugeSpielflaeche(Groesse::KLEIN, true);
    srand(time(nullptr));
    timerBlinke.setSingleShot(true);
    timerBlinke.setInterval(100);
    timerComputerIstDran.setSingleShot(true);
    timerComputerIstDran.setInterval(300);
    QObject::connect(&timerBlinke, SIGNAL(timeout()), this, SLOT(blinkeMitSeite()));
    QObject::connect(&timerComputerIstDran, SIGNAL(timeout()), this, SLOT(computerZug()));
}

void SpielLabel::erzeugeSpielflaeche(Groesse groesse, bool graueAus)
{
    timerBlinke.stop();
    timerComputerIstDran.stop();
    blinkZaehler = 0;
    kaestchenZumSchliessen.clear();
    int anzahlSpalten, anzahlZeilen;
    switch (groesse) {
    case Groesse::KLEIN:
        anzahlZeilen = 5;
        anzahlSpalten = 6;
        break;
    case Groesse::MITTEL:
        anzahlZeilen = 11;
        anzahlSpalten = 13;
        break;
    default:
        anzahlZeilen = 15;
        anzahlSpalten = 22;
    }
    anzahlKaestchen = anzahlZeilen*anzahlSpalten;
    b = anzahlSpalten*Kaestchen::breite + 2*abstandLinks + 1;
    h = anzahlZeilen*Kaestchen::breite + 2*abstandOben;
    resize(b, h);
    if (spielflaeche)
        delete spielflaeche;
    spielflaeche = new QPixmap {b, h};
    spielflaeche->fill(hintergrundfarbe);
    inKonstruktion = true;
    matrix = std::move(Matrix{anzahlZeilen, anzahlSpalten});
    for (int i {0}; i != anzahlZeilen; ++i) {
        for (int j {0}; j != anzahlSpalten; ++j) {
            matrix[i][j] = new Kaestchen{this,
                    abstandLinks + j*Kaestchen::breite, abstandOben + i*Kaestchen::breite,
                    j == 0 ? nullptr : matrix[i][j - 1],
                    i == 0 ? nullptr : matrix[i - 1][j]};
        }
    }
    if (graueAus && groesse == Groesse::MITTEL) {
        //Zeichne ausgegraute Winkel
        int teilpunktZeilen {3 + rand() % (anzahlZeilen - 6)};
        int teilpunktSpalten {3 + rand() % (anzahlSpalten - 6)};
        for (int i {0}; i != 4; ++i)
            anzahlKaestchen -=
                    zeichneWinkel((Ecke) (rand()%4),
                                  i < 2 ? 0 : teilpunktZeilen,
                                  i%2 ? 0 : teilpunktSpalten,
                                  (i < 2 ? teilpunktZeilen : anzahlZeilen) + (i < 2 ? 1: 0),
                                  (i%2 ? teilpunktSpalten : anzahlSpalten) + (i%2 ? 1: 0));
    }
    if (graueAus && groesse == Groesse::GROSS) {
        //Zeichne ausgegraute Winkel und Diagonalen
        int teilpunktZeilen {3 + rand() % (anzahlZeilen - 6)};
        int teilpunktSpalten1 {3 + rand() % (anzahlSpalten/2 - 6)};
        int teilpunktSpalten2 {6 + teilpunktSpalten1 + rand() % (anzahlSpalten/2 - 5)};
        for (int i {0}; i != 6; ++i)
            anzahlKaestchen -=
                    zeichneDiagonale(rand() & 1,
                                     i < 3 ? 1 : teilpunktZeilen + 1,
                                     (i%3 == 0 ? 0 : (i%3 == 1 ? teilpunktSpalten1 : teilpunktSpalten2)) + 1,
                                     i < 3 ? teilpunktZeilen - 1 : anzahlZeilen - 1,
                                     (i%3 == 0 ? teilpunktSpalten1 : (i%3 == 1 ? teilpunktSpalten2 : anzahlSpalten - 1)) - 1);
    }
    inKonstruktion = false;
    zeichne();
    standMensch = standComputer = 0;
    fertig = false;
    wechsleSpieler(menschStarted);
    zuege.clear();
    zugIt = zuege.begin();
    mainWindow->erlaubeRueckgaengig(false);
    mainWindow->erlaubeWiederherstellen(false);
    if (!menschIstDran) timerComputerIstDran.start();
}

int SpielLabel::zeichneWinkel(Ecke aussparung,
                              int minZeile, int minSpalte,
                              int maxZeile, int maxSpalte)
{
    int z0 {minZeile + 1 + rand() % (maxZeile - minZeile - 3)};
    int z1 {z0 + 1 + rand() % (maxZeile - z0 - 2)};
    int z2 {z1 + 1 + rand() % (maxZeile - z1 - 1)};
    int s0 {minSpalte + 1 + rand() % (maxSpalte - minSpalte - 3)};
    int s1 {s0 + 1 + rand() % (maxSpalte - s0 - 2)};
    int s2 {s1 + 1 + rand() % (maxSpalte - s1 - 1)};
    int res {0};
    if (aussparung != Ecke::LINKS_OBEN)
        for (int i {z0}; i != z1; ++i)
            for (int j {s0}; j != s1; ++j) {
                matrix[i][j]->graueAus();
                ++res;
            }
    if (aussparung != Ecke::RECHTS_OBEN)
        for (int i {z0}; i != z1; ++i)
            for (int j {s1}; j != s2; ++j) {
                matrix[i][j]->graueAus();
                ++res;
            }
    if (aussparung != Ecke::RECHTS_UNTEN)
        for (int i {z1}; i != z2; ++i)
            for (int j {s1}; j != s2; ++j) {
                matrix[i][j]->graueAus();
                ++res;
            }
    if (aussparung != Ecke::LINKS_UNTEN)
        for (int i {z1}; i != z2; ++i)
            for (int j {s0}; j != s1; ++j) {
                matrix[i][j]->graueAus();
                ++res;
            }
    return res;
}

int SpielLabel::zeichneDiagonale(bool vonLinksObenNachRechtsUnten,
                                 int minZeile, int minSpalte,
                                 int maxZeile, int maxSpalte)
{
    int res {0};
    int zDiff {maxZeile - minZeile};
    int sDiff {maxSpalte - minSpalte};
    bool iteriereUeberZeilen {zDiff >= sDiff};
    int i0 = iteriereUeberZeilen ? minZeile : minSpalte;
    int i1 = iteriereUeberZeilen ? maxZeile : maxSpalte;
    int j0 = iteriereUeberZeilen ? minSpalte : minZeile;
    double q = 1.*(iteriereUeberZeilen ? sDiff : zDiff)/(iteriereUeberZeilen ? zDiff : sDiff);
    double c = q*i0;
    for (int i {i0}; i != i1; ++i) {
        int s = iteriereUeberZeilen ? (j0 + (int) (q*i - c)) : i;
        if (!vonLinksObenNachRechtsUnten)
            s = maxSpalte + minSpalte - s;
        matrix[iteriereUeberZeilen ? i : (j0 + (int) (q*i - c))][s]->graueAus();
        ++res;
    }
    return res;
}

void SpielLabel::aktualisiereStand()
{
    fertig = standMensch + standComputer == anzahlKaestchen;
    QString s {"Mensch: "};
    s += to_string(standMensch).c_str();
    s += ", Computer: ";
    s += to_string(standComputer).c_str();
    if (!fertig && menschIstDran)
        s += ", du bist dran";
    if (fertig)
        s += ", fertig";
    mainWindow->aktualisiereStatusBar(s);
}

void SpielLabel::incStandMensch(bool inc)
{
    if (inc) ++standMensch;
    else --standMensch;
    aktualisiereStand();
}

void SpielLabel::incStandComputer(bool inc)
{
    if (inc) ++standComputer;
    else --standComputer;
    aktualisiereStand();
}

void SpielLabel::wechsleSpieler(bool menschIstDran)
{
    this->menschIstDran = menschIstDran;
    aktualisiereStand();
}

QPixmap* SpielLabel::getSpielflaeche() const
{
    return spielflaeche;
}

void SpielLabel::zeichne()
{
    if (!inKonstruktion) setPixmap(*spielflaeche);
}

int SpielLabel::breite() const
{
    return b;
}

int SpielLabel::hoehe() const
{
    return h;
}

void SpielLabel::mousePressEvent(QMouseEvent* event)
{
    if (!menschIstDran || event->button() != Qt::LeftButton
            || blinkZaehler || fertig)
        return;
    if (!zuege.empty() && (*zugIt) != zuege.back()) {
        mainWindow->erlaubeWiederherstellen(false);
        ++zugIt;
        for (list<Zug*>::iterator it {zugIt}; it != zuege.end();) {
            delete *it;
            it = zuege.erase(it);
        }
        zugIt = zuege.end();
        --zugIt;
    }
    int nx = (event->x() - abstandLinks)/Kaestchen::breite;
    int ny = (event->y() - abstandOben)/Kaestchen::breite;
    int kx = event->x() - abstandLinks - nx*Kaestchen::breite;
    int ky = event->y() - abstandOben - ny*Kaestchen::breite;
    Kaestchen* kaestchen = matrix[ny][nx];
    if (kaestchen->istAusgegraut()) return;
    Position pos;
    if (!kaestchen->markiereSeite(kx, ky, &pos)) {
        blinkSeite = kaestchen->getSeite(pos);
        blinkSeite->blinke(true);
        blinkZaehler = 1;
        blinkeMitWarnung = true;
        timerBlinke.start();
        return;
    }
    if (zuege.empty() || menschIstDran != (*zugIt)->mensch)
        neuerZug(new Zug {menschIstDran, kaestchen->getSeite(pos)});
    else
        (*zugIt)->seiten.push_back(kaestchen->getSeite(pos));
    bool kaestchenGeschlossen {kaestchen->geschlossen()};
    if (kaestchenGeschlossen) {
        kaestchen->zeichneGeschlossen(true);
        (*zugIt)->geschlosseneKaestchen.push_back(kaestchen);
    }
    Kaestchen* benachbartesKaestchen = kaestchen->getNachbar(pos);
    bool benachbartesKaestchenGeschlossen {benachbartesKaestchen->geschlossen()};
    if (benachbartesKaestchenGeschlossen) {
        benachbartesKaestchen->zeichneGeschlossen(true);
        (*zugIt)->geschlosseneKaestchen.push_back(benachbartesKaestchen);
    }
    if (kaestchenGeschlossen || benachbartesKaestchenGeschlossen)
        return;
    wechsleSpieler(false);
    timerComputerIstDran.start();
}

/*
 *Führt einen Zug mit dem Computer aus.
 *
 *- Gibt es ein Kästchen, bei dem drei Seiten markiert sind?
 *  Dann markiere die letzte Seite.
 *- Gibt es ein Kästchen, bei dem höchstens eine Seite markiert ist?
 *  Dann markiere eine beliebige Seite zu einem Nachbarkästchen,
 *  bei dem höchstens eine Seite markiert ist.
 *- Wähle ein Kästchen aus, bei dem zwei Seiten markiert sind,
 *  und markiere eine beliebige Seite,
 *  so dass der Mensch möglichst wenig Kästchen schließen kann.
 *
 */
void SpielLabel::computerZug()
{
    kaestchenMit3MarkiertenSeiten.clear();
    kaestchenMitMax1MarkierterSeite.clear();
    kaestchenMit2MarkiertenSeiten.clear();
    for (Kaestchen* kaestchen: matrix) {
        if (kaestchen->anzahlMarkierteSeiten() == 3)
            kaestchenMit3MarkiertenSeiten.push_back(kaestchen);
        else if (kaestchen->anzahlMarkierteSeiten() <= 1)
            kaestchenMitMax1MarkierterSeite.push_back(kaestchen);
        else if (kaestchen->anzahlMarkierteSeiten() == 2)
            kaestchenMit2MarkiertenSeiten.push_back(kaestchen);
    }
    if (rand() % 5 >= level
            && (!kaestchenMit2MarkiertenSeiten.empty() || !kaestchenMitMax1MarkierterSeite.empty()))
        kaestchenMit3MarkiertenSeiten.clear();
    if (rand() % 5 >= level
            && kaestchenMit3MarkiertenSeiten.empty() && !kaestchenMit2MarkiertenSeiten.empty())
        kaestchenMitMax1MarkierterSeite.clear();
    blinkSeite = nullptr;
    if (!kaestchenMit3MarkiertenSeiten.empty()) {
        Kaestchen* kaestchen {kaestchenMit3MarkiertenSeiten[rand() % kaestchenMit3MarkiertenSeiten.size()]};
        vector<Position> positionen {kaestchen->getOffenePositionen()};
        Position pos {positionen[0]};
        blinkSeite = kaestchen->getSeite(pos);
        kaestchenZumSchliessen.push_back(kaestchen);
        Kaestchen* nachbar {kaestchen->getNachbar(pos)};
        if (nachbar->anzahlMarkierteSeiten() == 3)
            kaestchenZumSchliessen.push_back(nachbar);
        kaestchen->markiereSeite(pos);
    }
    if (!blinkSeite && !kaestchenMitMax1MarkierterSeite.empty()) {
        do {
            int i = rand() % kaestchenMitMax1MarkierterSeite.size();
            Kaestchen* kaestchen = kaestchenMitMax1MarkierterSeite[i];
            vector<Position> positionen = {Position::OBEN, Position::RECHTS, Position::UNTEN, Position::LINKS};
            do {
                int j = rand() % positionen.size();
                Position pos {positionen[j]};
                Kaestchen* nachbar = kaestchen->getNachbar(pos);
                if (nachbar && nachbar->anzahlMarkierteSeiten() <= 1
                        && (nachbar->anzahlMarkierteSeiten() == 0
                            || nachbar->getMarkierteSeiten()[0] != kaestchen->getSeite(pos))
                        ) {
                    kaestchen->markiereSeite(pos);
                    blinkSeite = kaestchen->getSeite(pos);
                    break;
                }
                positionen.erase(positionen.begin() + j);
            } while (!positionen.empty());
            if (blinkSeite) break;
            kaestchenMitMax1MarkierterSeite.erase(kaestchenMitMax1MarkierterSeite.begin() + i);
        } while (!kaestchenMitMax1MarkierterSeite.empty());
    }
    if (!blinkSeite && !kaestchenMit2MarkiertenSeiten.empty()) {
        unsigned int anzahlSchliessbareKaestchen = 0xffffff;
        Kaestchen* gefundenesKaestchen;
        /*Pro Schleifendurchlauf werden alle Kästchen gefunden,
         *die der Mensch im darauffolgenden Zug schließen kann,
         *falls kaestchen markiert wird.
         */
        do {
            Kaestchen* kaestchen {kaestchenMit2MarkiertenSeiten[rand() % kaestchenMit2MarkiertenSeiten.size()]};
            if (rand() % 5 >= level) {
                gefundenesKaestchen = kaestchen;
                break;
            }
            vector<Kaestchen*> schliessbareKaestchen;
            untersucheKaestchen(kaestchen, schliessbareKaestchen);
            if (schliessbareKaestchen.size() < anzahlSchliessbareKaestchen) {
                anzahlSchliessbareKaestchen = schliessbareKaestchen.size();
                gefundenesKaestchen = kaestchen;
            }
        } while (!kaestchenMit2MarkiertenSeiten.empty());
        vector<Position> positionen {gefundenesKaestchen->getOffenePositionen()};
        Position pos {positionen[rand() % positionen.size()]};
        Kaestchen* nachbar {gefundenesKaestchen->getNachbar(pos)};
        if (nachbar->anzahlMarkierteSeiten() == 3)
            kaestchenZumSchliessen.push_back(nachbar);
        gefundenesKaestchen->markiereSeite(pos);
        blinkSeite = gefundenesKaestchen->getSeite(pos);
    }
    if (menschIstDran != (*zugIt)->mensch)
        neuerZug(new Zug {menschIstDran, blinkSeite});
    else
        (*zugIt)->seiten.push_back(blinkSeite);
    blinkZaehler = 2;
    blinkeMitWarnung = false;
    timerBlinke.start();
}

static bool enthaelt(vector<Kaestchen*>& v, Kaestchen* kaestchen)
{
    return find(v.begin(), v.end(), kaestchen) != v.end();
}

static void loesche(vector<Kaestchen*>& v, Kaestchen* kaestchen)
{
    v.erase(find(v.begin(), v.end(), kaestchen));
}

void SpielLabel::untersucheKaestchen(Kaestchen *kaestchen, vector<Kaestchen*>& schliessbareKaestchen)
{
    if (kaestchen->anzahlMarkierteSeiten() == 2) {
        if (enthaelt(kaestchenMit2MarkiertenSeiten, kaestchen))
            loesche(kaestchenMit2MarkiertenSeiten, kaestchen);
        schliessbareKaestchen.push_back(kaestchen);
    } else {
        int anzahlKlickbareSeiten {4 - kaestchen->anzahlMarkierteSeiten()};
        for (Position pos: kaestchen->getOffenePositionen()) {
            if (enthaelt(schliessbareKaestchen, kaestchen->getNachbar(pos)))
                --anzahlKlickbareSeiten;
        }
        if (anzahlKlickbareSeiten <= 1)
            schliessbareKaestchen.push_back(kaestchen);
        else
            return;
    }
    for (Position pos: kaestchen->getOffenePositionen()) {
        Kaestchen* nachbar = kaestchen->getNachbar(pos);
        if (!enthaelt(schliessbareKaestchen, nachbar))
            untersucheKaestchen(nachbar, schliessbareKaestchen);
    }
}

void SpielLabel::blinkeMitSeite()
{
    if ((blinkZaehler & 1))
        blinkSeite->setzeNormal();
    else
        blinkSeite->blinke(blinkeMitWarnung);
    if (++blinkZaehler != 4)
        timerBlinke.start();
    else {
        blinkZaehler = 0;
        if (!menschIstDran) {
            if (!kaestchenZumSchliessen.empty()) {
                for (Kaestchen* kaestchen: kaestchenZumSchliessen) {
                    kaestchen->zeichneGeschlossen(false);
                    (*zugIt)->geschlosseneKaestchen.push_back(kaestchen);
                }
                kaestchenZumSchliessen.clear();
                if (!fertig) timerComputerIstDran.start();
            } else {
                wechsleSpieler(true);
            }
        }
    }
}

void SpielLabel::neuerZug(Zug* zug)
{
    zuege.push_back(zug);
    zugIt = zuege.end();
    --zugIt;
    mainWindow->erlaubeRueckgaengig(true);
}

void SpielLabel::rueckgaengig()
{
    if (!menschIstDran) return;
    mainWindow->erlaubeWiederherstellen(true);
    for (int i {0}; i != 2; ++i) {
        for (Seite* seite: (*zugIt)->seiten)
            seite->setzeZurueck();
        for (Kaestchen* kaestchen: (*zugIt)->geschlosseneKaestchen)
            kaestchen->setzeZurueck((*zugIt)->mensch);
        --zugIt;
    }
    if (++zugIt == zuege.begin())
        mainWindow->erlaubeRueckgaengig(false);
    --zugIt;
}

void SpielLabel::wiederherstellen()
{
    if (!menschIstDran) return;
    mainWindow->erlaubeRueckgaengig(true);
    for (int i {0}; i != 2; ++i) {
        ++zugIt;
        for (Seite* seite: (*zugIt)->seiten)
            seite->setzeMarkiert();
        for (Kaestchen* kaestchen: (*zugIt)->geschlosseneKaestchen)
            kaestchen->zeichneGeschlossen((*zugIt)->mensch);
    }
    if (++zugIt == zuege.end())
        mainWindow->erlaubeWiederherstellen(false);
    --zugIt;
}

void SpielLabel::setzeLevel(int level)
{
    this->level = level;
}

void SpielLabel::setzeMenschStarted(bool menschStarted)
{
    this->menschStarted = menschStarted;
}

static QString filter {"Käsekästchen (*.kk)"};

void SpielLabel::speichern(Groesse groesse, bool speichernUnter)
{
    if (speichernUnter || dateiName.isEmpty()) {
        dateiName = QFileDialog::getSaveFileName(
                    this,
                    QString {"Spiel speichern"} + (speichernUnter ? " unter ..." : ""),
                    dateiName.isEmpty() ? QDir::homePath() : dateiName,
                    filter);
    }
    if (dateiName.isEmpty()) return;
    QFile file {dateiName};
    file.open(QIODevice::WriteOnly);
    file.putChar(static_cast<char>(groesse));
    file.putChar(level);
    file.putChar(menschIstDran);
    bool res;
    for (Kaestchen* kaestchen: matrix)
        res = file.putChar(kaestchen->status());
    file.close();
    if (res)
        mainWindow->zeigeTemporaereStatusNachricht("Datei gespeichert");
    else
        QMessageBox::warning(this, "Fehler", "Die Datei kann nicht gespeichert werden.");
}

void SpielLabel::oeffnen()
{
    dateiName = QFileDialog::getOpenFileName(
                this, "Spiel öffnen",
                dateiName.isEmpty() ? QDir::homePath() : dateiName,
                filter);
    if (dateiName.isEmpty()) return;
    QFile file {dateiName};
    file.open(QIODevice::ReadOnly);
    char c;
    file.getChar(&c);
    if (c < 0 || c > 2) {
        c = static_cast<char>(Groesse::KLEIN);
    }
    mainWindow->setzeGroesse(static_cast<Groesse>(c), false, true);
    file.getChar(&c);
    mainWindow->setzeLevel(c);
    file.getChar(&c);
    menschIstDran = static_cast<bool>(c);
    for (Kaestchen* kaestchen: matrix) {
        file.getChar(&c);
        kaestchen->setzeVomStatus(c);
    }
    file.close();
    if (!menschIstDran)
        timerComputerIstDran.start();
}

Kaesekaestchen.pro
#-------------------------------------------------
#
# Project created by QtCreator 2019-02-20T13:04:40
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = Kaesekaestchen
TEMPLATE = app

CONFIG += c++11

SOURCES += main.cpp\
        mainwindow.cpp \
    spiellabel.cpp \
    kaestchen.cpp \
    seite.cpp \
    matrix.cpp

HEADERS  += mainwindow.h \
    spiellabel.h \
    kaestchen.h \
    seite.h \
    matrix.h

FORMS    += mainwindow.ui

RESOURCES += \
    ressources.qrc

RC_ICONS = cheese.ico

Übersicht über die Klassen

Hier finden Sie eine Übersicht über die benutzten Klassen und ihre wichtigsten Methoden.

Stellt das Qt-Hauptfenster dar. Hier befinden sich die Slots, die in der Formulardatei mainwindow.ui angelegt worden sind. Sie bestimmen, was passieren soll, wenn ein Menüeintrag angeklickt worden ist.
Weiterhin wird hier die Statusbar aktualisiert.
Slots
Im Menü Datei:
  • void on_actionNeues_Spiel_triggered():
    Legt durch das SpielLabel ein neues Spiel in der ausgewählten Größe an.
  • void on_actionSpiel_oeffnen_triggered():
    Öffnet ein Spiel, das der Benutzer vorher gespeichert hat. Wird ebenfalls von SpielLabel erledigt.
  • void on_actionSpiel_speichern_triggered() und void on_actionSpiel_speichern_unter_triggered():
    Damit kann der Benutzer ein Spiel speichern. Wird von SpielLabel ausgeführt.

Im Menü Bearbeiten:
  • void on_actionRueckgaengig_triggered():
    Wenn der Benutzer schon einige Züge gemacht hat, kann er sie bis zum Anfang zurücknehmen. Nur in diesem Fall ist der Menüpunkt aktiv. Wird über das SpielLabel gesteuert, das zum (De)aktivieren void erlaubeRueckgaengig(bool wert) benutzt.
  • void on_actionWiederherstellen_triggered():
    Wenn der Benutzer Züge zurückgenommen hat, kann er das wieder rückgängig machen, also die zurückgenommen Züge wiederherstellen. Nur wenn er Züge zurückgenommen hat, ist der Menüpunkt aktiv. Wird über das SpielLabel gesteuert, das zum (De)aktivieren void erlaubeWiederherstellen(bool wert) benutzt.

Im Menü Einstellungen → Größe:
  • void on_actionKlein_triggered() , void on_actionMittel_triggered() und void on_actionGross_triggered():
    Legt die Spielfeldgröße fest. Wenn sie ausgewählt wird und sie sich von der aktuellen unterscheidet, wird im SpielLabel ein neues Spiel erzeugt.

Im Menü Einstellungen → Level:
  • void on_actionLevel5_triggered() bis void on_actionLevel1_triggered():
    Teilt dem SpielLabel den Level mit, so dass mit einem schlechteren Level beim Berechnen des Computerzugs günstige Kanten, mit dem der Computer ein Kästchen gewinnen kann, übersehen werden können.

Mit void on_actionMensch_startet_toggled(bool menschStarted) wird festgelegt, ob bei einem neuen Spiel der Mensch oder der Computer startet.

Das SpielLabel ist für die eigentliche Spieldurchführung verantwortlich:
  • Es erzeugt die Käsekästchen in void erzeugeSpielflaeche(Groesse groesse). Bei Größe → Mittel und Größe → Groß werden außerdem noch durch int zeichneWinkel(…) und int zeichneDiagonale(…) ausgegraute geometrische Figuren in die Spielfläche eingefügt.
  • Es berechnet den aktuellen Stand über void incStandMensch(bool inc), void incStandComputer(bool inc) und void aktualisiereStand(). Mit dem Argument bool inc wird festgelegt, ob inkrementiert oder dekrementiert werden soll (wenn der Benutzer Züge zurücknimmt, kann der Stand dekrementiert werden).
  • Es wertet die Klicks des Benutzers in die Kästchen aus und schließt die entsprechenden Kanten in void mousePressEvent(QMouseEvent* event). Dabei werden ungültige Kanten wie geschlossene oder Begrenzungen nicht akzeptiert.
  • Es lässt den Computer einen Zug berechnen in void computerZug(). Dabei geht der Computer in der folgenden Reihenfolge vor:
    1. Der einfachste Fall ist, dass ein Kästchen 3 geschlossene Seiten hat. Dann braucht der Computer nur noch die letzte offene Seite zu schließen.
    2. Gibt es ein Kästchen, bei dem höchstens eine Seite markiert ist? Dann markiere eine beliebige Seite zu einem Nachbarkästchen, bei dem höchstens eine Seite markiert ist.
      In diesem Beispiel kann er z. B. beim Kästchen 2 die linke oder die untere Seite schließen. Dann kann der Benutzer daraufhin kein Kästchen erbeuten. Wenn der Computer die rechte Seite schließen würde, würde dem Benutzer Kästchen 3 in die Hände fallen.
    3. Wähle ein Kästchen aus, bei dem zwei Seiten markiert sind, und markiere eine beliebige Seite, so dass der Mensch im darauffolgenden Zug möglichst wenig Kästchen schließen kann.
      In diesem Beispiel würde der Computer irgendeine Seite im rechten freien Bereich auswählen, weil in diesem Fall der Mensch nur 4 anstatt 6 Kästchen gewinnen kann.
    Bei 1. und 2. fallen dem Benutzer im darauffolgenden Zug keine Kästchen in die Hände.
    Wenn ein Spiellevel schlechter als 5 - Stark eingestellt ist, "übersieht" der Computer die Möglichkeiten, die es in 1. und 2. gibt, und zwar umso mehr, je schwächer der Level ist.
  • Es lässt den Benutzer Züge zurücknehmen und wiederherstellen über void rueckgaengig() und void wiederherstellen().
    Die Züge werden in der Membervariablen list<Zug*> zuege
  • (struct Zug ist auch eine Membervariable) gespeichert.
  • Es speichert und öffnet ein Spiel in void speichern(Groesse groesse, bool speichernUnter) und void oeffnen(). Dabei wird QFile statt ofstream und ifstream aus der Standardbibliothek benutzt, damit der Benutzer auch Umlaute und andere Sonderzeichen im Dateinamen benutzen kann.
Noch ein Hinweis zu Zählschleifen. Alle Welt schreibt for (int i = 0; i < 10; i++). Ich schreibe aber for (int i {0}; i != 10; ++i), weil das auch Bjarne Stroustrup in seinen C++ Büchern so macht. Das hat zwei Vorteile:
  1. Preinkrement ist schneller als Postinkrement, weil der alte Wert nicht gespeichert werden muss.
  2. Kann sein, dass der Test auf Ungleichheit schneller ist als der auf Strikt-Kleiner. (Wenn man bitweise von links auswertet, müsste es eigentlich genauso schnell sein?)

Hilfsklasse, um bequem auf die einzelnen Kästchen zugreifen zu können. Um z. B. auf das Kästchen in der zweiten Zeile und der 4. Spalte zugreifen zu können, kann ich einfach matrix[1][3] schreiben.

Intern werden die Zeilen mit den Kästchen aneinandergereiht und in einem vector<Kaestchen*> matrix gespeichert.

In der Verschiebezuweisung Matrix& operator=(Matrix&& m) werden die eventuell schon existierenden Kästchen in der aktuellen matrix gelöscht (so dass keine Speicherlecks entstehen), m.matrix in die aktuelle matrix verschoben und die Membervariablen anzahlZeilen und anzahlSpalten der aktuellen Instanz auf die von m gesetzt. Die Verschiebezuweisung wird in SpielLabel::erzeugeSpielflaeche(…) über matrix = std::move(Matrix{anzahlZeilen, anzahlSpalten})aufgerufen, wenn ein neues Spiel erzeugt wird. Beim Erzeugen einer Instanz von SpielLabel (passiert ein einziges Mal beim Programmstart) wird zunächst eine Membervariable Matrix matrix mit 0 Zeilen und Spalten angelegt, und in der Verschiebezuweisung wird eine nichtleere Matrix erzeugt.

Um über alle Kästchen in der Art von for (Kaestchen* kaestchen: matrix) zu iterieren, habe ich Kaestchen** begin() und Kaestchen** end() definiert.

Stellt ein einzelnes Kästchen dar.
  • Im Konstruktor werden u. a. das linke und das obere benachbarte Kästchen übergeben, sofern sie vorhanden sind. Für die 4 Kanten (Seiten) werden neue Instanzen von Seite erzeugt oder eine schon vorhandene des benachbarten Kästchens genommen.
  • Die Methode bool markiereSeite(int x, int y, Position *seitenPosition) wird aufgerufen, wenn der Benutzer in das Kästchen hineingeklickt hat und herausgefunden werden soll, welche Position (oben, rechts, unten, links) er angeklickt hat. Wenn er die Seite nicht anklicken darf, weil sie schon markiert ist oder sie eine Begrenzung darstellt, wird false zurückgegeben. In diesem Fall kann SpielLabel rot blinken und den Benutzer auf seinen Fehler hinweisen.
  • In void zeichneGeschlossen(bool mensch) werden das Smiley oder der Bildschirm gezeichnet, wenn der Benutzer oder der Computer ein Kästchen schließt.
  • Wenn der Benutzer Züge zurücknimmt, wird void setzeZurueck(bool mensch) benötigt. Das Argument dient dazu, um den Spielstand beim richtigen Spielpartner zu dekrementieren.
  • Wenn der Benutzer ein Spiel speichert, wird für jedes Kästchen char status() const aufgerufen. Es fasst alle Zustände des Kästchens in 8 Bits zusammen, und der Wert wird in der Datei gespeichert.
    Umgekehrt wird void setzeVomStatus(char status) aufgerufen, um den Zustand eines Kästchens zu setzen, wenn der Benutzer ein gespeichertes Spiel lädt.

Enthält sämtliche Seiten (Kanten) des Spiels, auch den Umriss und die ausgegrauten bei Größe → Mittel und Größe → Groß. Zwei benachbarte Kästchen teilen sich eine Seite.
Mit void zeichneLinie(Qt::GlobalColor farbe, int breite = duenneBreite) kann die Seite sich selber zeichnen.