Online Highscore mit C++ und PHP

Drucke diesen Post This page as PDF Posted by fkrauthan | Posted in C++, Linux, PHP, Sockets, WinAPI, Windows | Posted on 09-12-2009

Tags: , , , , , , , , , , ,

1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading ... Loading ...

0

Inhalt

Einführung
Eine kleine Online Highscore kann man in vielen Spielen gebrauchen. Denkt man nur an ein kleines Tetris oder ein simples Geschicklichkeitsspiel. Doch man findet im Internet kaum wie man so eine Highscore machen kann. Dabei geht das total simpel. Alles was man braucht ist ein C/C++ Programm (euer Spiel) und einen Webspace auf dem PHP läuft und schon kann man sich eine kleine, feine Highscore basteln, die für die meisten Spiele schon ausreicht. Natürlich kann man auch MySQL oder eine andere Datenbank auf der Serverseite verwenden um seine Ergebnisse zu verwalten, doch in diesem Tutorial werden wir eine simple Textdatei verwenden. Der Code wird so wie wir ihn schreiben werden auf Linux, Windows und Mac ohne Probleme compilieren (zumindest sind mir keine Fehler auf den 3 Plattformen bekannt)

Voraussetzungen
Um dieses Tutorial gut durcharbeiten zu können benötigst du Grundkenntnisse in C++, solltest einen Apache Server mit PHP aufsetzen können (oder einfach unter Windows xampp benutzen) und ein wenig PHP können. Wobei letzteres nur von Nöten ist, um die PHP Scripte komplett zu verstehen. Die wichtigsten Teile werden auch erklärt.

Was brauchen wir?
Wir werden hier kein Spiel programmieren, sondern nur ein kleines Programm das Punktzahl und Benutzernamen vom User abfragt und diese dann in die Highscore einträgt. Und eine Funktion um die Top10 aus der Highscore wieder auszulesen. Das sollte fürs erste genügen. Erweiterungen bleiben dem Leser vorbehalten.. Voraussetzung sind Sockets auf dem Client und PHP auf dem Server. Also stürzen wir uns mal auf den PHP Teil:

Die Highscore Server seitig auslesen
Das Auslesen an sich wird das leichteste sein. Wir lesen einfach unserer Textdatei in der die Highscore steht aus und geben sie an den Server zurück. Hier mal der Code, Erklärungen folgen später.

Code: PHP | Plain Text
  1. <?php
  2.     //Prüfen ob ID angegeben ist
  3.     if(!isset($_GET["id"])) {
  4.         //Sollte keine ID übergeben worden sein
  5.         echo "ERROR\n Keine Spiel ID uebergeben.\n";
  6.     }
  7.     else {
  8.         $id = $_GET["id"];
  9.  
  10.         //Prüfen ob eine Highscore Exestiert
  11.         if(!file_exists("./data/hs_".$id.".map")) {
  12.             //Sollte keine Highscore Exestieren
  13.             echo "ERROR\n Fuer dieses Spiel Exestiert keine Online Highscore.\n";
  14.         }
  15.         else {
  16.             //Laden der Highscore
  17.             $fp = fopen("./data/hs_".$id.".map", "r");
  18.             if(!$fp) {
  19.                 //Sollte er die Datei nicht öffnen können
  20.                 echo "ERROR\n Auf dem Server konnte die Highscore liste nicht geöffnet werden.\n";
  21.             }
  22.             else {
  23.                 //Das Sende OK senden
  24.                 echo "STARTSENDINGDATA\n";
  25.  
  26.                 //Alles einlesen
  27.                 while($line=fgets($fp)) {
  28.                     if(trim($line) != "") {
  29.                         echo trim($line)."\n";
  30.                     }
  31.                 }
  32.                 fclose($fp);
  33.  
  34.                 //Sagen das man fertig mit Senden ist
  35.                 echo "ENDSENDINGDATA\n";
  36.             }
  37.         }
  38.     }
  39.  
  40. ?>
  41.  
  42. ENDPHPDATA

So im Grunde ist das ganze sehr einfach. Er überprüft ob es eine Highscore Datei gibt. Wenn dies der Fall ist, dann sendet er STARTSENDINGDATA und schickt dann zeilenweise die Einträge. Wenn alle Daten gesendet wurden, schickt er ENDSENDINGDATA damit wir im C++ Programm später wissen wann alle Highscoreeinträge übermittelt sind. Zu guter Letzt geben wir einfach noch ein ENDPHPDATA aus damit wir wissen nun ist alles gesendet und wir mit dem Lesen aufhören können. Das war auch schon das lese Script. Man sollte noch beachten das wir eine Game ID erwarten. Diese Game ID wird dafür sein, dass wir den selben Script auch ohne Problem für mehrere Spiele verwenden können. Dazu übergeben wir beim abfragen und später beim eintragen einfach eine andere Game ID.

Das schreiben eines neuen Highscoreeintrages
Nun brauchen wir noch einen zweites Script. Dieser Script übernimmt das schreiben in die Highscoredatei und überprüft dabei ob es ein gültiger Eintrag ist oder ob einer versucht diesen zu fälschen. Dabei verwenden wir ein simples Prüfsummen verfahren. Dies bietet zwar nicht unbedingt den besten Schutz, sollte aber voll und ganz für kleine Spiele ausreichen. So wie immer zu erst der ganze Code:

Code: PHP | Plain Text
  1. <?php
  2.     function IsNewHighscore($points, $tmpHSArray, $maxHighscore) {
  3.         if($points<=0)
  4.             return false;
  5.    
  6.         $i=0;
  7.         $found=false;
  8.         for($i=0; $i<count($tmpHSArray);$i++) {
  9.             $tmpArray = explode(" ", $tmpHSArray[$i]);
  10.             if($tmpArray[1] < $points)
  11.                 $found=true;
  12.             else if($tmpArray[1]==$points)
  13.                 return false;
  14.         }
  15.    
  16.         if($i>=$maxHighscore&&$found==false)
  17.             return false;
  18.    
  19.         if($found==true||$i<$maxHighscore)
  20.             return true;
  21.    
  22.         return false;
  23.     }
  24.  
  25.     function SortHighScore($sort_array,$reverse) {
  26.         for ($i = 0; $i < sizeof($sort_array); $i++){
  27.             for ($j = $i + 1; $j < sizeof($sort_array); $j++){
  28.                 if($reverse){
  29.                     $tmpArray1 = explode(" ", $sort_array[$i]);
  30.                     $tmpArray2 = explode(" ", $sort_array[$j]);
  31.                     if ($tmpArray1[1] < $tmpArray2[1]){
  32.                         $tmp = $sort_array[$i];
  33.                         $sort_array[$i] = $sort_array[$j];
  34.                         $sort_array[$j] = $tmp;
  35.                     }
  36.                     } else {
  37.                     $tmpArray1 = explode(" ", $sort_array[$i]);
  38.                     $tmpArray2 = explode(" ", $sort_array[$j]);
  39.  
  40.                     if ($tmpArray1[1] > $tmpArray2[1]){
  41.                         $tmp = $sort_array[$i];
  42.                         $sort_array[$i] = $sort_array[$j];
  43.                         $sort_array[$j] = $tmp;
  44.                     }
  45.                     }
  46.              }
  47.         }
  48.  
  49.         return $sort_array;
  50.     }
  51. ?>
  52.  
  53. <?php
  54.     if(!isset($_GET["id"])) {
  55.         //Sollte keine ID übergeben worden sein
  56.         echo "ERROR\n Keine Spiel ID uebergeben.\n";
  57.     }
  58.     else if(!isset($_GET["name"])) {
  59.         //Sollte kein Name übergeben worden sein
  60.         echo "ERROR\n Kein Spieler Name uebergeben.\n";
  61.     }
  62.     else if(!isset($_GET["points"])) {
  63.         //Sollte keine Punkte übergeben worden sein
  64.         echo "ERROR\n Keine Spieler Punkte uebergeben.\n";
  65.     }
  66.     else if(!isset($_GET["time"])) {
  67.         //Sollte keine Zeit übergeben worden sein
  68.         echo "ERROR\n Keine Spiel Zeit uebergeben.\n";
  69.     }
  70.     else if(!isset($_GET["version"])) {
  71.         //Sollte keine version übergeben worden sein
  72.         echo "ERROR\n Keine Spiel Version uebergeben.\n";
  73.     }
  74.     else if(!isset($_GET["maxentrys"])) {
  75.         //Sollte keine Mximale Anzahl an Einträgen übergeben worden sein
  76.         echo "ERROR\n Keine Maximale Highscore Eintraege uebergeben.\n";
  77.     }
  78.     else if(!isset($_GET["prf"])) {
  79.         //Sollte keine Prüfsumme übergeben worden sein
  80.         echo "ERROR\n Keine Spiel pruefsumme uebergeben.\n";
  81.     }
  82.     else {
  83.         //Werte in Variablen schreiben
  84.         $id         = $_GET["id"];
  85.         $name       = $_GET["name"];
  86.         $points     = $_GET["points"];
  87.         $time       = $_GET["time"];
  88.         $version    = $_GET["version"];
  89.         $maxHSEntys = $_GET["maxentrys"];
  90.         $prf        = $_GET["prf"];
  91.  
  92.         $lokalprf = $id.($points*$time-strlen($name)+strlen($version)*($time+$points)-$maxHSEntys);
  93.         //Prüfen ob eine Highscore Exestiert
  94.         if(!file_exists("./data/hs_".$id.".map")) {
  95.             //Sollte keine Highscore Exestieren
  96.             echo "ERROR\n Fuer dieses Spiel Exestiert keine Online Highscore.\n";
  97.         }
  98.         else if($lokalprf!=$prf) {
  99.             //Sollte die Prüfsumme nicht stimmen
  100.             echo "ERROR\n Die Preufsumme stimmt nicht.\n";
  101.         }
  102.         else {
  103.             //Liste Laden und schaun ob wirklich neu
  104.             $fp = fopen("./data/hs_".$id.".map", "r");
  105.             if(!$fp) {
  106.                 //Sollte er die Datei nicht öffnen können
  107.                 echo "ERROR\n Auf dem Server konnte die Highscore liste nicht geöffnet werden.\n";
  108.             }
  109.             else {
  110.                 $tmpArray = array();
  111.                 while($line=fgets($fp)) {
  112.                     if(trim($line) != "") {
  113.                         array_push($tmpArray, trim($line));
  114.                     }
  115.                 }
  116.                 fclose($fp);
  117.  
  118.                 array_push($tmpArray, str_replace(' ', '_', $name) . " " . $points . " " . $time . " " . $version);
  119.                    
  120.                 $tmpArray = SortHighScore($tmpArray, 1);
  121.                 $fp = fopen("./data/hs_".$id.".map", "w");
  122.                 if(!$fp) {
  123.                     //Sollte er die Datei nicht zum schreiben öffnen können
  124.                     echo "ERROR\n Auf dem Server konnte die Highscore liste nicht beschrieben werden.\n";
  125.                 }
  126.                 else {
  127.                     $max = count($tmpArray);
  128.                     if($max>$maxHSEntys) $max = $maxHSEntys;
  129.  
  130.                     for($i=0; $i<$max;$i++) {
  131.                         fputs($fp, $tmpArray[$i]."\n");
  132.                     }
  133.                     fclose($fp);
  134.                 }
  135.             }
  136.         }
  137.     }
  138. ?>
  139.  
  140. ENDPHPDATA

Gut wie man sieht ist diese Datei deutlich umfangreicher. Zu aller erst definieren wir eine Funktion. Die nennt sich IsNewHighscore. Dabei wird einfach unsere Highscore datei gelesen und bei jedem eintrag geschaut ob der schlechter als unserer ist. Wenn dies der fall sein sollte dann gibt sie true zurück ansonsten gibt sie false zurück, was heißt das der Eintrag zu schlecht für die Highscore ist.

Dann definieren wir eine 2. Funktion die sich SortHighScore nennt. Diese übernimmt die Sortierung unserer Highscore. Hier müsst ihr nicht alles verstehen. Am Ende werde ich erklären wie eine Zeile in der Highscore Datei aussieht.

Dann kommt der Hauptblock dieses Scripts. Zu aller erst prüfen wir ob alle Parameter übergeben sind. Sollte dies nicht der Fall sein geben wir einen Fehler zurück und setzen ein ERROR davor, damit unser C++ Programm später wieder weiß, das nun ein Fehler kommt.

Nun wird eine Zeile für uns sehr wichtig.
$lokalprf = $id.($points*$time-strlen($name)+strlen($version)*($time+$points)-$maxHSEntys);
Hier berechnen wir nämlich unserere Prüfsumme. Die Variablen namen sollten klar sein wozu diese sind. Sie fangen mit einem $ an. Nun gibt es für die Prüfsummen berechnen sehr gut eine funktion die man im grunde immer braucht. strlen. Diese funktion gibt einem die Stringlänge eines übergebenen Strings zurück. Diese Prüfsummen Berechnung sollte jeder anderst machen, wobei man immer jeden Wert der dem Script übergeben wird mindestens einmal in die Prüfsumme verarbeiten sollte.

Nun laden wir die Highscoredatei. Lesen alle Einträge wieder ein. Fügen unseren neuen Eintrag ein, nachdem dieser für gültig als neuer Eintrag befunden worden ist. Dies geschieht in dieser Zeile array_push($tmpArray, $name . ” ” . $points . ” ” . $time . ” ” . $version); Wie man sieht hat unsere Datei als Trennzeichen ein Leerzeichen. Das schränkt ein, dass wir als Namen keinen Namen mit Leerzeichen übergeben dürfen. Hier kann man aber auch andere Trennzeichen verwenden. Man muss dies dann nur in den Funktionen IsNewHighscore und SortHighScore ändern.

Dann sortieren wir noch unseren Array mit Highscoreeinträgen und schreiben die Maximalanzahl an Einträgen zurück in die Datei. Am ende des Scriptes steht wieder unser berühmtes ENDPHPDATA. Das wars auch schon. Nun haben wir den PHP Part komplet fertig besprochen. Also können wir uns nun auf den C++ Part stürzen.

Definition der Highscore Klasse
So nun zeige ich euch einfach mal den Header unserer Highscore Klasse, um super simpel eine Highscore umsetzen zu können.

Code: C++ | Plain Text
  1. /*
  2.  * Highscore.h
  3.  *
  4.  *  Created on: 03.07.2009
  5.  *      Author: fkrauthan
  6.  */
  7.  
  8. #ifndef HIGHSCORE_H_
  9. #define HIGHSCORE_H_
  10.  
  11. #include <string>
  12. #include <vector>
  13. #include <cstdlib>
  14.  
  15.  
  16. class CHighscore {
  17.     public:
  18.         //Ein Highscore Eintrag
  19.         struct HIGHSCORE_ENTRY {
  20.             //Der Name des Users
  21.             std::string sUser;
  22.             //Dem seine Punktzahl
  23.             int iPoints;
  24.             //Die Gespielte Zeit (ich empfehle in Sekunden)
  25.             int iGameTime;
  26.         };
  27.  
  28.         //Der Konstruktor um alle Daten zu initsalisieren die wir brauchen
  29.         CHighscore(const std::string& server, const std::string& getadress, const std::string& insertadress, const std::string& gameversion, int maxhighscoreentrys=20);
  30.         //Der Destruktor. Hier räumen wir auf sollte das Socket noch offen sein.
  31.         ~CHighscore();
  32.  
  33.         //Die Highscore auf dem Server lesen
  34.         bool ReadHighscore();
  35.         //Das Gelesene sich holen
  36.         std::vector<HIGHSCORE_ENTRY>& GetHighscore();
  37.  
  38.         //Einen neuen Eintrag auf den Server schicken
  39.         bool InsertHighscore(const std::string& user, int points, int gametime);
  40.     private:
  41.         //Kleine Hilfsfunktionen
  42.         bool ConnectToServer();
  43.         bool SendHeader(const std::string& url);
  44.         void DisconnectToServer();
  45.  
  46.         //Funktion zum senden der Daten an den Server
  47.         void SendAll(const char* const buf, const int size);
  48.  
  49.         //Funktion zum lesen einer Zeile vom Server
  50.         void GetLine(std::stringstream& line);
  51.  
  52.  
  53.         //Die Verbindungsdaten Speichern
  54.         std::string m_sServer;
  55.         std::string m_sGetAdress;
  56.         std::string m_sInsertAdress;
  57.         std::string m_sGameVersion;
  58.         int         m_iMaxHighscoreEntrys;
  59.  
  60.         //Der Temporäre Speicher der Highscore
  61.         std::vector<HIGHSCORE_ENTRY> m_vHighscoreEntrys;
  62.  
  63.         //Die ID des Sockets
  64.         int m_iSocket;
  65. };
  66.  
  67. #endif /* HIGHSCORE_H_ */

So das ganze sieht nun etwas größer aus als es in wirklichkeit ist. Aber ich denke das sollte alles selbst erklärend sein. Also machen wir uns schnell an den Konstruktor und Destruktor.

Code: C++ | Plain Text
  1. /*
  2.  * Highscore.cpp
  3.  *
  4.  *  Created on: 03.07.2009
  5.  *      Author: fkrauthan
  6.  */
  7.  
  8. #include "Highscore.h"
  9.  
  10. #ifdef linux
  11.     #include <sys/socket.h>
  12.     #include <arpa/inet.h>
  13.     #include <netdb.h>
  14.     #include <errno.h>
  15. #else
  16.     #include <winsock2.h>
  17. #endif
  18.  
  19. #include <sstream>
  20. #include <iostream>
  21. #include <cstdlib>
  22.  
  23. CHighscore::CHighscore(const std::string& server, const std::string& getadress, const std::string& insertadress, const std::string& gameversion, int maxhighscoreentrys) {
  24.     //Einfaches zuweisen der Variablen
  25.     m_sServer               = server;
  26.     m_sGetAdress            = getadress;
  27.     m_sInsertAdress         = insertadress;
  28.     m_sGameVersion          = gameversion;
  29.     m_iMaxHighscoreEntrys   = maxhighscoreentrys;
  30.  
  31.     //Die Socket ID ungültig setzen
  32.     m_iSocket = -1;
  33. }
  34.  
  35. CHighscore::~CHighscore() {
  36.     //Beim schliesen die evt. noch geöffnete verbindung wieder schliesen
  37.     DisconnectToServer();
  38. }

So im grunde passiert hier nichts spannedes. Am anfang includieren wir alle wichtigen Header für die Socket Verbindung wobei wir hier eine unterscheidung zwischen Windows und Linux machen müssen. Im Konstruktor setzen wir einfach alle Parameter und die Socket ID setzen wir auf -1 was ungültig bedeutet.

Eine Verbindung aufbauen
Nun müssen wir mal die Hilfsfunktionen ConnectToServer und DisconnectToServer anpacken.

Code: C++ | Plain Text
  1. bool CHighscore::ConnectToServer() {
  2.     //Verbindung schliesen sollte sie schon offen sein
  3.     DisconnectToServer();
  4.  
  5.  
  6.     #ifndef linux
  7.         WSADATA w;
  8.         if(int result = WSAStartup(MAKEWORD(2,2), &w) != 0) {
  9.             std::cout << "Winsock 2 konnte nicht gestartet werden! Error #" << result << std::endl;
  10.             return false;
  11.         }
  12.     #endif
  13.  
  14.     hostent* phe = gethostbyname(m_sServer.c_str());
  15.  
  16.     if(phe == NULL) {
  17.         std::cout << "Hostname konnte nicht aufgeloest werden!" << std::endl;
  18.         return false;
  19.     }
  20.  
  21.     if(phe->h_addrtype != AF_INET) {
  22.         std::cout << "Ungueltiger Adresstyp!" << std::endl;
  23.         return false;
  24.     }
  25.  
  26.     //Wir haben nur IP V4 unterstüzung
  27.     if(phe->h_length != 4) {
  28.         std::cout << "Ungueltiger IP-Typ!" << std::endl;
  29.         return false;
  30.     }
  31.  
  32.    m_iSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  33.     if(m_iSocket == -1) {
  34.         std::cout << "Socket konnte nicht erstellt werden!" << std::endl;
  35.         return false;
  36.     }
  37.  
  38.     sockaddr_in service;
  39.     service.sin_family = AF_INET;
  40.     service.sin_port = htons(80);
  41.  
  42.     char** p = phe->h_addr_list;
  43.     int result;
  44.     do {
  45.         if(*p == NULL) {
  46.             std::cout << "Verbindung fehlgeschlagen!" << std::endl;
  47.             return false;
  48.         }
  49.  
  50.         service.sin_addr.s_addr = *reinterpret_cast<unsigned long*>(*p);
  51.         ++p;
  52.         result = connect(m_iSocket, reinterpret_cast<sockaddr*>(&service), sizeof(service));
  53.     } while(result == -1);
  54.  
  55.     return true;
  56. }
  57.  
  58. void CHighscore::DisconnectToServer() {
  59.     if(m_iSocket==-1) return;
  60.  
  61.     #ifdef linux
  62.         close(m_iSocket);
  63.     #else
  64.         closesocket(m_iSocket);
  65.     #endif
  66. }

Wie man sieht gibt es bei der Socket programmierung nur kleine unterschiede zwischen Windows und Linux. Der Code macht nichts anderes als eine Socket verbindung zu unserem Server aufzubauen. Vorher schliesen wir natürlich noch evt schon offene verbindungen.

Das Senden des HTML Headers
Nun haben wir eine offene Socket Verbindung zum HTTP Server. Also müssen wir nun unseren HTTP Request schicken. Dies geht aber zum glück sehr leicht. Hier die Funktion:

Code: C++ | Plain Text
  1. bool CHighscore::SendHeader(const std::string& url) {
  2.     if(m_iSocket==-1) return false;
  3.  
  4.     std::string request = "GET ";
  5.     request.append(url.c_str());
  6.     request.append(" HTTP/1.1\r\nHost: ");
  7.     request.append(m_sServer.c_str());
  8.     request.append("\r\nConnection: close\r\n\r\n");
  9.  
  10.     SendAll(request.c_str(), request.size());
  11.  
  12.     int code = 100;
  13.     std::string Protokoll;
  14.     std::stringstream firstLine;
  15.     while(code == 100) {
  16.         GetLine(firstLine);
  17.         firstLine >> Protokoll;
  18.         firstLine >> code;
  19.         if(code == 100) {
  20.             GetLine(firstLine);
  21.         }
  22.  
  23.         if(code != 200) {
  24.             //Sollte der Status Code nicht 200 Sein für alles OK dann HTML Fehler Code ausgeben
  25.             firstLine.ignore();
  26.             std::string msg;
  27.             getline(firstLine, msg);
  28.             std::cout << "Error #" << code << " - " << msg << std::endl;
  29.             return false;
  30.         }
  31.     }
  32.  
  33.     return true;
  34. }

Wie man sieht ist diese Funktion auch sehr simpel aufgebaut. Einfach die GET abfrage aufbauen und an den Server schicken und dann die Antwort vom Server auslesen. Wobei hier geschaut wird ob im Header der Status code 200 vor kommt für alles OK. Sollte ein andere kommen wie 404 also keine Datei gefunden, so geben wir diesen Fehler Code aus und berechen das ganze ab.

SendAll und GetLine
Nun haben wir im Code vorher schon des öfteren die 2 Funktionen SendAll und GetLine benutzt. Hier folgt nun deren definition:

Code: C++ | Plain Text
  1. void CHighscore::SendAll(const char* const buf, const int size) {
  2.     if(m_iSocket==-1) return;
  3.  
  4.     int bytesSent = 0;
  5.     do {
  6.         int result = send(m_iSocket, buf + bytesSent, size - bytesSent, 0);
  7.         if(result < 0) {
  8.             break;
  9.         }
  10.         bytesSent += result;
  11.     } while(bytesSent < size);
  12. }
  13.  
  14. void CHighscore::GetLine(std::stringstream& line) {
  15.     if(m_iSocket==-1) return;
  16.  
  17.     for(char c; recv(m_iSocket, &c, 1, 0) > 0; line << c) {
  18.         if(c == '\n') {
  19.             return;
  20.         }
  21.     }
  22. }

Im grunde ganz einfach. Bei Getline lesen wir einfach Byte weise was uns der Server zurück gibt und schauen ab wann ein ‘\n’ also eine neue Zeile kommt. Sollte dies erreicht sein hören wir auf in den stringstream zu lesen.
Bei SendAll ist es im Grunde genau das gleiche nur das wir dabei jeden Byte den wir bekommen an unser Socket schicken und somit an den Server schicken.
War also doch garnicht so schwer oder? Nun haben wir ja auch schon einen großen Teil geschafft. Kommen wir also zum nächsten schritt. Dem empfangen der Highscore vom Server.

Empfangen der Highscore
Dies ist nun nachdem wir so viel vorarbeit geleistet haben im grunde ein Kinderspiel. Zuerst einmal der Code und dann folgt die erklärung.

Code: C++ | Plain Text
  1. bool CHighscore::ReadHighscore() {
  2.     //Zum Server verbinden
  3.     if(!ConnectToServer()) return false;
  4.  
  5.     //Die GetURL für die Highscore aufrufen
  6.     if(!SendHeader(m_sGetAdress)) return false;
  7.  
  8.     //Leeren der aktuellen Highscore
  9.     m_vHighscoreEntrys.clear();
  10.  
  11.     //Nun müssen wir noch die Daten bezüglich der Highscore auslesen
  12.     bool startData = false;
  13.     bool error = false;
  14.     int i = 0;
  15.     while(true) {
  16.         std::stringstream line;
  17.         try {
  18.             GetLine(line);
  19.  
  20.             if(line.str()=="ERROR") {
  21.                 //Wir haben einen Fehler. Wir wissen das in der nächsten Zeile die Meldung steht
  22.                 GetLine(line);
  23.                 std::cout << "Online Highscore Fehler: " << line.str() << std::endl;
  24.                 error = true;
  25.                 break;
  26.             }
  27.             else if(line.str()=="ENDPHPDATA") {
  28.                 //Das ende aller Daten wir können mit lesen aufhören
  29.                 break;
  30.             }
  31.             else if(line.str()=="STARTSENDINGDATA") {
  32.                 //Nun kommen Highscore daten
  33.                 startData = true;
  34.             }
  35.             else if(line.str()=="ENDSENDINGDATA") {
  36.                 //Nun enden die Highscore daten
  37.                 startData = false;
  38.             }
  39.             else if(startData){
  40.                 std::vector<std::string> tmpVector;
  41.  
  42.                 std::string buf;
  43.  
  44.                 while (line >> buf)
  45.                     tmpVector.push_back(buf);
  46.  
  47.                 if(i==m_iMaxHighscoreEntrys)
  48.                     break;
  49.  
  50.                 if(tmpVector.size()<3) {
  51.                     std::cout << "Ungueltiges Format empfangen. Ueberspringe dieses" << std::endl;
  52.                 }
  53.                 else {
  54.                     //Den neuen Eintrag bei uns einfügen
  55.                     i++;
  56.                     HIGHSCORE_ENTRY tmpEntry;
  57.                     tmpEntry.sUser      = tmpVector[0];
  58.                     tmpEntry.iPoints    = std::atoi(tmpVector[1].c_str());
  59.                     tmpEntry.iGameTime  = std::atoi(tmpVector[2].c_str());
  60.  
  61.                     m_vHighscoreEntrys.push_back(tmpEntry);
  62.                 }
  63.             }
  64.         } catch(std::exception& e) {
  65.             break;
  66.         }
  67.     }
  68.  
  69.     //Die Verbindung zum Server wieder schliesen
  70.     DisconnectToServer();
  71.  
  72.     return !error;
  73. }
  74.  
  75. std::vector<CHighscore::HIGHSCORE_ENTRY>& CHighscore::GetHighscore() {
  76.     return m_vHighscoreEntrys;
  77. }

So und schon haben wir in einer schönen Struktur unsere Highscore. War doch garnicht so schwer oder? Ich denke der Code ist selbst erklärend. Wir lesen einfach Zeile für Zeile ein. Schauen ob die Zeile eine Fehler Zeile ist wenn ja brechen wir ab und geben die Fehler meldung aus. Wenn nein lassen wir das ganze weiterlaufen und schauen wo unsere Daten anfangen und wo sie aufhören. Und zwischen drinnen lesen wir das ganze aus und Tragen es in unserer Struktur ein.

Neuen Eintrag senden
So einen neuen Eintrag senden wird im grunde genauso leicht sein wie die Highscore abzurufen. Auch hier wieder mal der Code zuerst.

Code: C++ | Plain Text
  1. bool CHighscore::InsertHighscore(const std::string& user, int points, int gametime) {
  2.     //Zuerst holen wir uns die Aktuelle liste um schauen zu können ob neuer eintrag
  3.     if(!ReadHighscore()) return false;
  4.  
  5.     //Nun prüfen wir ob wir einen neuen Eintrag haben
  6.     bool newentry = false;
  7.     if((int)m_vHighscoreEntrys.size()<m_iMaxHighscoreEntrys) newentry = true;
  8.     else {
  9.         for(int i=0;i<(int)m_vHighscoreEntrys.size();i++) {
  10.             if(m_vHighscoreEntrys[i].iPoints<points) {
  11.                 newentry = true;
  12.                 break;
  13.             }
  14.         }
  15.     }
  16.  
  17.     //Es ist kein neuer Eintrag
  18.     if(!newentry) {
  19.         std::cout << "Kein neuer Eintrag\n";
  20.         return false;
  21.     }
  22.  
  23.     //Nun stellen wir die Eintrags URL zusammen
  24.     std::stringstream url;
  25.     url << m_sInsertAdress << "&name=" << user << "&points=" << points << "&time=" << gametime;
  26.  
  27.     //Nun berechnen wir unsere Prüfsumme. Diese muss genauso berechent werden wie im PHP Script
  28.     url << "&prf=mygame" << (points*gametime-user.size()+3*(gametime+points)-m_iMaxHighscoreEntrys);
  29.  
  30.     //Nun Schicken wir die neuen Daten an den Server
  31.     if(!ConnectToServer()) return false;
  32.     if(!SendHeader(url.str())) return false;
  33.  
  34.     //Nun nur noch schauen ob es fehler gibt
  35.     bool error = false;
  36.     while(true) {
  37.         std::stringstream line;
  38.         try {
  39.             GetLine(line);
  40.  
  41.             if(line.str()=="ERROR") {
  42.                 //Wir haben einen Fehler. Wir wissen das in der nächsten Zeile die Meldung steht
  43.                 GetLine(line);
  44.                 std::cout << "Online Highscore Fehler: " << line.str() << std::endl;
  45.                 error = true;
  46.                 break;
  47.             }
  48.             else if(line.str()=="ENDPHPDATA") {
  49.                 //Das ende aller Daten wir können mit lesen aufhören
  50.                 break;
  51.             }
  52.         } catch(std::exception& e) {
  53.             break;
  54.         }
  55.     }
  56.  
  57.     //Verbindung wieder schliesen
  58.     DisconnectToServer();
  59.  
  60.     //Nun müssen wir die Highscore nochmal einlesen um die Aktuelle Highscore zu haben die auch Online ist
  61.     if(!ReadHighscore()) return false;
  62.  
  63.     return !error;
  64. }

Im grunde ist auch hier der Code selbst erklärend. Wir holen uns die aktuelle online Highscore. Prüfen ob unser neuer Eintrag ein neuer wäre. Wenn ja schicken wir einen Request an den Server. Dabei haben wir eine Prüfsumme erstelle auf die selbe weise wie wir es auf PHP seite tun.
*Achtung*Diese Methode ist nicht die sicherste und kann durch rumprobieren, sowie ein wenig Debugen der Applikation unter umständen rausgefunden werden. Das verfahren sollte aber für kleine Minispiele voll ausreichend sein.*Achtung*
Danach prüfen wir ob es beim eintragen einen Fehler gab. Wenn ja geben wir diesen wie gewohnt aus. Nun holen wir uns nochmal die Highscore mit dem neuen Eintrag dazu. Das wars auch schon.

Zusammenfassung
Ok nun haben wir eine Klasse mit der wir simpelst Online Highscores lesen können, sowie neue Einträge schreiben können. Und das unter Windows, Linux und Mac. Und das beste. Auf Serverseite brauchen wir nur PHP. Unsere 2 PHP Scripte erledigen den rest. Im Anschluss folgt noch eine kleine Beispielanwendung.

Die Beispielanwendung
So hier werde ich einfach noch schnell eine Beispielanwendung zeigen, die den Umgang mit der Klasse demonstrieren. Ich werde dazu kein Wort mehr verlieren. Schaut euch einfach den Code an.

Code: C++ | Plain Text
  1. /*
  2.  * main.cpp
  3.  *
  4.  *  Created on: 03.07.2009
  5.  *      Author: fkrauthan
  6.  */
  7.  
  8. #include "Highscore.h"
  9. #include <iostream>
  10. #include <cstdlib>
  11.  
  12. int PrintMenu();
  13. void ReadHighscore(CHighscore* highscore);
  14. void InsertHighscore(CHighscore* highscore);
  15. void WaitforInput();
  16.  
  17. int main(int argc, char **argv) {
  18.     std::string server;
  19.     std::string getadresse;
  20.     std::string insertadresse;
  21.  
  22.     //Die Parameter abfragen
  23.     std::cout << "Highscore Beispiel:\n\n";
  24.     std::cout <<"Server (ohne \"http://\"): ";
  25.     std::cin >> server;
  26.     std::cout << "\n";
  27.     std::cout << "Get Adresse (Unterpfad zum PHP script)\n";
  28.     std::cout << "Beispiel bei \"http://www.test.de/highscore/get.php?id=mygame\" ist \"/highscore/get.php?id=mygame\"\n";
  29.     std::cout << "Get Adresse: ";
  30.     std::cin >> getadresse;
  31.     std::cout << "\n Insert Adresse (Wie bei get Adresse nur stat \"get.php?id=mygame\" \"insert.php?id=mygame\"\n";
  32.     std::cout << "\".\" eingeben um selbe wie Get Adresse wobei get.php durch insert.php ersetzt wird\n";
  33.     std::cout << "Insert Adresse: ";
  34.     std::cin >> insertadresse;
  35.     if(insertadresse==".") {
  36.         insertadresse = getadresse;
  37.  
  38.         std::string stringSearchString("get.php");
  39.         std::string::size_type pos = insertadresse.find(stringSearchString, 0);
  40.         int intLengthSearch = stringSearchString.length();
  41.         while(std::string::npos != pos) {
  42.             insertadresse.replace(pos, intLengthSearch, "insert.php");
  43.             pos = insertadresse.find(stringSearchString, pos + intLengthSearch);
  44.         }
  45.  
  46.         std::cout << insertadresse;
  47.     }
  48.  
  49.     std::cout << "\n\nInstance wird erzeugt...";
  50.     CHighscore tmpHighscore(server, getadresse, insertadresse, "1.0", 20);
  51.     std::cout << "fertig\n";
  52.  
  53.     bool end=false;
  54.     while(!end) {
  55.         switch(PrintMenu()) {
  56.             case 1:
  57.                 ReadHighscore(&tmpHighscore);
  58.                 break;
  59.             case 2:
  60.                 InsertHighscore(&tmpHighscore);
  61.                 break;
  62.             case 3:
  63.                 end = true;
  64.                 break;
  65.             default:
  66.                 break;
  67.         }
  68.     }
  69.  
  70.     std::cout << "\n\nProgramm wird beendet\n";
  71.  
  72.     return 0;
  73. }
  74.  
  75. int PrintMenu() {
  76.     std::string menuItem;
  77.     std::cout << "Menue: \n\n";
  78.     std::cout << "1: Highscore Lesen\n";
  79.     std::cout << "2: Highscore Schreiben\n";
  80.     std::cout << "3: Programm Beenden\n";
  81.     std::cout << "Ihre wahl (1-3): ";
  82.     std::cin >> menuItem;
  83.     std::cout << "\n";
  84.  
  85.     if(menuItem=="1"||menuItem=="2"||menuItem=="3") {
  86.         return std::atoi(menuItem.c_str());
  87.     }
  88.     else {
  89.         return 4;
  90.     }
  91. }
  92.  
  93. void ReadHighscore(CHighscore* highscore) {
  94.     std::cout << "Empfange Highscore liste...";
  95.     if(!highscore->ReadHighscore()) std::cout << "fehler\n";
  96.     else {
  97.         std::cout << "erfolgreich\n\nListe Highscore auf:\n";
  98.         std::vector<CHighscore::HIGHSCORE_ENTRY> entrys = highscore->GetHighscore();
  99.         std::cout << (int)entrys.size() << " Eintraege\n";
  100.         for(int i=0;i<(int)entrys.size();i++) {
  101.             std::cout << "Platz " << (i+1) << " | " << entrys[i].sUser << " | " << entrys[i].iPoints << " | " << entrys[i].iGameTime << "\n";
  102.         }
  103.         std::cout << "Auflistung erfolgreich\n\n";
  104.     }
  105.  
  106.     WaitforInput();
  107. }
  108.  
  109. void InsertHighscore(CHighscore* highscore) {
  110.     std::cout << "Neuen Eintrag anlegen.\n\n";
  111.  
  112.     std::string username;
  113.     std::cout << "Username: ";
  114.     std::cin >> username;
  115.     std::cout << "Punktzahl: ";
  116.     std::string tmp;
  117.     int punktzahl;
  118.     std::cin >> tmp;
  119.     punktzahl = std::atoi(tmp.c_str());
  120.     std::cout << "Spiel Zeit: ";
  121.     int spielzeit;
  122.     std::cin >> tmp;
  123.     spielzeit = std::atoi(tmp.c_str());
  124.  
  125.     std::cout << "\n\nTrage Eintrag in Highscore ein...";
  126.     if(!highscore->InsertHighscore(username, punktzahl, spielzeit)) {
  127.         std::cout << "fehler\n";
  128.     }
  129.     else {
  130.         std::cout << "erfolgreich\n";
  131.     }
  132.  
  133.     WaitforInput();
  134. }
  135.  
  136. void WaitforInput() {
  137.     std::cout << "\nBitte \".\" und dann ENTER drücken um Fortzufahren:\n";
  138.     std::string c;
  139.     std::cin >> c;
  140. }

Ok ist doch einfach die Highscore zu bedienen. Mit dieser Klasse steht einer eigenen Highscore nichts mehr im wege. Nun fragt ihr euch vielleicht wozu dieser parameter ID ist den wir übergeben müssen. Nun ganz stillschweigend kann man die selben PHP Scripte für ganz viele Spiele einsetzen die diese 3 Werte enthalten. Das heißt ihr müsst nicht jedes mal neu eine get.php und eine insert.php anlegen sondern könnt die selbe für mehrere Games verwenden.

Server Installation der PHP Scripte
So nun müssen wir nur noch kurz abklären wie ihr nun die PHP Scripte einsetzen könnt. Ladet die 2 PHP Dateien in ein Verzeichnis auf euren Server. Nun legt ihr noch einen Unterordner daten an. In diesem Unterordner liegen später die Highscoredateien. Nun müsst ihr eine Datei für euer Spiel anlegen. Diese Datei muss einfach eine leere Datei sein. Dabei setzt ihr “hs_” davor. Dann kommt der string den ihr bei id im Game übergebt und dann noch “.map” also Z.b. “hs_mygame.map“. Achtet darauf das diese Datei auch beschreibbar ist von eurem Webserver. Das wars auch schon auf Server seite. Ist doch einfach oder?

Auf Gameseite
In eurem Spiele Code müsst ihr nur eine Instance der Highscore klasse erzeugen. Die URLs korrekt übergeben und schon läuft es. Am besten schaut ihr euch dazu einfach das Beispiel an.
Unter Linux kann man es einfach Compeliren und Linken. Unter Windows muss noch die Lib “ws2_32.lib” zum Projekt hinzugelinkt werden. Das wars auch schon.

Verbesserungsvorschläge
Natürlich ist diese Klasse keinen falls die optimalste Lösung. Man könnte z.b. Die Überprüfung, ob ein neuer Highscore erreicht ist in eine Funktion auslagern. So kann man das direkt schon im Programm benutzen und so den User darauf hinweisen, ob er eine neue Highscore erreicht hat. Außerdem könnte man das Prüfsummen verfahren noch etwas ausbauen, sodass es noch schwerer wird die Berechnung herauszubekommen.

Downloads
So hier findet ihr die 2 PHP Scripte und das Test Projekt mit der Highscore Klasse zum Download

Schlusswort
So das wars aber nun. War ja auch ein komplexer Stoff den wir hier durchgenommen haben. Ich hoffe, dass dieses Tutorial einigen hilfreich sein wird auf ihrem weg in ihr Spiel eine Online Highscore einzubauen. Wie man sieht geht das ja nun ziemlich leicht. Somit gibt es die ausrede nicht mehr das man nicht weiß wie das geht oder dass es zu schwer ist. Und einen Webspace wo PHP drauf läuft kriegt man ja inzwischen an jeder datenecke umsonst^^ Wenn ihr fragen habt oder Probleme, dann schreibt doch einfach ein Kommentar. Natürlich könnt ihr auch gerne lobende Kommentare schreiben^^ Wenn ihr auch einen Online Viewer haben wollt für diese Highscore Klasse dann schreibt das einfach im Kommentar.
Noch fleißiges programmieren,
Florian Krauthan <fkrauthan>

Comments (0)

Vielen Dank, genau das was ich gerade gesucht habe! Hat mir wirklich weiter geholfen!

Write a comment