@Raphael_155: (Etwas spät, einiges wurde bereits gesagt, ich bin aber zu faul das jetzt noch raus zu kürzen.
)
Die `Network`-Klasse wird ja nur vom Client verwendet, da macht das nicht viel Sinn die in ein eigenes Modul auszulagern.
Die Klasse an sich macht nicht so wirklich Sinn, weil sie eigentlich nur das Socket-Objekt kapselt und nur eine Methode besitzt die nach dem erstellen des Objekts aufgerufen wird. Sie ”kapselt” noch den Rückgabewert von `Network.connect()`, dass aber nur weil die `__init__()` diesen Wert nicht als Rückgabwert haben kann. Der `connect()`-Code bräuchte nicht in einer eigenen Methode stehen, denn das wird nur von der `__init__()` aufgerufen und `getPos()` als trivialer Getter ist ”unpythonisch”, da kann man auch einfach direkt auf `pos` zugreifen.
Ausnahmebehandlung die einfach so tut als wäre die Ausnahme gar nicht passiert, und einfach weiter macht, ist tatsächlich manchmal sinnvoll, aber nicht wenn der Code gar nicht sinnvoll weiter machen kann. Wenn die Verbindung zum Server nicht klappt, geht ja das Spiel nicht wirklich, da kann man nicht einfach weitermachen.
Ausserdem sollte man keine nackten ``except:`` ohne konkrete Aushnahmen verwenden, denn das behandelt *alle* Ausnahmen, auch solche, die man an der Stelle gar nicht erwartet hat. Wenn das durch einen Programmierfehler ausgelöst wurde, dann möchte man das wissen, denn sonst findet man den nie oder nur sehr mühsam, wenn man die unerwartete Ausnahme behandelt als wäre sie eine andere, für die die Behandlung Sinn macht.
Beide Ausnahmebehandlungen in der `Network`-Klasse sollten einfach ersatzlos gestrichen werden. Denn wenn Verbindungsaufbau oder Senden/Empfangen nicht funktioniert hat, dann kann man nicht einfach so weitermachen als wäre nichts passiert, denn dann ist das Programm in einem Zustand in dem es sehr wahrscheinlich ist, dass es nur noch aus Folgefehlern bei der Kommunikation besteht.
Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.
Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase).
`width` und `height` auf Modulebene sind Konstanten. `win` ist eine Variable, die gehört nicht auf Modulebene. `clientNumber` wird auf Modulebene definiert, aber nirgends verwendet.
Namen sollte man nicht kryptisch abkürzen. Wenn man `window` meint, dann sollte man nicht nur `win` schreiben. Insbesondere weil das ja auch ein sinnvoller Begriff im Kontext eines Spiels ist, kann das verwirrend werden. Und auch einbuchstabige Namen sind ausser für `i` und `j` für ganze Zahlen die als Laufindex verwendet werden, oder `x`, `y`, und `z` für Koordinaten, in der Regel sehr schlechte Namen. Auch das anhängen von Nummern an Namen ist nicht gut. Dann will man entweder bessere Namen, oder gar keine einzelnen Werte sondern eine Datenstruktur. Oft eine Liste.
Ich sehe beim Client nirgends ein `pygame.init()`‽ Und das Gegenstück `pygame.quit()` wird an einer falschen Stelle aufgerufen, denn danach kommt noch ein neuzeichnen des Fensters, was nach dem `pygame.quit()` aber nicht mehr geht. Das würde ich durch ein ``try``/``finally`` lösen. Dann kann man auch `run` als Variable loswerden.
Grunddatentypen gehören nicht in Namen. Bei `str` verdeckst Du den eingebauten Datentyp `str`. Und bei `tup` muss das gar kein Tupel sein, da geht jeder Sequenztyp der eine Position enthält.
`read_pos()` liest keine Position und `make_pos()` erstellt keine Position. Das sind Funktionen die eine Position aus einer Zeichenkette erstellen, oder eine Zeichenkette aus einer Position. Das sollte man an den Namen erkennen können.
Die `Player`-Klasse enthält redundante Daten. Einmal `rect` und dann noch mal dessen Einzelteile als Attribute, und Du brauchst Code und musst aufpassen den immer an der passenden Stelle aufzurufen, damit diese Werte nicht auseinander laufen. Diese Daten sollten nur einmal gespeichert sein. Am besten unter `rect` und da auch nicht als Tupel, sondern als `pygame.Rect`, weil das eine Reihe nützlicher Eigenschaften hat, gegenüber dem ”dummen” Tupel. Zum Beispiel kann man das verschieben oder die Position neu setzen.
Nun noch zu einem Fehler: Tausende von Beispielen im Netz, und erschreckenderweise sogar Bücher, machen Socketprogrammierung falsch. TCP ist ein Datenstrom und kennt keine Nachrichten. Wenn man über eine TCP-Verbindung Nachrichten übermitteln will, dann muss man dafür ein Protokoll implementieren, dass diese Nachrichten erkennt, und voneinander trennen kann. Gegebenfalls auch Nachrichten oder Nachrichtenteile puffern kann, bis sie verarbeitet werden können.
Die `send()`-Methode auf Sockets muss nicht alles senden was man übergeben hat. Das lässt sich noch einfach beheben in dem man `sendall()` verwendet.
Aber `recv()` empfängt nicht was auf der anderen Seite als ein `sendall()` verschickt wurde, sondern einen beliebigen Ausschnitt vom aktuellen Anfang des Datenstroms. Code muss im Extremfall damit klar kommen können, das jeder `recv()`-Aufruf ein einzelnes Byte liefert. Sonst ist das Fehlerhaft. Auch wenn das scheinbar funktioniert, bei schönem Wetter und auf dem lokalen Rechner, das ist so halt nicht garantiert von der API.
Zwischenstand für den Client (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import socket
import pygame
SCREEN_SIZE = (500, 500)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
def connect():
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(("192.168.178.25", 5555))
#
# FIXME Das ist fehlerhaft, so funktionieren TCP-Sockets nicht.
#
return client_socket, client_socket.recv(2048).decode()
def communicate(client_socket, data):
client_socket.client.sendall(str.encode(data))
#
# FIXME Das ist fehlerhaft, so funktionieren TCP-Sockets nicht.
#
return client_socket.client.recv(2048).decode()
def parse_position(text):
return list(map(int, text.split(",")))
def convert_position_to_text(position):
return ",".join(map(str, position))
class Player:
def __init__(self, rect, color):
self.rect = rect
self.color = color
self.velocity = 3
@property
def position(self):
return self.rect.topleft
@position.setter
def position(self, value):
self.rect.topleft = value
def draw(self, surface):
pygame.draw.rect(surface, self.color, self.rect)
def move(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
self.rect.move_ip(-self.velocity, 0)
if keys[pygame.K_RIGHT]:
self.rect.move_ip(self.velocity, 0)
if keys[pygame.K_UP]:
self.rect.move_ip(0, -self.velocity)
if keys[pygame.K_DOWN]:
self.rect.move_ip(0, self.velocity)
def redraw_window(surface, players):
surface.fill((255, 255, 255))
for player in players:
player.draw(surface)
pygame.display.update()
def main():
pygame.init()
try:
screen = pygame.display.set_mode(SCREEN_SIZE)
pygame.display.set_caption("Client")
client_socket, start_position_text = connect()
player_size = (100, 100)
player = Player(
pygame.Rect(parse_position(start_position_text), player_size),
GREEN,
)
opponent = Player(pygame.Rect((0, 0), player_size), RED)
clock = pygame.time.Clock()
while True:
clock.tick(60)
opponent.position = parse_position(
communicate(
client_socket, convert_position_to_text(player.position)
)
)
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
player.move()
redraw_window(screen, [player, opponent])
finally:
pygame.quit()
if __name__ == "__main__":
main()