TigerJython | xx für Gymnasien |
Das im TigerJython integrierte Modul tcpcom ermöglicht es, Spiele, bei welchen zwei Benutzer über TCP/IP miteinander kommunizieren, zu programmieren. Die Verbindung zwischen den beiden Computern erfolgt über das Internet. |
In einem Klassenverband kann auch ein lokaler WLAN-Router oder ein mobiler WLAN-Hotspot, der auf einem Smartphone aktiviert ist, verwendet werden. Mehr zur Einrichtung der Kommunikation findet man hier.
In der Regel spielt es keine Rolle, auf welchen der beiden Computer das Server- und Clientprogramm läuft. Das Serverprogramm muss aber immer zuerst gestartet werden. Der Client erstellt danach unter der Verwendung der IP-Adresse des Servers und der Portnummer, welche im Serverprogramm festgelegt ist, eine Verbindung zum Server. Zum Testen kann man das Server- und das Client-Programm auf dem gleichen Computer ausführen, indem man die TigerJython-IDE zweimal startet und die IP-Adresse localhost verwendet.
Das Modul tcpcom ist eventgesteuert aufgebaut. Der Status der Verbindung wird mit der Callbackfunktion onStateChanged(state, msg) überwacht. Diese wird bei der Erzeugung von TCPServers bzw. TCPClients registriert.
Der Parameter state des Servers kennt folgende Zustände:TCP.Server.LISTENING TCP.Server.CONNECTED TCP.Server.MESSAGE TCP.Server.TERMINATE |
Server wartet auf eine neue Verbindung Die Verbindung zum Client wurde aufgebaut Der Server hat eine Message msg empfangen Der Server ist beendet |
TCPClient.CONNECTED TCPClient.MESSAGE TCPClient.DISSCONNECTED |
Verbindung erfolgreich erstellt Der Client hat eine Message msg empfangen Verbindung unterbrochen |
Mit der Funktion sendMessage() können Messages in Form eines Strings vom Server zum Client und umgekehrt gesendet werden. Mehr über die TCP/IP-Programmierung findet man unter www.tcpcom.ch.
Beispiel 1: Ein einfaches Schiffchen-Versenken Spiel
Spielregeln: Jeder Spieler erhält ein Spielfeld mit 8 Schiffen an zufällig gewählten Positionen. Die Spieler versuchen, abwechslungsweise mit einem Mausklick die Position der Schiffe des Gegners zu erraten. Damit man die gleiche Zelle nicht mehrmals wählt, wird jeder Versuch mit einem roten Kreuz markiert. Bei einem Treffer wird das Schiff versenkt und verschwindet vom Spieltfeld. Wer zuerst alle Gegnerschiffe versenkt hat, gewinnt.
Server |
Client |
In der Callbackfunktion onMousePressed() werden die Koordinaten des Mausklicks als String gesendet. Im Callback onStateChanged() empfängt der Partner diese message und meldet zurück, ob sich an dieser Position ein Schiff befindet ("hit") oder nicht ("mis"). Die verbleibende Anzahl Schiffe wird in der Statusbar angezeigt.
Server:
#TcpShipServer.py from gamegrid import * from tcpcom import TCPServer class Ship(Actor): def __init__(self): Actor.__init__(self, "sprites/boat.gif") def onMousePressed(e): global isMyMove if not isMyMove or isOver: return loc = toLocationInGrid(e.getX(), e.getY()) addActor(Actor("sprites/checkred.gif"), loc) server.sendMessage(str(loc.x) + str(loc.y)) # send location setStatusText("Wait!") isMyMove = False def onStateChanged(state, msg): global isMyMove, myHits, partnerHits, isOver, loc if state == TCPServer.PORT_IN_USE: setStatusText("TCP port occupied. Restart IDE.") elif state == TCPServer.LISTENING: setStatusText("Waiting for a partner") elif state == TCPServer.CONNECTED: setStatusText("Client connected. Wait!") elif state == TCPServer.MESSAGE: if msg == "hit": myHits += 1 setStatusText("Hit! Partner's fleet size " + str(nbShip - myHits) + ". Wait!") if myHits == nbShip: setStatusText("Game over, You won!") isOver = True elif msg == "miss": setStatusText("Miss! Partner's fleet size " + str(nbShip - myHits)) else: x = int(msg[0]) y = int(msg[1]) loc = Location(x, y) actor = getOneActorAt(loc, Ship) if actor != None: actor.removeSelf() refresh() server.sendMessage("hit") partnerHits += 1 if partnerHits == nbShip: setStatusText("Game over! Partner won") isOver = True return else: server.sendMessage("miss") setStatusText("You play! Partner's fleet size " + str(nbShip - myHits)) isMyMove = True def onNotifyExit(): server.terminate() dispose() makeGameGrid(5, 5, 50, Color.red, False, mousePressed = onMousePressed, notifyExit = onNotifyExit) addStatusBar(30) nbShip = 8 for i in range(nbShip): addActor(Ship(), getRandomEmptyLocation()) show() port = 5000 server = TCPServer(port, stateChanged = onStateChanged) isMyMove = False isOver = False myHits = 0 partnerHits = 0 |
-------------------------------------------------------------------------------------------------------------------------------------
Client:
# TcpShipClient from gamegrid import * from tcpcom import TCPClient class Ship(Actor): def __init__(self): Actor.__init__(self, "sprites/boat.gif") def onMousePressed(e): global isMyMove if not isMyMove or isOver: return loc = toLocationInGrid(e.getX(), e.getY()) addActor(Actor("sprites/checkred.gif"), loc) client.sendMessage(str(loc.x) + str(loc.y)) # send location setStatusText("Wait!") isMyMove = False def onStateChanged(state, msg): global isMyMove, myHits, partnerHits, isOver, loc if state == TCPClient.CONNECTED: setStatusText("Connection established. You play!") elif state == TCPClient.CONNECTION_FAILED: setStatusText("Connection failed") elif state == TCPClient.DISCONNECTED: setStatusText("Server died") isMyMove = False elif state == TCPClient.MESSAGE: if msg == "hit": myHits += 1 setStatusText("Hit! Partner's fleet size " + str(nbShip - myHits) + ". Wait!") if myHits == nbShip: setStatusText("Game over, You won!") isOver = True elif msg == "miss": setStatusText("Miss! Partner's fleet size " + str(nbShip - myHits) + ". Wait!") else: x = int(msg[0]) y = int(msg[1]) loc = Location(x, y) actor = getOneActorAt(loc, Ship) if actor != None: actor.removeSelf() refresh() client.sendMessage("hit") partnerHits += 1 if partnerHits == nbShip: setStatusText("Game over! Partner won") isOver = True return else: client.sendMessage("miss") setStatusText("You play! Partner's fleet size " + str(nbShip - myHits)) isMyMove = True def onNotifyExit(): client.disconnect() dispose() makeGameGrid(5, 5, 50, Color.red, False, mousePressed = onMousePressed, notifyExit = onNotifyExit) addStatusBar(30) nbShip = 8 for i in range(nbShip): addActor(Ship(), getRandomEmptyLocation()) show() host = "localhost" port = 5000 client = TCPClient(host, port, stateChanged = onStateChanged) client.connect() isMyMove = True isOver = False myHits = 0 partnerHits = 0 |
Erklärungen zum Programmcode:
addActor(Actor("sprites/checkred.gif"), loc): Setzt eine Marke in die gewählte Zelle | |
client.sendMessage(str(loc.x) + str(loc.y)): Die Koordinaten des Mausklicks werden als String gesendet (zwei Ziffern zwischen 0 und 4) | |
state == TCPClient.MESSAGE: Empfangen werden entweder die Strings "hit, "miss" oder die Koordinaten der Zelle | |
x = int(msg[0]), y = int(msg[1]: Das erste Zeichen des msg-Strings ist die x- und das zweite Zeichen die y-Koordinate der getroffenen Zelle | |
actor = getOneActorAt(loc, Ship) : Nur Actrors der Klasse Ship werden berücksichtigt. Falls sich in der gleichen Zelle eine rote Marke befindet, wird None zurückgegeben |