Online Highscore mit C++ und PHP
Posted by fkrauthan | Posted in C++, Linux, PHP, Sockets, WinAPI, Windows | Posted on 09-12-2009
Tags: c++, cpp, highscore, linux, online, online highscore, PHP, socket, Sockets, windows, winsock, winsocket
0
Inhalt
- Einführung
- Voraussetzungen
- Was brauchen wir?
- Die Highscore Server seitig auslesen
- Das schreiben eines neuen Highscoreeintrages
- Definition der Highscore Klasse
- Eine Verbindung aufbauen
- Das Senden des HTML Headers
- SendAll und GetLine
- Empfangen der Highscore
- Neuen Eintrag senden
- Zusammenfassung
- Die Beispielanwendung
- Server Installation der PHP Scripte
- Auf Gameseite
- Verbesserungsvorschläge
- Downloads
- Schlusswort
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.
- <?php
- //Prüfen ob ID angegeben ist
- //Sollte keine ID übergeben worden sein
- echo "ERROR\n Keine Spiel ID uebergeben.\n";
- }
- else {
- $id = $_GET["id"];
- //Prüfen ob eine Highscore Exestiert
- //Sollte keine Highscore Exestieren
- echo "ERROR\n Fuer dieses Spiel Exestiert keine Online Highscore.\n";
- }
- else {
- //Laden der Highscore
- if(!$fp) {
- //Sollte er die Datei nicht öffnen können
- echo "ERROR\n Auf dem Server konnte die Highscore liste nicht geöffnet werden.\n";
- }
- else {
- //Das Sende OK senden
- echo "STARTSENDINGDATA\n";
- //Alles einlesen
- }
- }
- //Sagen das man fertig mit Senden ist
- echo "ENDSENDINGDATA\n";
- }
- }
- }
- ?>
- 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:
- <?php
- function IsNewHighscore($points, $tmpHSArray, $maxHighscore) {
- if($points<=0)
- return false;
- $i=0;
- $found=false;
- for($i=0; $i<count($tmpHSArray);$i++) {
- if($tmpArray[1] < $points)
- $found=true;
- else if($tmpArray[1]==$points)
- return false;
- }
- if($i>=$maxHighscore&&$found==false)
- return false;
- if($found==true||$i<$maxHighscore)
- return true;
- return false;
- }
- function SortHighScore($sort_array,$reverse) {
- if($reverse){
- if ($tmpArray1[1] < $tmpArray2[1]){
- $tmp = $sort_array[$i];
- $sort_array[$i] = $sort_array[$j];
- $sort_array[$j] = $tmp;
- }
- } else {
- if ($tmpArray1[1] > $tmpArray2[1]){
- $tmp = $sort_array[$i];
- $sort_array[$i] = $sort_array[$j];
- $sort_array[$j] = $tmp;
- }
- }
- }
- }
- return $sort_array;
- }
- ?>
- <?php
- //Sollte keine ID übergeben worden sein
- echo "ERROR\n Keine Spiel ID uebergeben.\n";
- }
- //Sollte kein Name übergeben worden sein
- echo "ERROR\n Kein Spieler Name uebergeben.\n";
- }
- //Sollte keine Punkte übergeben worden sein
- echo "ERROR\n Keine Spieler Punkte uebergeben.\n";
- }
- //Sollte keine Zeit übergeben worden sein
- echo "ERROR\n Keine Spiel Zeit uebergeben.\n";
- }
- //Sollte keine version übergeben worden sein
- echo "ERROR\n Keine Spiel Version uebergeben.\n";
- }
- //Sollte keine Mximale Anzahl an Einträgen übergeben worden sein
- echo "ERROR\n Keine Maximale Highscore Eintraege uebergeben.\n";
- }
- //Sollte keine Prüfsumme übergeben worden sein
- echo "ERROR\n Keine Spiel pruefsumme uebergeben.\n";
- }
- else {
- //Werte in Variablen schreiben
- $id = $_GET["id"];
- $name = $_GET["name"];
- $points = $_GET["points"];
- $time = $_GET["time"];
- $version = $_GET["version"];
- $maxHSEntys = $_GET["maxentrys"];
- $prf = $_GET["prf"];
- //Prüfen ob eine Highscore Exestiert
- //Sollte keine Highscore Exestieren
- echo "ERROR\n Fuer dieses Spiel Exestiert keine Online Highscore.\n";
- }
- else if($lokalprf!=$prf) {
- //Sollte die Prüfsumme nicht stimmen
- echo "ERROR\n Die Preufsumme stimmt nicht.\n";
- }
- else {
- //Liste Laden und schaun ob wirklich neu
- if(!$fp) {
- //Sollte er die Datei nicht öffnen können
- echo "ERROR\n Auf dem Server konnte die Highscore liste nicht geöffnet werden.\n";
- }
- else {
- }
- }
- $tmpArray = SortHighScore($tmpArray, 1);
- if(!$fp) {
- //Sollte er die Datei nicht zum schreiben öffnen können
- echo "ERROR\n Auf dem Server konnte die Highscore liste nicht beschrieben werden.\n";
- }
- else {
- if($max>$maxHSEntys) $max = $maxHSEntys;
- for($i=0; $i<$max;$i++) {
- }
- }
- }
- }
- }
- ?>
- 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.
- /*
- * Highscore.h
- *
- * Created on: 03.07.2009
- * Author: fkrauthan
- */
- #ifndef HIGHSCORE_H_
- #define HIGHSCORE_H_
- #include <string>
- #include <vector>
- #include <cstdlib>
- class CHighscore {
- public:
- //Ein Highscore Eintrag
- struct HIGHSCORE_ENTRY {
- //Der Name des Users
- std::string sUser;
- //Dem seine Punktzahl
- int iPoints;
- //Die Gespielte Zeit (ich empfehle in Sekunden)
- int iGameTime;
- };
- //Der Konstruktor um alle Daten zu initsalisieren die wir brauchen
- CHighscore(const std::string& server, const std::string& getadress, const std::string& insertadress, const std::string& gameversion, int maxhighscoreentrys=20);
- //Der Destruktor. Hier räumen wir auf sollte das Socket noch offen sein.
- ~CHighscore();
- //Die Highscore auf dem Server lesen
- bool ReadHighscore();
- //Das Gelesene sich holen
- std::vector<HIGHSCORE_ENTRY>& GetHighscore();
- //Einen neuen Eintrag auf den Server schicken
- bool InsertHighscore(const std::string& user, int points, int gametime);
- private:
- //Kleine Hilfsfunktionen
- bool ConnectToServer();
- bool SendHeader(const std::string& url);
- void DisconnectToServer();
- //Funktion zum senden der Daten an den Server
- void SendAll(const char* const buf, const int size);
- //Funktion zum lesen einer Zeile vom Server
- void GetLine(std::stringstream& line);
- //Die Verbindungsdaten Speichern
- std::string m_sServer;
- std::string m_sGetAdress;
- std::string m_sInsertAdress;
- std::string m_sGameVersion;
- int m_iMaxHighscoreEntrys;
- //Der Temporäre Speicher der Highscore
- std::vector<HIGHSCORE_ENTRY> m_vHighscoreEntrys;
- //Die ID des Sockets
- int m_iSocket;
- };
- #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.
- /*
- * Highscore.cpp
- *
- * Created on: 03.07.2009
- * Author: fkrauthan
- */
- #include "Highscore.h"
- #ifdef linux
- #include <sys/socket.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #include <errno.h>
- #else
- #include <winsock2.h>
- #endif
- #include <sstream>
- #include <iostream>
- #include <cstdlib>
- CHighscore::CHighscore(const std::string& server, const std::string& getadress, const std::string& insertadress, const std::string& gameversion, int maxhighscoreentrys) {
- //Einfaches zuweisen der Variablen
- m_sServer = server;
- m_sGetAdress = getadress;
- m_sInsertAdress = insertadress;
- m_sGameVersion = gameversion;
- m_iMaxHighscoreEntrys = maxhighscoreentrys;
- //Die Socket ID ungültig setzen
- m_iSocket = -1;
- }
- CHighscore::~CHighscore() {
- //Beim schliesen die evt. noch geöffnete verbindung wieder schliesen
- DisconnectToServer();
- }
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.
- bool CHighscore::ConnectToServer() {
- //Verbindung schliesen sollte sie schon offen sein
- DisconnectToServer();
- #ifndef linux
- WSADATA w;
- if(int result = WSAStartup(MAKEWORD(2,2), &w) != 0) {
- std::cout << "Winsock 2 konnte nicht gestartet werden! Error #" << result << std::endl;
- return false;
- }
- #endif
- hostent* phe = gethostbyname(m_sServer.c_str());
- if(phe == NULL) {
- std::cout << "Hostname konnte nicht aufgeloest werden!" << std::endl;
- return false;
- }
- if(phe->h_addrtype != AF_INET) {
- std::cout << "Ungueltiger Adresstyp!" << std::endl;
- return false;
- }
- //Wir haben nur IP V4 unterstüzung
- if(phe->h_length != 4) {
- std::cout << "Ungueltiger IP-Typ!" << std::endl;
- return false;
- }
- m_iSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
- if(m_iSocket == -1) {
- std::cout << "Socket konnte nicht erstellt werden!" << std::endl;
- return false;
- }
- sockaddr_in service;
- service.sin_family = AF_INET;
- service.sin_port = htons(80);
- char** p = phe->h_addr_list;
- int result;
- do {
- if(*p == NULL) {
- std::cout << "Verbindung fehlgeschlagen!" << std::endl;
- return false;
- }
- service.sin_addr.s_addr = *reinterpret_cast<unsigned long*>(*p);
- ++p;
- result = connect(m_iSocket, reinterpret_cast<sockaddr*>(&service), sizeof(service));
- } while(result == -1);
- return true;
- }
- void CHighscore::DisconnectToServer() {
- if(m_iSocket==-1) return;
- #ifdef linux
- close(m_iSocket);
- #else
- closesocket(m_iSocket);
- #endif
- }
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:
- bool CHighscore::SendHeader(const std::string& url) {
- if(m_iSocket==-1) return false;
- std::string request = "GET ";
- request.append(url.c_str());
- request.append(" HTTP/1.1\r\nHost: ");
- request.append(m_sServer.c_str());
- request.append("\r\nConnection: close\r\n\r\n");
- SendAll(request.c_str(), request.size());
- int code = 100;
- std::string Protokoll;
- std::stringstream firstLine;
- while(code == 100) {
- GetLine(firstLine);
- firstLine >> Protokoll;
- firstLine >> code;
- if(code == 100) {
- GetLine(firstLine);
- }
- if(code != 200) {
- //Sollte der Status Code nicht 200 Sein für alles OK dann HTML Fehler Code ausgeben
- firstLine.ignore();
- std::string msg;
- getline(firstLine, msg);
- std::cout << "Error #" << code << " - " << msg << std::endl;
- return false;
- }
- }
- return true;
- }
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:
- void CHighscore::SendAll(const char* const buf, const int size) {
- if(m_iSocket==-1) return;
- int bytesSent = 0;
- do {
- int result = send(m_iSocket, buf + bytesSent, size - bytesSent, 0);
- if(result < 0) {
- break;
- }
- bytesSent += result;
- } while(bytesSent < size);
- }
- void CHighscore::GetLine(std::stringstream& line) {
- if(m_iSocket==-1) return;
- for(char c; recv(m_iSocket, &c, 1, 0) > 0; line << c) {
- if(c == '\n') {
- return;
- }
- }
- }
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.
- bool CHighscore::ReadHighscore() {
- //Zum Server verbinden
- if(!ConnectToServer()) return false;
- //Die GetURL für die Highscore aufrufen
- if(!SendHeader(m_sGetAdress)) return false;
- //Leeren der aktuellen Highscore
- m_vHighscoreEntrys.clear();
- //Nun müssen wir noch die Daten bezüglich der Highscore auslesen
- bool startData = false;
- bool error = false;
- int i = 0;
- while(true) {
- std::stringstream line;
- try {
- GetLine(line);
- if(line.str()=="ERROR") {
- //Wir haben einen Fehler. Wir wissen das in der nächsten Zeile die Meldung steht
- GetLine(line);
- std::cout << "Online Highscore Fehler: " << line.str() << std::endl;
- error = true;
- break;
- }
- else if(line.str()=="ENDPHPDATA") {
- //Das ende aller Daten wir können mit lesen aufhören
- break;
- }
- else if(line.str()=="STARTSENDINGDATA") {
- //Nun kommen Highscore daten
- startData = true;
- }
- else if(line.str()=="ENDSENDINGDATA") {
- //Nun enden die Highscore daten
- startData = false;
- }
- else if(startData){
- std::vector<std::string> tmpVector;
- std::string buf;
- while (line >> buf)
- tmpVector.push_back(buf);
- if(i==m_iMaxHighscoreEntrys)
- break;
- if(tmpVector.size()<3) {
- std::cout << "Ungueltiges Format empfangen. Ueberspringe dieses" << std::endl;
- }
- else {
- //Den neuen Eintrag bei uns einfügen
- i++;
- HIGHSCORE_ENTRY tmpEntry;
- tmpEntry.sUser = tmpVector[0];
- tmpEntry.iPoints = std::atoi(tmpVector[1].c_str());
- tmpEntry.iGameTime = std::atoi(tmpVector[2].c_str());
- m_vHighscoreEntrys.push_back(tmpEntry);
- }
- }
- } catch(std::exception& e) {
- break;
- }
- }
- //Die Verbindung zum Server wieder schliesen
- DisconnectToServer();
- return !error;
- }
- std::vector<CHighscore::HIGHSCORE_ENTRY>& CHighscore::GetHighscore() {
- return m_vHighscoreEntrys;
- }
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.
- bool CHighscore::InsertHighscore(const std::string& user, int points, int gametime) {
- //Zuerst holen wir uns die Aktuelle liste um schauen zu können ob neuer eintrag
- if(!ReadHighscore()) return false;
- //Nun prüfen wir ob wir einen neuen Eintrag haben
- bool newentry = false;
- if((int)m_vHighscoreEntrys.size()<m_iMaxHighscoreEntrys) newentry = true;
- else {
- for(int i=0;i<(int)m_vHighscoreEntrys.size();i++) {
- if(m_vHighscoreEntrys[i].iPoints<points) {
- newentry = true;
- break;
- }
- }
- }
- //Es ist kein neuer Eintrag
- if(!newentry) {
- std::cout << "Kein neuer Eintrag\n";
- return false;
- }
- //Nun stellen wir die Eintrags URL zusammen
- std::stringstream url;
- url << m_sInsertAdress << "&name=" << user << "&points=" << points << "&time=" << gametime;
- //Nun berechnen wir unsere Prüfsumme. Diese muss genauso berechent werden wie im PHP Script
- url << "&prf=mygame" << (points*gametime-user.size()+3*(gametime+points)-m_iMaxHighscoreEntrys);
- //Nun Schicken wir die neuen Daten an den Server
- if(!ConnectToServer()) return false;
- if(!SendHeader(url.str())) return false;
- //Nun nur noch schauen ob es fehler gibt
- bool error = false;
- while(true) {
- std::stringstream line;
- try {
- GetLine(line);
- if(line.str()=="ERROR") {
- //Wir haben einen Fehler. Wir wissen das in der nächsten Zeile die Meldung steht
- GetLine(line);
- std::cout << "Online Highscore Fehler: " << line.str() << std::endl;
- error = true;
- break;
- }
- else if(line.str()=="ENDPHPDATA") {
- //Das ende aller Daten wir können mit lesen aufhören
- break;
- }
- } catch(std::exception& e) {
- break;
- }
- }
- //Verbindung wieder schliesen
- DisconnectToServer();
- //Nun müssen wir die Highscore nochmal einlesen um die Aktuelle Highscore zu haben die auch Online ist
- if(!ReadHighscore()) return false;
- return !error;
- }
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.
- /*
- * main.cpp
- *
- * Created on: 03.07.2009
- * Author: fkrauthan
- */
- #include "Highscore.h"
- #include <iostream>
- #include <cstdlib>
- int PrintMenu();
- void ReadHighscore(CHighscore* highscore);
- void InsertHighscore(CHighscore* highscore);
- void WaitforInput();
- int main(int argc, char **argv) {
- std::string server;
- std::string getadresse;
- std::string insertadresse;
- //Die Parameter abfragen
- std::cout << "Highscore Beispiel:\n\n";
- std::cout <<"Server (ohne \"http://\"): ";
- std::cin >> server;
- std::cout << "\n";
- std::cout << "Get Adresse (Unterpfad zum PHP script)\n";
- std::cout << "Beispiel bei \"http://www.test.de/highscore/get.php?id=mygame\" ist \"/highscore/get.php?id=mygame\"\n";
- std::cout << "Get Adresse: ";
- std::cin >> getadresse;
- std::cout << "\n Insert Adresse (Wie bei get Adresse nur stat \"get.php?id=mygame\" \"insert.php?id=mygame\"\n";
- std::cout << "\".\" eingeben um selbe wie Get Adresse wobei get.php durch insert.php ersetzt wird\n";
- std::cout << "Insert Adresse: ";
- std::cin >> insertadresse;
- if(insertadresse==".") {
- insertadresse = getadresse;
- std::string stringSearchString("get.php");
- std::string::size_type pos = insertadresse.find(stringSearchString, 0);
- int intLengthSearch = stringSearchString.length();
- while(std::string::npos != pos) {
- insertadresse.replace(pos, intLengthSearch, "insert.php");
- pos = insertadresse.find(stringSearchString, pos + intLengthSearch);
- }
- std::cout << insertadresse;
- }
- std::cout << "\n\nInstance wird erzeugt...";
- CHighscore tmpHighscore(server, getadresse, insertadresse, "1.0", 20);
- std::cout << "fertig\n";
- bool end=false;
- while(!end) {
- switch(PrintMenu()) {
- case 1:
- ReadHighscore(&tmpHighscore);
- break;
- case 2:
- InsertHighscore(&tmpHighscore);
- break;
- case 3:
- end = true;
- break;
- default:
- break;
- }
- }
- std::cout << "\n\nProgramm wird beendet\n";
- return 0;
- }
- int PrintMenu() {
- std::string menuItem;
- std::cout << "Menue: \n\n";
- std::cout << "1: Highscore Lesen\n";
- std::cout << "2: Highscore Schreiben\n";
- std::cout << "3: Programm Beenden\n";
- std::cout << "Ihre wahl (1-3): ";
- std::cin >> menuItem;
- std::cout << "\n";
- if(menuItem=="1"||menuItem=="2"||menuItem=="3") {
- return std::atoi(menuItem.c_str());
- }
- else {
- return 4;
- }
- }
- void ReadHighscore(CHighscore* highscore) {
- std::cout << "Empfange Highscore liste...";
- if(!highscore->ReadHighscore()) std::cout << "fehler\n";
- else {
- std::cout << "erfolgreich\n\nListe Highscore auf:\n";
- std::vector<CHighscore::HIGHSCORE_ENTRY> entrys = highscore->GetHighscore();
- std::cout << (int)entrys.size() << " Eintraege\n";
- for(int i=0;i<(int)entrys.size();i++) {
- std::cout << "Platz " << (i+1) << " | " << entrys[i].sUser << " | " << entrys[i].iPoints << " | " << entrys[i].iGameTime << "\n";
- }
- std::cout << "Auflistung erfolgreich\n\n";
- }
- WaitforInput();
- }
- void InsertHighscore(CHighscore* highscore) {
- std::cout << "Neuen Eintrag anlegen.\n\n";
- std::string username;
- std::cout << "Username: ";
- std::cin >> username;
- std::cout << "Punktzahl: ";
- std::string tmp;
- int punktzahl;
- std::cin >> tmp;
- punktzahl = std::atoi(tmp.c_str());
- std::cout << "Spiel Zeit: ";
- int spielzeit;
- std::cin >> tmp;
- spielzeit = std::atoi(tmp.c_str());
- std::cout << "\n\nTrage Eintrag in Highscore ein...";
- if(!highscore->InsertHighscore(username, punktzahl, spielzeit)) {
- std::cout << "fehler\n";
- }
- else {
- std::cout << "erfolgreich\n";
- }
- WaitforInput();
- }
- void WaitforInput() {
- std::cout << "\nBitte \".\" und dann ENTER drücken um Fortzufahren:\n";
- std::string c;
- std::cin >> c;
- }
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>



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