# Strictly Platonic, a game by Christopher Night, 20 August 2009 # Requires pygame and PyOpenGL import math, random, OpenGL, pygame from OpenGL.GL import * from OpenGL.GLU import * class Angle: # An angle that remembers its sin and cos def __init__(self, a, s = None, c = None): self.a = a if s or c: self.sin, self.cos = s, c else: na = math.pi * a / 180. self.sin, self.cos = math.sin(na), math.cos(na) def __neg__(self): return Angle(-self.a, -self.sin, self.cos) def double(self): return Angle(2 * self.a, 2 * self.sin * self.cos, 2 * self.cos ** 2 - 1) class Vector: # A vector that remembers its norm def __init__(self, (x, y, z), n = None): self.x, self.y, self.z = x, y, z self.n = n def setnorm(self): self.n = math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2) return self.n def norm(self): if not self.n: self.setnorm() return self.n def copy(self): if not self.n: self.setnorm() return Vector((self.x, self.y, self.z), self.n) def __add__(self, v): return Vector((self.x + v.x, self.y + v.y, self.z + v.z)) def __sub__(self, v): return Vector((self.x - v.x, self.y - v.y, self.z - v.z)) def __mul__(self, a): return Vector((self.x * a, self.y * a, self.z * a), self.norm() * abs(a)) def normvec(self): # Normalized x,y,z Vector n = self.norm() if n == 1: return self.copy() return Vector((self.x / n, self.y / n, self.z / n), 1) def normalize(self): n = self.setnorm() self.x /= n self.y /= n self.z /= n def normtrip(self): # Normalized x,y,z triple (not a Vector) n = self.norm() return (self.x, self.y, self.z) if n == 1 else (self.x / n, self.y / n, self.z / n) def dot(self, v): return v.x * self.x + v.y * self.y + v.z * self.z def cross(self, v): return Vector((self.y * v.z - self.z * v.y, self.z * v.x - self.x * v.z, self.x * v.y - self.y * v.x)) def rmatrix(self, a): # Rotation matrix by a given Angle x, y, z = self.normtrip() s, c, cc = a.sin, a.cos, 1 - a.cos return ((x**2*cc+c, x*y*cc-z*s, x*z*cc+y*s), (x*y*cc+z*s, y**2*cc+c, y*z*cc-x*s), (x*z*cc-y*s, y*z*cc+x*s, z**2*cc+c)) def rotate(self, rm): v = [x * self.x + y * self.y + z * self.z for x,y,z in rm] return Vector(v, self.norm()) def pursue(self, v, fac, dmin = None, dmax = None): d = (v - self) * fac n = d.norm() if dmin and n < dmin: return v if dmax and n > dmax: return self + d * (dmax / n) return self + d @staticmethod def rand(): r = lambda: random.uniform(-1, 1) v = Vector((r(), r(), r())) while v.norm() > 1: v = Vector((r(), r(), r())) return v.normvec() yhat = Vector((0,1,0)) zhat = Vector((0,0,1)) class Vertex: # Vertex of a sprite. Remembers its adjacencies and rotation matrix def __init__(self, s, p): self.sprite = s self.p = p self.n = p.normvec() self.faces = [] self.neighbors = [] if s.__class__ == Solid: self.rmatrix = self.p.rmatrix(-Angle(360. / s.k)) self.color = (1,) + (random.random(),) * 2 def addneighbor(self, v): if not v in self.neighbors: self.neighbors.append(v) if not self in v.neighbors: v.neighbors.append(self) def near(self, p): dv = self.p - p return dv.norm() < 0.001 class Face: # Face of a sprite. Remembers its normal and vertices def __init__(self, s, vs): self.sprite = s self.vertices = list(vs) x, y, z = 0, 0, 0 for v in self.vertices: v.faces.append(self) self.n = (vs[1].p - vs[0].p).cross(vs[2].p - vs[1].p).normvec() self.color = None self.texture = None self.hcolor = None def highlight(self, c = (1, 1, 0)): self.hcolor = c def unhighlight(self): self.hcolor = None def loadtexture(self, surf): data = pygame.image.tostring(surf, "RGBX", 1) glEnable(GL_TEXTURE_2D) self.texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.texture) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf.get_width(), surf.get_height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) def draw(self): if self.texture: glBindTexture(GL_TEXTURE_2D, self.texture) tx, ty = 0, 0 glBegin(GL_POLYGON) glNormal3f(self.n.x, self.n.y, self.n.z) color = self.hcolor or self.color if color: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color) for v in self.vertices: if self.texture: glTexCoord2f(tx, ty) tx, ty = 1 - ty, tx elif not color: glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, v.color) glVertex3f(v.p.x, v.p.y, v.p.z) glEnd() class Shape: def __init__(self): self.vertices = [] self.faces = [] self.p = Vector((0, 0, 0)) def vnum(self, v): for j in range(len(self.vertices)): if self.vertices[j] == v: return j return -1 def findvertex(self, p): for v in self.vertices: if v.near(p): return v v = Vertex(self, p) self.vertices.append(v) return v def addface(self, vs): f = Face(self, vs) self.faces.append(f) return f def draw(self): glPushMatrix() glTranslatef(self.p.x, self.p.y, self.p.z) for f in self.faces: f.draw() glPopMatrix() def scale(self, a): for v in self.vertices: v.p = v.p * a def highlight(self, c = (1, 1, 0)): for f in self.faces: f.highlight(c) def unhighlight(self): for f in self.faces: f.unhighlight() class Sprite (Shape): # An object that faces a certain direction, can be bound def __init__(self): Shape.__init__(self) self.n = zhat self.f = yhat self.bound, self.bface, self.newbface = None, None, None self.ticker = 0 def normalize(self): self.n.normalize() self.f = self.f - self.n * self.f.dot(self.n) self.f.normalize() self.ticker = 0 def checkticker(self): self.ticker += 1 if self.ticker >= 20: self.normalize() def draw(self): glPushMatrix() glTranslatef(self.p.x, self.p.y, self.p.z) nx, ny, nz = self.n.x, self.n.y, self.n.z r = math.sqrt(nx ** 2 + ny ** 2) alpha = math.atan2(r, nz) f0 = Vector((-nx*ny*(1-nz)/r**2, nx**2*(1-nz)/r**2+nz, -ny)) cosx = f0.dot(self.f) c = f0.cross(self.f) sinx = c.norm() if c.dot(self.n) > 0 else -c.norm() beta = math.atan2(sinx, cosx) glRotatef(beta * 180 / math.pi, nx, ny, nz) glRotatef(alpha * 180 / math.pi, -ny / r, nx / r, 0) for f in self.faces: f.draw() glPopMatrix() def yaw(self, A): m = self.n.rmatrix(A) self.f = self.f.rotate(m) self.checkticker() def roll(self, A): m = self.f.rmatrix(A) self.n = self.n.rotate(m) self.checkticker() def pitch(self, A): m = self.n.cross(self.f).rmatrix(A) self.f = self.f.rotate(m) self.n = self.n.rotate(m) self.checkticker() def go(self, d): self.p = self.p + self.f * d def unbind(self): if not self.bound: return self.p, self.n, self.f = self.vecs() self.bound.boundsprites.remove(self) self.bound, self.bface = None, None self.p = self.p + self.n self.normalize() def vecs(self): if not self.bound: return self.p, self.n, self.f m = self.bound.amatrix return self.bound.p + self.p.rotate(m), self.n.rotate(m), self.f.rotate(m) def setbface(self, bface): self.bface = bface self.newbface = bface def checknewbface(self): if not self.newbface: return None self.newbface = None return self.bface class Solid (Shape): # Platonic solid def __init__(self, A, k): Shape.__init__(self) self.A = A # dihedral angle beta self.k = k # number of faces any given vertex belongs to self.build() # calculate inradius p = Vector((0,0,0)) for v in self.faces[0].vertices: p = p + v.p self.inrad = p.norm() / len(self.faces[0].vertices) self.boundsprites = [] self.setmotion() def setmotion(self, a = zhat, b = zhat, p0 = Vector((0,0,0)), omega = 0, psi = 0): self.a = a # Axis of rotation self.b = b # Axis of revolution self.p0 = p0 # Initial position self.alpha, self.beta = 0, 0 # Angles of rotation and revolution self.omega, self.psi = omega, psi # Angular speed of rotation and revolution self.update(0) def update(self, dt): self.alpha += self.omega * dt self.beta += self.psi * dt self.p = self.p0.rotate(self.b.rmatrix(Angle(self.beta))) self.amatrix = self.a.rmatrix(Angle(self.alpha)) self.nmatrix = self.a.rmatrix(-Angle(self.alpha)) def scale(self, a): Shape.scale(self, a) self.inrad *= a def build(self): # Start with two seed vertices v1 = self.findvertex(zhat) p = v1.p.rotate(yhat.rmatrix(self.A)) v2 = self.findvertex(p) v1.addneighbor(v2) # Every ordered pair of vertices that forms an edge of a known face explored = [] while 1: # Find a vertex which hasn't been fully explored ve = [v for v in self.vertices if len(v.faces) < self.k] if not len(ve): break v0 = ve[0] # Find an adjacent vertex it's not part of a side with yet v1 = [v for v in v0.neighbors if (v0, v) not in explored][0] vs = [v0, v1] explored.append((v0, v1)) # Traverse the face until you get back to where you started while vs[-1] != v0: # position of the new vertex p = vs[-2].p.rotate(vs[-1].rmatrix) # find vertex if it exists. Else, add it v2 = self.findvertex(p) explored.append((vs[-1], v2)) v2.addneighbor(vs[-1]) vs.append(v2) del vs[-1] self.faces.append(Face(self, vs)) def colorfaces(self, (r1, g1, b1), (r2, g2, b2)): for f in self.faces: f.color = random.uniform(r1, r2), random.uniform(g1, g2), random.uniform(b1, b2) def proximity(self, sprite, d): m = self.nmatrix p = (sprite.p - self.p).rotate(m) bface = max((f.n.dot(p), f) for f in self.faces)[1] return p.dot(bface.n) - self.inrad < d def bindsprite(self, sprite): sprite.bound = self if sprite in self.boundsprites: self.positionbound(sprite) else: self.boundsprites.append(sprite) self.positionnewbound(sprite) def draw(self): glPushMatrix() glTranslatef(self.p.x, self.p.y, self.p.z) glRotatef(self.alpha, self.a.x, self.a.y, self.a.z) for f in self.faces: f.draw() for s in self.boundsprites: s.draw() glPopMatrix() def positionbound(self, sprite): # Find which face it's on by looking for best match bface = max((f.n.dot(sprite.p), f) for f in self.faces)[1] sprite.p = sprite.p * (self.inrad / sprite.p.dot(bface.n)) if sprite.bface == bface: return sprite.setbface(bface) # Get rotation matrix to transform forward vector r = sprite.n.cross(sprite.bface.n) sina = r.norm() cosa = sprite.n.dot(sprite.bface.n) t = r.rmatrix(Angle(0, sina, cosa)) sprite.f = sprite.f.rotate(t) # Match up normal vector sprite.n = sprite.bface.n def positionnewbound(self, sprite): # Find which face it's on by looking for best match m = self.nmatrix p = (sprite.p - self.p).rotate(m) bface = max((f.n.dot(p), f) for f in self.faces)[1] sprite.p = p * (self.inrad / p.dot(bface.n)) sprite.setbface(bface) # Get rotation matrix to transform forward vector n2 = sprite.n.rotate(m) r = n2.cross(sprite.bface.n) sina = r.norm() cosa = n2.rotate(m).dot(sprite.bface.n) t = r.rmatrix(Angle(0, sina, cosa)) sprite.f = sprite.f.rotate(m).rotate(t) # Match up normal vector sprite.n = sprite.bface.n sprite.normalize() class Stars: def __init__(self, n = 200, d = 1000): self.ps = [] for j in range(n): self.ps.append(Vector.rand() * d) def draw(self): # glPointSize(2) glBegin(GL_POINTS) glColor3f(1, 1, 1) for p in self.ps: glVertex3f(p.x, p.y, p.z) glEnd() tetrahedron = Solid(Angle(109.471, math.sqrt(8)/3, -1./3), 3) cube = Solid(Angle(70.529, math.sqrt(8)/3, 1./3), 3) octahedron = Solid(Angle(90, 1, 0), 4) dodecahedron = Solid(Angle(41.810, 2./3, math.sqrt(5)/3.), 3) icosahedron = Solid(Angle(63.435, 2/math.sqrt(5), 1/math.sqrt(5)), 5) solids = (tetrahedron, cube, octahedron, dodecahedron, icosahedron) faces = [] for s in solids: faces += s.faces icosahedron.scale(60) icosahedron.setmotion(Vector.rand(), zhat, Vector((0,0,0)), 0.05, 0) tetrahedron.scale(32) dodecahedron.scale(32) cube.scale(32) octahedron.scale(32) r = lambda: random.uniform(80, 144) tetrahedron.setmotion(Vector.rand(), zhat, Vector((0,r(),0)), 0.01, 0.02) dodecahedron.setmotion(Vector.rand(), zhat, Vector((0,-r(),0)), 0.01, 0.02) cube.setmotion(Vector.rand(), yhat, Vector((r(),0,0)), 0.01, 0.02) octahedron.setmotion(Vector.rand(), yhat, Vector((-r(),0,0)), 0.01, 0.02) tetrahedron.colorfaces((1, 0, 0), (1, 0.3, 0)) cube.colorfaces((1, 1, 1), (1, 1, 1)) octahedron.colorfaces((0.2, 1, 0.2), (0.3, 1, 0.3)) icosahedron.colorfaces((0, 0, 0.4), (0, 0, 0.5)) dodecahedron.colorfaces((0, 0, 0.03), (0, 0, 0.06)) ship = Sprite() v0 = ship.findvertex(Vector((0, 6, 0))) v1 = ship.findvertex(Vector((-2, 0, 0))) v2 = ship.findvertex(Vector((0, 0, 1.5))) v3 = ship.findvertex(Vector((2, 0, 0))) v4 = ship.findvertex(Vector((0, -2, 0))) v5 = ship.findvertex(Vector((-2, 3, 0))) v6 = ship.findvertex(Vector((-3, 2, 0))) v7 = ship.findvertex(Vector((-2, 2, 1))) v8 = ship.findvertex(Vector((-2, -4, 0))) v9 = ship.findvertex(Vector((2, 3, 0))) v10 = ship.findvertex(Vector((3, 2, 0))) v11 = ship.findvertex(Vector((2, 2, 1))) v12 = ship.findvertex(Vector((2, -4, 0))) ship.addface((v0, v1, v2)) ship.addface((v0, v2, v3)) ship.addface((v2, v1, v4)) ship.addface((v2, v4, v3)) ship.addface((v0, v3, v4, v1)) ship.addface((v5, v6, v7)) ship.addface((v7, v6, v8)) ship.addface((v8, v5, v7)) ship.addface((v5, v8, v6)) ship.addface((v9, v11, v10)) ship.addface((v10, v11, v12)) ship.addface((v9, v12, v11)) ship.addface((v9, v10, v12)) for face in ship.faces: face.color = (0.5, 0.5, 0.5) class camera: def __init__(self): self.setp0() def setp0(self): self.c = Vector((80, 0, -80)) self.p = Vector((0, 0, -200)) self.u = Vector((1, 0, 0)) def setview(self): glMatrixMode(GL_MODELVIEW) glLoadIdentity() gluLookAt(self.c.x, self.c.y, self.c.z, self.p.x, self.p.y, self.p.z, self.u.x, self.u.y, self.u.z) def pursue(self, np, nc, nu): self.p = self.p.pursue(np, 0.15, 0.01) self.c = self.c.pursue(nc, 0.2, 0.01) self.u = self.u.pursue(nu, 0.2).normvec() class label: labels = [] def __init__(self, t, fname, fsize, c0 = (1, 1, 1), c1 = (0, 0, 1), border = 2, padding = 2, maxlen = 100): self.t = t self.fname = fname self.fsize = fsize self.maxlen = maxlen self.c0, self.c1 = c0, c1 self.pc0, self.pc1 = [c * 255 for c in c0], [c*255 for c in c1] self.border, self.padding = border, padding self.render() self.position() self.active = False label.labels.append(self) def render(self): font = pygame.font.SysFont(self.fname, self.fsize) s = [] j = 0 self.x, self.y = 0, 0 while len(self.t): p = self.t[0:self.maxlen].rfind(" ") if len(self.t) > self.maxlen else len(self.t) s.append(font.render(self.t[0:p], True, self.pc1)) self.t = self.t[p+1:] j += 1 self.x = max(self.x, s[-1].get_width()) self.y += self.fsize + self.padding d = (self.border + self.padding) self.x += 2 * d self.y += 2 * d s2 = pygame.Surface((self.x, self.y)) s2.fill(self.pc0) for j in range(len(s)): s2.blit(s[j], (d, d + j * (self.padding + self.fsize))) pygame.draw.rect(s2, self.pc1, pygame.Rect(0, 0, self.x, self.y), self.border) data = pygame.image.tostring(s2, "RGBX", 1) self.texture = glGenTextures(1) glBindTexture(GL_TEXTURE_2D, self.texture) try: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.x, self.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) except OpenGL.error.GLError: nextpow = lambda x: min(1 << k for k in range(20) if 1 << k >= x) px, py = nextpow(self.x), nextpow(self.y) s2 = pygame.transform.smoothscale(s2, (px, py)) data = pygame.image.tostring(s2, "RGBX", 1) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, px, py, 0, GL_RGBA, GL_UNSIGNED_BYTE, data) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) def position(self): self.x0, self.y0 = (sx-self.x)/2., sy/20 def upperright(self): self.x0, self.y0 = sx - self.x - sy/20, sy - self.y - sy/20 @staticmethod def prep(): glDisable(GL_DEPTH_TEST) glDisable(GL_LIGHTING) glMatrixMode(GL_PROJECTION) glLoadIdentity() glOrtho(0, sx, 0, sy, -1, 1) glMatrixMode(GL_MODELVIEW) glLoadIdentity() def draw(self): glBindTexture(GL_TEXTURE_2D, self.texture) glBegin(GL_QUADS) tx, ty = 0, 0 for j in range(4): glTexCoord2f(tx, ty) glVertex3f(self.x0 + tx * self.x, self.y0 + ty * self.y, 0) tx, ty = 1 - ty, tx glEnd() @staticmethod def drawall(dt): label.prep() for l in label.labels: if l.active: l.draw() if l.active < 0: l.active += dt if l.active >= 0: l.active = False def dice(pattern = "000000000"): s = pygame.Surface((256, 256)) s.fill((255, 255, 255)) for j in range(9): if pattern[j] != "0": pygame.draw.circle(s, (0,0,0), (128 + (j//3 - 1) * 80, 128 + (j%3 - 1) * 80), 30) return s def init(): global sx, sy pygame.init() flags = pygame.FULLSCREEN | pygame.OPENGLBLIT | pygame.DOUBLEBUF pygame.display.set_mode((0, 0), flags) sx, sy = pygame.display.get_surface().get_size() pygame.mouse.set_visible(False) glEnable(GL_CULL_FACE) glEnable(GL_TEXTURE_2D) glLightfv(GL_LIGHT0, GL_DIFFUSE, (1, 1, 1, 1)) glLightfv(GL_LIGHT0, GL_POSITION, (2, 2, 1, 0)) glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (0.5, 0.5, 0.5, 1)) glEnable(GL_LIGHT0) glEnable(GL_LIGHTING) cube.faces[0].loadtexture(dice("000010000")) cube.faces[1].loadtexture(dice("100000001")) cube.faces[2].loadtexture(dice("100010001")) cube.faces[3].loadtexture(dice("101010101")) cube.faces[4].loadtexture(dice("101000101")) cube.faces[5].loadtexture(dice("111000111")) pygame.font.init() init() mlabel = [None] * 5 mlabel[1] = label("Mission 1: Land on all five worlds", "arial", 40, (0, 0, 1), (1,1,1)) mlabel[2] = label("Mission 2: Land only on highlighted world", "arial", 40, (0, 0, 1), (1,1,1)) mlabel[3] = label("Mission 3: Land on all 50 faces", "arial", 40, (0, 0, 1), (1,1,1)) mlabel[4] = label("Final mission: Highlight all faces", "arial", 40, (0, 0, 1), (1,1,1)) info = label("Enter: mission briefing. Shift: view controls", "arial", 24, (0, 0, 1), (1, 1, 1)) info.y0 -= sy/40 for m in mlabel[1:5]: m.y0 = info.y0 + info.y + 10 brief = [None] * 5 brief[1] = label("Mission 1 briefing: Visit all five worlds to complete your mission. In space, your ship is controlled by the space bar and the arrow keys. When you land, the controls change. Press Shift to view the controls. Take off from a world by pressing Tab. Can you locate the mysterious dodecahedron? Press Enter to remove this briefing.", "arial", 20, (0, 0, 1), (1, 1, 1)) brief[2] = label("Mission 2 briefing: Once again, you must visit all five worlds, but this time, you must do it in the correct order. Land only on the yellow highlighted world. If you land anywhere else, you will fail your mission. Be careful when taking off that you don't immediately land again. When in space, pressing Tab can help you get your bearings.", "arial", 20, (0, 0, 1), (1, 1, 1)) brief[3] = label("Mission 3 briefing: Land on every face of every world, in any order you want. When you are on a world, you will find it useful to hold Ctrl to see where you're going. Remember, you can move with either the space bar or the up key.", "arial", 20, (0, 0, 1), (1, 1, 1)) brief[4] = label("Final mission briefing: Once again, land on every face of every world. You can do it in any order. If you revisit a face, you will not fail the mission, but you will undo your previous work. Good luck!", "arial", 20, (0, 0, 1), (1, 1, 1)) title1 = label("Strictly Platonic", "arial", 80, (0, 0, 1), (1, 1, 1), 4, 16) title1.x0, title1.y0 = (sx - title1.x) / 2, (sy - title1.y) / 2 + 40 title2 = label("by Christopher Night", "arial", 32, (0, 0, 1), (1, 1, 1)) title2.x0, title2.y0 = title1.x0 + title1.x - title2.x - 20, title1.y0 - title2.y / 2 controls = [] controls.append(label("At any time", "arial", 36, (0, 0.5, 0), (1, 1, 1))) controls[-1].y0 += 160 controls.append(label("Enter: briefing", "arial", 24, (0, 0.5, 0), (1, 1, 1))) controls[-1].y0 += 120 controls.append(label("Shift: controls", "arial", 24, (0, 0.5, 0), (1, 1, 1))) controls[-1].y0 += 80 controls.append(label("Esc/Q: quit", "arial", 24, (0, 0.5, 0), (1, 1, 1))) controls[-1].y0 += 40 controls.append(label("Backspace: abort mission", "arial", 24, (0, 0.5, 0), (1, 1, 1))) controls[-1].y0 += 0 controls.append(label("In space", "arial", 36, (0.5, 0, 0), (1, 1, 1))) controls[-1].y0 += 160 controls[-1].x0 -= 280 controls.append(label("Left/right: roll", "arial", 24, (0.5, 0, 0), (1, 1, 1))) controls[-1].y0 += 120 controls[-1].x0 -= 280 controls.append(label("Up/down: pitch", "arial", 24, (0.5, 0, 0), (1, 1, 1))) controls[-1].y0 += 80 controls[-1].x0 -= 280 controls.append(label("Tab: find center", "arial", 24, (0.5, 0, 0), (1, 1, 1))) controls[-1].y0 += 40 controls[-1].x0 -= 280 controls.append(label("/: swap pitch control", "arial", 24, (0.5, 0, 0), (1, 1, 1))) controls[-1].y0 += 0 controls[-1].x0 -= 280 controls.append(label("On the surface", "arial", 36, (0, 0, 0.5), (1, 1, 1))) controls[-1].y0 += 160 controls[-1].x0 += 280 controls.append(label("Left/right: turn", "arial", 24, (0, 0, 0.5), (1, 1, 1))) controls[-1].y0 += 120 controls[-1].x0 += 280 controls.append(label("Space/up: go", "arial", 24, (0, 0, 0.5), (1, 1, 1))) controls[-1].y0 += 80 controls[-1].x0 += 280 controls.append(label("Tab: liftoff", "arial", 24, (0, 0, 0.5), (1, 1, 1))) controls[-1].y0 += 40 controls[-1].x0 += 280 controls.append(label("Ctrl: Flip camera", "arial", 24, (0, 0, 0.5), (1, 1, 1))) controls[-1].y0 += 0 controls[-1].x0 += 280 m1prog = [None] * 6 for j in range(6): m1prog[j] = label("Worlds remaining: %s" % j, "arial", 24, (0, 0, 1), (1, 1, 1)) m1prog[j].upperright() m3prog = [None] * 5 for j in range(5): s = solids[j] n = len(s.faces) m3prog[j] = [None] * (n+1) for k in range(n+1): m3prog[j][k] = label(" %s " % k, "arial", 24, s.faces[0].color, (1,1,1) if j != 1 else (0,0,0)) m3prog[j][k].upperright() m3prog[j][k].x0 -= (5-j) * 60 lfail = label("Mission Failed", "arial", 40, (1, 0, 0), (1,1,1)) lcom = label("Mission Complete", "arial", 40, (0, 1, 0), (1,1,1)) clock = pygame.time.Clock() stars = Stars() cam = camera() pitch = 0.1 mission = 1 titleonce = True while mission < 5: for l in label.labels: l.active = False mlabel[mission].active = -5000 info.active = -10000 if titleonce: title1.active = -2000 title2.active = -2000 titleonce = False if mission == 1: targetlist = list(solids) m1prog[5].active = -3000 elif mission == 2: targetlist = list(solids) random.shuffle(targetlist) m1prog[5].active = -3000 elif mission == 3 or mission == 4: targetlist = list(faces) for j in range(5): s = solids[j] n = len([f for f in s.faces if f in targetlist]) m3prog[j][n].active = -3000 for s in solids: s.unhighlight() if mission == 2: targetlist[0].highlight() ship.p = Vector((0, 0, 240)) ship.n = Vector((1, 0, 0)) ship.f = Vector((0, 0, -1)) cam.setp0() liftingoff, failure, complete = False, 1000, 2000 failing, completing = False, False if ship.bound: ship.unbind() starttime = pygame.time.get_ticks() st = 0 while failure and complete: clock.tick(32) dt = clock.get_time() e = pygame.event.get() quitkeys = (pygame.K_q, pygame.K_ESCAPE) helpkeys = (pygame.K_h, pygame.K_c, pygame.K_LSHIFT, pygame.K_RSHIFT) infokeys = (pygame.K_b, pygame.K_RETURN) pswapkeys = (pygame.K_p, pygame.K_SLASH) isquit = lambda x: x.type == pygame.KEYDOWN and x.key in quitkeys if any(isquit(x) for x in e): exit() if any(x.type == pygame.KEYDOWN and x.key in infokeys for x in e): brief[mission].active = not brief[mission].active for c in controls: c.active = False mlabel[mission].active = False info.active = False if any(x.type == pygame.KEYDOWN and x.key in helpkeys for x in e): for c in controls: c.active = not c.active brief[mission].active = False mlabel[mission].active = False info.active = False if any(x.type == pygame.KEYDOWN and x.key in pswapkeys for x in e): pitch = -pitch k = pygame.key.get_pressed() keys = [a for a in range(len(k)) if k[a]] if pygame.K_BACKSPACE in keys: failing = True if failing: failure -= dt if failure <= 0: failure = False elif completing: complete -= dt if complete <= 0: complete = False elif liftingoff: ship.p += ship.n * (dt / 25.) liftingoff -= dt if liftingoff <= 0: liftingoff = False elif ship.bound: ship.bound.bindsprite(ship) if pygame.K_LEFT in keys: ship.yaw(Angle(0.1 * dt)) if pygame.K_RIGHT in keys: ship.yaw(Angle(-0.1 * dt)) if pygame.K_UP in keys or pygame.K_SPACE in keys: ship.go(0.05 * dt) if pygame.K_TAB in keys: ship.unbind() liftingoff = 500 else: if pygame.K_LEFT in keys: ship.roll(Angle(-0.1 * dt)) if pygame.K_RIGHT in keys: ship.roll(Angle(0.1 * dt)) if pygame.K_UP in keys: ship.pitch(Angle(-pitch * dt)) if pygame.K_DOWN in keys: ship.pitch(Angle(pitch * dt)) if pygame.K_SPACE in keys: ship.go(0.05 * dt) if pygame.K_TAB in keys: ship.f = ship.p.normvec() * -1 ship.n = ship.f.cross(yhat).normvec() if ship.p.norm() > 400: ship.p = ship.p * (400 / ship.p.norm()) for s in solids: s.update(dt) if not ship.bound and not liftingoff and s.proximity(ship, 0.5): s.bindsprite(ship) if mission == 1: if s in targetlist: targetlist.remove(s) if len(targetlist): m1prog[len(targetlist)].active = -3000 elif mission == 2: if targetlist[0] == s: s.unhighlight() targetlist.remove(s) if len(targetlist): m1prog[len(targetlist)].active = -3000 if len(targetlist): targetlist[0].highlight() else: failing = True b = ship.checknewbface() if b and b in targetlist and mission == 3: b.highlight() targetlist.remove(b) if b and mission == 4: if b in targetlist: b.highlight() targetlist.remove(b) else: b.unhighlight() targetlist.append(b) if b and (mission == 3 or mission == 4): for j in range(5): s = solids[j] n = len([f for f in s.faces if f in targetlist]) for m in m3prog[j]: m.active = False m3prog[j][n].active = -(2000 if b in s.faces else 1000) if not len(targetlist) and not completing: completing = True p, n, f = ship.vecs() if failing or completing: cam.c += (cam.c - cam.p).normvec() * (dt / 2.) elif pygame.key.get_mods() & pygame.KMOD_LCTRL: cam.pursue(p + f * 10, p + n * 30 + f * 60, n) elif ship.bound: cam.pursue(p + f * 30, p + n * 30 - f * 30, n) else: cam.pursue(ship.p + ship.f * 30, ship.p + ship.n * 2 - ship.f * 30, ship.n) glClearColor(0, 0, 0, 1) glClearDepth(1) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) glMatrixMode(GL_PROJECTION) glLoadIdentity() gluPerspective(50, 1. * sx / sy, 1, 4000) cam.setview() glDisable(GL_DEPTH_TEST) glDisable(GL_LIGHTING) stars.draw() glEnable(GL_DEPTH_TEST) glEnable(GL_LIGHTING) for s in solids: s.draw() if not ship.bound: ship.draw() lfail.active = failing lcom.active = completing label.drawall(dt) glFlush() pygame.display.flip() if not complete: t = pygame.time.get_ticks() - starttime print "Mission %s completed in %.3f seconds" % (mission, t / 1000.) mission += 1