├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── blender ├── 14_cubes_high_score.sk4 ├── 20_cubes_score.sk4 ├── __init__.py ├── create_scene_4d.py ├── previous_outputs │ ├── first_test_render.avi │ ├── first_test_render.mp4 │ ├── first_test_render_screen_record.mp4 │ ├── vaporwave_render.avi │ └── vaporwave_render.mp4 ├── snake_visualization.blend ├── snake_visualization.blend1 ├── snake_visualization_corrupted.blend └── snake_visualization_test_render.blend ├── images ├── Labelled_instructions.odp ├── Labelled_instructions.png ├── Screenshot_1.png └── main_screen.png ├── main.py ├── profiler.py ├── profiler_data ├── Opt_draw_functions │ ├── beauty_stats.txt │ ├── profiler_beautify_stats.txt │ └── stats └── base_test │ ├── profiler_beautify_stats.txt │ └── profiler_stats ├── settings ├── _tmp_replay_file.sk4 ├── paths.txt └── replay.txt ├── src ├── __init__.py ├── bfh.py ├── g_eng.py ├── keybuf.py ├── load_replay.py ├── mat.py ├── paths.txt ├── poly.py ├── prj.py ├── qua.py ├── rate.py ├── rem_path.py ├── replay.py ├── rot4.py ├── score.py ├── snake.py ├── vec.py └── visu.py └── tests ├── 14_cubes_high_score.sk4 ├── dont_reset.sk4 ├── frame_test.sk4 ├── game_frame_test.sk4 ├── ho_perso_ancora.sk4 ├── how_to_lose ├── new_game_after_yea.sk4 ├── new_game_yea.sk4 ├── omgis8mb.sk4 └── test_new_replay.sk4 /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | frames/ 3 | blender/outputs/ 4 | tests/ 5 | settings/scores* 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Pella86 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Snake 4d 2 | This program is a 4 spatial dimensions snake game. The snake is inspired by the nokia game, or the arcades before that 3 | The implementation is a simple perspective projection from the 4d space to the 2d screen 4 | 5 | 6 | ## Usage 7 | The game requires python (3.0) to be installed and the tkinter package. 8 | To run the game just use 9 | 10 | ```python main.py``` 11 | 12 | ## Trailer - rendered in Blender3d 13 | https://youtu.be/M1nB-Q0JOBA 14 | 15 | ## How to play 16 | ![Instruction image](/images/Labelled_instructions.png) 17 | 18 | The snake can move in 8 directions (in 2 dimensions there are 4, in 3 dimensions there are 6 and in the 4 space there are 8 possible directions). 19 | The directions are mapped to the keys WASD and IJKL. 20 | To help visualize where the snake is going there are 3 screens, 1 having a perspective projection of the game, which in general is quite confusing (although looks cool). 21 | There are two more screens, where the snake game is flat projected on the major planes. These flat prjection can help to follow where the snake is going. 22 | The flat screens are set so that the direction of the snakes corresponds to the keyboard direction. Namely pressing A will turn the snake left in the YX plane, while pressing L will turn the snake right in the WZ plane. 23 | The food is a blue cube randomly spawned in the game, catching the food gives one point. 24 | going outside the borders or crossing the snake with itself ends the game. 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/__init__.py -------------------------------------------------------------------------------- /blender/14_cubes_high_score.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/14_cubes_high_score.sk4 -------------------------------------------------------------------------------- /blender/20_cubes_score.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/20_cubes_score.sk4 -------------------------------------------------------------------------------- /blender/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/__init__.py -------------------------------------------------------------------------------- /blender/create_scene_4d.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Jun 28 01:14:38 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | # ============================================================================= 9 | # Imports 10 | # ============================================================================= 11 | 12 | # make the folder src as a local import path 13 | import sys 14 | if "../src" not in sys.path: 15 | sys.path.append("../src") 16 | 17 | # python imports 18 | import math 19 | 20 | # blender imports 21 | import bpy 22 | 23 | # custom imports 24 | import load_replay 25 | import prj 26 | import rot4 27 | 28 | # ============================================================================= 29 | # Script constants 30 | # ============================================================================= 31 | 32 | # load the replay 33 | replay = load_replay.LoadReplay() 34 | replay.load_replay_file("./20_cubes_score.sk4") 35 | 36 | # map the materials to the colors of the snake 37 | color_to_mat = {} 38 | color_to_mat["black"] = "BBoxMat" 39 | color_to_mat["blue"] = "FoodMat" 40 | color_to_mat["green"] = "SnakeMat" 41 | 42 | 43 | # 4d camera 44 | cam4 = prj.Cam4() 45 | 46 | # ============================================================================= 47 | # Functions 48 | # ============================================================================= 49 | 50 | # clear eventual random cubes around 51 | def clear_hcubes(prefix): 52 | print("Clear cubes function...") 53 | 54 | for obj in bpy.data.objects: 55 | if obj.name.startswith(prefix): 56 | print(f"\rObject {obj.name} being deleted", end="") 57 | 58 | ob = bpy.data.objects[obj.name] 59 | bpy.context.scene.collection.objects.unlink(ob) 60 | 61 | bpy.data.objects.remove(ob) 62 | 63 | print() 64 | 65 | print(f"deleted {prefix}_ objects") 66 | 67 | # creates an objet starting from a 4d set of points, and eventual rotations 68 | def create_blender_object(name, poly4, rot): 69 | # name it 70 | name = "hcube_" + name 71 | 72 | # create mesh 73 | meshName = name + "_mesh" 74 | 75 | # project the stuff 76 | v_list3 = [] 77 | for v in poly4.v_list: 78 | vrot = rot4.rot_v(v, *rot) 79 | v3 = cam4.prj(vrot) 80 | v_list3.append(v3.coords) 81 | 82 | me = bpy.data.meshes.new(meshName) 83 | me.from_pydata(v_list3, poly4.e_list, poly4.f_list) 84 | 85 | # create the object and mesh 86 | ob = bpy.data.objects.new(name, me) 87 | 88 | # add the material 89 | mat_name = color_to_mat[poly4.color] 90 | 91 | mat = bpy.data.materials[mat_name] 92 | ob.data.materials.append(mat) 93 | 94 | # link the created objects to the scene 95 | bpy.context.scene.collection.objects.link(ob) 96 | 97 | # return the ob 98 | return ob 99 | 100 | 101 | def hide(ob, prop, value, frame): 102 | ob.__setattr__(prop, value) 103 | ob.keyframe_insert(data_path=prop, frame=frame) 104 | 105 | 106 | def hide_range(ob, prop, start, end): 107 | hide(ob, prop, True, start - 1) 108 | hide(ob, prop, False, start) 109 | hide(ob, prop, True, end + 1) 110 | 111 | def visibility(ob, start, end): 112 | # turn off visibility on previous frames 113 | hide_range(ob, "hide_viewport", start, end) 114 | hide_range(ob, "hide_render", start, end) 115 | 116 | # ============================================================================= 117 | # Start the animation construction 118 | # ============================================================================= 119 | 120 | # clear the scene in case there are hyper cubes around 121 | clear_hcubes("hcube") 122 | 123 | # rotation 124 | angle = 0 125 | 126 | # constants 127 | # the game frames to be read 128 | game_frames = len(replay.frames) 129 | 130 | # animation frames per game frame 131 | ani_game_frames = 2 132 | 133 | for game_frame in range(game_frames): 134 | 135 | for frame in range(ani_game_frames): 136 | 137 | p_list = replay.get_frame() 138 | 139 | 140 | ani_frame = game_frame * ani_game_frames + frame 141 | 142 | for i, p in enumerate(p_list): 143 | rot = [angle, 0, 0, 0, 0, 0] 144 | 145 | ob = create_blender_object(f"{ani_frame}_{i}", p, rot) 146 | visibility(ob, ani_frame, ani_frame) 147 | 148 | 149 | angle += math.radians(1.44) 150 | 151 | print(f"\rCreated game frame: {game_frame}, blender frame: {ani_frame}", end="") 152 | replay.next_frame() 153 | 154 | print() 155 | 156 | # read all frames 157 | 158 | # for each frame 159 | # copy 160 | # set the 4d camera position 161 | # create the blender object 162 | # set visibility of object 163 | 164 | 165 | print("--------SCRIPT DONE---------------") -------------------------------------------------------------------------------- /blender/previous_outputs/first_test_render.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/previous_outputs/first_test_render.avi -------------------------------------------------------------------------------- /blender/previous_outputs/first_test_render.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/previous_outputs/first_test_render.mp4 -------------------------------------------------------------------------------- /blender/previous_outputs/first_test_render_screen_record.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/previous_outputs/first_test_render_screen_record.mp4 -------------------------------------------------------------------------------- /blender/previous_outputs/vaporwave_render.avi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/previous_outputs/vaporwave_render.avi -------------------------------------------------------------------------------- /blender/previous_outputs/vaporwave_render.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/previous_outputs/vaporwave_render.mp4 -------------------------------------------------------------------------------- /blender/snake_visualization.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/snake_visualization.blend -------------------------------------------------------------------------------- /blender/snake_visualization.blend1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/snake_visualization.blend1 -------------------------------------------------------------------------------- /blender/snake_visualization_corrupted.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/snake_visualization_corrupted.blend -------------------------------------------------------------------------------- /blender/snake_visualization_test_render.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/blender/snake_visualization_test_render.blend -------------------------------------------------------------------------------- /images/Labelled_instructions.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/images/Labelled_instructions.odp -------------------------------------------------------------------------------- /images/Labelled_instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/images/Labelled_instructions.png -------------------------------------------------------------------------------- /images/Screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/images/Screenshot_1.png -------------------------------------------------------------------------------- /images/main_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/images/main_screen.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 17 13:20:44 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | # ============================================================================= 9 | # Imports 10 | # ============================================================================= 11 | 12 | import sys 13 | if "./src" not in sys.path: 14 | sys.path.append("./src") 15 | 16 | # py imports 17 | import datetime 18 | import shutil 19 | 20 | # GUI stuff 21 | from tkinter import (Tk, _tkinter, StringVar, Label, Menu, Toplevel, 22 | messagebox) 23 | 24 | # project imports 25 | import visu 26 | import g_eng 27 | import score 28 | import replay 29 | import keybuf 30 | import rate 31 | 32 | 33 | #============================================================================== 34 | # # TO DO list 35 | #============================================================================== 36 | 37 | # Think about a better way to do the replay 38 | 39 | #============================================================================== 40 | # Help message 41 | #============================================================================== 42 | 43 | INFO_TEXT = '''*****************snake 4d*****************\n 44 | Rules: \n 45 | 1. eat the food (blue cube) \n 46 | 2. don't hit the walls (black) \n 47 | 3. don't cross yourself (green) \n 48 | \n 49 | How to play: \n 50 | W I \n 51 | ASD JKL \n 52 | These keys control the SNAKE in the 6 \n 53 | directions: \n 54 | \n 55 | W = UP \n 56 | A = RIGHT \n 57 | S = DOWN \n 58 | D = LEFT \n 59 | I = OUT \n 60 | J = FORWARD \n 61 | K = IN \n 62 | L = REVERSE \n 63 | \n 64 | To control the CAMERA use: \n 65 | Y, X, C to control 3d rotations \n 66 | V, B, N, M, T or Z to control 4d \n 67 | rotations \n 68 | **********************************************\n''' 69 | 70 | #============================================================================== 71 | # Main Application 72 | #============================================================================== 73 | 74 | class MainApp: 75 | ''' 76 | Main Application 77 | 78 | controls everything in the game from graphics to game engine 79 | 80 | Keyword argument: 81 | root -- the parent frame, best if is root 82 | 83 | Public methods: 84 | rot4 -- 4d rotations 85 | rot3 -- 3d rotations 86 | help_cmd -- command that calls the help message 87 | new_game -- command that reinitializes the game 88 | toggle_pause -- pauses unpauses the game 89 | move -- triggers the change of direction given by the key pressed 90 | draw -- draws the scene 91 | updater -- the cycle ticker of the game 92 | 93 | Instance variables: 94 | self.game -- the game engine 95 | self.root -- the parent frame 96 | self.score_str -- is the text variable for the score 97 | self.areas -- are the drawing areas 98 | self.rot3k_to_angle -- maps the keys to the 3d rotations 99 | self.rot4k_to_angle -- maps the keys to the 4d rotations 100 | self.key_buffer -- the keys buffer 101 | self.paused -- the game state paused or not 102 | self.score_board -- the score display utilites 103 | self.replay -- manages the replay 104 | ''' 105 | 106 | def __init__(self, root): 107 | # start the game engine 108 | self.game = g_eng.GameEngine() 109 | 110 | # root frame of tkinter 111 | self.root = root 112 | 113 | # creates top menu 114 | self.create_menu() 115 | 116 | # score label 117 | self.score_str = StringVar() 118 | self.score_str.set("Score: 0") 119 | 120 | label = Label(self.root, textvariable=self.score_str) 121 | label.grid(row=0, column=0, columnspan=2) 122 | 123 | # game areas, first area is the perspective view, 124 | # the other two areas are the orthographic 2d projections of the game 125 | # the project flat class inverts the y axis, so that when pressing 126 | # wasd or ijkl the direction match the keyboard 127 | 128 | self.areas = [] 129 | 130 | # area 0 - perspective 131 | proj = visu.ProjectCam() 132 | area4 = visu.VisuArea(root, proj, [500, 500]) 133 | area4.fP.grid(row=1, column=0, columnspan=2) 134 | self.areas.append(area4) 135 | 136 | # area 1 - 2d projection of the yx axis 137 | proj = visu.ProjectFlat("yx") 138 | proj.zoom = 0.5 139 | area_xy = visu.VisuArea(root, proj, [250, 250], "YX") 140 | area_xy.fP.grid(row=2, column=0) 141 | self.areas.append(area_xy) 142 | 143 | # area 2 - 2d projection of the wz axis 144 | proj = visu.ProjectFlat("wz") 145 | proj.zoom = 0.5 146 | area_wz = visu.VisuArea(root, proj, [250, 250], "WZ") 147 | area_wz.fP.grid(row=2, column=1) 148 | self.areas.append(area_wz) 149 | 150 | # snake movements 151 | 152 | # moving keys pressed wasd, ijkl 153 | bind_key = ["w", "s", "a", "d", "i", "k", "j", "l"] 154 | for key in bind_key: 155 | self.root.bind(key, self.move) 156 | 157 | # scene rotations - both + and - roations are supported, pressing x 158 | # will rotate the scene in a direction and pressing X (shift+x) will 159 | # rotate backwards in respect to that direction 160 | # x, y, c: 3d rotations 161 | # v, b, n, m, t, z: 4d rotations 162 | self.rot3k_to_angle = self.set_rotation_keys("xyc") 163 | self.rot4k_to_angle = self.set_rotation_keys("vbnmtz") 164 | 165 | # the buffered key press 166 | self.key_buffer = keybuf.KeyBuffer() 167 | 168 | # pause function 169 | self.paused = True 170 | self.root.bind("p", self.toggle_pause) 171 | 172 | # new game function 173 | self.root.bind("", self.new_game) 174 | 175 | # adds a central text to the first area for instructions 176 | self.areas[0].add_text("Press any key to play") 177 | 178 | # the scores, which are saved in score.txt 179 | self.score_board = score.ScoreBoard(self.root) 180 | 181 | # creates the replay settings 182 | self.replay = replay.Replay(self.game) 183 | 184 | 185 | 186 | def create_menu(self): 187 | ''' 188 | creates the top drop down menus 189 | 190 | File: 191 | New Game 192 | Quit 193 | Replay: 194 | Replay settings 195 | Save replay 196 | Load replay 197 | Help: 198 | Help 199 | ''' 200 | 201 | menubar = Menu(self.root) 202 | filemenu = Menu(menubar, tearoff=0) 203 | filemenu.add_command(label="New Game", command=lambda: self.new_game(1)) 204 | filemenu.add_cascade(label="Quit", command=self.root.destroy) 205 | 206 | replaymenu = Menu(menubar, tearoff=0) 207 | replaymenu.add_command(label="Replay settings", command=self.replay_settings_display) 208 | replaymenu.add_command(label="Save replay", command=self.save_replay) 209 | replaymenu.add_command(label="Load replay", command=self.load_replay) 210 | 211 | helpmenu = Menu(menubar, tearoff=0) 212 | helpmenu.add_command(label="Help", command=self.help_cmd) 213 | 214 | menubar.add_cascade(label="File", menu=filemenu) 215 | menubar.add_cascade(label="Replay", menu =replaymenu) 216 | menubar.add_cascade(label="Help", menu=helpmenu) 217 | 218 | self.root.config(menu=menubar) 219 | 220 | def load_replay(self): 221 | filename = self.replay.select_load_path() 222 | 223 | if filename: 224 | self.replay.reset_replay_file() 225 | 226 | self.paused = True 227 | 228 | self.replay.load_replay(filename) 229 | 230 | def save_replay(self): 231 | 232 | if self.replay.tmp_replay_file_is_empty(): 233 | messagebox.showwarning(title= "No replay file", 234 | message="The replay file is empty\nis the record setting off?") 235 | else: 236 | filename = self.replay.select_save_path() 237 | 238 | if filename: 239 | # copy the temp replay to a new filename 240 | src = self.replay.tmp_replay_file 241 | shutil.copy(src, filename) 242 | 243 | def replay_settings_display(self): 244 | # Displays the replay settings, which are the record checkbox 245 | self.replay.replay_settings.display(self.root) 246 | 247 | # binds the rotation keys to the function and set how big is the rotation 248 | def set_rotation_keys(self, keys): 249 | n_rot = len(keys) 250 | angle_delta = 5 # degrees 251 | 252 | # rotations mapping 253 | rot_keys = list(keys) + list(keys.upper()) 254 | for key in rot_keys: 255 | self.root.bind(key, self.rot) 256 | 257 | # creates a dictionary that maps the key pressed to set of angles 258 | # if the key is capital (shift+key) it will have a negative rotation 259 | rot_to_angle = {} 260 | 261 | for i, k in enumerate(rot_keys): 262 | # set the rotations to 0 263 | possible_rots = [0 for i in range(n_rot)] 264 | 265 | # assign the angle to the relevant rotation 266 | possible_rots[i % n_rot] = angle_delta if k.islower() else -angle_delta 267 | rot_to_angle[k] = possible_rots 268 | 269 | return rot_to_angle 270 | 271 | 272 | # controls the rotations in response to a key press 273 | def rot(self, event): 274 | # if pressed clear the annoying instruction text 275 | self.areas[0].clear_text() 276 | 277 | # perform the camera rotations only in the perspective one 278 | # in the others, doesn't make sense 279 | 280 | # if the key pressed is in the 4d rotations 281 | # if the key is a 3d rotation the get function will return None 282 | rotation = self.rot4k_to_angle.get(event.char) 283 | 284 | if rotation: 285 | self.areas[0].project_method.rotate4(rotation) 286 | else: 287 | # else the key must be a 3d rotation 288 | rotation = self.rot3k_to_angle[event.char] 289 | 290 | self.areas[0].project_method.rotate3(rotation) 291 | 292 | # shows the help board 293 | def help_cmd(self): 294 | tl = Toplevel(self.root) 295 | label = Label(tl, text=INFO_TEXT, font=("Fixedsys", 12)) 296 | label.pack() 297 | 298 | # starts a new game by rebooting the entire game engine 299 | def new_game(self, event): 300 | self.game = g_eng.GameEngine() 301 | self.areas[0].clear_text() 302 | self.areas[0].add_text("Press any key to play") 303 | self.key_buffer.clear() 304 | 305 | # pause toggle 306 | def toggle_pause(self, event): 307 | # the pause works only if the game is running 308 | if self.game.state != g_eng.GameEngine.gamestate_game_over: 309 | if self.paused: 310 | self.paused = False 311 | self.areas[0].clear_text() 312 | else: 313 | self.paused = True 314 | self.areas[0].add_text("Press P to continue") 315 | 316 | # move command control 317 | def move(self, event): 318 | pressed_key = event.char 319 | self.paused = False 320 | # this pushes the key to the buffer, the actual actuator is the 321 | # get key in the updated function 322 | self.key_buffer.push_key(pressed_key) 323 | 324 | # draw the scenes 325 | def draw(self): 326 | for area in self.areas: 327 | area.clear_area() 328 | area.draw_plist(self.game.p_list) 329 | 330 | # the UI cycle 331 | def updater(self): 332 | 333 | # rates of update 334 | draw_rate = rate.Rate(1 / 25.0) 335 | game_rate = rate.Rate(1 / 2.0) 336 | update_rate = rate.Rate(1 / 50.0) 337 | replay_rate = rate.Rate(1 / 2.0) 338 | 339 | # reset the replay file overwriting it with a empty byte string 340 | self.replay.reset_replay_file() 341 | 342 | 343 | while True: 344 | 345 | if replay_rate.is_time(): 346 | self.replay.play_frames() 347 | 348 | # drawing scenese 349 | if draw_rate.is_time(): 350 | self.draw() 351 | 352 | # game stuff 353 | if game_rate.is_time(): 354 | 355 | if self.paused or self.game.state == g_eng.GameEngine.gamestate_game_over: 356 | pass 357 | else: 358 | # clears annoying text 359 | self.areas[0].clear_text() 360 | 361 | # reads the next key, thus the next direction from the 362 | # buffer 363 | next_dir = self.key_buffer.get_key() 364 | 365 | # if the key is actually a direction and is a valid one 366 | # here could actually skip the invalid directions... 367 | # like random keys but keep the backward to head direction 368 | # which is a mistake the player can make 369 | if next_dir: 370 | self.game.snake.change_dir(next_dir) 371 | 372 | self.game.routine() 373 | 374 | # writes the frames if the record option is on 375 | self.replay.save_replay_frame(self.game) 376 | 377 | # updates the score label 378 | self.score_str.set("Score: " + str(self.game.score)) 379 | 380 | # if the games ends in a game over, then show the top score 381 | # board 382 | if self.game.state == g_eng.GameEngine.gamestate_game_over: 383 | self.areas[0].add_text("Game Over\nPress space for new game") 384 | 385 | # create new score 386 | curr_score = score.Score(datetime.datetime.now(), self.game.score) 387 | self.score_board.add_score(curr_score) 388 | self.score_board.render_scores() 389 | 390 | # pause the game 391 | self.paused = True 392 | 393 | # update the tkinter cycle 394 | if update_rate.is_time(): 395 | self.root.update_idletasks() 396 | self.root.update() 397 | 398 | # updates the checkbox 399 | self.replay.replay_settings.read_state() 400 | 401 | # main program 402 | def main(): 403 | print("Snake 4D (2.0)") 404 | 405 | #initialize tk root and application 406 | root = Tk() 407 | root.title("Snake 4d (2.0)") 408 | 409 | mapp = MainApp(root) 410 | 411 | try: 412 | mapp.updater() 413 | 414 | # there is a pesky error when the user presses the X of the main window 415 | # this catches the error 416 | except _tkinter.TclError as exp: 417 | print("Tkinter error: ", end="") 418 | print(exp.__class__) 419 | print(exp) 420 | 421 | # rethrow any other exception 422 | except Exception as exp: 423 | print(exp.__class__) 424 | print(exp) 425 | raise exp 426 | 427 | 428 | if __name__ == "__main__": 429 | main() 430 | -------------------------------------------------------------------------------- /profiler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 23 23:12:55 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | import cProfile 9 | import main 10 | import pstats 11 | import os 12 | 13 | 14 | test_name = "Opt_draw_functions" 15 | run_prof = False 16 | 17 | test_folder = "./profiler_data/" + test_name + os.sep 18 | if not os.path.isdir(test_folder): 19 | os.mkdir(test_folder) 20 | 21 | if __name__ == "__main__": 22 | if run_prof: 23 | cProfile.runctx("main.main()", globals(), locals(), test_folder + "stats") 24 | else: 25 | with open( test_folder + "beauty_stats.txt", "w") as f: 26 | p = pstats.Stats(test_folder + 'stats', stream=f) 27 | p.strip_dirs().sort_stats("cumulative").print_stats() 28 | -------------------------------------------------------------------------------- /profiler_data/Opt_draw_functions/beauty_stats.txt: -------------------------------------------------------------------------------- 1 | Sun May 27 22:28:01 2018 ./profiler_data/Opt_draw_functions\stats 2 | 3 | 88789169 function calls (87461071 primitive calls) in 43.331 seconds 4 | 5 | Ordered by: cumulative time 6 | 7 | ncalls tottime percall cumtime percall filename:lineno(function) 8 | 2/1 0.000 0.000 43.334 43.334 {built-in method builtins.exec} 9 | 1 0.000 0.000 43.331 43.331 main.py:343(main) 10 | 1 0.014 0.014 43.263 43.263 main.py:286(updater) 11 | 484 0.009 0.000 41.147 0.085 main.py:280(draw) 12 | 1452 0.049 0.000 41.033 0.028 visu.py:171(draw_plist) 13 | 11976 0.350 0.000 30.235 0.003 visu.py:122(draw_poly) 14 | 191616 5.185 0.000 29.643 0.000 visu.py:45(add_edge) 15 | 17649679 20.773 0.000 27.200 0.000 vec.py:113(is_close) 16 | 11976 0.018 0.000 10.749 0.001 visu.py:167(project4) 17 | 3992 0.131 0.000 6.427 0.002 visu.py:186(project) 18 | 7984 0.996 0.000 4.304 0.001 visu.py:256(project) 19 | 1137769/4049 1.279 0.000 3.130 0.001 copy.py:132(deepcopy) 20 | 18360057 3.104 0.000 3.104 0.000 vec.py:45(check_dimensions) 21 | 68833/4049 0.228 0.000 3.050 0.001 copy.py:268(_reconstruct) 22 | 68833/4049 0.148 0.000 2.982 0.001 copy.py:236(_deepcopy_dict) 23 | 72882/8098 0.227 0.000 2.922 0.000 copy.py:210(_deepcopy_list) 24 | 21641692 2.667 0.000 2.667 0.000 vec.py:28(__getitem__) 25 | 11260/11244 2.083 0.000 2.086 0.000 {method 'call' of '_tkinter.tkapp' objects} 26 | 63872 0.336 0.000 1.806 0.000 prj.py:142(prj) 27 | 484 0.001 0.000 1.429 0.003 __init__.py:1169(update) 28 | 63872 0.294 0.000 1.403 0.000 prj.py:67(prj) 29 | 447104 0.893 0.000 1.168 0.000 vec.py:64(dot) 30 | 19191806 1.117 0.000 1.117 0.000 {built-in method builtins.abs} 31 | 131615 0.451 0.000 0.715 0.000 vec.py:84(__sub__) 32 | 485 0.001 0.000 0.591 0.001 __init__.py:1172(update_idletasks) 33 | 129568 0.177 0.000 0.569 0.000 copy.py:219(_deepcopy_tuple) 34 | 538716 0.390 0.000 0.471 0.000 vec.py:20(__init__) 35 | 129568 0.070 0.000 0.382 0.000 copy.py:220() 36 | 217852 0.160 0.000 0.380 0.000 vec.py:137(__init__) 37 | 191626 0.151 0.000 0.308 0.000 vec.py:154(__init__) 38 | 2344569 0.286 0.000 0.286 0.000 {method 'get' of 'dict' objects} 39 | 129238 0.115 0.000 0.209 0.000 vec.py:181(__init__) 40 | 127744 0.150 0.000 0.193 0.000 vec.py:106(__eq__) 41 | 3748 0.025 0.000 0.175 0.000 visu.py:107(draw_point) 42 | 7984 0.161 0.000 0.166 0.000 {built-in method builtins.__build_class__} 43 | 210548 0.112 0.000 0.153 0.000 copy.py:252(_keep_alive) 44 | 1692515 0.142 0.000 0.142 0.000 {built-in method builtins.id} 45 | 137666 0.038 0.000 0.141 0.000 copy.py:273() 46 | 1452 0.058 0.000 0.104 0.000 visu.py:151(clear_area) 47 | 992617 0.096 0.000 0.096 0.000 {method 'append' of 'list' objects} 48 | 5624 0.014 0.000 0.092 0.000 __init__.py:2464(_create) 49 | 255488 0.088 0.000 0.088 0.000 visu.py:267(__init__) 50 | 395550 0.081 0.000 0.081 0.000 vec.py:22() 51 | 68833 0.075 0.000 0.075 0.000 {method '__reduce_ex__' of 'object' objects} 52 | 137666 0.069 0.000 0.069 0.000 {built-in method builtins.getattr} 53 | 1874 0.009 0.000 0.066 0.000 visu.py:116(draw_edge) 54 | 797653 0.063 0.000 0.063 0.000 copy.py:190(_deepcopy_atomic) 55 | 3748 0.002 0.000 0.059 0.000 __init__.py:2493(create_rectangle) 56 | 1 0.000 0.000 0.056 0.056 __init__.py:1997(__init__) 57 | 52 0.000 0.000 0.056 0.001 g_eng.py:128(routine) 58 | 1 0.055 0.055 0.055 0.055 {built-in method _tkinter.create} 59 | 52 0.001 0.000 0.051 0.001 g_eng.py:90(check_collision) 60 | 68837 0.042 0.000 0.042 0.000 {built-in method builtins.hasattr} 61 | 68833 0.031 0.000 0.042 0.000 copyreg.py:87(__newobj__) 62 | 1874 0.002 0.000 0.037 0.000 __init__.py:2484(create_line) 63 | 127744 0.037 0.000 0.037 0.000 {built-in method math.tan} 64 | 160491 0.032 0.000 0.032 0.000 {built-in method builtins.isinstance} 65 | 131615 0.030 0.000 0.030 0.000 vec.py:86() 66 | 7496 0.011 0.000 0.028 0.000 visu.py:102(convert_to_canvas_coord) 67 | 5656 0.011 0.000 0.028 0.000 __init__.py:1309(_options) 68 | 7559 0.015 0.000 0.024 0.000 vec.py:99(__truediv__) 69 | 80120 0.022 0.000 0.022 0.000 {method 'update' of 'dict' objects} 70 | 46779 0.017 0.000 0.020 0.000 main.py:101(is_time) 71 | 1452 0.020 0.000 0.020 0.000 visu.py:59(reset_check_in) 72 | 29 0.000 0.000 0.020 0.001 __init__.py:1694(__call__) 73 | 3915 0.011 0.000 0.018 0.000 vec.py:74(__add__) 74 | 4582 0.002 0.000 0.016 0.000 __init__.py:2506(delete) 75 | 1 0.000 0.000 0.016 0.016 __init__.py:2052(destroy) 76 | 5670 0.009 0.000 0.014 0.000 __init__.py:93(_cnfmerge) 77 | 68833 0.011 0.000 0.011 0.000 {built-in method __new__ of type object at 0x0000000062CBB620} 78 | 1 0.000 0.000 0.011 0.011 main.py:115(__init__) 79 | 68833 0.011 0.000 0.011 0.000 {built-in method builtins.issubclass} 80 | 74504 0.011 0.000 0.011 0.000 {method 'items' of 'dict' objects} 81 | 18740 0.006 0.000 0.010 0.000 vec.py:146(x) 82 | 18740 0.006 0.000 0.009 0.000 vec.py:149(y) 83 | 15/6 0.000 0.000 0.008 0.001 __init__.py:2296(destroy) 84 | 52 0.000 0.000 0.007 0.000 snake.py:48(move) 85 | 63 0.000 0.000 0.007 0.000 poly.py:132(create_cube4d) 86 | 7984 0.006 0.000 0.006 0.000 visu.py:293() 87 | 37480 0.006 0.000 0.006 0.000 vec.py:34(get_set_coord) 88 | 56 0.000 0.000 0.006 0.000 snake.py:38(create_cube) 89 | 1 0.000 0.000 0.006 0.006 __init__.py:1472(configure) 90 | 1 0.000 0.000 0.006 0.006 __init__.py:1460(_configure) 91 | 7984 0.005 0.000 0.005 0.000 visu.py:266(EIndex) 92 | 8047 0.005 0.000 0.005 0.000 poly.py:16(__init__) 93 | 52 0.000 0.000 0.005 0.000 g_eng.py:116(evaluate_collision) 94 | 63 0.002 0.000 0.004 0.000 poly.py:67(cube4d) 95 | 5 0.000 0.000 0.004 0.001 snake.py:57(add_segment) 96 | 47829 0.003 0.000 0.003 0.000 {built-in method time.time} 97 | 54 0.000 0.000 0.003 0.000 __init__.py:339(set) 98 | 54 0.003 0.000 0.003 0.000 {method 'globalsetvar' of '_tkinter.tkapp' objects} 99 | 1 0.000 0.000 0.002 0.002 score.py:81(__init__) 100 | 1 0.000 0.000 0.002 0.002 score.py:33(__init__) 101 | 1874 0.002 0.000 0.002 0.000 visu.py:30(__init__) 102 | 5694 0.002 0.000 0.002 0.000 {built-in method builtins.len} 103 | 27 0.001 0.000 0.002 0.000 __init__.py:1382(_substitute) 104 | 15 0.000 0.000 0.002 0.000 __init__.py:2280(__init__) 105 | 11269 0.002 0.000 0.002 0.000 {built-in method _tkinter._flatten} 106 | 52 0.000 0.000 0.002 0.000 visu.py:98(clear_text) 107 | 398 0.001 0.000 0.002 0.000 g_eng.py:78(intersect) 108 | 62 0.000 0.000 0.002 0.000 score.py:13(__init__) 109 | 61 0.000 0.000 0.001 0.000 score.py:22(read_from_line) 110 | 7559 0.001 0.000 0.001 0.000 vec.py:101() 111 | 61 0.000 0.000 0.001 0.000 {built-in method strptime} 112 | 61 0.000 0.000 0.001 0.000 _strptime.py:562(_strptime_datetime) 113 | 5975 0.001 0.000 0.001 0.000 {method 'getint' of '_tkinter.tkapp' objects} 114 | 61 0.001 0.000 0.001 0.000 _strptime.py:321(_strptime) 115 | 5 0.000 0.000 0.001 0.000 {built-in method builtins.print} 116 | 14 0.000 0.000 0.001 0.000 iostream.py:342(write) 117 | 15 0.001 0.000 0.001 0.000 iostream.py:180(schedule) 118 | 1 0.000 0.000 0.001 0.001 score.py:89(render_scores) 119 | 5 0.000 0.000 0.001 0.000 __init__.py:2742(__init__) 120 | 3915 0.001 0.000 0.001 0.000 vec.py:77() 121 | 6 0.000 0.000 0.001 0.000 g_eng.py:56(initialize_food) 122 | 1 0.000 0.000 0.001 0.001 g_eng.py:19(__init__) 123 | 5679 0.001 0.000 0.001 0.000 {built-in method builtins.callable} 124 | 1 0.000 0.000 0.001 0.001 score.py:85(add_score) 125 | 1 0.000 0.000 0.001 0.001 score.py:53(write_scores) 126 | 297 0.000 0.000 0.001 0.000 __init__.py:1388(getint_event) 127 | 3 0.000 0.000 0.001 0.000 __init__.py:2855(__init__) 128 | 3 0.000 0.000 0.000 0.000 visu.py:66(__init__) 129 | 63 0.000 0.000 0.000 0.000 vec.py:92(__mul__) 130 | 1 0.000 0.000 0.000 0.000 visu.py:179(__init__) 131 | 1 0.000 0.000 0.000 0.000 snake.py:12(__init__) 132 | 53 0.000 0.000 0.000 0.000 g_eng.py:42(generate_plist) 133 | 1 0.000 0.000 0.000 0.000 __init__.py:2061(readprofile) 134 | 5 0.000 0.000 0.000 0.000 genericpath.py:27(isfile) 135 | 2 0.000 0.000 0.000 0.000 visu.py:93(add_text) 136 | 5 0.000 0.000 0.000 0.000 {built-in method nt.stat} 137 | 2 0.000 0.000 0.000 0.000 __init__.py:2496(create_text) 138 | 2 0.000 0.000 0.000 0.000 {built-in method io.open} 139 | 28 0.000 0.000 0.000 0.000 __init__.py:1206(bind) 140 | 61 0.000 0.000 0.000 0.000 _strptime.py:29(_getlang) 141 | 28 0.000 0.000 0.000 0.000 __init__.py:1189(_bind) 142 | 61 0.000 0.000 0.000 0.000 locale.py:565(getlocale) 143 | 27 0.000 0.000 0.000 0.000 __init__.py:1334(nametowidget) 144 | 52 0.000 0.000 0.000 0.000 main.py:81(get_key) 145 | 33 0.000 0.000 0.000 0.000 __init__.py:1351(_register) 146 | 3 0.000 0.000 0.000 0.000 __init__.py:2389(__init__) 147 | 6 0.000 0.000 0.000 0.000 g_eng.py:58(generate_rand_point) 148 | 27 0.000 0.000 0.000 0.000 main.py:272(move) 149 | 16 0.000 0.000 0.000 0.000 __init__.py:581(destroy) 150 | 27 0.000 0.000 0.000 0.000 enum.py:265(__call__) 151 | 79 0.000 0.000 0.000 0.000 {method 'pop' of 'list' objects} 152 | 1 0.000 0.000 0.000 0.000 __init__.py:2027(_loadtk) 153 | 2 0.000 0.000 0.000 0.000 prj.py:125(calc_tmatrix) 154 | 15 0.000 0.000 0.000 0.000 __init__.py:2247(_setup) 155 | 1 0.000 0.000 0.000 0.000 __init__.py:2316(__init__) 156 | 2 0.000 0.000 0.000 0.000 __init__.py:1950(wm_protocol) 157 | 6 0.000 0.000 0.000 0.000 __init__.py:2203(grid_configure) 158 | 3 0.000 0.000 0.000 0.000 __init__.py:2724(__init__) 159 | 5 0.000 0.000 0.000 0.000 __init__.py:2869(add) 160 | 61 0.000 0.000 0.000 0.000 locale.py:462(_parse_localename) 161 | 24 0.000 0.000 0.000 0.000 random.py:172(randrange) 162 | 56 0.000 0.000 0.000 0.000 {method 'getboolean' of '_tkinter.tkapp' objects} 163 | 2 0.000 0.000 0.000 0.000 prj.py:52(calc_tmatrix) 164 | 6 0.000 0.000 0.000 0.000 vec.py:203(cross) 165 | 35 0.000 0.000 0.000 0.000 {method 'deletecommand' of '_tkinter.tkapp' objects} 166 | 1 0.000 0.000 0.000 0.000 prj.py:103(__init__) 167 | 61 0.000 0.000 0.000 0.000 {built-in method _locale.setlocale} 168 | 61 0.000 0.000 0.000 0.000 {method 'groupdict' of '_sre.SRE_Match' objects} 169 | 61 0.000 0.000 0.000 0.000 {method 'match' of '_sre.SRE_Pattern' objects} 170 | 1 0.000 0.000 0.000 0.000 prj.py:26(__init__) 171 | 1 0.000 0.000 0.000 0.000 {method 'readlines' of '_io._IOBase' objects} 172 | 27 0.000 0.000 0.000 0.000 main.py:78(push_key) 173 | 1 0.000 0.000 0.000 0.000 :1() 174 | 61 0.000 0.000 0.000 0.000 locale.py:379(normalize) 175 | 27 0.000 0.000 0.000 0.000 enum.py:515(__new__) 176 | 1 0.000 0.000 0.000 0.000 prj.py:160(change_position) 177 | 2 0.000 0.000 0.000 0.000 __init__.py:2879(add_command) 178 | 88 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects} 179 | 10 0.000 0.000 0.000 0.000 {method 'strftime' of 'datetime.date' objects} 180 | 15 0.000 0.000 0.000 0.000 threading.py:1104(is_alive) 181 | 24 0.000 0.000 0.000 0.000 random.py:222(_randbelow) 182 | 35 0.000 0.000 0.000 0.000 {method 'createcommand' of '_tkinter.tkapp' objects} 183 | 1 0.000 0.000 0.000 0.000 __init__.py:467(__init__) 184 | 27 0.000 0.000 0.000 0.000 snake.py:41(change_dir) 185 | 1 0.000 0.000 0.000 0.000 __init__.py:296(__init__) 186 | 4 0.000 0.000 0.000 0.000 ntpath.py:74(join) 187 | 10 0.000 0.000 0.000 0.000 vec.py:49(normalize) 188 | 3 0.000 0.000 0.000 0.000 __init__.py:2873(add_cascade) 189 | 1 0.000 0.000 0.000 0.000 __init__.py:325(__del__) 190 | 1 0.000 0.000 0.000 0.000 score.py:62(get_top_ten_scores) 191 | 1 0.000 0.000 0.000 0.000 ntpath.py:233(basename) 192 | 1 0.000 0.000 0.000 0.000 ntpath.py:199(split) 193 | 2 0.000 0.000 0.000 0.000 copyreg.py:96(_slotnames) 194 | 9 0.000 0.000 0.000 0.000 ntpath.py:121(splitdrive) 195 | 15 0.000 0.000 0.000 0.000 {built-in method nt.urandom} 196 | 1 0.000 0.000 0.000 0.000 prj.py:93(change_position) 197 | 5 0.000 0.000 0.000 0.000 __init__.py:2119(pack_configure) 198 | 14 0.000 0.000 0.000 0.000 vec.py:39(magnitude) 199 | 1 0.000 0.000 0.000 0.000 {built-in method builtins.sorted} 200 | 4 0.000 0.000 0.000 0.000 __init__.py:59(_stringify) 201 | 29 0.000 0.000 0.000 0.000 __init__.py:1373(_root) 202 | 4 0.000 0.000 0.000 0.000 vec.py:173(cross) 203 | 63 0.000 0.000 0.000 0.000 vec.py:94() 204 | 15 0.000 0.000 0.000 0.000 threading.py:1062(_wait_for_tstate_lock) 205 | 1 0.000 0.000 0.000 0.000 _collections_abc.py:664(__contains__) 206 | 14 0.000 0.000 0.000 0.000 iostream.py:284(_is_master_process) 207 | 33 0.000 0.000 0.000 0.000 __init__.py:1689(__init__) 208 | 3 0.000 0.000 0.000 0.000 __init__.py:1977(wm_title) 209 | 1 0.000 0.000 0.000 0.000 os.py:664(__getitem__) 210 | 1 0.000 0.000 0.000 0.000 ntpath.py:222(splitext) 211 | 2 0.000 0.000 0.000 0.000 _bootlocale.py:11(getpreferredencoding) 212 | 14 0.000 0.000 0.000 0.000 iostream.py:297(_schedule_flush) 213 | 8 0.000 0.000 0.000 0.000 {method 'search' of '_sre.SRE_Pattern' objects} 214 | 122 0.000 0.000 0.000 0.000 {method 'toordinal' of 'datetime.date' objects} 215 | 15 0.000 0.000 0.000 0.000 iostream.py:87(_event_pipe) 216 | 15 0.000 0.000 0.000 0.000 __init__.py:2289() 217 | 76 0.000 0.000 0.000 0.000 {method 'lower' of 'str' objects} 218 | 3 0.000 0.000 0.000 0.000 cp1252.py:22(decode) 219 | 15 0.000 0.000 0.000 0.000 {method 'acquire' of '_thread.lock' objects} 220 | 62 0.000 0.000 0.000 0.000 score.py:65() 221 | 34 0.000 0.000 0.000 0.000 {built-in method builtins.repr} 222 | 2 0.000 0.000 0.000 0.000 {built-in method _locale._getdefaultlocale} 223 | 43 0.000 0.000 0.000 0.000 {method 'getrandbits' of '_random.Random' objects} 224 | 10 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects} 225 | 1 0.000 0.000 0.000 0.000 score.py:57() 226 | 2 0.000 0.000 0.000 0.000 visu.py:245(__init__) 227 | 14 0.000 0.000 0.000 0.000 {built-in method nt.getpid} 228 | 1 0.000 0.000 0.000 0.000 {method 'write' of '_io.TextIOWrapper' objects} 229 | 3 0.000 0.000 0.000 0.000 {built-in method _codecs.charmap_decode} 230 | 1 0.000 0.000 0.000 0.000 :989(_handle_fromlist) 231 | 2 0.000 0.000 0.000 0.000 __init__.py:1876(wm_iconname) 232 | 1 0.000 0.000 0.000 0.000 score.py:27(__str__) 233 | 61 0.000 0.000 0.000 0.000 {method 'keys' of 'dict' objects} 234 | 2 0.000 0.000 0.000 0.000 {method 'getvar' of '_tkinter.tkapp' objects} 235 | 1 0.000 0.000 0.000 0.000 genericpath.py:117(_splitext) 236 | 61 0.000 0.000 0.000 0.000 {method 'weekday' of 'datetime.date' objects} 237 | 2 0.000 0.000 0.000 0.000 {method 'get' of 'mappingproxy' objects} 238 | 1 0.000 0.000 0.000 0.000 os.py:732(encodekey) 239 | 2 0.000 0.000 0.000 0.000 cp1252.py:18(encode) 240 | 61 0.000 0.000 0.000 0.000 {method 'end' of '_sre.SRE_Match' objects} 241 | 3 0.000 0.000 0.000 0.000 main.py:97(__init__) 242 | 15 0.000 0.000 0.000 0.000 {built-in method nt.fspath} 243 | 15 0.000 0.000 0.000 0.000 threading.py:506(is_set) 244 | 5 0.000 0.000 0.000 0.000 {method 'replace' of 'str' objects} 245 | 1 0.000 0.000 0.000 0.000 main.py:67(__init__) 246 | 14 0.000 0.000 0.000 0.000 {built-in method math.sqrt} 247 | 1 0.000 0.000 0.000 0.000 {built-in method now} 248 | 16 0.000 0.000 0.000 0.000 {method 'values' of 'dict' objects} 249 | 12 0.000 0.000 0.000 0.000 main.py:207() 250 | 1 0.000 0.000 0.000 0.000 {method 'isoformat' of 'datetime.datetime' objects} 251 | 1 0.000 0.000 0.000 0.000 {method 'writelines' of '_io._IOBase' objects} 252 | 1 0.000 0.000 0.000 0.000 {method 'globalunsetvar' of '_tkinter.tkapp' objects} 253 | 24 0.000 0.000 0.000 0.000 {method 'bit_length' of 'int' objects} 254 | 4 0.000 0.000 0.000 0.000 {method 'join' of 'str' objects} 255 | 1 0.000 0.000 0.000 0.000 os.py:726(check_str) 256 | 3 0.000 0.000 0.000 0.000 visu.py:42(__init__) 257 | 2 0.000 0.000 0.000 0.000 {built-in method _codecs.charmap_encode} 258 | 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 259 | 1 0.000 0.000 0.000 0.000 score.py:50(add_score) 260 | 5 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects} 261 | 6 0.000 0.000 0.000 0.000 main.py:191() 262 | 1 0.000 0.000 0.000 0.000 codecs.py:185(__init__) 263 | 1 0.000 0.000 0.000 0.000 codecs.py:259(__init__) 264 | 1 0.000 0.000 0.000 0.000 ntpath.py:33(_get_bothseps) 265 | 3 0.000 0.000 0.000 0.000 __init__.py:1492(__str__) 266 | 3 0.000 0.000 0.000 0.000 {method 'rfind' of 'str' objects} 267 | 1 0.000 0.000 0.000 0.000 {built-in method builtins.max} 268 | 1 0.000 0.000 0.000 0.000 {built-in method _stat.S_ISREG} 269 | 1 0.000 0.000 0.000 0.000 {method 'rstrip' of 'str' objects} 270 | 1 0.000 0.000 0.000 0.000 codecs.py:213(setstate) 271 | 1 0.000 0.000 0.000 0.000 __init__.py:336(__str__) 272 | 1 0.000 0.000 0.000 0.000 {method 'remove' of 'list' objects} 273 | 274 | 275 | -------------------------------------------------------------------------------- /profiler_data/Opt_draw_functions/profiler_beautify_stats.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/profiler_data/Opt_draw_functions/profiler_beautify_stats.txt -------------------------------------------------------------------------------- /profiler_data/Opt_draw_functions/stats: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/profiler_data/Opt_draw_functions/stats -------------------------------------------------------------------------------- /profiler_data/base_test/profiler_beautify_stats.txt: -------------------------------------------------------------------------------- 1 | Wed May 23 23:43:25 2018 stats 2 | 3 | 58428901 function calls (57901460 primitive calls) in 56.609 seconds 4 | 5 | Ordered by: cumulative time 6 | 7 | ncalls tottime percall cumtime percall filename:lineno(function) 8 | 2/1 0.000 0.000 56.609 56.609 {built-in method builtins.exec} 9 | 1 0.006 0.006 56.609 56.609 :1() 10 | 1 0.000 0.000 56.603 56.603 main.py:110(main) 11 | 1 0.462 0.462 56.535 56.535 main.py:79(updater) 12 | 200 0.005 0.000 52.510 0.263 main.py:74(draw) 13 | 598 6.951 0.012 42.198 0.071 visu.py:71(clear_area) 14 | 19705681 6.693 0.000 35.247 0.000 __init__.py:2506(delete) 15 | 19925068/19925058 33.222 0.000 33.226 0.000 {method 'call' of '_tkinter.tkapp' objects} 16 | 597 0.017 0.000 10.307 0.017 visu.py:79(draw_plist) 17 | 4560 0.336 0.000 7.663 0.002 visu.py:57(draw_poly) 18 | 145920 0.555 0.000 4.011 0.000 visu.py:50(draw_edge) 19 | 72960 0.459 0.000 3.289 0.000 visu.py:41(draw_point) 20 | 235 0.001 0.000 3.222 0.014 __init__.py:1172(update_idletasks) 21 | 218880 0.463 0.000 2.929 0.000 __init__.py:2464(_create) 22 | 4560 0.005 0.000 2.628 0.001 visu.py:75(project4) 23 | 1520 0.047 0.000 2.405 0.002 visu.py:94(project) 24 | 145920 0.077 0.000 1.974 0.000 __init__.py:2484(create_line) 25 | 802560 0.609 0.000 1.302 0.000 vec.py:120(__init__) 26 | 364800 0.474 0.000 1.237 0.000 visu.py:36(convert_to_canvas_coord) 27 | 451848/1608 0.507 0.000 1.221 0.001 copy.py:132(deepcopy) 28 | 27336/1608 0.086 0.000 1.196 0.001 copy.py:268(_reconstruct) 29 | 27336/1608 0.055 0.000 1.171 0.001 copy.py:236(_deepcopy_dict) 30 | 28944/3216 0.089 0.000 1.149 0.000 copy.py:210(_deepcopy_list) 31 | 72960 0.042 0.000 1.073 0.000 __init__.py:2493(create_rectangle) 32 | 218898 0.391 0.000 0.977 0.000 __init__.py:1309(_options) 33 | 926420 0.665 0.000 0.791 0.000 vec.py:18(__init__) 34 | 24320 0.114 0.000 0.671 0.000 prj.py:118(prj) 35 | 121787 0.380 0.000 0.609 0.000 vec.py:81(__sub__) 36 | 24320 0.110 0.000 0.534 0.000 prj.py:51(prj) 37 | 218907 0.305 0.000 0.493 0.000 __init__.py:93(_cnfmerge) 38 | 146014 0.287 0.000 0.458 0.000 vec.py:96(__truediv__) 39 | 170240 0.340 0.000 0.446 0.000 vec.py:61(dot) 40 | 802560 0.254 0.000 0.393 0.000 vec.py:129(x) 41 | 802560 0.235 0.000 0.360 0.000 vec.py:132(y) 42 | 73224 0.205 0.000 0.329 0.000 vec.py:71(__add__) 43 | 1605120 0.264 0.000 0.264 0.000 vec.py:32(get_set_coord) 44 | 51456 0.069 0.000 0.224 0.000 copy.py:219(_deepcopy_tuple) 45 | 3040 0.096 0.000 0.217 0.000 visu.py:116(project) 46 | 930464 0.180 0.000 0.180 0.000 {built-in method builtins.isinstance} 47 | 1227642 0.179 0.000 0.179 0.000 vec.py:26(__getitem__) 48 | 3083957 0.158 0.000 0.158 0.000 {built-in method time.time} 49 | 51456 0.027 0.000 0.151 0.000 copy.py:220() 50 | 585289 0.125 0.000 0.125 0.000 vec.py:20() 51 | 72974 0.056 0.000 0.115 0.000 vec.py:137(__init__) 52 | 931041 0.114 0.000 0.114 0.000 {method 'get' of 'dict' objects} 53 | 85 0.001 0.000 0.105 0.001 g_eng.py:107(routine) 54 | 465120 0.096 0.000 0.096 0.000 {method 'update' of 'dict' objects} 55 | 413895 0.085 0.000 0.085 0.000 vec.py:43(check_dimensions) 56 | 50886 0.044 0.000 0.083 0.000 vec.py:164(__init__) 57 | 85 0.001 0.000 0.080 0.001 g_eng.py:78(check_collision) 58 | 235 0.001 0.000 0.078 0.000 __init__.py:1169(update) 59 | 48644 0.059 0.000 0.076 0.000 vec.py:103(__eq__) 60 | 1 0.000 0.000 0.065 0.065 __init__.py:1997(__init__) 61 | 1 0.064 0.064 0.064 0.064 {built-in method _tkinter.create} 62 | 437772 0.061 0.000 0.061 0.000 {built-in method _tkinter._flatten} 63 | 83616 0.044 0.000 0.059 0.000 copy.py:252(_keep_alive) 64 | 534994 0.057 0.000 0.057 0.000 {method 'append' of 'list' objects} 65 | 54672 0.014 0.000 0.055 0.000 copy.py:273() 66 | 672153 0.054 0.000 0.054 0.000 {built-in method builtins.id} 67 | 246243 0.033 0.000 0.033 0.000 {method 'items' of 'dict' objects} 68 | 27336 0.028 0.000 0.028 0.000 {method '__reduce_ex__' of 'object' objects} 69 | 146014 0.028 0.000 0.028 0.000 vec.py:98() 70 | 316776 0.026 0.000 0.026 0.000 copy.py:190(_deepcopy_atomic) 71 | 54672 0.025 0.000 0.025 0.000 {built-in method builtins.getattr} 72 | 121787 0.025 0.000 0.025 0.000 vec.py:83() 73 | 219361 0.024 0.000 0.024 0.000 {method 'getint' of '_tkinter.tkapp' objects} 74 | 218897 0.023 0.000 0.023 0.000 {built-in method builtins.callable} 75 | 90 0.001 0.000 0.018 0.000 {built-in method builtins.print} 76 | 188 0.001 0.000 0.017 0.000 iostream.py:342(write) 77 | 27340 0.017 0.000 0.017 0.000 {built-in method builtins.hasattr} 78 | 27336 0.011 0.000 0.016 0.000 copyreg.py:87(__newobj__) 79 | 273 0.013 0.000 0.016 0.000 iostream.py:180(schedule) 80 | 73224 0.014 0.000 0.014 0.000 vec.py:74() 81 | 48640 0.013 0.000 0.013 0.000 {built-in method math.tan} 82 | 94 0.001 0.000 0.011 0.000 poly.py:113(create_cube4d) 83 | 85 0.000 0.000 0.010 0.000 snake.py:49(move) 84 | 89 0.000 0.000 0.009 0.000 snake.py:38(create_cube) 85 | 38 0.000 0.000 0.009 0.000 __init__.py:1694(__call__) 86 | 94 0.003 0.000 0.008 0.000 poly.py:52(cube4d) 87 | 85 0.000 0.000 0.005 0.000 g_eng.py:97(evaluate_collision) 88 | 1 0.000 0.000 0.005 0.005 __init__.py:2052(destroy) 89 | 27336 0.004 0.000 0.004 0.000 {built-in method __new__ of type object at 0x000000006437B620} 90 | 27336 0.004 0.000 0.004 0.000 {built-in method builtins.issubclass} 91 | 1 0.000 0.000 0.003 0.003 main.py:38(__init__) 92 | 4 0.000 0.000 0.003 0.001 g_eng.py:44(initialize_food) 93 | 37 0.001 0.000 0.003 0.000 __init__.py:1382(_substitute) 94 | 3134 0.003 0.000 0.003 0.000 poly.py:11(__init__) 95 | 594 0.002 0.000 0.002 0.000 g_eng.py:66(intersect) 96 | 3 0.000 0.000 0.002 0.001 snake.py:58(add_segment) 97 | 9/3 0.000 0.000 0.002 0.001 __init__.py:2296(destroy) 98 | 188 0.000 0.000 0.002 0.000 iostream.py:297(_schedule_flush) 99 | 3 0.000 0.000 0.002 0.001 visu.py:13(__init__) 100 | 9 0.000 0.000 0.001 0.000 __init__.py:2280(__init__) 101 | 273 0.000 0.000 0.001 0.000 threading.py:1104(is_alive) 102 | 86 0.001 0.000 0.001 0.000 g_eng.py:32(generate_plist) 103 | 407 0.000 0.000 0.001 0.000 __init__.py:1388(getint_event) 104 | 1 0.000 0.000 0.001 0.001 g_eng.py:14(__init__) 105 | 273 0.001 0.000 0.001 0.000 {built-in method nt.urandom} 106 | 3 0.000 0.000 0.001 0.000 __init__.py:2724(__init__) 107 | 3 0.000 0.000 0.001 0.000 __init__.py:2742(__init__) 108 | 273 0.000 0.000 0.001 0.000 threading.py:1062(_wait_for_tstate_lock) 109 | 1 0.000 0.000 0.000 0.000 snake.py:12(__init__) 110 | 1604 0.000 0.000 0.000 0.000 {built-in method builtins.len} 111 | 1 0.000 0.000 0.000 0.000 visu.py:87(__init__) 112 | 94 0.000 0.000 0.000 0.000 vec.py:89(__mul__) 113 | 1 0.000 0.000 0.000 0.000 __init__.py:2061(readprofile) 114 | 188 0.000 0.000 0.000 0.000 iostream.py:284(_is_master_process) 115 | 37 0.000 0.000 0.000 0.000 __init__.py:1334(nametowidget) 116 | 273 0.000 0.000 0.000 0.000 {method 'acquire' of '_thread.lock' objects} 117 | 273 0.000 0.000 0.000 0.000 iostream.py:87(_event_pipe) 118 | 2 0.000 0.000 0.000 0.000 prj.py:99(trans_matrix) 119 | 1 0.000 0.000 0.000 0.000 __init__.py:2027(_loadtk) 120 | 85 0.000 0.000 0.000 0.000 main.py:29(get_key) 121 | 37 0.000 0.000 0.000 0.000 enum.py:265(__call__) 122 | 4 0.000 0.000 0.000 0.000 genericpath.py:27(isfile) 123 | 4 0.000 0.000 0.000 0.000 {built-in method nt.stat} 124 | 37 0.000 0.000 0.000 0.000 main.py:68(move) 125 | 3 0.000 0.000 0.000 0.000 __init__.py:2389(__init__) 126 | 122 0.000 0.000 0.000 0.000 {method 'pop' of 'list' objects} 127 | 1 0.000 0.000 0.000 0.000 prj.py:81(__init__) 128 | 1 0.000 0.000 0.000 0.000 __init__.py:1950(wm_protocol) 129 | 74 0.000 0.000 0.000 0.000 {method 'getboolean' of '_tkinter.tkapp' objects} 130 | 9 0.000 0.000 0.000 0.000 __init__.py:2247(_setup) 131 | 1 0.000 0.000 0.000 0.000 prj.py:136(change_position) 132 | 6 0.000 0.000 0.000 0.000 vec.py:186(cross) 133 | 4 0.000 0.000 0.000 0.000 g_eng.py:46(generate_rand_point) 134 | 37 0.000 0.000 0.000 0.000 enum.py:515(__new__) 135 | 188 0.000 0.000 0.000 0.000 {built-in method nt.getpid} 136 | 37 0.000 0.000 0.000 0.000 main.py:26(push_key) 137 | 2 0.000 0.000 0.000 0.000 prj.py:33(trans_matrix) 138 | 8 0.000 0.000 0.000 0.000 __init__.py:1206(bind) 139 | 273 0.000 0.000 0.000 0.000 threading.py:506(is_set) 140 | 8 0.000 0.000 0.000 0.000 __init__.py:1189(_bind) 141 | 1 0.000 0.000 0.000 0.000 prj.py:16(__init__) 142 | 16 0.000 0.000 0.000 0.000 random.py:172(randrange) 143 | 18 0.000 0.000 0.000 0.000 vec.py:37(magnitude) 144 | 9 0.000 0.000 0.000 0.000 __init__.py:1351(_register) 145 | 37 0.000 0.000 0.000 0.000 {method 'split' of 'str' objects} 146 | 10 0.000 0.000 0.000 0.000 __init__.py:581(destroy) 147 | 10 0.000 0.000 0.000 0.000 vec.py:47(normalize) 148 | 6 0.000 0.000 0.000 0.000 __init__.py:2119(pack_configure) 149 | 4 0.000 0.000 0.000 0.000 vec.py:110(__str__) 150 | 1 0.000 0.000 0.000 0.000 prj.py:74(change_position) 151 | 16 0.000 0.000 0.000 0.000 random.py:222(_randbelow) 152 | 3 0.000 0.000 0.000 0.000 __init__.py:2203(grid_configure) 153 | 37 0.000 0.000 0.000 0.000 snake.py:41(change_dir) 154 | 37 0.000 0.000 0.000 0.000 __init__.py:1373(_root) 155 | 4 0.000 0.000 0.000 0.000 ntpath.py:74(join) 156 | 9 0.000 0.000 0.000 0.000 ntpath.py:121(splitdrive) 157 | 94 0.000 0.000 0.000 0.000 vec.py:91() 158 | 4 0.000 0.000 0.000 0.000 vec.py:156(cross) 159 | 16 0.000 0.000 0.000 0.000 {method 'format' of 'str' objects} 160 | 1 0.000 0.000 0.000 0.000 ntpath.py:233(basename) 161 | 1 0.000 0.000 0.000 0.000 ntpath.py:199(split) 162 | 11 0.000 0.000 0.000 0.000 {method 'createcommand' of '_tkinter.tkapp' objects} 163 | 11 0.000 0.000 0.000 0.000 {method 'deletecommand' of '_tkinter.tkapp' objects} 164 | 1 0.000 0.000 0.000 0.000 _collections_abc.py:664(__contains__) 165 | 1 0.000 0.000 0.000 0.000 os.py:664(__getitem__) 166 | 2 0.000 0.000 0.000 0.000 {method 'getvar' of '_tkinter.tkapp' objects} 167 | 2 0.000 0.000 0.000 0.000 copyreg.py:96(_slotnames) 168 | 1 0.000 0.000 0.000 0.000 ntpath.py:222(splitext) 169 | 9 0.000 0.000 0.000 0.000 __init__.py:2289() 170 | 1 0.000 0.000 0.000 0.000 :989(_handle_fromlist) 171 | 2 0.000 0.000 0.000 0.000 visu.py:105(__init__) 172 | 32 0.000 0.000 0.000 0.000 {method 'getrandbits' of '_random.Random' objects} 173 | 1 0.000 0.000 0.000 0.000 os.py:732(encodekey) 174 | 9 0.000 0.000 0.000 0.000 __init__.py:1689(__init__) 175 | 1 0.000 0.000 0.000 0.000 genericpath.py:117(_splitext) 176 | 18 0.000 0.000 0.000 0.000 {built-in method math.sqrt} 177 | 1 0.000 0.000 0.000 0.000 main.py:16(__init__) 178 | 16 0.000 0.000 0.000 0.000 {method 'bit_length' of 'int' objects} 179 | 9 0.000 0.000 0.000 0.000 {method 'lower' of 'str' objects} 180 | 9 0.000 0.000 0.000 0.000 {built-in method builtins.repr} 181 | 15 0.000 0.000 0.000 0.000 {built-in method nt.fspath} 182 | 5 0.000 0.000 0.000 0.000 {method 'replace' of 'str' objects} 183 | 10 0.000 0.000 0.000 0.000 {method 'values' of 'dict' objects} 184 | 2 0.000 0.000 0.000 0.000 {method 'get' of 'mappingproxy' objects} 185 | 1 0.000 0.000 0.000 0.000 os.py:726(check_str) 186 | 1 0.000 0.000 0.000 0.000 ntpath.py:33(_get_bothseps) 187 | 3 0.000 0.000 0.000 0.000 {method 'rfind' of 'str' objects} 188 | 1 0.000 0.000 0.000 0.000 {method 'rstrip' of 'str' objects} 189 | 1 0.000 0.000 0.000 0.000 {built-in method builtins.max} 190 | 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 191 | 1 0.000 0.000 0.000 0.000 {method 'remove' of 'list' objects} 192 | 1 0.000 0.000 0.000 0.000 {method 'upper' of 'str' objects} 193 | 194 | 195 | -------------------------------------------------------------------------------- /profiler_data/base_test/profiler_stats: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/profiler_data/base_test/profiler_stats -------------------------------------------------------------------------------- /settings/_tmp_replay_file.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/settings/_tmp_replay_file.sk4 -------------------------------------------------------------------------------- /settings/paths.txt: -------------------------------------------------------------------------------- 1 | save_dir=C:/Users/Media Markt/Desktop/Vita Online/Programming/Snake4d/tests 2 | load_path=C:/Users/Media Markt/Desktop/Vita Online/Programming/Snake4d/tests 3 | -------------------------------------------------------------------------------- /settings/replay.txt: -------------------------------------------------------------------------------- 1 | record=True 2 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/src/__init__.py -------------------------------------------------------------------------------- /src/bfh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Jun 27 17:24:58 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # Imports 10 | #============================================================================== 11 | import struct 12 | 13 | #============================================================================== 14 | # Helpers 15 | #============================================================================== 16 | 17 | def as_bytes(dtype, data): 18 | return struct.pack(dtype, data) 19 | 20 | #============================================================================== 21 | # Constants 22 | #============================================================================== 23 | 24 | # little conversion table for the supported files 25 | type_to_size = {} 26 | type_to_size['I'] = 4 27 | type_to_size['d'] = 8 28 | type_to_size['c'] = 1 29 | 30 | #============================================================================== 31 | # Binary file class 32 | #============================================================================== 33 | 34 | class BinaryFile: 35 | ''' reads the bytes from a file object with custom cumulative offset''' 36 | 37 | def __init__(self, fobj, co = 0): 38 | ''' 39 | self.file is a file object, self.co is the cumulative offset where 40 | to start the procedure 41 | ''' 42 | 43 | self.file = fobj 44 | self.co = co 45 | 46 | def write(self, dtype, data): 47 | ''' writes a data packet and moves the offset''' 48 | self.file.seek(self.co) 49 | b = as_bytes(dtype, data) 50 | self.file.write(b) 51 | self.co += len(b) 52 | 53 | def read(self, dtype): 54 | ''' 55 | reads a data packet and moves the offset, returns the data packet 56 | in the specified format 57 | ''' 58 | self.file.seek(self.co) 59 | size_read = type_to_size[dtype] 60 | b = self.file.read(size_read) 61 | self.co += size_read 62 | return struct.unpack(dtype, b)[0] 63 | 64 | def write_string(self, string): 65 | ''' 66 | Writess a string saving the length first and then the caracters 67 | encoded with UTF-8 68 | ''' 69 | 70 | self.file.seek(self.co) 71 | 72 | strlen = len(string) 73 | 74 | #write str len 75 | self.write("I", strlen) 76 | 77 | fmt = 'c'*strlen 78 | 79 | data = [] 80 | for c in string: 81 | data.append(bytes(c, "utf-8")) 82 | 83 | b = struct.pack(fmt, *data) 84 | self.file.write(b) 85 | self.co += len(b) 86 | 87 | def read_string(self): 88 | ''' readst the string from a binary file... in ascii? mmh... 89 | ''' 90 | self.file.seek(self.co) 91 | 92 | # read the length 93 | strlen = self.read("I") 94 | 95 | b = self.file.read(strlen) 96 | s = str(b, "ascii") 97 | self.co += strlen 98 | return s 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/g_eng.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue May 22 22:15:01 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | # ============================================================================= 9 | # Imports 10 | # ============================================================================= 11 | 12 | # py imports 13 | import random 14 | import copy 15 | 16 | # project imports 17 | import snake 18 | import poly 19 | import vec 20 | 21 | #============================================================================== 22 | # Game Engine class 23 | # the class manages the game, score, collisions, ... 24 | #============================================================================== 25 | 26 | class GameEngine: 27 | ''' The class manages the game itself, checks collisions and keeps track 28 | of the polygons in game 29 | ''' 30 | 31 | # collision states 32 | collision_none = 0 33 | collision_outbbox = 1 34 | collision_food = 2 35 | collision_snake = 3 36 | 37 | # game states 38 | gamestate_game_over = 0 39 | gamestate_run = 1 40 | 41 | 42 | def __init__(self): 43 | # creates a snake, which is positioned in the center and with 4 44 | # segments in the -x direction 45 | self.snake = snake.Snake() 46 | 47 | # create the bounding box, the variable is used to test border 48 | # collisions 49 | self.bbox_size = 3 50 | self.bbox = poly.create_cube4d(vec.V4(0, 0, 0, 0), self.bbox_size * 2, "black") 51 | 52 | # add a food hypercube 53 | self.food = self.initialize_food() 54 | 55 | # game state 56 | self.state = self.gamestate_run 57 | 58 | # score (sum of the cubes) 59 | self.score = 0 60 | 61 | # the list of polygons to be drawn 62 | self.p_list = [] 63 | self.generate_plist() 64 | 65 | def generate_plist(self): 66 | ''' Generates the collection of polygons in game, the list will be 67 | used by the graphic engine to render the stuff ''' 68 | 69 | # reset the polygon list 70 | self.p_list = [] 71 | 72 | # add the bbox 73 | self.p_list.append(self.bbox) 74 | 75 | # add the food 76 | self.p_list.append(self.food) 77 | 78 | # add the snake cubes 79 | for cube in self.snake.p_list: 80 | self.p_list.append(cube) 81 | 82 | def initialize_food(self): 83 | ''' Puts the food in the game, checking if is a valid food position ''' 84 | 85 | # Generates random coordinates for the food 86 | def generate_rand_point(): 87 | coords = [] 88 | for i in range(4): 89 | coords.append(random.randrange(-self.bbox_size + 1, self.bbox_size - 1)) 90 | return vec.V4(coords) 91 | 92 | invalid_position = True 93 | while invalid_position: 94 | food_point = generate_rand_point() 95 | invalid_position = False 96 | 97 | # search if the food collides with the snake 98 | for p in self.snake.p_list: 99 | if self.intersect(food_point, p): 100 | invalid_position = True 101 | break 102 | 103 | return poly.create_cube4d(food_point, 0.9, "blue") 104 | 105 | 106 | def intersect(self, point, polygon): 107 | ''' This function checks for the intersection head of the snake with 108 | other polygons. 109 | The polygon class stores the biggest and smallest vertex in the first 2 110 | elements, this is dependent on the create cube function tho, 111 | but it could be solved by the max and min functions ''' 112 | pbbox_low = polygon.v_list[0] 113 | pbbox_high = polygon.v_list[1] 114 | 115 | for i in range(4): 116 | if not (point[i] > pbbox_low[i] and point[i] < pbbox_high[i]): 117 | return False 118 | return True 119 | 120 | def check_collision(self): 121 | ''' This function checks if the moved snake hits something ''' 122 | 123 | # make the snake move in the next cube 124 | self.snake.move() 125 | 126 | # have to make the bbox bigger by one unit so that the program can 127 | # use the intersect function to detect a out of box collision 128 | ext_bbox = copy.deepcopy(self.bbox) 129 | ext_bbox.v_list[0] = self.bbox.v_list[0] - vec.V4(1,1,1,1) 130 | ext_bbox.v_list[1] = self.bbox.v_list[1] + vec.V4(1,1,1,1) 131 | 132 | # if is outside of the bbox 133 | if not self.intersect(self.snake.head_pos, ext_bbox): 134 | return self.collision_outbbox 135 | 136 | # if the snake got food 137 | if self.intersect(self.snake.head_pos, self.food): 138 | return self.collision_food 139 | 140 | # if the snake crosses itself 141 | for p in self.snake.p_list[:-1]: 142 | if self.intersect(self.snake.head_pos, p): 143 | return self.collision_snake 144 | 145 | # else no collisions 146 | return self.collision_none 147 | 148 | def evaluate_collision(self, collision): 149 | ''' Decides if the game is over ''' 150 | # decide what to do in case of collision 151 | 152 | # out of bounding box or collision with itself will make a game over 153 | if collision == self.collision_outbbox or collision == self.collision_snake: 154 | self.state = self.gamestate_game_over 155 | 156 | elif collision == self.collision_food: 157 | self.snake.add_segment() 158 | self.food = self.initialize_food() 159 | self.score += 1 160 | 161 | def routine(self): 162 | ''' Stuff happening every frame 163 | the game routine, checks the collision which will "move" the snake 164 | evaluates it and generates the new plist 165 | ''' 166 | c = self.check_collision() 167 | self.evaluate_collision(c) 168 | self.generate_plist() 169 | 170 | def frame_as_bytes(self, bf): 171 | ''' Needed to save the replay ''' 172 | bf.write("I", len(self.p_list)) 173 | for p in self.p_list: 174 | p.as_bytes(bf) 175 | 176 | 177 | if __name__ == "__main__": 178 | geng = GameEngine() 179 | geng.routine() 180 | 181 | import bfh 182 | 183 | with open("../tests/frame_test.sk4", "wb") as f: 184 | bf = bfh.BinaryFile(f) 185 | 186 | geng.frame_as_bytes(bf) 187 | -------------------------------------------------------------------------------- /src/keybuf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun May 31 21:40:41 2020 4 | 5 | @author: maurop 6 | """ 7 | 8 | 9 | #============================================================================== 10 | # Key buffer 11 | #============================================================================== 12 | 13 | class KeyBuffer: 14 | ''' 15 | Key Buffer 16 | 17 | is a simple buffer that keeps track of the key pressed while playing 18 | it makes a more fluid control of the snake when the game lags. 19 | 20 | Methods: 21 | __init__ 22 | push_key 23 | get_key 24 | clear 25 | 26 | Instance variables: 27 | self.buf -- a simple list doing the job of a LIFO stack 28 | self.key_to_dir -- transforms the key in directions 29 | ''' 30 | 31 | def __init__(self): 32 | ''' 33 | class is initialised with a buffer, and a dictionary that will 34 | convert the keys to directions 35 | ''' 36 | 37 | self.buf = [] 38 | 39 | # construct the dictionary that changes the snake direction 40 | self.key_to_dir = {} 41 | bind_key = ["w", "s", "a", "d", "i", "k", "j", "l"] 42 | possible_dirs = ["UP", "DOWN", "LEFT", "RIGHT", "FW", "RW", "IN", "OUT"] 43 | 44 | for direction, key in zip(possible_dirs, bind_key): 45 | self.key_to_dir[key] = direction 46 | 47 | def push_key(self, key): 48 | ''' 49 | insert the key in the buffer 50 | 51 | Keyword arguments: 52 | key -- can be any character 53 | ''' 54 | 55 | self.buf.append(key) 56 | 57 | def get_key(self): 58 | '''gets the next key, if none is found returns None''' 59 | 60 | if self.buf: 61 | return self.key_to_dir[self.buf.pop(0)] 62 | else: 63 | return None 64 | 65 | def clear(self): 66 | '''Just empties the buffer''' 67 | 68 | self.buf = [] -------------------------------------------------------------------------------- /src/load_replay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 1 19:18:01 2020 4 | 5 | @author: maurop 6 | """ 7 | 8 | import bfh 9 | import poly 10 | 11 | def str_file_size(size): 12 | ''' Puts the file size in the 1 kb format instead of 1000 b''' 13 | 14 | # Size limits in bytes 15 | limits = [1, 1e3, 1e6, 1e9, 1e12] 16 | 17 | # Corresponding letters 18 | letters = ["", "K", "M", "G", "T"] 19 | 20 | # Fuse the two and invert the list 21 | sizes = list(zip(limits, letters))[::-1] 22 | 23 | # start from the bigger till one finds an appropriate limit 24 | for limit, letter in sizes: 25 | value = size / limit 26 | if value >= 1: 27 | value = size / limit 28 | if letter: 29 | return f"{value:.1f} {letter}b" 30 | else: 31 | value = int(value) 32 | return f"{value} b" 33 | 34 | value = int(size) 35 | return f"{value} b" 36 | 37 | 38 | class LoadReplay: 39 | ''' This class loads a replay file''' 40 | 41 | def __init__(self): 42 | # contains the frames 43 | self.frames = [] 44 | 45 | # marks which frame is the frame selected 46 | self.current_frame = 0 47 | 48 | def load_replay_file(self, filename): 49 | ''' Loads the replay file and appends it to the frames''' 50 | self.frames = [] 51 | 52 | with open(filename, "rb") as f: 53 | bf = bfh.BinaryFile(f) 54 | 55 | n_bytes = len(f.read()) 56 | 57 | print("Replay file size:", str_file_size(n_bytes)) 58 | 59 | while bf.co < n_bytes: 60 | 61 | # read frame 62 | p_list_len = bf.read("I") 63 | 64 | p_list = [poly.Polygon() for i in range(p_list_len)] 65 | 66 | for p in p_list: 67 | p.interpret_bytes(bf) 68 | 69 | self.frames.append(p_list) 70 | 71 | print("Frames loaded:", len(self.frames)) 72 | 73 | def next_frame(self): 74 | ''' increments the frame and returns the current frame''' 75 | if self.current_frame + 1 < len(self.frames): 76 | self.current_frame += 1 77 | return self.get_frame() 78 | 79 | def next_frame_wrap(self): 80 | ''' Wraps around the end to start back, used in the play/pause 81 | animation''' 82 | if self.current_frame + 1 < len(self.frames): 83 | self.current_frame += 1 84 | return self.get_frame() 85 | else: 86 | self.current_frame = 0 87 | 88 | def previous_frame(self): 89 | ''' decrements the frame and returns the current frame''' 90 | if self.current_frame - 1 > 0: 91 | self.current_frame -= 1 92 | return self.get_frame() 93 | 94 | def get_frame(self): 95 | ''' gets the current frame''' 96 | return self.frames[self.current_frame] -------------------------------------------------------------------------------- /src/mat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri May 18 20:44:22 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # Matrix class 10 | # utilities for an nxm 2D matrix 11 | #============================================================================== 12 | 13 | # errors 14 | class MatExcept(Exception): 15 | pass 16 | 17 | # class 18 | class Matrix: 19 | ''' MxN matrix class 20 | - can be initializated with aa nested list or with sizes 21 | - item setters and getters throw MathException 22 | - implemented: 23 | - operator + 24 | - operator - 25 | - operator * 26 | - can multy by scalar 27 | - can be transposed 28 | - cam be printed 29 | ''' 30 | 31 | def __init__(self, m, n = None): 32 | # store matrix in a linear vector 33 | self.mat = [] 34 | 35 | # if the first argument is a list and is a list of list 36 | # construct the matrix starting with that as an element 37 | if type(m) == list and type(m[0]) is list and n is None: 38 | self.m = len(m) 39 | self.n = len(m[0]) 40 | 41 | self.init_mat() 42 | for i in range(self.m): 43 | for j in range(self.n): 44 | self[i, j] = m[i][j] 45 | 46 | # if the first argument is a list, yet not a list of list 47 | # assume the user wants to create a mx1 vector 48 | elif type(m) is list and n is None: 49 | self.m = len(m) 50 | self.n = 1 51 | 52 | self.init_mat() 53 | for i in range(self.m): 54 | self[i, 0] = m[i] 55 | 56 | # else initialize a 0ed mxn matrix 57 | else: 58 | self.m = m 59 | self.n = n 60 | self.init_mat() 61 | 62 | def init_mat(self): 63 | for i in range(self.m): 64 | for j in range(self.n): 65 | self.mat.append(0) 66 | 67 | # getter 68 | def __getitem__(self, idx): 69 | linear_index = idx[1] * self.m + idx[0] 70 | return self.mat[linear_index] 71 | 72 | # setter 73 | def __setitem__(self, idx, c): 74 | if idx[0] >= self.m or idx[0] < 0: raise MatExcept("Matrix: row out of range") 75 | if idx[1] >= self.n or idx[1] < 0: raise MatExcept("Matrix: col out of range") 76 | 77 | linear_index = idx[1] * self.m + idx[0] 78 | self.mat[linear_index] = c 79 | 80 | # operator + elementwise sum 81 | def __add__(self, m2): 82 | if self.m == m2.m and self.n == m2.n: 83 | new_mat = [] 84 | for i in range(len(self.mat)): 85 | new_mat.append(self.mat[i] + m2.mat[i]) 86 | 87 | mnew = Matrix(self.m, self.n) 88 | mnew.mat = new_mat 89 | return mnew 90 | 91 | else: 92 | raise MatExcept("Matrix: addition matrices not same size") 93 | 94 | # operator - elementwise 95 | def __sub__(self, m2): 96 | if self.m == m2.m and self.n == m2.n: 97 | new_mat = [] 98 | for i in range(len(self.mat)): 99 | new_mat.append(self.mat[i] - m2.mat[i]) 100 | 101 | mnew = Matrix(self.m, self.n) 102 | mnew.mat = new_mat 103 | return mnew 104 | 105 | else: 106 | raise MatExcept("Matrix: subtraction matrices not same size") 107 | 108 | # matrix multiplication 109 | def __mul__(self, m2): 110 | if self.n == m2.m: 111 | mulmat = Matrix(self.m, m2.n) 112 | for i in range(mulmat.m): 113 | for j in range(mulmat.n): 114 | for m in range(self.n): 115 | mulmat[i, j] += self[i, m] * m2[m, j] 116 | return mulmat 117 | else: 118 | raise MatExcept("Matrix: multiplication matrix columns different then other matrix rows") 119 | 120 | def scalar(self, k): 121 | mat_new = [] 122 | for m in self.mat: 123 | mat_new.append(m * k) 124 | 125 | mres = Matrix(self.m, self.n) 126 | mres.mat = mat_new 127 | 128 | return mres 129 | 130 | def transpose(self): 131 | tmat = Matrix(self.n, self.m) 132 | 133 | for i in range(self.m): 134 | for j in range(self.n): 135 | tmat[j, i] = self[i, j] 136 | return tmat 137 | 138 | 139 | def __str__(self): 140 | s = "" 141 | for i in range(self.m): 142 | for j in range(self.n): 143 | 144 | s += str(self[i, j]) + " " 145 | s += "\n" 146 | 147 | return s 148 | 149 | #============================================================================== 150 | # Squared Matrix utilities 151 | # for a squared matrix (mxm) 152 | #============================================================================== 153 | 154 | class SquareMatrix(Matrix): 155 | 156 | def __init__(self, m): 157 | if type(m) is list: 158 | if len(m) != len(m[0]): raise MatExcept("SqMat: Not a square matrix") 159 | super().__init__(m) 160 | else: 161 | super().__init__(m, m) 162 | 163 | def is_diagonal(self): 164 | for i in range(self.m): 165 | for j in range(self.n): 166 | if i == j and self[i, j] == 0: 167 | return False 168 | 169 | if i != j and self[i, j] != 0: 170 | return False 171 | return True 172 | 173 | def is_lower_triangular(self): 174 | for i in range(self.m): 175 | for j in range(self.n): 176 | if j <= i and self[i, j] == 0: 177 | return False 178 | 179 | if j > i and self[i, j] != 0: 180 | return False 181 | return True 182 | 183 | def is_upper_triangular(self): 184 | for i in range(self.m): 185 | for j in range(self.n): 186 | if i <= j and self[i, j] == 0: 187 | return False 188 | 189 | if i > j and self[i, j] != 0: 190 | return False 191 | return True 192 | 193 | def get_identity(self): 194 | 195 | imatrix = SquareMatrix(self.m) 196 | for i in range(self.m): 197 | imatrix[i, i] = 1 198 | 199 | return imatrix 200 | 201 | if __name__ == "__main__": 202 | 203 | print("Test size initialization") 204 | m = Matrix(2, 3) 205 | print(m) 206 | 207 | print ("Test list initialization") 208 | 209 | m_ini = [ [2, 3, 4], 210 | [1, 0, 0] ] 211 | 212 | m = Matrix(m_ini) 213 | 214 | print(m) 215 | 216 | print("Test setter") 217 | m[1, 2] = 1 218 | print(m) 219 | 220 | print("Test transpose") 221 | 222 | m2 = Matrix(2, 3) 223 | m2[1, 2] = 3 224 | print(m2) 225 | print(m2.transpose()) 226 | 227 | print("Test addition and scalar multiplication") 228 | print("m + m2*4") 229 | 230 | print(m + m2.scalar(4)) 231 | 232 | print("Test multiplication") 233 | 234 | m1 = Matrix(2, 3) 235 | m1[0, 0] = 2 236 | m1[0, 1] = 3 237 | m1[0, 2] = 4 238 | m1[1, 0] = 1 239 | 240 | print(m1) 241 | 242 | m2 = Matrix(3, 2) 243 | m2[0, 1] = 1000 244 | m2[1, 0] = 1 245 | m2[1, 1] = 100 246 | m2[2, 1] = 10 247 | print(m2) 248 | 249 | print("m1 * m2") 250 | 251 | print(m1 * m2) 252 | 253 | print("m1 and m2") 254 | 255 | m1 = [ [1, 2], 256 | [3, 4] ] 257 | m1 = Matrix(m1) 258 | 259 | print(m1) 260 | 261 | m2 = [ [0, 1], 262 | [0, 0] ] 263 | m2 = Matrix(m2) 264 | 265 | print(m2) 266 | 267 | mres = m1 * m2 268 | print(mres) 269 | 270 | print("m1 * m2") 271 | 272 | mres = m2 * m1 273 | print(mres) 274 | 275 | 276 | 277 | print("Test square matrix") 278 | 279 | m = [ [1, 1, 1], 280 | [0, 1, 1], 281 | [0, 0, 1] ] 282 | 283 | m = SquareMatrix(m) 284 | 285 | print(m) 286 | print("Is diagonal, is lower, is upper triangular") 287 | print(m.is_diagonal()) 288 | print(m.is_lower_triangular()) 289 | print(m.is_upper_triangular()) 290 | 291 | print() 292 | 293 | print("Test identity") 294 | print(m.get_identity()) 295 | 296 | 297 | 298 | 299 | 300 | -------------------------------------------------------------------------------- /src/paths.txt: -------------------------------------------------------------------------------- 1 | init_dir_save=./save 2 | init_dir_load=./load 3 | -------------------------------------------------------------------------------- /src/poly.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 17 15:34:47 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | # ============================================================================= 9 | # Imports 10 | # ============================================================================= 11 | 12 | import vec 13 | import bfh 14 | 15 | #============================================================================== 16 | # Polygon class 17 | # simple utility for the polygon class 18 | #============================================================================== 19 | 20 | class Polygon: 21 | '''The polygon class has the methods to write itself in a binary file''' 22 | 23 | def __init__(self): 24 | self.v_list = [] 25 | self.e_list = [] 26 | self.f_list = [] 27 | self.color = "red" 28 | 29 | def as_bytes(self, bf): 30 | ''' writes the polygon ''' 31 | 32 | # write the color at the beginning 33 | bf.write_string(self.color) 34 | 35 | # write vertex list 36 | bf.write("I", len(self.v_list)) 37 | 38 | for v in self.v_list: 39 | v.as_bytes(bf) 40 | 41 | # write edge list 42 | 43 | # first transform it in a linear array 44 | earr = [] 45 | for e in self.e_list: 46 | earr.append(e[0]) 47 | earr.append(e[1]) 48 | 49 | # write that array 50 | earrlen = len(earr) 51 | bf.write("I", earrlen) 52 | for e in earr: 53 | bf.write("I", e) 54 | 55 | # write face list 56 | 57 | # transform it in a linear array 58 | farr = [] 59 | for f in self.f_list: 60 | farr.append(f[0]) 61 | farr.append(f[1]) 62 | farr.append(f[2]) 63 | farr.append(f[3]) 64 | 65 | # then write it 66 | farrlen = len(farr) 67 | bf.write("I", farrlen) 68 | for f in farr: 69 | bf.write("I", f) 70 | 71 | def interpret_bytes(self, bf): 72 | # read the color 73 | self.color = bf.read_string() 74 | 75 | # read vertex list 76 | 77 | v_list = [] 78 | v_len = bf.read("I") 79 | 80 | for i in range(v_len): 81 | v4 = vec.V4(0, 0, 0, 0) 82 | v4.interpret_bytes(bf) 83 | v_list.append(v4) 84 | 85 | self.v_list = v_list 86 | 87 | # read edge list 88 | earrlen = bf.read("I") 89 | 90 | # read in the linear array containg the values 91 | earr = [] 92 | for i in range(earrlen): 93 | e = bf.read("I") 94 | earr.append(e) 95 | 96 | # the array is composed of a series of couples (0 1)(2 3) 97 | # which indicates which vertex are connected 98 | half_earrlen = int(earrlen / 2) 99 | 100 | # initialize the edge list with 0s 101 | self.e_list = [[0,0] for i in range(half_earrlen)] 102 | 103 | # assign those values to the initialized edge list 104 | for i in range(half_earrlen): 105 | self.e_list[i][0] = earr[i*2] 106 | self.e_list[i][1] = earr[i*2 + 1] 107 | 108 | # read face list 109 | farrlen = bf.read("I") 110 | 111 | # read the linear array containgin the vertex indexes that compose a 112 | # face 113 | farr = [] 114 | for i in range(farrlen): 115 | f = bf.read("I") 116 | farr.append(f) 117 | 118 | # the array is composed of a series of couples (0 1 2 3)(5 6 7 8) 119 | # which indicates which vertex form a face 120 | half_farrlen = int(farrlen / 4) 121 | 122 | # initialize the list 123 | self.f_list = [[0, 0, 0, 0] for i in range(half_farrlen)] 124 | 125 | # assign the values read to the face list 126 | for i in range(half_farrlen): 127 | self.f_list[i][0] = farr[i*4] 128 | self.f_list[i][1] = farr[i*4 + 1] 129 | self.f_list[i][2] = farr[i*4 + 2] 130 | self.f_list[i][3] = farr[i*4 + 3] 131 | 132 | 133 | 134 | def write_file(self, filename): 135 | print("-- write --") 136 | with open(filename, "wb") as f: 137 | bf = bfh.BinaryFile(f) 138 | self.as_bytes(bf) 139 | 140 | def read_file4(self, filename): 141 | print("-- read --") 142 | with open(filename, "rb") as f: 143 | bf = bfh.BinaryFile(f) 144 | self.interpret_bytes(bf) 145 | 146 | 147 | #============================================================================== 148 | # Generate 3d cube 149 | #============================================================================== 150 | 151 | # v1, v2 are the respectively lower and upper corner 152 | def cube3d(v1,v2): 153 | polygon = Polygon() 154 | 155 | v3 = vec.V3(v1[0],v2[1],v1[2]) 156 | v4 = vec.V3(v2[0],v1[1],v2[2]) 157 | v5 = vec.V3(v2[0],v2[1],v1[2]) 158 | v6 = vec.V3(v2[0],v1[1],v1[2]) 159 | v7 = vec.V3(v1[0],v1[1],v2[2]) 160 | v8 = vec.V3(v1[0],v2[1],v2[2]) 161 | 162 | polygon.v_list.append(v1) 163 | polygon.v_list.append(v2) 164 | polygon.v_list.append(v3) 165 | polygon.v_list.append(v4) 166 | polygon.v_list.append(v5) 167 | polygon.v_list.append(v6) 168 | polygon.v_list.append(v7) 169 | polygon.v_list.append(v8) 170 | 171 | polygon.e_list.append((0,5)) 172 | polygon.e_list.append((5,4)) 173 | polygon.e_list.append((4,2)) 174 | polygon.e_list.append((2,0)) 175 | 176 | polygon.e_list.append((5,3)) 177 | polygon.e_list.append((4,1)) 178 | polygon.e_list.append((2,7)) 179 | polygon.e_list.append((0,6)) 180 | 181 | polygon.e_list.append((1,3)) 182 | polygon.e_list.append((3,6)) 183 | polygon.e_list.append((6,7)) 184 | polygon.e_list.append((7,1)) 185 | 186 | return polygon 187 | 188 | #============================================================================== 189 | # Generate hypercube 190 | #============================================================================== 191 | 192 | # v0 and v1 are respectively the lower and upper corner 193 | def cube4d(v0,v1): 194 | polygon = Polygon() 195 | 196 | polygon.v_list.append(v0) #v0 197 | polygon.v_list.append(v1) #v1 198 | 199 | polygon.v_list.append(vec.V4(v1[0],v0[1],v0[2],v0[3])) #v2 200 | polygon.v_list.append(vec.V4(v1[0],v1[1],v0[2],v0[3])) #v3 201 | polygon.v_list.append(vec.V4(v0[0],v1[1],v0[2],v0[3])) #v4 202 | polygon.v_list.append(vec.V4(v0[0],v0[1],v1[2],v0[3])) #v5 203 | polygon.v_list.append(vec.V4(v1[0],v0[1],v1[2],v0[3])) #v6 204 | polygon.v_list.append(vec.V4(v1[0],v1[1],v1[2],v0[3])) #v7 205 | polygon.v_list.append(vec.V4(v0[0],v1[1],v1[2],v0[3])) #v8 206 | 207 | polygon.v_list.append(vec.V4(v0[0],v0[1],v0[2],v1[3])) #v9 208 | polygon.v_list.append(vec.V4(v1[0],v0[1],v0[2],v1[3])) #v10 209 | polygon.v_list.append(vec.V4(v1[0],v1[1],v0[2],v1[3])) #v11 210 | polygon.v_list.append(vec.V4(v0[0],v1[1],v0[2],v1[3])) #v12 211 | polygon.v_list.append(vec.V4(v0[0],v0[1],v1[2],v1[3])) #v13 212 | polygon.v_list.append(vec.V4(v1[0],v0[1],v1[2],v1[3])) #v14 213 | polygon.v_list.append(vec.V4(v0[0],v1[1],v1[2],v1[3])) #v15 214 | 215 | polygon.e_list.append((0,2)) 216 | polygon.e_list.append((0,9)) 217 | polygon.e_list.append((2,3)) 218 | polygon.e_list.append((2,10)) 219 | polygon.e_list.append((3,4)) 220 | polygon.e_list.append((3,11)) 221 | polygon.e_list.append((4,0)) 222 | polygon.e_list.append((4,12)) 223 | 224 | polygon.e_list.append((0,5)) 225 | polygon.e_list.append((2,6)) 226 | polygon.e_list.append((4,8)) 227 | polygon.e_list.append((3,7)) 228 | 229 | polygon.e_list.append((5,6)) 230 | polygon.e_list.append((5,13)) 231 | polygon.e_list.append((6,7)) 232 | polygon.e_list.append((6,14)) 233 | polygon.e_list.append((7,8)) 234 | polygon.e_list.append((7,1)) 235 | polygon.e_list.append((8,5)) 236 | polygon.e_list.append((8,15)) 237 | 238 | polygon.e_list.append((9,10)) 239 | polygon.e_list.append((10,11)) 240 | polygon.e_list.append((11,12)) 241 | polygon.e_list.append((12,9)) 242 | 243 | polygon.e_list.append((10,14)) 244 | polygon.e_list.append((11,1)) 245 | polygon.e_list.append((12,15)) 246 | polygon.e_list.append((9,13)) 247 | 248 | polygon.e_list.append((13,14)) 249 | polygon.e_list.append((14,1)) 250 | polygon.e_list.append((1,15)) 251 | polygon.e_list.append((15,13)) 252 | 253 | polygon.f_list.append((1,14,6,7)) 254 | polygon.f_list.append((6,2,3,7)) 255 | polygon.f_list.append((2,10,11,3)) 256 | polygon.f_list.append((10,14,1,11)) 257 | 258 | polygon.f_list.append((7,6,5,8)) 259 | polygon.f_list.append((3,2,0,4)) 260 | polygon.f_list.append((11,10,9,12)) 261 | polygon.f_list.append((1,14,13,15)) 262 | 263 | polygon.f_list.append((14,6,5,13)) 264 | polygon.f_list.append((6,2,0,5)) 265 | polygon.f_list.append((2,10,9,0)) 266 | polygon.f_list.append((10,14,13,9)) 267 | 268 | polygon.f_list.append((1,15,8,7)) 269 | polygon.f_list.append((8,4,3,7)) 270 | polygon.f_list.append((4,12,11,3)) 271 | polygon.f_list.append((12,15,1,11)) 272 | 273 | polygon.f_list.append((1,11,3,7)) 274 | polygon.f_list.append((14,6,2,10)) 275 | polygon.f_list.append((15,8,4,12)) 276 | polygon.f_list.append((13,5,0,9)) 277 | 278 | polygon.f_list.append((15,13,5,8)) 279 | polygon.f_list.append((5,8,4,0)) 280 | polygon.f_list.append((0,4,12,9)) 281 | polygon.f_list.append((13,9,12,15)) 282 | 283 | return polygon 284 | 285 | #============================================================================== 286 | # Generate an hypercube given a center and a size 287 | #============================================================================== 288 | 289 | def create_cube4d( point, size, color): 290 | v = vec.V4(1, 1, 1, 1) * size/2.0 291 | 292 | higher = point + v 293 | lower = point - v 294 | 295 | c = cube4d(lower,higher) 296 | c.color = color 297 | return c 298 | 299 | 300 | if __name__ == "__main__": 301 | 302 | c4 = cube4d(vec.V4(-1, -1, -1, -1), vec.V4(1, 1, 1, 1)) 303 | 304 | c4.write_file("./test_poly.poly") 305 | 306 | c4.read_file4("./test_poly.poly") -------------------------------------------------------------------------------- /src/prj.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 16 23:45:48 2018 4 | 5 | @author: Mauro 6 | """ 7 | #============================================================================== 8 | # Camera utilities 9 | #============================================================================== 10 | 11 | import vec, math 12 | 13 | #============================================================================== 14 | # Errors 15 | #============================================================================== 16 | 17 | class CamExcept(Exception): 18 | pass 19 | 20 | #============================================================================== 21 | # 3d camera 22 | #============================================================================== 23 | 24 | class Cam3: 25 | 26 | def __init__(self): 27 | 28 | # vertex where the camera is 29 | self.From = vec.V3(0,0,5) 30 | 31 | # vertex where the camera points 32 | self.To = vec.V3(0,0,0) 33 | 34 | # up direction 35 | self.Up = vec.V3(0,1,0) 36 | 37 | # transformation matrix 38 | self.t_matrix = self.calc_tmatrix() 39 | 40 | # 2d screen projection sizes 41 | self.Lx = 7 42 | self.Ly = 7 43 | 44 | # center of 2d screen 45 | self.Cx = 0 46 | self.Cy = 0 47 | 48 | # view angle (set to 60 deg by default) 49 | self.view_angle = math.pi/3 50 | 51 | def calc_tmatrix(self): 52 | ''' calculate the transformation matrix ''' 53 | 54 | C = self.To - self.From 55 | C.normalize() 56 | 57 | A = self.Up.cross(C) 58 | A.normalize() 59 | 60 | B = C.cross(A) 61 | if B.magnitude() == 0: CamExcept("Cam3: t matrix B is 0") 62 | #B.normalize() apparently useless? 63 | 64 | return [A,B,C] 65 | 66 | def prj(self,point): 67 | ''' project a point on the 2d screen ''' 68 | 69 | if point == vec.V3(0, 0, 0): raise CamExcept("Cam3: point is 0") 70 | 71 | V = point - self.From 72 | 73 | T = 1 / math.tan(self.view_angle / 2) 74 | 75 | div = V.dot(self.t_matrix[2]) 76 | 77 | if div == 0: CamExcept("Cam3: S div is 0") 78 | 79 | S = T / div 80 | 81 | Sx = self.Cx + (self.Lx * S * V.dot(self.t_matrix[0])) 82 | Sy = self.Cx + (self.Lx * S * V.dot(self.t_matrix[1])) 83 | 84 | return vec.V2(Sx,Sy) 85 | 86 | # changing position of the target or form means to calculate the transform 87 | # matrix again 88 | 89 | def change_target(self, t): 90 | self.To = t 91 | self.t_matrix = self.calc_tmatrix() 92 | 93 | def change_position(self, p): 94 | self.From = p 95 | self.t_matrix = self.calc_tmatrix() 96 | 97 | #============================================================================== 98 | # 4d Camera 99 | #============================================================================== 100 | 101 | class Cam4: 102 | 103 | def __init__(self): 104 | self.From = vec.V4(10, 0, 0, 0) 105 | self.To = vec.V4(0,0,0,0) 106 | self.Up = vec.V4(0,1,0,0) 107 | self.Over = vec.V4(0,0,1,0) 108 | 109 | self.t_matrix = self.calc_tmatrix() 110 | 111 | # prjection screen cube center 112 | self.Bx = 0 113 | self.By = 0 114 | self.Bz = 0 115 | 116 | # projection screen cube dimensions 117 | self.Lx = 1.5 118 | self.Ly = 1.5 119 | self.Lz = 1.5 120 | 121 | # 4d view angle (set at 60 deg by default) 122 | self.view_angle = math.pi/3 123 | 124 | def calc_tmatrix(self): 125 | '''calculates the transformation matrix''' 126 | 127 | D = self.To - self.From 128 | D.normalize() 129 | 130 | A = self.Up.cross(self.Over, D) 131 | A.normalize() 132 | 133 | B = self.Over.cross(D, A) 134 | B.normalize() 135 | 136 | C = D.cross(A, B) 137 | if C.magnitude() == 0: raise CamExcept("Cam4: C module is 0") 138 | 139 | return [A,B,C,D] 140 | 141 | def prj(self, point): 142 | '''project the a 4d point into a 3d cube screen''' 143 | point = point - self.From 144 | if point == vec.V4(0, 0, 0, 0): raise CamExcept("Cam4: prj point null vec") 145 | 146 | T = 1/(math.tan(self.view_angle)/2) 147 | 148 | S = T / (point.dot(self.t_matrix[3])) 149 | 150 | x = self.Bx + (self.Lx * S * point.dot(self.t_matrix[0])) 151 | y = self.By + (self.Ly * S * point.dot(self.t_matrix[1])) 152 | z = self.Bz + (self.Lz * S * point.dot(self.t_matrix[2])) 153 | 154 | return vec.V3(x,y,z) 155 | 156 | def change_target(self, t): 157 | self.To = t 158 | self.t_matrix = self.calc_tmatrix() 159 | 160 | def change_position(self, p): 161 | self.From = p 162 | self.t_matrix = self.calc_tmatrix() 163 | 164 | if __name__ == "__main__": 165 | 166 | cam = Cam4() 167 | 168 | p = vec.V4(1, 1, 1, 1) 169 | p_proj = cam.prj(p) 170 | 171 | print(p_proj) 172 | 173 | cam = Cam3() 174 | 175 | p_proj = cam.prj(p_proj) 176 | 177 | print(p_proj) 178 | 179 | -------------------------------------------------------------------------------- /src/qua.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 16 23:46:53 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # Quaternion utilities 10 | #============================================================================== 11 | 12 | import math, vec 13 | import copy 14 | 15 | class Q(vec.V4): 16 | ''' Quaternion class ''' 17 | 18 | def __init__(self, w, x, y = None, z = None): 19 | ''' The quaternion defined as Q with scalar s and vector (i, j, k) gets 20 | mapped to a V4 (x, y, z, w) so that s is w, i, j, k are x, y, z 21 | ''' 22 | # possibly change in Q(x,y,z,w) or Q(s, v) 23 | 24 | # if the first argument is a vector 25 | if isinstance(x, vec.V3): 26 | y = x[1] 27 | z = x[2] 28 | x = x[0] 29 | 30 | super().__init__(x, y, z, w) 31 | 32 | def qi(self): 33 | ''' conjugate of the quaternion ''' 34 | for i in range(0, self.dimension - 1): 35 | self.coords[i] = -self.coords[i] 36 | return self 37 | 38 | def cross(self, q2): 39 | ''' cross product between quaternions ''' 40 | s1 = self.w() 41 | s2 = q2.w() 42 | 43 | v1 = vec.V3(self.x(), self.y(), self.z()) 44 | v2 = vec.V3( q2.x(), q2.y(), q2.z()) 45 | 46 | s3 = s1 * s2 - (v1.dot(v2)) 47 | 48 | v3 = (v2 * s1 + v1 * s2) + v1.cross(v2) 49 | 50 | return Q(s3, v3) 51 | 52 | def hamilton(self, q): 53 | ''' hamilton multiplication ''' 54 | a1 = self.w() 55 | a2 = q.w() 56 | 57 | b1 = self.x() 58 | b2 = q.x() 59 | 60 | c1 = self.y() 61 | c2 = q.y() 62 | 63 | d1 = self.z() 64 | d2 = q.z() 65 | 66 | w = a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2 67 | x = a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2 68 | y = a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2 69 | z = a1 * d2 + b1 * c2 - c1 * b2 + d1 * a2 70 | 71 | return Q(w, x, y, z) 72 | 73 | def get_vector(self): 74 | '''get the vector part of the quaternion''' 75 | return vec.V3(self.x(), self.y(), self.z()) 76 | 77 | 78 | def rot_around_axis(point, axis, angle): 79 | ''' function that rotates a point around a given axis ''' 80 | 81 | axis.normalize() 82 | 83 | # construct the quaternion 84 | s = math.cos(angle / 2) 85 | v = axis * math.sin(angle /2) 86 | 87 | q = Q(s, v) 88 | qi = copy.deepcopy(q).qi() 89 | 90 | p = Q(0, point) 91 | 92 | prot = q.cross(p).cross(qi) 93 | return vec.V3(prot.x(), prot.y(), prot.z()) 94 | 95 | if __name__ == "__main__": 96 | 97 | print("----------") 98 | 99 | q1 = Q(1,2,3,4) 100 | 101 | print(q1) 102 | 103 | q2 = Q(5, vec.V3(1, 2, 3)) 104 | 105 | print(q2) 106 | 107 | print(q1.cross(q2)) 108 | 109 | 110 | print("----------") 111 | 112 | # temptative quaternion rotation 113 | # problem is to find the given angle and axis 114 | # probably use bivectors or a rotor 115 | qL = Q(1, 0, 0, 0) 116 | qL.normalize() 117 | qR = Q(0, 0, 1, 0) 118 | qR.normalize() 119 | 120 | p = vec.V4(1, 1, 1, 1) 121 | 122 | pi = qL.hamilton(p).hamilton(qR) 123 | 124 | print(pi) 125 | -------------------------------------------------------------------------------- /src/rate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun May 31 21:42:17 2020 4 | 5 | @author: maurop 6 | """ 7 | 8 | # ============================================================================= 9 | # Imports 10 | # ============================================================================= 11 | 12 | import time 13 | 14 | #============================================================================== 15 | # Rate class 16 | # for update rate, fps, ... 17 | #============================================================================== 18 | 19 | class Rate: 20 | ''' 21 | Rate 22 | 23 | small class to manage fps and various update rates 24 | ''' 25 | 26 | def __init__(self, rate): 27 | '''Initialize the rate calling for the time function 28 | 29 | Keyword argument: 30 | rate -- is a float representing 1/s frame rate 31 | ''' 32 | 33 | self.rate = rate 34 | self.init_time = time.time() 35 | 36 | def is_time(self): 37 | '''Returns true if the current time surpasses the rate''' 38 | 39 | if time.time() - self.init_time > self.rate: 40 | self.init_time = time.time() 41 | return True 42 | else: 43 | return False -------------------------------------------------------------------------------- /src/rem_path.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jun 2 17:44:37 2020 4 | 5 | @author: maurop 6 | """ 7 | 8 | import os 9 | 10 | 11 | class RememberPath: 12 | 13 | ''' This class serves as a database for the open/save file path, 14 | it remembers which was the last directory used''' 15 | 16 | #File where path are stored 17 | paths_file = "./settings/paths.txt" 18 | 19 | # class attribute dictionary storing the paths 20 | paths = {} 21 | 22 | def __init__(self, name, path): 23 | ''' creates a path checking if is already present in the file''' 24 | 25 | # read the file 26 | if os.path.isfile(self.paths_file): 27 | self.read() 28 | 29 | # add if is a new path 30 | self.add_path(name, path) 31 | 32 | # internal variable identfying the path 33 | self.name = name 34 | 35 | def add_path(self, name, path): 36 | ''' Append path and append it to the file, if is not present 37 | in the file ''' 38 | 39 | if self.paths.get(name) == None: 40 | self.paths[name] = path 41 | self.write() 42 | 43 | def get(self): 44 | return self.paths[self.name] 45 | 46 | 47 | def assign(self, path): 48 | ''' assign a new path to the name, then update the file ''' 49 | self.paths[self.name] = path 50 | self.write() 51 | 52 | def read(self): 53 | with open(self.paths_file, "r") as f: 54 | lines = f.readlines() 55 | 56 | for line in lines: 57 | parts = line.split("=") 58 | self.paths[parts[0].strip()] = parts[1].strip() 59 | 60 | def write(self): 61 | with open(self.paths_file, "w") as f: 62 | for name, path in self.paths.items(): 63 | f.write(f"{name}={path}\n") -------------------------------------------------------------------------------- /src/replay.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Sun May 31 20:53:24 2020 4 | 5 | @author: maurop 6 | """ 7 | 8 | # ============================================================================= 9 | # Imports 10 | # ============================================================================= 11 | 12 | # py imports 13 | import os 14 | from tkinter import (IntVar, StringVar, Toplevel, Checkbutton, Button, 15 | filedialog, Label) 16 | 17 | # project imports 18 | import bfh 19 | import load_replay 20 | import rem_path 21 | 22 | # ============================================================================= 23 | # Replay settings 24 | # ============================================================================= 25 | 26 | class ReplaySettings: 27 | 28 | ''' The class manages the replay settings which are a check box for 29 | recording and a path to a file where the replay will get stored''' 30 | 31 | file = "./settings/replay.txt" 32 | 33 | def __init__(self): 34 | # Default value if the replay setting file doesn't exist 35 | self.record = True 36 | 37 | # read the replay setting file 38 | if os.path.isfile(self.file): 39 | self.read() 40 | 41 | # set up the variable bound to the check box 42 | self.recordvar = IntVar() 43 | self.recordvar.set(1 if self.record else 0) 44 | 45 | def display(self, frame): 46 | ''' Displays a checkbox that can be marked for recording ''' 47 | 48 | # create a top level 49 | level = Toplevel(frame) 50 | 51 | # create a radio button for the recording 52 | checkbox = Checkbutton(level, text = "Record", variable = self.recordvar) 53 | checkbox.pack() 54 | 55 | # create a label that shows when the checkbox is updated 56 | # display No change if isn't changed 57 | # display changed for 1 second if is changed 58 | # display no change again? 59 | # maybe use a check button? 60 | 61 | def read_state(self): 62 | ''' This function reads the check box and updates the settings 63 | accordingly 64 | ''' 65 | if self.recordvar.get() == 1: 66 | current_record_state = True 67 | else: 68 | current_record_state = False 69 | 70 | if current_record_state != self.record: 71 | self.record = current_record_state 72 | self.save() 73 | 74 | def save(self): 75 | ''' saves the setting file overwriting it''' 76 | with open(self.file, "w") as f: 77 | f.write("record=" + str(self.record) + "\n") 78 | 79 | def read(self): 80 | ''' reads the setting file ''' 81 | with open(self.file, "r") as f: 82 | lines = f.readlines() 83 | 84 | self.record = False if lines[0].split("=")[1].strip() == "False" else True 85 | 86 | class Replay: 87 | ''' The class manages the loading and saving replays ''' 88 | 89 | tmp_replay_file = "./settings/_tmp_replay_file.sk4" 90 | 91 | def __init__(self, geng): 92 | # controls if the users wants to record the replay 93 | self.replay_settings = ReplaySettings() 94 | 95 | # the replay loading class 96 | self.replay = load_replay.LoadReplay() 97 | 98 | # the game engine is needed because the replay polygons can be 99 | # assigned directly to the p_list of the game engine and thus rendered 100 | self.game = geng 101 | 102 | # state to check if the replay is playing 103 | self.play_state = False 104 | 105 | # shows the play/pause string 106 | self.str_play = StringVar() 107 | 108 | # saves the last directory used 109 | self.previous_save_dir = rem_path.RememberPath("save_dir", "/") 110 | self.previous_load_dir = rem_path.RememberPath("load_path", "/") 111 | 112 | def elaborate_path(self, filename, previous_directory): 113 | ''' Function that given a file name splits in path and stores it in 114 | the previous path and construct a replay filename''' 115 | 116 | pos = filename.rfind("/") 117 | 118 | previous_directory.assign(filename[ : pos]) 119 | 120 | name = filename[pos + 1 : ] 121 | # check if the extention was added, if it wasnt, add the extention 122 | if name[::-1][0:4] != ".sk4"[::-1]: 123 | name += ".sk4" 124 | 125 | return os.path.join(previous_directory.get(), name) 126 | 127 | def select_save_path(self): 128 | ''' Prompt the user to select a path ''' 129 | 130 | initdir = self.previous_save_dir.get() 131 | title = "Name a new file" 132 | extentions = (("snake4d files", ".sk4"),("all files", "*.*")) 133 | pathname = filedialog.asksaveasfilename(initialdir=initdir, 134 | title=title, 135 | filetypes=extentions) 136 | 137 | if pathname: 138 | return self.elaborate_path(pathname, self.previous_save_dir) 139 | 140 | def select_load_path(self): 141 | initdir = self.previous_load_dir.get() 142 | title = "Select a replay file" 143 | extentions = (("snake4d files", ".sk4"),("all files", "*.*")) 144 | pathname = filedialog.askopenfilename(initialdir=initdir, 145 | title=title, 146 | filetypes=extentions) 147 | 148 | if pathname: 149 | return self.elaborate_path(pathname, self.previous_load_dir) 150 | 151 | 152 | def reset_replay_file(self): 153 | # resets the replay file... it should be changed because more often 154 | # than not overwrites replays 155 | with open(self.tmp_replay_file, "wb") as f: 156 | f.write(b"") 157 | 158 | def save_replay_frame(self, geng): 159 | ''' At the update game engine tick this function will append a frame 160 | to the file ''' 161 | if self.replay_settings.record: 162 | filename = self.tmp_replay_file 163 | with open(filename, "ab") as fobj: 164 | replay_file = bfh.BinaryFile(fobj) 165 | geng.frame_as_bytes(replay_file) 166 | 167 | def load_replay(self, filename): 168 | # add a top level to manage the replay 169 | 170 | toplevel = Toplevel() 171 | toplevel.title("Replay controls") 172 | 173 | # add the filename as a label 174 | filename = filename.replace("\\", "/") 175 | pos = filename.rfind("/") + 1 176 | name = filename[pos:] 177 | 178 | filename_label = Label(toplevel, text=name) 179 | filename_label.grid(row=0, columnspan=5) 180 | 181 | # add buttons allback back play/pause forward allfw 182 | 183 | b_allback = Button(toplevel, text="|<", command=self.all_back) 184 | b_allback.grid(row=1, column=0) 185 | 186 | b_back = Button(toplevel, text="<", command=self.back) 187 | b_back.grid(row=1, column=1) 188 | 189 | 190 | self.str_play.set("pause" if self.play_state else "play") 191 | 192 | b_play = Button(toplevel, textvariable=self.str_play, command=self.play) 193 | b_play.grid(row=1, column=2) 194 | 195 | b_fw = Button(toplevel, text=">", command=self.fw) 196 | b_fw.grid(row=1, column=3) 197 | 198 | b_allfw = Button(toplevel, text=">|", command=self.allfw) 199 | b_allfw.grid(row=1, column=4) 200 | 201 | # load the replay 202 | self.replay.load_replay_file(filename) 203 | 204 | # add a frame visualizer 205 | self.frame_number_var = StringVar() 206 | 207 | frame_number_label = Label(toplevel, textvariable=self.frame_number_var) 208 | frame_number_label.grid(row=2, column=0, columnspan=5) 209 | 210 | self.update_frame_number() 211 | 212 | def update_frame_number(self): 213 | tot_frames = len(self.replay.frames) 214 | current_frame = self.replay.current_frame + 1 215 | s = f"Frame: {current_frame}/{tot_frames}" 216 | self.frame_number_var.set(s) 217 | 218 | def tmp_replay_file_is_empty(self): 219 | with open(self.tmp_replay_file, "rb") as f: 220 | nbytes = len(f.read()) 221 | return nbytes == 0 222 | 223 | def set_plist(self, p_list): 224 | if p_list: 225 | self.game.p_list = p_list 226 | self.update_frame_number() 227 | 228 | # commands to control the replay 229 | # all back, back 1, play/pause, forward 1, all forward 230 | 231 | def all_back(self): 232 | self.replay.current_frame = 0 233 | 234 | p_list = self.replay.get_frame() 235 | 236 | self.set_plist(p_list) 237 | self.update_frame_number() 238 | 239 | def back(self): 240 | p_list = self.replay.previous_frame() 241 | self.set_plist(p_list) 242 | 243 | def play(self): 244 | # toggle the play state 245 | if self.play_state: 246 | self.play_state = False 247 | else: 248 | self.play_state = True 249 | 250 | self.str_play.set("pause" if self.play_state else "play") 251 | 252 | 253 | def fw(self): 254 | p_list = self.replay.next_frame() 255 | self.set_plist(p_list) 256 | 257 | def allfw(self): 258 | self.replay.current_frame = len(self.replay.frames) - 1 259 | p_list = self.replay.get_frame() 260 | self.set_plist(p_list) 261 | 262 | def play_frames(self): 263 | 264 | # make sure something is loaded 265 | if self.replay.frames: 266 | 267 | if self.play_state: 268 | 269 | p_list = self.replay.next_frame_wrap() 270 | self.set_plist(p_list) 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /src/rot4.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri May 18 20:44:22 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # 4d rotation utilities 10 | #============================================================================== 11 | 12 | import mat, vec, math 13 | 14 | #============================================================================== 15 | # 4d rot functions 16 | # each function represent a rotation matrix 17 | #============================================================================== 18 | 19 | def rot_alpha(v, alpha): 20 | mxy = [ [ math.cos(alpha), math.sin(alpha), 0, 0], 21 | [ -math.sin(alpha), math.cos(alpha), 0, 0], 22 | [ 0, 0, 1, 0], 23 | [ 0, 0, 0, 1] ] 24 | 25 | mxy = mat.SquareMatrix(mxy) 26 | r = mxy * mat.Matrix(v.coords) 27 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 28 | 29 | def rot_beta(v, beta): 30 | myz = [ [ 1, 0, 0, 0], 31 | [ 0, math.cos(beta), math.sin(beta), 0], 32 | [ 0, -math.sin(beta), math.cos(beta), 0], 33 | [ 0, 0, 0, 1] ] 34 | 35 | myz = mat.SquareMatrix(myz) 36 | r = myz * mat.Matrix(v.coords) 37 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 38 | 39 | def rot_gamma(v, gamma): 40 | mzx = [ [ math.cos(gamma), 0, -math.sin(gamma), 0], 41 | [ 0, 1, 0, 0], 42 | [ math.sin(gamma), 0, math.cos(gamma), 0], 43 | [ 0, 0, 0, 1] ] 44 | 45 | mzx = mat.SquareMatrix(mzx) 46 | r = mzx * mat.Matrix(v.coords) 47 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 48 | 49 | def rot_delta(v, delta): 50 | mxw = [ [ math.cos(delta), 0, 0, math.sin(delta)], 51 | [ 0, 1, 0, 0], 52 | [ 0, 0, 1, 0], 53 | [ -math.sin(delta), 0, 0, math.cos(delta)] ] 54 | 55 | mxw = mat.SquareMatrix(mxw) 56 | r = mxw * mat.Matrix(v.coords) 57 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 58 | 59 | def rot_rho(v, rho): 60 | myw = [ [ 1, 0, 0, 0], 61 | [ 0, math.cos(rho), 0, -math.sin(rho)], 62 | [ 0, 0, 1, 0], 63 | [ 0, math.sin(rho), 0, math.cos(rho)] ] 64 | 65 | myw = mat.SquareMatrix(myw) 66 | r = myw * mat.Matrix(v.coords) 67 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 68 | 69 | def rot_epsilon(v, epsilon): 70 | mzw = [ [ 1, 0, 0, 0], 71 | [ 0, 1, 0, 0], 72 | [ 0, 0, math.cos(epsilon), -math.sin(epsilon)], 73 | [ 0, 0, math.sin(epsilon), math.cos(epsilon)] ] 74 | 75 | mzw = mat.SquareMatrix(mzw) 76 | r = mzw * mat.Matrix(v.coords) 77 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 78 | 79 | #============================================================================== 80 | # 4d rotation all in one 81 | # the rot_v function returns a rotated vector for all 6 possible rotation 82 | # angles 83 | #============================================================================== 84 | 85 | def rot_v(v, alpha, beta, gamma, delta, rho, epsilon): 86 | 87 | # XY Plane rot matrix 88 | mxy = [ [ math.cos(alpha), math.sin(alpha), 0, 0], 89 | [ -math.sin(alpha), math.cos(alpha), 0, 0], 90 | [ 0, 0, 1, 0], 91 | [ 0, 0, 0, 1] ] 92 | 93 | mxy = mat.SquareMatrix(mxy) 94 | 95 | # YZ Plane rot matrix 96 | myz = [ [ 1, 0, 0, 0], 97 | [ 0, math.cos(beta), math.sin(beta), 0], 98 | [ 0, -math.sin(beta), math.cos(beta), 0], 99 | [ 0, 0, 0, 1] ] 100 | 101 | myz = mat.SquareMatrix(myz) 102 | 103 | # ZX Plane rot matrix 104 | mzx = [ [ math.cos(gamma), 0, -math.sin(gamma), 0], 105 | [ 0, 1, 0, 0], 106 | [ math.sin(gamma), 0, math.cos(gamma), 0], 107 | [ 0, 0, 0, 1] ] 108 | 109 | mzx = mat.SquareMatrix(mzx) 110 | 111 | # XW Plane rot matrix 112 | mxw = [ [ math.cos(delta), 0, 0, math.sin(delta)], 113 | [ 0, 1, 0, 0], 114 | [ 0, 0, 1, 0], 115 | [ -math.sin(delta), 0, 0, math.cos(delta)] ] 116 | 117 | mxw = mat.SquareMatrix(mxw) 118 | 119 | # YW Plane rot matrix 120 | myw = [ [ 1, 0, 0, 0], 121 | [ 0, math.cos(rho), 0, -math.sin(rho)], 122 | [ 0, 0, 1, 0], 123 | [ 0, math.sin(rho), 0, math.cos(rho)] ] 124 | 125 | myw = mat.SquareMatrix(myw) 126 | 127 | # ZW Plane rot matrix 128 | mzw = [ [ 1, 0, 0, 0], 129 | [ 0, 1, 0, 0], 130 | [ 0, 0, math.cos(epsilon), -math.sin(epsilon)], 131 | [ 0, 0, math.sin(epsilon), math.cos(epsilon)] ] 132 | 133 | mzw = mat.SquareMatrix(mzw) 134 | 135 | # multiply them 136 | 137 | mres = mxy * myz * mzx * mxw * myw * mzw 138 | 139 | mv = mat.Matrix(v.coords) # creates a 1 column matrix 140 | 141 | r = mres * mv 142 | 143 | return vec.V4(r[0,0], r[1,0], r[2,0], r[3,0]) 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/score.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri May 25 22:01:56 2018 4 | 5 | @author: Mauro 6 | """ 7 | #============================================================================== 8 | # Imports 9 | #============================================================================== 10 | 11 | # py imports 12 | import datetime 13 | from tkinter import Label, Toplevel 14 | import os 15 | 16 | # project imports 17 | import visu, poly, vec #needed for the graph 18 | 19 | # ============================================================================= 20 | # Score data structure 21 | # ============================================================================= 22 | 23 | class Score: 24 | ''' Class representing a score, which is a date and a integer''' 25 | 26 | def __init__(self, date, score = None): 27 | if type(date) == str: 28 | self.read_from_line(date) 29 | elif type(date) is datetime.datetime and type(score) is int: 30 | self.date = date 31 | self.score = score 32 | else: 33 | raise ValueError("wrong parameters: " + str(date) + ", " + str(score)) 34 | 35 | def read_from_line(self, line): 36 | data = line.split(" ") 37 | self.date = datetime.datetime.strptime(data[0], "%Y-%m-%dT%H:%M:%S.%f") 38 | self.score = int(data[1]) 39 | 40 | def __hash__(self): 41 | return hash((self.date, self.score)) 42 | 43 | def __eq__(self, rhs): 44 | return self.score == rhs.score and self.date == rhs.date 45 | 46 | def __str__(self): 47 | s = self.date.isoformat() + " " 48 | s += str(self.score) 49 | return s 50 | 51 | 52 | class Scores: 53 | 54 | ''' A list of scores 55 | the class manages the reading and writing to file of the scores and the 56 | sorting of different scores 57 | ''' 58 | 59 | def __init__(self): 60 | 61 | # put the score file in the settings and untrack it from git 62 | self.filename = "./settings/scores.txt" 63 | 64 | self.scores = [] 65 | 66 | # number of scores kept between max scores and recent scores 67 | self.keep_scores = 20 68 | 69 | self.last_score = None 70 | 71 | # reads the file and appends the scores 72 | self.read_scores() 73 | 74 | 75 | def read_scores(self): 76 | self.scores = [] 77 | 78 | if os.path.isfile(self.filename): 79 | with open(self.filename, "r") as f: 80 | lines = f.readlines() 81 | 82 | for line in lines: 83 | s = Score(line) 84 | self.scores.append(s) 85 | 86 | def add_score(self, score): 87 | self.scores.append(score) 88 | self.last_score = score 89 | 90 | def sort_by_score(self, limit): 91 | scores = sorted(self.scores, key=lambda s : s.score, reverse=True) 92 | return scores[:limit] 93 | 94 | def sort_by_date(self, limit): 95 | scores = sorted(self.scores, key=lambda s : s.date, reverse=True) 96 | return scores[:limit] 97 | 98 | def write_scores(self): 99 | # keep the last 20 scores and the best 20 scores 100 | best_scores = self.sort_by_score(self.keep_scores) 101 | recent_scores = self.sort_by_date(self.keep_scores) 102 | 103 | # make sure no duplicate score is reported 104 | scores = set(best_scores + recent_scores) 105 | 106 | # write the date and score on a new line 107 | with open(self.filename, "w") as f: 108 | f.writelines([str(score) + "\n" for score in scores]) 109 | 110 | def get_top_ten_scores(self): 111 | return self.sort_by_score(10) 112 | 113 | # gets the last 20 scores in order of time 114 | def get_last_scores(self): 115 | return self.sort_by_date(20) 116 | 117 | # ============================================================================= 118 | # Class Score Board - GUI 119 | # ============================================================================= 120 | 121 | class ScoreBoard: 122 | 123 | ''' The GUI representation of the class scores ''' 124 | 125 | def __init__(self, parent): 126 | self.parent = parent 127 | self.scores = Scores() 128 | 129 | def add_score(self, score): 130 | self.scores.add_score(score) 131 | self.scores.write_scores() 132 | 133 | def render_scores(self): 134 | board = Toplevel(self.parent) 135 | 136 | # title lable 137 | titlel = Label(board, text = "---- HIGH SCORE ----", font=("Arial", 20)) 138 | titlel.grid(row = 0, column = 0) 139 | 140 | # get top ten 141 | top_ten = self.scores.get_top_ten_scores() 142 | 143 | # score list 144 | # take highest score 145 | hscore = top_ten[0].score 146 | # get the character width 147 | cwidth_score = len(str(hscore)) 148 | # construct the format so that the highest score will give the spacing 149 | format_score = "{: >" + str(cwidth_score) + "}" 150 | 151 | for i, score in enumerate(top_ten): 152 | 153 | # format the scores and date 154 | date_str = score.date.strftime("%d %b %y") 155 | fscore = format_score.format(score.score) 156 | 157 | s = f"{date_str} - {fscore}" 158 | 159 | # if the score is the last obtained color it in orange 160 | if score == self.scores.last_score: 161 | color = "darkorange3" 162 | rel = "groove" 163 | else: 164 | color = "green" 165 | rel = "flat" 166 | 167 | ltop_board = Label(board, text=s, font=("Fixedsys", 14), fg = color, 168 | borderwidth=2, relief=rel, padx=1, pady=1) 169 | 170 | ltop_board.grid(row=(i + 1), column = 0, padx=0, pady=0) 171 | 172 | #in case needed is a graph of the scores 173 | graph = visu.VisuArea(board, None, [250, 100], "Score Evolution") 174 | graph.fP.grid(row=0, column = 1, rowspan=len(top_ten)) 175 | 176 | graph.c_center_w = 0 177 | graph.c_center_h = 0 178 | 179 | p = poly.Polygon() 180 | 181 | # get the scores 182 | score_list = self.scores.get_last_scores() 183 | score_list = score_list[::-1] 184 | 185 | # for each score create a point 186 | point_list = [] 187 | for i, score in enumerate(score_list): 188 | v = vec.V2(i, score.score) 189 | point_list.append(v) 190 | 191 | # determine the height of the graph 192 | p_max = max(point_list, key= lambda polygon : polygon.y()) 193 | graph.area_h = p_max.y() + p_max.y() * 0.15 194 | graph.area_w = len(score_list) 195 | 196 | # construct the line using the polygon class 197 | e_list = [] 198 | for i, p in enumerate(point_list[:-1]): 199 | e_list.append([i, i + 1]) 200 | 201 | p.v_list = point_list 202 | p.e_list = e_list 203 | p.color = "red" 204 | 205 | graph.draw_poly(p) 206 | # add axis 207 | graph.draw_edge(vec.V2(0, graph.area_h), vec.V2(graph.area_w, graph.area_h), kwargs={"fill":"black"}) 208 | 209 | # write a label reporting the max score 210 | graph.canvas.create_text(5, p_max.y(), 211 | text= "Max score: " + str(p_max.y()), 212 | anchor= "w") 213 | 214 | 215 | 216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /src/snake.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue May 22 14:25:00 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # Snake class 10 | #============================================================================== 11 | 12 | import vec, poly 13 | import copy 14 | 15 | # to do: make segment smaller in the directions orthogonal to the direction 16 | # this will make the snake less simmetric 17 | # head could be a thetra hedron or half a hypersphere on top of 18 | # cylinder 19 | 20 | class Snake: 21 | 22 | def __init__(self): 23 | # position of the head 24 | self.head_pos = vec.V4(0, 0, 0, 0) 25 | # direction 26 | self.head_dir ="UP" 27 | #initial size 28 | init_size = 4 29 | 30 | # polygon list of the snake 31 | self.p_list = [] 32 | 33 | for i in range(init_size): 34 | next_cube_center = self.head_pos - vec.V4( (init_size - 1) - i, 0, 0 , 0) 35 | next_cube = self.create_cube(next_cube_center) 36 | self.p_list.append(next_cube) 37 | 38 | # generate the directions vectors and forbbidden directions 39 | possible_dirs = ["UP", "DOWN", "LEFT", "RIGHT", "FW", "RW", "IN", "OUT"] 40 | 41 | self.dir_v = {} 42 | self.opposite_dir = {} 43 | for i, direction in enumerate(possible_dirs): 44 | # generate direction vectors 45 | v = [0, 0, 0, 0] 46 | v[int(i / 2)] = 1 if i % 2 == 0 else -1 47 | self.dir_v[direction] = vec.V4(v) 48 | 49 | # generate forbidden directions (ex. UP -> cant go DOWN) 50 | self.opposite_dir[direction] = possible_dirs[i + (1 if i % 2 == 0 else -1)] 51 | 52 | # create an hypercube segment 53 | def create_cube(self, point): 54 | return poly.create_cube4d(point, 1., "green") 55 | 56 | def change_dir(self, new_dir): 57 | if new_dir != self.opposite_dir[self.head_dir]: 58 | self.head_dir = new_dir 59 | return True 60 | else: 61 | return False 62 | 63 | def move(self): 64 | # move the snake head 65 | self.head_pos = self.head_pos + self.dir_v[self.head_dir] 66 | 67 | # move the snake body by popping the last element and adding a 68 | # segment in the head 69 | self.p_list.pop(0) 70 | self.p_list.append(self.create_cube(self.head_pos)) 71 | 72 | def add_segment(self): 73 | # copy the last block 74 | last_block = copy.deepcopy(self.p_list[-1]) 75 | # add to polygon list 76 | self.p_list.append(last_block) 77 | 78 | 79 | if __name__ == "__main__": 80 | 81 | pass 82 | -------------------------------------------------------------------------------- /src/vec.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 16 22:37:10 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # # Vector utility for 2, 3, 4 dimensions 10 | #============================================================================== 11 | 12 | import math 13 | 14 | import bfh 15 | 16 | #============================================================================== 17 | # Constants 18 | #============================================================================== 19 | 20 | # is close constant 21 | epsilon = 0.00000001 22 | 23 | #============================================================================== 24 | # Helpers 25 | #============================================================================== 26 | 27 | 28 | 29 | #============================================================================== 30 | # Errors 31 | #============================================================================== 32 | 33 | class VecExcept(Exception): 34 | pass 35 | 36 | #============================================================================== 37 | # Vector base class 38 | #============================================================================== 39 | 40 | class Vector: 41 | 42 | def __init__(self, dimensions, coords = None): 43 | if isinstance(dimensions, str): 44 | self.read_file(dimensions) 45 | else: 46 | if coords is None: 47 | self.coords = [0 for i in range(dimensions)] 48 | else: 49 | self.coords = coords 50 | 51 | self.dimension = dimensions 52 | 53 | # setter and getter 54 | def __getitem__(self, i): 55 | return self.coords[i] 56 | 57 | def __setitem__(self, i, v): 58 | self.coords[i] = v 59 | 60 | # allows to define return v.x() and v.x() = 5 61 | def get_set_coord(self, coord_idx, value = False): 62 | if value: 63 | self.coords[coord_idx] = value 64 | return self.coords[coord_idx] 65 | 66 | # magnitude of the vector 67 | def magnitude(self): 68 | s = 0 69 | for c in self.coords: 70 | s += c**2 71 | return math.sqrt(s) 72 | 73 | # helper function for checking the dimensions 74 | def check_dimensions(self, v2): 75 | if self.dimension != v2.dimension: 76 | raise VecExcept("Vector: wrong dimensions") 77 | 78 | def check_class(self, v2): 79 | return self.__class__ == v2.__class__ 80 | 81 | # normalize to 1 82 | def normalize(self): 83 | m = self.magnitude() 84 | if m == 0: VecExcept("Vector: module is 0") 85 | for i in range(self.dimension): 86 | self.coords[i] /= m 87 | return self 88 | 89 | # angle from a axis 90 | def angle(self, v2): 91 | m = self.magnitude() * v2.magnitude() 92 | if m != 0: 93 | cos_theta = (self.dot(v2) ) / m 94 | return math.acos(cos_theta) 95 | else: 96 | VecExcept("Vector: Zero Division Error") 97 | 98 | # dot product 99 | def dot(self, v2): 100 | self.check_dimensions(v2) 101 | dot_p = 0 102 | for i in range(self.dimension): 103 | dot_p += self.coords[i] * v2[i] 104 | return dot_p 105 | 106 | # operator + (elementwise sum) 107 | def __add__(self, v2): 108 | self.check_dimensions(v2) 109 | 110 | new_coords = [0 for i in range(self.dimension)] 111 | 112 | for i in range(self.dimension): 113 | new_coords[i] = self.coords[i] + v2[i] 114 | 115 | return self.__class__(new_coords) 116 | 117 | # operator - (elementwise subtraction) 118 | def __sub__(self, v2): 119 | self.check_dimensions(v2) 120 | new_coords = [0 for i in range(self.dimension)] 121 | for i in range(self.dimension): 122 | new_coords[i] = self.coords[i] - v2[i] 123 | return self.__class__(new_coords) 124 | 125 | # operator * (elementwise multiplication with a constant) 126 | def __mul__(self, k): 127 | # if k is a vector do the dot product 128 | new_coords = [0 for i in range(self.dimension)] 129 | for i in range(self.dimension): 130 | new_coords[i] = self.coords[i] * k 131 | return self.__class__(new_coords) 132 | 133 | # operator / (elementwise division) 134 | def __truediv__(self, d): 135 | if d == 0: raise VecExcept("Vector: Zero Division Error") 136 | new_coords = [0 for i in range(self.dimension)] 137 | for i in range(self.dimension): 138 | new_coords[i] = self.coords[i] / d 139 | return self.__class__(new_coords) 140 | 141 | # operator == (check exactly if the vectors are the same) 142 | def __eq__(self, v2): 143 | self.check_dimensions(v2) 144 | for i in range(self.dimension): 145 | if self.coords[i] != v2[i]: 146 | return False 147 | return True 148 | 149 | # if the vector are close enough they are considered the same 150 | def is_close(self, v2): 151 | self.check_dimensions(v2) 152 | 153 | for i in range(self.dimension): 154 | if abs(self.coords[i] - v2[i]) > epsilon: 155 | return False 156 | return True 157 | 158 | # representation to string 159 | def __str__(self): 160 | s = "(" 161 | for c in self.coords: 162 | s += "{:.5g}".format(c) + ":" 163 | s = s[:-1] + ")" 164 | return s 165 | 166 | def as_bytes(self, bf): 167 | bf.write("I", self.dimension) 168 | for c in self.coords: 169 | bf.write("d", c) 170 | 171 | def interpret_bytes(self, bf): 172 | self.dimension = bf.read('I') 173 | 174 | self.coords = [0 for i in range(self.dimension)] 175 | for i in range(self.dimension): 176 | self.coords[i] = bf.read('d') 177 | 178 | def write_file(self, filename): 179 | with open(filename, "wb") as f: 180 | # save dimenstions 181 | bf = bfh.BinaryFile(f) 182 | self.as_bytes(bf) 183 | 184 | def read_file(self, filename): 185 | with open(filename, "rb") as f: 186 | bf = bfh.BinaryFile(f) 187 | self.interpret_bytes(bf) 188 | #============================================================================== 189 | # 2d vector 190 | #============================================================================== 191 | 192 | class V2(Vector): 193 | 194 | def __init__(self, x, y = None): 195 | 196 | if y is None and type(x) is list: 197 | super().__init__(2, x) 198 | elif y is None and type(x) is str: 199 | super().__init__(x) 200 | else: 201 | super().__init__(2) 202 | self.coords[0] = x 203 | self.coords[1] = y 204 | 205 | def x(self, c = False): 206 | return self.get_set_coord(0, c) 207 | 208 | def y(self, c = False): 209 | return self.get_set_coord(1, c) 210 | 211 | #============================================================================== 212 | # 3d vector 213 | #============================================================================== 214 | 215 | class V3(Vector): 216 | 217 | def __init__(self, x, y = None, z = None): 218 | 219 | if y is None and z is None and type(x) is list: 220 | super().__init__(3, x) 221 | elif y is None and type(x) is str: 222 | super().__init__(x) 223 | else: 224 | super().__init__(3) 225 | self.coords[0] = x 226 | self.coords[1] = y 227 | self.coords[2] = z 228 | 229 | def x(self, c = False): 230 | return self.get_set_coord(0, c) 231 | 232 | def y(self, c = False): 233 | return self.get_set_coord(1, c) 234 | 235 | def z(self, c = False): 236 | return self.get_set_coord(2, c) 237 | 238 | # cross product between 2 vectors 239 | def cross(self, v2): 240 | x = self[1] * v2[2] - self[2] * v2[1] 241 | y = self[2] * v2[0] - self[0] * v2[2] 242 | z = self[0] * v2[1] - self[1] * v2[0] 243 | return V3(x,y,z) 244 | 245 | #============================================================================== 246 | # 4d vector 247 | #============================================================================== 248 | 249 | class V4(Vector): 250 | 251 | def __init__(self, x, y = None, z = None, w = None): 252 | if type(x) is list and y is None and z is None and w is None: 253 | super().__init__(4, x) 254 | elif y is None and type(x) is str: 255 | super().__init__(x) 256 | else: 257 | super().__init__(4) 258 | self.coords[0] = x 259 | self.coords[1] = y 260 | self.coords[2] = z 261 | self.coords[3] = w 262 | 263 | def x(self, c = False): 264 | return self.get_set_coord(0, c) 265 | 266 | def y(self, c = False): 267 | return self.get_set_coord(1, c) 268 | 269 | def z(self, c = False): 270 | return self.get_set_coord(2, c) 271 | 272 | def w(self, c = False): 273 | return self.get_set_coord(3, c) 274 | 275 | def cross(self, v, w): 276 | A = (v[0] * w[1])-(v[1] * w[0]) 277 | B = (v[0] * w[2])-(v[2] * w[0]) 278 | C = (v[0] * w[3])-(v[3] * w[0]) 279 | D = (v[1] * w[1])-(v[2] * w[1]) 280 | E = (v[1] * w[3])-(v[3] * w[1]) 281 | F = (v[2] * w[3])-(v[3] * w[2]) 282 | 283 | x = (self[1] * F)-(self[2] * E)+(self[3] * D) 284 | y = -(self[0] * F)+(self[2] * C)-(self[3] * B) 285 | z = (self[0] * E)-(self[1] * C)+(self[3] * A) 286 | w = -(self[0] * D)+(self[1] * B)-(self[2] * A) 287 | 288 | return V4(x, y, z, w) 289 | 290 | 291 | 292 | if __name__ == "__main__": 293 | # print("vector math utilites") 294 | # 295 | # print("------- 2d -------") 296 | # 297 | # va = V2(0, 1) 298 | # print("va:", va) 299 | # 300 | # vb = V2(1, 0) 301 | # print("vb:", vb) 302 | # 303 | # vc = va + vb 304 | # print("sum:", vc) 305 | # 306 | # print("------- 3d -------") 307 | # 308 | # va = V3(0, 0, 1) 309 | # print("va:", va) 310 | # print("angle to x axis:", math.degrees(va.angle(V3(1, 0, 0)))) 311 | # 312 | # vb = V3(1, 0, 1) 313 | # print("vb:", vb) 314 | # 315 | # vc = (va + vb) / 2 316 | # print("mean:", vc) 317 | # 318 | # print("mag:", vc.magnitude()) 319 | # print("dot:", vc.dot(va)) 320 | # 321 | # print("cross:", vc.cross(va)) 322 | # 323 | # 324 | # v2 = V2(1, 0) 325 | # v3 = V3(1, 0, 0) 326 | # 327 | # try: 328 | # v = v2 + v3 329 | # except VecExcept as e: 330 | # print(e) 331 | # 332 | # print("------- 4d -------") 333 | # 334 | # a = V4(4, 1, 2, 3) 335 | # b = V4(3, 2, 1, 1) 336 | # c = V4(1, 2, 3, 2) 337 | # 338 | # print("cross 4:", a.cross(b, c).normalize()) 339 | 340 | v4 = V4(1.2, 1.5, 1.6, 2042104.2) 341 | 342 | print("--- write ---") 343 | v4.write_file("./test_vector.vec") 344 | 345 | print("--- read ---") 346 | v4read = V4("./test_vector.vec") 347 | print(v4read) 348 | 349 | 350 | 351 | 352 | -------------------------------------------------------------------------------- /src/visu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 23 15:33:29 2018 4 | 5 | @author: Mauro 6 | """ 7 | 8 | #============================================================================== 9 | # Drawing classes 10 | #============================================================================== 11 | 12 | from tkinter import Frame, Canvas, Label 13 | import vec, prj, poly, qua, rot4 14 | import copy 15 | import math 16 | 17 | # The drawing functions are the ones taking more time 18 | # One way to reduce the drawing burden is to reduce the drawed objects 19 | 20 | # optimization 1 21 | # dont draw overlapping edges and vertex 22 | # in the flat projection there is a huge ammount of overlapping vertexes and 23 | # edges, this filtering assumes that scanning the polygon array for 24 | # equal x and y is less expensive then drawing them 25 | 26 | # optimization 2 27 | # draw only new stuff, construct a drawn primitives buffer that checks if the 28 | # edge was already drawn. The edges are queried and if they are present in the 29 | # buffer, they get a check in flag 30 | # if they are not present, they dont have the check in flag so they can be 31 | # easly deleted 32 | 33 | #============================================================================== 34 | # Class Edge 35 | # little utility to control the drawn edge 36 | #============================================================================== 37 | 38 | class Edge: 39 | 40 | def __init__(self, v1, v2, e, color): 41 | # start and end point 42 | self.v1 = v1 43 | self.v2 = v2 44 | 45 | # relationship with the vertexes 46 | self.e = e 47 | 48 | # color 49 | self.c = color 50 | 51 | # canvas item number, needed for deletion 52 | self.item_n = None 53 | 54 | # vertex item numbers 55 | self.v1_item_n = None 56 | self.v2_item_n = None 57 | 58 | # if true, means someone wants to draw the vector. 59 | # if False, nobody wants the vector so it can be safely removed 60 | self.checked_in = False 61 | 62 | #============================================================================== 63 | # class Drawn Edges 64 | # little utilites to control the edge alredy drawn on the screen 65 | #============================================================================== 66 | 67 | class DrawnEdges: 68 | 69 | def __init__(self): 70 | self.drawn = [] 71 | 72 | # add one edge to the list 73 | def add_edge(self, v1, v2, inpe, color): 74 | edge_present = False 75 | 76 | for i, e in enumerate(self.drawn): 77 | # find the closest possible edge to the one inputed 78 | # if tehre is one already in the list, then mark it 79 | # as already drawn 80 | if e.v1.is_close(v1) and e.v2.is_close(v2): 81 | edge_present = True 82 | edge_i = i 83 | break 84 | 85 | # if the edge is already present check it in 86 | if edge_present: 87 | self.drawn[edge_i].checked_in = True 88 | # if the edge is not present, pipe it to draw it 89 | else: 90 | self.drawn.append(Edge(v1, v2, inpe, color)) 91 | 92 | def reset_check_in(self): 93 | for i in range(len(self.drawn)): 94 | self.drawn[i].checked_in = False 95 | 96 | #============================================================================== 97 | # class Area visualization 98 | # manages all the drawing functions 99 | #============================================================================== 100 | 101 | class VisuArea: 102 | 103 | def __init__(self, parent_frame, project_method, area_size, title = ""): 104 | 105 | # parent frame 106 | self.fP = Frame(parent_frame) 107 | 108 | # title if present 109 | if title: 110 | label = Label(self.fP, text=title) 111 | label.pack() 112 | 113 | # how big in pixels is the area 114 | self.cw = area_size[0] 115 | self.ch = area_size[1] 116 | 117 | # how big is the area in custom units 118 | # self.area_w / self.cw = 1 unit 119 | self.area_w = 5 120 | self.area_h = 5 121 | 122 | # where the center is 123 | self.c_center_w = self.cw / 2 124 | self.c_center_h = self.ch / 2 125 | 126 | # the canvas where drawing happens 127 | self.canvas = Canvas(self.fP, width = self.cw, height = self.ch) 128 | self.canvas.pack() 129 | 130 | # projection method, can be perspective or flat 131 | self.project_method = project_method 132 | 133 | # the already drawn edges class 134 | self.drawn_edges = DrawnEdges() 135 | 136 | # text drawn on the canvas 137 | self.text_items = [] 138 | 139 | # text utilieties, add and clear text 140 | def add_text(self, text): 141 | item_n = self.canvas.create_text([self.cw / 2., self.ch / 2.], text=text, font=("Fixedsys", 26), fill="lightgreen", justify="center") 142 | self.text_items.append(item_n) 143 | 144 | def clear_text(self): 145 | for i in self.text_items: 146 | self.canvas.delete(i) 147 | 148 | # converts the units and adjust for the y axis inversion 149 | # 0,0 is now bottom left corner 150 | def convert_to_canvas_coord(self, x, y): 151 | h = self.ch - (self.ch / self.area_h) * y - self.c_center_h 152 | w = (self.cw / self.area_w) * x + self.c_center_w 153 | return vec.V2(w, h) 154 | 155 | # draw a square centered to the point 156 | def draw_point(self,v, cvsize, kwargs): 157 | v = self.convert_to_canvas_coord(v.x(), v.y()) 158 | sizev = vec.V2(cvsize, cvsize) 159 | 160 | lp = v - sizev / 2 # low point 161 | hp = v + sizev / 2 # high point 162 | 163 | return self.canvas.create_rectangle(lp.x(), lp.y(), hp.x(), hp.y(), **kwargs) 164 | 165 | # draw a line 166 | def draw_edge(self, start, end, kwargs): 167 | vx = self.convert_to_canvas_coord(start.x(), start.y()) 168 | vy = self.convert_to_canvas_coord(end.x(), end.y()) 169 | 170 | return self.canvas.create_line(vx.x(), vx.y(), vy.x(), vy.y(), **kwargs) 171 | 172 | # draw an entire 2d polygon 173 | def draw_poly(self, poly2, draw_vert = True): 174 | 175 | # select which edges needs to be drawn 176 | for e in poly2.e_list: 177 | v1 = poly2.v_list[ e[0] ] 178 | v2 = poly2.v_list[ e[1] ] 179 | self.drawn_edges.add_edge(v1, v2, e, poly2.color) 180 | 181 | # this should draw only the edges that are selected 182 | for i, edge in enumerate(self.drawn_edges.drawn): 183 | if edge.item_n == None: 184 | # draws the edge 185 | inum = self.draw_edge(edge.v1, edge.v2, kwargs={"fill" : edge.c}) 186 | self.drawn_edges.drawn[i].item_n = inum 187 | 188 | # draws vertexes 189 | if draw_vert: 190 | inum = self.draw_point(poly2.v_list[edge.e[0]], 3.5, kwargs={"fill" : edge.c}) 191 | self.drawn_edges.drawn[i].v1_item_n = inum 192 | 193 | inum = self.draw_point(poly2.v_list[edge.e[1]], 3.5, kwargs={"fill" : edge.c}) 194 | self.drawn_edges.drawn[i].v2_item_n = inum 195 | 196 | # check-in that the primitive has been drawn 197 | self.drawn_edges.drawn[i].checked_in = True 198 | 199 | # removes edges and vertexes not needed anymore 200 | def clear_area(self): 201 | # create a new list 202 | tmp_drawn = [] 203 | 204 | # add the edges to keep and remove the one that are not needed anymore 205 | for e in self.drawn_edges.drawn: 206 | if e.checked_in == False: 207 | self.canvas.delete(e.item_n) 208 | if e.v1_item_n: 209 | self.canvas.delete(e.v1_item_n) 210 | self.canvas.delete(e.v2_item_n) 211 | else: 212 | tmp_drawn.append(e) 213 | 214 | # the new filtered list becomes the the list 215 | self.drawn_edges.drawn = tmp_drawn 216 | self.drawn_edges.reset_check_in() 217 | 218 | # transform a 4d polygon to a 2d one 219 | def project4(self, poly): 220 | p2 = self.project_method.project(poly) 221 | return p2 222 | 223 | # draw all the polygons in a list 224 | def draw_plist(self, p_list): 225 | for p in p_list: 226 | p2 = self.project4(p) 227 | self.draw_poly(p2, True) 228 | 229 | #============================================================================== 230 | # Perspective projection 231 | #============================================================================== 232 | 233 | class ProjectCam: 234 | 235 | def __init__(self): 236 | # 3d cam 237 | self.cam3 = prj.Cam3() 238 | self.cam3.change_position(vec.V3(2.5, 2.5, 2.5)) 239 | 240 | # 4d cam 241 | self.cam4 = prj.Cam4() 242 | self.cam4.change_position(vec.V4(15, 0.1, 0.1, 0.1)) 243 | 244 | # construct the index to axis mapping for the 3d-rotations 245 | self.idx_to_axis = {} 246 | for i in range(3): 247 | axis = [0 for k in range(3)] 248 | axis[i] = 1 249 | self.idx_to_axis[i] = vec.V3(axis) 250 | 251 | # projects 4d to 2d 252 | def project(self, poly4): 253 | # copy values like edges and colors 254 | polygon = copy.deepcopy(poly4) 255 | 256 | # calculate new vertexes 257 | for i in range(len(polygon.v_list)): 258 | p3 = self.cam4.prj(polygon.v_list[i]) 259 | p2 = self.cam3.prj(p3) 260 | polygon.v_list[i] = p2 261 | return polygon 262 | 263 | # rotate the camera in the 3-space 264 | def rotate3(self, angles): 265 | # angles is a list of angles around the main axes 266 | # angles = [angle, angle, angle] 267 | for i in range(3): 268 | if angles[i] != 0: 269 | angle = math.radians(angles[i]) 270 | axis = self.idx_to_axis[i] 271 | v = qua.rot_around_axis(self.cam3.From, axis, angle) 272 | self.cam3.change_position(v) 273 | 274 | # rotate camera in the 4-space 275 | def rotate4(self, angles): 276 | 277 | if angles[0] != 0: 278 | angle = math.radians(angles[0]) 279 | rotv = rot4.rot_alpha(self.cam4.From, angle) 280 | 281 | if angles[1] != 0: 282 | angle = math.radians(angles[1]) 283 | rotv = rot4.rot_beta(self.cam4.From, angle) 284 | 285 | if angles[2] != 0: 286 | angle = math.radians(angles[2]) 287 | rotv = rot4.rot_gamma(self.cam4.From, angle) 288 | 289 | if angles[3] != 0: 290 | angle = math.radians(angles[3]) 291 | rotv = rot4.rot_delta(self.cam4.From, angle) 292 | 293 | if angles[4] != 0: 294 | angle = math.radians(angles[4]) 295 | rotv = rot4.rot_rho(self.cam4.From, angle) 296 | 297 | if angles[5] != 0: 298 | angle = math.radians(angles[5]) 299 | rotv = rot4.rot_epsilon(self.cam4.From, angle) 300 | 301 | self.cam4.change_position(rotv) 302 | 303 | 304 | #============================================================================== 305 | # Flat projection 306 | #============================================================================== 307 | 308 | class ProjectFlat: 309 | 310 | def __init__(self, axes): 311 | # axes is a string of 2 letters corresponding to the axis to project 312 | 313 | # construct a axis to index mapping 314 | self.ax2coord = {} 315 | ax_names = "xyzw" 316 | for i, c in enumerate(ax_names): 317 | self.ax2coord[c] = i 318 | 319 | # zoom level 320 | self.zoom = 0.3 321 | 322 | # i and j are then used to gather the coordinates 323 | # i and j must be between 0 and 3 or the above specified axes 324 | self.i = self.ax2coord[axes[0]] 325 | self.j = self.ax2coord[axes[1]] 326 | 327 | # flatten the points according to the above description 328 | def project(self, poly4): 329 | # copy the polygon 330 | poly2 = poly.Polygon() 331 | poly2.color = poly4.color 332 | 333 | # extract the 2d information 334 | for v in poly4.v_list: 335 | # is specular inverted to match the the keyboard directions 336 | x = v[self.i] * self.zoom * -1 337 | y = v[self.j] * self.zoom 338 | poly2.v_list.append(vec.V2(x, y)) 339 | 340 | # index / vertex class 341 | class EIndex: 342 | def __init__(self, v1, v2, e): 343 | self.v1 = v1 344 | self.v2 = v2 345 | self.e = e 346 | 347 | preserved = [] 348 | 349 | # gather the edges of the 4d polygon 350 | for e in poly4.e_list: 351 | # gather the vertexes of the projected polygon 352 | v1 = poly2.v_list[e[0]] 353 | v2 = poly2.v_list[e[1]] 354 | 355 | ei = EIndex(v1, v2, e) 356 | 357 | # scan the edge vertexes to find overlapping edges 358 | # based on difference norm 359 | unique = True 360 | for pei in preserved: 361 | if ei.v1.is_close(pei.v1) and ei.v2.is_close(pei.v2): 362 | unique = False 363 | break 364 | 365 | if unique: 366 | preserved.append(ei) 367 | 368 | # construct the edge list based on the is close filtering above 369 | # this reduces the number of edges to draw by 4 on average 370 | poly2.e_list = [ei.e for ei in preserved] 371 | 372 | return poly2 -------------------------------------------------------------------------------- /tests/14_cubes_high_score.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/14_cubes_high_score.sk4 -------------------------------------------------------------------------------- /tests/dont_reset.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/dont_reset.sk4 -------------------------------------------------------------------------------- /tests/frame_test.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/frame_test.sk4 -------------------------------------------------------------------------------- /tests/game_frame_test.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/game_frame_test.sk4 -------------------------------------------------------------------------------- /tests/ho_perso_ancora.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/ho_perso_ancora.sk4 -------------------------------------------------------------------------------- /tests/how_to_lose: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/how_to_lose -------------------------------------------------------------------------------- /tests/new_game_after_yea.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/new_game_after_yea.sk4 -------------------------------------------------------------------------------- /tests/new_game_yea.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/new_game_yea.sk4 -------------------------------------------------------------------------------- /tests/omgis8mb.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/omgis8mb.sk4 -------------------------------------------------------------------------------- /tests/test_new_replay.sk4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Pella86/Snake4d/cdf3773b42efc888affa33dd22ebe56a48f6d979/tests/test_new_replay.sk4 --------------------------------------------------------------------------------