From 7f6b53b5493452f56804aaafe6870bbcbbe4ba88 Mon Sep 17 00:00:00 2001 From: qvalentin Date: Sat, 2 Apr 2022 16:54:46 +0200 Subject: [PATCH] init - add more spaceships and start with doku --- Code/UI/Field.py | 15 +++++++++-- Code/UI/PlayingField.py | 31 ++++++++++++---------- Code/UI/Shape.py | 9 ++++++- Code/UI/Shapes.py | 39 ++++++++++++++++++++-------- README.md | 37 ++++++++++++++++++++++++++ images/Verbindungsaufbau | 1 + images/Verbindungsaufbau.drawio.svg | 4 +++ images/Verbindungsaufbau.png | Bin 0 -> 14476 bytes 8 files changed, 109 insertions(+), 27 deletions(-) create mode 100644 README.md create mode 100644 images/Verbindungsaufbau create mode 100644 images/Verbindungsaufbau.drawio.svg create mode 100644 images/Verbindungsaufbau.png diff --git a/Code/UI/Field.py b/Code/UI/Field.py index 0c778aa..8396e72 100644 --- a/Code/UI/Field.py +++ b/Code/UI/Field.py @@ -11,11 +11,22 @@ class Field: self.width = GeneralConfig.fields_amount_x + 2 self.height = GeneralConfig.fields_amount_y + 2 self.field_shift = -10 + self.shapes = Shapes() self.squares = self._creat_squares() - self.shapes = {Shape.VERTICAL_GLIDER: Shapes().creat_vertical_glieder} + + def get_square_on_click_pos(self, mousclick_pos): + index_x = 0 + index_y = 0 + for line in self.squares: + index_y += 1 + index_x = 0 + for square in line: + index_x += 1 + if square.rect.collidepoint(mousclick_pos): + return (index_y, index_x) def create_shape(self, shape: Shape, start_square_pos): - self.squares = self.shapes[shape](start_square_pos, self.squares) + self.squares = self.shapes.create_shape(start_square_pos,self.squares, shape) def _creat_squares(self): squares = [ diff --git a/Code/UI/PlayingField.py b/Code/UI/PlayingField.py index 604b82d..6d6e83f 100644 --- a/Code/UI/PlayingField.py +++ b/Code/UI/PlayingField.py @@ -33,18 +33,23 @@ class GameState: self.is_evolving = not self.is_evolving self.neighbours.toggle_pause(self.is_evolving) if event.key == pygame.K_v: - index_x = 0 - index_y = 0 - - for line in self.field.squares: - index_y += 1 - index_x = 0 - for square in line: - index_x += 1 - if square.rect.collidepoint(pygame.mouse.get_pos()): - self.field.create_shape(Shape.VERTICAL_GLIDER, - (int(index_y ), - int(index_x ))) + self.field.create_shape(Shape.VERTICAL_GLIDER_LEFT, + self.field.get_square_on_click_pos(pygame.mouse.get_pos())) + if event.key == pygame.K_b: + self.field.create_shape(Shape.VERTICAL_GLIDER_RIGHT, + self.field.get_square_on_click_pos(pygame.mouse.get_pos())) + if event.key == pygame.K_n: + self.field.create_shape(Shape.GLIDER_LEFT_DOWN, + self.field.get_square_on_click_pos(pygame.mouse.get_pos())) + if event.key == pygame.K_m: + self.field.create_shape(Shape.GLIDER_RIGHT, + self.field.get_square_on_click_pos(pygame.mouse.get_pos())) + if event.key == pygame.K_y: + self.field.create_shape(Shape.VERTICAL_GLIDER_RIGHT, + self.field.get_square_on_click_pos(pygame.mouse.get_pos())) + if event.key == pygame.K_x: + self.field.create_shape(Shape.VERTICAL_GLIDER_LEFT, + self.field.get_square_on_click_pos(pygame.mouse.get_pos())) def update_field_with_input(self, event): for line in self.field.squares: @@ -91,7 +96,7 @@ def run_game(game_state: GameState): time_elapsed_since_last_action += clock.get_time() if game_state.is_evolving: - if time_elapsed_since_last_action > 100: + if time_elapsed_since_last_action > 10: # start = ti.time() game_state.update_borders() game_state.evolve() diff --git a/Code/UI/Shape.py b/Code/UI/Shape.py index f9076fc..412aa5e 100644 --- a/Code/UI/Shape.py +++ b/Code/UI/Shape.py @@ -2,4 +2,11 @@ from enum import Enum class Shape(Enum): - VERTICAL_GLIDER = 1 + HORIZONTAL_GLIDER_LEFT = 1 + HORIZONTAL_GLIDER_RIGHT=2 + GLIDER_LEFT_DOWN = 3 + GLIDER_RIGHT=4 + VERTICAL_GLIDER_RIGHT=5 + VERTICAL_GLIDER_LEFT=6 + + diff --git a/Code/UI/Shapes.py b/Code/UI/Shapes.py index a7813e4..c6f54a2 100644 --- a/Code/UI/Shapes.py +++ b/Code/UI/Shapes.py @@ -1,23 +1,40 @@ +from operator import itemgetter + from Code import Config +from Code.UI.Shape import Shape class Shapes: - def creat_vertical_glieder(self, start_square_pos: tuple, squares): - points =[(0,0),(1,0),(2,0),(2,1),(1,2)] - if self._check_bounderies((6, 6), start_square_pos, squares): + def create_shape(self, start_square_pos: tuple, squares, type): + return self._create_shape(start_square_pos, squares, Structures.shapes[type]) + + def _get_size(self, points): + x = max(max(points, key=itemgetter(0))) + y = max(max(points, key=itemgetter(1))) + return (x, y) + + def _create_shape(self, start_square_pos: tuple, squares, points): + if self._check_bounderies(self._get_size(points), start_square_pos, squares): for point in points: - squares[start_square_pos[0]+point[1]][start_square_pos[1]+point[0]].active = True + squares[start_square_pos[0] + point[1]][start_square_pos[1] + point[0]].active = True return squares - - def _creat_shape(self,start_square_pos: tuple, squares): - - def _check_bounderies(self, bounderies, start_square_pos: tuple, field): - x_valid = (Config.GeneralConfig.fields_amount_x-2 - start_square_pos[1]) > bounderies[0] - y_valid = (Config.GeneralConfig.fields_amount_y-2 - start_square_pos[1]) > bounderies[1] - + print(bounderies) + delta_start_pos_x = start_square_pos[0] + delta_start_pos_y = start_square_pos[1] + x_valid = (Config.GeneralConfig.fields_amount_x - 2 - int(delta_start_pos_x)) > bounderies[0] + y_valid = (Config.GeneralConfig.fields_amount_y - 2 - int(delta_start_pos_y)) > bounderies[1] return x_valid and y_valid +class Structures: + shapes = {Shape.GLIDER_LEFT_DOWN: [(0, 0), (1, 0), (2, 0), (2, 1), (1, 2)], + Shape.HORIZONTAL_GLIDER_LEFT: [(0, 0), (1, 0), (2, 0), (3, 0), (4, 1), (4, 3), (0, 1), (0, 2), (1, 3)], + Shape.HORIZONTAL_GLIDER_RIGHT: [(0, 1), (0, 3), (1, 0), (3, 0), (2, 0), (4, 0), (4, 1), (4, 2), (3, 3)], + Shape.GLIDER_RIGHT: [(0, 2), (1, 0), (1, 2), (2, 1), (2, 2)], + Shape.VERTICAL_GLIDER_RIGHT: [(1, 0), (3, 0), (0, 1), (0, 3), (0, 2), (0, 4), (1, 4), (2, 4), (3, 3)], + Shape.VERTICAL_GLIDER_LEFT: [(0, 0), (0, 1), (0, 2), (0, 3), (1, 4), (3, 4), (1, 0), (2, 0), (3, 1)] + + } diff --git a/README.md b/README.md new file mode 100644 index 0000000..0f9b730 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# Game of Life über mehrere Monitore + +Implementierung des berühmten [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) als verteiltes System. +Jeder Teilnehmer hat einen Bereich in dem er Game of Life berechnet und seine Kanten mit den andren Teilnehmer austauscht. +Die gesamte Koordination erfolgt dezentral, Kommunikation ist immer p2p-basiert. + + +## Einstieg +Um an dem Spiel teilzunehmen, muss jeder Teilnehmer (außer der erste) beim Starten der Anwendung die IP-Adresse und den +Port eines anderen Teilnehmers angeben, mit dem er sich verbinden will. +Zusätzlich muss die Richtung der Kante, mit welcher er sich mit diesem verbinden will, angegeben werden. +Da jeder Teilnehmer an jeder seiner Kanten maximal mit einem anderen Teilnehmer verbunden sein kann, ist es möglich, +dass die Kante, die beim Einstieg gewählt wird, bereits besetzt ist. +Ist dies der Fall, wird der Anfragende automatisch an denjenigen weitergeleitet, der diese Kante besetzt. +Dies passiert so lange bis eine freie Kante gefunden wird und sich somit zwei Teilnehmer verbinden. +Wenn man sich also **rechts** von Teilnehmer 1 verbinden will, ist es nicht garantiert, dass man direkt mit Teilnehmer 1 +verbunden wird, sondern nur, dass man räumlich **rechts** von Teilnehmer 1 liegt. + +![Schema Verbindungsaufbau](./images/Verbindungsaufbau.drawio.svg) + +## Suche +Die Suche ist recht simpel. +Jeder Teilnehmer kennt nur die Knoten, welche die für ihn relevanten Informationen haben und diese verändern sich im Laufe +der Zeit auch nicht. + +## Verbreitung +Eine relevante Information, die an das gesamte Netz verbreitet werden muss, ist eine Zustandsänderung bezüglich der +Pausierung der Simulation. +Hierfür wird einfaches Flooding verwendet. +D.h. jeder Teilnehmer schickt den neuen Zustand an alle seine Nachbarn und diese senden ihn wiederum an ihre Nachbarn. +Da es nur zwei mögliche Zustände für den Pause-Zustand geben kann, ist Flooding ausreichend effizient, zyklische Nachrichten + werden vermieden, indem ein Teilnehmer die Information nicht mehr weiterleitet, wenn er sie bereits bekommen hat, sich also +schon im richtigen Zustand befindet. + +## Zeitliche Synchronisation +Um sicherzustellen, dass alle Teilnehmer gleichzeitig den Entwicklungsschritt durchführen und somit der Randaustausch auch +korrekt funktioniert. diff --git a/images/Verbindungsaufbau b/images/Verbindungsaufbau new file mode 100644 index 0000000..2292fb6 --- /dev/null +++ b/images/Verbindungsaufbau @@ -0,0 +1 @@ +1VjbcpswEP0aP9bDHecxdtJ20stkJmmTPnUUWECtQFTINs7XV4AEyCS+pGlIXhLt0e4i7e5Z1kzsRVp+YChPvtAQyMQywnJin00syzT9mfhXIZsG8U2jAWKGQ6nUAVf4HiSo1JY4hEJT5JQSjnMdDGiWQcA1DDFG17paRIn+1BzFMACuAkSG6A0OedKgM9fo8I+A40Q92TTkToqUsgSKBIV03YPs84m9YJTyZpWWCyBV8FRcGrv3j+y2B2OQ8UMM6EXp399/Kv6cej/n32buBY/Sd7Y8G9+oC0Mo7i9FynhCY5ohct6hc0aXWQiVV0NInc5nSnMBmgL8BZxvZDLRklMBJTwlchdKzG976x+Vq6nlSvGslK5rYaOEjLPNbV/om1VyZ1dLyrC5YXWtRwMnoYIuWQA7oqUKELEY+A49q02v4AXQFMR5hB0Dgjhe6edAskDjVq/LoVjINB6RUul3hchSPulaQB5KRXLm2V2RtzHpZX2dYA5XOaqvvxZU1jOGirwhV4TLKvPSwQoYh3J3WIdhUAa+JIbsDJYn5XWPZxJKehRT2LMHzhmTC4bGBf9ALpg6F/wX5IJ1IBceKYIX4sKQDF9BrCyPiDPP75hYxdXqO7A7nIXLLH57DdGcGqajF4I3O9lTCbV0CQyLQAOTYIQJWVBCWX1z2zPQiekLvOCM/obejnXme8ZIBeX9Y0HVpqeMoU1PIac440XP82UFdM3Ksbaa1WzrZbtH3xXJ1Iu5OUFX2u1Vnl7t1rDzW6+h09vGK+v0/iBQHf8LlEWsGgoHPYJBkFQ1so2P+tqYuv2eceAENXX77cLc0yqekePem3hpGGNm9InvgCMHgVE7/aFVMOoY7T04ObAh/a8BkwyStNobv9duT9WOP3KvnQ3ieAMiKoxUf7NByIIlW9U0aio6PK1+wwsxIKgocKAHUCfd/+9vT61YmRtj6jjGiZYe2x1kx30gOwo7bsgZTCXtVxX1dGcr6w11pdWu8WafoyZSA0fHjl1tWapqNrSxSx+jOmvlnkZRAXyyXcI7Ry0hdt9kGvXuy5Z9/hc= \ No newline at end of file diff --git a/images/Verbindungsaufbau.drawio.svg b/images/Verbindungsaufbau.drawio.svg new file mode 100644 index 0000000..0151560 --- /dev/null +++ b/images/Verbindungsaufbau.drawio.svg @@ -0,0 +1,4 @@ + + + +
T1 
T1 
Neue
Verbindung
Neue...
T2
T2
Verbindungsanfrage
rechts
Verbindungsanfrage...
Neuer
Teilnehmer
Neuer...
Weiterleiten
Weiterleit...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/images/Verbindungsaufbau.png b/images/Verbindungsaufbau.png new file mode 100644 index 0000000000000000000000000000000000000000..b2ba94569a5f9da0c23faecee563824cc1d0f556 GIT binary patch literal 14476 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 IP;SZp3yU{@6aWAK literal 0 HcmV?d00001