TigerJython xx
für Gymnasien

Spiele mit TCP/IP Kommunikation


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

Der Client kennt folgende Zustände:
  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
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

-------------------------------------------------------------------------------------------------------------------------------------
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
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

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