From 7c8e354103e8f39674c621957c143d8765ff81b4 Mon Sep 17 00:00:00 2001 From: justuswolff Date: Sat, 15 Jun 2024 12:51:21 +0200 Subject: [PATCH] pathfinding --- hashengine.py | 91 ++++++++++++++++++++++++++++++++++++++- main.py | 23 +++++++--- scriptingdocumentation.md | 7 +++ testlandscapes/path.txt | 4 ++ tests/PATHFINDTEST | 1 + 5 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 testlandscapes/path.txt create mode 100644 tests/PATHFINDTEST diff --git a/hashengine.py b/hashengine.py index c9d6d86..2ea1171 100644 --- a/hashengine.py +++ b/hashengine.py @@ -6,6 +6,7 @@ import time import wave import os import math +import heapq class stdrend: def __init__(self, size, cam): @@ -151,7 +152,7 @@ def loadsound(path): frames = fd.readframes(1000000000) return frames -cammode = enum({"editable": 0, "follow": 1}) +cammode = enum({"editable": 0, "follow": 1, "freecam": 2}) class event: def __init__(self): @@ -197,9 +198,20 @@ class camera(obj): self.touch = False self.char = " " - def update(self): + def update(self, game): + """ + if char == "w": preview.camera.position += hashengine.vector2(y=1) + if char == "a": preview.camera.position += hashengine.vector2(x=1) + if char == "s": preview.camera.position -= hashengine.vector2(y=1) + if char == "d": preview.camera.position -= hashengine.vector2(x=1) + """ if self.mode == cammode.follow and self.subject: self.position = self.subject.position + if self.mode == cammode.freecam: + if game.isdown("w"): self.position += vector2(y=1) + if game.isdown("s"): self.position -= vector2(y=1) + if game.isdown("a"): self.position += vector2(x=1) + if game.isdown("d"): self.position -= vector2(x=1) class seqobj: def __init__(self, objects): @@ -334,6 +346,7 @@ class game: for y in range(self._size[1]): tochange.append((x, y)) #self._renderer.pix(x, y, " ", color3(255, 255, 255), color3(255, 255, 255)) + self.camera.update(self) for i in list(self._objects.values()): if isinstance(i, obj): pos = i.position + self.camera.position @@ -365,6 +378,80 @@ class game: for i in self.currentsounds: i.stop() +class pathfinding: + def __init__(self, grid_size, objects, game): + self.grid_size = grid_size + self.objects = objects + self._calculate_bounds() + self.grid = [[1 for _ in range(self.grid_width)] for _ in range(self.grid_height)] + self.game = game + self._init_grid() + + def _init_grid(self): + for obj in self.objects: + if obj.collide: + x, y = int(obj.position.x) + self.offset_x, int(obj.position.y) + self.offset_y + if 0 <= x < self.grid_width and 0 <= y < self.grid_height: + self.grid[y][x] = 0 + + def _calculate_bounds(self): + min_x, max_x = 0, self.grid_size[0] + min_y, max_y = 0, self.grid_size[1] + for obj in self.objects: + if obj.collide: + min_x = min(min_x, int(obj.position.x)) + max_x = max(max_x, int(obj.position.x)) + min_y = min(min_y, int(obj.position.y)) + max_y = max(max_y, int(obj.position.y)) + self.grid_width = max_x - min_x + 1 + self.grid_height = max_y - min_y + 1 + self.offset_x = -min_x + self.offset_y = -min_y + + def _heuristic(self, a, b): + return abs(a[0] - b[0]) + abs(a[1] - b[1]) + + def find_path(self, start, end): + self.objects = list(self.game._objects.values()) + self._calculate_bounds() + self._init_grid() + start = (int(start.x) + self.offset_x, int(start.y) + self.offset_y) + end = (int(end.x) + self.offset_x, int(end.y) + self.offset_y) + open_set = [] + heapq.heappush(open_set, (0, start)) + came_from = {} + g_score = {start: 0} + f_score = {start: self._heuristic(start, end)} + while open_set: + _, current = heapq.heappop(open_set) + if math.dist(end, current) <= 1: + path = [] + while current in came_from: + path.append(current) + current = came_from[current] + path.append(start) + path.reverse() + return [vector2(x - self.offset_x, y - self.offset_y) for x, y in path] + + neighbors = [ + (current[0] + dx, current[1] + dy) + for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)] + if 0 <= current[0] + dx < self.grid_width and 0 <= current[1] + dy < self.grid_height + ] + + for neighbor in neighbors: + if self.grid[neighbor[1]][neighbor[0]] == 0: + continue + tentative_g_score = g_score[current] + 1 + + if neighbor not in g_score or tentative_g_score < g_score[neighbor]: + came_from[neighbor] = current + g_score[neighbor] = tentative_g_score + f_score[neighbor] = tentative_g_score + self._heuristic(neighbor, end) + heapq.heappush(open_set, (f_score[neighbor], neighbor)) + + return [] + if __name__ == "__main__": testgame = game(sounddir="testsound") object = obj() diff --git a/main.py b/main.py index ab31185..86ceb68 100644 --- a/main.py +++ b/main.py @@ -476,10 +476,11 @@ def halatribute(event): setattr(gamedata[currentobj]["args"][parent], name, new) if "ID" in gamedata[currentobj]["args"]: temp = preview.getobjbyid(gamedata[currentobj]["args"]["ID"]) - setattr(temp, name, new) + setattr(getattr(temp, parent), name, new) atritree.delete(*atritree.get_children()) objtree.focus("") updselect(None) + preview.render() def updatepreviewcam(char): global cooldown @@ -707,9 +708,11 @@ def testing(): def APIGEN(): global APIPLUG + global maingame API = {"print": log, "HASHBASE": hashengine} API["HASHGAME"] = maingame API["SOUND"] = lambda data: gsound(data, maingame) + API["PATHFIND"] = hashengine.pathfinding(maingame._size, list(maingame._objects.values()), maingame) for i in APIPLUG: exec(i, globals()) temp = globals()["PLUGINAPIFUNC"]() @@ -1083,7 +1086,6 @@ def GUIinit(): menu.add_cascade(label=LH.string("langs"), menu=langmenu) for i in LH.getlangs(): langmenu.add_command(label=i, command=lambda i=i: selectlang(i)) - loadplugins() container.mainloop() # attribute changers @@ -1163,6 +1165,7 @@ def aposx(old): for i in str(tempvar): if not i in numbers: return old + if str(tempvar).split(".")[1] == "0": return int(tempvar) return tempvar def aposy(old): @@ -1190,6 +1193,7 @@ def aposy(old): for i in str(tempvar): if not i in numbers: return old + if str(tempvar).split(".")[1] == "0": return int(tempvar) return tempvar def execgame(gametree, shouldlog=True, fAPIPLUG=[], fRUNPLUG=[]): @@ -1208,15 +1212,22 @@ def execgame(gametree, shouldlog=True, fAPIPLUG=[], fRUNPLUG=[]): clog = shouldlog run() +def execcmd(target): + try: + exec(target, globals()) + except Exception as e: + return e + def cmd(): print(f"hashengine version: {version}") print("CMD") + autoexecute = ["loadplugins()"] + for i in autoexecute: + print(f"> {i}") + execcmd(i) while True: cmd = input("> ") - try: - exec(cmd, globals()) - except Exception as e: - print(e) + print(execcmd(cmd)) class rsound: def __init__(self): diff --git a/scriptingdocumentation.md b/scriptingdocumentation.md index febaefc..7005b97 100644 --- a/scriptingdocumentation.md +++ b/scriptingdocumentation.md @@ -70,3 +70,10 @@ Hashengine supports audio playback. As previously said, HASHBASE.loadsound(\]) or if youre using loadsound then SOUND(loadsound(\)). this will return a **SOUND** Class which can playback the sound. to play the sound use \.play(). to stop the sound use \.stop() and to yield until the sound is done playing sue \.wait() +## PATHFIND + +### Hashengine provides you with built-in pathfinding + +Hashengine has an built pathfinding system (A*). to use it, use PATHFIND.find_path(\, \). +it returns an list of vector2's representing every single waypoint. + diff --git a/testlandscapes/path.txt b/testlandscapes/path.txt new file mode 100644 index 0000000..431fc0b --- /dev/null +++ b/testlandscapes/path.txt @@ -0,0 +1,4 @@ +B # + # + # + A \ No newline at end of file diff --git a/tests/PATHFINDTEST b/tests/PATHFINDTEST new file mode 100644 index 0000000..f88c9b0 --- /dev/null +++ b/tests/PATHFINDTEST @@ -0,0 +1 @@ +[[{'id': 'obj', 'name': 'B', 'SID': 'TqykIpLtCKwtqAdSrksOjKtUmNXVMkXPEnNKVYAjnCbhDGwNbiefXdeJhMxBeNOTWfRCHZWOtdMxmHlcCDPdEXOzGmhuXTQyaVywRqwWOxdVioLSUAZkpivegeBalvtkgfVkIVlaJUGJLHLiOCdncxyNGkciReWUntWGjfQTuIeZSGwWtpakiQQgCZCFpkPUUWYlvLNADLvmCOzKwGQCIMnaVavcHYDEhJStHnzPlYIgASEBDMywrmZeUxtrOlO', 'args': {'anchored': True, 'char': 'B', 'collide': True, 'friction': 0, 'gravity': 0, 'acceleration': {'x': 0, 'y': 0, 'ARGID': 'vector2'}, 'bcolor': {'b': 255, 'g': 255, 'r': 255, 'ARGID': 'color3'}, 'fcolor': {'b': 0, 'g': 0, 'r': 0, 'ARGID': 'color3'}, 'position': {'x': 0, 'y': 0, 'ARGID': 'vector2'}, 'velocity': {'x': 0, 'y': 0, 'ARGID': 'vector2'}}}, {'id': 'obj', 'name': '#', 'SID': 'TZIeEulKFDdSyFercdukmdlaWqXJtxCNsYUJRehtpqWXtqNrCRIQppHJlLHYlxmOfzSJwjGOMFhOGWFRybutzbfWmxBuuThfZKRaeHqqqdocjkfPxfBVZsqZFTgKYQXcUXtXpXnkSUYZqGZGztgeuOJXyKwTJTLaGMtMfvXmmNXviOlnmBlTbdzgZQoJLTPJqTkmavOKepbCcrzcjDUuCZpfnOaTuehRnyPHFcXbiPIOFRmsqpWfXKwWzREsfyV', 'args': {'anchored': True, 'char': '#', 'collide': True, 'friction': 0, 'gravity': 0, 'acceleration': {'x': 0, 'y': 0, 'ARGID': 'vector2'}, 'bcolor': {'b': 255, 'g': 255, 'r': 255, 'ARGID': 'color3'}, 'fcolor': {'b': 0, 'g': 0, 'r': 0, 'ARGID': 'color3'}, 'position': {'x': 5, 'y': 0, 'ARGID': 'vector2'}, 'velocity': {'x': 0, 'y': 0, 'ARGID': 'vector2'}}}, {'id': 'obj', 'name': '#', 'SID': 'HtBTelOcoHVYzWzpvfwphPDTJCUQgZjmOXomRObTJkhMuefWHbCZPQWSeaGZOcbWXknUYaerTNDmsNOhwAOQZJpHxwrAwtBbyLWuQwggXUXKgWDyUzCNaMLeVaWDEIXRXdWVxmRObSotLnIUDfqByenIsHpitNcMWXCqGpLTzApWHKzhMfIoFoswtqrPydSOOqAWXWLyVhNlhIPjqTUqOyxhBtxJoouAZWfsLBriRwCYQJIsPmFUsWTEZLUzqJi', 'args': {'anchored': True, 'char': '#', 'collide': True, 'friction': 0, 'gravity': 0, 'acceleration': {'x': 0, 'y': 0, 'ARGID': 'vector2'}, 'bcolor': {'b': 255, 'g': 255, 'r': 255, 'ARGID': 'color3'}, 'fcolor': {'b': 0, 'g': 0, 'r': 0, 'ARGID': 'color3'}, 'position': {'x': 5, 'y': 1, 'ARGID': 'vector2'}, 'velocity': {'x': 0, 'y': 0, 'ARGID': 'vector2'}}}, {'id': 'obj', 'name': '#', 'SID': 'rpmRdCpAZzcbSvkgQKCOzzeUnFahtWUVmoEJuXVlVejBTMumOrRRLDLjnpuMkkoztSOaLTVGGEgYzTdAJJXcwfJPXemlENOQFlItDHRojMUmZAlPeCdawyaLqkhHPJPJRVUZCHQOJYlixucjpuqyhcorIUKnzxNYWdQSCIOyHHOIyHMAzZEQLrlwBsCgKAXwTMhNqNXGAEscABPlgvULVegLIbqdVRlQAiQTMbhArjOGxtJhBORsAIUDkrRrViz', 'args': {'anchored': True, 'char': '#', 'collide': True, 'friction': 0, 'gravity': 0, 'acceleration': {'x': 0, 'y': 0, 'ARGID': 'vector2'}, 'bcolor': {'b': 255, 'g': 255, 'r': 255, 'ARGID': 'color3'}, 'fcolor': {'b': 0, 'g': 0, 'r': 0, 'ARGID': 'color3'}, 'position': {'x': 5, 'y': 2, 'ARGID': 'vector2'}, 'velocity': {'x': 0, 'y': 0, 'ARGID': 'vector2'}}}, {'id': 'obj', 'name': 'A', 'SID': 'kZbEQPYcJFNelliPHswwvvDVeiYGLXYeYvAGHdmDmxdIAFqWlkAFPxNCBlVRXgofJIaBBQaOMUHcnAyFmsHaorUhFIlblXHSdLFsRtLWmkMpPnBmpHrJZhkNCmNEvaZdCtNWnbJfAWdaFOlfHSYNIbenaNQhLXUntneNdMIlpEgWrCVhaOimAcXRDtMwusYibgkCZIIPLnsHtXWcSjjskdzylorzBNCNOJwnLqFLkDLbbeiaoKcqhfdsdMwPNzn', 'args': {'anchored': True, 'char': 'A', 'collide': True, 'friction': 0, 'gravity': 0, 'acceleration': {'x': 0, 'y': 0, 'ARGID': 'vector2'}, 'bcolor': {'b': 255, 'g': 255, 'r': 255, 'ARGID': 'color3'}, 'fcolor': {'b': 0, 'g': 0, 'r': 0, 'ARGID': 'color3'}, 'position': {'x': -2, 'y': -2, 'ARGID': 'vector2'}, 'velocity': {'x': 0, 'y': 0, 'ARGID': 'vector2'}}}, {'id': 'script', 'name': 'Skript', 'SID': 'jjlcqqosdzDsDAjdUlAzeudsNiDEfggOTzGZZdhYPUtMzWPDEnsAgaOpiEeZofjivwPOkkLejUqHRJzIaUwdcNCkeuoEzRyMsLLxoIzzAJbWxlNIcmayhgZDTPAHAEKalgBYizLaxhXISElzdxcaJdoqfAgHJGDkRlvpPPNHoTnizOrVUNcnVpEncJUuOwBevnNRoHhptbJoKRHHsINkJXtkGxIrOVBObXGHlEbXQKyJKSisqvedYtazodZoXUJ', 'args': {'code': 'start = HASHGAME.getobjbySID("TqykIpLtCKwtqAdSrksOjKtUmNXVMkXPEnNKVYAjnCbhDGwNbiefXdeJhMxBeNOTWfRCHZWOtdMxmHlcCDPdEXOzGmhuXTQyaVywRqwWOxdVioLSUAZkpivegeBalvtkgfVkIVlaJUGJLHLiOCdncxyNGkciReWUntWGjfQTuIeZSGwWtpakiQQgCZCFpkPUUWYlvLNADLvmCOzKwGQCIMnaVavcHYDEhJStHnzPlYIgASEBDMywrmZeUxtrOlO")\nend = HASHGAME.getobjbySID("kZbEQPYcJFNelliPHswwvvDVeiYGLXYeYvAGHdmDmxdIAFqWlkAFPxNCBlVRXgofJIaBBQaOMUHcnAyFmsHaorUhFIlblXHSdLFsRtLWmkMpPnBmpHrJZhkNCmNEvaZdCtNWnbJfAWdaFOlfHSYNIbenaNQhLXUntneNdMIlpEgWrCVhaOimAcXRDtMwusYibgkCZIIPLnsHtXWcSjjskdzylorzBNCNOJwnLqFLkDLbbeiaoKcqhfdsdMwPNzn")\nHASHGAME.camera.mode = 2\npath = PATHFIND.find_path(start.position, end.position)\nfor i in path:\n\ttemp = HASHBASE.obj()\n\ttemp.position = i\n\ttemp.char = "-"\n\ttemp.anchored = True\n\tHASHGAME.addobj(temp)'}}], {'Erstellte Objekte': [0, 1, 2, 3, 4]}, 10, 10] \ No newline at end of file