Toggle Navigation
Hatchery
Eggs
Maze3D
Maze3D.py
Users
Badges
Login
Register
MCH2022 badge?
go to mch2022.badge.team
Maze3D.py
raw
Content
### Author: Leiden Tech ### Description: Leiden Tech ### Category: Games ### License: MIT ### Appname: Maze3D ### Built-in: no #TODO - this crashes for no apparent reason after playing for a while, or just letting it sit, I don't see the problem import badge import ugfx import machine import math import random import appglue fgcolor = ugfx.BLACK bgcolor = ugfx.WHITE font = "Roboto_Black22" grid = [] #size of maze - TODO can't be bigger than 4x5 otherwise fails with: # maximum recursion depth exceeded # have to figure a non-recursive way to generate the maze - sigh w = 3 h = 4 #size of screen screenX = 296 screenY = 128 #TODO size of wall block - should maybe be abs((screenX - viewX) / (w * 2 + 1)) for variable sized mazes but then viewX, viewY has to be hard-coded pixelW = 10 pixelH = 10 #size of view screen viewX = screenX - (pixelW * (w * 2 + 1) ) - 6 #200 viewY = screenY - 3 #125 #defines x,y compass and array of 3 points for the poly char object dirOffset = [[0, 1, "N", [[-1, 1], [0, -1], [1, 1]]], [-1, 0, "E", [[-1, -1], [1, 0], [-1, 1]]], [0, -1, "S", [[-1, -1], [0, 1], [1, -1]]], [1, 0, "W", [[1, -1], [-1, 0], [1, 1]]]] #character direction - posX,posY set after maze is built direction = 0 posX = 0 posY = 0 compass = dirOffset[direction][2] def Maze3D(): global posX, posY badge.init() badge.eink_init() ugfx.init() ugfx.input_init() ugfx.input_attach(ugfx.JOY_UP, lambda pressed: btn_up(pressed)) ugfx.input_attach(ugfx.JOY_DOWN, lambda pressed: btn_down(pressed)) ugfx.input_attach(ugfx.JOY_LEFT, lambda pressed: btn_left(pressed)) ugfx.input_attach(ugfx.JOY_RIGHT, lambda pressed: btn_right(pressed)) ugfx.input_attach(ugfx.BTN_SELECT, lambda pressed: btn_select(pressed)) ugfx.input_attach(ugfx.BTN_START, lambda pressed: btn_start(pressed)) ugfx.input_attach(ugfx.BTN_A, lambda pressed: btn_a(pressed)) ugfx.input_attach(ugfx.BTN_B, lambda pressed: btn_b(pressed)) #Default random seed doesn't seem to work - does it work for anyone? [year, month, mday, wday, hour, minute, second, microseconds] = machine.RTC().datetime() random.seed(int(microseconds)) clearScreen() makeMaze() clearStatus() posX = random.randrange(1, w * 2 - 1, 2) posY = random.randrange(1, h * 2 - 1, 2) grid[posX][posY][1] = 1 ugfx.flush() display() """ Start at a random cell. Mark the current cell as visited, and get a list of its neighbors. starting with a random neighbor and stepping through neighbors until a valid unvisited one is found: remove the wall between the current cell and that neighbor, and then recurse with that neighbor as the start point. when you hit the end of the recursion, start over with the next valid neighbor """ def makeMaze(): global grid """ sets up an array of WxH grid[x][y] = [wall,status] wall is True/False status is numeric 1) position of char 2) position of exit 3) position of monster 4) position of treasure 5) etc? """ #Set all positions in grid to wall grid = [[[True, 0] for i in range(h * 2 + 1)] for j in range(w * 2 + 1)] #Remove every other one to set up grid grid[x][y] = [wall,status] for x in range(1, w * 2 + 1, 2): ######### for y in range(1, h * 2 + 1, 2): # # # # # grid[x][y] = [False, 0] ######### #start at random cell and walk # # # # # startw = random.randrange(1, w * 2 - 1, 2) ######### starth = random.randrange(1, h * 2 - 1, 2) # # # # # walk(startw, starth, w * 2 - 1, h * 2 - 1) ######### def walk(x, y, ww, hh): global grid #mark current cell as visited grid[x][y][1] = 1 #get list of neighbors neighbors = [(x - 2, y), (x, y + 2), (x + 2, y), (x, y - 2)] #For each random neighbor #going to have to implement my own shuffle here, sigh neighbors = shuffle(neighbors) for (xx, yy) in neighbors: #skip if out of range if xx <= 0 or xx > ww or yy <= 0 or yy > hh: continue #Skip if already visited if grid[xx][yy][1] == 1: continue #if not previously visited, remove the connecting wall if xx == x: if grid[xx][yy][1] == 0: grid[xx][max(y, yy) - 1] = [False, 0] if yy == y: if grid[xx][yy][1] == 0: grid[max(x, xx) - 1][yy] = [False, 0] #recurse using neighbor as start point walk(xx, yy, ww, hh) def shuffle(target): #doesn't exist in micropython apparently, gotta roll my own length = len(target) secondary = -1 if length < 2: #what are you going to shuffle then moron? return for i in range(length): #maybe math.ceil(length/2)+1): primary = random.randint(0, length - 1) while secondary == primary: #no point in shuffling to itself secondary = random.randint(0, length - 1) tmp = target[primary] target[primary] = target[secondary] target[secondary] = tmp return target def clearStatus(): global grid for i in range(0, (w * 2 + 1)): for j in range(0, (h * 2 + 1)): grid[i][j][1] = 0 def drawCompass(): global compass #area is MUCH quicker than clear... hmm ugfx.area(0, 0, screenX, screenY, bgcolor) #compass - has to be set before drawView because that relies on this value compass = dirOffset[direction][2] ugfx.string(screenX - (pixelW * (w * 2 + 1)), 100, compass, font, fgcolor) #There is room for status messages here #ugfx.string(screenX - (pixelW * (w * 2 + 1)) + 20, 100, str(posX), font, fgcolor) #ugfx.string(screenX - (pixelW * (w * 2 + 1)) + 40, 100, str(posY), font, fgcolor) def drawMaze(matrix, startX, startY, fixed = None): charSize = 4 if fixed is None: fixed = direction #offset where to start drawing map for y in range(len(matrix[0])): for x in range(len(matrix)): if matrix[x][y][0] is True: #wall ugfx.area(startX + (pixelW * x), startY + (pixelH * y), pixelW, pixelH, fgcolor) if matrix[x][y][1] == 1: #char pos triangle pointing in direction polyArray = resizePoly(dirOffset[fixed][3], charSize) ugfx.fill_polygon(startX + (pixelW * x) + math.floor(pixelH/2), startY + ( pixelH * y ) + math.floor(pixelH/2)-1, polyArray, fgcolor) def resizePoly(array, size): #for variable sized char indicator polyArray = [] for point in array: polyArray.append([point[0]*size, point[1]*size]) return polyArray #These set up the array of what we can see def chunkN(): array = [] column = [] for row in grid[posX-1:posX+2]: column.append(row[:posY+1][::-1]) for i in range(len(column[0])): t = [e[i] for e in column] array.append(t) return array def chunkE(): array = [] for row in grid[posX:]: array.append(row[posY-1:posY+2]) return array def chunkS(): array = [] column = [] for row in grid[posX-1:posX+2]: column.append(row[posY:]) for i in range(len(column[0])): t = [e[i] for e in column] array.append(t[::-1]) return array def chunkW(): array = [] for row in grid[:posX+1]: array.append(row[posY-1:posY+2][::-1]) return array[::-1] def draw3dView(): done = False row = 0 maxDepth = max(w*2,h*2) #This could be calculated on the fly but it's probably not worth the effort offsetArray = [[0, 0], [28, 18], [48, 30], [62, 40], [77, 49], [84, 54], [90, 60]] maxDepth = min(len(offsetArray)-1, maxDepth) posGrid = [] #rotate and slice grid for view matrix if ( compass == "N"): posGrid = chunkN() if ( compass == "E"): posGrid = chunkE() if ( compass == "S"): posGrid = chunkS() if ( compass == "W"): posGrid = chunkW() #drawMaze(posGrid, 0, 0, 1) #draws what view is based on #box around view ugfx.box(0, 0, viewX, viewY, fgcolor) while not done and row < maxDepth: #foreach depth #calculate the default points based on offset array offsetX = offsetArray[row + 1][0] offsetY = offsetArray[row + 1][1] startLTX = offsetArray[row][0] startLTY = offsetArray[row][1] endLTX = offsetX endLTY = offsetY #previous loop end numbers startLBX = offsetArray[row][0] startLBY = viewY - offsetArray[row][1] endLBX = offsetX endLBY = viewY - offsetY startRTX = viewX - offsetArray[row][0] startRTY = offsetArray[row][1] endRTX = viewX - offsetX endRTY = offsetY startRBX = viewX - offsetArray[row][0] startRBY = viewY - offsetArray[row][1] endRBX = viewX - offsetX endRBY = viewY - offsetY #if left is not wall, lower the start position if posGrid[row][0][0] is False: startLTY = offsetY startLBY = viewY - offsetY #if front is a wall move start right if posGrid[row + 1][1][0] is True: endLTX = viewX - offsetX endLBX = viewX - offsetX #if right is a wall move start to right wall and skip right if posGrid[row][2][0] is False: endLTX = viewX - offsetArray[row][0] endLBX = viewX - offsetArray[row][0] done = True else: #draw left vertical line ugfx.line(endLTX, endLTY, endLBX, endLBY, fgcolor) else: #left is wall #draw left vertical line ugfx.line(endLTX, endLTY, endLBX, endLBY, fgcolor) #draw two lines for that position ugfx.line(startLTX, startLTY, endLTX, endLTY, fgcolor) ugfx.line(startLBX, startLBY, endLBX, endLBY, fgcolor) if done: break #starting right side #if right is not a wall, lower the start position if posGrid[row][2][0] is False: startRTY = offsetY startRBY = viewY - offsetY #if front is a wall move end left if posGrid[row + 1][1][0] is True: endRTX = offsetX endRBX = offsetX done = True else: #draw right vertical line ugfx.line(endRTX, endRTY, endRBX, endRBY, fgcolor) else: #draw right vertical line ugfx.line(endRTX, endRTY, endRBX, endRBY, fgcolor) if posGrid[row + 1][1][0] is True: #draw end wall and finish #this is going to be redundant in some cases I think ugfx.line(endRTX, endRTY, endLTX, endLTY, fgcolor) ugfx.line(endRBX, endRBY, endLBX, endLBY, fgcolor) done = True #draw right lines ugfx.line(startRTX, startRTY, endRTX, endRTY, fgcolor) ugfx.line(startRBX, startRBY, endRBX, endRBY, fgcolor) if done: break row = row + 1 return def display(): drawCompass() drawMaze(grid, screenX - (pixelW * (w * 2 + 1)), 0) draw3dView() ugfx.flush() def quit(): ugfx.clear(ugfx.WHITE) ugfx.flush() ugfx.string(50, 50, "Quitting", font, fgcolor) ugfx.flush() appglue.start_app("launcher", False) def clearfg(): ugfx.clear(fgcolor) ugfx.flush() def clearbg(): ugfx.clear(bgcolor) ugfx.flush() def clearScreen(): clearfg() clearbg() def btn_a(pressed): if pressed: ugfx.clear(ugfx.WHITE) ugfx.flush() display() def btn_b(pressed): if pressed: display() def btn_up(pressed): global posX, posY if pressed: newX=posX - dirOffset[direction][0] newY=posY - dirOffset[direction][1] if grid[newX][newY][0] is False: grid[posX][posY][1] = 0 grid[newX][newY][1] = 1 posX = newX posY = newY display() def btn_down(pressed): global direction if pressed: for i in range(2): direction = direction + 1 if direction >= len(dirOffset): direction = 0 display() def btn_left(pressed): global direction if pressed: direction = direction - 1 if direction < 0: direction = len(dirOffset) - 1 display() def btn_right(pressed): global direction if pressed: direction = direction+1 if direction >= len(dirOffset): direction = 0 display() def btn_start(pressed): if pressed: quit() def btn_select(pressed): if pressed: Maze3D() Maze3D()