import itertools
import time
import tkinter
import vector
################################################################################
class Polygon:
"Polygon(*vertices) -> Polygon"
__slots__ = 'vertices'
def __init__(self, *vertices):
"Initializes polygon with a list of vertices."
self.vertices = vertices
def translate(self, offset):
"Moves the polygon by the specified offset."
for vertex in self.vertices:
vertex += offset
def rotate(self, direction):
"Rotates the polygon by a vector's direction."
for vertex in self.vertices:
vertex.direction += direction
def scale(self, factor):
"Increases or decreases size of the polygon."
for vertex in self.vertices:
vertex *= factor
def copy(self):
"Copies the polygon by copying its vertices."
return Polygon(*(vertex.copy() for vertex in self.vertices))
################################################################################
class Graphics:
"Graphics(canvas) -> Graphics"
__slots__ = 'canvas'
def __init__(self, canvas):
"Initializes graphics by wrapping a canvas."
self.canvas = canvas
def draw(self, polygon, fill, outline):
"Draws a polygon on the underlying canvas."
self.canvas.create_polygon(*itertools.chain(*polygon.vertices),
fill=fill, outline=outline)
def write(self, x, y, text, fill):
"Writes the text to bottom-left of location."
self.canvas.create_text(x, y, text=text, fill=fill, anchor=tkinter.NW)
def clear(self):
"Clears canvas of all objects shown on it."
self.canvas.delete(tkinter.ALL)
def fill(self, background):
"Fills in the canvas with the given color."
self.canvas.configure(background=background)
################################################################################
class Process(tkinter.Frame):
"Process(master, width, height) -> Process"
FRAMESKIP = 5 # How many frames should skip before issuing speed warning?
RENDER_EACH_SECOND = 15 # How often should the DISPLAY be updated / second?
UPDATE_EACH_SECOND = 30 # How often should the PHYSICS be updated / second?
@classmethod
def main(cls, width, height):
"Creates a process in a window and executes it."
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Processing 1.1')
root.resizable(False, False)
view = cls(root, width, height)
view.grid()
root.mainloop()
########################################################################
def __init__(self, master, width, height):
"Initializes process and starts simulation loops."
super().__init__(master)
canvas = tkinter.Canvas(self, width=width, height=height,
background='white', highlightthickness=0)
canvas.bind('<1>', self.mouse_pressed)
canvas.grid()
graphics = Graphics(canvas)
self.setup(graphics.fill)
render = 1 / self.RENDER_EACH_SECOND
update = 1 / self.UPDATE_EACH_SECOND
self.__loop(lambda: self.render(graphics), render, time.clock(), 0)
self.__loop(lambda: self.update(update), update, time.clock(), 0)
def __loop(self, method, interval, target, errors):
"Runs the method after each interval has passed."
method()
target += interval
ms = int((target - time.clock()) * 1000)
if ms >= 0:
self.after(ms, self.__loop, method, interval, target, 0)
elif errors == self.FRAMESKIP:
self.speed_warning()
self.after_idle(self.__loop, method, interval, target, 0)
else:
self.after_idle(self.__loop, method, interval, target, errors + 1)
########################################################################
def setup(self, background):
"This method is called before render and update."
raise NotImplementedError()
def render(self, graphics):
"This method is called when screen should render."
raise NotImplementedError()
def update(self, interval):
"This method is called when physics should update."
raise NotImplementedError()
def mouse_pressed(self, event):
"This method is called when left-clicking mouse."
raise NotImplementedError()
def speed_warning(self):
"This method is called when code is running slow."
raise NotImplementedError()
################################################################################
import recipe576904; recipe576904.bind_all(globals())