Diplomarbeit Demonstration der Dynamik in Logistik- und Produktionsnetzwerken anhand des Beer Distribution Game in einer Online-Version Christoph Duijts Betreuer: Dipl.-Wi.-Ing. Jörg Nienhaus Zürichbergstrasse 18 CH-8028 Zürich Institut für Informationssysteme: Logistik und Informationsmanagement: Prof. Dr. C.A. Zehnder ETH Zentrum, IFW CH-8092 Zürich Prof. Dr. Paul Schönsleben ETH-Zentrum für Unternehmenswissenschaft Zürichbergstrasse 18 CH-8028 Zürich Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 1 Abstract / Management Summary Das Beer Game wurde entwickelt als Einführung in die Konzepte der Systemdynamik. Gespielt werden dürfte es hauptsächlich von Studenten oder an Seminaren für Firmen. Die Spieler machen die Erfahrung, eine Rolle in einem komplexen System zu spielen und sehen anschliessend die Auswirkungen ihres Handelns. Das Ziel des Spiels ist die Minimierung der Kosten für eine gegebene Supply-Chain. Die Spieler erleben den sogenannten Bullwhip-Effekt – die Bestellmengen vervielfachen sich über die Länge der Lieferkette, da die Spieler jeweils nur die Bestellinformationen des jeweiligen Vorgängers kennen. Die Aufgabe dieser Diplomarbeit war es, das bestehende klassische Brettspiel auf eine OnlineVariante abzubilden. Dazu wurde eine Client-Server Architektur aufgesetzt. Umgesetzt wurde der Client als Java-Applet und der Server als Java-Programm. Die Kommunikation wird über eine Socket Connection abgewickelt. Als Erweiterung zum klassischen Beergame kann auch der Computer einzelne (oder alle) Rollen übernehmen und nach gewissen vorgegebenen Strategien spielen. Die Auswertung erfolgt in zahlreichen Charts, welche entweder einzelne oder überlagerte Kurven enthalten, welche den Spielverlauf sehr schön aufzeigen. In der Auswertung ist es auch möglich, sich jede einzelne Runde (mit den Zahlen und Graphen aller Mitspieler) nochmals anzusehen. Die Spieler werden über ihre (weltweit eindeutige) E-Mailadresse identifiziert. Jeder Spieler hat die Möglichkeit, sein eigenes neues Spiel mit verschiedenen Parametern zu kreieren und danach andere Spieler einzuladen oder selbst einem anderen bestehenden Spiel beizutreten. Das Spiel wurde so programmiert, dass ein einzelner Spieler ein laufendes Spiel nicht blockieren kann. Dazu wird eine fixe Zeit pro Bestellrunde vorgegeben – läuft diese ab, übernimmt der Computer die Bestellung. Fällt ein Client ganz aus, wird dessen Rolle ebenfalls an den Computer übertragen. Die beendeten Spiele werden auf dem Server gespeichert. Den Spielern steht jederzeit die Möglichkeit offen, die Auswertung ihrer beendeten Spiele wieder anzusehen. Dies ist sinnvoll, falls das Spiel zu einer späteren Zeit (ev. in einer Vorlesung) besprochen werden soll. Der folgende Bericht liegt in vier Hauptteilen vor. Im ersten Teil folgt eine Auseinandersetzung mit theoretischen Grundlagen des Spieles sowie eine Situationsanalyse zu bereits bestehenden Varianten. Im zweiten Teil wird auf die Wahl der Entwicklungsumgebung eingegangen und im dritten Teil dann das Objektmodell und die Programmstruktur erläutert. Dazu gehört eine Kurzbeschreibung aller Klassen sowie deren wichtigsten Methoden. Abschliessend im vierten Teil folgt dann die Spielbeschreibung selbst mit verschiedenen Screenshots. Seite 2/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 2 Vorwort Als ich auf der Suche nach einer möglichen Diplomarbeit war, bin ich auf der Homepage der Professur für Logistik- und Informationsmanagement von Herrn Prof. Dr. Schönsleben auf diese Aufgabe gestossen. Die Problemstellung hat mich vom ersten Moment an fasziniert und die Faszination hat bis zum Schluss nicht mehr losgelassen. Mit freundlicher Unterstützung von meinem Diplomprofessor - Herrn Prof. Dr. Zehnder (Institut für Informationssysteme) - war es dann möglich, diese bereichsübergreifende Arbeit in Angriff zu nehmen. Neben der eigentlichen Aufgabe bestand die Motivation vor allem in der Herausforderung, mich einmal mit einer modernen Programmiersprache intensiv auseinandersetzen zu können (ich hatte zuvor weder in Java noch in einer anderen objektorientierten Sprache programmiert). Es war eine sehr interessante und spannende Erfahrung und das Projekt hat vom Anfang bis zum Ende Spass gemacht. An dieser Stelle muss ich meinem Freund Simon Zweifel einen herzlichen Dank aussprechen, da er mir als Informatiker während der ganzen Arbeit online als kompetenter Ratgeber in Programmierfragen zur Seite gestanden ist. Ohne ihn wäre diese Arbeit bei weitem nicht so weit fortgeschritten. Herzlich zu Dank verpflichtet bin ich auf meinen Eltern Karl und Ruth Duijts, welche die Korrekturlesung übernommen haben sowie Nina Reiser für die spanische Übersetzung. Im Laufe des Projektes wurde aus der Arbeit eher ein Hobby - Probleme wurden in zahlreichen Nachtschichten und Wochenendeinsätzen gelöst. Auch Schreckensmomente fehlten natürlich nicht; zum Beispiel ist in der Abgabewoche mein Laptop ausgestiegen, auf dem sowohl das gesamte Programm als auch dieser Bericht gespeichert war (selbstverständlich gab es Sicherheitskopien – aber eben...). Glücklicherweise stellte sich heraus, dass „bloss“ das Netzteil defekt war – ein paar Stunden später war mein sonst so zuverlässiges Mac-PowerBook wieder einsatzfähig. Mit dieser Arbeit schliesse ich nun mein Studium ab. Wenn ich zurück blicke, darf ich sagen, dass es mir sehr gefallen hat und dass ich dasselbe Studium wieder wählen würde. Ich freue mich nun aber auch auf eine neue Herausforderung im Berufsleben. Zürich, 1.2.2001 Christoph Duijts Seite 3/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Inhaltsverzeichnis 1 Abstract / Management Summary __________________________________________ 2 2 Vorwort_______________________________________________________________ 3 3 Aufgabenstellung _______________________________________________________ 6 4 Das Beer Distribution Game_______________________________________________ 8 4.1 4.2 4.3 5 Situationsanalyse ______________________________________________________ 11 5.1 5.1.1 5.1.2 5.1.3 5.2 6 6.3.1 6.3.2 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.5 6.6 Brettspiel – Klassische Variante___________________________________________________11 Computerized Beer Game _______________________________________________________12 Simple ++ Variante ____________________________________________________________14 Beurteilung ____________________________________________________________ 15 Pflichtenheft / Leitlinien für die Umsetzung___________________________________ 16 Auswahl der Programmiersprache, Entwicklungsumgebung _____________________ 16 Java – offene Probleme __________________________________________________ 17 Java Support _________________________________________________________________17 Unterschiede in der Java Implementation ____________________________________________17 Lösungsansätze für die Implementierung_____________________________________ 18 Ausschliessliche Verwendung von JDK 1.1.x und früher ________________________________18 Installieren von nicht Kern Java Klassen auf den Clients_________________________________18 Unterstützung für nur einen Browsertyp _____________________________________________18 Verwenden des Java Plug-in _____________________________________________________18 Verwenden von Java Web Start ___________________________________________________19 Zusammenfassung der Lösungsansätze______________________________________ 19 Die Wahl ______________________________________________________________ 20 Diskussion und Auswahl der Architektur____________________________________ 21 7.1 7.1.1 7.1.2 7.2 7.2.1 7.2.2 7.2.3 7.2.4 8 Existierende Varianten des Beer Games______________________________________ 11 Umsetzungskonzept ____________________________________________________ 16 6.1 6.2 6.3 7 Die Entstehung __________________________________________________________ 8 Das traditionelle Beer Game ________________________________________________ 8 Theoretische Grundlagen – der Bullwhip Effekt ________________________________ 9 Umsetzungsentscheid Programmarchitektur __________________________________ 21 Der Client ___________________________________________________________________21 Der Server ___________________________________________________________________21 Kommunikationskonzept _________________________________________________ 22 http-requests (request/response) ___________________________________________________22 Socket connection _____________________________________________________________22 RMI – Remote Method Invocation _________________________________________________22 Umsetzungsentscheid ___________________________________________________________22 Die Umsetzung ________________________________________________________ 23 8.1 8.1.1 8.1.2 8.1.3 8.2 Kurzübersicht über die Packages und ihre Klassen_____________________________ 23 Das Package „beergame.server ____________________________________________________23 Das Package „beergame.client“ ___________________________________________________25 Das Package „beergame.client.images“ _____________________________________________27 Das Spielkonzept ________________________________________________________ 28 Seite 4/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 9 Klassen des Package beergame.server ______________________________________ 29 9.1 9.2 9.3 9.4 9.5 9.6 9.7 10 BeergameServer.java ____________________________________________________ HelloThread.java. _______________________________________________________ SendThread.java ________________________________________________________ ListenThread.java _______________________________________________________ PlayerId.java ___________________________________________________________ Game.java _____________________________________________________________ Strategy.java ___________________________________________________________ 29 30 30 33 33 34 37 Klassen des Package beergame.client _____________________________________ 39 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 10.9 10.10 10.11 10.12 10.13 BeergameClient.java _____________________________________________________ ClientListener.java ______________________________________________________ ClientSender.java _______________________________________________________ GameLogic.java_________________________________________________________ OrderPerformedTransition _______________________________________________ NextRoundTransistion ___________________________________________________ ServerMessage.java______________________________________________________ Res.java _______________________________________________________________ Ref.java _______________________________________________________________ PnlStatistics.java ______________________________________________________ DlgNewGame.java_____________________________________________________ DlgJoinGame_________________________________________________________ PnlStock.java, PnlGoodsReceiving.java und PnlTranport.java _________________ 39 42 43 43 48 48 50 51 52 52 55 56 57 11 mögliche Erweiterungen des Spieles______________________________________ 58 12 Spielbeschreibung____________________________________________________ 60 12.1 12.2 12.3 12.4 12.5 12.6 12.7 12.8 12.9 13 13.1 13.2 13.3 14 Die Anmeldung _________________________________________________________ Der Welcome-Screen _____________________________________________________ Ein neues Spiel erzeugen __________________________________________________ Beitreten zu einem Spiel __________________________________________________ Spielstart ______________________________________________________________ Das Spiel ______________________________________________________________ Die Auswertung _________________________________________________________ Auswertung einer Computer Simulation _____________________________________ Animationen ___________________________________________________________ 60 61 62 64 65 66 67 68 69 Anhang ____________________________________________________________ 70 Übersicht über die Klassen und ihre Abhängigkeiten ___________________________ 72 Der Server Start Prozess / Start of a socket connection__________________________ 73 The Invitation Process – a three way handshake _______________________________ 74 Literaturverzeichnis __________________________________________________ 76 Seite 5/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 3 Aufgabenstellung Herrn Christoph Duijts Herbstweg 110 8050 Zürich Dipl.-Wi.-Ing. Jörg Nienhaus Zürichbergstrasse 18 CH-8028 Zürich Tel. +41 1 632 05 23, Fax +41 1 632 10 40 [email protected] http://www.lim.ethz.ch Zürich, 6. September 2001 Diplomarbeit Arbeitsbeginn: Abgabetermin: 22. Oktober 2001 21. Januar 2001 (60 Werktage) Demonstration der Dynamik in Logistik- und Produktionsnetzwerken anhand des Beer Distribution Game in einer Online-Version Das zu Beginn der 60er Jahre in der System Dynamics Group am Massachusetts Institute of Technology (MIT) entstandene Beer Distribution Game veranschaulicht den in Logistik- und Produktionsnetzwerken auftretenden Bullwhip-Effekt: Ein Unternehmen macht die Anzahl der beim Zulieferer zu bestellenden Komponenten von den Bestellungen des ihm im Netzwerk vorgelagerten Unternehmens (und nicht von denen des Endkunden) abhängig. Deshalb schwanken die Bestellmengen umso stärker, je mehr Stufen im Netzwerk zwischen einem Unternehmen und dem Endkunden liegen. Die steigende Variabilität der Bestellmengen erfordert bei den Zulieferern mit zunehmender Tiefe im Netzwerk immer höhere Sicherheitsbestände, damit sie ihre Lieferfähigkeit gewährleisten können. Diesen Effekt vollziehen beim Beer Distribution Game vier Teilnehmer nach, indem sie ein Logistik- und Produktionsnetzwerk bestehend aus Lieferant, Produzent, Grosshändler und Einzelhändler simulieren. Im Rahmen der Diplomarbeit sollen Sie ein Konzept für eine Online-Version der Simulation erstellen und es anschliessend umsetzen. Eine solche Online-Version erlaubt Teilnehmern an beliebigen Standorten auf der Welt, sich zu einer vorher verabredeten Zeit auf einer Webseite zu treffen und am Beer Distribution Game teilzunehmen. Ferner kann die sonst manuell vorzunehmende Auswertung der Ergebnisse – wie bei einer am ETH-Zentrum für Unternehmenswissenschaften (BWI) bereits vorhandenen Version in Simple++ – automatisch erfolgen. Seite 6/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich In einem ersten Schritt machen Sie sich mit den Regeln des Beer Distribution Game und der dieser Simulation zugrunde liegenden Theorie vertraut. Zeigen Sie anschliessend, welche Anforderungen sich aus den Regeln für die Umsetzung in eine Online-Version ergeben. Berücksichtigen Sie dabei, dass die Simulation beim klassischen Beer Distribution Game in Runden getaktet erfolgt. Schlagen Sie Varianten und Erweiterungen der Regeln vor, die durch die erweiterten Möglichkeiten einer Online-Version möglich sind, und wählen Sie die aus Ihrer Sicht besonders geeigneten aus. Denken Sie daran, dass Sie für die Teilnehmer Möglichkeiten vorsehen müssen, sich zu einer verabredeten Zeit zum Spiel zu treffen. Ferner sind für nicht besetzte Positionen die Bestellungen automatisch anhand eines Bestellmusters zu generieren. Nachdem Sie Anforderungen an die Online-Version des Beer Distribution Game spezifiziert haben, wählen Sie die zur Umsetzung des Konzeptes geeignete Informationstechnologie. Beziehen Sie Java (für Client und Server), sowie Perl und PHP (für den Server) als Programmiersprachen in die Evaluation ein. Prüfen Sie auch, ob der Einsatz von Corba sinnvoll ist. Berücksichtigen Sie bei der Evaluation, dass die Simulation eingebettet in eine Webseite mit gängigen Browsern verwendbar sein soll. Abschliessend erstellen Sie ein geeignetes Objektmodell und nehmen die Implementation vor. Wir erwarten von Ihnen u.a.: l l l l ein angemessenes Studium von Fachliteratur und Webseiten einen strukturierten Quellcode Ihres Programmes eine sorgfältige Dokumentation Ihrer Arbeit1 eine Vorgehensweise nach den Grundsätzen wissenschaftlichen Arbeitens Herr Prof. Dr. Paul Schönsleben und Herr Jörg Nienhaus vom ETH-Zentrum für Unternehmenswissenschaften (BWI) werden Sie in die Thematik einführen und Ihre Arbeit begleiten. Die Beurteilung der Arbeit nimmt Herr Prof. Carl August Zehnder vom Institut für Informationssysteme vor. Zürich, 2. Oktober 2001 Prof. Dr. Paul Schönsleben Jörg Nienhaus, Assistent 1 Als Basis für die formale Gestaltung Ihres Berichtes sowie für eine allgemeine Einführung in die Methodik des wissenschaftlichen Arbeitens empfehlen wir: Theisen, Manuel René: Wissenschaftliches Arbeiten, 8. Aufl., München: Verlag Franz Vahlen, 1997 Seite 7/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 4 Das Beer Distribution Game 4.1 Die Entstehung2 Das traditionelle Beer Distribution Game entstand in den Anfängen der „systems dynamic“ in den sechziger Jahren. Seit drei Jahrzehnten wird das Spiel, welches einen äusserst einfaches, modellhaftes Produktions- und Distributionswerkzeug nachbildet, verwendet als eine Einführung in das Systemdenken in verschiedensten Schulungen, sowie bei Aus- und Weiterbildungskursen. 4.2 Das traditionelle Beer Game3 Unter dem traditionellem Beer Game wird das ursprüngliche Brettspiel verstanden. Vier Spieler bzw. Teams sitzen in einer Reihe und verkörpern eine Lieferkette, nämlich die Fabrik (Factory), das Distributionszentrum (Distribution), den Grosshändler (Wholesaler) und den Einzelhändler (Retailer). Jeder Spieler bestellt bei seinem Lieferanten die notwendigen/vermuteten Bedarfe und versucht gleichzeitig, die Bedarfe seiner Kunden zu decken. Dabei hat er keine Kenntnisse über die Bestellvorgänge ausserhalb seines Bereiches. Die Endkundenbestellungen (externe Nachfrage) werden durch einen verdeckten Kartenstapel vor dem Einzelhändler erzeugt. Die Bestände an Lager, im Wareneingang und auf Transport werden durch Spielsteine (meist eben Bierdeckel) verkörpert. Durch die Felder Wareneingang und Transport werden die Verzögerung durch den Transport und die Warenannahme simuliert. Jeder Spielstein an Lager erzeugt in jeder Spielrunde Lagerkosten von $0.50. Jeder Bestellrückstand (Backlog – Unfähigkeit, dem Bedarf seines Kunden nachzukommen) ergibt pro Spielrunde Kosten von $1. Die Gesamtkosten pro Spielstation setzen sich somit wie folgt zusammen: Gesamtkosten = Σ (Lagerbestände * $0.50) + Σ (Bestellrückstände* $1.00) Das Ziel des Retailers, Distributors, Wholesalers und der Factory ist die Gesamtkosten zu minimieren, entweder individuell oder für das ganze System Zu beachten ist, dass Bestellrückstände doppelt so hoch „bestraft“ werden wie Lagerbestände. Der Moderator des Spiels achtet darauf, dass unter den Spielern keine Informationen (ausser Bestellungen) ausgetauscht werden und veranlasst die Spieler synchron zu spielen. Sämtliche Vorgänge (Bestände, Lieferungen, Bestellrückstände und Bestellungen) werden durch jeden Teilnehmer auf einem Statistikblatt festgehalten. Der Moderator führt die anschliessende Diskussion und Auswertung der Statistikblätter und fasst gewonnene Erkenntnisse zusammen. Die Verblüffung der Spieler während der Besprechung der Ergebnisse besteht im Normalfall darin, dass die externe Nachfrage (Kartenstapel bei Einzelhändler) durchs ganze Spiel hindurch konstant war mit der einzigen Ausnahme von einem Sprung von vier auf acht Einheiten zwischen der vierten und fünften Spielrunde. 2 3 Vgl: Sterman D 1992 Vgl: Burkhalter P. 2001 Seite 8/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 4.3 Theoretische Grundlagen – der Bullwhip Effekt Das Ziel des Beer Games ist es den Teilnehmern den sogenannten „Bullwhip Effekt“ (auch bekannt als „whiplash“ oder „whipsaw“) Effekt auf eine einfache und eindrückliche Art zu demonstrieren4. Der Bullwhip Effekt ist nichts anderes als eine Verstärkung der Variabilität der Nachfrageamplitude über die Stufen einer Supply Chain Kette. Der Grund lässt sich finden in der fehlenden (globaler) Information der tatsächlichen Nachfrage beim Endverbraucher. Oder anders ausgedrückt: Die Bestellmuster teilen eine gemeinsame Eigenschaft. Die Variabilität in der Bestellung für den Lieferanten (upstream) sind immer grösser als die Bestellungen des eigenen Kunden (downstream). Allgemeine Symptome solcher Variabilitäten können übermässige Lagerbestände, schlechte Produktionsvorhersagen, ungenügende oder überhöhte Kapazitäten, schlechter Kundenservice aufgrund von nicht verfügbaren Produkten oder lange Rückstände in der Lieferung sein. Abbildung 1: Zunahme der Variabilität der Nachfrage in der Supply Chain In der Literatur (Lee, Simchi-Levi) werden vier Hauptgründe für die Entstehung des Bullwhip Effekt aufgeführt, welche sich aber schliesslich immer auf die (lokal) fehlende globale Information der Verbrauchsmenge des Endkunden zurückführen lässt. Die Gründe sind: 1. Demand forecast updating Die Vorhersage für die kommenden Bestellmengen wird mangels Informationen oft aus vergangenen Bestellungen des direkten Kunden abgeleitet (und nicht von der tatsächlichen Nachfrage des Endkunden). Diese Vorhersagen werden laufend angepasst und dann an den eigenen Lieferanten weitergegeben. Da die Vorhersagen nicht nur den wahren Verbrauch, sondern auch die jeweiligen Sicherheitsbestände beinhalten, sind die Fluktuationen der 4 Vgl: Lee H.L. et al: 1997 Seite 9/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Bestellmengen über die Zeit oft viel grösser als die tatsächliche Nachfrage. Je länger die Supply Chain ist, desto grösser werden die Fluktuationen. 2. Lead Time Die Durchlaufzeit hat einen signifikanten Einfluss auf die Variabilität der Bestellungen. Gründe lassen sich finden in der Erhöhung der Sicherheitsbestände und somit der Bestellmengen. 3. Batch Ordering Aus Gründen der Kosteneffizienz (Bestell- und Transportkosten) wird oft in Batches (Losen) bestellt. Dabei werden hereinkommende Bestellungen des eigenen Kunden nicht sofort an den eigenen Lieferanten weitergegeben sondern zuerst gesammelt. Die Auslösung einer Bestellung kann zum Beispiel wöchentlich oder monatlich erfolgen oder dann, wenn ein „full truck load“ erreicht wird. Genau dies führt aber zu einer massiven Steigerung der Variabilität der Nachfrage zwischen Kunde und Lieferant und führt zum Bullwhip Effekt. 4. Price fluctuation Bestellungen werden oft nicht kontinuierlich ausgelöst sondern dann getätigt, wenn die Ware gerade günstig gekauft werden kann (Währungsschwankungen, Mengenrabatte, Spezielle Aktionen usw.). Dieses sogenannte „forward buying“ widerspiegelt offensichtlich die tatsächliche Nachfrage nicht und zeigt sich direkt im Bullwhip Effekt wieder. 5. Inflated Orders (Rationing and shortage gaming) Wenn der Lieferant in Verzug ist und die Bestellmengen die Kapazitäten überschreiten, müssen die Lieferungen rationiert werden. Dies geschieht oft so, dass die Kunden nur einen Teil (z.B. 50%) der bestellten Ware erhalten. Ist bekannt, dass Lieferschwierigkeiten auftreten können, bestellen Kunden oft die mehrfache Menge und/oder bei verschiedenen Lieferanten. Diese Reaktion führ zwar zu einem lokalen Optimum (der Kunde erhält schnellstmöglich seine 100%), doch global gesehen führt dies zu einer massiven Überschätzung des Kundenbedarfs. Sobald sich die Lage beruhigt, verschwinden die Bestellungen plötzlich und Bestellannullierungen häufen sich. Lösungsansätze zur Vermeidung des Bullwhip Effekts Natürlich lassen sich die Symptome teilweise auch lokal bekämpfen, indem zum Beispiel Preise stabil gehalten werden (Every day low price – EDLP), Strafen für Annulationen eingeführt werden, die Durchlaufzeiten reduziert oder externe Logistikpartner (kleinere Losgrössen, economy of scale) zugezogen werden. Die einzige Möglichkeit, den Bullwhip Effekt längerfristig zu vermeiden oder zumindest einzuschränken, bleibt aber, die Information über die Nachfrage des Endkunden auf der gesamten Lieferkette global verfügbar zu machen. Dies entspricht grundsätzlich einer Weitergabe der jeweiligen Absatzdaten. Genau dies soll mit dem Beer Game veranschaulicht werden. Seite 10/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 5 Situationsanalyse 5.1 Existierende Varianten des Beer Games 5.1.1 Brettspiel – Klassische Variante5 Beim klassischen Brettspiel werden die Funktionen Retailer, Wholesaler, Distributor und Factory von je einem Mitspieler oder einem Mitspielerteam übernommen. Kundenbestellungen liegen anhand eines vorbereiteten, verdeckten Kartenstapels vor. Die Auswertung erfolgt von Hand, ev. durch Nachführen einer Excel Tabelle mit anschliessender graphischer Auswertung. Exemplarisch ist unten das Ergebnis eines Spiels beim Lieferantentag eines Schweizer Maschinenbau-Unternehmens zu sehen. Die Fotos dieser Seite wurde ebenfalls bei diesem Anlass aufgenommen. Bestellungen 45 40 Abbildung 2: Lieferantentag eines Schweizer MaschinenbauUnternehmens 35 30 Kunde EinzelhŠndler 25 Grossist 20 Verteiler Produzent 15 10 5 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Woche Lagerbestand 40 20 0 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 EinzelhŠndler -20 Grossist Verteiler Produzent -40 -60 -80 -100 Woche Abbildung 3: Das Beer Distribution Game Brettspiel 5 Vgl. Sterman J. D. Seite 11/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 5.1.2 Computerized Beer Game6 Diese Variante liegt anhand einer Windowsapplikation vor. Im Gegensatz zum klassischen Brettspiel kann jedoch nur ein menschlicher Mitspieler daran teilnehmen, die restlichen Funktionen werden vom Computer übernommen. Als Erweiterung zum klassischen Beer Game wurden drei weitere Modi eingeführt: ð Global Information Mode: Lagerbestände und Bestellungen der gesamten Lieferkette sind global verfügbar. Dies erlaubt dem Mitspieler die Bestellungen besser zu planen und die Dynamik der Logistikkette besser zu verstehen. ð Short Lead Time Mode: Die zweistufige Verzögerung durch den Transport und den Wareneingang wird um eine Stufe reduziert. Dies führt zu einer schnelleren Reaktion auf Bestellungen und führt somit normalerweise zu einem kleineren Backlog. ð Centralized Mode: Dem Spieler sind alle Informationen verfügbar. Die Bestellungen werden direkt in der „Factory“ platziert und werden so schnell wie möglich durch das System zum „Retailer“ gesendet (es werden keine Lager ausser beim Retailer selbst geführt). Zudem ist es möglich die Bestellstrategie der Computermitspieler festzulegen. Folgende Optionen sind pro automatischem Mitspieler möglich (s, S, Q und M können als Variablen für jede Stufe festgelegt werden): ð s-S: Sobald der Lagerbestand unter „s“ fällt, wird vom System eine Bestellung platziert, um das Lager wiederum auf die Menge „S“ zu bringen 6 Vgl: Simchi-Levi 2000 (mit beiliegender CD) Seite 12/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich ð s-Q: Sobald der Lagerbestand unter „s“ fällt, wird eine Bestellung der Menge „Q“ ausgelöst. ð Order to S: In jeder Spielrunde platziert das System eine Bestellung so, dass der Lagerbestand wiederum auf „S“ gebracht wird. (direkte Weitergabe der Bestellung vom Kunden an den Lieferanten). ð Order Q: Das System bestellt in jeder Runde konstant Q Einheiten. ð Update s: Wenn der Lagerbestand unter „s“ fällt, wird bestellt, bis „s“ erreicht wird. Die maximale Bestellmenge beträgt „S“. Die eigene Bestellung wird aus dem Schnitt und der Standardabweichung früherer Bestellungen errechnet. ð Echelon: Sobald der Lagerbestand unter „s“ fällt, wird bestellt bis „s“ erreicht wird unter Einhaltung der maximalen Bestellmenge von S. „s“ wird für jede Station in der Lieferkette nach einer modifizierten Version der Standard Echelon Formel berechnet. Die Auswertung des Spiels erfolgt anhand einer einfachen Graphik (vgl. unten) und einer Anzeige der Bestellmenge des Spielers über die einzelnen Spielperioden. Ausserdem lässt sich von allen Stationen den Durchschnitt und die Standardabweichung der Bestellungen anzeigen. Abbildung 4: Graph Auswertung des Computerized Beer Games Seite 13/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 5.1.3 Simple ++ Variante7 Diese Variante wurde entwickelt in einer Studienarbeit am Betriebswissenschaftlichen Institut der ETH Zürich. Es ging darum, verschiedene Bestellstrategien im Beer Game zu testen und auszuwerten. Aus diesem Grund ist wiederum (maximal) nur ein Mitspieler zugelassen, die restlichen Stationen werden interaktiv vom Computer mit der ihm zugeteilten Strategie bedient. Diese Version bietet zudem die Möglichkeit, die Bestellmengen eines gespielten klassischen Beer Games in entsprechende Tabellen einzugeben und danach eine Auswertung dieses Spiels durchzuführen. Folgende Strategien wurden implementiert: ð Nachspielen: Ein klassisches Beer Game wird nachgespielt (Eingabe der jeweiligen Bestellmengen in Tabellen) ð Interaktiv: Einem Mitspieler wird ermöglicht, die eigene Strategie im Wettbewerb mit den verbleibenden Spielstationen und den ihnen zugeteilten Bestellstrategien zu testen. ð Bestellbestand: Die Losgrösse wird anhand der klassischen Andler Formel8 bestimmt. ð MRP (Material Requirements Planning): Hierzu müssen die Bedarfe der Kunden bekannt sein. Diese Strategie macht aus Sicht dieser Arbeit jedoch wenig Sinn, da es im Beer Game genau darum geht, dass diese Informationen nicht vorhanden sind, as den zu demonstrierenden Bullwhip Effekt9 ja auslöst. ð Between Sobald der Lagerbestand unter eine bestimmte Menge (m) fällt wird, eine Bestellung ausgelöst berechnet aus einer Lagerbestandsobergrenze minus die noch an Lager vorhandenen Menge (inkl. Backlog). ð Sensitiv Wie „Between“, ausser, dass für den Lagerbestand auch noch die Bestände in Arbeit (Transport, Wareneingang) berücksichtigt werden. ð Random1X Spezifikation der Parameter Random_min, Random_max sowie Orderstop. 7 Vgl. Burkhalter J.P. 2001 Vgl Schönsleben P. 1998 9 Vgl. Lee H.L. 1997 8 Seite 14/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich ð Strategie1X Es wird grundsätzlich die Bestellmenge des Kunden direkt an den Lieferanten weitergegeben. Variationen werden gemacht im Umgang mit dem Backlog. ð Strategie2X Bestellmenge errechnet sich durch die Mittelung der letzten y Bestellungen (moving average). Variationen liegen wiederum im Umgang mit dem Backlog. Eine ausführliche Beschreibung der einzelnen Strategien sowie deren Auswertung kann in der Studienarbeit nachgelesen werden. 5.2 Beurteilung Das Ziel dieser Arbeit ist es in erster Linie, eine Variante des Spiels zu implementieren, welche es vier Mitspielern ermöglichen sollte, gleichzeitig miteinander online über das Internet im selben Spiel zu spielen. Diese Anforderung wird in keiner der bereits vorhandenen Lösungen erfüllt. Die vorliegende Arbeit soll nun genau diese Lücke schliessen. Seite 15/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 6 Umsetzungskonzept 6.1 Pflichtenheft / Leitlinien für die Umsetzung Aus der Aufgabenstellung lassen sich folgende vier Hauptanforderungen herauslesen: 1. Es soll über das Internet gespielt werden können. Mehrere Spieler sollen an einem Spiel teilnehmen können und gleichzeitig sollen mehrere Spiele gleichzeitig stattfinden können. 2. Das Spiel soll mit möglichst vielen Clients (Betriebssystemen und Browsern) direkt kompatibel sein. Eine hohe Benutzerfreundlichkeit soll angestrebt werden. 3. Die Spieler sollen sich das Spiel, an welchem sie teilnehmen, selber auslesen können. Dies ermöglicht es mit bestimmten Teilnehmern zu einer verabredeten Zeit zu spielen. 4. Die Auswertung der Resultate soll automatisch vorgenommen werden Drei weitere Anforderungen erscheinen sinnvoll: 5. Der Computer soll nicht besetzte Rollen übernehmen können oder Spieler ersetzen, welche ausgefallen sind, damit das Spiel nicht unterbrochen wird. 6. Es soll eine Zeitlimite pro Spielrunde eingeführt werden, damit ein einzelner Spieler nicht das ganze Spiel blockieren kann. Bei Ablauf dieser Zeitlimite übernimmt der Computer die Bestellung für den betreffenden Spieler 7. Die Spielsprache soll englisch oder mehrsprachig sein, da auch Spieler, bzw. Institute aus der Westschweiz (EPFL Lausanne) Interesse an diesem Spiel bekundet haben. 6.2 Auswahl der Programmiersprache, Entwicklungsumgebung In Absprache mit dem Betreuer haben wir uns auf die Programmiersprache Java festgelegt. Als Entwicklungsumgebung wurde der JBuilder (5) von Borland gewählt. Der Client soll demnach in Form eines Java-Applets umgesetzt werden. Es hat sich gezeigt, dass Skriptsprachen wie Perl, PHP, ASP und Javascript für die obigen Anforderungen wenig geeignet sind. Gründe sind unter anderem: è Zuwenig/nicht Interaktiv (z.B. für bewegte Abläufe/Animationen oder clientseitige Validierung von Eingabeparametern) è Code wird bei grossen Programmen schnell unübersichtlich Programmiersprachen haben diese Nachteile weniger. Insbesondere Java hat sich geradezu angeboten für die Umsetzung dieser Aufgabe. Zudem wird bei Java die Möglichkeit geboten, eine ausgewachsene Entwicklungsumgebungen zu verwenden. Seite 16/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 6.3 Java – offene Probleme 10 Um ein Applet sauber zum laufen zu bringen müssen viele Feinheiten beachtet werden. Zum Beispiel variieren die verschiedenen Browser (wie zum Beispiel der Internet Explorer oder Netscape) im Level der Unterstützung des JDK und jeder Browser verfolgt einen anderen Ansatz in der Implementation von Java. 6.3.1 Java Support Ein verbreitetes Problem ist, dass das JDK mit welchem das Applet entwickelt wurde nicht kompatibel sind mit den Virtual Machines (VM) der verschiedenen Browser. Viele Benutzer verwenden noch „alte“ Browser, welche JDK1.1.5 oder frühere Versionen benutzen. Swing wird erst in JDK 1.1.7 eingeführt. Es ist sogar so, dass Java 2 – welches Swing voll unterstützt – teilweise nicht einmal in der neusten Browsergeneration implementiert ist. Folgende Browser unterstützen das JDK 1.1: è Netscape 4.06 (und höher) è Internet Explorer 4.01 (und höher) 6.3.2 Unterschiede in der Java Implementation Es gibt zwar Richtlinien und Spezifikationen welche (Java-) Funktionen von den Browsern unterstützt werden müssen, die Implementation derselben sowie aller Erweiterungen bleibt jedoch den Herstellern überlassen. Ein Unterschied liegt zum Beispiel in der Implementation des „Security Managers“ sowie der „Security Levels“. So wird „Medium Security“ in verschiedenen Browsern verschieden interpretiert und verschiedene Aktionen werden zugelassen oder eben nicht. Ausserdem werden von den Herstellern sogar Anpassungen in der Kernfunktionalität von Java gemacht, inklusive der vollständigen Auslassung bestimmter Eigenschaften. Dies führt zu unterschiedlichen (Fehl-) Verhaltensweisen der Applets in verschiedenen Browsern. Es ist praktisch unmöglich, immer um alle diese Fehler herumzukodieren. Ein weiterer Unterschied ist festzustellen beim Verhalten der Applets auf verschiedenen Betriebssystemen desselben Browsers. Beispielsweise können „multi-threaded applets“ auf einer Plattform laufen, auf einer anderen aber nur sequentiell ausgeführt werden aufgrund von Problemen des „Threading Models“. (Macintosh zum Beispiel unterstützt das Multitasking erst ab System OSX vollständig). Diese Unterschiede machen ein ausgedehntes Testen und Debuggen unumgänglich. 10 Vgl: Web Application Developer’s Guide, Borland Software Corporation Seite 17/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 6.4 Lösungsansätze für die Implementierung11 6.4.1 Ausschliessliche Verwendung von JDK 1.1.x und früher Programmieren der Applets in JDK 1.1.x und früheren Versionen beinhaltet eine Limitation auf die Funktionalität (und Bugs) dieser Versionen. Die meisten Browser unterstützen nun JDK 1.1.5, nicht aber die Swing Komponenten, welche in JDK 1.1.7 eingeführt werden. Wenn Applets mit modernen Entwicklungsumgebungen (basierend auf Java2) geschrieben werden, muss streng darauf geachtet werden, dass nur AWT Komponenten verwendet werden und nicht von den neuen Komponenten Gebrauch gemacht wird. Der grösste Nachteil in der Verwendung von JDK 1.1.x und früher liegt darin, dass die komfortablen, erweiterten Funktionen von Java2 nicht genutzt werden können. Zudem muss Rücksicht auf die Umsetzung in den verschiedenen Browsern und Betriebssystemen genommen werden. 6.4.2 Installieren von nicht Kern Java Klassen auf den Clients Wenn nicht Kern Java Klassen auf den Clients installiert werden, kann unter Anpassung des „Classpath“ ein download umgangen werden für Klassen, welche vom Applet verwendet werden. Dies ist jedoch grundsätzlich nur in einer Intranet-Umgebung möglich. 6.4.3 Unterstützung für nur einen Browsertyp Wenn das Applet für nur eine Version eines bestimmten Browsers programmiert wird, kann der Aufwand für speziellen Code und Unterhalt sowie umfangreichem Debugging minimiert werden. Dies in Kombination mit einer Implementation in JDK 1.1.x würde viele Probleme verhindern. Dies ist jedoch wiederum nur in Intranet Umgebungen möglich, welche durch eine „IS policy“ geregelt werden. 6.4.4 Verwenden des Java Plug-in Die meisten Browserprobleme können durch die Verwendung des Java Plug-in von Sun umgangen werden. Dieses Plug-in ist derzeit die einzige Möglichkeit, allen Browsern dieselbe Version des JDK zur Verfügung zu stellen. Ein grosser Vorteil dieser Lösung zeigt sich ausserdem darin, dass auch die Swing Klassen (Java2) unterstützt werden. Wird von einem Browser zum ersten Mal eine „plug-in enabled“ HTML Seite aufgerufen, erfolgt automatisch die Aufforderung, das Plug-in herunterzuladen. Einmal installiert hat die Client Maschine die offizielle Virtual Machine von Sun und das aktuellste JDK (inklusive Swing) lokal installiert. Der grösste Nachteil bei dieser Variante ist die Notwendigkeit eines einmaligen Downloads von etwa 5MB. 11 Vgl: Web Application Developer’s Guide, Borland Software Corporation Seite 18/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 6.4.5 Verwenden von Java Web Start12 Java Web Start ist eine neue Applikationsentwicklungstechnologie von Sun Microsystems, welche das Ausführen beliebiger Applets oder ausgewachsener Java Applikationen von einem Link auf einer Webseite im Browser ermöglicht. Wenn das Programm nicht bereits auf dem Computer vorhanden ist, werden alle nötigen Daten heruntergeladen und lokal in einem Cache gespeichert, so dass sie auch zu einem späteren Zeitpunkt wieder ausgeführt werden können. Sicherheit wird gewährleistet durch die Ausführung der Programme in einer sogenannten „Sandbox“, welche vergleichbar ist zur „Sandbox“, in welcher die herkömmlichen Applets ausgeführt werden. Ausserdem ermöglicht es Java Web Start die neuste Java 2 Technologie zu verwenden – mit jedem beliebigen Browser. Diese Variante kombiniert die Vorteile einer Java Applikation mit einer sicheren Umgebung. Der Nachteil besteht darin, dass Java Web Start sehr neu ist und noch kaum auf einem Computer installiert ist. Der Download (full package international) hat eine Grösse von 8.7MB. 6.5 Zusammenfassung der Lösungsansätze Lösungsansatz Verwendung von... maximal sJDK 1.1.x Vorteile Nachteile Kein Download nötig - Technologie Es werden verschiedene JDKs wird von den meisten gängigen verwendet welche zu Browsern unterstützt Inkompatibilitäten führen können. Eine Beschränkung auf bestimmte Browserversionen ist notwendig Neuere Features (Swing...) werden nicht unterstützt. Dasselbe JDK für alle Betriebssystem Download des Java Plug-in 1.3 und Browser notwendig (einmalig ca. 5 MB) Java Plug-in Hochgradig kompatibel Unterstützt alle Funktionalitäten von Java 2 (inklusive Swing) Java Web Start Java Applikationen (und Applets) Java Web Start muss lokal installiert können übers Internet in einer sein (einmaliger Download von 8.7 sicheren Umgebung ausgeführt MB) werden. Hochgradig kompatibel Unterstütz alle Funktionalitäten von Java 2 (inklusive Swing) 12 Vgl: http://java.sun.com/products/javawebstart/ Seite 19/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 6.6 Die Wahl Obwohl sowohl das Plug-in als auch Java Web Start grosse Vorteile in Bezug auf plattformübergreifende Kompatibilität aufweisen, wurde zusammen mit dem Betreuer die Lösung der ausschliesslichen Verwendung des JDK 1.1.x gewählt. Der Grund, welcher den Ausschlag gegeben hat liegt in der Benutzerfreundlichkeit. Ein Download wird so nicht vorausgesetzt und jedermann hat direkten Zugriff auf das Spiel. Diese Lösung schliesst jedoch die Verwendung von Swing aus. Zudem wird ein umfangreiches Testen für die verschiedenen Betriebssysteme und Browsern notwendig. Seite 20/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 7 Diskussion und Auswahl der Architektur Eine Client-Server Architektur zeigte sich bald als ideal für die Umsetzung dieses Spieles. Dies aus mehreren Gründen: 1. Applets können aus Sicherheitsgründen nur mit dem Server kommunizieren, von dem sie heruntergeladen worden sind. Eine direkte Kommunikation zwischen Applets ist nicht möglich. 2. Der Server kann alle Spielstände halten – bei Ausfall eines Clients gehen somit keine Informationen verloren. 3. Der Server kann beendete Spiele speichern und für Auswertungen weiterverwenden oder zu einem späteren Zeitpunkt wieder zugänglich machen. Punkt 1 könnte zwar umgangen werden, indem nicht Applets, sondern Java Applikationen auf den Clients verwendet werden. Diese Lösung bietet dem Spielteilnehmer aber die grossen Vorteile der Sicherheit der Applets nicht mehr und kommt aus diesem Grund nicht in Frage. Zudem würde sich eine Client-Server Architektur auch ohne diese Einschränkung geradezu anbieten. 7.1 Umsetzungsentscheid Programmarchitektur 7.1.1 Der Client Der Client wird in Form eines Java Applets umgesetzt. Der Client erhält alle relevanten Daten zum Spiel direkt vom Server und kommuniziert ausschliesslich mit diesem. Das GUI besteht aus obgenannten Gründen (vgl. Umsetzungskonzept) ausschliesslich aus AWT Komponenten. 7.1.2 Der Server Der Server selbst ist eine Java Applikation, welche die gesamte Spiellogik beinhaltet. Neben der Zuweisung der verschiedenen Clients zu den Spielen hat er auch die Aufgabe, verschiedene nicht besetzte Rollen selbst zu übernehmen. Zudem hat er die Aufgabe, beendete Spiele abzuspeichern, um sie zu einem späteren Zeitpunkt wieder abrufen zu können. Seite 21/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 7.2 Kommunikationskonzept In Java gibt es drei Möglichkeiten über das Internet zu kommunizieren. Diese werden nachfolgend kurz besprochen: 7.2.1 http-requests (request/response) Der Vorteil von http-requests ist, dass diese Methode sehr einfach ist. Zudem benutzt sie nur den Port 80 und hat somit keine Probleme mit Firewalls. Http-requests funktionieren aber nur unidirektional, das heisst, der Server kann von sich aus nicht kommunizieren und die Verbindung bleibt nicht dauerhaft bestehen. Da nur über einen Port kommuniziert wird, muss die Benutzer-ID jedem request mitgegeben werden. Ein Broadcast von Nachrichten oder nur schon das Versenden einer Nachricht von Client-Server-Client ist schwierig und setzt ein regelmässiges Abfragen des Servers durch alle Clients voraus. Dies würde für unser Spiel zwangsläufig einen recht hohen Kommunikations-Overhead erzeugen. 7.2.2 Socket connection Die Socket connection bietet eine sehr schlanke und effiziente Kommunikation: Die Verbindung ist bidirektional, das heisst, Client und Server können kommunizieren und die Erkennung der Clients erfolgt über die Portnummern. Die Nachteile dieser Kommunikationsart liegen in der komplexeren Umsetzung – ein eigenes Kommunikationsprotokoll muss implementiert werden. Zudem wird eine Rekonfiguration eventueller Firewalls notwendig, da weitere Ports neben dem Port 80 für die Kommunikation benötigt werden. In unseren Tests mit den interessierten Instituten der EPFL und der UNI Bern zeigten sich aber keinerlei Probleme. 7.2.3 RMI – Remote Method Invocation RMI ermöglicht wie die Socket Connection eine bidirektionale Kommunikation. RMI ist sehr elegant, da die Kommunikation über direkte Methodenaufrufe von Server auf Client und umgekehrt erfolgt. RMI ist objektorientiert. Die Nachteile liegen wiederum in der komplexen Umsetzung – es werden spezielle Stub und Skeleton Klassen benötigt. Zudem ist eine RMI Registry notwendig. Leider hat diese bei Internetverbindungen (noch) oft Schwierigkeiten. Zudem wird eine RMI Verbindung von den meisten HTTP-Proxyservern nicht zugelassen. Dies bedeutet, dass RMI via http getunnelt werden muss, was über die Anbindung an einen beliebigen Port passiert. Eine Rekonfiguration eventueller Firewalls ist auch hier unumgänglich. 7.2.4 Umsetzungsentscheid Für die Umsetzung dieses Spiels haben wir uns für die Socket Connection entschieden. Der Grund liegt vor allem in der bidirektionalen, sauberen und effizienten Kommunikation. Die Socket Connection ist ausserdem weniger problematisch als RMI. Seite 22/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 8 Die Umsetzung Für die Umsetzung wurde je ein Package für den Server und eins für den Client erstellt. Im Folgenden werden die einzelnen Packages (alphabetisch) mit ihren Klassen kurz vorgestellt. Eine ausführlichere Beschreibung der wichtigeren Klassen befindet sich in den Kapiteln 9 und 10. 8.1 Kurzübersicht über die Packages und ihre Klassen 8.1.1 Das Package „beergame.server BeergameServer.java Das eigentliche Programm mit der main() Methode. Beim Aufstarten liest er die gespeicherten PlayerId’s und initialisiert das Spiel. Während der Laufzeit verwaltet er die Spiel- und Spielerlisten. Als Parameter kann die Anzahl zugelassener Verbindungen (resp. Spieler) übergeben werden Diese Klasse sollte in das Startup-Verzeichnis des hostenden Servers aufgenommen werden. (Ausführliche Beschreibung: Abschnitt 9.1, Seite 29) Game.java Die Spiellogik. Verwaltet und steuert das jeweilige Spiel. Hier werden Spiel- und Spielermodi, alle Stati, Namen, Bestellungen, Lagerbestände usw. gespeichert. Ebenso sind alle Methoden enthalten, welche für die Spielsteuerung zuständig sind. Pro Spiel wird eine Instanz dieser Klasse erzeugt. Wird ein Spiel regulär abgeschlossen, wird das jeweilige Objekt auf die Festplatte in den Ordner „Saved Games“ ausgeschrieben. Dies ermöglicht es später, auch bei einem Server-Shutdown wieder auf die Spiele zuzugreifen. (Ausführliche Beschreibung: Abschnitt 9.6, Seite 34) HelloThread.java Der Thread welcher für den Verbindungsaufbau verantwortlich ist. Er hört auf einem vorgegebenen basePort, welcher die Applets auch kennen. Sobald ein Applet gestartet wird, teilt dieser Thread der neuen Verbindung einen fest zugewiesenen Port zu, auf dem die zukünftige Kommunikation dann stattfindet (Ausführliche Beschreibung: Abschnitt 9.2, Seite 30) Seite 23/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich ListenThread.java Pro neue Verbindung wird ein ListenThread erzeugt. Dieser wartet auf Nachrichten des entsprechenden Clients und leitet diese weiter. (Ausführliche Beschreibung: Abschnitt 9.4, Seite 33) PlayerId.java Die Spielerverwaltung. Enthält die Spielerlisten und pro Spieler einen Vektor der bereits gespielten Spiele. Enthält auch die Methoden um gespeicherte Spiele von der Festplatte auszulesen. Diese Klasse wird in regelmässigen Abständen auf Veränderungen geprüft und auf die Festplatte ausgeschrieben. (Ausführliche Beschreibung: Abschnitt 9.5, Seite 33) SendThread.java Pro Verbindung wird ein SendThread eröffnet. Dieser sendet die Servernachrichten an den ihm zugehörigen Client. Der SendThread enthält für den jeweiligen Spieler auch alle relevanten (spielerspezifischen) Informationen. (Ausführlich Beschreibung: Abschnitt 9.3, Seite 30) Strategy.java Diese Klasse enthält die verschiedenen Strategien, welche den Computerspielern zugeteilt werden können. Dazu werden unter anderem Methoden zur Verfügung gestellt, um den Moving-Average sowie die Standardabweichung über beliebige Datensätze zu berechnen. (Ausführliche Beschreibung: Abschnitt 9.7, Seite 37) Seite 24/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 8.1.2 Das Package „beergame.client“ Das Package „beergame.client“ enthält folgende Klassen (in alphabetischer Reihenfolge): BeergameClient.java Das Applet selbst, welches für den Verbindungsaufbau mit dem Server verantwortlich ist zudem vor allem das GUI mit den Action Listeners enthält. (Ausführliche Beschreibung: Abschnitt 10.1, Seite 39) ClientListener.java Ein Thread, welcher auf dem zugewiesenen Port auf Messages vom Server wartet, diese dann verarbeitet und weiterleitet (Ausführliche Beschreibung: Abschnitt10.2, Seite 42) ClientSender.java Ein Thread, welcher Nachrichten über eine Socket Connection vom Client an den Server verschickt (Ausführliche Beschreibung: Abschnitt 10.3, Seite 43) DlgGameStart.java Ein Dialog, der vor dem Spielstart aufgerufen wird und abfragt, ob die Spieler bereit sind. DlgJoinGame.java Ein Dialog, der das Interface zur Verfügung stellt, einem Spiel beizutreten. Er stellt die Funktionen zur Verfügung, die gewünschte Rolle auszuwählen und enthält Infos zum Spiel. Bei Änderungen (z.B. eine Rolle wird an einen anderen Spieler vergeben) wird er automatisch aktualisiert. (Ausführliche Beschreibung: Abschnitt 10.12, Seite 56) DlgJoinRequest.java Ein Dialog der den Spielinhaber um Zutrittserlaubnis für andere Spieler anfragt. DlgLogon.java Ein Dialog, der beim Aufrufen der Beergame Homepage erscheint und den Benutzer nach seinem Namen und Emailadresse (=ID) abfragt. DlgNewGame.java Ein Dialog, welcher dem Spieler die Möglichkeit bietet ein neues Spiel zu kreieren. Unter anderem können hier der Spielmodus, die Computerspieler sowie verschiedene Spielparameter festgelegt werden. (Ausführliche Beschreibung: Abschnitt 10.11, Seite 55) Seite 25/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich GameLogic.java Methoden, welche die eigentliche Spiellogik des Clients enthalten und den gesamten Spielablauf sowie den Status des Clients festhalten und steuern. (Ausführliche Beschreibung: Abschnitt 10.4, Seite 43) ListData.java Eine kleine Klasse, welche (ursprünglich) die vom Server empfangenen Listen identifiziert. Die Klasse wird kaum mehr gebraucht, da der grösste Teil der Listen mittels Arrays und Vectors versandt wird. ModalParent.java Ein Interface, welches bei Dialogen die Parent-Klasse auf Modal stellt (Browser unterstützen keine modalen Dialoge im Moment). NextRoundTransition Ein Thread, welcher an die Klassse gameLogic.java angehängt ist. Er ist u.a. verantwortlich für die Animationen nachdem eine neue Runde gestartet hat (Ankunft Lastwagen, Ankunft der neuen Bestellung) (Beschreibung: Abschnitt 10.6, Seite 48) OrderPerformedTransition.java Eine Thread, welcher an die Klasse gameLogic.java angehängt ist. Er ist verantwortlich für die Animationen. Unter anderem verschiebt er die Fässer und lässt den Lastwagen fahren. (Beschreibung: Abschnitt Error! Reference source not found., Seite Error! Bookmark not defined.) PnlGoodsReceiving.java Ein Panel, welches in BeergameClient.java importiert wird und die Graphik und Animation des Wareneingangs zur Verfügung stellt. (Beschreibung: Abschnitt 10.13, Seite 57) PnlImage Eine allgemeine Klasse, welches die Klasse Panel um die Funktion erweitert ein Bild an einer beliebigen Stelle zu platzieren., PnlSignal.java Ein Panel, welches das Lichtsignal zur Verfügung stellt, welches den Status der einzelnen Spieler anzeigt. Die Lichter können einzeln angesteuert werden. Seite 26/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich PnlStatistics.java Ein Panel, in dem die verschiedenen Auswertungscharts gezeichnet werden. Es werden umfangreiche Funktionen zur Verfügung gestellt um einen oder mehrere Graphen pro Chart zu zeichnen und dynamisch zu aktualisieren. Die Achsen können automatisch den übergebenen Werten angepasst werden. (Ausführliche Beschreibung: Abschnitt 10.10, Seite52) PnlStock.java Ein Panel, welcher die Graphik sowie die Animation für das Lager zur Verfügung stellt. (Beschreibung: Abschnitt 10.13, Seite 57) PnlTransport.java Ein Panel, welcher die Graphik sowie die Animation für den Transport zur Verfügung stellt. (Beschreibung: Abschnitt 10.13, Seite 57) Ref.java Eine Klasse, welche Referenzen (wie Spielerstati, Spielmodi, usw.) auf Integers abbildet, um den Kommunikationsaufwand klein zu halten. Diese Klasse wird auch vom Server importiert. (Ausführliche Beschreibung: Abschnit 10.9,Seite 52) Res.java Eine Klasse, welche alle String Ressourcen enthält. Dies ermöglicht es, den Client in mehreren Sprachen anzubieten. Dieses Klasse wird auch vom Server importiert. (Ausführliche Beschreibung: Abschnitt 10.8, Seite 51) ServerMessage.java Die Klasse, welche die verschiedenen Formate der ServerMessages (Stings, Integers, Arrays, Vectors) definiert sowie alle ServerMessages als Referenz enthält um Programmierfehlern vorzubeugen. (Ausführliche Beschreibung: Abschnitt 10.7, Seite 50) 8.1.3 Das Package „beergame.client.images“ Dies ist ein Sub-Packge von beergame client und enthält die Bildressourcen für das Spiel. Seite 27/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 8.2 Das Spielkonzept Wie bereits erwähnt können die Applets aus Sicherheitsgründen untereinander nicht kommunizieren. Die Clients in der obigen Darstellung haben also keine direkte Verbindung untereinander. Um die verschiedenen Clients im Server unterscheiden zu können wird pro Client ein Send- und ein Listen- Thread erzeugt, welcher die Applets eindeutig identifiziert. Somit entsteht im Server eine virtuelle Abbildung der Clients, welche es den realen Cients ermöglicht quasi direkt miteinander zu kommunizieren. Wird eine Message einem Send Thread mittels der Methode „addMsg“ übergeben, sendet dieser diese Message automatisch an seinen Client weiter. Umgekehrt, wenn der Listen Thread des Servers eine Message von seinem Client erhält, hängt dieser dessen Id an die Nachricht an. Danach wird die Message-Id angeschaut und die Message mit den Message-Parametern an die entsprechende Methode weitergeleitet, welche sich entweder im eigenen SendThread, in einem anderen SendThread oder direkt in der Klasse Game befindet. Eine Identifizierung des Clients im Server ist möglich, da pro Client-Server-Verbindung eine eigene Portnummer verwendet wird und somit jeder ListenThread nur Messages seines eigenen Clients erhält. Seite 28/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 9 Klassen des Package beergame.server 9.1 BeergameServer.java13 Die Klasse BeergameServer ist die main( ) – Klasse des Servers. Um den Server zu starten wird also diese Klasse aufgerufen. Sinnvollerweise sollte diese Klasse automatisch gestartet werden durch ein Bootscript auf dem jeweiligen hostenden Server. Als Argument kann die Anzahl zulässiger gleichzeitiger Verbindungen übergeben werden. Dies java –classpath “ ... “ beergame.server.BergameServer –connections=50 entspricht genau der Anzahl Ports, welche im Server reserviert werden. Standardmässig ist 200 eingestellt. public class BeergameServer extends Thread { public final static int basePort = 8890; static Hashtable playerlist = new Hashtable(); static Hashtable gamelist = new Hashtable(); static boolean availablePort[]; static int maxConnections = 200; private static boolean shutdown = false; private static int warningTime = 60; //minutes private static int socketnum ; ... Als Basis Port ist 8890 eingestellt. Dies bedeutet, dass die erste Verbindung den Port 8890 zugeteilt bekommt, für jede weitere Verbindung wird ein Port höher oder ein freigewordener Port dazwischen verwendet. Der HelloThread, welcher für den Verbindungsaufbau zuständig ist, hört immer auf dem Basisport minus 1, in diesem Fall also auf 8889. Weiter verwaltet die Klasse BeergameServer zwei Hashtables und zwar eine für die Spieler- und eine für die Spielliste. Die Spielerliste enthält die Namen aller Spieler, die Online sind. Damit die Spieler und Spiele auch eindeutig identifiziert werden können, müssen die Namen jeweils eindeutig sein. Sobald ein Spieler sich aber abmeldet oder ein Spiel beendet wurde, steht der Name wieder zur Verfügung. Ein sauberer „Shutdown“ kann vollzogen werden, indem im Verzeichnis, in dem sich auch das Package beergame.server befindet, ein (leeres) Textfile erzeugt oder abgelegt wird mit dem Namen „shutdown.txt“. Dies wird am besten automatisch durch den hostenden Server vollzogen durch einen Eintrag im „Shutdown-script“. Die Klasse BeergameServer prüft mittels eines Threads periodisch (jede Minute), ob sich ein File dieses Namens in dem oben erwähnten Verzeichnis befindet. Falls ja, wird allen Clients eine Meldung geschickt, dass der Server in einer bestimmten Zeit (=warningTime) abgeschaltet wird. Gleichzeitig werden keine neuen Verbindungen mehr akzeptiert. Standardmässig ist hier eine Stunde eingestellt. Diese Zeit sollte genügen, dass alle Spiele noch zu Ende gespielt werden können. 13 Diese Klasse wurde in Anlehnung an das Musterbeispiel des JBuilder programmiert. Vgl. JBuilder 5 Professional, Samples, Chess Seite 29/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich HelloThread.java14. 9.2 Wie der Name der Klasse schon sagt, ist der HelloThread ein Thread, der für den Verbindungsaufbau verantwortlich ist. Der BeergameServer instanziert in der run() Methode genau einen HelloThread. Er liest sich aus dem BeergameServer die maximale Anzahl der Verbindungen aus und reserviert sich die Ports. Die eigene run() – Methode wartet auf eine Verbindung eines Clients auf dem Port 8889. Sobald sich eine Client mit der Message „Hello“ meldet, wird diesem mittels der Methode getPortNumber() des BeergameServers einen neuen Port zugewiesen. Gleichzeitig wird ein neuer SendThread erzeugt, der dann wiederum einen ListenThread für diese Verbindung instanziert. Damit ist eine neue Verbindung eröffnet und der HelloThread wartet wiederum auf den nächsten Verbindungsversuch eines Clients. Der HelloThread besitzt eine finalize()- Methode, welche den Socket bei Abbruch des Programms schliesst. Im Anhang 13.1 befindet sich eine Skizze, welche den gesamten Ablauf ein wenig deutlicher zu veranschaulichen versucht. 9.3 SendThread.java Für jede Verbindung wird durch den HelloThread eine Instanz der Klasse SendThread erzeugt. Die Instanz SendThread hat grundsätzlich zwei Aufgaben. Zum einen leitet sie Nachrichten an den zugehörigen Client weiter, zum anderen ist sie aber auch verantwortlich für das Handling empfangener Nachrichten durch den ListenThread. Der SendThread ist als virtuelles Abbild des Clients im Server zu verstehen. Die Zustände des Clients werden also im SendThread gespeichert: class SendThread extends Thread { public int myChainLink; public boolean isReady =false; public boolean timeout =false; private final int port; private Vector msgque = null; private Vector playedGameVector = null; private ServerSocket serverSocket = null; private ObjectOutputStream os ; private int timeoutCount=0; private boolean isPlaying =false; private Game game = null; private PlayerId playerId; private Strategy strategy; ... è Die Variable „myChainLink“ beinhaltet die Rolle des Clients. (0=Customer, 1=Retailer, 2= Wholesaler, 3= Distributor, 4= Factory). è Der Vektor „playedGameVector“ beinhaltet eine Referenz auf alle Spiel, welche der zugehörige Spieler bereits beendet hat. è In der Variablen „game“ ist die Referenz auf die Instanz des Objekts Game gespeichert, welches der Spieler entweder selbst erzeugt hat, oder bei dem er beigetreten ist. (vgl. auch Klasse Game.java) è Die Variable „playerId“ enthält alle Informationen zum Spieler selbst (vgl. auch Klasse PlayerId.java) è Die Variable "strategy" enthält die Spielstrategien für Computerspieler. Diese wird benötigt, falls der Client unerwarteterweise ausfällt. In diesem Fall übernimmt der SendThread die letzte Bestellung für die aktuelle Runde (falls diese noch nicht ausgeführt ist) und übergibt die gespielte Rolle dem Computer zum weiterspielen (vgl auch Klasse Strategy) 14 Diese Klasse wurde in Anlehnung aus einem mitgelieferten Musterbeispiel des JBuilder programmiert .Vgl. JBuilder 5 Professional, Samples, Chess Seite 30/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Der SendThread arbeitet folgendermassen: Empfangene Nachrichten werden vom ListenThread in den Vektor „msgque“ (=message ... smsg = (ServerMessage) msgque.firstElement(); Queue) eingefügt. Diese Nachrichten msgque.removeElementAt(0); ... werden dann der Reihe nach if (smsg.msgid.equals(ServerMessage.BYE) || smsg.msgid.equals(ServerMessage.DEAD)) { handleBye(smsg); abgearbeitet. Als erstes wird die if (smsg.msgid.equals(ServerMessage.DEAD)) break alive; Message-ID angeschaut und dann } else if (smsg.msgid.equals(ServerMessage.NAME)) aufgrund dieser entschieden, welche handleName(smsg); else if (smsg.msgid.equals(ServerMessage.LIST)) Methode für die Verarbeitung zuständig sendMessage(smsgList(),os); else if (smsg.msgid.equals(ServerMessage.GAME_LIST)) sendMessage(new ist oder ob die Nachricht an den eigenen ServerMessage(0,ServerMessage.GAME_LIST,getGameList()),os); else if (smsg.msgid.equals(ServerMessage.GET_SAVED_GAME)) Client gesandt werden soll (siehe links). handleGetSavedGame(smsg); else if (smsg.msgid.equals(ServerMessage.APPLY_NEW_GAME)) handleApplyNewGame(smsg); else if (smsg.msgid.equals(ServerMessage.CREATE_NEW_GAME)) handleCreateNewGame(smsg); else if (smsg.msgid.equals(ServerMessage.NEW_GAME_CANCELLED)) handleNewGameCancelled(smsg); else if (smsg.msgid.startsWith(ServerMessage.INVITE)) handleInvitation(smsg); else if (smsg.msgid.startsWith(ServerMessage.JOIN)) handleJoin(smsg); else if (smsg.msgid.startsWith(ServerMessage.GAME_ACCEPT)) handleGameAccept(smsg); else if (smsg.msgid.equals(ServerMessage.REFUSE_INVITATION) handleRefuseInvitation(smsg); else if (smsg.msgid.equals(ServerMessage.REFUSED_INVITATION)) handleRefusedInvitation(smsg); else if (smsg.msgid.equals(ServerMessage.R_GAME_UPDATE)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.SETUP_GAME)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.QUIT_GAME)) handleQuitGame(); else if (smsg.msgid.equals(ServerMessage.SETUP_COMPUTER_GAME)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.ADD_PLAYER)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.REMOVE_PLAYER)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.GAME_IS_READY)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.CLIENT_READY)) handleClientReady(smsg); else if (smsg.msgid.equals(ServerMessage.CLIENT_REPORTS_READY)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.START)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.ORDER_PERFORMED)) handleOrderPerformed(smsg); else if (smsg.msgid.equals(ServerMessage.TIME_UP)) handleTimeUp(smsg); else if (smsg.msgid.equals(ServerMessage.CLIENT_REPORTS_TIME_OUT)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.HUMAN_PART_TO_COMPUTER)) sendMessage(smsg,os); else if (smsg.msgid.equals(ServerMessage.NEXT_ROUND)) sendMessage(smsg,os); else if (smsg.msgid.startsWith(ServerMessage.CHAT)) handleChat(smsg); else if(smsg.msgid.equals(ServerMessage.STATUS)) sendMessage(smsg,os); else if(smsg.msgid.equals(ServerMessage.GAME_OVER)) { sendMessage(smsg,os); handleGameOver(); ... Es würde wenig Sinn machen, auf jede Methode einzeln einzugehen, die wichtigsten werden aber im Folgenden kurz beschrieben: • addMsg(): Fügt eine neue Message in den Vector „msgque“ ein • handleName(): Identifiziert den Spieler anhand seiner Email-Adresse und prüft das Passwort. Dieses ist momentan standardmässig auf „password“ eingestellt und wird nicht abgefragt. Falls der Spieler existiert, wird eine Liste seiner bereits gespielten Spiele zurückgegeben, ansonsten wird eine neue Spieler-Id (=PlayerId) erzeugt. • handleChat(): Sendet je nach Spielmodus eine Chatnachricht als Broadcast an alle Spieler, nur an die Spieler des eigenen Spiels oder gar nicht weiter. • sendToAllComakers(): Diese Methode sendet eine übergebene Nachricht an alle Mitspieler weiter. • • updateIdlePlayers() und updateGameList(): Führen die jeweiligen Listen in den Clients nach. • handleApplyNewGame(): Prüft, ob der Spieler berechtig ist, ein neues Spiel zu erzeugen. Falls der Spieler noch kein Game referenziert hat und „isPlaying=false“ ist, wird GRANT_NEW_GAME zurückgegeben, ansonsten REFUSE_NEW_GAME. Seite 31/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich • handleCreateNewGame(): Erzeugt ein neues Objekt „game“ mit den übergebenen Parametern • handleNewGameCancelled(): Setzt den Spieler wieder auf „isPlaying=false“ zurück und aktualisiert die Spielerliste. • handleJoin(): Diese Methode ist verantwortlich für den gesamten Prozess einem Spiel als Spieler beizutreten und danach bei Erfolg dem neuen Spieler die Spielparameter zu übergeben(setupThisPlayer). Dabei wird im SendThread des neuen Spielers unter anderem eine Referenz auf die entsprechende Objekt „game“ gemacht. • handleInvitation(): Ist ähnlich wie die handleJoin() Methode, nur, dass hier ein Spieler eingeladen wird, einem Spiel beizutreten. Der eingeladene Spieler kann die Einladung entweder annehmen (und seine Rolle aussuchen) oder ablehnen. • handleClientReady(): Prüft vor dem Spiel, ob alle Spieler zugewiesen und bereit sind und startet danach das Spiel. • handleTimeUp(): Falls ein Spieler die Zeitlimite überschritten hat, übernimmt der Computer die Bestellung für die entsprechende Rolle. Dies verhindert, dass ein einzelner Spieler das gesamte Spiel blockieren kann. • handleOrderPerformed(): Diese Methode übernimmt die Bestellung des jeweiligen Clients und informiert die anderen Mitspieler über den neuen Status (was zum Beispiel zu einem Wechsel des Lichtsignals in den Applets führt). Sobald alle menschlichen Mitspieler die Bestellung ausgeführt haben (entweder eigenhändig oder durch timeout), wird die Transition für die nächste Runde ausgelöst. • handleGameOver(): Dem entsprechenden Spieler wird einen Eintrag in den Vektor „playedGames“ in der PlayerId gemacht. Dadurch ist es möglich, später die Auswertung des Spieles wieder anzuschauen. • handleGetSavedGame(): Liest das gewünschte Spiel von der Festplatte aus und sendet die Parameter an den Client. • handleQuitGame(): Diese Methode wird aufgerufen wenn der Client ein Spiel beendet hat, oder ausgefallen ist. Falls das Spiel noch läuft wird der Client durch einen Computerspieler ersetzt. Falls der Client einem Spiel zugewiesen war, das Spiel aber noch nicht gestartet hat wird die Rolle wieder für andere Spieler verfügbar gemacht. Ist das Spiel regulär beendet worden, wird die Referenz auf das Objekt Game auf null gesetzt, aus der Spielliste entfernt und das Objekt der Garbage Collection überlassen. Seite 32/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 9.4 ListenThread.java Pro Client-Server Verbindung wird ein ListenThread instanziert, welcher auf Nachrichten des Clients wartet. Pro Verbindung existieren also immer ein Listen- und ein SendThread-Objekt im Server. Der ListenThread wird durch den SendThread erzeugt, welcher als Parameter gerade sich selbst sowie den zur Verbindung zugehörigen Socket übergibt. ... Sobald eine Message ankommt, wird der Message als Id den smsg.port = sendThread.getPort(); Port zugewiesen, auf dem sie hereingekommen ist und direkt sendThread.addMsg(smsg); ... dem SendThread mittels der Methode „addmsg()“zur weiteren Verarbeitung übergeben. 9.5 PlayerId.java Die Klasse PlayerId verwaltet die verschiedenen Spieler sowie deren Daten. Die Spieler werden anhand ihrer E-Mailadresse identifiziert. Die E-Mailadresse hat den Vorteil, dass sie weltweit eindeutig ist. Der Einfacheit halber wird aber intern diese Id auf eine Nummer abgebildet. Dies ist vor allem nützlich für die Indexierung in den Hashtables usersByUser, usersById und usersByEmail. Pro Spieler werden die E-Mailadresse, das Passwort und der Name sowie die gespielten Spiele abgespeichert, wobei momentan das Passwort standardmässig auf „password“ eingestellt ist und von den Benutzern nicht eingegeben wird. Ausserdem verwaltet es Methoden, um beendete Spiele ebenfalls als Objekte auf die Festplatte auszuschreiben. Das Zieldirectory ist der Ordner „SavedGames“, welcher im gleichen Verzeichnis liegt wie das beergame.server Package. Das Objekt PlayerId („implements Serializable“). wird selbst periodisch auf die Festplatte in das Objekt „users.obj“ ausgeschrieben Nach einem Server-Shutdown wird es jeweils wieder frisch eingelesen. Auf diese Weise gehen die Daten auch bei einem Serverausfall nicht verloren. Auch hier werde ich ganz kurz auf die wichtigsten Methoden eingehen: • addPlayer(): Erzeugt – falls die Id noch nicht existiert – einen neuen Spieler mit den übergebenen Parametern. • update() und updatePlayer(): Aktualisieren die Daten des Spielers. • writeUserFile(): Schreibt die Instanzen des Objekts PlayerId in das Objekt „users.obj“ auf die Festplatte über einen ObjectOutputStream aus. • readUserFile(): Versucht, die Spielerliste (users.obj) von der Festplatte einzulesen. Diese Methode wird jeweils beim Start des „BeergameServer“ aufgerufen • saveGame(): Schreibt ein beendetes Spiel über einen ObjectOutputStream auf die Festplatte aus. Diese Methode wird durch die jeweilige Instanz des Objekt „Game“ in der Methode gameOver() aufgerufen. • getSavedGame(): Liest ein gespieltes Spiel von der Festplatte aus und sendet die Parameter an den aufrufenden Client zurück. Ein Client sieht nur die Spiele, an denen er selbst beteiligt war. Wollen andere Spiele eingesehen werden, muss man sich unter der jeweiligen anderen EmailSeite 33/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Adresse einloggen. Dies ist vor allem der Fall, wenn während einer Vorlesung oder Übungsstunde die Spiele nochmals besprochen werden sollen. 9.6 Game.java Die Klasse Game ist eigentlich die Kernklasse des ganzen Spiels. Sie enthält alle Daten, Zustände und Modi der jeweiligen Spiele. Jedes Spiel kennt seine Mitspieler durch eine Referenz auf die jeweiligen SendThreads. Diese Referenzen werden in einem Array verwaltet, wobei die Arraynummer gleichzeitig die ID für die Spielrolle ist. Der Retailer hat die ID 1, der Wholesaler die 2, der Distributor die 3 und die Factory die 4. Vom Computer selbst werden noch die 0 (Customer) und die 5 (Supplier) verwaltet. Weiter kennt jedes Objekt „game“ (=Instanz de Klasse Game) den Spielinhaber. Auch dieser wird über eine Referenz auf seinen SendThread in der Variablen „gameOwnerSendThread verwaltet“. Der Spielinhaber ist normalerweise der Erzeuger des Spiels. Fällt dieser jedoch aus, wird automatisch derjenige menschliche Mitspieler Spielinhaber, welcher die kleinste SpielerId hat. Gleichzeitig mit dem Übergang wird die Variable „needsPermissionToJoin“ auf „false“ gesetzt. Verbleiben keine menschlichen Mitspieler mehr, wird das (Objekt) Spiel gelöscht. Für die Spieldaten werden drei zweidimensionale Arrays verwaltet und zwar für die Bestellungen (order[ ][ ]), den Lagerbestand(stock[ ][ ]) und die ausgelieferten Güter (goodsInTransport[ ][ ]), wobei der erste Index jeweils die Runde und der zweite die Id des Comakers bzw. Mitspielers angibt. Die Arrays haben also die Dimension [#Spielrunden][Länge der Supply Chain], wobei die Länge der Supply Chain 6 ist inklusive des Customers und des Suppliers. Weitere Arrays, welche verwaltet werden: comakerName[ ] Die Namen der jeweiligen Mitspieler comakerStatus[ ] Der Status der jeweiligen Spieler (Mensch, Computer, Tentative, Idle...) comakerStrategy [ ] Die Strategie des jeweiligen Computerspielers comakerReady [ ] Der Zustand des jeweiligen Spielers (ready, not_ready, timout..) Die wichtigsten Integers: currentRound die aktuelle Runde im Spiel timePerRound die Zeit, welche pro Bestellung zur Verfügung steht und bei Ablauf zu einem Timeout führt gameLength die Anzahl Runden des aktuellen Spiels gameMode der Modus des Spiels (Klassisch, Klassisch Plus, Computer Simulation) openGame der Status des Spiels, bestimmt, ob Spieler nur eingeladen (INVITE-Funktion) werden können oder ob diese auch selbständig dem entprechenden Spiel beitreten (JOIN-Funktion) können. Seite 34/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich chatMode bestimmt, ob Chat aktiviert ist oder nicht Die wichtigsten Booleans: isRunning zeigt an, ob das Spiel im Gange ist humansPlaying zeigt an, ob menschliche Mitspieler am Spiel beteiligt sind oder ob eine Computer Simulation läuft Jedes Spiel erhält eine Spiel-ID, welche relevant ist, um gespeicherte Spiele wieder laden zu können. Diese setzt sich zusammen aus dem Datum (Jahr, Monat, Tag, Stunde, Minute) sowie dem Spielnamen und der Endung „.obj“. Diese ID ist eindeutig, da auch der Spielname selbst zur Laufzeit eindeutig ist. Ein Beispiel einer Spiel-ID könnte so aussehen: 2002.1.18 14.17 Cardinal.obj Diese Id wird gerade auch als Namen des entsprechenden Spiel-Objekts verwendet welches auf der Festplatte abgespeichert wird und auch so in der Liste der gespielten Spiele angezeigt. Wird ein neues Spiel erzeugt, werden alle übergebenen Parameter des Clients gesetzt und der „Initial State“ erzeugt. Das Spiel beginnt bei Runde 1. Runde 0 wird vom Computer vorgegeben und entspricht für alle Comakers einer Bestellung von 4. In Runde 1 sind jeweils 12 Stück an Lager, 4 im Wareneingang und 4 im Transport für jeden Comaker. Falls der Spielmodus auf Computer Simulation eingestellt ist, wird das gesamte Spiel mit den vorgegebenen Strategien durchgerechnet und am Schluss die Resultate dem Client gesamthaft zur graphischen Auswertung und Darstellung übergeben. Ansonsten wird geprüft, ob der Spielerzeuger alleiniger Spieler ist und falls ja wird das Spiel gestartet. Haben zusätzliche Rollen den Status „Human“, wird gewartet, bis alle Spieler zugewiesen sind. Im Folgenden werden wiederum kurz die wichtigsten Methoden beschrieben: • smsgGameInfo( ): Hat als Rückgabewert den Typ ServerMessage und liefert relevante Informationen zu einem bestehenden Spiel. Diese Methode wird gebraucht, wenn ein Client ein Join ausführt oder eine Invitation erhält. • updateGameRegistrationDialog( ): Diese Methode schickt eine Message zu den Clients, um die verfügbaren Rollen in der Eingabemaske „PnlJoinGame“ zu aktualisieren. Dies ist notwendig, da die Zuweisung von Rollen an Spieler parallel erfolgen kann. Sobald ein Spieler eine Rolle zugeteilt bekommen hat, werden in den Clients die Buttons für diese Rolle auf inaktiv geschaltet. Sobald alle Rollen vergeben sind, schliessen sich Eingabemasken in den Clients mit einer Meldung, das Spiel sei ausgebucht, automatisch wieder. • checkGameReady(): Prüft, ob alle Rollen entweder an menschliche Mitspieler oder an den Computer vergeben sind und schickt – falls diese Bedingung erfüllt ist - die Meldung GAME_IS_READY an die Spieler dieses Spiels. Seite 35/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich • checkGameStart(): Diese Methode wartet auf die Bestätigung aller Clients, dass sie bereit seien und startet danach das Spiel durch die Methode startGame(). • smsgSetupGame(): Liefert als Rückgabewert eine ServerMessage, welche an den entsprechenden Client versandt wird. Diese Message enthält alle relevanten Parameter, um den Client für das entsprechende Spiel aufzusetzen. • makeTransition(): Diese Methode wird jeweils am Ende einer Spielrunde aufgerufen, wenn alle Spieler ihre Bestellung aufgegeben haben (entweder regulär oder durch timeout). Sie enthält den Ablauf für den Übergang von einer zur nächsten Runde. Dazu werden zum einen für jeden Spieler die Bestände berechnet und nachgeführt (Lager, Wareneingang, Transport). Anschliessend werden die Spieler wieder auf „isReady=false“ gesetzt und „currentRound“ um eins erhöht. Danach werden direkt die Bestellungen der Computerspieler (falls vorhanden) für die nächste Runde mit den entsprechenden Strategien berechnet (Methode makeComputerOrders()) und die Computerspieler wieder auf „isReady=true“ gesetzt. Zum Schluss wird geprüft, ob das Spiel im Modus Computersimulation läuft. Falls ja wird direkt (rekursiv) wiederum die Methode makeTransition() aufgerufen. Ansonsten werden an die Mitspieler die ServerMessage NEXT_ROUND versandt und auf deren Bestellungen gewartet. Ist die letzte Spielrunde erreicht (currentRound==gameLength), wird die Methode handleGameOver() oder handleComputerGameOver() aufgerufen. • sendToAllComakers(): Diese Methode existiert auch in der Klasse SendThread selbst. Im Gegensatz dazu wird hier aber die übergebene Message an die SendThreads aller menschlichen Mitspieler verteilt. Dieselbe Methode in den SendThreads schickt die Message nur an die anderen SendThreads der menschlichen Mitspieler (und nicht an sich selbst). • handleGameOver(): Diese Methode beendet das Spiel und setzt alle SendThreads auf Null. Dies ist notwendig, um das Spiel danach auf die Festplatte mittels der Methode PlayerId.saveGame(this) auszuschreiben. (Es soll ja nur das Objekt „game“ und nicht die SendThreads auch gespeichert werden.) • handleComputerGameOver(): Diese Methode wird am Ende eines Spiels aufgerufen, welches im Simulationsmodus durchgerechnet wurde. In diesem Falle besitzt der Client, welcher die Simulation spezifiziert hat noch keine Daten. Diese werden nun zusammengestellt und dem entsprechenden Client mittels eines Vectors in einer ServerMessage zur Verfügung gestellt. Spiele, welche im Simulationsmodus durchgerechnet worden sind, werden nicht abgespeichert, da sie einfach reproduzierbar sind. Seite 36/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 9.7 Strategy.java Die Klasse Strategy enthält alle Strategien, welche den Computerspielern zur Verfügung stehen. Zurzeit sind drei Strategien implementiert: Strategie keepTotalGoodsZero(): Diese Strategie berechnet, wie viel bestellt werden muss, um gerade auf den Lagerbestand null zu kommen. Grundsätzlich ist diese Strategie ungeeignet, da sie keine Rücksicht auf vergangene Bestellungen oder Sicherheitsbestände nimmt. Sie war geplant für den Einsatz bei einem Spielertimeout. Sie wird im Moment aber nicht mehr eingesetzt. Strategie keepLevelOfStock(): Dies ist für den vorgegebenen klassischen Spielablauf (resp. Bestellmuster) systembedingt eigentlich beinahe die idealste Strategie. Sie gibt die erhaltenen Bestellungen jeweils 1:1 weiter. Es wird somit angestrebt, die gesamten Waren in Transport auf einem konstanten Niveau zu halten. Dies verhindert riesige Überbestellungen und gleichzeitig die damit verbundenen Lieferschwierigkeiten der Supplier. Strategie movAvrgStdDev(): Diese Strategie verhält sich ähnlich wie die menschlichen Mitspieler. Sie berechnet aus dem gleitendendem Mittelwert (moving average) und der Standardabweichung die gewünschte Menge an Artikel in der eigenen Fabrik. Diese wird dann verglichen mit der tatsächlichen Menge und sollte die Differenz negativ sein, wird eine Bestellung ausgelöst. Die Definition „alle Artikel“ sieht folgendermassen aus (Anmerkung: negativer Lagerbestand bedeutet Lieferrückstand): ActualTotalGoods= Lager (negativ oder positiv) + Bestand im Wareneingang + Bestand im Transport + bestellte, aber noch nicht gelieferte Artikel + letzte Bestellung. Der gewünschte Bestand an totalen Artikeln wird mit folgender Formel berechnet: PreferredTotalGoods = ceil (delay * movingAverage + securityFactor * standardDeviation) , wobei der Delay der Verzögerung zwischen Bestellung und Lagereingang entspricht. Die Bestellung sieht nun folgendermassen aus: Order = (PreferredTotalGoods>ActualTotalGoods) ? PreferredTotalGoods-ActualTotalGoods : 0; Wird also lange Zeit auf einem konstanten Nivea bestellt, wird die Standardabweichung kleiner und der Sicherheitsbestand wird gesenkt. Sind die Schwankungen in den Bestellungen gross, wird auch der Sicherheitsbestand erhöht. Dies führt über die gesamte Supply-Chain eben genau zum Bullwhip-Effekt, welcher sich hier sehr schön zeigt, da jeder Comaker eine grössere Bestellung weitergibt als vom Vorgänger erhalten. Diese Bestellstrategie wird momentan auch eingesetzt bei einem Timout oder Totalausfall eines menschlichen Mitspielers. Seite 37/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Wiederum werden kurz die wichtigsten Methoden dieser Klasse beschrieben: • KeepLevelOfStock(): berechnet den Rückgabewert für die Strategie „Keep Level Of Stock“ • MovAvrgStdDev(): berechnet den Rückgabewert für die Strategie des gleitenden Mittelwertes und der Standardabweichung. • KeepTotalGoodsZero(): berechnet den Rückgabewert für die gleichnamige Strategie. • GetTotalGoods(): berechnet für einen Comaker die totale Anzahl Waren, welche sich bei ihm im Umlauf befinden oder bestellt sind. • GetStandardDeviation(): berechnet aus einem gegebenen Array die Standardabweichung aus den Daten der letzten x Perioden. • GetMovingAverage(): berechnet aus einem gegebenen Array den gleitenden Mittelwert für die letzten x Perioden. Seite 38/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10 Klassen des Package beergame.client 10.1 BeergameClient.java Diese Klasse ist das eigentliche Applet (extends Applet). Sie hat zwei Hauptaufgaben: Zum einen ist sie für den Verbindungsaufbau zum Server verantwortlich, zum anderen stellt sie den grössten Teil der Komponenten (inklusive ActionListeners) für das Graphical User Interface (GUI) zur Verfügung. Ebenso verwaltet sie den Status und spezifische Informationen zum Spieler, welche nicht den Spielablauf betreffen. Sobald das Applet geladen ist wird direkt nach dem Konstruktor die Methode „startSocket“ aufgerufen. Diese liest als erstes die Sprache des Benutzerinterfaces aus den Parametern der aufrufenden HTML Seite aus und versucht danach die Socketverbindung zum HelloThread des Servers zu starten. Ist der Verbindungsaufbau erfolgreich, wird vom Server die Portnummer für die zukünftige Kommunikation zurückgegeben. Ansonsten wird auf dem Client eine Fehlermeldung ausgegeben. Parallel dazu beginnt die Initialisierung des Applets bzw. aller Komponenten, welche für den Benutzer sichtbar sind - unter Berücksichtigung der gewählten Sprache. Als letztes wird der LogonDialog aufgerufen, welcher es dem Benutzer ermöglicht, sich für das Spiel anzumelden. Damit ist der Client aufgesetzt und für die weiteren Schritte bereit (vgl auch Screenshots ab Seite 60). Die wichtigsten Variablen: Name Typ Beschreibung host String IP-Adresse des Servers language String Die Sprache des Benutzerinterfaces. Wird beim Applet Start als Parameter aus dem HTML-Text ausgelesen. pass String Das Passwort des Benutzers. login String Der Benutzername des Spielers. Dieser muss unter allen angemeldeten Spieler eindeutig sein. loggedOn Boolean Gibt an, ob der Benutzer beim Server angemeldet ist. res RessourceBundle Enthält alle Strings der ausgewählten Sprache (language), auf welche mit einem Schlüssel zugegriffen wird. ourServer ClientListener Ein Listener-Thread, welche auf Nachrichten des Servers wartet. gameLogic GameLogic Klasse, welche alle Methoden und Zustände des laufenden Spiels enthält, an dem sich der Spieler beteiligt. Seite 39/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich clock Thread Stellt die Timer Funktion zur Verfügung, welche die verbleibende Zeit pro Runde angibt. base URL Die URL des Hosts. Wird benötigt als Referenz für den Speicherort der Bilder. mt MediaTracker Tracker, welche den Downloadstatus der Bilder prüft. clrComaker Color Die zugewiesenen Farben der einzelnen Comakers Die wichtigsten Methoden: • startClock(): erzeugt einen neuen Thread „clock“ und startet diesen (Methode run()). Diese Methode wird zu Beginn jeder Spielrunde aufgerufen. • stopClock()/resumeClock(): hält die Uhr an beziehungsweise lässt sie weiterlaufen. Diese zwei Methoden werden zurzeit nicht mehr verwendet. • destroyClock(): zerstört den Thread clock • run(): lässt den Timer von der Startzeit (ein Spielparameter) an abwärts laufen. Ist die verbleibende Zeit kleiner als 30s, wird die Farbe des Timers auf gelb, bei weniger als 10s auf rot umgestellt. Hat der Timer 0 erreicht, wird die Methode gameLogic.handleTimeUp() in der Klasse gameLogic aufgerufen. • fillPlayerList(): aktualisiert die Liste der Spieler. Angezeigt werden jeweils die Spieler, welche online sind und noch keinem Spiel zugewiesen sind. Da die Spieler ausgewählt werden können, wird jeweils nicht die gesamte Liste ersetzt (was die Selektion auch löschen würde), sondern es werden nur diejenigen Spieler hinzugefügt, welche neu sind und diejenigen Spieler entfernt , welche in der neuen Liste nicht mehr erscheinen. • fillGameList(): analog zur Methode fillPlayerList(). Es werden jeweils diejenigen Spiele angezeigt, welche noch Plätze für Mitspieler frei haben. • fillPlayedGameList(): Methode, welches die Liste der bereits gespielten Spiele aktualisiert. Direkt nach der Anmeldung werden die dazu notwendigen Parameter dem Client vom Server für den betreffenden Spieler übergeben, welcher dann diese Methode aufruft. Seite 40/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich • showMessageDialog(): ruft ein neues Fenster auf mit den übergebenen Strings als Message und Titel. Dieser Dialog wird hauptsächlich für Statusmeldungen und Warnungen verwendet. • showGraphWindow(): ruft ein neues Fenster mit dem übergebenen StatisticsPanel auf. Diese Methode kann dazu verwendet werden, die einzelnen Auswertungsgraphiken vergrössert darzustellen. Diese Methode ist aber in der jetzigen Version nicht in Verwendung. Sie kann für eine spätere Erweiterung des Programms eingesetzt werden • bttnLogon_actionPerformed(): je nach Status wird hier der Spieler ein- oder ausgeloggt. Ist der Spieler noch nicht eingeloggt, wird die Methode startSocket() aufgerufen, ansonsten wird die Socketverbindung geschlossen. • bttnInvite_actionPerformed(): liest den markierten Spieler aus der „PlayerList“ aus und prüft die Auswahl auf Korrektheit (nicht der eigene Name, nicht kein Namen) und ruft bei Erfolg die Methode gameLogic.invitePlayer() auf, welche den Parameter für die weitere Verarbeitung schliesslich an den Server sendet. Gleichzeitig wird dem Spieler eine Meldung über die erfolgte Einladung ausgegeben. • bttnJoinGame_actionPerformed(): liest das markierte Spiel aus der „GameList“ aus und extrahiert den Spielnamen (Name des Spiels ohne bereits zugewiesene Spieler). Anschliessend wird die ServerMessage JOIN mit diesem Parameter an den Server bzw. den eigenen Listen-und SendThread im Server versandt, welcher die weitere Verarbeitung und Prüfung übernimmt. Dem Spieler wird eine Meldung ausgegeben, dass die Anfrage erfolgt ist. • bttnNewGame_actionPerformed(): Sendet die ServerMessage „APPLY_NEW_GAME“ an den Server, welcher weitere Prüfungen übernimmt und bei Erfolg eine Message „GRANT_NEW_GAME“ zurückschickt, welche im Client dann zum Offnen des Panels „PnlNewGame“ führt. • bttnViewGame_actionPerformed() liest aus der Liste„ListPlayedGames“ das markierte Spiel aus, prüft die Auswahl und sendet diesen Parameter bei Erfolg in der ServerMessage GET_SAVED_GAME an den Server. Dieser versucht, das gewünschte Spiel von der Festplatte auszulesen und sendet alle notwendigen Parameter für die Auswertung an den Client zurück. Weiter sind verschiedene ActionListeners für die Auswertung der Graphiken implementiert, welche es erlauben, einzelne Graphen ein- oder auszublenden, den Verlauf der Graphen über die einzelnen Runden zu verfolgen oder die Auswertungsgraphiken von den Einzelgraphen auf die überlagerten Graphen umzuschalten. Seite 41/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.2 ClientListener.java Diese Klasse (ein Thread) ist das Gegenstück zum SendThread im Server. Es hört über einen ObjecteInputStream auf Nachrichten vom Server und leitet diese entsprechend der MessageID an die verantwortlichen Methoden weiter. Eine ... if (smsg.msgid.equals(ServerMessage.BYE)) { Instanz dieser Klasse wird in der Methode bgc.logonEnable(false); //disable the world break; „startSocket“ des BeergameClient } else if (smsg.msgid.equals(ServerMessage.WELCOME)) aufgerufen. Die meisten Messages werden handleWelcome(smsg); else if (smsg.msgid.equals(ServerMessage.STATUS)) direkt in der Klasse „GameLogic“ behandelt, handleStatus(smsg); else if (smsg.msgid.equals(ServerMessage.PLAYERNAME_IN_USE)) ein paar wenige in dieser Klasse selbst. handlePlayerNameInUse(); else if (smsg.msgid.equals(ServerMessage.PLAYED_GAMES)) bgc.fillPlayedGameList(smsg); else if (smsg.msgid.equals(ServerMessage.INVITED)) bgc.gameLogic.handleInvited(smsg); else if (smsg.msgid.equals(ServerMessage.GRANT_NEW_GAME)) bgc.gameLogic.handleGrantNewGame(smsg); else if (smsg.msgid.equals(ServerMessage.REFUSE_NEW_GAME)) handleRefuseNewGame(smsg); else if (smsg.msgid.equals(ServerMessage.GAME_CREATED)) handleGameCreated(smsg); else if (smsg.msgid.equals(ServerMessage.GAME_NAME_EXISTS)) handleGameNameExists(smsg); else if (smsg.msgid.equals(ServerMessage.REFUSED_INVITATION)) handleRefusedInvitation(smsg); else if (smsg.msgid.equals(ServerMessage.JOIN_REQUESTED)) bgc.gameLogic.handleJoinRequest(smsg); else if (smsg.msgid.equals(ServerMessage.JOIN_REFUSED)) bgc.showMessageDialog(bgc,bgc.res.getString("JOIN_REFUSED"),"Info"); else if (smsg.msgid.equals(ServerMessage.GAME_ACCEPTED)) bgc.showMessageDialog(bgc,MessageFormat.format(bgc.res.getString("ACCE PTED_YOUR_INVITATION"),new Object[]{smsg.msg[0]}),"Info"); else if (smsg.msgid.equals(ServerMessage.R_GAME_UPDATE)) bgc.gameLogic.handleUpdateJoinGame(smsg); else if (smsg.msgid.equals(ServerMessage.NOT_ASSIGNED)) bgc.showMessageDialog(bgc,bgc.res.getString("COULD_NOT_ASSIGN"),""); else if (smsg.msgid.equals(ServerMessage.SETUP_GAME)) bgc.gameLogic.setupGame(smsg); else if (smsg.msgid.equals(ServerMessage.SETUP_COMPUTER_GAME)) bgc.gameLogic.handleSetupComputerGame(smsg); else if (smsg.msgid.equals(ServerMessage.ADD_PLAYER)) bgc.gameLogic.handleAddPlayer(smsg); else if (smsg.msgid.equals(ServerMessage.REMOVE_PLAYER)) bgc.gameLogic.handleRemovePlayer(smsg); else if (smsg.msgid.equals(ServerMessage.GAME_IS_READY)) bgc.gameLogic.handleGameIsReady(smsg); else if (smsg.msgid.equals(ServerMessage.CLIENT_REPORTS_READY)) bgc.gameLogic.handleOtherClientReady(smsg); else if (smsg.msgid.equals(ServerMessage.CLIENT_REPORTS_TIME_OUT)) bgc.gameLogic.handleOtherClientTimeOut(smsg); else if (smsg.msgid.equals(ServerMessage.HUMAN_PART_TO_COMPUTER)) bgc.gameLogic.handleHumanPartToComputer(smsg); else if (smsg.msgid.equals(ServerMessage.START)) bgc.gameLogic.handleStart(); else if (smsg.msgid.equals(ServerMessage.NEXT_ROUND)) bgc.gameLogic.handleNextRound(smsg); else if (smsg.msgid.equals(ServerMessage.GAME_OVER)) bgc.gameLogic.handleGameOver(smsg); else if (smsg.msgid.equals(ServerMessage.LIST)) bgc.fillPlayerList(smsg.msg[0]); else if (smsg.msgid.equals(ServerMessage.GAME_LIST)) bgc.fillGameList(smsg.msg[0]); else if (smsg.msgid.equals(ServerMessage.CHAT)) bgc.infoText.append(smsg.msg[0] + "\n"); else { System.out.println("ClientListener ignored " + smsg.msgid); } Die wichtigsten davon werden hier wiederum kurz aufgeführt: • handleWelcome(): Schliesst den Logon Dialog, falls dieser noch offen ist und setzt die Willkommensüberschrift im GUI • handleRefuseNewGame(): Ruft einen Message Dialog auf und informiert den Spieler, dass die Erzeugung eines neuen Spiels abgelehnt wurde, da er bereits bei einem Spiel angemeldet sei. • handleGameNameExists(): Informiert den Spieler über einen Message Dialog, dass der gewählte Spielname bereits in Gebrauch ist. • handleGameCreated(): Schliesst den NewGame Panel und setzt im Client die Id des eigenen Spielers. • handlePlayerNameInUse(): Informiert den Spieler über einen Message Dialog, dass der gewählte Spielername bereits in Gebrauch ist. • handleRefusedInvitation(): Informiert den Spieler über einen Message Dialog, dass ein eingeladener Spieler die Einladung abgelehnt hat. Seite 42/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.3 ClientSender.java Der Client Sender ist neben dem ClientListener verantwortlich für die Kommunikation zwischen Client und Server. Der Thread ist Verantwortlich für das Versenden der übergebenen Nachrichten an den (spielereigenen) ListenThread im Server. Dies geschieht über einen Socket mittels des ObjectOutputStreams. Erzeugt wird die (einzige) Instanz der Klasse im BeergameClient in der Methode „startSocket()“. Muss eine Nachricht an den Server versandt werden, wird diese dem Vektor „msgque“ – eine Warteschlange - des ClientSenders mittels der Methode „sendMsg()“ angehängt. Die Nachrichten werden dann der Reihe nach abgearbeitet und an den Server weitergeleitet. Zu einer Nachricht (Message) gehört immer die MessageId. Dazu kommen die Daten in Form von Strings, String Arrays (ein oder zweidimensional), Integer oder Integer Arrays (ein oder zweidimensional). Die Datentypen können auch kombiniert (z.B. je als String- und Integer Doppelarrays) übergeben werden. 10.4 GameLogic.java Diese Klasse ist die eigentlich Kernklasse im Client für den Spielablauf. Sie enthält alle Zustände und Informationen des laufenden Spiels sowie die Methoden zur Spielsteuerung. Grundsätzlich erhält die Klasse GameLogic alle relevanten Daten vom Server und verändert diese nicht. Die Berechnungen und Zustandstransitionen finden grundsätzlich im Server statt, genauer in der Klasse beergame.Server.Game und im eigenen SendThread. Dies verhindert den Verlust von Daten und/oder Zuständen, falls ein Client ausfällt. Der Client ist jedoch verantwortlich für die Darstellung und Auswertung der erhaltenen Daten. Die einzigen Berechnungen, die der Client durchführt, betreffen die Kosten und die Waren im Wareneingang beides Datensätze welche redundante Information darstellen: Die Waren im Wareneingang entsprechen genau den Waren in Transport der letzten Runde und die Kosten lassen sich direkt aus den Lagerbeständen ableiten. Auf diese Weise wird versucht, den Kommunikationsaufwand klein zu halten. Die wichtigsten Variablen sind: Name Typ Beschreibung comakerId Integer Die Id der Rolle des Spielers: 1-> Retailer, 2-> Wholesaler 3-> Distributor 4-> Factory. 0 ist reserviert für den Customer und 5 für den Supplier. gameId String Die Id des Spiels. Sie setzt sich zusammen aus aktuellem Datum, Zeit, sowie Namen des Spiels und der Endung "obj". (vgl. beergame.Server.Game) gameName String der Name des Spiels comakerName String[ ] der Array von Namen aller Comaker actualRound Integer die aktuelle Runde des Spiels Seite 43/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich gameMode Integer der Spielmodus (klassisch, klassisch plus, ...) chatMode Integer legt fest, ob chatten möglich ist oder nicht gameOver boolean zeigt an, ob das Spiel läuft oder beendet ist gameLength Integer die Anzahl Spielrunden timePerRound long die Zeit pro Bestellung (in Millisekunden) actualCosts double die aktuellen Kosten des Spielers iAmReady boolean der Status des Spielers playerReady Integer [ ] der Status aller Comaker order Integer [ ][ ] Die Bestellungen aller (bisherigen) Runden und aller Spieler stock Integer [ ][ ] Die Lagerbestände aller (bisherigen) Runden und Spieler goodsReceiving Integer [ ][ ] Die Waren im Wareneingang aller (bisherigen) Runden und Spieler. Diese Daten werden vom Server nicht übermittelt – sie entsprechen ganz einfach den Zahlen „goodsInTransport minus eine Runde. Übersichtlichkeitshalber wird diesen Daten im Client aber ein eigener Array zur Verfügung gestellt. goodsInTransport Integer [ ][ ] Die Waren in Transport aller bisherigen Runden und Spieler Beschreibung der wichtigsten Methoden: • handleGrantNewGame(): Erzeugt eine Instanz der Klasse PnlNewGame und stellt dem Spieler damit eine Eingabemaske zur Verfügung, um ein neues Spiel zu definieren. • invitePlayer(): Erzeugt eine ServerMessage mit dem Parameter des Namens des Spielers, der eingeladen werden soll. • handleInvited(): Erzeugt eine Instanz der Klasse DlgJoinGame, welche dem Spieler ein Interface zur Verfügung stellt dem entsprechenden Spiel beizutreten oder die Einladung abzulehnen. • handleJoinRequest(): Erzeugt eine Instanz der Klasse DlgJoinRequest. Dem Spieler eröffnet sich ein Dialogfenster, in dem er die Anfrage eines anderen Spielers, seinem Spiel beizutreten ablehnen oder akzeptieren kann. Seite 44/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich • handleUpdateGameInfo(): aktualisiert die zur Verfügung stehenden Rollen im Panel „Join Game“. Sobald eine Rolle an einen anderen Spieler vergeben ist, wird der Button der entsprechenden Rolle inaktiviert und der Name des neuen Spielers unter der Rolle aufgeführt. Dieses Vorgehen ist notwendig, da die Spieler parallel den verschiedenen Spielen beitreten können. Ein Spieler erhält diese Nachricht nur, wenn er sich gerade im Status der Rollenauswahl (Join/Invitation) des entsprechenden Spiels befindet. • handleSetupComputerGame(): Diese Methode wird aufgerufen, falls für ein Spiel eine Computersimulation (also nur Computerspieler) durchgeführt worden ist. Alle relevanten Parameter werden dem Client vom Server in Form eines Vektors zur Verfügung gestellt. Zuerst wird die Methode SetupGame() mit den entsprechenden Parametern aufgerufen. Danach werden die Datensätze der Bestellungen, Lagerbestände und Waren in Transport gesetzt und zum Schluss die Methode evaluateGame() aufgerufen, welche die verschiedenen Auswertungsgraphiken erzeugt. • setupGame(): In dieser Methode werden alle relevanten Parameter für das Spiel gesetzt. Es sind dies unter anderem die comakerId, die aktuelle Runde (1), die Spiellänge, die Zeit pro Runde, der Spielmodus, der Chatmodus, die Spielernamen, der Spielnamen selbst sowie die Booleans gameOver und gameIsSet. Des Weiteren werden die Arrays order, stock, goodsInTransport und playerReady instanziert. und die Anfangswerte in denselben gesetzt. In einem weiteren Schritt wird das Panel „TopOverview“ zusammengestellt, welches die Übersicht über das gesamte Spiel enthält. Alle Graphik Panels werden instanziert und danach die Methode „setLabels()“ und „setTheSignals()“ aufgerufen. Schliesslich werden noch die Strings der jeweiligen Sprache gesetzt und der Initialzustand der „LastOrderList“ erstellt. • handleQuitGame(): Diese Methode setzt alle relevanten Spielparameter zurück. • handleAddPlayer(): Fügt einem Spiel einen weiteren Spieler hinzu und setzt seine Parameter. • handleRemovePlayer(): Wird aufgerufen, wenn ein Spieler das Spiel verlässt, bevor es gestartet hat. Die entsprechende Rolle wird wieder auf „verfügbar“ geschaltet. Seine im Spiel gesetzten Parameter werden rückgängig gemacht. • handleGameIsReady(): Diese Methode wird aufgerufen, sobald alle Rollen in einem Spiel vergeben sind. Sie erzeugt einen Dialog, in dem der Spieler abgefragt wird, ob er bereit sei. • handleIamReady(): Wird aufgerufen, sobald der Spieler im Dialogfeld „DlgGameStart“ seine Bereitschaft bekundet hat. Sie sendet eine Statusmeldung an den Server. Sobald alle Mitspieler bereit sind, wird das Spiel gestartet. • handleOtherClientReady(): Diese Methode wird aufgerufen, wenn sich ein anderer Mitspieler diese Spiels als „bereit“ gemeldet hat. Die Methode setzt das Signal des entsprechenden Spielers danach auf grün. • handleStart(): Setzt den entsprechenden Spieler auf iAmReady=false, aktualisiert das GUI, setzt den ChatMode (enable/disable), schreibt eine Statusmeldung und setzt die Signale auf Orange für Seite 45/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich alle Nicht-Computer Spieler. (Die Computerspieler haben ihre Bestellung bereits ausgeführt.) Schliesslich wird der Timer gestartet und auf die erste Bestellung gewartet. • handleTimeUp(): Falls der Spieler die Bestellung der aktuellen Runde ausgeführt hat, passiert nichts, ansonsten wird dem Server die ServerMessage TIME_UP übermittelt und der „timeUpCounter“ um eins erhöht. • handleOtherClientTimeUp(): Setzt eine Statusmeldung, sobald ein anderer Spieler in das Timeout gelaufen ist und setzt dessen Signal auf Rot. • handleHumanPartToComputer(): Diese Methode wird aufgerufen durch eine ServerMessage, falls ein anderer Spieler ausgefallen (bzw. die SocketConnection abgebrochen) ist. Die Rolle des entsprechenden Spielers wird mit dem Vermerk „(Computer)“ ergänzt und eine Statusmeldung wird ausgegeben. Das Signal des entsprechenden Spielers wird auf Rot gestellt. • handlePerformOrder(): Diese Methode wird aufgerufen durch das drücken des „send“ Buttons. Der Eingabewert des Feldes „Order“ wird geprüft und bei Bedarf eine Fehlermeldung ausgegeben (kein Integer, Bestellung zu gross oder zu klein). Ansonsten wird der Boolean „iAmReady“ auf true gesetzt, die „LastOrderList“ nachgeführt und der Order-Graph aktualisiert sowie der Button „send“ inaktiviert. Zum Schluss wird dem Server die Bestellung des Spielers mittels der ServerMessage ORDER_PERFORMED übermittelt. • handleNextRound(): Sobald alle Clients bestellt haben (regulär oder mit timeout), ruft der Server über eine ServerMessage diese Methode auf, welche den Übergang zur nächsten Runde einleitet. Zuerst wird der Boolean „iAmReady“ auf false gesetzt, die neue Rundennummer übernommen, die Bestellungen sowie die Lagerbestände, Waren in Wareneingang und die Waren in Transport aller Comaker nachgeführt. Die jeweiligen Kosten werden berechnet und ebenfalls dargestellt. Methoden zur Berechnung der aktuellen Kosten: Die Stati der Mitspieler werden mittels des private synchronized double[][] getCostsPerRoundByComaker(int round) { Arrays playerReady[] gesetzt und double[][] costs= new double[scLength+2][round+1]; for (int cm=1;cm<=scLength;cm++) { anschliessend das GUI mittels der Methode costs[cm]= calculateCosts(cm,round); } „setLabels()“ und „setTheSignals“ return costs; } aktualisiert. Ebenfalls werden die Graphen private synchronized double[] calculateCosts(int comaker, int actualRound){ „Bestellungen“ und „Kosten pro Runde“ neu double costs[] = new double[actualRound+1]; for(int i=0;i<=actualRound;i++){ gezeichnet. Zum Schluss wird eine costs[i] = (stock[i][comaker]>=0)?stock[i][comaker]*0.5:-stock[i][comaker]*1.0; } return costs; Statusmeldung geschrieben, der Button } „Send“ aktiviert und der Timer für die nächste Runde gestartet. • setLabels(): Ist verantwortlich für die Aktualisierung des GUI. Sie wird aufgerufen zu Beginn des Spieles und vor jeder neuen Runde. Sie wird auch im „Evaluationsmodus“ verwendet, um die verschiedenen Runden nochmals anschauen zu können. Dabei werden je nach Spielmodus und Spieler-ID die relevanten Labels aktualisiert, sichtbar oder unsichtbar gemacht. Im Falle eines Timeouts des Spielers wird jetzt die vom Computer ausgeführte Bestellung im Graph rot nachgezeichnet und eine Statusmeldung ausgegeben. Seite 46/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich • setTheSignals(): Setzt, beziehungsweise aktualisiert die Signale der einzelnen Comaker im Übersichtspanel, je nach Status der einzelnen Mitspieler. Der Status wird vom Server mittels des Arrays „playerReady[]“ übergeben. • enableGloabalInformationMode() & disableGlobalInformationMode() Machen die Informationen aller anderen Mitspieler im Übersichtspanel sichtbar, respektive unsichtbar. Namentlich sind dies die Bestellungen, der Lagerbestand, der Wareneingang sowie die Waren in Transport und die Kosten. • handleGameOver(): Diese Methode wird – wie alle anderen Methoden, welche das Spiel steuern – über eine ServerMessage vom Server aufgerufen. Zuerst wird der Boolean gameOver auf True gesetzt und der Timer gestoppt. Anschliessend wird die Spiel-Id in die Liste der gespielten Spiele aufgenommen. (Der Server hat das Spiel bereits auf die Festplatte gespeichert.) Schliesslich wird der Button „send“ auf „show evaluation“ (in der jeweiligen Sprache) umbenannt und wieder aktiviert. • evaluateGame(): Kann nach dem Spielende durch den Spieler aufgerufen werden, indem er auf den Button „show evaluation“ klickt. Dadurch wird in einem ersten Schritt das GUI auf die Auswertung vorbereitet: es werden verschiedene Card-Layouts vom Modus „Game“ auf „Evaluation“ umgeschaltet, danach werden die Arrays neu sortiert nach Comaker und Runde (während des Spiels Sortierung nach Runde und Comaker) sowie ein neuer Array erstellt mit den Kosten aller Mitspieler. Anschliessend werden aus allen Datensätzen (Bestellungen, Lager, Kosten usw.) die maximalen und minimalen Werte bestimmt. In einem weiteren Schritt werden dann die Graphen vorbereitet, die Achsen mit den Maximal und Minimalwerten initialisiert, die Farben gesetzt (Beschriftung, Achsen, Kurven) und die entsprechenden Werte übergeben. • handleShowSingleGraphs():Schaltet die Ansicht auf die Einzelgraphiken um. • handleShowCombinedGraphs(): Schaltet die Ansicht auf die überlagerten Graphen um. • handleDrawComakerGraph(): Schaltet je nach Zustand der Checkboxes einzelne Kurven ein oder aus in den Diagrammen mit den überlagerten Kurven und setzt gleichzeitig die Signale der Mitspieler auf Rot oder Grün – je nachdem, ob ihre Daten dargestellt werden. • goBackAll(): Ein Button Event. Setzt in der Auswertung die Datensätze auf die Runde 1 zurück und inaktiviert die Buttons „GoBackAll“ und „GoBackOne“. Die Graphen werden anschliessend nachgeführt. • goBackOne(): Ein Button Event. Setzt in der Auswertung die Runde um eins zurück und ruft die Methode redrawGraphs() mit der aktuellen Runde auf. • goForwardOne(): Ein Button Event. Setzt in der Auswertung die Runde um eins vor und ruft die Methode „redrawGraphs()“ mit der entsprechenden Runde auf. • goForwardAll(): Ein Button Event. Setzt in der Auswertung die letzte Runde (currentRoundView=gameLength) und ruft ebenfalls die Methode „redrawGraphs()“ auf. Seite 47/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich • redrawGraphs(): Setzt die Werte in den Diagrammen entsprechend der gewünschten Runde und führt ein „repaint()“ durch. Diese Methode ruft ebenfalls die Methode „setLabels()“mit den entsprechenden Parametern auf, damit in der Übersicht auch die Zahlenwerte der entsprechenden Runde zur Verfügung stehen. • enableChat() & disableChat(): Aktivieren bzw. deaktivieren die Möglichkeit, Chatnachrichten zu versenden. 10.5 OrderPerformedTransition Diese Klasse – ein Thread – übernimmt die Steuerung der Animation des GUI nachdem der jeweilige Spieler seine Bestellung aufgegeben hat. Ein Thread ist notwendig, da die Animation zeitlich gesteuert wird. Das Bewegen des Lastwagens: ... //drive the truck away int truckMovingTime=3; //sec double locY=220.0; int y; for (int x=400;x<=600;x++) { locY-=40.0/200; y = (int) java.lang.Math.floor(locY); gL.bgc.pnlTransport.setLocation(x,y); try {sleep(truckMovingTime*1000/200);} catch (Exception ex) {} } ... . Als erstes erfolgt der Übergang der Waren im Wareneingang ins Lager. Dabei wird der Wareneingang Fass für Fass ab- und das Lager aufgebaut. Danach erfolgt die Auslieferung an den Kunden und die Entladung der Waren in Transport. Zuletzt wird der Lastwagen zurückgefahren. Die Klasse ist direkt der Klasse GameLogic.java angegliedert, da sie logisch zu dieser gehört. 10.6 NextRoundTransistion Dieser Thread wird jeweils zu Beginn einer neuen Runde aufgerufen. Er übernimmt die Animation für das GUI welche den Übergang von einer zur nächsten Runde betreffen. Unter anderem wird der beladene Lastwagen des Zulieferers „hineingefahren“, sowie die Ankunft der neuen Bestellung des Kunden animiert. Bevor dieser Thread ausgeführt wird, wird geprüft er ob der OrderPerformedThread bereits abgeschlossen ist. Ist dieser noch am Laufen, wird auf dessen Abschluss gewartet Das warten auf den Abschluss des OrderPerformedThreads: ... while (gL.opTisRunning) { try {sleep(1000);} catch (Exception ex) {} } ... Seite 48/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Seite 49/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.7 ServerMessage.java ADD_PLAYER APPLY_NEW_GAME BYE CHAT CLIENT_READY CLIENT_REPORTS_READY CLIENT_REPORTS_TIME_OUT CREATE_NEW_GAME DEAD GAME_ACCEPT GAME_ACCEPTED GAME_CREATED GAME_IS_READY GAME_LIST GAME_NAME_EXISTS GAME_OVER GRANT_NEW_GAME GET_SAVED_GAME HUMAN_PART_TO_COMPUTER INFO INFORMATION INVITE INVITED JOIN JOIN_ACCEPT JOIN_ACCEPTED JOIN_CHOOSE_PART JOIN_REFUSE JOIN_REFUSED JOIN_REQUEST JOIN_REQUESTED LIST LOGON_LIST NAME NEW_GAME_CANCELLED NOT_ASSIGNED NOTE NEXT_ROUND ORDER_PERFORMED PLAYED_GAMES PLAYER_LIST PLAYERNAME_IN_USE QUIT_GAME REFUSE_INVITATION REFUSED_INVITATION REFUSE_NEW_GAME REMOVE_PLAYER R_GAME_UPDATE SETUP_COMPUTER_GAME SETUP_GAME START STATUS TIME_UP UPDATE_INFO UPDATE_USER WELCOME Die Klasse ServerMessage definiert die Form der Nachrichten, welche zwischen dem Server und dem Client versandt werden. Dazu werden als Referenz alle Message ID’s als public static final Strings zur Verfügung gestellt. Jede Message wird anhand ihrer Message-ID identifiziert. Jede Message enthält Parameter in Form von Strings, Integers, StringArrays, Integer-Arrays oder eines Vektors. Für jede Message-ID besteht demnach eine Methode, welche die übergebenen Message Parameter verarbeitet. Links ist eine List der ID’s zu sehen, welche für das BeerGame eingeführt wurden Es werden im Moment sieben verschiedene Message Typen zur Verfügung gestellt. Diese unterscheiden sich in den verschiedenen Kombinationen der versandten Datentypen. Folgende Aufrufe werden Unterstützt: 1. (int port, String messageId, String message) 2. (int port, String messageId, String[] message) 3. (int port, String messageId, int messageInt) 4. int port, String messageId, int[] message) 5. (int port ,String messageId , String message, int messageInt) 6. (int port, String messageId, String[] message,int[] messageInt) 7. (int port, String messageId, Vector msgVector) Seite 50/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.8 Res.java Die Klasse Res (Ressourcen) macht nichts anderes als über einen Schlüssel Strings einer bestimmten Sprache zur Verfügung zu stellen. Alle verwendeten Strings für das GUI werden von dieser Klasse bezogen mittels des Aufrufs: res.getString(„KEY“) Je nach Initialisierung des Applets wird der dem Schlüssel entsprechende String in einer anderen Sprache zurückgegeben. Im Moment werden Englisch, Deutsch und Spanisch unterstützt. Die Strings sind folgendermassen abgelegt: static final Object[][] contents = { ... ....{ "WELCOME", "Welcome ","Willkommen " , "Bienvenidos " }, { "CUSTOMER", "Customer","Kunde", "Cliente“ }, { "RETAILER", "Retailer" ,"Einzelhändler", "Detailliste"}, { "WHOLESALER", "Wholesaler" ,"Grosshändler", "Mayoriste"}, ... { "WAIT_FOR_APPROVE", "please wait for approve to join the game \"{0}\" as {1}" , "Anfrage um dem Spiel \"{0}\" als {1} beizutreten wurde gesendet" , "por favor espera una respuesta"}, ... Der erste String ist der Schlüssel, die folgenden Strings stellen die betreffenden Rückgabewerte dar. In die geschweiften Klammern werden zur Laufzeit aus dem Programm Werte eingesetzt. Im Beispiel oben mit dem Schlüssel „WAIT_FOR_APPROVE“ der Spielname (in Methoden welche das gewünschte „res“Objekt zur Verfügung stellen Hochkommata) und die ausgewählte Rolle. Auf public Object[][] getContents() { diese Weise ist es auf sehr einfach möglich, bei setLanguageArrays(LANGUAGE); return languageArray; Bedarf das Programm um weitere Sprachen zu } erweitern. private void setLanguageArrays(int language) { for (int i=0;i<contents.length;i++) { languageArray[i][0]=contents[i][0];//the key if (contents[i][language]!="*") languageArray[i][1]=contents[i][language]; else languageArray[i][1]=contents[i][1];//take the english string } } Im Moment werden in dieser Klasse um die 200 verschiedene Strings in verschiedenen Sprachen verwaltet. Seite 51/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.9 Ref.java public static final int CUSTOMER = 0; public static final int RETAILER = 1; public static final int WHOLESALER = 2; public static final int DISTRIBUTOR =3; public static final int FACTORY =4; public static final int SUPPLIER=5; public static final int WAREHOUSE =10; public static final int GOODS_RECEIVING =11; public static final int TRANSPORT = 12; //comaker status public static final int MY_PART=20; public static final int HUMAN=21; public static final int COMPUTER=22; public static final int NOT_YET_ASSIGNED=23; public static final int TENTATIVE=24; public static final int NOT_PLAYING=25; public static final int INVITATION=30; public static final int JOIN=31; public static final int UPDATE=32; public static final int STRATEGY_KEEP_LEVEL_OF_STOCK=50; public static final int STRATEGY_MOV_AVRG=51; public static final int PLAYER_NOT_READY=60; public static final int PLAYER_NOT_READY_TIMOUT=61; public static final int PlAYER_IS_READY=62; public static final int PlAYER_IS_READY_TIME_OUT=63; public static final int PlAYER_NOT_ASSIGNED=64; public static final int PlAYER_SIGNAL_DARK=65; public static final int PLAYER_SIGNAL_GREEN=66; public static final int PLAYER_SIGNAL_RED=67; public static final int GAME_NOT_READY=70; public static final int GAME_IS_READY=71; public static final int MODE_CLASSIC=80; public static final int MODE_COMPUTER_SIMULATION=81; public static final int MODE_GLOBAL_INFORMATION=82; public static final int MODE_OPEN_GAME=83; public static final int MODE_PRIVATE_GAME=84; public static final int MODE_CHAT_ENABLED=85; public static final int MODE_CHAT_DISABLED=86; public static final int MODE_JOIN_NEEDS_PERMISSION=87; public static final int MODE_JOIN_NEEDS_NO_PERMISSION=88; Die Klasse Ref (References) enthält wie der Name schon sagt Referenzen. Hier werden „sprechende Namen“ für die Kommunikation zwischen Server und Clients oder für den einfacheren Zugriff von Methoden auf Integers abgebildet. Dies ist vor allem nützlich, wenn relative Bezüge wichtig werden, also wenn zum Beispiel der Vorgänger oder der Nachfolger eines Comakers angesprochen werden soll. Der „Wholesaler“ kann dann zum Beispiel sein: è Ref.RETAILER+1 (Nachfolger) è Ref.DISTRIBUTOR –1 (Vorgänger) oder noch allgemeiner: è this.comakerId Vorgänger) +-1 (für Nachfolger bzw. und so weiter. Ausserdem wird durch die Abbildung der Strings auf Integers die Kommunikation zwischen Server und Client vereinfacht. public static final String PART_FREE_SIGN=" * "; 10.10 PnlStatistics.java Diese Klasse erweitert – wie der Name andeutet – die Klasse Panel. Sie stellt ein Panel zur Verfügung, dessen Funktion es ist, Graphen aus übergebenen Werten darzustellen. Diese Klasse wurde sehr allgemein programmiert. So ist es möglich, Instanzen dieser Klasse in verschiedensten Ausprägungen zu erzeugen. Zum einen können die Achsen dynamisch an die übergebenen Werte angepasst oder fix vorgegeben werden. Es können beliebig viele überlagerte Kurven gezeichnet werden (oder eben auch nur eine). Ebenso können die Farbe der Achsen, der Beschriftung und der Kurven selbst beliebig gewählt werden. Es ist sogar möglich, einzelne Kurvensegmente anders einzufärben. Dies wird zum Beispiel verwendet, um bei einem Timeout die Bestellungen welche vom Computer ausgeführt worden sind von denjenigen des menschlichen Mitspielers zu unterscheiden. Intern werden alle Werte als „Double“ behandelt, der Methode können aber auch Arrays von Integern übergeben werden, welche dann intern „gecasted“ werden. Die Methode besitzt folgende Vier Konstruktoren: Seite 52/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 1. PnlStatistics(double[][] values,int graphAreaWidth, int graphAreaHeigth) 2. PnlStatistics(int[][] values,int graphAreaWidth, int graphAreaHeigth) 3. PnlStatistics(int[] values,int graphAreaWidth, int graphAreaHeigth) 4. PnlStatistics(int graphAreaWidth, int graphAreaHeigth Die wichtigsten Variablen dieser Klasse werden im folgende kurz beschrieben: graphAreaWidth Integer Die Breite, welche der Graph im Panel haben soll in Pixel graphAreaHeight Integer Die Höhe, welche der Graph im Panel haben soll in Pixel. clrDefaultGraph Color Die Farbe, welche standardmässig für das Zeichnen einer Kurve verwendet wird clrAxes Color Die Farbe,, in der die Achsen gezeichnet werden. Standardmässig ist „darkGray“ eingestellt clrFont Color Die Farbe in der die Achsenbeschriftung gezeichnet wird. Standardmässig ist „darkGray“ eingestellt. Die folgenden Variablen sind als „private“ deklariert, können aber über entsprechende „set“ Methoden eingestellt werden: offsetLeft, offsetRight, offsetTop, offsetBottom Intgers Die Offsets, welche die Ränder der Fläche des Graphen angeben. Diese Variablen sind „private“, können aber über eine „setOffset“ Methode gesetzt werden. initialXAxesLength initialMinValueY initialMaxValueY Integers Geben die Anfangswerte (in Einheiten der übergebenen Werte) der Achsen vor. scaleAxes Boolean Gibt an, ob die Achsen den Werten angepasst werden sollen. Falls der Wert auf true gesetzt ist, werden die Achsen bei Überschreiten der Maximal- bzw. Minimalwerte frisch berechnet. Dies führt dazu, dass der Graph die gegebene Fläche immer optimal ausfüllt. Die Beschreibung der wichtigsten Methoden dieser Klasse: Seite 53/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich è setValues(): Diese Methode ist überladen. Das heisst es können Werte für den entsprechenden Graph in verschiedenster Form übergeben //get the draw params werden. Konkret unterstützt werden: if(scaleAxes) { for (int arrayId=0;arrayId<values.length;arrayId++) { if (maxArrayLength<values[arrayId].length){ zweidimensionale „Double“ Arrays, maxArrayLength=values[arrayId].length; } zweidimensionale „Integer“ Arrays, for (int i=0;i<values[arrayId].length;i++) { if (values[arrayId][i]<minValueY){ minValueY=(int) java.lang.Math.floor(values[arrayId][i]); und eindimensionale „Integer“ } if (values[arrayId][i]>maxValueY){ maxValueY=(int) java.lang.Math.ceil(values[arrayId][i]); Arrays. Dazu kommt die } } Möglichkeit, ebenfalls die Fläche } } tickDistanceX= ((double)(graphAreaWidth-offsetLeft-offsetRight)/(maxArrayLength-1)); des Graphen (neu) zu setzen. Bei tickDistanceY= ((double)(graphAreaHeight -offsetBottom-offsetTop)/(maxValueY-minValueY)); den zweidimensionalen Arrays clear(g); //draw the axes setForeground(clrFont); entspricht die erste Dimension g.setColor(clrAxes); g.setFont(new java.awt.Font("Arial", 0, 10)); immer einer Kurve und die zweite x0=offsetLeft; xMax=(int) java.lang.Math.floor((maxArrayLength-1)*tickDistanceX+offsetLeft); Dimension der entsprechenden yMin=graphAreaHeight-offsetBottom; y0=(int) java.lang.Math.floor(graphAreaHeight+minValueY*tickDistanceY-offsetBottom); yMax=(int) java.lang.Math.floor((graphAreaHeight-(maxValueY-minValueY)*tickDistanceY-offsetBottom)); Werte dieser Kurve. Um die Klasse g.drawLine( x0,y0,xMax,y0); //x-axes g.drawLine( x0,yMin,x0,yMax); //y-axes allgemein zu halten, werden alle //draw the ticks on the x-axes: for (int i=1;i<maxArrayLength-1;i++){ Werte intern auf „Doubles“ int tickX=(int) java.lang.Math.floor(x0+i*tickDistanceX); if (i%10==0){ abgebildet. (Eine Bestellung 1 wird g.drawLine(tickX,y0-3,tickX,y0+3);//the big ticks g.drawString(Integer.toString(i),tickX-5,y0+12); } also als 1.0 behandelt.) Wie leicht else if (i%5==0 && tickDistanceX*5>=20){//20 points for fontlenght g.drawLine(tickX,y0-3,tickX,y0+3);//the big ticks g.drawString(Integer.toString(i),tickX-5,y0+12); einzusehen ist, beeinflusst das die } else if (tickDistanceX>=1.5) Genauigkeit der Darstellung nicht g.drawLine(tickX,y0-1,tickX,y0+1);//the small ticks } //draw the ticks on the y-axes (im Gegenteil), einzig der for (int i=0;i<=maxValueY-minValueY;i++) { int tickY=(int) java.lang.Math.floor(graphAreaHeight-(offsetBottom+i*tickDistanceY)); Speicherbedarf erhöht sich leicht, if ((i+minValueY)%500==0 && tickDistanceY*200<10){//don't draw 500 if we draw in 200er steps g.drawLine(x0-3,tickY,x0+3,tickY); g.drawString(Integer.toString(i+minValueY),x0-17,tickY+3); wenn Doubles anstelle von Integern } if ((i+minValueY)%200==0 && tickDistanceY*200>=10){ g.drawLine(x0-3,tickY,x0+3,tickY); verwendet werden. g.drawString(Integer.toString(i+minValueY),x0-17,tickY+3); public synchronized void paint(Graphics g) { maxArrayLength=initialXAxesLength; minValueY=initialMinValueY; maxValueY=initialMaxValueY; } if ((i+minValueY)%100==0 && tickDistanceY*100>=10 ){//don't draw 50 if we draw in 20er steps g.drawLine(x0-3,tickY,x0+3,tickY); g.drawString(Integer.toString(i+minValueY),x0-17,tickY+3); } if ((i+minValueY)%50==0 && tickDistanceY*50>=10 &&tickDistanceY*20<10 ){//don't draw 50 if we draw in 20er steps g.drawLine(x0-3,tickY,x0+3,tickY); g.drawString(Integer.toString(i+minValueY),x0-17,tickY+3); } else if ((i+minValueY)%20==0 && tickDistanceY*20>=10){//>=10 point font g.drawLine(x0-3,tickY,x0+3,tickY); g.drawString(Integer.toString(i+minValueY),x0-17,tickY+3); } else if ((i+minValueY)%10==0 && tickDistanceY*10>=10){//>=10 point font g.drawLine(x0-3,tickY,x0+3,tickY); g.drawString(Integer.toString(i+minValueY),x0-17,tickY+3); } else if (tickDistanceY>=1.5) g.drawLine(x0-1,tickY,x0+1,tickY); } //draw the different graphs for (int arrayId=0;arrayId<values.length;arrayId++) {//for each graph to draw if(graphsToDrawContains(arrayId)) { for (int i=0;i<values[arrayId].length-1;i++) {//the segments of the graphs int p1X=(int)java.lang.Math.floor(i*tickDistanceX+offsetLeft); int p1Y=(int)java.lang.Math.floor(graphAreaHeight-(values[arrayId][i]-minValueY)*tickDistanceY-offsetBottom); int p2X=(int)java.lang.Math.floor((i+1)*tickDistanceX+offsetLeft); int p2Y=(int)java.lang.Math.floor(graphAreaHeight-(values[arrayId][i+1]-minValueY)*tickDistanceY-offsetBottom); //set the color of the segment if (clrSegment[arrayId][i]==null) { if (clr[arrayId]==null) clrSegment[arrayId][i]=clrDefaultGraph; else clrSegment[arrayId][i]=clr[arrayId]; } g.setColor(clrSegment[arrayId][i]); //draw the next segment (strength: 2 points) g.drawLine(p1X,p1Y,p2X,p2Y); g.drawLine(p1X,p1Y+1,p2X,p2Y+1); } } } è setGraphsToDraw(): Diese Methode bestimmt, welche Graphen der übergebenen Doppelarrays gezeichnet werden sollen. So ist es möglich, auf einfachste Weise einzelne Graphen ein oder auszublenden, ohne die gesamten Werte frisch übergeben zu müssen. è setInitialAxes(): Über diese Methode kann der Anfangswertebereich (oder je nach dem Boolean „scaleAxes“ der Fixwertbereich) der Achsen gesetzt werden. } Seite 54/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich è setGraphColor(): Über diese Methode kann die Farbe eines bestimmten Graphen gesetzt werden è getGraphColor()/getValues()/getGraphsToDraw() geben die entsprechenden Werte zurück. Diese Methoden werden verwendet, um eine Vergrösserung des ausgewählten Graphen in einem neuen Fenster darzustellen. Die Funktion „showGraphWindow“ ist in der Klasse „BeergameClient“ implementiert, wird aber derzeit nicht verwendet. è paint(): diejenige Methode, welche die Graphen schliesslich zeichnet. Um einen kleinen Eindruck der Umsetzung des gesamten Spieles zu erhalten ist diese Methode – stellvertretend für viele andere – auf der vorhergehenden Seite abgedruckt worden. 10.11 DlgNewGame.java Diese Klasse stellt das Interface zum Erstellen eines neuen Spiels in einem Dialog zur Verfügung. Grundsätzlich hätte diese Klasse auch direkt in die Klasse „BeergameClient“ integriert werden können. Da die Klasse BeergameClient aber bereits sehr gross ist (über 1500 Zeilen), wurde für alle GUI Komponenten, für welche es sinnvoll war, eine eigene Klasse erstellt. Allein diese Klasse hat wiederum über 600 Zeilen... Eine Instanz dieser Klasse wird erzeugt durch die ServerMessage „GRANT_NEW_GAME“, nachdem der Spieler auf den Button „New Game“ geklickt hat und der Server die Berechtigung für die Erstellung eines neuen Spiels erteilt hat. Grundsätzlich ist diese Klasse nichts anderes als eine Eingabemaske für die Spielparameter. Ergänzt wird sie durch ActionHandlers, welche die Maske dynamisch anpassen aufgrund der Eingaben des Benutzers. Ein paar wenige Methoden übernehmen die Prüfung der (lokalen) Korrektheit der eingegebenen Daten. Ist diese Prüfung erfolgreich abgeschlossen, werden die Parameter an den Server übermittelt und nach einer weiteren Prüfung bei Erfolg das Spiel erzeugt (vgl. Methode handleCreateNewGame() in beergame.server.SendThread). Die wichtigsten Methoden dieser Klasse sind: • bttnOk_actionPerformed(): aufgerufen durch den ActionListener des Ok-Buttons. Diese Methode liest alle Werte der Eingabemaske ein und überprüft sie soweit möglich auf Korrektheit und gibt bei Bedarf Fehlermeldungen bzw. Feedback zurück. Ist die Prüfung erfolgreich abgeschlossen, werden die Parameter in Arrays gepackt und mittels der ServerMessage CREATE_NEW_GAME an den Server versandt. • bttnCancel_actionPerformed(): sendet an den Server die Message NEW_GAME_CANCELLED, damit der Server weiss, dass dieser Spieler wieder verfügbar ist. Ausserdem wird die Eingabemaske (dieses Objekt)s geschlossen. • handlePlayerStrategyEnabled(): diese Methode wird ebenfalls durch einen ActionListener aufgerufen. Sobald eine Spielrolle auf„Computer“ umgestellt worden ist, wird eine Auswahl von Strategien eingeblendet, welche dieser Computerspieler spielen kann. Ansonsten wird diese Auswahl wieder ausgeblendet. Seite 55/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.12 DlgJoinGame Diese Klasse stellt – sehr ähnlich der Klasse DlgNewGame – dem Spieler eine Maske für die Auswahl einer Rolle für ein Spiel zur Verfügung. Ein Objekt dieser Klasse wird erzeugt und dargestellt wenn der Spieler ein Spiel markiert und den Join-Button geklickt hat oder wenn ein Spielinhaber einen bestimmten Spieler zum Mitspielen eingeladen hat. Die Maske stellt – neben den Informationen zum betreffenden Spiel - vier Buttons zur Verfügung – je einen für den Retailer, Wholesaler, Distributor und die Factory. Je nachdem, ob die Rollen bereits vergeben sind (an einen anderen Mitspieler oder vom Computer besetzt), sind die Buttons aktiv oder nicht. Ist dieser Panel offen und ein anderer Spieler wählt gleichzeitig eine Rolle des entsprechenden Spiels aus, wird der Button der entsprechenden Rolle sofort durch eine ServerMessage inaktiviert. Diese verhindert, dass bereits vergebene Rollen ausgewählt werden können. Sind alle Rollen vergeben ohne dass der Spieler eine Rolle gewählt hat, wird dieses Panel geschlossen und dem Spieler eine Meldung ausgegeben, dass er zu langsam gewesen sei und sein Glück bei einem anderen Spiel versuchen soll.... Hat sich der Spieler für eine Rolle entschieden und den entsprechenden Button angeklickt, wird die Anfrage an den Server übergeben, welche diese nochmals prüft und bei Erfolg dem Spieler die gewünschte Rolle zuteilt. Darauf wird dieses Dialogfenster geschlossen und das Spiel mittels der Methode „setupGame()“ aufgesetzt. Sobald alle Rollen vergeben sind, startet dann das Spiel. Die wichtigsten Methoden sind: • setPlayersAvailable(): aktiviert oder inaktiviert die Buttons der verschiedenen Rollen. Wird durch den Konstruktor des Objekts und durch ServerMessages aufgerufen. • updatePlayerStatus(): setzt den Namen eines neu hinzugekommenen Spielers und ruft die Methoden „setPlayersAvailable()“ sowie „checkGameFull()“ auf. • sendAcceptInvitation(): sendet die ausgewählte Rolle an den Server. (Anmerkung: der Name der Methode ist etwas unglücklich und ist auf die Tatsache zurückzuführen, dass ich zuerst die Invitation-Funktion und erst später die Join Funktion implementiert habe. Die Methode wird natürlich aber auch bei einem JOIN aufgerufen.) • bttnRefuseInvitation_actionPerformed: teilt dem Server (bzw. danach dem entsprechenden Client) mit, dass der Spieler diesem Spiel nicht beitreten möchte. Seite 56/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 10.13 PnlStock.java, PnlGoodsReceiving.java und PnlTranport.java Diese Klassen sind wiederum aus der Klasse „BeergameClient“ ausgelagert worden, um diese ein wenig schlanker zu machen. Diese drei Klassen beinhalten die Grafiken für die Darstellung des Lagers, der Waren im Wareneingang und der Waren in Transport. Methode paint() in PnlStock.java ... //the ground below the barrels g.drawImage(imgGround, 0, 200, this); //the barrels itself int counter=0; int z=4; while (z>=0) { int x=0; while (x<=3){ int y=3; while (y>=0) { if (++counter>nrOfBarrels) return; g.drawImage(imgBarrel, x*38+y*20, z*40-y*20+62, this); //img=38x43 y--; } x++; } z--; } ... Methode paint() in PnlTransport.java ... int counter=0; int offsetX=70; int offsetY=-20; //1.) z-axes from top to bottom => calculate the height and rest to draw //2.) y-axes from behind to front //3.) x-axes from right to left Die Hauptaufgabe besteht jeweils in der korrekten Darstellung der Anzahl Fässer. Diese Aufgabe hat sich als ziemlich „tricky“ herausgestellt. Der Grund ist, dass die Fässer „dreidimensional“ dargestellt werden, also hintereinander und aufeinander gestapelt. Dies erfordert – neben der Umrechnung eines dreidimensionalen Zustandes auf ein zweidimensionales Bild – eine Berücksichtigung der Sichtbarkeit, je nach Blickwinkel des Beobachters (die Waren im Lager und Lagereingang haben eine andere Ausrichtung als die Waren in Transport). Die Methoden mussten dementsprechend umgestellt werden (siehe links.). //nrOfBarrels=47; int n=(nrOfBarrels>48)?48:nrOfBarrels;//maximum load of truck is 48 int xMax = 4; int yMax = 3; int zMax = 4; int ZzMax = (int) Math.floor((double) n/(xMax*yMax)); // nrOfBarrels / BarrelsPerLevel int XzMax = (int) Math.floor((double) (Math.floor( (n%(xMax*yMax))/yMax) )); int YxzMax = (n%(xMax*yMax))%yMax; Wie links zu sehen ist, wird die Aufgabe ungleich komplizierter, wenn ein Blickwinkel von vorne unten for (int z=0;z<=ZzMax;z++) { for (int x = xMax-1;x>=((z==0)?(xMax-1-XzMax):0); x--) { gewählt wird. Hier muss für die for (int y = 0; y < ((z==0 && x==xMax-1-XzMax)?YxzMax:yMax) ;y++) { //imgBarrelSmall=30x34 Sichtbarkeit zuerst das Fass zuoberst //calculation from 3-d to 2-d g.drawImage(imgBarrel, x*20+y*20+offsetX, (zMax-ZzMax+z)*31-y*6+x*1+offsetY , this); und zuhinterst gezeichnet werden. Aus } } diesem Grund muss errechnet werden, } g.drawImage(this.imgTruck,0,40,this); wo sich dieses Fass (und danach alle ... anderen) im dreidimensionalen Raum befinden. In der ersten Methode kann einfach ein Counter mitgezählt werden, welcher die Schleife unterbricht, sobald die Anzahl notwendiger Fässer gezeichnet ist. Seite 57/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 11 mögliche Erweiterungen des Spieles Selbstverständlich wären noch viele Erweiterungen des Spieles möglich gewesen. Da die Zeit der Diplomarbeit aber beschränkt war, musste ich Prioritäten setzen. Für den Fall, dass zu einem späteren Zeitpunkt dieses Programm erweitert werden soll, führe ich nachfolgend ein paar Punkte auf, bei denen angesetzt werden könnte. Implementation weiterer Strategien: Dies wäre sicher eine interessante Erweiterung, welche wenig Aufwand erfordert, da das Programm eigentlich bereits einen ausgewachsenen Simulator mit vollständiger (graphischer) Auswertung zur Verfügung stellt. Methoden für die Berechnung von Standardabweichung und gleitendem Mittelwerte sind ebenfalls bereits implementiert. Es könnte sehr einfach gezeigt werden, wie verschiedene weitere Spielstrategien sich unter – und gegeneinander verhalten. Ausserdem könnten dem Spieler zusätzliche Parameter zum Einstellen zur Verfügung gestellt werden, wie zum Beispiel die Anzahl Perioden des gleitenden Mittelwerts oder der Sicherheitsfaktor. Weitere Bestellmuster des Endkunden: Eine weitere wenig aufwändige Erweiterung wäre die Variation des Bestellmusters des Endkunden. Hier könnte zum Beispiel mit Zufallsschwankungen, Saisonalitäten und anderen Einflüssen gespielt werden. Erweiterte Animationen im Client: Um den Ubergang von einer Runde auf die nächste den Spielteilnehmern besser verständlich zu machen, wäre es sinnvoll ,vermehrt Animationen einzuführen. Es könnte dies zum Beispiel ein kleiner Kran sein, der die Fässer transportiert. Erweiterung der Spiellogik und des Spielablaufs: Eine Möglichkeit würde bestehen in der Variation der Länge der Supply Chain. Das Spiel wurde extra sehr allgemein programmiert, um solche Erweiterungen zu ermöglichen. Eine kleine Herausforderung wäre wahrscheinlich das GUI dynamisch anzupassen. Eine weitere Idee wäre, das Spiel von einer Kette auf eine baumartige Struktur zu erweitern. Dies würde bedeuten, dass es zum Beispiel eine Fabrik, zwei Verteiler, vier Grosshändler und acht Kleinhändler geben könnte. Benutzeranmeldung per E-Mail: Für eine eindeutige Identifizierung der Spieler könnte das Passwort auf die angegebene EmailAdresse versandt werden. Dies würde zum einen die Gültigkeit der Email-Adresse überprüfen und zum anderen den Spieler vor „unberechtigem“ Anmelden unter seiner Email-Adresse schützen. Es muss allerdings abgewogen werden, ob diese Massnahmen wirklich notwendig sind. Ein Vorteil (und gleichzeitig eine zusätzlich Erweiterung) könnte sein, dass man zum Beispiel für eine Übung unter Student en, die Teilnehmer per Email direkt für ein bestimmtes Spiel einladen könnte. Seite 58/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Seite 59/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12 Spielbeschreibung Im Folgenden wird versucht – unter Zuhilfenahme von Screenshots - einen kleinen Eindruck des Spieles zu vermitteln. 12.1 Die Anmeldung Sobald das Applet geladen ist, wird die Socketverbindung gestartet und der Logon-Dialog erscheint. Hier kann der Spieler seinen Namen und seine E-Mail-Adresse eingeben. Die E-MailAdresse wird verwendet als globaler- und der Name als lokaler Identifikator. Diese bedeutet, dass der Spieler mittels seiner E-Mail-Adresse immer seine früher gespielten Spiele wieder ansehen kann. Der Name selbst identifiziert den Spieler hingegen nur während der gerade laufenden Session eindeutig. Ist der Name bereits in Verwendung, muss eine anderer gewählt werden. Seite 60/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.2 Der Welcome-Screen Nach einer erfolgreichen Anmeldung hat der Spieler mehrere Möglichkeiten: Falls er die Regeln des Beergames noch nicht kennt, kann er sich mittels der Beschreibungen einlesen. Ein ChatFenster steht zur Verfügung um sich mit anderen Spielern zu unterhalten. Auf der rechten Seite des Applets finden sich verschiedene Listen: In der Spielerliste werden alle Spieler aufgeführt, welche online und noch keinem Spiel zugewiesen sind. In der Liste der offenen Spiele werden jene Spiele angezeigt, welche noch Plätze (- * - ) freihaben. In der vierten Liste auf der rechten Seite werden die Spiele aufgeführt, welche der Spieler bereits absolviert hat. Als Spiel-ID wird jeweils das Datum, die Uhrzeit und der Spielname verwendet. Nun kann der Spieler entweder ein neues Spiel erzeugen, bei einem Spiel mitspielen oder sich die Auswertungen eines alten Spiels ansehen Seite 61/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.3 Ein neues Spiel erzeugen Ein neues Spiel kann erzeugt werden über den Knopf „neues Spiel“: Verschiedene Parameter können nun festgelegt werden: è Der Spielname: Ist der Name des Spiels und muss in der laufenden Session eindeutig sein. Das heisst es können nicht zwei Spiele mit dem gleichen Namen gleichzeitig existieren. Diese ID wird auch verwendet (zusammen mit Datum und Uhrzeit) für die spätere Wiedererkennung des Spieles. è Der Spielmodus: Drei Varianten sind möglich. Der klassische Modus entspricht dem klassischen Brettspiel. Die Rundenzahl ist auf 25 festgelegt und Informationen der anderen Mitspieler sind während des Spiels nicht verfügbar. Im Modus „klassisch plus“ können weitere Einstellungen gemacht werden. Die Rundenzahl kann gewählt, der Chat eingeschaltet sowie die Informationen aller Mitspieler verfügbar gemacht werden. Der dritte Modus ist die Computer-Simulation. Hier gibt es nur Computerspieler Es können aber verschiedene Strategien für die einzelnen Computerspieler festgelegt und die Auswirkungen anschliessend in der Auswertung studiert werden è Die Zeit pro Spielrunde: Gibt an, wie viel Zeit pro Bestellung zur Verfügung steht. Läuft diese Zeit ab, übernimmt der Computer für den betreffenden Spieler die Bestellung. Dies verhindert, dass ein einzelner Spieler das gesamte Spiel blockieren kann. è Die Anzahl Runden: Entspricht der Anzahl zu spielenden Runden. Das Minimum ist zehn und das Maximum 99. Seite 62/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich è Die Spielerauswahl: Hier stehen wiederum drei Varianten zur Verfügung. Der Spieler kann seine eigene Rolle auswählen und festlegen, welche Rollen von anderen Mitspielern und welche von Computerspielern übernommen werden sollen. è Die Strategieauswahl: Hier kann der Spieler für die Computerspieler die zu spielende Strategie auswählen. Zurzeit sind zwei Strategien verfügbar. „moving average & standard deviation“ berechnet die zu bestellende Menge aus den Zahlen der Vergangenheit mittels gleitendem Mittelwert und Standardabweichung. „keep level of stock“ versucht das Lager auf einem möglichst Konstanten Niveau zu halten. Die erstere entspricht etwa dem Verhalten eines menschlichen Mitspielers. è Kommentar für andere Mitspieler. Hier kann beliebiger Text eingegeben werden, welcher andere Mitspieler später sehen bei der Anmeldung zu diesem Spiel. è Checkbox „Informationen aller Comaker verfügbar“: Falls sie aktiviert wird (nicht im Modus klassisch) sind während des Spiels in der Übersicht die Informationen aller Mitspieler sichtbar (Lager, Bestellung...) è Checkbox „chat während des Spiels eingeschaltet“ : Schaltet die Chatfunktion während des Spiels ein und ermöglicht es den Spielern sich zu unterhalten. è Checkbox „andere Spieler dürfen diesem Spiel beitreten“: Falls aktiviert, können sich andere Spieler mittels des Knopfes „mitspielen“ zu diesem Spiel anmelden. Andernfalls kann nur der Spielerzeuger die anderen Mitspieler einladen mitzuspielen (mittels des Knopfes „Spieler einladen“ è Checkbox „Erlaubnis notwendig um diesem Spiel beizutreten“. Falls aktiviert, kann der Spielerzeuger eine Anfrage akzeptieren oder ablehnen. Ansonsten werden die Spieler direkt dem Spiel zugewiesen Seite 63/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.4 Beitreten zu einem Spiel Einem Spiel beitreten ist entweder per Einladung (Knopf „Spieler Einladen“ ) oder per Einschreiben (Knopf „mitspielen“) möglich. Beides führt schliesslich zum unten sichtbaren Dialog: Angezeigt werden die wichtigsten Informationen zum Spiel, sowie die noch verfügbaren Rollen, welche durch Drücken des jeweiligen Knopfes ausgewählt werden kann. Wählt ein anderer Spieler gleichzeitig eine Rolle aus, wird diese (bzw. der entsprechenden Knopf) sofort inaktiv und der Name des entsprechenden Spielers wird angezeigt. Dies verhindert das Anmelden zweier Spieler für dieselbe Rolle. Seite 64/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.5 Spielstart Sobald eine Rolle gewählt wurde, oder ein eigenes Spiel kreiert hat wechselt die Ansicht des Applets auf „Spielstart“ wie unten abgebildet. Jedem Spieler ist eine eigene Farbe zugeordnet, welche auch für die Auswertung verwendet wird. Andere Spieler können weiterhin dem Spiel beitreten. Noch nicht besetzte Rollen werden mit einem Stern „*“ gekennzeichnet. Sobald alle Spieler zugewiesen sind (respektive alle Rollen an von Menschen oder Computerspielern besetzt sind) ist das Spiel bereit und ein Dialog erscheint, welcher die Spieler nach Ihrer Bereitschaft abfragt und über den Spielstart informiert. Sobald alle Spieler „bereit“ zurückgemeldet wird der Button „senden“ aktiviert und der Timer gestartet. Seite 65/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.6 Das Spiel Sobald alle Spieler bereit sind, wird das Spiel gestartet. Die Bestellungen müssen nun aufgrund der vorhandenen Informationen gemacht werden. Ein Spielaublauf könnte folgendermassen aussehen: Runde 4: Das Spiel hat begonnen Runde 16: Der Bullwhip-Effekt hat voll zugeschlagen Die Spieler geben nun in jeder Runde in der vorgegebenen Zeit ihre Bestellungen auf. Sehr schön ist hier zu sehen wie sich die Achsen der Graphen dynamisch den Werten anpassen. Runde 17: Das Lager erholt sich langsam Runde 18: Das Spiel hat sich eingependelt Runde 22: Der Spieler hat ein Timeout... Runde 25: Spielende. Mittels des Knopfs “Auswertung” werden die Auswertungsgaphiken sichtbar. Das Segment des Bestellungs-Graphen wird rot gefärbt Seite 66/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.7 Die Auswertung Die Auswertung des obigen Spieles ist links zu sehen. Es werden zwei verschiedene Ansichten angeboten. Die erste enthält die Einzelgraphiken der Bestellungen, Lagerbeständen, Kosten pro Runde und summierten Kosten aller Mitspieler. Sehr schön ist der Bullwhip Effekt zu Beobachten, wenn mittels der Buttons oberhalb der Graphiken durch die einzelnen Runden gefahren wird. In der zweiten Darstellung werden die Graphen überlagert gezeigt. Zudem werden in einer Tabelle die totalen Kosten pro Mitspieler, die Kosten pro Runde, die durchschnittliche Bestellmenge sowie die Standardabweichung der Bestellungen angezeigt. In dieser Darstellung können die Kurven der Mitspieler einzeln ein oder ausgeblendet werden. (im obigen Beispiel ist der Endkunde ausgeblendet). Zuunterst können die Kosten der gesamten Supply Chain abzulesen welche ja zu minimieren waren... Seite 67/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.8 Auswertung einer Computer Simulation Den Spielern wird die Möglichkeit geboten Computersimulationen durchzuführen. Dazu können den einzelnen Computerspielern verschieden Strategien zugeordnet werden und anschliessend die Auswirkungen in der Auswertung betrachtet werden. Simuliert werden kann über eine beliebige Anzahl Runden zwischen 10 und 99. Die Rundenzahl wurde auf 99 beschränkt, da sich bis dahin alle Systeme eingependelt haben und damit die Kommunikation zwischen Client und Server nicht zu sehr belastet werden kann. Die Datenmenge welche der Server dem Client übermitteln muss beläuft sich auf knapp 4kB bei 99 Runden (3772byte). Dies ist zwar sehr wenig im Vergleich zu einem Bild auf einer normalen Webseite, kann aber bei vielen Aufrufen doch etwas ausmachen, wenn z.B. 1000 Runden möglich wären. Nachfolgend ist eine Simulation über 35 Runden zu sehen, in welcher allen Computerspielern die Strategie „moving average & standard deviation“ zugeordnet worden ist. In den Charts auf der linken Seite ist der Verlauf bis zur Runde 15 ersichtlich, rechts ist jeweils das gesamte Spiel zu sehen. Seite 68/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 12.9 Animationen Es ist recht schwierig Animationen in einem Textdokument darzustellen J .Dennoch um einen kleinen Eindruck zu vermitteln habe ich hier noch zwei Bilder angehängt. Auf dem einen ist zu sehen, wie der Lastwagen gerade ins Bild hinein fährt. Auf dem Anderen kommt gerade die Bestellung des Kunden an. Für den vollen Live-Effekt kann ich nur ein ausprobieren des Spieles empfehlen... Seite 69/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 13 Anhang Im Folgenden habe ich noch drei Ablaufdiagramme angehängt, welche ich für die erste Zwischenpräsentation verwendet habe. Aus zeitlichen Gründen war es mir nicht mehr möglich diese vollständig auf den aktuellsten Stand zu bringen. Einzelne Abläufe (oder Teile des angegebene Source Code) können demnach leicht vom Original abweichen. Dennoch dienen Sie als gutes Beispiel wie die Prozesse in diesem Spiel etwa ablaufen. Anhang Seite 70/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Seite 71/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 13.1 Übersicht über die Klassen und ihre Abhängigkeiten Seite 72/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 13.2 Der Server Start Prozess / Start of a socket connection Seite 73/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 13.3 The Invitation Process – a three way handshake Seite 74/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich Seite 75/76 Diplomarbeit, The Beer Distribution Game Christoph Duijts Eidgenössische Technische Hochschule Zürich Swiss Federal Institute of TechnologyZurich 14 Literaturverzeichnis Burkhalter J. P. 2001: Simulation der Dynamik in Supply Chains mit Hilfe des Simulators SIMPLE ++, Studienarbeit ETHZ, BWI, Team Prof. Paul Schönsleben Flanagan D. 1998: JAVA IN A NUTSHELL, O’Reilly Verlag Köln Hieber R. u.a. 2001: Supporting the Beer Game by the simultaneous use of simulation software Center of Enterprise Science (BWI), Swiss Federal Institut of Technology ETH Zürich Lee H. u.a. 1997: The Bullwhip Effect in Supply Chains Sloan Management Review Schönsleben, P. 1998: Integrales Logistikmanagement, Springer Verlag, Berlin Heidelberg Sedgewick R. u.a. 1999: Algorithms in Java Part 1-4 Addison-Wesley Simchi-Levi D. u.a.. 2000: Designing and Managing the Supply Chain, Irwin McGraw-Hill Sterman J.D. : Teaching Takes Off, Flight Simulators for Management Education „The Beer Game“ Sloan School of Management, Massachusetts Institute of Technology (MIT): http://web.mit.edu/jsterman/www/sdg/beergame.html Theisen M.R. 1997 Wissenschaftliches Arbeiten, 8. Auflage Verlage Franz, München Weitere: Beer Game Präsentationsordner Caglar D. u.a. 2000: Beer Game http://beergame.iems.nwu.edu/index.html Seite 76/76