Schlangenspiel in C ++

359833
Funky

Dies ist meine Version des Snake-Spiels in C ++. Wie könnte es verbessert werden und welche allgemeinen Hinweise wären für zukünftige Projekte nützlich?

#include <iostream>
#include <conio.h>

void run();
void printMap();
void initMap();
void move(int dx, int dy);
void update();
void changeDirection(char key);
void clearScreen();
void generateFood();

char getMapValue(int value);

// Map dimensions
const int mapwidth = 20;
const int mapheight = 20;

const int size = mapwidth * mapheight;

// The tile values for the map
int map[size];

// Snake head details
int headxpos;
int headypos;
int direction;

// Amount of food the snake has (How long the body is)
int food = 3;

// Determine if game is running
bool running;

int main()
{
    run();
    return 0;
}

// Main game function
void run()
{
    // Initialize the map
    initMap();
    running = true;
    while (running) {
        // If a key is pressed
        if (kbhit()) {
            // Change to direction determined by key pressed
            changeDirection(getch());
        }
        // Upate the map
        update();

        // Clear the screen
        clearScreen();

        // Print the map
        printMap();

        // wait 0.5 seconds
        _sleep(500);
    }

    // Print out game over text
    std::cout << "\t\t!!!Game over!" << std::endl << "\t\tYour score is: " << food;

    // Stop console from closing instantly
    std::cin.ignore();
}

// Changes snake direction from input
void changeDirection(char key) {
    /*
      W
    A + D
      S

      1
    4 + 2
      3
    */
    switch (key) {
    case 'w':
        if (direction != 2) direction = 0;
        break;
    case 'd':
        if (direction != 3) direction = 1;
        break;
    case 's':
        if (direction != 4) direction = 2;
        break;
    case 'a':
        if (direction != 5) direction = 3;
        break;
    }
}

// Moves snake head to new location
void move(int dx, int dy) {
    // determine new head position
    int newx = headxpos + dx;
    int newy = headypos + dy;

    // Check if there is food at location
    if (map[newx + newy * mapwidth] == -2) {
        // Increase food value (body length)
        food++;

        // Generate new food on map
        generateFood();
    }

    // Check location is free
    else if (map[newx + newy * mapwidth] != 0) {
        running = false;
    }

    // Move head to new location
    headxpos = newx;
    headypos = newy;
    map[headxpos + headypos * mapwidth] = food + 1;

}

// Clears screen
void clearScreen() {
    // Clear the screen
    system("cls");
}

// Generates new food on map
void generateFood() {
    int x = 0;
    int y = 0;
    do {
        // Generate random x and y values within the map
        x = rand() % (mapwidth - 2) + 1;
        y = rand() % (mapheight - 2) + 1;

        // If location is not free try again
    } while (map[x + y * mapwidth] != 0);

    // Place new food
    map[x + y * mapwidth] = -2;
}

// Updates the map
void update() {
    // Move in direction indicated
    switch (direction) {
    case 0: move(-1, 0);
        break;
    case 1: move(0, 1);
        break;
    case 2: move(1, 0);
        break;
    case 3: move(0, -1);
        break;
    }

    // Reduce snake values on map by 1
    for (int i = 0; i < size; i++) {
        if (map[i] > 0) map[i]--;
    }
}

// Initializes map
void initMap()
{
    // Places the initual head location in middle of map
    headxpos = mapwidth / 2;
    headypos = mapheight / 2;
    map[headxpos + headypos * mapwidth] = 1;

    // Places top and bottom walls 
    for (int x = 0; x < mapwidth; ++x) {
        map[x] = -1;
        map[x + (mapheight - 1) * mapwidth] = -1;
    }

    // Places left and right walls
    for (int y = 0; y < mapheight; y++) {
        map[0 + y * mapwidth] = -1;
        map[(mapwidth - 1) + y * mapwidth] = -1;
    }

    // Generates first food
    generateFood();
}

// Prints the map to console
void printMap()
{
    for (int x = 0; x < mapwidth; ++x) {
        for (int y = 0; y < mapheight; ++y) {
            // Prints the value at current x,y location
            std::cout << getMapValue(map[x + y * mapwidth]);
        }
        // Ends the line for next x value
        std::cout << std::endl;
    }
}

// Returns graphical character for display from map value
char getMapValue(int value)
{
    // Returns a part of snake body
    if (value > 0) return 'o';

    switch (value) {
        // Return wall
    case -1: return 'X';
        // Return food
    case -2: return 'O';
    }
}
Antworten
37
Vielleicht hättest du es auch Cross-Plateforms machen können Nicolas Charvoz vor 5 Jahren 0
"enum" für "direction" anstelle von 1,2,3,4 und möglicherweise eine Nachschlagetabelle für "direction" -> "dx", "dy" Karthik T vor 5 Jahren 1
Fügen Sie ein emscripten-Build hinzu und hosten Sie es auf Github-Seiten oder jsfiddle zum Spielen. wtjones vor 5 Jahren 0
Zu Ihrer Information: [interessante verwandte Antwort zu Stack Overflow] (http://stackoverflow.com/questions/18263708/how-to-make-the-snake-bite) rolfl vor 5 Jahren 1
Dein Spiel ist großartig, ich lerne daraus :) Damjan Pavlica vor einem Jahr 0

2 Antworten auf die Frage

30
Yann

Wenn Sie nicht wissen, wie lange die Spielschleife auf jedem Computer sleepdauert, ist es im Allgemeinen eine schlechte Übung, wenn Sie Ihre Konstante ändern. Wenn Sie wissen, dass Sie 2 Bilder pro Sekunde benötigen, ist es eine gute Möglichkeit, diese Linie aufrecht zu erhalten, indem Sie die Zeit am Anfang der Spielschleife ermitteln und am Ende den Unterschied herausfinden, um die Zeit zu berechnen, die Sie für den Schlaf benötigen um den Schritt gleich zu halten. Wenn die Schleife beispielsweise 0,1 Sekunden benötigt und Sie 2 Bilder pro Sekunde benötigen, setzen Sie den Schlaf von 0,4 Sekunden ein.

Abgesehen davon würde ich vielleicht sagen, dass Sie eine andere Variable neben der, fooddie snakeLengthoder etwas ist. Ich weiß nicht, ob Sie die Partitur auf dem Bildschirm ausdrucken, aber wenn Sie die Partitur nachverfolgen, kann ich mir vorstellen, dass Sie bei 0 und nicht bei 3 beginnen möchten Es ist kein großes Geschäft, wenn Sie die Lesbarkeit verbessern.

directionErwägen Sie möglicherweise eine Aufzählung mit UP, DOWN, LINKS und RECHTS, da es im Moment etwas schwierig ist, zu folgen, und Sie müssen nicht allzu viel an der Logik ändern, da es sich bei den Enumerationen um inteiniges mehr handelt, also Sie kann leicht auf die Art und Weise vergleichen, die Sie jetzt tun. Ich bin mir jedoch nicht sicher, ob ich dem, was Ihre directionWerte betreffen, folgen kann, da ich nirgendwo etwas sehe, directiondas auf 5 eingestellt ist, so dass eine Überprüfung unnötig erscheint.

In Ihrer generateFoodFunktion greifen Sie mapdirekt auf die Stelle zu, an der Sie eine Funktion erstellt haben, die genau diese Aufgabe erfüllt. getMapValueDaher möchten Sie vielleicht die Verwendung dieser Funktion in Betracht ziehen, da Sie sich zu einem späteren Zeitpunkt für eine MapKlasse entscheiden können. Beim Zugriff auf private Variablen treten Fehler auf (ich hoffe es!).

Abgesehen davon scheinen die Dinge ziemlich gut zu sein, also werde ich mit dem Picking beginnen: P. Ich würde nur Kleinigkeiten wie das Alphabetisieren Ihrer #includes- und Funktionsprototypen vorschlagen . Es ist kein so großer Deal, seit Sie 2 haben, aber etwas zu bedenken. Darüber hinaus fühlt sich Ihr clearScreen()und printMap()fühlt sich sehr wie ein Draw(), so dass Sie beide möglicherweise in diese Funktion einwickeln und einfach aufrufen initkönnen update, drawund cleanup(wenn Sie Objekte laden und Zeiger und was nicht verwenden), da Sie dies zu tun scheinen beinahe dem Muster der Spielschleife folgen (nebenbei gesagt, wenn Sie vorhaben, mehr Spiele zu erstellen, das gesamte Buch zu lesen, ist es eine schöne Sache) und wenn Sie diesen Artikel lesen, wird mein Standpunkt besser erklärt sleep.

22
glampert

Diese Überprüfung bezieht sich hauptsächlich auf Verbesserungen des Codestils und der allgemeinen Codequalität.

OOP:

Die erste Überlegung ist, dass wir für ein C ++ - Programm einige objektorientierte Programmierung (OOP) erwarten . Ihr Programm ist im Wesentlichen eine strukturierte Programmierung, die C und nicht C ++ ähnelt.

Sie sollten damit beginnen, den Code in einige Klassen umzuwandeln. Einige Klassen mögen SnakeGame, Board/ Mapund Foodkommen mir in den Sinn.

Globale Variablen:

Da Sie keine Klassen verwendet haben, sind in der Datei einige globale Variablen enthalten. Globale Werte sollten in den meisten Fällen vermieden werden. In Ihrem Spiel hätten Sie diese Variablen als Argumente an die Funktionen übergeben können.

Angenommen, Sie möchten bei den Globalen bleiben. Wenn ja, sollten Sie dennoch versuchen, den Umfang zu minimieren. Wenn die Variablen außerhalb der Datei, in der sie deklariert wurden, nicht benötigt werden, sollten Sie sie in einen unbenannten Namespace einschließen, um die Variablendatei festzulegen. So was:

namespace 
{
    // The tile values for the map
    int map[size];

    // Snake head details
    int headxpos;
    int headypos;
    int direction;

    ...
} // namespace

Auf diese Variablen kann jetzt nicht außerhalb der Datei zugegriffen werden, in der sie deklariert wurden. Dies verringert die Wahrscheinlichkeit unerwünschter Zustandsänderungen durch andere Module und macht das Debuggen etwas einfacher.

Es ist auch vorzuziehen, globale Variablen immer mit einem sicheren Standardwert zu initialisieren. Diese Variablen:

int headxpos;
int headypos;
int direction;
bool running;

Sollte zu etwas initialisieren.

Benennung von Leckerbissen:

Eine bevorzugte Namenskonvention für Konstanten wie mapwidthund mapheight, ist ALL_UPPERCASE. Dies trägt zur besseren Lesbarkeit bei, da klar von veränderlichen Variablen zu Konstanten zur Kompilierzeit unterschieden wird.

const int MAP_WIDTH      = 20;
const int MAP_HEIGHT     = 20;
const int TOTAL_MAP_SIZE = MAP_WIDTH * MAP_HEIGHT;

Ist TOTAL_MAP_SIZEauch beschreibender als nur size.

Verschiedenes:

Sie können die Funktionsprototypen vermeiden, indem Sie einfach main()am Ende der Datei platzieren.

getMapValue()könnte vereinfacht werden, indem Sie die Option durch ifeinen Standardfall im Schalter ersetzen :

char getMapValue(int value)
{
    switch (value) {
    case -1 : return 'X'; // Return wall
    case -2 : return 'O'; // Return food
    default : return 'o'; // Returns a part of snake body
    }
}

Portabilität:

conio.hist leider nur Windows, daher kann dieser Code unter anderen Betriebssystemen nicht kompiliert werden. Ich weiß nicht, von jedem einfachen Ersatz kbhit()und getch()doch _sleep() kann ersetzt werden durch std::this_thread::sleep_for()in C ++ 11.

system("cls") ist auch nicht Teil des C ++ - Standards und daher nicht portierbar.

Nur meine zwei Cent, ich sehe eine Menge OOP an meinem Arbeitsplatz, wo es nicht hingehört. Ehrlich gesagt denke ich, dass die Leute zuerst lernen sollten zu programmieren und dann OOP zu lernen, also würde ich diesen Vorschlag viel niedriger einschätzen als Sie. Davon abgesehen, sind Sie nicht falsch, außer wenn Sie behaupten, dass alle C ++ OOP benötigen oder erwarten. Poik vor 5 Jahren 5
@Poik, ich behaupte auf keinen Fall, dass alle C ++ - Anforderungen die Verwendung von OOP erwarten. Deshalb habe ich den Satz verwendet: "Wir erwarten, einige ** objektorientierte Programmierung zu sehen", da dies das wichtigste von der Sprache unterstützte Paradigma ist. In dem hier vorgestellten Code würde OOP tatsächlich eine bessere Lösung bieten. Genauso wie reine Funktionsprogrammierung. OOP sollte verwendet werden, wenn es einen Vorteil bietet, nicht nur weil es der "richtige Weg" ist, Dinge zu tun. glampert vor 5 Jahren 0
Ich verstehe das Anti-Oop / pro-prozedurale Argument nicht. Aus architektonischer Sicht ist eine Klasse (oder wie auch immer Ihre Sprache es nennen möchte) nur eine sofortige Version dessen, was Sie bereits schreiben. (Die Globalen Ihrer Datei werden zu Mitgliedsvariablen und Funktionen zu Methoden.) Der größte Unterschied besteht darin, dass OOP flexibler ist, da Schnittstellen * zur Laufzeit * definiert und erfüllt werden können, was beispielsweise den Austausch von * sauberer * Laufzeitimplementierung oder das Sammeln von Sammlungen nach Schnittstellen anstelle des Speicherlayouts ermöglicht. weberc2 vor 5 Jahren 2
@Poik Tut mir leid, dass ich es in C ++ markiert habe. Ich habe gerade diesen Beitrag [Link] (http://meta.codereview.stackexchange.com/questions/2581/tagging-c-vs-c) aufgerufen, wobei die oberste Antwort "if Eine Person schreibt den Code in einem C-Stil und kompiliert ihn mit C ++, dann ist es eine C ++ - Frage ". Allerdings sehe ich, dass der OOP den Code viel lesbarer machen würde und ich danke Ihnen für den Tipp :). Funky vor 5 Jahren 0
@TheFailure, Keine Notwendigkeit, sich zu entschuldigen, deine Frage ist sehr legitim. Wenn Sie sich dazu entschließen, den Unterricht mithilfe von Kursen zu verbessern, senden Sie bitte eine Folgefrage, die wir gerne prüfen werden! ;) glampert vor 5 Jahren 0
Diese Antwort war auch sehr hilfreich, kann jedoch nur eine akzeptieren :( Funky vor 5 Jahren 0
@ weberc2: Tut mir leid, ich war etwas hart im Wortlaut, aber ich bin keinesfalls Anti-OOP. Ich bin nur ein bisschen müde, Klassen mit einer Funktion und nichts anderem oder schlimmerem zu sehen (ich werde diesen Gedanken behalten). Aus architektonischer Sicht sollten Sie OOP nur dann verwenden, wenn Sie OOP verwenden (oder nur üben). Außerdem bietet C ++ mehr als nur OOP. Es ist ein Paradigma, keine Religion. TheFailure, Sie haben nichts falsch gemacht, und mit C ++ haben Sie die Möglichkeit, zu OOP zu wechseln, was gut zum Game-Design passt. Tut mir leid, dass ich auf den Knie reagiert habe, es war hier nicht angesagt, ich dachte nur, dass OOP weniger Nachdruck brauchte. Poik vor 5 Jahren 0
@Poik keine Sorgen. Ich habe mich nicht beleidigt, und ich habe Sie mit meinem Kommentar nicht besonders ins Visier genommen; Ich habe eine Menge Leute (C-Programmierer) über OOP meckern hören. Ich habe wirklich Unglauben ausgedrückt, weil es meinen Erfahrungen als Polyglott-Programmierer zuwiderläuft. Ich bin auch nicht über OOP religiös, aber zwischen OOP und Verfahrensstilen scheint OOP der klare Gewinner in Bezug auf Wiederverwendung und Ausdruckskraft zu sein. Das heißt, ich habe nicht viel Erfahrung mit der prozeduralen Programmierung, deshalb bin ich interessiert, das Gegenargument zu hören. weberc2 vor 5 Jahren 0
@ weberc2 Dies ist leider nicht das Forum dafür, aber der größte Teil meines Knicks besteht in erster Linie darin, nicht wiederverwendbaren OOP-Code von Leuten zu erben, die das Paradigma nicht verstehen, aber die es nur verwenden, weil ihnen immer gesagt wurde, dass sie dies tun sollen . Das und als High Performance Computing-Forscher neige ich dazu, viel Prozedurcodierung zu verwenden, da die meisten Dinge zumindest anfangs ad hoc sind. Wenn ich mich als nützlich erwiesen habe, versuche ich es herauszufiltern oder zu entscheiden, was mit ihm passieren wird. Wenn ich es anders herum mache, wäre das viel mehr Arbeit für mich, aber wie immer ist Ihre Laufleistung unterschiedlich. Poik vor 5 Jahren 2
@Poik Ich weiß, das ist nicht das Forum dafür; Ich habe es nicht als offizielle Frage gestellt. Ich hielt es nicht für wesentlich mehr Off-Topic als für Ihren Beitrag. Auf jeden Fall sehe ich eine Menge nicht wiederverwendbarer OOP-Codes, aber auch viele nicht wiederverwendbare Verfahrenscodes. Ich denke, dass schlechte Programmierer ungeachtet von Paradigmen schlechten Code produzieren werden, aber meine kleine Erfahrung lässt vermuten, dass OOP Ihnen bessere Werkzeuge für die Wiederverwendung von Code bietet. Ich kann verstehen, warum Sie aufgrund Ihrer hohen Anforderungen weniger auf Abstraktion achten und OOP für Sie weniger nützlich machen. Mein Feld profitiert mehr von der schnellen Entwicklung und damit von OOP. weberc2 vor 5 Jahren 0