import tkinter as tk import string import random import threading import time import wave import os import math import heapq class stdrend: def __init__(self, size, cam): self._size = size self._grid = {} self._win = tk.Tk() tkeys = list(string.ascii_letters) self._keys = {} for i in tkeys: self._keys[i] = False self._win.bind("", self.keypupd) self._win.bind("", self.keydupd) for y in range(size[1]): for x in range(size[0]): temp = tk.Label(text=" ") temp.grid(row=y, column=x) self._win.update() self._grid[f"{x}:{y}"] = temp def keypupd(self, event): event = event.char if event in self._keys: self._keys[event] = True def keydupd(self, event): event = event.char if event in self._keys: self._keys[event] = False def getkeys(self): return self._keys def coltohex(self, target): colors = [] target = [target.r, target.g, target.b] for i in target: colors.append(("0"*(2-len(hex(i)[2:])))+hex(i)[2:]) out = "" for i in colors: out = out + i return "#"+out def update(self): self._win.update() def pix(self, x, y, text, bcolor, fcolor): if f"{x}:{y}" in self._grid: self._grid[f"{x}:{y}"].config(text=text, bg=self.coltohex(bcolor), fg=self.coltohex(fcolor)) class color3: def __init__(self, r=0, g=0, b=0): self.r = r self.g = g self.b = b self._type = "color3" def __add__(self, v): temp = color3(self.r+v.r, self.g+v.g, self.b+v.b) temp.r = temp.r%255 temp.g = temp.g%255 temp.b = temp.b%255 return temp def __sub__(self, v): temp = color3(self.r-v.r, self.g-v.g, self.b-v.b) temp.r = temp.r%255 temp.g = temp.g%255 temp.b = temp.b%255 return temp def __mul__(self, v): temp = color3(self.r*v.r, self.g*v.g, self.b*v.b) temp.r = temp.r%255 temp.g = temp.g%255 temp.b = temp.b%255 return temp def __iadd__(self, v): temp = color3(self.r+v.r, self.g+v.g, self.b+v.b) temp.r = temp.r%255 temp.g = temp.g%255 temp.b = temp.b%255 return temp def __isub__(self, v): temp = color3(self.r-v.r, self.g-v.g, self.b-v.b) temp.r = temp.r%255 temp.g = temp.g%255 temp.b = temp.b%255 return temp def __imul__(self, v): temp = color3(self.r*v.r, self.g*v.g, self.b*v.b) temp.r = temp.r%255 temp.g = temp.g%255 temp.b = temp.b%255 return temp class vector2: def __init__(self, x=0, y=0): self.x = x self.y = y self._type = "vector2" def _magnitude(self): return abs(self.x+self.y) def __add__(self, v): return vector2(self.x+v.x, self.y+v.y) def __sub__(self, v): return vector2(self.x-v.x, self.y-v.y) def __mul__(self, v): return vector2(self.x*v.x, self.y*v.y) def __iadd__(self, v): return vector2(self.x+v.x, self.y+v.y) def __isub__(self, v): return vector2(self.x-v.x, self.y-v.y) def __imul__(self, v): return vector2(self.x*v.x, self.y*v.y) class NULL: def __init__(self): return None class enum: def __init__(self, sel): self._sel = dict(sel) for i in self._sel: setattr(self, i, self._sel[i]) def getposssel(self): return list(self._sel.keys()) def loadsound(path): with wave.open(path) as fd: frames = fd.readframes(1000000000) return frames cammode = enum({"editable": 0, "follow": 1, "freecam": 2}) class event: def __init__(self): self._attached = [] def execute(self): threads = [] for i in self._attached: temp = threading.Thread(target=i) temp.start() threads.append(temp) return threads def attach(self, target): self._attached.append(target) class obj: def __init__(self): self.position = vector2() self.char = " " self.ID = 0 self.gravity = 0 self.acceleration = vector2() self.velocity = vector2() self.friction = 0 self.collide = True self.anchored = False self.bcolor = color3(255, 255, 255) self.fcolor = color3() self._touching = event() class model: def __init__(self, objects): self._objects = objects self.ID = 0 class camera(obj): def __init__(self): super().__init__() self.mode = cammode.editable self.subject = None self.collide = False self.touch = False self.char = " " 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): self._objects = objects def moveby(self, pos): for i in self._objects: i.position += pos class game: def __init__(self, size=[10, 10], renderer=stdrend, sounddir=""): if renderer == None: raise TypeError("Renderer class needed!") self.sounds = {} self.currentsounds = [] for i in os.listdir(sounddir): if not "." in i: continue if i.split(".")[1] != "wav": continue self.sounds[i.split(".")[0]] = loadsound(sounddir+"/"+i) self._size = size self._objects = {} self._SIDS = {} self._SEQSIDS = {} self.camera = camera() self._renderer = renderer(size, self.camera) self._threads = [] def getobjbySID(self, target): return self._objects[self._SIDS[target]] def getobjseqbySID(self, target): out = [] for i in self._SEQSIDS[target]: out.append(self._objects[i]) return seqobj(out) def isdown(self, key): temp = self._renderer.getkeys() if key in temp: return temp[key] def collidingpos(self, pos, ignore): out = [] for i in self._objects: i = self._objects[i] if i.ID in ignore: continue if math.dist([i.position.x, i.position.y], [pos.x, pos.y]) < 1 and i.collide == True: out.append(i) return out def colliding(self, target): out = [] if target.collide == False: return [] out = self.collidingpos(target.position, [target.ID,]) return out def handlecollision(self, target: obj, target2: obj): if target2.anchored == True: target.velocity = vector2() else: xtrue = False ytrue = False if target.velocity.x > 0: xtrue = True if target.velocity.y > 0: ytrue = True half = vector2(abs(target.velocity.x)/2, abs(target.velocity.y)/2) if not xtrue: half = vector2(-abs(half.x), half.y) if not ytrue: half = vector2(half.x, -abs(half.y)) target.velocity = vector2(half.x, half.y) target2.velocity = half self._threads.extend(target._touching.execute()) self._threads.extend(target2._touching.execute()) def calcphysobj(self, target: obj): opos = vector2(target.position.x, target.position.y) collide = False if target.anchored == True: return [opos, collide] if target.collide == True: colliding = self.collidingpos(target.position+target.velocity, [target.ID,]) for i in colliding: target._touching.execute() i._touching.execute() self.handlecollision(target, i) collide = True target.position += target.velocity target.velocity += vector2(0, target.gravity) target.velocity += target.acceleration temp = 2 if target.friction != 0: temp = 2 / target.friction else: temp = 1 x = target.velocity.x y = target.velocity.y if x != 0: x = x/temp if y != 0: y = y/temp target.velocity = vector2(x, y) return [opos, collide] def calcphysmodel(self, target: model): for i in target._objects: self.calcphysobj(i) def addobj(self, obj): id = "" for i in range(256): id = id + random.choice(list(string.ascii_letters)) obj.ID = id self._objects[id] = obj def removeobj(self, obj): self._objects.pop(obj.ID) obj.ID = NULL() def removeobjbyid(self, id): self._objects.pop(id).ID = NULL() def between(self, min, max, target): if min < target < max: return True return False def getobjbyid(self, id): return self._objects[id] def render(self): tochange = [] for x in range(self._size[0]): 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 if not self.between(-1, self._size[0], pos.x) or not self.between(-1, self._size[1], pos.y): continue# pos = vector2(round(pos.x), round(pos.y)) self._renderer.pix(pos.x, pos.y, i.char, i.bcolor, i.fcolor) if (pos.x, pos.y) in tochange: tochange.remove((pos.x, pos.y)) if isinstance(i, model): for tobj in i._objects: pos = tobj.position + self.camera.position if not self.between(-1, self._size[0], pos.x) or not self.between(-1, self._size[1], pos.y): continue# pos = vector2(round(pos.x), round(pos.y)) self._renderer.pix(pos.x, pos.y, tobj.char, tobj.bcolor, tobj.fcolor) if (pos.x, pos.y) in tochange: tochange.remove((pos.x, pos.y)) for i in tochange: self._renderer.pix(i[0], i[1], " ", color3(255, 255, 255), color3(255, 255, 255)) self._renderer.update() def startscript(self, target): temp = threading.Thread(target=target) temp.start() self._threads.append(temp) def stopscripts(self): for i in self._threads: i.join(.0) def stopsounds(self): 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() object.char = "#" object.anchored = False object.position = vector2(5, 5) object.gravity = 1 floor = obj() floor.char = "#" floor.anchored = True floor.position = vector2(5, 9) floor.gravity = 0 floor.bcolor = color3(255, 255, 255) testgame.addobj(object) testgame.addobj(floor) testgame.render() print(object.ID) while True: testgame.calcphysobj(object) testgame.calcphysobj(floor) testgame.render() time.sleep(0)