Pinball Fantasies unter WindowsXP mit globaler Highscoreliste

Aus NOBAQ
Zur Navigation springenZur Suche springen
Autohotkey logo.gif

Vor etlichen Tagen ist mir ein altes Spiel in die Hände gesprungen das ich früher gerne gespiele habe: Pinball Fantasies. Das Spiel steuert die Grafikkarte in nicht standardisierten Modi an und läuft somit nicht in der DOS-Emulation von Windows. Desweiteren braucht es 400kB an unterem Speicher, d.h. DOS muss mit highmen.sys und LOADHIGH die Treiber über die magischen 640k verbannen. Ich habe versucht, das Spiel unter VMware zum Laufen zu bekommen. Keine Chance. Der Bildschirm blieb schwarz. Das scheint daran zu liegen dass VMware eine proprietäre Grafikkarte emuliert. Auch mit Microsoft VirtualPC hatte ich kein Glück: Es erschien zwar ein Bild (falsche Farben und Auflösung!) und dann stürzte es ab. Also hab ich mich dran gemacht, einen alten 486er Notebook ("pylon", der für 2 Jahre am Spitzboden als fli4l-Router gedient hatte) mit einer 32MB CF-Karte auszustatten und hab ein altes DOS 5.0 mit Pinball installiert. Nun hab ich aber eine Lösung gefunden, mit dem das Flipper perfekt unter Windows läuft. Und dazu hab ich gleich ein Programm geschrieben, das die Highscores auf einem globalen Server speichert und synchronisiert. So können mehrere Leute wettkämpfen ;-)


Das Spiel

Pinball Fantasies ist IMHO die beste Flippersimulation die jemals gemacht wurde. Das Spiel hat vier Tische, die hier abgebildet sind:

  • Partyland
  • Speed Devils
  • Speed Devils
  • Billion Dollar
Partyland2.png
Speeddevils2.png
Billiondollar1.png
Stonesnbones1.png

Der Emulator, der es kann: DOSBox

Die Lösung für das Spielen unter WindowsXP ist einmal denkbar einfach: DOSBox, ein DOS bzw. 486-Emulator schafft es, Pinball ohne Probleme zu spielen. Ich hab das Programm installiert, mir die benötigten Dateien (zwei DLLs, DoxBox.exe und die conf-Datei) rauskopiert und wieder deinstalliert.

Start mit DOSBox

DOSBox wird gestartet und sodann ein Windows"verzeichnis" gemountet. Befindet sich Pinball in d:\spiele\pinball, so gibt man z.B. ein:

mount c d:\spiele
c:
cd spiele
cd pinball
pinball

Nun ist eine Vorkehrung zu treffen: DOXBox braucht sehr viele Resourcen. Das Herabsetzen von Frames wirkt sehr störend, weshalb ich als Resourcensparen empfehle, die Soundqualität sehr niedrig zu stellen, da diese ohnehin (verhältnismäßig) sehr gut ist. Mit

setsound

wird der Sound konfiguriert. Das geht sogar in der Dos-Box von Windows. Die Einstellungen werden dann in SOUND.CFG geschrieben. Die besten Resultate habe ich mit “Sound Blaster 16″ und den Defaulteinstellungen erzielt. Bei Soundqualität jedoch “Niedrig”. Es reicht ;-)

Pinball kann nun mit ALT+Enter auf Fullscreen geschaltet werden.


Integration von DOSBox

Da das alles sehr umständlich ist und das ganze DOSBox nur 3,5MB (!) braucht, habe ich es als Art "Library" betrachtet, um Pinball zu verwenden. DOSBox soll transparent für den "User" sein.

Als erstes wird ein Verzeichnis "pinball" angelegt, das zukünftige normale "Startverzeichnis" des Spiels. In ein weiteres Unterverzeichnis "pinball" wird das original DOS Pinball kopiert. Nun wird ein zweites Unterverzeichnis "lib" angelegt, in das DOSBox kommt. Es reicht, nur dosbox.conf, SDL.dll, SDL_net.dll und dosbox.exe zu kopieren.

In das Hauptverzeichnis kommt dann noch eine Batchdatei mit dem Namen "pinball.cmd":

@echo off
lib\dosbox.exe pinball\pinball.exe -exit -fullscreen

Der Befehl sorgt dafür, dass das Pinballverzeichnis automatisch gemountet wird und Pinball gestartet wird. Mit “-exit” wird DOSBox automatisch nach Pinball beendet und “-fullscreen” sollte klar sein.

Pinball kann nun ganz bequem unter Windows (XP etc) gestartet werden und man merkt nicht einmal, dass DOSBox mitläuft.


Die Highscoreliste

Ein einzelner 486er hätte schon einen Vorteil: Alle spielen am gleichen PC und damit gibt es eine Highscoreliste, um die man wetteifern kann! Eine nette Alternative wäre allerdings, diese über Netzwerk abzugleichen.

Das bloße Kopieren der Dateien bringt zu viele Raceconditions und ist praktisch ungeeignet.

Also hab ich mir das Format angesehen. Es ist wirklich einfach. Für jeden Tisch existiert eine .HI-Datei mit genau 64 Byte (TABLE1.HI bis TABLE4.HI). Jede Datei hat 4 Einträge (4 Plätze in der Highscore) à 16 Bytes. Die ersten 12 Bytes geben dabei die Highscore an, wobei jede Zehnerstelle einem Oktet entspricht. Und zwar in Binär. Darauf folgen drei Zeichen für den Namen. Abgeschlossen ist das ganze mit einer Null.


Die Synchronisation

Jetzt stellt sich nur mehr die Frage wie man das synchronisiert. Am einfachsten ist es wohl, ein PHP-Script zu schreiben, mit dem man die .HI Dateien uploaded. Die Scores werden dann in eine Datenbank eingetragen und sodann eine neue Datei mit den ersten 4 Plätzen erstellt. Das hat mehrere Vorteile:

  • Absolut frei von Raceconditions
  • Es gehen keine Daten verloren
  • Jeder hat zu jeder Zeit die aktuelle Highscore
  • Die Highscoreliste kann vom Web aus abgerufen werden
  • Die Highscoreliste kann im Web kann auch ältere Plätze und zusätzliche Daten wie voller Name und Datum enthalten

Clientseitig braucht dann nur mehr über ein Script jede HI-Datei upgeloaded werden und die alte Datei mit der neuen, heruntergeladenen überschrieben werden.

Zu diesem Zeitpunkt wusste ich noch nicht wie ich das genau mache, vermutlich über curl oder libcurl.


Die mySQL Tabelle

Zuerst werden folgende beiden Tabellen erstellt:

CREATE TABLE `pinball_score` (
`table` int(11) NOT NULL default '0',
`score` bigint(20) NOT NULL default '0',
`name` char(3) NOT NULL default '',
`ts` datetime NOT NULL default '0000-00-00 00:00:00',
PRIMARY KEY (`table`,`score`,`name`)
) TYPE=MyISAM;

Diese enthält die Table, die Score und den dreistelligen Namen als Primärschlüssel. Als zusätzliches Feld ist ein Timestamp dabei.

Eine weitere Tabelle ist optional:

CREATE TABLE `pinball_names` (
`nick` char(3) NOT NULL default '',
`name` varchar(100) NOT NULL default '',
PRIMARY KEY (`nick`)
) TYPE=MyISAM;

Diese dient zur Auflösung in "Realnames".

Das PHP-Script

Das PHP-Script ist eigentlich selbsterklärend. Da sich die Codefunktion von dem verdammten Wordpress leider immer vollkommen daneben benimmt, bleibt mir nichts andres übrig, als das Script raufzuladen und einen Link anzubieten:

Media:pinball_serverphp.txt

In kurzen Worten: Zuerst wird kontrolliert, ob eine Datei mit richtigem Namen raufgeladen wurde (z.B. TABLE2.HI). Wenn ja, wird diese gelesen und in eine normale Dezimalzahl und einen String konvertiert. Nun wird geprüft, ob dieser Eintrag in der Datenbank bereits vorhanden ist. Wenn nein, wird dieser per INSERT eingefügt und zusätzlich der Timestamp. REPLACE INTO geht hier nicht, weil dann jedesmal der Timestamp mitaktualisiert werden würde.

Im zweiten Teil werden die ersten 4 Zeilen wieder ausgelesen und in das Pinball-Format konvertiert. Diese Datei wird dann einfach an den Client geschickt.

Weiters zeigt das Script, wenn keine Datei hochgeladen wurde, einfach alle Highscores an!


Steuerung am Client - curl

Ich hab mich doch gegen eine komplizierte Lösung mit C++ und libcurl ausgesprochen und lieber das viel einfachere curl-Kommandotool verwendet.

Mit folgendem Befehl passiert genau das, was ich bezwecken will: Die Datei TABLE2.HI wird hochgeladen und mit dem Script verarbeitet. Das Ergebnis wird in SERVER.OUT abgespeichert:

curl -o SERVER.OUT -F file=@TABLE2.HI http://www.server.tld/path_to_script.php

Über eine Batchdatei geht das nicht elegant, denn ich würde ja auch gerne auf Fehler kontrollieren.


Das Userinterface - AutoHotkey

Aufgerufen wird das ganze dann aber mittels einer kleinen, simplen, netten Scriptsprache: AutoHotkey. Wirklich SEHR empfehlenswert!

Das Script führt einfach die obere curl-Zeile für jede Table aus und kontrolliert, ob die Rückgabedaten genau 64 Byte sind. Ist dies der Fall ist alles in Butter und die Originaldatei wird überschrieben. Ist dies nicht der Fall, wird die erste Zeile ausgelesen und als Fehlermeldung ausgegeben. Ist die erste Zeile leer (z.B. Server nicht gefunden o.ä.) erscheint “Serverfehler”.

Das ganze ist noch in einen wunderschönen Progressdialog gegossen, sodass der User nichts davon merkt:

Highscore.png

Das Script sieht nun wie folgt aus:

#NoTrayIcon
SyncTable(table)
{
RunWait curl -o SERVER.OUT -F file=@%table% http://www.server.tld/path_to_script.php, , Hide
FileGetSize, size, SERVER.OUT
if size <> 64
{
FileReadLine, line, SERVER.OUT, 1
len := StrLen(line)
Progress, OFF
if(len <= 0)
{
line := "Verbindungsfehler"
}
MsgBox, 0, Serverfehler, %line%
FileDelete SERVER.OUT
EXIT
}
else
{
FileDelete %table%
FileCopy, SERVER.OUT, %table%
FileDelete SERVER.OUT
}
}
SetWorkingDir %A_ScriptDir%\..\pinball
Progress 0, Partyland, Synchronisiere Highscoreliste, Highscoreliste
SyncTable("TABLE1.HI")
Progress 25, Speed Devils
SyncTable("TABLE2.HI")
Progress 50, Billion Dollar
SyncTable("TABLE3.HI")
Progress 75, StonesNBones
SyncTable("TABLE4.HI")
Progress 90
Sleep, 1000
Progress OFF

Das das nun alles seine Form bekommt, wird im Hauptverzeichnis noch ein Unterverzeichnis “score” erstellt. Dort wird nur mehr curl.exe, sync.ahk (das obere Script) und AutoHotkey.exe reinkopiert.

Natürlich wäre es auch möglich, sync.ahk direkt in eine exe-Datei zu “compilieren”, jedoch sehe ich keinen Sinn darin, ausser dass es ggf. die Fehlersuche erschwert. Also liefere ich gleich den kompletten Autohotkey Interpreter mit.

Die pinball.cmd wird dann noch wie folgt erweitert:

@echo off
score\AutoHotKey.exe score\sync.ahk
lib\dosbox.exe pinball\pinball.exe -exit -fullscreen
score\AutoHotKey.exe score\sync.ahk

Nun wird sowohl vor, als auch nach dem Spiel automatisch die Highscoreliste synchronisiert! Der Benutzer merkt nichts davon, ausser einen kurzen “Progress”dialog.

Weiters kommt in das Verzeichnis noch eine Highscoreliste.url mit dem Link zu den Highscores im Web sowie eine obligatorische README-Datei.

Das ganze Paket hat nun entpackt eine Größe von 7.7MB, mit zip gepackt gar nur 3MB. Ideal! :-)


Sicherheit

Es ist mir schon klar, dass man hier leicht fälschen kann, aber das könnte man durch den einfachen Aufbau der Highscoretabelle sowieso. Aber im Endeffekt gehts ja eh nur um den Spass ;-)

<comment>sdf</comment>