#!python # -*- coding: utf-8 -*- # Last updated: <2021/10/07 20:04:46 +0900> u""" Make road tilemap. cursor key or WASD : scroll Windows10 x64 21H1 + Python 3.9.7 64bit + pygame 2.0.1 """ import json import pygame import sys import random SCRW, SCRH = 512, 512 map_w = 64 map_h = 64 tile_w = 8 tile_h = 8 class RoadRoller: HLINE = 1 VLINE = 2 CORNER_DR = 32 CROSS_DLR = 33 CORNER_DL = 34 CROSS_UDR = 64 CROSS_TILE = 65 CROSS_UDL = 66 CORNER_UR = 96 CROSS_ULR = 97 CORNER_UL = 98 CORNER_LIST = [CORNER_DL, CORNER_DR, CORNER_UL, CORNER_UR] CORNER = [ # old direction, new direction, tile id [0, 3, CORNER_UL], [0, 1, CORNER_DL], [1, 0, CORNER_UR], [1, 2, CORNER_UL], [2, 1, CORNER_DR], [2, 3, CORNER_UR], [3, 2, CORNER_DL], [3, 0, CORNER_DR] ] JUNCTION = [ # UDLR, tile id # [0b00001, 35], # UDLR = 0001 # [0b00010, 3], [0b00011, HLINE], # [0b00100, 67], [0b00101, CORNER_DR], [0b00110, CORNER_DL], [0b00111, CROSS_DLR], # [0b01000, 99], [0b01001, CORNER_UR], [0b01010, CORNER_UL], [0b01011, CROSS_ULR], [0b01100, VLINE], [0b01101, CROSS_UDR], [0b01110, CROSS_UDL], [0b01111, CROSS_TILE], # UDLR = 1111 ] NOT_CONNECT = { "u": [-1, 0, HLINE, CORNER_UL, CORNER_UR, CROSS_ULR], # check up "d": [-1, 0, HLINE, CORNER_DL, CORNER_DR, CROSS_DLR], # check down "l": [-1, 0, VLINE, CORNER_UL, CORNER_DL, CROSS_UDL], # check left "r": [-1, 0, VLINE, CORNER_UR, CORNER_DR, CROSS_UDR] # check right } CORNER_ID_LIST = { CORNER_DL: { "mask": 0b01001, "match": { 0b01001: CROSS_TILE, 0b01000: CROSS_UDL, 0b00001: CROSS_DLR, 0b00000: CORNER_DL } }, CORNER_UL: { "mask": 0b00101, "match": { 0b00101: CROSS_TILE, 0b00100: CROSS_UDL, 0b00001: CROSS_ULR, 0b00000: CORNER_UL } }, CORNER_DR: { "mask": 0b01010, "match": { 0b01010: CROSS_TILE, 0b01000: CROSS_UDR, 0b00010: CROSS_DLR, 0b00000: CORNER_DR } }, CORNER_UR: { "mask": 0b00110, "match": { 0b00110: CROSS_TILE, 0b00100: CROSS_UDR, 0b00010: CROSS_ULR, 0b00000: CORNER_UR } } } def __init__(self, x, y, direc, tilemap): self.active = True self.x = x self.y = y self.oldx = x self.oldy = y self.direc = direc self.old_direc = direc self.tilemap = tilemap self.map_w = len(tilemap[0]) self.map_h = len(tilemap) self.count = random.randint(3, 16) * 2 self.set_tile(x, y, self.direc) def set_new_direction(self): self.count = random.randint(3, 16) * 2 if random.random() <= 0.6: # not change direction return # change direction oldd = self.direc if random.random() < 0.5: self.direc = (self.direc + 1) % 4 else: self.direc = (self.direc - 1) % 4 # set corner tile for _d in RoadRoller.CORNER: if oldd == _d[0] and self.direc == _d[1]: id = self.tileid(self.x, self.y) if oldd == 0 or self.old_direc == 2: if id != RoadRoller.HLINE: return else: if id != RoadRoller.VLINE: return self.tilemap[self.y][self.x] = _d[2] break def in_area(self): _x, _y = self.x, self.y if _x < 0 or _y < 0 or _x >= self.map_w or _y >= self.map_h: return False return True def move(self): if self.direc == 0: self.x += 1 elif self.direc == 1: self.y += 1 elif self.direc == 2: self.x -= 1 elif self.direc == 3: self.y -= 1 else: print("Error: direc=%d" % self.direc) def tileid(self, x, y): if x < 0 or y < 0: return -1 if x >= self.map_w or y >= self.map_h: return -1 return self.tilemap[y][x] def get_udlr_id(self, x, y): u = self.tileid(x, y - 1) d = self.tileid(x, y + 1) l = self.tileid(x - 1, y) r = self.tileid(x + 1, y) return u, d, l, r def get_udlr_bit(self, u, d, l, r): ret = 0 if u > 0: ret += 0b01000 if d > 0: ret += 0b00100 if l > 0: ret += 0b00010 if r > 0: ret += 0b00001 return ret def check_ignore_id(self, dirstr, value): if value in RoadRoller.NOT_CONNECT[dirstr]: return 0 return value def get_corner_id(self, x, y, id): u, d, l, r = self.get_udlr_id(x, y) u = self.check_ignore_id("u", u) d = self.check_ignore_id("d", d) l = self.check_ignore_id("l", l) r = self.check_ignore_id("r", r) fg = self.get_udlr_bit(u, d, l, r) d = RoadRoller.CORNER_ID_LIST[id] fg = fg & d["mask"] if fg in d["match"]: return d["match"][fg] else: sys.exit("Not found id = %d" % id) def exist_udlr(self, x, y, direc): u, d, l, r = self.get_udlr_id(x, y) u = self.check_ignore_id("u", u) d = self.check_ignore_id("d", d) l = self.check_ignore_id("l", l) r = self.check_ignore_id("r", r) return self.get_udlr_bit(u, d, l, r) def set_tile(self, x, y, direc): m = self.tilemap oid = self.tileid(x, y) if oid in RoadRoller.CORNER_LIST: # existing corner tile m[y][x] = self.get_corner_id(x, y, oid) return udlr = self.exist_udlr(x, y, direc) if direc == 0 or direc == 2: m[y][x] = RoadRoller.HLINE for _d in RoadRoller.JUNCTION: if _d[0] == udlr: m[y][x] = _d[1] break else: m[y][x] = RoadRoller.VLINE for _d in RoadRoller.JUNCTION: if _d[0] == udlr: m[y][x] = _d[1] break def update(self): if not self.active: return self.oldx = self.x self.oldy = self.y self.move() if not self.in_area(): # area over. I am dead self.set_tile(self.oldx, self.oldy, self.old_direc) self.active = False return oid = self.tileid(self.x, self.y) if oid != 0: # existing tile. Should I die? if random.random() > 0.85: self.active = False elif oid == RoadRoller.HLINE and (self.direc == 0 or self.direc == 2): self.active = False elif oid == RoadRoller.VLINE and (self.direc == 1 or self.direc == 3): self.active = False # set tile self.set_tile(self.x, self.y, self.direc) self.set_tile(self.oldx, self.oldy, self.old_direc) self.old_direc = self.direc if not self.active: return self.count -= 1 if self.count <= 0: self.set_new_direction() def make_gen_list(): """Make road roller generate position list.""" lst = [] yi = random.randint(0, 6) * 2 while yi < map_h: lst.append((0, yi, 0)) yi += random.randint(2, 6) * 2 xi = random.randint(0, 6) * 2 while xi < map_w: lst.append((xi, 0, 1)) xi += random.randint(2, 6) * 2 yi = random.randint(0, 6) * 2 while yi < map_h: lst.append((map_w - 1, yi, 2)) yi += random.randint(2, 6) * 2 xi = random.randint(0, 6) * 2 while xi < map_w: lst.append((xi, map_h - 1, 3)) xi += random.randint(2, 6) * 2 return lst def clear_map(tilemap): """Clear Tilemap.""" for y in range(len(tilemap)): for x in range(len(tilemap[0])): tilemap[y][x] = 0 def split_image(img, tilew, tileh): """Split image.""" imgs = [] w, h = tilew, tileh xc = img.get_width() // w yc = img.get_height() // h for yi in range(yc): for xi in range(xc): x, y = xi * w, yi * h imgs.append(img.subsurface(pygame.Rect(x, y, w, h))) return imgs pygame.init() screen = pygame.display.set_mode((SCRW, SCRH), pygame.DOUBLEBUF) tilemap = [[0 for i in range(map_w)] for j in range(map_h)] clear_map(tilemap) # load bg chip image imgname = "road_chip.png" img = pygame.image.load(imgname).convert_alpha() # split bg chip image bgtiles = split_image(img, tile_w, tile_h) xofs, yofs = 0, 0 xofs_max = max(0, map_w - (SCRW // tile_w)) yofs_max = max(0, map_h - (SCRH // tile_h)) gen_list = make_gen_list() bidx = 0 obj = None running = True recreate = False # not_fix = True clock = pygame.time.Clock() # Main loop while running: # update # check event for ev in pygame.event.get(): if ev.type == pygame.QUIT: running = False if ev.type == pygame.KEYDOWN: if ev.key == pygame.K_ESCAPE or ev.key == pygame.K_q: # Push ESC or Q key running = False if ev.key == pygame.K_SPACE: recreate = True if recreate: # clear map and start recreate clear_map(tilemap) gen_list = make_gen_list() bidx = 0 obj = None recreate = False # not_fix = True # create map if bidx < len(gen_list): if obj is None: # born road Roller _x, _y, _direc = gen_list[bidx] obj = RoadRoller(_x, _y, _direc, tilemap) else: # move road roller obj.update() if not obj.active: obj = None bidx += 1 # scroll BG pygame.event.pump() pressed = pygame.key.get_pressed() if pressed[pygame.K_LEFT] or pressed[pygame.K_a]: xofs -= 1 if xofs < 0: xofs = 0 if pressed[pygame.K_RIGHT] or pressed[pygame.K_d]: xofs += 1 if xofs > xofs_max: xofs = xofs_max if pressed[pygame.K_UP] or pressed[pygame.K_w]: yofs -= 1 if yofs < 0: yofs = 0 if pressed[pygame.K_DOWN] or pressed[pygame.K_s]: yofs += 1 if yofs > yofs_max: yofs = yofs_max # draw start screen.fill((40, 60, 200)) # clear screen # draw tilemap BG xc = SCRW // tile_w yc = SCRH // tile_h for yi in range(yc): for xi in range(xc): xii = xi + xofs yii = yi + yofs if xii >= map_w or yii >= map_h: continue i = tilemap[yii][xii] x = xi * tile_w y = yi * tile_h screen.blit(bgtiles[i], (x, y)) pygame.display.flip() clock.tick_busy_loop(60) cap = "Tilemap BG - %5.2f FPS, objs: %d/%d, SPC key: Reset" % (clock.get_fps(), bidx, len(gen_list)) pygame.display.set_caption(cap) pygame.quit() sys.exit()