jsuarez/extra/embyr_deprecated/embyr/embyr.py (246 lines of code) (raw):
#Author: Joseph Suarez
from pdb import set_trace as T
from scipy.misc import imread
import numpy as np
import time
import pygame
from pygame.surface import Surface
import kivy as kv
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics.texture import Texture as kvTex
from kivy.graphics import Rectangle
from kivy.config import Config
from kivy.clock import Clock
from kivy.core.window import Window
Config.set('input', 'mouse', 'mouse,multitouch_on_demand')
#Wrapper for all Embyr programs
#Mandatory, required by kivy
class Application(kv.app.App):
def __init__(self, size):
super().__init__()
pygame.init()
self.size = size
self.W, self.H = size
Window.size = (self.W//2, self.H//2)
self.scale = 1.5
#Run a thunk forever
def loop(self, func, interval=0):
Clock.schedule_interval(func, interval)
#Run a thunk once
def once(self, func, delay=0):
Clock.schedule_once(func, delay)
#Wrapper for all draw canvas objects.
#Provides a seamless interface between
#pygame surfaces and kivy graphics using
#a fast flip() buffer transfer
#Expected top performance on a laptop:
#~60fps at 1080p or ~24fps at 4k
#key: alpha mask color
#border: size of frame around canvas
class Container(kv.uix.widget.Widget):
def __init__(self, size, pos=(0, 0), key=None, border=3, **kwargs):
super().__init__(**kwargs)
self.W, self.H = size
self.size, self.border = size, border
self.surf = pygame.Surface((self.W, self.H))
#self.surf = pygame.Surface((int(1.5*self.W), int(1.5*self.H)))
self.texture = kvTex.create(size=size, colorfmt="rgb")
self.left, self.right = self.border, self.W-self.border
self.top, self.bottom = self.border, self.H-self.border
self.scale = 1.5
if key is not None:
self.surf.set_colorkey(key)
with self.canvas:
self.screen = Rectangle(pos=pos, size=size)
if hasattr(self, 'on_key_down'):
Window.bind(on_key_down=self.on_key_down)
#Clear the canvas
def reset(self):
self.fill(Neon.BLACK.rgb)
if self.border > 0:
self.renderBorder()
#Only use in the top level canvas
#Transfers a pygame data buffer to a kivyv texture
def flip(self):
W, H = self.surf.get_width(), self.surf.get_height()
#surf = pygame.transform.scale(self.surf, (int(W*self.scale), int(H*self.scale)))
data = pygame.image.tostring(self.surf, 'RGB', True)
self.texture.blit_buffer(data, bufferfmt="ubyte", colorfmt="rgb")
self.screen.texture = self.texture
#Render a frame around the canvas
def renderBorder(self):
if self.border == 0:
return
for coords in [
(0, 0, self.W, self.top),
(0, 0, self.left, self.H),
(0, self.bottom, self.W, self.H),
(self.right, 0, self.W, self.H)
]:
pygame.draw.rect(self.surf, Neon.RED.rgb, coords)
### Blitting and drawing wrappers ###
def blit(self, container, pos, area=None, flags=0):
pos = (pos[0]+self.border, pos[1]+self.border)
self.surf.blit(container, pos, area=area, special_flags=flags)
self.renderBorder()
def fill(self, color):
self.surf.fill(color)
def rect(self, color, coords, lw=0):
pygame.draw.rect(self.surf, color, coords, lw)
def line(self, color, start, end, lw=1):
pygame.draw.line(self.surf, color, start, end, lw)
class ECanvas(Container):
def __init__(self, args, **kwargs):
size = kwargs.pop('size')
super().__init__(size, **kwargs)
canvas, realm, textures, fonts = args
self.textures = textures
self.fonts = fonts
self.realm = realm
self.canvas = canvas
class Panel(ECanvas):
def __init__(self, args, **kwargs):
super().__init__(args, **kwargs)
self.canvas.add(self.screen)
#Return the visible portion of a tiled environment
def mapCrop(screen, env, txSz, trans):
offs, txSz = offsets(screen, *env.shape[:2], txSz, trans)
minH, maxH, minW, maxW = offs
return env[minH:maxH, minW:maxW], txSz
#Array index offsets based on a transformation
def offsets(screen, R, C, txSz, trans=None):
W, H = screen.W, screen.H
sx, sy, tx, ty = W, H, 0, 0
if trans is not None:
sx, sy, tx, ty = trans
ratio = sx / W #assume uniform transform
txSz = txSz * ratio
nW = int(W / txSz)
nH = int(H / txSz)
minW = -int(min(tx / txSz, 0))
minH = -int(min(ty / txSz, 0))
maxW = min(minW + nW, C)
maxH = min(minH + nH, H)
return (minH, maxH, minW, maxW), txSz
#Make a set of mipmaps for a dict of textures
#with sizes specivied by mipLevels
def mips(texs, mipLevels):
rets = {}
for sz in mipLevels:
if sz not in rets:
rets[sz] = {}
for k, tx in texs.items():
rets[sz][k] = pygame.transform.scale(tx, (sz, sz))
return rets
#Mipmaps for a single texture
def mip(tex, mipLevels):
return dict((sz, pygame.transform.scale(tex, (sz, sz))) for sz in mipLevels)
#Get the mip map "closest" to the specified resolution
def mipKey(txSz, mipLevels):
inds = np.array(mipLevels) >= txSz
return mipLevels[np.argmax(inds)]
#Render a full tiled map to the given screen (Container)
#env specifies an array of texture indices (requires a tex dict)
#or an array of colors otherwise.
#iso can be used to render square maps in isometric perspective
def renderMap(screen, env, txSz, tex=None, iso=False):
shape = env.shape
H, W = shape[:2]
for h in range(H):
for w in range(W):
if tex is None:
ww, hh = tileCoords(w, h, W, H, txSz, iso)
screen.rect(env[h, w], (ww, hh, txSz, txSz))
else:
mat = tex[env[h, w]]
renderTile(screen, mat, w, h, txSz, iso)
#Convert a tile to isometric
def tileToIso(tex, txSz):
tex.set_colorkey(Neon.BLACK.rgb)
tex = pygame.transform.rotate(tex, -45)
ww, hh = tex.get_width(), tex.get_height()//2
tex = pygame.transform.scale(tex, (ww, hh))
return tex
#coords of a tile in ortho perspective
def cartCoords(w, h, txSz):
return int(w*txSz), int(h*txSz)
#coords of a tile in isometric perspective
def isoCoords(w, h, W, H, txSz):
nW, nH = W//txSz, H//txSz
ww = nW//2 + (w - h) / 2
hh = nH//4 + (w + h) / 4
w, h = cartCoords(ww, hh, txSz)
return w, h
#coords of a tile in ortho/isometric perspective
def tileCoords(w, h, W, H, txSz, iso):
if iso:
return isoCoords(w, h, W, H, txSz)
else:
return cartCoords(w, h, txSz)
#Isometric render a tile
def renderIso(screen, tex, w, h, txSz):
tex = tileToIso(tex, txSz)
w, h = isoCoords(w, h, screen.W, screen.H, txSz)
screen.blit(tex, (w, h))
#Ortho render a tile
def renderCart(screen, tex, w, h, txSz):
w, h = cartCoords(w, h, txSz)
screen.blit(tex, (w, h))
#Ortho/isometric render a tile
def renderTile(screen, tex, w, h, txSz, iso):
if iso:
renderIso(screen, tex, w, h, txSz)
else:
renderCart(screen, tex, w, h, txSz)
#Basic color class with different color schemes
#usable almost anywhere (provides hex/rgb255/rgb1 vals)
class Color:
def __init__(self, name, hexVal):
self.name = name
self.hex = hexVal
self.rgb = rgb(hexVal)
self.norm = rgbNorm(hexVal)
self.value = self.rgb #Emulate enum
def rgb(h):
h = h.lstrip('#')
return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
def rgbNorm(h):
h = h.lstrip('#')
return tuple(int(h[i:i+2], 16)/255.0 for i in (0, 2, 4))
#Neon color pallete + a few common game colors.
#Why would you need anything else?
class Neon:
RED = Color('RED', '#ff0000')
ORANGE = Color('ORANGE', '#ff8000')
YELLOW = Color('YELLOW', '#ffff00')
GREEN = Color('GREEN', '#00ff00')
MINT = Color('MINT', '#00ff80')
CYAN = Color('CYAN', '#00ffff')
BLUE = Color('BLUE', '#0000ff')
PURPLE = Color('PURPLE', '#8000ff')
MAGENTA = Color('MAGENTA', '#ff00ff')
FUCHSIA = Color('FUCHSIA', '#ff0080')
SPRING = Color('SPRING', '#80ff80')
SKY = Color('SKY', '#0080ff')
WHITE = Color('WHITE', '#ffffff')
GRAY = Color('GRAY', '#666666')
BLACK = Color('BLACK', '#000000')
BLOOD = Color('BLOOD', '#bb0000')
BROWN = Color('BROWN', '#7a3402')
GOLD = Color('GOLD', '#eec600')
SILVER = Color('SILVER', '#b8b8b8')
#Hacker green
TERM = Color('TERM', '#41ff00')
#Purple alpha
MASK = Color('MASK', '#d67fff')
#Get the 12 neon colors
def color12():
return (
Neon.RED, Neon.ORANGE, Neon.YELLOW,
Neon.GREEN, Neon.MINT, Neon.CYAN,
Neon.BLUE, Neon.PURPLE, Neon.MAGENTA,
Neon.FUCHSIA, Neon.SPRING, Neon.SKY)
#Get a random neon color
def rand12():
twelveColor = color12()
randInd = np.random.randint(0, len(twelveColor))
return twelveColor[randInd]
#Pygame Surface initializer from file
def Texture(path, mask=None):
try:
img = imread(path)[:, :, :3]
except FileNotFoundError:
raise
img = pygame.pixelcopy.make_surface(img)
if mask is not None:
img.set_colorkey(mask)
#For some reason, pygame loads images transformed
img = pygame.transform.flip(img, True, False)
img = pygame.transform.rotate(img, 90)
return img
#Pygame blank Surface initializer
def Canvas(size, mask=None):
img = pygame.Surface(size)
if mask is not None:
img.set_colorkey(mask)
return img
#Pygame font wrapper
class Font:
def __init__(self, size, name='freesansbold.ttf'):
self.font = pygame.font.Font(name, size)
def render(self, size, color):
return self.font.render(size, 1, color)
#Exponentially decaying FPS tracker
class FPSTracker:
def __init__(self):
self.start = time.time()
self.eda = EDA(k=0.95)
self.fpsVal = 0.0
def update(self):
tick = time.time() - self.start
self.eda.update(1.0/tick)
self.fpsVal = self.eda.eda
self.start = time.time()
@property
def fps(self):
return str(self.fpsVal)[:5]
#Exponentially decaying average
class EDA():
def __init__(self, k=0.9):
self.k = k
self.eda = None
def update(self, x):
if self.eda is None:
self.eda = x
return
self.eda = (1-self.k)*x + self.k*self.eda