TCP-Kommunikation
Das im TigerJython integrierte Modul tcpcom ermöglicht es, Anwendungen und Spiele, bei welchen zwei Benutzer über das Internet miteinander kommunizieren, zu programmieren. Damit können auch einfache Turtlegrafik-Programme erstellt werden, die über TCP/IP miteinander kommunizieren. Dabei spielt das eine Programm die Rolle eines Servers und das andere die eines Clients.
|
|
|
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.
Die Verbindung wird immer auf gleiche Art aufgebaut: Zuerst muss das Serverprogramm gestartet werden. Der Server wartet nun auf einem bestimmten IP-Port auf einen Client. Der Client erstellt nachher 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.
Beispiel 1: Die Turtle auf dem Client-Computer wird durch Mausklicks auf dem Server gesteuert. Nach der Aufbau der Verbindung werden auf dem Server per Mausklick Punkte gewählt, zu welchen sich die Turtle bewegen soll. Die Zeichnungsbefehle werden vom Server zum Client gesendet und ebenfalls in seinem Turtlefenster ausgeführt.
|
Server |
Client |
|
|
|
Programmcode Server:
# TcpEx1Server.py
from gturtle import *
from tcpcom import TCPServer
def onMousePressed(x, y):
server.sendMessage("moveTo(" + str(x) + "," + str(y) + ")")
moveTo(x, y)
dot(10)
def onCloseClicked():
server.terminate()
dispose()
def onStateChanged(state, msg):
if state == TCPServer.CONNECTED:
setStatusText("Partner ready. Click to draw a dot!")
makeTurtle(mousePressed = onMousePressed, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
port = 5000
server = TCPServer(port, stateChanged = onStateChanged)
setStatusText("Waiting for a partner...")
|
|
|
|
Programmcode Client:
# TcpEx1Client.py
from gturtle import *
from tcpcom import TCPClient
def onCloseClicked():
client.disconnect()
dispose()
def onStateChanged(state, msg):
if state == TCPClient.MESSAGE:
exec(msg)
dot(10)
elif state == TCPClient.DISCONNECTED:
setStatusText("Server died")
makeTurtle(closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
host = "localhost"
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
setStatusText("Client connecting...")
if client.connect():
setStatusText("Connected")
|
|
|
|
Erklärungen zum Programmcode:
Beispiel 2: Auf dem Server wird eine Freihandzeichnung mit gedrückten Maustaste erstellt. Die Mauskoordinaten werden fortlaufend zum Client gesendet, wodurch die gleiche Figur erscheint. In diesem Beispiel werden nicht die ganzen Befehle, sondern nur die aktuellen Mauskoordinaten und "p" für setPos() und "m" für moveTo() gesendet. Beim Client wird der empfangene Strings in Teile zerlegt und damit ein Turtlebefehl ausgeführt (siehe Erklärungen zum Programmcode).
|
Server |
Client |
|
|
|
# TcpEx2Server.py
from gturtle import *
from tcpcom import TCPServer
def onMousePressed(x, y):
setPos(x, y)
server.sendMessage("p," + str(x) + "," + str(y))
def onMouseDragged(x, y):
moveTo(x, y)
server.sendMessage("m," + str(x) + "," + str(y))
def onCloseClicked():
server.terminate()
dispose()
def onStateChanged(state, msg):
if state == TCPServer.CONNECTED:
setStatusText("Partner ready, Drag the mouse!")
makeTurtle(mousePressed = onMousePressed, mouseDragged = onMouseDragged,
closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
port = 5000
server = TCPServer(port, stateChanged = onStateChanged, )
setStatusText("Waiting for a client...")
|
|
|
|
# TcpEx2Client.py
from gturtle import *
from tcpcom import TCPClient
def onCloseClicked():
client.disconnect()
dispose()
def onStateChanged(state, msg):
if state == TCPClient.MESSAGE:
li = msg.split(",")
if li[0] == "p":
setPos(float(li[1]), float(li[2]))
if li[0] == "m":
moveTo(float(li[1]), float(li[2]))
if state == TCPClient.DISCONNECTED:
setStatusText("Server died")
makeTurtle(closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
host = "localhost"
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
setStatusText("Client connecting...")
if client.connect():
setStatusText("Connected")
|
|
|
|
Erklärungen zum Programmcode:
|
li = msg.split(","): separiert die durch Komma getrennten String-Elemente und speichert sie in einer Liste |
|
if li[0] == "p": wenn das erste Listenelement "p" ist, wird der Befehl setPos() verwendet |
|
setPos(float(li[1]), float(li[2])): das zweite Listenelement ist die x-Koordinate, das dritte ist die y-Koordinate. Diese müssen in Floats konvertiert werden |
Beispiel 3: Server und Client wählen abwechslungsweise mit einem Mausklick eine Fläche aus. Diese wird auf beiden Computern gefüllt. Zuerst muss das Serverprogramm gestartet werden. Nach der Aufbau der Verbindung wählt Client die erste Fläche. Die Mausklicks der beiden Partner müssen nacheinander erfolgen, d.h. der Client muss mit der Wahl der nächsten Fläche warten, bis der Server seinen Mausklick ausgeführt hat. Dies ereicht man hier mit einem Flag isMyTurn, das auf False bzw. True gesetzt wird. Der nächste Mausklick kann nur im True-Zustand erfolgen.
|
Server |
Client |
|
|
|
|
|
# TcpEx3Server.py
from gturtle import *
from tcpcom import TCPServer
def onMousePressed(x, y):
global isMyTurn
if not isMyTurn:
return
setPos(x, y)
setFillColor("green")
fill(x, y)
server.sendMessage("fill(" + str(x) + "," + str(y) + ")")
isMyTurn = False
setStatusText("Wait!")
def onCloseClicked():
server.terminate()
dispose()
def onStateChanged(state, msg):
global isMyTurn
if state == TCPServer.CONNECTED:
setStatusText("Partner entered my game room")
if state == TCPServer.MESSAGE:
setFillColor("red")
exec(msg)
setStatusText("Click to fill any part!")
isMyTurn = True
makeTurtle(mousePressed = onMousePressed, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
for k in range(12):
for i in range(6):
forward(50)
right(60)
left(30)
port = 5000
server = TCPServer(port, stateChanged = onStateChanged)
setStatusText("Waiting for a partner...")
isMyTurn = False
|
|
|
|
# TcpEx3Client.py
from gturtle import *
from tcpcom import TCPClient
def onMousePressed(x, y):
global isMyTurn
if not isMyTurn:
return
setPos(x, y)
setFillColor("red")
fill(x, y)
client.sendMessage("fill(" + str(x) + "," + str(y) + ")")
isMyTurn = False
setStatusText("Wait!")
def onCloseClicked():
client.disconnect()
dispose()
def onStateChanged(state, msg):
global isMyTurn
if state == TCPClient.MESSAGE:
setFillColor("green")
exec(msg)
setStatusText("Click to fill any part!")
isMyTurn = True
elif state == TCPClient.DISCONNECTED:
setStatusText("Server died")
isMyTurn = False
makeTurtle(mousePressed = onMousePressed, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
for k in range(12):
for i in range(6):
forward(50)
right(60)
left(30)
host = "localhost"
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
setStatusText("Client connecting...")
if client.connect():
setStatusText("Click to fill any part!")
isMyTurn = True
|
|
|
|
Erklärungen zum Programmcode:
Beispiel 4: Ein einfaches Schiffchen-Versenken-Spiel.
In einem eindimensionalen Gitter, welches aus 10 Zellen besteht, werden zufällig 4 Schiffe angeordnet. Die Spieler wählen abwechslungsweise eine Zelle und versuchen die Position der Schiffe des Gegners zu erraten. Wenn sich in der gewählten Zelle ein Schiff befindet, wird es versenkt (mit Hintergrundfarbe übermalt). Ist die Zelle leer, hat man Pech gehabt. Das Spiel ist beendet, wenn einer der beiden Spieler keine Schiffe mehr hat.
Die Position der Schiffe wird mit Hilfe von Zufallszahlen bestimmt. Es ist vorteilhaft, dafür die Funktion random.sample() zu verwenden, die verschiedene Zufallszahlen erzeugt. Übertragen werden hier nicht die Koordinaten des Mausklicks, sondern die Nummer der getroffenden Zelle. Diese lässt sich aus den Mausklick-Koordinaten berechen. Um herauszufinden, ob getroffen wurde, prüft man mit getPixelColorStr() die Hintergrundfarbe im Zellenmittelpunkt. Die Anzahl der getroffenen Schiffe wird in den Variaben myHits und partnersHits gezäht.
# ShipServer.py
from gturtle import *
from tcpcom import TCPServer
import random
def drawBoard():
setPenColor("gray")
clear("aliceblue")
for x in range(-250, 250, 50):
setPos(x, 0)
setFillColor("blue")
startPath()
repeat 4:
forward(50)
right(90)
fillPath()
def createShips():
li = random.sample(range(1, 10), 4) # 4 unique random numbers 1..10
for i in li:
setPos(-275 + i * 50, 25)
drawImage("sprites/boat.gif")
def onMousePressed(x, y):
global isMyTurn
setPos(x, y)
if getPixelColorStr() == "aliceblue" or isOver or not isMyTurn:
return
nb = int((x + 250) / 50) + 1 # cell number
server.sendMessage(str(nb))
isMyTurn = False
def onCloseClicked():
server.terminate()
dispose()
def onStateChanged(state, msg):
global isMyTurn, myHits, partnerHits
if state == TCPServer.CONNECTED:
setStatusText("Partner entered my game room")
if state == TCPServer.MESSAGE:
if msg == "hit":
myHits += 1
setStatusText("Hit! Partner's remaining fleet size " + str(4 - myHits))
if myHits == 4:
setStatusText("Game over, You won!")
isOver = True
elif msg == "miss":
setStatusText("Miss! Partner's remaining fleet size " + str(4 - myHits))
else:
nb = int(msg) # cell number
x = -275 + nb * 50
setPos(x , 25)
if getPixelColorStr() != "blue":
server.sendMessage("hit")
setPenColor("blue")
dot(45) # erase ship
partnerHits += 1
if partnerHits == 4:
setStatusText("Game over! Partner won!")
isOver = True
return
else:
server.sendMessage("miss")
setStatusText("Make your move!")
isMyTurn = True
makeTurtle(mousePressed = onMousePressed, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
drawBoard()
createShips()
port = 5000
server = TCPServer(port, stateChanged = onStateChanged)
setStatusText("Waiting for game partner...")
isOver = False
isMyTurn = False
myHits = 0
partnerHits = 0
|
|
|
|
# ShipClient.py
from gturtle import *
import random
from tcpcom import TCPClient
def drawBoard():
clear("aliceblue")
setPenColor("gray")
for x in range(-250, 250, 50):
setPos(x, 0)
setFillColor("blue")
startPath()
repeat 4:
forward(50)
right(90)
fillPath()
def createShips():
li = random.sample(range(1, 10), 4) # 4 unique random numbers 1..10
for i in li:
setPos(-275 + i * 50, 25)
drawImage("sprites/boat.gif")
def onMousePressed(x, y):
global isMyTurn
setPos(x, y)
if getPixelColorStr() == "aliceblue" or isOver or not isMyTurn:
return
nb = int((x + 250) / 50) + 1 # cell number
client.sendMessage(str(nb))
isMyTurn = False
def onCloseClicked():
client.disconnect()
dispose()
def onStateChanged(state, msg):
global isMyTurn, myHits, partnerHits
if state == TCPClient.MESSAGE:
if msg == "hit":
myHits += 1
setStatusText("Hit! Partner's remaining fleet size " + str(4 - myHits))
if myHits == 4:
setStatusText("Game over, You won!")
isOver = True
elif msg == "miss":
setStatusText("Miss! Partner's remaining fleet size " + str(4 - myHits))
else:
nb = int(msg) # cell number
x = -275 + nb * 50
setPos(x , 25)
if getPixelColorStr() != "blue":
client.sendMessage("hit")
setPenColor("blue")
dot(45) # erase ship
partnerHits += 1
if partnerHits == 4:
setStatusText("Game over! Partner won")
isOver = True
return
else:
client.sendMessage("miss")
setStatusText("Make your move!")
isMyTurn = True
elif state == TCPClient.DISCONNECTED:
setStatusText("Server died")
drawBoard()
isMyTurn = False
makeTurtle(mousePressed = onMousePressed, closeClicked = onCloseClicked)
addStatusBar(30)
hideTurtle()
drawBoard()
host = "localhost"
port = 5000
client = TCPClient(host, port, stateChanged = onStateChanged)
setStatusText("Client connecting...")
isOver = False
myHits = 0
partnerHits = 0
if client.connect():
setStatusText("Make a move!")
createShips()
isMyTurn = True
else:
setStatusText("Server game room closed")
|
|
|
|
Erklärungen zum Programmcode:
Weiterführende Beispiele zur TCP-Programmierung mit dem
tcpcom-Modul findet man unter
www.tcpcom.ch.
1) |
Ein Auto (Turle-Bild) befindet sich im Client-Turtlefenster. Klickt man in das Server-Turtlefenster, so fährt das Auto im Client-Turtlefenster geradeaus an die entsprechende Position.
Anleitung:
Wähle als Vorlage TcpEx1Server.py und TcpEx1Client.py. Im Client-Fenster muss die Turtle sichtbar sein. Im Serverfenster ist die Turtle versteckt und es wird an der Stelle des Mausklicks ein Punkt gezeichnet. Das Bild des Autos gibst du in makeTurtle() an:
| |
Server
|
|
Client
|
|
makeTurtle("sprites/car3.png", closeClicked = onCloseClicked)
Verwende den Befehl penUp(), damit die Turtlespuren nicht gezeichnet werden.
|
2) |
Zeichne ein Gitter mit 7 horizontalen und 7 vertikalen Zellen. Server und Client wählen abwechslungsweise mit einem Mausklick eine Zelle aus. Die durch Client gewählten Zellen werden rot, die durch Server gewählten gelb gefärbt.
Verwende als Vorlage TcpEx3Server.py und TcpEx3Client.py (Beispiel 3).
Du kannst dann mit deinem Partner ein "Four in a row Game" spielen.
Spielregeln: |
|
Server
|
Client
|
|
- |
Die neu gewählten Zellen müssen unten an bereits gefärbte Zellen anschliessen |
- |
Wer zuerst 4 Zellen in einer Reihe, Spalte oder Diagonale mit eigener Farbe gefärbt hat, hat gewonnen. |
Ein "richtiges" Four in a row Game findest du im Kapitel Spielprogrammierung. |
3) |
Auf dem Server wird mit gedrückter Maustaste eine Figur gezeichnet. Dabei werden forlaufend die Mauskoordinaten zum Client gesendet, so dass beim Client die gleiche Figur entsteht. Beim Loslassen der Maus ist der Client an der Reihe. Dieser füllt mit einem Mausklick eine beliebige geschlossene Fläche aus und sendet die Mauskoordinaten zum Server, damit die gleiche Fläche auch auf dem Server ausgefüllt wird. Danach kommt wieder der Server zum Zug.
Diese Aufgabe ist also eine Kombination der Beispiele 2 und 3. |
|
Server
|
Client |
4) |
Programmiere ein einfaches Nimmspiel.
Spielregeln:
- In einem Zug kann ein Spieler 1, 2, oder 3 Quadrate mit dem linken Mausklick entfernen
- Der Zug wird mit dem rechten Mausklick beendet
- Danach kommt der zweite Spieler zum Zug
- Wer das letzte Quadrat entfernen muss, hat verloren.
Server
Client
|
|