├── __init__.py ├── saves ├── hose.lvl ├── test.lvl └── house1.lvl ├── readme.md ├── cubedict.py ├── .gitignore ├── scene.py ├── cube.py ├── main.py └── server.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /saves/hose.lvl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtshrmn/cubeland/master/saves/hose.lvl -------------------------------------------------------------------------------- /saves/test.lvl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mtshrmn/cubeland/master/saves/test.lvl -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Cube Land 2 | 3 | 4 | *Cube Land* is a voxel based sculpting engine, for multiple users. 5 | - Join *Cube Land* 6 | - Create a sculpture 7 | - Magic 8 | 9 | ### Technologies involved 10 | 11 | *Cube Land* uses a number of open source projects to work properly: 12 | 13 | - openGL 14 | - pygame 15 | --- 16 | ### Installation 17 | 18 | *Cube Land* requires [python3.6](https://www.python.org/downloads/release/python-361/) to run. 19 | 20 | Install the dependencies and devDependencies and start the server. 21 | 22 | ```sh 23 | $ pip install pygame pyopengl 24 | ``` 25 | 26 | ### Usage 27 | 28 | ###### Once you installed the dependencies, it's all fairly simple. 29 | 30 | Starting the server: 31 | 32 | ```sh 33 | $ cd cubeland 34 | $ python server.py [IP] [PORT] 35 | ``` 36 | 37 | Launching the application: 38 | 39 | ```sh 40 | $ python main.py [IP] [PORT] 41 | ``` 42 | 43 | --- 44 | ## Demo: 45 | ##### how the application looks after few block placements. 46 | ![alt text](https://i.imgur.com/WCweq24.png "Cubeland structure in progress") 47 | 48 | ### Controls: 49 | 50 | - **W** - walk **forward** 51 | - **A** - walk **right** 52 | - **S** - walk **backward** 53 | - **D** - walk **right** 54 | - **Shift** - descend 55 | - **Space** - ascend 56 | - **MOUSE1** - **place** block 57 | - **MOUSE2** - **delete** block 58 | -------------------------------------------------------------------------------- /cubedict.py: -------------------------------------------------------------------------------- 1 | """ 2 | a new data structure to hold the map 3 | it's basically a python dictionary with a bit more to it. 4 | """ 5 | 6 | 7 | class CubeDict: 8 | def __init__(self): 9 | self.dict = dict() 10 | 11 | @staticmethod 12 | def index_gen(cords): 13 | """ 14 | generates a string for the indexing of the dictionary 15 | :param cords: coordinates to translate 16 | :return: 17 | """ 18 | x = round(float(cords[0]), 2) 19 | y = round(float(cords[1]), 2) 20 | z = round(float(cords[2]), 2) 21 | return str(x) + '$' + str(y) + '$' + str(z) 22 | 23 | def append(self, cube): 24 | """ 25 | a function to append the cube to the data structure. 26 | :type cube: cube.Cube 27 | :return: 28 | """ 29 | index_str = self.index_gen((cube.x, cube.y, cube.z)) 30 | self.dict[index_str] = cube 31 | 32 | def pop(self, cords): 33 | """ 34 | pop cube from data 35 | :param cords: the coordinates which we want to remove 36 | :return: the cube that is popped. if no object in coordinates, return __str__ function 37 | """ 38 | index_str = self.index_gen(cords) 39 | try: 40 | return self.dict.pop(index_str) 41 | except KeyError: 42 | return self.__str__() 43 | 44 | def draw(self): 45 | """ 46 | iterates over the data and draws each cube. 47 | :return: 48 | """ 49 | for key in list(self.dict): 50 | self.dict[key].draw() 51 | 52 | def __str__(self): 53 | """ 54 | the data structure string method 55 | :return: the string of the dict 56 | """ 57 | return str(self.dict) 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python,pycharm 3 | 4 | ### PyCharm ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/dictionaries 12 | 13 | # Sensitive or high-churn files: 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.xml 17 | .idea/**/dataSources.local.xml 18 | .idea/**/sqlDataSources.xml 19 | .idea/**/dynamic.xml 20 | .idea/**/uiDesigner.xml 21 | 22 | # Gradle: 23 | .idea/**/gradle.xml 24 | .idea/**/libraries 25 | 26 | # CMake 27 | cmake-build-debug/ 28 | 29 | # Mongo Explorer plugin: 30 | .idea/**/mongoSettings.xml 31 | 32 | ## File-based project format: 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | fabric.properties 54 | 55 | ### PyCharm Patch ### 56 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 57 | 58 | # *.iml 59 | # modules.xml 60 | # .idea/misc.xml 61 | # *.ipr 62 | 63 | # Sonarlint plugin 64 | .idea/sonarlint 65 | 66 | ### Python ### 67 | # Byte-compiled / optimized / DLL files 68 | __pycache__/ 69 | *.py[cod] 70 | *$py.class 71 | 72 | # C extensions 73 | *.so 74 | 75 | # Distribution / packaging 76 | .Python 77 | env/ 78 | build/ 79 | develop-eggs/ 80 | dist/ 81 | downloads/ 82 | eggs/ 83 | .eggs/ 84 | lib/ 85 | lib64/ 86 | parts/ 87 | sdist/ 88 | var/ 89 | wheels/ 90 | *.egg-info/ 91 | .installed.cfg 92 | *.egg 93 | 94 | # PyInstaller 95 | # Usually these files are written by a python script from a template 96 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 97 | *.manifest 98 | *.spec 99 | 100 | # Installer logs 101 | pip-log.txt 102 | pip-delete-this-directory.txt 103 | 104 | # Unit test / coverage reports 105 | htmlcov/ 106 | .tox/ 107 | .coverage 108 | .coverage.* 109 | .cache 110 | nosetests.xml 111 | coverage.xml 112 | *,cover 113 | .hypothesis/ 114 | 115 | # Translations 116 | *.mo 117 | *.pot 118 | 119 | # Django stuff: 120 | *.log 121 | local_settings.py 122 | 123 | # Flask stuff: 124 | instance/ 125 | .webassets-cache 126 | 127 | # Scrapy stuff: 128 | .scrapy 129 | 130 | # Sphinx documentation 131 | docs/_build/ 132 | 133 | # PyBuilder 134 | target/ 135 | 136 | # Jupyter Notebook 137 | .ipynb_checkpoints 138 | 139 | # pyenv 140 | .python-version 141 | 142 | # celery beat schedule file 143 | celerybeat-schedule 144 | 145 | # SageMath parsed files 146 | *.sage.py 147 | 148 | # dotenv 149 | .env 150 | 151 | # virtualenv 152 | .venv 153 | venv/ 154 | ENV/ 155 | 156 | # Spyder project settings 157 | .spyderproject 158 | .spyproject 159 | 160 | # Rope project settings 161 | .ropeproject 162 | 163 | # mkdocs documentation 164 | /site 165 | 166 | # End of https://www.gitignore.io/api/python,pycharm -------------------------------------------------------------------------------- /scene.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | import math 3 | import numpy 4 | from OpenGL.GL import * 5 | from OpenGL.GLU import * 6 | 7 | 8 | class Scene(object): 9 | """ 10 | Scene class is in charge of all the GUI and openGL controls. 11 | it is here to help with rendering and interacting with the user. 12 | """ 13 | 14 | def __init__(self, w=1920, h=1080, fov=55, fps=60, flags=pygame.OPENGL | pygame.DOUBLEBUF | pygame.HWSURFACE, 15 | debug=False): 16 | """ 17 | this is the initializing function, it runs when the class is being called. 18 | :param w: width of window 19 | :param h: height of window 20 | :param fov: field of view 21 | :param fps: frames per second 22 | :param flags: flags for pygame 23 | :param debug: enable or disable debug mode 24 | """ 25 | pygame.init() 26 | pygame.mouse.set_visible(False) 27 | pygame.event.set_grab(not debug) 28 | if debug: 29 | w = 680 30 | h = 480 31 | self.screen = pygame.display.set_mode((w, h), flags) 32 | 33 | else: 34 | self.screen = pygame.display.set_mode((w, h), flags | pygame.FULLSCREEN) 35 | 36 | glMatrixMode(GL_PROJECTION) 37 | gluPerspective(fov, w / h, 0.001, 100000.0) 38 | glMatrixMode(GL_MODELVIEW) 39 | self.keys = dict() 40 | self.mouse = dict() 41 | self.clock = pygame.time.Clock() 42 | self.fps = fps 43 | self.look_speed = 0.2 44 | self.move_speed = 0.1 45 | self.w = w 46 | self.h = h 47 | self.light_pos = [0, 0, 0, 0] 48 | self.pos = (0, 0, 0) 49 | self.debug = debug 50 | self.m = None 51 | 52 | def shading(self, pos): 53 | """ 54 | handles the shading of the scene using some openGL trickery. 55 | :param pos: position of the light source. 56 | :return: 57 | """ 58 | glEnable(GL_LIGHTING) 59 | glShadeModel(GL_FLAT) 60 | glEnable(GL_COLOR_MATERIAL) 61 | glMatrixMode(GL_MODELVIEW) 62 | 63 | self.light_pos = pos 64 | # position is in x, y, z, w format 65 | # if w is 0 then the light is "directional" 66 | # otherwise it is "positional" 67 | 68 | glLightfv(GL_LIGHT0, GL_AMBIENT, [0.6, 0.6, 0.6]) 69 | glLightfv(GL_LIGHT0, GL_DIFFUSE, [0.4, 0.4, 0.4]) 70 | glLightfv(GL_LIGHT0, GL_SPECULAR, [1, 0, 1]) 71 | glLightfv(GL_LIGHT0, GL_POSITION, self.light_pos) 72 | glEnable(GL_LIGHT0) 73 | 74 | def loop(self): 75 | """ 76 | this function is responsible for updating and controlling the GUI every frame. 77 | :return: True 78 | """ 79 | pygame.display.flip() 80 | pygame.event.pump() 81 | 82 | self.keys = dict((i, int(v)) for i, v in enumerate(pygame.key.get_pressed()) if i < 305) 83 | self.mouse = dict((int(i), int(v)) for i, v in enumerate(pygame.mouse.get_pressed()) if i < 6) 84 | glLightfv(GL_LIGHT0, GL_POSITION, self.light_pos) # make sure the light stays where it should be 85 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) 86 | glEnable(GL_DEPTH_TEST) 87 | 88 | self.clock.tick(self.fps) 89 | return True 90 | 91 | def controls(self, w_key=ord('w'), s_key=ord('s'), a_key=ord('a'), d_key=ord('d'), up_key=32, down_key=304): 92 | """ 93 | control the camera and translate the rendering as needed to create a realistic first person experience. 94 | :param w_key: forward key 95 | :param s_key: backward key 96 | :param a_key: left key 97 | :param d_key: right key 98 | :param up_key: ascend key 99 | :param down_key: descend key 100 | :return: 101 | """ 102 | for event in pygame.event.get(): 103 | if event.type == pygame.QUIT: 104 | pygame.quit() 105 | quit() 106 | 107 | # The actual camera setting cycle 108 | mouse_dx, mouse_dy = pygame.mouse.get_rel() 109 | 110 | buffer = glGetDoublev(GL_MODELVIEW_MATRIX) 111 | c = -1 * numpy.mat(buffer[:3, :3]) * numpy.mat(buffer[3, :3]).T 112 | # c is camera center in absolute coordinates, 113 | self.pos = c 114 | glTranslate(*c) 115 | m = buffer.flatten() 116 | self.m = m 117 | glRotate(mouse_dx * self.look_speed, m[1], m[5], m[9]) 118 | glRotate(mouse_dy * self.look_speed, m[0], m[4], m[8]) 119 | 120 | # compensate roll 121 | glRotated(-math.atan2(-m[4], m[5]) * 180 / math.pi, m[2], m[6], m[10]) 122 | glTranslate(*(-c)) 123 | 124 | # move forward-back or right-left 125 | # fwd = 0.1 if 'w' is pressed; -0.1 if 's' 126 | fwd = self.move_speed * (self.keys[w_key] - self.keys[s_key]) 127 | strafe = self.move_speed * (self.keys[a_key] - self.keys[d_key]) 128 | hover = self.move_speed * (self.keys[down_key] - self.keys[up_key]) 129 | 130 | if abs(fwd) or abs(strafe) or abs(hover): 131 | m = glGetDoublev(GL_MODELVIEW_MATRIX).flatten() 132 | glTranslate(fwd * m[2], 0, fwd * m[10]) 133 | glTranslate(0, hover * m[5], 0) 134 | glTranslate(strafe * m[0], 0, strafe * m[8]) 135 | -------------------------------------------------------------------------------- /cube.py: -------------------------------------------------------------------------------- 1 | from OpenGL.GL import * 2 | 3 | ''' 4 | the base class for the building blocks that will appear on the screen when the game is run. 5 | it's a generic box, everything is customizable. 6 | this class is the parent of all other objects that will be rendered in the game. 7 | ''' 8 | 9 | 10 | class Box(object): 11 | def __init__(self, x, y, z, xi, yi, zi, color): 12 | """ 13 | initializing function, it runs every time the class is being called. 14 | :param x: x position 15 | :param y: y position 16 | :param z: z position 17 | :param xi: x vector scale 18 | :param yi: y vector scale 19 | :param zi: z vector scale 20 | :param color: color in rgb 21 | """ 22 | self.x = x 23 | self.y = y 24 | self.z = z 25 | self.xi = xi 26 | self.yi = yi 27 | self.zi = zi 28 | self.color = color 29 | 30 | def draw(self): 31 | """ 32 | the OpenGL instructions so the box will be rendered properly. 33 | this function draws the object on screen. 34 | :return: 35 | """ 36 | vertices = ( 37 | (self.x + self.xi, self.y - self.yi, self.z - self.zi), 38 | (self.x + self.xi, self.y + self.yi, self.z - self.zi), 39 | (self.x - self.xi, self.y + self.yi, self.z - self.zi), 40 | (self.x - self.xi, self.y - self.yi, self.z - self.zi), 41 | (self.x + self.xi, self.y - self.yi, self.z + self.zi), 42 | (self.x + self.xi, self.y + self.yi, self.z + self.zi), 43 | (self.x - self.xi, self.y - self.yi, self.z + self.zi), 44 | (self.x - self.xi, self.y + self.yi, self.z + self.zi) 45 | 46 | ) 47 | 48 | surfaces = ( 49 | (4, 5, 1, 0), # right 50 | (3, 2, 7, 6), # left 51 | (6, 7, 5, 4), # front 52 | (0, 1, 2, 3), # back 53 | (1, 5, 7, 2), # top 54 | (4, 0, 3, 6) # bottom 55 | ) 56 | 57 | colors = [ 58 | 0.7, # right 59 | 0.7, # left 60 | 1, # front 61 | 0.6, # back 62 | 0.9, # top 63 | 0.3 # bottom 64 | 65 | ] 66 | 67 | vao = [] 68 | new_color = [] 69 | 70 | # generate color vertex array. 71 | for color in colors: 72 | for i in range(4): 73 | new_color.append(color * self.color[0]) 74 | new_color.append(color * self.color[1]) 75 | new_color.append(color * self.color[2]) 76 | 77 | # generate surface vertex array. 78 | for surface in surfaces: 79 | for vertex in surface: 80 | vao.append(vertices[vertex]) 81 | 82 | glEnableClientState(GL_VERTEX_ARRAY) 83 | glEnableClientState(GL_COLOR_ARRAY) 84 | glVertexPointer(3, GL_FLOAT, 0, vao) 85 | glColorPointer(3, GL_FLOAT, 0, new_color) 86 | glDrawArrays(GL_QUADS, 0, len(vao)) 87 | 88 | glDisableClientState(GL_VERTEX_ARRAY) 89 | glDisableClientState(GL_COLOR_ARRAY) 90 | 91 | 92 | ''' 93 | a cube, it's the same as a box, but all scales are the same. 94 | ''' 95 | 96 | 97 | class Cube(Box): 98 | # simple cube with coordinates and a scale. 99 | def __init__(self, cords, i, color): 100 | """ 101 | initializing function 102 | :param cords: tuple of the x, y, z coordinates. 103 | :param i: scale in all three dimensions 104 | :param color: color of the cube in rgb 105 | """ 106 | super().__init__(cords[0], cords[1], cords[2], i, i, i, color) 107 | 108 | 109 | ''' 110 | a floor, it's a huge flat plain that is square 111 | ''' 112 | 113 | 114 | class Floor(Box): 115 | # a squared 2d plane with a coordinates and a scale 116 | def __init__(self, x, y, z, i, color): 117 | """ 118 | 119 | :param x: x coordinate 120 | :param y: y coordinate 121 | :param z: z coordinate 122 | :param i: scale of the plain 123 | :param color: color in rgb 124 | """ 125 | super().__init__(x, y, z, i, 0.001, i, color) 126 | 127 | 128 | ''' 129 | wireframed cube with only outlines 130 | ''' 131 | 132 | 133 | class WireCube(Cube): 134 | # wireframe of a cube 135 | def __init__(self, coords, i): 136 | super().__init__(coords, i, None) 137 | 138 | def draw(self): 139 | """ 140 | draw function for the wireframe. 141 | :return: 142 | """ 143 | vertices = ( 144 | (self.x + self.xi, self.y - self.yi, self.z - self.zi), 145 | (self.x + self.xi, self.y + self.yi, self.z - self.zi), 146 | (self.x - self.xi, self.y + self.yi, self.z - self.zi), 147 | (self.x - self.xi, self.y - self.yi, self.z - self.zi), 148 | (self.x + self.xi, self.y - self.yi, self.z + self.zi), 149 | (self.x + self.xi, self.y + self.yi, self.z + self.zi), 150 | (self.x - self.xi, self.y - self.yi, self.z + self.zi), 151 | (self.x - self.xi, self.y + self.yi, self.z + self.zi) 152 | 153 | ) 154 | 155 | edges = ( 156 | (0, 1), 157 | (0, 3), 158 | (0, 4), 159 | (2, 1), 160 | (2, 3), 161 | (2, 7), 162 | (6, 3), 163 | (6, 4), 164 | (6, 7), 165 | (5, 1), 166 | (5, 4), 167 | (5, 7), 168 | 169 | ) 170 | 171 | glBegin(GL_LINES) 172 | glColor3fv((1, 1, 1)) 173 | 174 | for edge in edges: 175 | for vertex in edge: 176 | glVertex3fv(vertices[vertex]) 177 | glEnd() 178 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from scene import * 2 | from cube import * 3 | import socket 4 | from _thread import * 5 | import sys 6 | import select 7 | from queue import Queue 8 | from pickle import loads 9 | 10 | 11 | def approx(n): 12 | """ 13 | handles the grid layout in the 3d world. converts a coordinate into the nearest grid position. 14 | :param n: the coordinates of an object . 15 | :return: the closest coordinates that apply to the world grid. 16 | """ 17 | goal = 0.1 18 | n1 = round(n, 1) 19 | if n1 + goal - n < goal / 2: 20 | return round(n1 + goal, 4) 21 | elif n1 + goal - n > goal * 1.5: 22 | return round(n1 - goal, 4) 23 | else: 24 | return round(n1, 4) 25 | 26 | 27 | def update(q): 28 | """ 29 | syncs information with the server to ensure full synchronization. 30 | :param q: the queue that the client uses to communicate with the server 31 | :return: None 32 | """ 33 | global payload 34 | socket_list = [sys.stdin, s] # Get the list sockets which are readable 35 | ready_to_read, ready_to_write, in_error = select.select(socket_list, [], []) 36 | 37 | while True: 38 | for sock in ready_to_read: 39 | 40 | if sock == s: 41 | # incoming message from remote server, s 42 | 43 | data = sock.recv(4096).decode('utf-8') 44 | 45 | if not data: 46 | print('\nDisconnected from chat server') 47 | sys.exit() 48 | else: 49 | # print data 50 | q.put(data) 51 | sys.stdout.flush() 52 | 53 | else: 54 | # user entered a message 55 | # msg = sys.stdin.readline() 56 | s.send(str.encode(payload)) 57 | sys.stdout.flush() 58 | 59 | 60 | def payload_gen(coordinates): 61 | """ 62 | generates a string to send for the server based on the coordinates. 63 | :param coordinations: the coordinates of an object 64 | :return: the string that should be sent to server as a payload 65 | """ 66 | x = round(float(coordinates[0]), 2) 67 | y = round(float(coordinates[1]), 2) 68 | z = round(float(coordinates[2]), 2) 69 | return str(x) + '$' + str(y) + '$' + str(z) 70 | 71 | 72 | def cords_gen(l): 73 | """ 74 | the opposite of the payload_gen() function - generates a coordinates from the string received from server. 75 | :param l: list of strings that the server sent as coordinates 76 | :return: a universal form of coordinates, a tuple of floats 77 | """ 78 | return tuple([float(x) for x in l][:-1]) 79 | 80 | 81 | if __name__ == '__main__': 82 | 83 | if len(sys.argv) != 3: 84 | print('Wrong arguments') 85 | sys.exit(1) 86 | else: 87 | 88 | host = sys.argv[1] 89 | port = int(sys.argv[2]) 90 | 91 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 92 | 93 | # connect to remote host 94 | try: 95 | s.connect((host, port)) 96 | except: 97 | print('Unable to connect') 98 | sys.exit() 99 | 100 | print('Connected to remote host. You can start sending messages') 101 | sys.stdout.flush() 102 | 103 | scene = Scene(fov=55, flags=pygame.OPENGL | pygame.DOUBLEBUF) 104 | scene.move_speed = 0.01 105 | scene.shading([10, 3, 2, 1]) 106 | 107 | cubes = loads(s.recv(102400)) 108 | print('received map info', str(type(cubes))) 109 | 110 | objects = [ 111 | Floor(0, -0.05, 0, 2.5, (0.2, 0.2, 0.2)), 112 | cubes 113 | ] 114 | 115 | q = Queue() 116 | cooldown = 0 117 | 118 | start_new_thread(update, (q,)) 119 | while scene.loop(): 120 | # graphics 121 | for item in objects: 122 | item.draw() 123 | 124 | scene.controls() 125 | # wireframe position calculations 126 | cooldown += 1 127 | coords = tuple(map(lambda x: approx(float(x)), [pos for pos in scene.pos])) 128 | coords = [ 129 | coords[0] - approx(scene.m[2] / 3), 130 | coords[1] - approx(scene.m[6] / 3), 131 | coords[2] - approx(scene.m[10] / 3) 132 | ] 133 | 134 | for index, cord in enumerate(coords): 135 | if cord == 0: 136 | coords[index] = 0 137 | 138 | WireCube(coords, 0.05).draw() 139 | 140 | if scene.debug: 141 | pass 142 | 143 | # get packet from server 144 | 145 | payload = payload_gen(coords) 146 | 147 | # handle events 148 | if scene.mouse[2] and cooldown > 10: 149 | cubes.append(Cube(coords, 0.05, (0, 0, 1))) 150 | cooldown = 0 151 | payload = payload_gen(coords) + '$1' 152 | s.sendall(str.encode(payload)) 153 | 154 | if scene.mouse[0] and cooldown > 10: 155 | cubes.pop(tuple([round(x, 2) for x in coords])) 156 | cooldown = 0 157 | payload = payload_gen(coords) + '$2' 158 | s.sendall(str.encode(payload)) 159 | 160 | data = None 161 | try: 162 | data = q.get(False, 0) 163 | except: 164 | pass 165 | 166 | if data: 167 | info = data.split('$') 168 | print(data) 169 | if len(info) == 4: 170 | 171 | if info[3] == '1': 172 | cubes.append(Cube(cords_gen(info), 0.05, (0, 0, 1))) 173 | if info[3] == '2': 174 | print('attempting to remove' + str(cords_gen(info)), str(cubes.pop(cords_gen(info)))) 175 | 176 | payload = payload_gen(coords) 177 | 178 | if scene.keys[ord('q')]: 179 | s.sendall(str.encode('quit')) 180 | break 181 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import socket 3 | from select import select 4 | from pickle import dumps, loads 5 | from cubedict import CubeDict 6 | from cube import Cube 7 | 8 | ''' 9 | This is the server, it's used to enable the multiplayer synchronization between all clients. 10 | It has its own commands for manipulating the map: 11 | 12 | clear - reset map (works only when the server is empty) 13 | load [map_name] - loads the map into the server (works ony when server is empty) 14 | save [map.name] - saves the map to a file on the local machine, if the file already exists, it'll overwrite the save. 15 | ''' 16 | 17 | 18 | def server(HOST, PORT): 19 | """the server itself.""" 20 | server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 21 | server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 22 | server_socket.bind((HOST, PORT)) 23 | server_socket.listen(10) 24 | cubes = CubeDict() 25 | 26 | # add server socket object to the list of readable connections 27 | SOCKET_LIST.append(server_socket) 28 | 29 | print("server started on port " + str(PORT)) 30 | 31 | while True: 32 | 33 | # non blocking input system. 34 | while sys.stdin in select([sys.stdin], [], [], 0)[0]: 35 | command = input().split(' ') 36 | 37 | try: 38 | # simple commands 39 | if command[0] == 'load': 40 | if len(SOCKET_LIST) < 2: 41 | if len(command) > 1: 42 | with open('saves/' + command[1], 'rb') as f: 43 | cubes = loads(f.read()) 44 | print('load complete') 45 | else: 46 | print('invalid arguments') 47 | 48 | else: 49 | print('cannot load map, please try again later') 50 | elif command[0] == 'save': 51 | # save map 52 | if len(command) > 1: 53 | with open('saves/' + command[1], 'wb') as f: 54 | f.write(dumps(cubes)) 55 | print('save complete') 56 | else: 57 | print('invalid arguments') 58 | elif command[0] == 'clear': 59 | if len(SOCKET_LIST) < 2: 60 | cubes = CubeDict() 61 | else: 62 | print('cannot clear map, please try again later') 63 | 64 | else: 65 | print('unkown command "' + command[0] + '"') 66 | 67 | except: 68 | print('ERROR: file not found') 69 | 70 | # get the list sockets which are ready to be read through select 71 | # 4th arg, time_out = 0 : poll and never block 72 | ready_to_read, ready_to_write, in_error = select(SOCKET_LIST, [], [], 0) 73 | 74 | for sock in ready_to_read: 75 | # a new connection request recieved 76 | if sock == server_socket: 77 | sockfd, addr = server_socket.accept() 78 | SOCKET_LIST.append(sockfd) 79 | print("Client (%s, %s) connected" % addr) 80 | 81 | broadcast(server_socket, sockfd, "[%s:%s] entered our chatting room\n" % addr) 82 | payload = dumps(cubes) 83 | sockfd.sendall(payload) 84 | 85 | # a message from a client, not a new connection 86 | else: 87 | # process data recieved from client, 88 | try: 89 | # receiving data from the socket. 90 | data = sock.recv(RECV_BUFFER).decode('utf-8') 91 | if data: 92 | # there is something in the socket 93 | broadcast(server_socket, sock, data) 94 | modify_map(data, cubes) 95 | else: 96 | # remove the socket that's broken 97 | if sock in SOCKET_LIST: 98 | SOCKET_LIST.remove(sock) 99 | 100 | # at this stage, no data means probably the connection has been broken 101 | broadcast(server_socket, sock, "Client (%s, %s) is offline\n" % addr) 102 | 103 | # exception 104 | except: 105 | broadcast(server_socket, sock, "Client (%s, %s) is offline\n" % addr) 106 | continue 107 | 108 | 109 | # broadcast messages to all connected clients 110 | def broadcast(server_socket, sock, message): 111 | """ 112 | :param server_socket: the socket of the server 113 | :param sock: the client socket 114 | :param message: a message that the server will send to all sockets except the client 115 | :return: 116 | """ 117 | for socket in SOCKET_LIST: 118 | # send the message only to peer 119 | if socket != server_socket and socket != sock: 120 | try: 121 | socket.send(str.encode(message)) 122 | except: 123 | # broken socket connection 124 | socket.close() 125 | # broken socket, remove it 126 | if socket in SOCKET_LIST: 127 | SOCKET_LIST.remove(socket) 128 | 129 | 130 | def modify_map(data_stream, cubes): 131 | """ 132 | :param data_stream: the message the client has sent 133 | :param cubes: the map object that we will modify 134 | :return: 135 | """ 136 | if data_stream: 137 | info = data_stream.split('$') 138 | if len(info) == 4: 139 | 140 | if info[3] == '1': 141 | cubes.append(Cube(tuple([float(x) for x in info][:-1]), 0.05, (0, 0, 1))) 142 | if info[3] == '2': 143 | cubes.pop(tuple([float(x) for x in info][:-1])) 144 | 145 | 146 | if __name__ == "__main__": 147 | try: 148 | SOCKET_LIST = [] 149 | RECV_BUFFER = 4096 150 | if len(sys.argv) != 3: 151 | print('Wrong arguments') 152 | raise KeyboardInterrupt 153 | sys.exit(server(sys.argv[1], int(sys.argv[2]))) 154 | except KeyboardInterrupt: 155 | print('shutting down') 156 | -------------------------------------------------------------------------------- /saves/house1.lvl: -------------------------------------------------------------------------------- 1 | ?ccubedict 2 | CubeDict 3 | q)?q}qXdictq}q(X 0.0$0.0$0.2qccube 4 | Cube 5 | q)?q}q(Xxq GXyq 6 | GXzq G???????Xxiq G????????Xyiq 7 | G????????XziqG????????XcolorqKKK?qubX 0.0$0.0$0.1qh)?q}q(h Gh 8 | Gh G????????h G????????h 9 | G????????hG????????hhubX 0.0$0.0$0.0qh)?q}q(h Gh 10 | Gh Gh G????????h 11 | G????????hG????????hhubX 0.0$0.0$0.5qh)?q}q(h Gh 12 | Gh G??h G????????h 13 | G????????hG????????hhubX 0.0$0.0$0.6qh)?q}q(h Gh 14 | Gh G??333333h G????????h 15 | G????????hG????????hhubX 0.0$0.0$0.7qh)?q}q(h Gh 16 | Gh G??ffffffh G????????h 17 | G????????hG????????hhubX 0.0$0.1$0.7q h)?q!}q"(h Gh 18 | G????????h G??ffffffh G????????h 19 | G????????hG????????hhubX 0.0$0.2$0.7q#h)?q$}q%(h Gh 20 | G???????h G??ffffffh G????????h 21 | G????????hG????????hhubX 0.0$0.2$0.6q&h)?q'}q((h Gh 22 | G???????h G??333333h G????????h 23 | G????????hG????????hhubX 0.0$0.2$0.5q)h)?q*}q+(h Gh 24 | G???????h G??h G????????h 25 | G????????hG????????hhubX 0.0$0.1$0.5q,h)?q-}q.(h Gh 26 | G????????h G??h G????????h 27 | G????????hG????????hhubX 0.0$0.1$0.2q/h)?q0}q1(h Gh 28 | G????????h G???????h G????????h 29 | G????????hG????????hhubX 0.0$0.2$0.2q2h)?q3}q4(h Gh 30 | G???????h G???????h G????????h 31 | G????????hG????????hhubX 0.0$0.2$0.0q5h)?q6}q7(h Gh 32 | G???????h Gh G????????h 33 | G????????hG????????hhubX 0.0$0.2$0.1q8h)?q9}q:(h Gh 34 | G???????h G????????h G????????h 35 | G????????hG????????hhubX 0.0$0.1$0.0q;h)?q<}q=(h Gh 36 | G????????h Gh G????????h 37 | G????????hG????????hhubX 38 | -0.1$0.0$-0.1q>h)?q?}q@(h G????????h 39 | Gh G????????h G????????h 40 | G????????hG????????hhubX 41 | -0.2$0.0$-0.1qAh)?qB}qC(h G???????h 42 | Gh G????????h G????????h 43 | G????????hG????????hhubX 44 | -0.3$0.0$-0.1qDh)?qE}qF(h G??333333h 45 | Gh G????????h G????????h 46 | G????????hG????????hhubX 47 | -0.4$0.0$-0.1qGh)?qH}qI(h G???????h 48 | Gh G????????h G????????h 49 | G????????hG????????hhubX 50 | -0.1$0.1$-0.1qJh)?qK}qL(h G????????h 51 | G????????h G????????h G????????h 52 | G????????hG????????hhubX 53 | -0.1$0.2$-0.1qMh)?qN}qO(h G????????h 54 | G???????h G????????h G????????h 55 | G????????hG????????hhubX 56 | -0.2$0.2$-0.1qPh)?qQ}qR(h G???????h 57 | G???????h G????????h G????????h 58 | G????????hG????????hhubX 59 | -0.3$0.2$-0.1qSh)?qT}qU(h G??333333h 60 | G???????h G????????h G????????h 61 | G????????hG????????hhubX 62 | -0.5$0.2$-0.1qVh)?qW}qX(h G??h 63 | G???????h G????????h G????????h 64 | G????????hG????????hhubX 65 | -0.4$0.2$-0.1qYh)?qZ}q[(h G???????h 66 | G???????h G????????h G????????h 67 | G????????hG????????hhubX 68 | -0.5$0.0$-0.1q\h)?q]}q^(h G??h 69 | Gh G????????h G????????h 70 | G????????hG????????hhubX 0.0$0.0$-0.1q_h)?q`}qa(h Gh 71 | Gh G????????h G????????h 72 | G????????hG????????hhubX 0.0$0.1$-0.1qbh)?qc}qd(h Gh 73 | G????????h G????????h G????????h 74 | G????????hG????????hhubX 0.0$0.2$-0.1qeh)?qf}qg(h Gh 75 | G???????h G????????h G????????h 76 | G????????hG????????hhubX 0.0$0.3$-0.1qhh)?qi}qj(h Gh 77 | G??333333h G????????h G????????h 78 | G????????hG????????hhubX 0.0$0.2$0.3qkh)?ql}qm(h Gh 79 | G???????h G??333333h G????????h 80 | G????????hG????????hhubX 0.0$0.2$0.4qnh)?qo}qp(h Gh 81 | G???????h G???????h G????????h 82 | G????????hG????????hhubX -0.7$0.0$0.0qqh)?qr}qs(h G??ffffffh 83 | Gh Gh G????????h 84 | G????????hG????????hhubX -0.7$0.0$0.1qth)?qu}qv(h G??ffffffh 85 | Gh G????????h G????????h 86 | G????????hG????????hhubX -0.7$0.0$0.2qwh)?qx}qy(h G??ffffffh 87 | Gh G???????h G????????h 88 | G????????hG????????hhubX -0.7$0.0$0.3qzh)?q{}q|(h G??ffffffh 89 | Gh G??333333h G????????h 90 | G????????hG????????hhubX -0.7$0.0$0.4q}h)?q~}q(h G??ffffffh 91 | Gh G???????h G????????h 92 | G????????hG????????hhubX -0.7$0.0$0.5q?h)?q?}q?(h G??ffffffh 93 | Gh G??h G????????h 94 | G????????hG????????hhubX -0.7$0.0$0.7q?h)?q?}q?(h G??ffffffh 95 | Gh G??ffffffh G????????h 96 | G????????hG????????hhubX -0.7$0.0$0.8q?h)?q?}q?(h G??ffffffh 97 | Gh G??????h G????????h 98 | G????????hG????????hhubX -0.7$0.0$0.6q?h)?q?}q?(h G??ffffffh 99 | Gh G??333333h G????????h 100 | G????????hG????????hhubX 101 | -0.6$0.0$-0.1q?h)?q?}q?(h G??333333h 102 | Gh G????????h G????????h 103 | G????????hG????????hhubX 104 | -0.6$0.1$-0.1q?h)?q?}q?(h G??333333h 105 | G????????h G????????h G????????h 106 | G????????hG????????hhubX 107 | -0.6$0.2$-0.1q?h)?q?}q?(h G??333333h 108 | G???????h G????????h G????????h 109 | G????????hG????????hhubX 110 | -0.7$0.0$-0.1q?h)?q?}q?(h G??ffffffh 111 | Gh G????????h G????????h 112 | G????????hG????????hhubX 113 | -0.7$0.1$-0.1q?h)?q?}q?(h G??ffffffh 114 | G????????h G????????h G????????h 115 | G????????hG????????hhubX 116 | -0.7$0.2$-0.1q?h)?q?}q?(h G??ffffffh 117 | G???????h G????????h G????????h 118 | G????????hG????????hhubX 119 | -0.7$0.3$-0.1q?h)?q?}q?(h G??ffffffh 120 | G??333333h G????????h G????????h 121 | G????????hG????????hhubX -0.7$0.2$0.0q?h)?q?}q?(h G??ffffffh 122 | G???????h Gh G????????h 123 | G????????hG????????hhubX -0.7$0.2$0.1q?h)?q?}q?(h G??ffffffh 124 | G???????h G????????h G????????h 125 | G????????hG????????hhubX -0.7$0.2$0.3q?h)?q?}q?(h G??ffffffh 126 | G???????h G??333333h G????????h 127 | G????????hG????????hhubX -0.7$0.2$0.2q?h)?q?}q?(h G??ffffffh 128 | G???????h G???????h G????????h 129 | G????????hG????????hhubX -0.7$0.2$0.4q?h)?q?}q?(h G??ffffffh 130 | G???????h G???????h G????????h 131 | G????????hG????????hhubX -0.7$0.2$0.5q?h)?q?}q?(h G??ffffffh 132 | G???????h G??h G????????h 133 | G????????hG????????hhubX -0.7$0.2$0.6q?h)?q?}q?(h G??ffffffh 134 | G???????h G??333333h G????????h 135 | G????????hG????????hhubX -0.7$0.1$0.8q?h)?q?}q?(h G??ffffffh 136 | G????????h G??????h G????????h 137 | G????????hG????????hhubX -0.7$0.2$0.8q?h)?q?}q?(h G??ffffffh 138 | G???????h G??????h G????????h 139 | G????????hG????????hhubX -0.7$0.2$0.7q?h)?q?}q?(h G??ffffffh 140 | G???????h G??ffffffh G????????h 141 | G????????hG????????hhubX -0.7$0.1$0.5q?h)?q?}q?(h G??ffffffh 142 | G????????h G??h G????????h 143 | G????????hG????????hhubX -0.7$0.1$0.4q?h)?q?}q?(h G??ffffffh 144 | G????????h G???????h G????????h 145 | G????????hG????????hhubX -0.7$0.1$0.3q?h)?q?}q?(h G??ffffffh 146 | G????????h G??333333h G????????h 147 | G????????hG????????hhubX -0.7$0.1$0.2q?h)?q?}q?(h G??ffffffh 148 | G????????h G???????h G????????h 149 | G????????hG????????hhubX -0.7$0.1$0.1q?h)?q?}q?(h G??ffffffh 150 | G????????h G????????h G????????h 151 | G????????hG????????hhubX -0.7$0.1$0.0q?h)?q?}q?(h G??ffffffh 152 | G????????h Gh G????????h 153 | G????????hG????????hhubX -0.7$0.1$0.6q?h)?q?}q?(h G??ffffffh 154 | G????????h G??333333h G????????h 155 | G????????hG????????hhubX -0.7$0.1$0.7q?h)?q?}q?(h G??ffffffh 156 | G????????h G??ffffffh G????????h 157 | G????????hG????????hhubX -0.6$0.0$0.8q?h)?q?}q?(h G??333333h 158 | Gh G??????h G????????h 159 | G????????hG????????hhubX -0.5$0.0$0.8q?h)?q?}q?(h G??h 160 | Gh G??????h G????????h 161 | G????????hG????????hhubX -0.4$0.0$0.8q?h)?q?}q?(h G???????h 162 | Gh G??????h G????????h 163 | G????????hG????????hhubX -0.3$0.0$0.8q?h)?q?}q?(h G??333333h 164 | Gh G??????h G????????h 165 | G????????hG????????hhubX -0.1$0.0$0.8q?h)?q?}q?(h G????????h 166 | Gh G??????h G????????h 167 | G????????hG????????hhubX -0.2$0.0$0.8q?h)?q?}q?(h G???????h 168 | Gh G??????h G????????h 169 | G????????hG????????hhubX -0.1$0.1$0.8q?h)?q?}q?(h G????????h 170 | G????????h G??????h G????????h 171 | G????????hG????????hhubX -0.1$0.2$0.8q?h)?q?}q?(h G????????h 172 | G???????h G??????h G????????h 173 | G????????hG????????hhubX -0.2$0.2$0.8q?h)?q?}q?(h G???????h 174 | G???????h G??????h G????????h 175 | G????????hG????????hhubX -0.3$0.2$0.8q?h)?q?}q?(h G??333333h 176 | G???????h G??????h G????????h 177 | G????????hG????????hhubX -0.4$0.2$0.8q?h)?q?}q?(h G???????h 178 | G???????h G??????h G????????h 179 | G????????hG????????hhubX -0.5$0.2$0.8q?h)?q?}q?(h G??h 180 | G???????h G??????h G????????h 181 | G????????hG????????hhubX -0.6$0.2$0.8q?h)?q?}q?(h G??333333h 182 | G???????h G??????h G????????h 183 | G????????hG????????hhubX 0.0$0.0$0.8q?h)?q?}r(h Gh 184 | Gh G??????h G????????h 185 | G????????hG????????hhubX 0.0$0.1$0.8rh)?r}r(h Gh 186 | G????????h G??????h G????????h 187 | G????????hG????????hhubX 0.0$0.2$0.8rh)?r}r(h Gh 188 | G???????h G??????h G????????h 189 | G????????hG????????hhubX 0.0$0.3$0.8rh)?r}r (h Gh 190 | G??333333h G??????h G????????h 191 | G????????hG????????hhubX -0.7$0.3$0.8r 192 | h)?r }r (h G??ffffffh 193 | G??333333h G??????h G????????h 194 | G????????hG????????hhubusb. --------------------------------------------------------------------------------