Advertisement
here2share

# Tk_3D_Maze.py

Aug 5th, 2016
233
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 53.81 KB | None | 0 0
  1. # Tk_3D_Maze.py
  2.  
  3. import copy
  4. import math
  5. from Tkinter import *
  6. import random
  7. import time
  8.  
  9. CYCLE_AMOUNT = 5 # higher number -> fewer cycles
  10.  
  11. # if you change the resolution,
  12. # you may need to slightly alter the following two:
  13. CAM_HEIGHT = 0.125
  14. CAM_WIDTH = 0.015
  15.  
  16. # do not edit these:
  17. CAM_LENGTH = 0.1
  18. CAM_SEP = 0.04
  19. WALL_H = 0.5
  20. CELL_SIZE = 40 # pixels
  21. DEBUG = False
  22. FOV = math.pi/2
  23.  
  24. ################################################################################
  25. ##### Point & Seg Helper Functions #############################################
  26. ################################################################################
  27.  
  28. chk=[]
  29. import inspect
  30.  
  31. def trace():
  32.     print
  33.     chk.sort()
  34.     for z in chk: print z
  35.     print
  36.  
  37. def tr():
  38.     z=inspect.currentframe().f_back.f_lineno
  39.     if z not in chk:
  40.         print z
  41.         chk.append(z)
  42.  
  43. def flipCoin(): # ZZZ
  44.     return random.choice([True, False])
  45.  
  46. def smallChance(): # ZZZ
  47.     choices = [True]
  48.     choices.extend([False]*CYCLE_AMOUNT)
  49.     return random.choice(choices)
  50.  
  51. def withinEp(x, y):
  52.     """Returns True if x and y are within some predefined epsilon: 0.0001"""
  53.     epsilon = 0.0001
  54.     return abs(x-y) < epsilon
  55.  
  56. def chopDomain(x):
  57.     """Chops number to fit within [1,1] for use with arccos"""
  58.     if (x > 1):
  59.         return 1
  60.     elif (x < -1):
  61.         return -1
  62.     else:
  63.         return x
  64.  
  65. def isNumber(x):
  66.     """Returns True if x in an int, long, or float; False otherwise"""
  67.     return type(x) in (int, long, float)
  68.  
  69. def mathSign(x):
  70.     if (x == 0):
  71.         return 0
  72.     else:
  73.         return (x/abs(x))
  74.  
  75. def xKey(point):
  76.     return point.x
  77.  
  78. def yKey(point):
  79.     return point.y
  80.  
  81. def extremeX(pointSet):
  82.     minPoint = min(pointSet, key=xKey)
  83.     maxPoint = max(pointSet, key=xKey)
  84.     return (minPoint, maxPoint)
  85.  
  86. def extremeY(pointSet):
  87.     minPoint = min(pointSet, key=yKey)
  88.     maxPoint = max(pointSet, key=yKey)
  89.     return (minPoint, maxPoint)
  90.  
  91. def hexColor(red, green, blue):
  92.     return ("#%02x%02x%02x" % (red, green, blue))
  93.  
  94. def makeColor(row, col, rows, cols):
  95.     if (((row == rows-1) and (col == cols-2)) or
  96.           ((row == rows-2) and (col == cols-1))):
  97.         color = hexColor(255,255,255)
  98.     else:
  99.         green = 255*(row+col)/float(rows+cols)
  100.         blue = 255*(rows+cols-row-col)/float(rows+cols)
  101.         red = 40
  102.         color = hexColor(red, green, blue)
  103.     return color
  104.  
  105. def rgbFromHex(color): # ZZZ
  106.     # get RGB color from hex
  107.     red = int(color[1:3], 16)
  108.     green = int(color[3:5], 16)
  109.     blue = int(color[5:], 16)
  110.     return (red, green, blue)
  111.  
  112. def leftChannelColor(color):
  113.     # cyan-tint color for right channel of
  114.     # red-cyan anaglyph
  115. #   (red, green, blue) = rgbFromHex(color)
  116. #   gray = (red+green+blue)/2
  117. #   newGray = min(255, gray*2)
  118.     # I could not get this to work, so I resorted
  119.     # to black/white (rather, red/cyan) for 3DG:
  120.     return hexColor(0, 255, 255)
  121.  
  122. def shrinkScreenSeg(x, h, otherX, otherH):
  123.     if (abs(x) > abs(h)):
  124.         newX = 1.0*mathSign(x)
  125.         a = newX / x
  126.         newH = a*h + (1-a)*otherH
  127.     else:
  128.         newH = 1.0*mathSign(h)
  129.         a = newH / h
  130.         newX = a*x + (1-a)*otherX
  131.     return (newX, newH)
  132.  
  133. ################################################################################
  134. ##### Point, Seg, Ray Classes ##################################################
  135. ################################################################################
  136.  
  137. class Point(object):
  138.     def __init__(self, x, y):
  139.         if (not isNumber(x) or not isNumber(y)):
  140.             assert(False), "cannot make a point from non-numbers"
  141.         self.x = x
  142.         self.y = y
  143.  
  144.     def __eq__(self, other):
  145.         return (withinEp(self.x, other.x) and withinEp(self.y, other.y))
  146.  
  147.     def __ne__(self, other):
  148.         return not self.__eq__(other)
  149.  
  150.     def __hash__(self):
  151.         hashables = (self.x, self.y)
  152.         return hash(hashables)
  153.  
  154.     def dist(self, other): # ZZZ
  155.         dx = self.x - other.x
  156.         dy = self.y - other.y
  157.         return math.sqrt(dx**2 + dy**2)
  158.  
  159. class Seg(object):
  160.     def __init__(self, p1, p2, color=hexColor(0,0,0)):
  161.         # sanity check
  162.         if not (type(p1) == type(p2) == Point):
  163.             assert(False), "cannot make a seg from nonpoints"
  164.         self.p1 = p1
  165.         self.p2 = p2
  166.         self.color = color
  167.         self.isVert = (self.kind() == "vert")
  168.         self.isHoriz = (self.kind() == "horiz")
  169.  
  170.     def __eq__(self, other):
  171.         if ((self.p1 == other.p1) and (self.p2 == other.p2)):
  172.             return True
  173.         elif ((self.p1 == other.p2) and (self.p2 == other.p1)):
  174.             return True
  175.         else:
  176.             return False
  177.  
  178.     def __hash__(self):
  179.         hashables = (self.p1.x, self.p1.y, self.p2.x, self.p2.y)
  180.         return hash(hashables)
  181.  
  182.     def kind(self):
  183.         if (self.p1.x == self.p2.x):
  184.             return "vert"
  185.         elif (self.p1.y == self.p2.y):
  186.             return "horiz"
  187.         else:
  188.             return "other"
  189.  
  190.     def withinDist(self, eye, dist):
  191.         """Returns True if eye is within a certain distance of
  192.         the segment (in a rectangular sense around the edges)"""
  193.         minX = min(self.p1.x, self.p2.x)
  194.         maxX = max(self.p1.x, self.p2.x)
  195.         minY = min(self.p1.y, self.p2.y)
  196.         maxY = max(self.p1.y, self.p2.y)
  197.         return ((minX - dist < eye.x < maxX + dist) and
  198.                 (minY - dist < eye.y < maxY + dist))
  199.  
  200.  
  201. class Ray(object):
  202.     def __init__(self, eye, target):
  203.         self.eye = eye
  204.         self.dx = target.x - eye.x
  205.         self.dy = target.y - eye.y
  206.         self.target = target
  207.  
  208.     def __mul__(self, scale):
  209.         newTargetX = self.eye.x + scale*self.dx
  210.         newTargetY = self.eye.y + scale*self.dy
  211.         return Ray(self.eye, Point(newTargetX, newTargetY))
  212.  
  213.     def dot(self, other):
  214.         if (type(other) != Ray):
  215.             assert(False), "cannot dot non-rays"
  216.         if (self.eye != other.eye):
  217.             assert(False), "rays have different starting points"
  218.         return self.dx * other.dx + self.dy * other.dy
  219.  
  220.     def norm(self):
  221.         return math.sqrt(self.dot(self))
  222.  
  223.     def angle(self, other):
  224.         if (type(other) != Ray):
  225.             assert(False), "angle not defined for non-rays"
  226.         if (self.eye != other.eye):
  227.             assert(False), "rays have different starting points"
  228.         value = chopDomain(self.dot(other) /float(self.norm() * other.norm()))
  229.         angle = math.acos(value)
  230.         return angle # radians
  231.  
  232.     def rotate(self, angle):
  233.         # angle in radians
  234.         # taken from jbahr-hw8.py
  235.         rotationMatrix = Matrix([[math.cos(angle), -math.sin(angle)],
  236.                                  [math.sin(angle), math.cos(angle)]])
  237.         oldNorm = self.norm()
  238.         dir = Vector([self.dx, self.dy])
  239.         newDir = rotationMatrix.mult(dir)
  240.         newPoint = Point(newDir.elements[0], newDir.elements[1])
  241.         newTarget = Point(newPoint.x + self.eye.x, newPoint.y + self.eye.y)
  242.         newRay = Ray(self.eye, newTarget)
  243.         return newRay
  244.  
  245. class Matrix(object):
  246.     def __init__(self, elements):
  247.         self.elements = elements
  248.         self.rows = len(elements)
  249.         if (type(elements[0]) == list):
  250.             self.cols = len(elements[0])
  251.         else:
  252.             # handle vectors too
  253.             self.cols = 1
  254.  
  255.     def mult(self, other):
  256.         if (type(other) != Vector):
  257.             assert(False), "cannot be multiplied"
  258.         # other must be a vector
  259.         def dotprod(row):
  260.             return Vector(row).dot(other)
  261.         # using map was the idea of James Wu
  262.         return Vector(map(dotprod, self.elements))
  263.  
  264.     def transpose(self):
  265.         # this implementation was taken from recitation
  266.         return Matrix(zip(*self.elements))
  267.  
  268. class Vector(Matrix):
  269.     def __init__(self, elements):
  270.         # column vector
  271.         self.elements = elements
  272.         self.rows = len(elements)
  273.  
  274.     def dot(self, other):
  275.         if (type(other) != Vector):
  276.             assert(False), "cannot dot non-vectors"
  277.         def prod(a):
  278.             return a[0] * a[1]
  279.         return sum(map(prod,zip(self.elements,other.elements)))
  280.  
  281.     def norm(self):
  282.         return math.sqrt(self.dot(self))
  283.  
  284.     def __mul__(self, other):
  285.         if (not isNumber(other)):
  286.             assert(False), "cannot multiply"
  287.         return Vector(map(lambda x : other*x, self.elements))
  288.  
  289.     def __rmul__(self, other):
  290.         return self.__mul__(other)
  291.  
  292.     def __add__(self, other):
  293.         newElements = map(sum, zip(self.elements, other.elements))
  294.         return Vector(newElements)
  295.  
  296.     def __neg__(self):
  297.         return Vector(map(lambda x: -x, self.elements))
  298.  
  299. class ScreenSeg(object):
  300.     # keeps track of distance from center of screen and height
  301.     def __init__(self, cam, seg):
  302.         self.color = seg.color
  303.         # MUST be given a seg that is visible in the direction of the cam
  304.         # Seg pruning must be done beforehand!
  305.         # following is some linear algebra
  306.         v1 = Ray(cam.viewRay.eye, seg.p1)
  307.         v2 = Ray(cam.viewRay.eye, seg.p2)
  308.  
  309.         screenV1 = v1 * (cam.viewRay.norm()**2 / cam.viewRay.dot(v1))
  310.         screenV2 = v2 * (cam.viewRay.norm()**2 / cam.viewRay.dot(v2))
  311.  
  312.         self.x1 = screenV1.dot(cam.rightRay)
  313.         self.x2 = screenV2.dot(cam.rightRay)
  314.  
  315.         self.h1 = WALL_H * screenV1.norm() / v1.norm()
  316.         self.h2 = WALL_H * screenV2.norm() / v2.norm()
  317.  
  318.         self.shrink()
  319.  
  320.     def __str__(self):
  321.         (x1,h1,x2,h2) = (self.x1,self.h1,self.x2,self.h2)
  322.         return str((x1,h1,x2,h2))
  323.  
  324.     def shrink(self):
  325.         # required because tkinter cannot draw points too far from
  326.         # the visible portion of the canvas
  327.         # if abs(x),abs(h) < 1, it can be drawn
  328.         (x1, x2, h1, h2) = (self.x1, self.x2, self.h1, self.h2)
  329.         if ((abs(x1) > 1) or (abs(h1) > 1)):
  330.             (self.x1, self.h1) = shrinkScreenSeg(x1, h1, x2, h2)
  331.         if ((x2 > 1) or (h2 > 1)):
  332.             (self.x2, self.h2) = shrinkScreenSeg(x2, h2, x1, h1)
  333.  
  334.  
  335.        
  336.  
  337. class Intersection(object):
  338.     # represents an intersection of a ray and a wall, which can be either
  339.     # "normal" - ray.eye --- ray.target --- wall
  340.     #   this should usually obscure the wall
  341.     # "behind" - ray.eye --- wall --- ray.target
  342.     #   the wall is generally in front of the obstruction
  343.     # "backwards" - wall --- ray.eye --- ray.target
  344.     #   this generally does not obscure the wall
  345.     # "infinity"
  346.     #   the ray and wall are parallel
  347.     def __init__(self, point, kind):
  348.         self.point = point
  349.         self.kind = kind # str
  350.  
  351. ################################################################################
  352. ##### Line Intersection Functions ##############################################
  353. ################################################################################
  354.  
  355. def intersectRayAndRookSeg(ray, segment):
  356.     """Given a ray and a rook segment, returns their intersection.  The point
  357.     of intersection is not guaranteed to lie on the segment."""
  358.     if (segment.isHoriz):
  359.         return intersectRayAndHorizSegment(ray, segment)
  360.     elif (segment.isVert):
  361.         return intersectRayAndVertSegment(ray, segment)
  362.     else:
  363.         assert(False), "not a rook segment"
  364.  
  365. def intersectRayAndVertSegment(ray, segment):
  366.     """Given a ray and a "vert" segment, return an intersection, unless
  367.     the ray and segment are collinear, at which point this will return
  368.     the entire segment"""
  369.     ### NOTE:  The "eye" is forbidden from lying on a segment ###
  370.     # sanity check
  371.     if (not segment.isVert):
  372.         assert(False), "not a vertical segment"
  373.     if (ray.dx != 0):
  374.         # Note that segment.p1.x == segment.p2.x since it is vertical
  375.         # pointOnLine = k*(dx,dy) + eye.  This solves for k:
  376.         k = (segment.p1.x - ray.eye.x) / float(ray.dx)
  377.         yIntercept = k*ray.dy + ray.eye.y
  378.         intPoint = Point(segment.p1.x, yIntercept)
  379.         if (k < 0):
  380.             return Intersection(intPoint, "backwards")
  381.         elif (k < 1):
  382.             return Intersection(intPoint, "behind")
  383.         else:
  384.             return Intersection(intPoint, "normal")
  385.     else:
  386.         if (segment.p1.x == ray.eye.x):
  387.             # collinear!
  388.             return segment
  389.         else:
  390.             yIntercept = 1 if (ray.dy > 0) else -1
  391.             return Intersection(Point(0,yIntercept), "infinity")
  392.  
  393.  
  394.  
  395. def intersectRayAndHorizSegment(ray, segment):
  396.     """Given a ray and a "horiz" segment, return an intersection, unless
  397.     the ray and segment are collinear, at which point this will return
  398.     the entire segment"""
  399.     # sanity check
  400.     if (not segment.isHoriz):
  401.         assert(False), "not a horizontal segment"
  402.     if (ray.dy != 0):
  403.         # Note that segment.p1.y == segment.p2.y since it is horizontal
  404.         # pointOnLine = k*(dx,dy) + eye.  This solves for k:
  405.         k = (segment.p1.y - ray.eye.y) / float(ray.dy)
  406.         xIntercept = k*ray.dx + ray.eye.x
  407.         intPoint = Point(xIntercept, segment.p1.y)
  408.         if (k < 0):
  409.             return Intersection(intPoint, "backwards")
  410.         elif (k < 1):
  411.             return Intersection(intPoint, "behind")
  412.         else:
  413.             return Intersection(intPoint, "normal")
  414.     else:
  415.         if (segment.p1.y == ray.eye.y):
  416.             # collinear!
  417.             return segment
  418.         else:
  419.             xIntercept = 1 if (ray.dx > 0) else -1
  420.             return Intersection(Point(xIntercept,0), "infinity")
  421.  
  422. def intersectWalls(seg1, seg2):
  423.     """Given two orthogonal rook segs, returns the predicted
  424.     intersection (if they were to stretch into lines)"""
  425.     if (seg1.kind() == seg2.kind()):
  426.         assert(False), "segs not perpendicular"
  427.     if ((seg1.kind() == "other") or (seg2.kind() == "other")):
  428.         assert(False), "not rook segments"
  429.     elif (seg1.isHoriz):
  430.         return Point(seg2.p1.x, seg1.p1.y)
  431.     elif (seg1.isVert):
  432.         return Point(seg1.p1.x, seg2.p1.y)
  433.     else:
  434.         # should never happen
  435.         assert(False), "ERROR!"
  436.  
  437.  
  438. ################################################################################
  439. ##### Line Intersection Functions ##############################################
  440. ################################################################################
  441.  
  442.  
  443. def obstructViaIntersections(cross1, cross2, wall, seg):
  444.     """Given two intersections, a wall, and a segment, return a set containing the
  445.     portions on the segment.  The wall is what obscured the segment to produce
  446.     the two intersections (which are collinear with the seg)."""
  447.     # sanity check
  448.     if ((type(cross1) != Intersection) or (type(cross2) != Intersection)):
  449.         assert(False), "received non-intersections"
  450.     elif (type(seg) != Seg):
  451.         assert(False), "received non-segment"
  452.     # I recognize this is AWFUL style, but I can't think of another
  453.     #  way to handle these cases
  454.     # Most of these cases are really distinct
  455.     if (cross1.kind == "normal"):
  456.         if (cross2.kind == "normal"):
  457.             return normNormIntersect(cross1,cross2,wall,seg)
  458.         elif (cross2.kind == "behind"):
  459.             return normBehindIntersect(cross1,cross2,wall,seg)
  460.         elif (cross2.kind == "backwards"):
  461.             return normBackIntersect(cross1,cross2,wall,seg)
  462.         elif (cross2.kind == "infinity"):
  463.             return normInfIntersect(cross1,cross2,wall,seg)
  464.     elif (cross1.kind == "behind"):
  465.         if (cross2.kind == "normal"):
  466.             return normBehindIntersect(cross2,cross1,wall,seg)
  467.         elif (cross2.kind == "behind"):
  468.             return behindBehindIntersect(cross1,cross2,wall,seg)
  469.         elif (cross2.kind == "backwards"):
  470.             return behindBackIntersect(cross1,cross2,wall,seg)
  471.         elif (cross2.kind == "infinity"):
  472.             return behindInfIntersect(cross1,cross2,wall,seg)
  473.     elif (cross1.kind == "backwards"):
  474.         if (cross2.kind == "normal"):
  475.             return normBackIntersect(cross2,cross1,wall,seg)
  476.         elif (cross2.kind == "behind"):
  477.             return behindBackIntersect(cross2,cross1,wall,seg)
  478.         elif (cross2.kind == "backwards"):
  479.             return backBackIntersect(cross1,cross2,wall,seg)
  480.     elif (cross1.kind == "infinity"):
  481.         if (cross2.kind == "normal"):
  482.             return normInfIntersect(cross2,cross1,wall,seg)
  483.         elif (cross2.kind == "behind"):
  484.             return behindInfIntersect(cross2,cross1,wall,seg)
  485.  
  486. def normNormIntersect(cross1,cross2,wall,seg):
  487.     # sanity check
  488.     if ((type(cross1) != Intersection) or (type(cross2) != Intersection)):
  489.         assert(False), "received non-intersections"
  490.     if (type(seg) != Seg):
  491.         assert(False), "received non-seg"
  492.     if (seg.isVert):
  493.         return normNormVertIntersect(cross1,cross2,seg)
  494.     elif (seg.isHoriz):
  495.         return normNormHorizIntersect(cross1,cross2,seg)
  496.  
  497. def normNormHorizIntersect(cross1,cross2,seg):
  498.     crossPoint1 = cross1.point
  499.     crossPoint2 = cross2.point
  500.     segSet = set([seg.p1, seg.p2])
  501.     crossSet = set([crossPoint1, crossPoint2])
  502.     (minSegPoint, maxSegPoint) = extremeX(segSet)
  503.     (minCrossPoint, maxCrossPoint) = extremeX(crossSet)
  504.     if (minCrossPoint.x <= minSegPoint.x):
  505.         if (maxCrossPoint.x < minSegPoint.x):
  506.             # nothing obscured
  507.             return set([seg])
  508.         elif (maxCrossPoint.x < maxSegPoint.x):
  509.             # obscured on left
  510.             return set([Seg(maxCrossPoint, maxSegPoint, seg.color)])
  511.         else:
  512.             # entirely obscured
  513.             return set()
  514.     elif (minCrossPoint.x < maxSegPoint.x):
  515.         if (maxCrossPoint.x < maxSegPoint.x):
  516.             # centrally obscured
  517.             return set([Seg(minSegPoint,minCrossPoint, seg.color),
  518.                         Seg(maxCrossPoint,maxSegPoint, seg.color)])
  519.         else:
  520.             # obscured on right
  521.             return set([Seg(minSegPoint,minCrossPoint, seg.color)])
  522.     else:
  523.         return set([seg])
  524.  
  525. def normNormVertIntersect(cross1,cross2,seg):
  526.     crossPoint1 = cross1.point
  527.     crossPoint2 = cross2.point
  528.     segSet = set([seg.p1, seg.p2])
  529.     crossSet = set([crossPoint1, crossPoint2])
  530.     (minSegPoint, maxSegPoint) = extremeY(segSet)
  531.     (minCrossPoint, maxCrossPoint) = extremeY(crossSet)
  532.     if (minCrossPoint.y <= minSegPoint.y):
  533.         if (maxCrossPoint.y < minSegPoint.y):
  534.             # nothing obscured
  535.             return set([seg])
  536.         elif (maxCrossPoint.y < maxSegPoint.y):
  537.             # obscured on top
  538.             return set([Seg(maxCrossPoint, maxSegPoint, seg.color)])
  539.         else:
  540.             # entirely obscured
  541.             return set()
  542.     elif (minCrossPoint.y < maxSegPoint.y):
  543.         if (maxCrossPoint.y < maxSegPoint.y):
  544.             # centrally obscured
  545.             return set([Seg(minSegPoint,minCrossPoint, seg.color),
  546.                         Seg(maxCrossPoint,maxSegPoint, seg.color)])
  547.         else:
  548.             # obscured on bottom
  549.             return set([Seg(minSegPoint,minCrossPoint, seg.color)])
  550.     else:
  551.         return set([seg])
  552.  
  553. def normBehindIntersect(cross,behindCross,wall,seg):
  554.     newCross = intersectWalls(wall,seg)
  555.     newIntersection = Intersection(newCross, "normal")
  556.     return normNormIntersect(cross, newIntersection, wall, seg)
  557.  
  558. def normBackIntersect(cross,backCross,wall,seg):
  559.     # we want to find the remaining portion of the seg
  560.     #  on the opposite side of the backCross
  561.     if (seg.isVert):
  562.         return normBackVertIntersect(cross,backCross,wall,seg)
  563.     elif (seg.isHoriz):
  564.         return normBackHorizIntersect(cross,backCross,wall,seg)
  565.     else:
  566.         assert(False), "seg should be vert or horiz"
  567.  
  568. def normBackVertIntersect(normCross, backCross, wall, seg):
  569.     cross = intersectWalls(wall, seg)
  570.     segSet = set([seg.p1, seg.p2])
  571.     (minSegPoint, maxSegPoint) = extremeY(segSet)
  572.     if (backCross.point.y < cross.y):
  573.         # want bottom half of line
  574.         botPoint = extremeY(set([minSegPoint, normCross.point]))[0] # min
  575.         topPoint = extremeY(set([maxSegPoint, normCross.point]))[0] # min
  576.         if (topPoint.y < botPoint.y):
  577.             return set()
  578.         else:
  579.             return set([Seg(botPoint, topPoint, seg.color)])
  580.     else:
  581.         # want top half of line
  582.         botPoint = extremeY(set([minSegPoint, normCross.point]))[1] # max
  583.         topPoint = extremeY(set([maxSegPoint, normCross.point]))[1] # max
  584.         if (topPoint.y < botPoint.y):
  585.             return set()
  586.         else:
  587.             return set([Seg(botPoint, topPoint, seg.color)])
  588.  
  589. def normBackHorizIntersect(normCross, backCross, wall, seg):
  590.     cross = intersectWalls(wall, seg)
  591.     segSet = set([seg.p1, seg.p2])
  592.     (minSegPoint, maxSegPoint) = extremeX(segSet)
  593.     if (backCross.point.x < cross.x):
  594.         # want left half of line
  595.         leftPoint = extremeX(set([minSegPoint, normCross.point]))[0] # min
  596.         rightPoint = extremeX(set([maxSegPoint, normCross.point]))[0] # min
  597.         if (rightPoint.x <= leftPoint.x):
  598.             return set()
  599.         else:
  600.             return set([Seg(leftPoint, rightPoint, seg.color)])
  601.     else:
  602.         # want right half of line
  603.         leftPoint = extremeX(set([minSegPoint, normCross.point]))[1] # max
  604.         rightPoint = extremeX(set([maxSegPoint, normCross.point]))[1] # max
  605.         if (rightPoint.x <= leftPoint.x):
  606.             return set()
  607.         else:
  608.             return set([Seg(leftPoint, rightPoint, seg.color)])
  609.    
  610.  
  611. def normInfIntersect(cross,infCross,wall,seg):
  612.     # we want to find the remaining portion of the seg
  613.     #  on the opposite side of the infCross
  614.     if (seg.isVert):
  615.         return normInfVertIntersect(cross,infCross,wall,seg)
  616.     elif (seg.isHoriz):
  617.         return normInfHorizIntersect(cross,infCross,wall,seg)
  618.     else:
  619.         assert(False), "seg should be vert or horiz"
  620.  
  621. def normInfVertIntersect(cross, infCross, wall, seg):
  622.     segSet = set([seg.p1, seg.p2])
  623.     (minSegPoint, maxSegPoint) = extremeY(segSet)
  624.     if (infCross.point.y > 0):
  625.         # obscured above cross
  626.         topPoint = extremeY(set([maxSegPoint, cross.point]))[0] # min
  627.         botPoint = extremeY(set([minSegPoint, cross.point]))[0] # min
  628.         if (topPoint.y < botPoint.y):
  629.             return set()
  630.         else:
  631.             return set([Seg(botPoint, topPoint, seg.color)])
  632.     elif (infCross.point.y < 0):
  633.         # obscured below cross
  634.         topPoint = extremeY(set([maxSegPoint, cross.point]))[1] # max
  635.         botPoint = extremeY(set([minSegPoint, cross.point]))[1] # max
  636.         if (topPoint.y < botPoint.y):
  637.             return set()
  638.         else:
  639.             return set([Seg(botPoint, topPoint, seg.color)])
  640.     else:
  641.         assert(False), "infCross should be vertical"
  642.  
  643.  
  644. def normInfHorizIntersect(cross, infCross, wall, seg):
  645.     segSet = set([seg.p1, seg.p2])
  646.     (minSegPoint, maxSegPoint) = extremeX(segSet)
  647.     if (infCross.point.x > 0):
  648.         # obscured to right of cross
  649.         rightPoint = extremeX(set([maxSegPoint, cross.point]))[0] # min
  650.         leftPoint = extremeX(set([minSegPoint, cross.point]))[0] # min
  651.         if (rightPoint.x < leftPoint.x):
  652.             return set()
  653.         else:
  654.             return set([Seg(leftPoint, rightPoint, seg.color)])
  655.     elif (infCross.point.x < 0):
  656.         # obscured to left of cross
  657.         rightPoint = extremeX(set([maxSegPoint, cross.point]))[1] # max
  658.         leftPoint = extremeX(set([minSegPoint, cross.point]))[1] # max
  659.         if (rightPoint.x < leftPoint.x):
  660.             return set()
  661.         else:
  662.             return set([Seg(leftPoint, rightPoint, seg.color)])
  663.     else:
  664.         assert(False), "infCross should be horizontal"
  665.  
  666. def behindBehindIntersect(behindCross1,behindCross2,wall,seg):
  667.     # the (obstructing) wall is behind the seg
  668.     # so nothing is obstructed
  669.     return set([seg])
  670.  
  671. def behindBackIntersect(behindCross,backCross,wall,seg):
  672.     # requires a picture to understand:
  673.     #  *** ###|
  674.     #  ***.###|
  675.     #  *** ###|
  676.     # if . is the eye and | represents the obstructing wall:
  677.     #  backCross must be in the * section
  678.     #  behindCross must be in the # section
  679.     # (The eye may not be in a seg)
  680.     # We must remove the part of the seg that extends beyond the wall
  681.     if (seg.isVert):
  682.         return behindBackVertIntersect(behindCross,backCross,wall,seg)
  683.     elif (seg.isHoriz):
  684.         return behindBackHorizIntersect(behindCross,backCross,wall,seg)
  685.     else:
  686.         assert(False), "seg should be vert or horiz"
  687.  
  688. def behindBackVertIntersect(behindCross, backCross, wall, seg):
  689.     newCross = intersectWalls(wall, seg)
  690.     crossSet = set([newCross, behindCross.point, backCross.point])
  691.     (minCrossPoint, maxCrossPoint) = extremeY(crossSet)
  692.     if (wall.p1.y > behindCross.point.y):
  693.         # wall crosses above
  694.         # (could choose backCross, also)
  695.         # quick check
  696.         (botSegPoint,topSegPoint) = extremeY(set([seg.p1,seg.p2]))
  697.         topPoint = extremeY(set([topSegPoint, newCross]))[0] # min
  698.         if (botSegPoint.y >= topPoint.y):
  699.             return set()
  700.         else:
  701.             return set([Seg(botSegPoint, topPoint, seg.color)])
  702.     else:
  703.         (botSegPoint,topSegPoint) = extremeY(set([seg.p1,seg.p2]))
  704.         botPoint = extremeY(set([botSegPoint, newCross]))[1] # max
  705.         if (topSegPoint.y <= botPoint.y):
  706.             return set()
  707.         else:
  708.             return set([Seg(botPoint, topSegPoint, seg.color)])
  709.  
  710.  
  711. def behindBackHorizIntersect(behindCross, backCross, wall, seg):
  712.     newCross = intersectWalls(wall, seg)
  713.     crossSet = set([newCross, behindCross.point, backCross.point])
  714.     (minCrossPoint, maxCrossPoint) = extremeX(crossSet)
  715.     if (wall.p1.x > behindCross.point.x):
  716.         # wall crosses to right
  717.         # (could choose backCross, also)
  718.         # quick check
  719.         (botSegPoint,topSegPoint) = extremeX(set([seg.p1,seg.p2]))
  720.         topPoint = extremeX(set([topSegPoint, newCross]))[0] # min
  721.         if (botSegPoint.x >= topPoint.x):
  722.             return set()
  723.         else:
  724.             return set([Seg(botSegPoint, topPoint, seg.color)])
  725.     else:
  726.         (botSegPoint,topSegPoint) = extremeX(set([seg.p1,seg.p2]))
  727.         botPoint = extremeX(set([botSegPoint, newCross]))[1] # max
  728.         if (topSegPoint.x <= botPoint.x):
  729.             return set()
  730.         else:
  731.             return set([Seg(botPoint, topSegPoint, seg.color)])
  732.  
  733.  
  734.  
  735. def behindInfIntersect(behindCross,infCross,wall,seg):
  736.     # requires a picture:
  737.     #  .###|
  738.     #  *###|
  739.     #  *###|
  740.     # the infCross must be in the * section
  741.     # the behindCross must be in the * section
  742.     # any portion of the segment that extends beyond the wall is obscured
  743.     #  just like with the behindBackIntersect
  744.     if (seg.isVert):
  745.         return behindInfVertIntersect(behindCross,infCross,wall,seg)
  746.     elif (seg.isHoriz):
  747.         return behindInfHorizIntersect(behindCross,infCross,wall,seg)
  748.     else:
  749.         assert(False), "seg should be vert or horiz"
  750.  
  751.  
  752. def behindInfVertIntersect(behindCross,infCross,wall,seg):
  753.     segPointSet = set([seg.p1, seg.p2])
  754.     (minSegPoint, maxSegPoint) = extremeY(segPointSet)
  755.     cross = intersectWalls(wall,seg)
  756.     if (infCross.point.y > 0):
  757.         # wall above eye
  758.         topPoint = extremeY(set([maxSegPoint, cross]))[0] # min
  759.         botPoint = extremeY(set([minSegPoint, cross]))[0] # min
  760.         if (topPoint.y < botPoint.y):
  761.             return set()
  762.         else:
  763.             return set([Seg(botPoint, topPoint, seg.color)])
  764.     elif (infCross.point.y < 0):
  765.         # wall below eye
  766.         topPoint = extremeY(set([maxSegPoint, cross]))[1] # max
  767.         botPoint = extremeY(set([minSegPoint, cross]))[1] # max
  768.         if (topPoint.y < botPoint.y):
  769.             return set()
  770.         else:
  771.             return set([Seg(botPoint, topPoint, seg.color)])
  772.     else:
  773.         assert(False), "infCross should be vertical"
  774.        
  775.  
  776. def behindInfHorizIntersect(behindCross,infCross,wall,seg):
  777.     segPointSet = set([seg.p1, seg.p2])
  778.     (minSegPoint, maxSegPoint) = extremeX(segPointSet)
  779.     cross = intersectWalls(wall,seg)
  780.     if (infCross.point.x > 0):
  781.         # wall to right of eye
  782.         leftPoint = extremeX(set([minSegPoint, cross]))[0] # min
  783.         rightPoint = extremeX(set([maxSegPoint, cross]))[0] # min
  784.         if (rightPoint.x < leftPoint.x):
  785.             return set()
  786.         else:
  787.             return set([Seg(leftPoint, rightPoint, seg.color)])
  788.     elif (infCross.point.x < 0):
  789.         # wall to left of eye
  790.         leftPoint = extremeX(set([minSegPoint, cross]))[1] # max
  791.         rightPoint = extremeX(set([maxSegPoint, cross]))[1] # max
  792.         if (rightPoint.x < leftPoint.x):
  793.             return set()
  794.         else:
  795.             return set([Seg(leftPoint, rightPoint, seg.color)])
  796.     else:
  797.         assert(False), "infCross should be horizontal"
  798.  
  799. def backBackIntersect(backCross1,backCross2,wall,seg):
  800.     return set([seg])
  801.            
  802. ################################################################################
  803. ##### Total Visibility of a Segment ############################################
  804. ################################################################################
  805.  
  806. def obstructSeg(eye, wall, seg):
  807.     """Given an eye, a certain seg, and an (obstructing) wall, this returns
  808.     the remaining visible portion of the seg as a set of segments (or an empty
  809.     set)."""
  810.     ray1 = Ray(eye, wall.p1)
  811.     ray2 = Ray(eye, wall.p2)
  812.     cross1 = intersectRayAndRookSeg(ray1, seg)
  813.     cross2 = intersectRayAndRookSeg(ray2, seg)
  814.     if ((type(cross1) == Seg) or (type(cross2) == Seg)):
  815.         # something obscured entire segment
  816.         # NOTE: There is a small side effect, since
  817.         # the entire seg is returned even if the obstruction lies behind
  818.         # however, the seg must be viewed straight on for this to happen
  819.         #, so in the 3D case, it doesn't matter
  820.         return set()
  821.     return obstructViaIntersections(cross1, cross2, wall, seg)
  822.  
  823. def obstructSegViaSegSet(eye, segSet, seg):
  824.     """Given an eye, a certain seg, and a set of other segs, this returns the
  825.     remaining visible portion of the specific seg when obstructed by the whole
  826.     set."""
  827.     # sanity check
  828.     if (type(seg) != Seg): assert(False), "seg not of type Seg"
  829.     if (type(segSet) != set): assert(False), "segSet not of type set"
  830.     if (type(eye) != Point): assert(False), "eye not a Point"
  831.     remainingPieces = set([seg])
  832.     newPieces = set()
  833.     for wall in segSet:
  834.         for piece in remainingPieces:
  835.             newPieces = newPieces | obstructSeg(eye, wall, piece) # union
  836.         remainingPieces = newPieces
  837.         newPieces = set()
  838.     return remainingPieces
  839.  
  840.  
  841. def obstructSegs(eye, segSet):
  842.     """Given an eye and a set of segments, this returns the visible portions
  843.     (as a set) of each segment."""
  844.     visible = set()
  845.     for seg in segSet:
  846.         otherSegs = segSet - set([seg])
  847.         visible = visible.union(obstructSegViaSegSet(eye, otherSegs, seg))
  848.     return visible
  849.  
  850.  
  851. ################################################################################
  852. ##### Camera Class #############################################################
  853. ################################################################################
  854.  
  855. class Camera(object):
  856.     def __init__(self, viewRay):
  857.         if (type(viewRay) != Ray):
  858.             assert(False), "Camera requires Ray"
  859.         self.viewRay = viewRay
  860.         self.rightRay = viewRay.rotate(-math.pi/2)
  861.         self.height = CAM_HEIGHT
  862.  
  863.     def rotate(self, angle):
  864.         # angle in radians
  865.         viewRay = self.viewRay
  866.         self.viewRay = self.viewRay.rotate(angle)
  867.         self.rightRay = self.rightRay.rotate(angle)
  868.  
  869.     def translate(self, vector):
  870.         newX = self.viewRay.eye.x + vector.elements[0]
  871.         newY = self.viewRay.eye.y + vector.elements[1]
  872.         newEye = Point(newX, newY)
  873.         newTargetX = self.viewRay.target.x + vector.elements[0]
  874.         newTargetY = self.viewRay.target.y + vector.elements[1]
  875.         newTarget = Point(newTargetX, newTargetY)
  876.         newRightX = self.rightRay.target.x + vector.elements[0]
  877.         newRightY = self.rightRay.target.y + vector.elements[1]
  878.         newRightTarget = Point(newRightX, newRightY)
  879.         self.viewRay = Ray(newEye, newTarget)
  880.         self.rightRay = Ray(newEye, newRightTarget)
  881.        
  882.  
  883. ################################################################################
  884. ##### Maze Class ###############################################################
  885. ################################################################################
  886.  
  887. class Maze(object):
  888.     def __init__(self, rows, cols):
  889.         (self.rows, self.cols) = (rows, cols)
  890.         self.initCells()
  891.         self.initPoints()
  892.         self.initSegs()
  893.         self.makeMaze()
  894.  
  895.     def initCells(self):
  896.         (rows, cols) = (self.rows, self.cols)
  897.         # more points than cells
  898.         cRows = rows - 1
  899.         cCols = cols - 1
  900.         self.cells = [[i+cCols*j for i in xrange(cCols)] for j in xrange(cRows)]
  901.  
  902.     def initCellsAsOne(self):
  903.         (rows, cols) = (self.rows, self.cols)
  904.         # more points than cells
  905.         cRows = rows - 1
  906.         cCols = cols - 1
  907.         self.cells = [[1]*cCols for i in xrange(cRows)]
  908.  
  909.     def initPoints(self):
  910.         (rows, cols) = (self.rows, self.cols)
  911.         self.points = [[0]*cols for i in xrange(rows)]
  912.         for row in xrange(rows):
  913.             for col in xrange(cols):
  914.                 self.points[row][col] = Point(row, col)
  915.  
  916.     def initSegs(self):
  917.         # we start with all possible segments
  918.         (rows, cols) = (self.rows, self.cols)
  919.         self.segs = list()
  920.         for row in xrange(rows):
  921.             for col in xrange(cols):
  922.                 curPoint = Point(row,col)
  923.                 color = makeColor(row, col, rows, cols)
  924.                 if (row + 1 < rows):
  925.                     nextPoint = Point(row+1,col)
  926.                     self.segs.append(Seg(curPoint, nextPoint, color))
  927.                 if (col + 1 < cols):
  928.                     nextPoint = Point(row,col+1)
  929.                     self.segs.append(Seg(curPoint, nextPoint, color))
  930.  
  931.     def removeSeg(self, seg, cellVal1, cellVal2):
  932.         if (seg in self.segs):
  933.             self.segs.remove(seg)
  934.         self.renameCells(cellVal1, cellVal2)
  935.  
  936.     def renameCells(self, cellVal1, cellVal2):
  937.         (cRows, cCols) = (self.rows - 1, self.cols - 1)
  938.         (fromVal, toVal) = (max(cellVal1, cellVal2), min(cellVal1, cellVal2))
  939.         for row in xrange(cRows):
  940.             for col in xrange(cCols):
  941.                 if (self.cells[row][col] == fromVal):
  942.                     self.cells[row][col] = toVal
  943.  
  944.     def isFinishedMaze(self):
  945.         (cRows, cCols) = (self.rows - 1, self.cols - 1)
  946.         for row in xrange(cRows):
  947.             for col in xrange(cCols):
  948.                 if (self.cells[row][col] != 0):
  949.                     return False
  950.         return True
  951.            
  952.  
  953.     def makeMaze(self):
  954.         # I am borrowing heavily from the algorithm used here:
  955.         # kosbie.net/cmu/fall-12/15-112/handouts/notes-recursion/mazeSolver.py
  956.         (rows, cols) = (self.rows, self.cols)
  957.         (cRows, cCols) = (rows-1, cols-1)
  958.         while (not self.isFinishedMaze()):
  959.             cRow = random.randint(0, cRows-1)
  960.             cCol = random.randint(0, cCols-1)
  961.             curCell = self.cells[cRow][cCol]
  962.             if flipCoin(): # try to go east
  963.                 if (cCol == cCols - 1): continue # at edge
  964.                 targetCell = self.cells[cRow][cCol + 1]
  965.                 dividingSeg = Seg(Point(cRow,cCol+1),
  966.                                   Point(cRow+1,cCol+1))
  967.                 if (curCell == targetCell):
  968.                     if (dividingSeg in self.segs):
  969.                         if (smallChance()):
  970.                             self.removeSeg(dividingSeg, curCell, targetCell)
  971.                 else:
  972.                     self.removeSeg(dividingSeg, curCell, targetCell)
  973.             else: # try to go north
  974.                 if (cRow == cRows - 1): continue # at edge
  975.                 targetCell = self.cells[cRow+1][cCol]
  976.                 dividingSeg = Seg(Point(cRow+1,cCol),
  977.                                   Point(cRow+1,cCol+1))
  978.                 if (curCell == targetCell):
  979.                     continue
  980.                 else:
  981.                     self.removeSeg(dividingSeg, curCell, targetCell)
  982.  
  983.     def deadCornerCell(self, row, col, dir):
  984.         (rows, cols) = (self.rows, self.cols)
  985.         (cRows, cCols) = (rows - 1, cols - 1)
  986.         if (dir == "UL"):
  987.             # checking to the upper left
  988.             # if shielded by dead cells to the bottom right, this is dead
  989.             rightCell = self.cells[row][col+1]
  990.             downCell = self.cells[row-1][col]
  991.             return (((self.hasSeg(row, col, "right")) or (rightCell == 0)) and
  992.                     ((self.hasSeg(row, col, "down")) or (downCell == 0)))
  993.         elif (dir == "UR"):
  994.             leftCell = self.cells[row][col-1]
  995.             downCell = self.cells[row-1][col]
  996.             return (((self.hasSeg(row, col, "left")) or (leftCell == 0)) and
  997.                     ((self.hasSeg(row, col, "down")) or (downCell == 0)))
  998.         elif (dir == "DL"):
  999.             rightCell = self.cells[row][col+1]
  1000.             upCell = self.cells[row+1][col]
  1001.             return (((self.hasSeg(row, col, "right")) or (rightCell == 0)) and
  1002.                     ((self.hasSeg(row, col, "up")) or (upCell == 0)))
  1003.         elif (dir == "DR"):
  1004.             leftCell = self.cells[row][col-1]
  1005.             upCell = self.cells[row+1][col]
  1006.             return (((self.hasSeg(row, col, "left")) or (leftCell == 0)) and
  1007.                     ((self.hasSeg(row, col, "up")) or (upCell == 0)))
  1008.         else:
  1009.             assert(False), "not a direction"
  1010.  
  1011.  
  1012.  
  1013.         return False
  1014.  
  1015.     def cullCorners(self, eye):
  1016.         eyeRow = int(math.floor(eye.y))
  1017.         eyeCol = int(math.floor(eye.x))
  1018.         (rows, cols) = (self.rows, self.cols)
  1019.         (cRows, cCols) = (rows - 1, cols - 1)
  1020.         # xranges are reversed so that we check progressively
  1021.         # further from the eye (since this process "cascades")
  1022.         culledFlag = False
  1023.         # bottom left
  1024.         if ((eyeRow != 0) and (eyeCol != 0)):
  1025.             for row in xrange(eyeRow-1, -1, -1):
  1026.                 for col in xrange(eyeCol-1, -1, -1):
  1027.                     if (self.deadCornerCell(row, col, "DL")):
  1028.                         if (self.cells[row][col] != 0):
  1029.                             self.cells[row][col] = 0 # dead
  1030.                             culledFlag = True
  1031.         # bottom right
  1032.         if ((eyeRow != 0) and (eyeCol != cCols)):
  1033.             for row in xrange(eyeRow-1, -1, -1):
  1034.                 for col in xrange(eyeCol+1, cCols):
  1035.                     if (self.deadCornerCell(row, col, "DR")):
  1036.                         if (self.cells[row][col] != 0):
  1037.                             self.cells[row][col] = 0 # dead
  1038.                             culledFlag = True
  1039.         # top left
  1040.         if ((eyeRow != cRows) and (eyeCol != 0)):
  1041.             for row in xrange(eyeRow+1, cRows):
  1042.                 for col in xrange(eyeCol-1, -1, -1):
  1043.                     if (self.deadCornerCell(row, col, "UL")):
  1044.                         if (self.cells[row][col] != 0):
  1045.                             self.cells[row][col] = 0 # dead
  1046.                             culledFlag = True
  1047.         # top right
  1048.         if ((eyeRow != cRows) and (eyeCol != cCols)):
  1049.             for row in xrange(eyeRow+1, cRows):
  1050.                 for col in xrange(eyeCol+1, cCols):
  1051.                     if (self.deadCornerCell(row, col, "UR")):
  1052.                         if (self.cells[row][col] != 0):
  1053.                             self.cells[row][col] = 0 # dead
  1054.                             culledFlag = True
  1055.         return culledFlag # something was deleted
  1056.                
  1057.  
  1058.        
  1059.     def removeDeadSandwichedSegs(self):
  1060.         (rows, cols) = (self.rows, self.cols)
  1061.         (cRows, cCols) = (rows - 1, cols - 1)
  1062.         # check right
  1063.         for row in xrange(cRows):
  1064.             for col in xrange(cCols - 1):
  1065.                 if (self.cells[row][col] == self.cells[row][col+1] == 0):
  1066.                     deadSeg = Seg(Point(col+1, row), Point(col+1, row+1))
  1067.                     if (deadSeg in self.checkSegs):
  1068.                         self.checkSegs.remove(deadSeg)
  1069.         # check far right
  1070.         for row in xrange(cRows):
  1071.             if (self.cells[row][cCols-1] == 0):
  1072.                 deadSeg = Seg(Point(cCols+1, row), Point(cCols+1, row+1))
  1073.                 if (deadSeg in self.checkSegs):
  1074.                     self.checkSegs.remove(deadSeg)
  1075.         # check up
  1076.         for row in xrange(cRows - 1):
  1077.             for col in xrange(cCols):
  1078.                 if (self.cells[row][col] == self.cells[row+1][col] == 0):
  1079.                     deadSeg = Seg(Point(col, row+1), Point(col+1, row+1))
  1080.                     if (deadSeg in self.checkSegs):
  1081.                         self.checkSegs.remove(deadSeg)
  1082.         # check far top
  1083.         for col in xrange(cCols):
  1084.             if (self.cells[cRows-1][col] == 0):
  1085.                 deadSeg = Seg(Point(col, cRows+1), Point(col+1, cRows+1))
  1086.                 if (deadSeg in self.checkSegs):
  1087.                     self.checkSegs.remove(deadSeg)
  1088.         return None
  1089.                        
  1090.                    
  1091.  
  1092.     def hasSeg(self, row, col, dir):
  1093.         y = row
  1094.         x = col
  1095.         if (dir == "left"):
  1096.             return (Seg(Point(x, y), Point(x, y+1)) in self.checkSegs)
  1097.         elif (dir == "right"):
  1098.             return (Seg(Point(x+1,y), Point(x+1,y+1)) in self.checkSegs)
  1099.         elif (dir == "up"):
  1100.             return (Seg(Point(x, y+1), Point(x+1, y+1)) in self.checkSegs)
  1101.         elif (dir == "down"):
  1102.             return (Seg(Point(x, y), Point(x+1, y)) in self.checkSegs)
  1103.         else:
  1104.             assert(False), "not a direction"
  1105.  
  1106.     def deleteCellsInDir(self, delRow, delCol, dir):
  1107.         # destructive function
  1108.         (rows, cols) = (self.rows, self.cols)
  1109.         (cRows, cCols) = (rows - 1, cols - 1)
  1110.         if ((delRow == cRows) or (delRow < 0) or
  1111.             (delCol == cCols) or (delCol < 0)):
  1112.             # out of bounds
  1113.             return None
  1114.         if (dir == "left"):
  1115.             for col in xrange(0, delCol+1):
  1116.                 self.cells[delRow][col] = 0
  1117.         elif (dir == "right"):
  1118.             for col in xrange(delCol, cCols):
  1119.                 self.cells[delRow][col] = 0
  1120.         elif (dir == "down"):
  1121.             for row in xrange(0, delRow+1):
  1122.                 self.cells[row][delCol] = 0
  1123.         elif (dir == "up"):
  1124.             for row in xrange(delRow, cRows):
  1125.                 self.cells[row][delCol] = 0
  1126.         else:
  1127.             assert(False), "not a direction"
  1128.  
  1129.  
  1130.     def cullSegs(self, eye):
  1131.         # only return segs which could possibly be visible to reduce
  1132.         # render time
  1133.         # mark all cells as 1 (alive)
  1134.         # we will mark cells as 0 (dead) if they cannot possible be seen
  1135.         # walls sandwiched between dead cells are invisible and will be culled
  1136.         eyeRow = int(math.floor(eye.y))
  1137.         eyeCol = int(math.floor(eye.x))
  1138.         (rows, cols) = (self.rows, self.cols)
  1139.         (cRows, cCols) = (rows - 1, cols - 1)
  1140.         self.initCellsAsOne()
  1141.         self.checkSegs = copy.copy(self.segs)
  1142.         for col in xrange(eyeCol, cCols):
  1143.             if self.hasSeg(eyeRow, col, "right"):
  1144.                 self.deleteCellsInDir(eyeRow, col+1, "right")
  1145.                 break
  1146.         for col in xrange(eyeCol, -1, -1):
  1147.             if self.hasSeg(eyeRow, col, "left"):
  1148.                 self.deleteCellsInDir(eyeRow, col-1, "left")
  1149.                 break
  1150.         for row in xrange(eyeRow, cRows):
  1151.             if self.hasSeg(row, eyeCol, "up"):
  1152.                 self.deleteCellsInDir(row+1, eyeCol, "up")
  1153.                 break
  1154.         for row in xrange(eyeRow, -1, -1):
  1155.             if self.hasSeg(row, eyeCol, "down"):
  1156.                 self.deleteCellsInDir(row-1, eyeCol, "down")
  1157.                 break
  1158.         while(self.cullCorners(eye)):
  1159.             # cullCorners will remove cells invisible by a corner
  1160.             # it will return true if something was removed
  1161.             pass
  1162.         self.removeDeadSandwichedSegs()
  1163.         # will remove segs sandwiched between dead cells
  1164.         return set(self.checkSegs)
  1165.  
  1166.  
  1167. ################################################################################
  1168. ##### Animation Class ##########################################################
  1169. ################################################################################
  1170.  
  1171.  
  1172. # taken from jbahr-hw9.py
  1173. class Animation(object):
  1174.     def __init__(self, width=500, height=300):
  1175.         self.root = Tk()
  1176.         self.width = width
  1177.         self.height = height
  1178.         self.canvas = Canvas(self.root, width=self.width, height=self.height)
  1179.         self.canvas.pack()
  1180.         self.init()
  1181.         self.root.bind("<KeyPress>", self.keyPressed)
  1182.         self.root.bind("<KeyRelease>", self.keyReleased)
  1183.         self.root.bind("<Button-1>", self.mousePressed)
  1184.  
  1185.     def run(self):
  1186.         self.timerFired()
  1187.         self.root.mainloop()
  1188.  
  1189.     def init(self):
  1190.         pass
  1191.  
  1192.     def redrawAll(self):
  1193.         pass
  1194.  
  1195.     def keyPressed(self, event):
  1196.         pass
  1197.  
  1198.     def keyReleased(self, event):
  1199.         pass
  1200.  
  1201.     def mousePressed(self, event):
  1202.         pass
  1203.  
  1204. ################################################################################
  1205. ##### MazeGame Animation Class #################################################
  1206. ################################################################################
  1207.  
  1208.  
  1209. class MazeGame(Animation):
  1210.     def __init__(self, mazeSize, width=700, height=500):
  1211.         self.mazeRows = mazeSize
  1212.         self.mazeCols = mazeSize
  1213.         self.mode = "3D"
  1214.         super(MazeGame, self).__init__(width, height)
  1215.         self.root.resizable(width=0, height=0) # non-resizable
  1216.  
  1217. ######################
  1218. ####### Model ########
  1219. ######################
  1220.  
  1221.     def init(self):
  1222.         self.isGameOver = False
  1223.         self.isHelp = True
  1224.         self.initMaze()
  1225.         self.initCamera()
  1226.  
  1227.     def initCamera(self):
  1228.         self.speed = 0.3
  1229.         self.rotateSpeed = math.pi/10
  1230.         self.cameraLength = CAM_LENGTH
  1231.         self.cameraSep = CAM_SEP
  1232.         # we start closest to (0,0)
  1233.         # facing in the x direction
  1234.         # check if facing wall
  1235.         startPoint = Point(0.5, 0.5)
  1236.         if (Seg(Point(0,1),Point(1,1)) in self.maze.segs):
  1237.             secondCamStart = Point(0.5, 0.5 - self.cameraSep)
  1238.             secondCamView = Point(0.5 + self.cameraLength, 0.5 - self.cameraSep)
  1239.             viewPoint = Point(0.5 + self.cameraLength, 0.5)
  1240.         else:
  1241.             secondCamStart = Point(0.5 + self.cameraSep, 0.5)
  1242.             secondCamView = Point(0.5 + self.cameraSep, 0.5 + self.cameraLength)
  1243.             viewPoint = Point(0.5, 0.5 + self.cameraLength)
  1244.         self.camera = Camera(Ray(startPoint, viewPoint))
  1245.         self.secondCamera = Camera(Ray(secondCamStart, secondCamView))
  1246.         self.cameraVel = 0
  1247.         self.sideCameraVel = 0
  1248.         self.cameraRotVel = 0
  1249.  
  1250.     def initMaze(self):
  1251.         print "Generating random maze..."
  1252.         self.maze = Maze(self.mazeRows, self.mazeCols)
  1253.         print "Finished generating maze!"
  1254.  
  1255.  
  1256. ######################
  1257. ##### Controller #####
  1258. ######################
  1259.  
  1260.     def timerFired(self):
  1261.         if (self.mode == "3D"):
  1262.             self.screenSegs = set()
  1263.             self.visibleSegs = set()
  1264.             self.circularVisibleSegs = set()
  1265.             (self.visibleSegs,
  1266.              self.circularVisibleSegs) = self.firstPersonVisibleSegs(self.camera)
  1267.             self.projectVisibleSegsToScreen(self.camera,
  1268.                                             self.screenSegs,
  1269.                                             self.visibleSegs)
  1270.         elif (self.mode == "3DG"):
  1271.             self.screenSegs = set()
  1272.             self.secScreenSegs = set()
  1273.             self.visibleSegs = set()
  1274.             self.secVisibleSegs = set()
  1275.             self.circularVisibleSegs = set()
  1276.             self.secCircularVisibleSegs = set()
  1277.             (self.visibleSegs, self.circularVisibleSegs) = \
  1278.                                         self.firstPersonVisibleSegs(self.camera)
  1279.             (self.secVisibleSegs, self.secCircularVisibleSegs) = \
  1280.                     self.firstPersonVisibleSegs(self.secondCamera)
  1281.             self.projectVisibleSegsToScreen(self.camera,
  1282.                                             self.screenSegs,
  1283.                                             self.visibleSegs)
  1284.             self.projectVisibleSegsToScreen(self.secondCamera,
  1285.                                             self.secScreenSegs,
  1286.                                             self.secVisibleSegs)
  1287.         elif (self.mode == "2D"):
  1288.             self.topDownVisibleSegs()
  1289.         self.updateCamera()
  1290.         self.isWin()
  1291.         self.redrawAll()
  1292.         delay = 1 # ms
  1293.         self.canvas.after(delay, self.timerFired)
  1294.  
  1295.     def topDownVisibleSegs(self):
  1296.         eye = self.camera.viewRay.eye
  1297.         possibleSegs = self.maze.cullSegs(eye)
  1298.         self.circularVisibleSegs = obstructSegs(eye, possibleSegs)
  1299.  
  1300.     def firstPersonVisibleSegs(self, cam):
  1301.         # check if each seg in visibleSegs is within 90 degrees of cam.viewRay
  1302.         # given visSegs to store visibleSegs and circSegs to store
  1303.         # circularly visible segs
  1304.         visSegs = set()
  1305.         circSegs = set()
  1306.         eye = cam.viewRay.eye
  1307.         possibleSegs = self.maze.cullSegs(eye)
  1308.         circSegs = obstructSegs(eye, possibleSegs) # visible in 360
  1309.         visSegs = set()
  1310.         for seg in circSegs:
  1311.             ray1 = Ray(eye, seg.p1)
  1312.             ray2 = Ray(eye, seg.p2)
  1313.             angle1 = abs(ray1.angle(cam.viewRay))
  1314.             angle2 = abs(ray2.angle(cam.viewRay))
  1315.             screenEye = cam.viewRay.target
  1316.             screenDir = (cam.rightRay.dx, cam.rightRay.dy)
  1317.             rightPoint = Point(screenEye.x+screenDir[0], screenEye.y+screenDir[1])
  1318.             screenRay = Ray(screenEye, rightPoint)
  1319.             viewRay = cam.viewRay
  1320.             if ((angle1 < FOV) and (angle2 < FOV)):
  1321.                 visSegs.add(seg)
  1322.             elif ((angle1 >= FOV) and (angle2 < FOV)):
  1323.                 newIntersect = intersectRayAndRookSeg(screenRay, seg)
  1324.                 newPoint = newIntersect.point
  1325.                 # check for special case
  1326.                 if (0 < ray2.dot(viewRay)/viewRay.norm() < viewRay.norm()):
  1327.                     continue
  1328.                 else:
  1329.                     visSegs.add(Seg(newPoint, seg.p2, seg.color))
  1330.             elif ((angle1 < FOV) and (angle2 >= FOV)):
  1331.                 newIntersect = intersectRayAndRookSeg(screenRay, seg)
  1332.                 newPoint = newIntersect.point
  1333.                 # check for special case
  1334.                 if (0 < ray1.dot(viewRay)/viewRay.norm() < viewRay.norm()):
  1335.                     continue
  1336.                 else:
  1337.                     visSegs.add(Seg(seg.p1, newPoint, seg.color))
  1338.             else:
  1339.                 # seg is completely behind
  1340.                 continue
  1341.         return (visSegs, circSegs)
  1342.                
  1343.     def projectVisibleSegsToScreen(self, cam, screenSegs, visSegs):
  1344.         for seg in visSegs:
  1345.             screenSegs.add(ScreenSeg(cam, seg))
  1346.  
  1347.     def updateCamera(self):
  1348.         topDownModes = ["2D"]
  1349.         firstPersonModes = ["3D", "3DG"]
  1350.         if (self.mode in topDownModes):
  1351.             self.topDownUpdateCamera()
  1352.         elif (self.mode in firstPersonModes):
  1353.             self.firstPersonUpdateCamera()
  1354.         else:
  1355.             assert(False), "Not a valid mode"
  1356.  
  1357.  
  1358.     def topDownUpdateCamera(self):
  1359.         self.camera.rotate(self.cameraRotVel)
  1360.         self.secondCamera.rotate(self.cameraRotVel)
  1361.         self.camera.translate(self.cameraVel)
  1362.         self.secondCamera.translate(self.cameraVel)
  1363.         if not self.cameraIsLegal():
  1364.             # move back!
  1365.             self.camera.translate(- self.cameraVel)
  1366.             self.secondCamera.translate(- self.cameraVel)
  1367.  
  1368.  
  1369.     def firstPersonUpdateCamera(self):
  1370.         viewDir = Vector([self.camera.viewRay.dx, self.camera.viewRay.dy])
  1371.         rightDir = Vector([self.camera.rightRay.dx, self.camera.rightRay.dy])
  1372.         velocity = (self.cameraVel/viewDir.norm()) * viewDir
  1373.         sideVel = (self.sideCameraVel/rightDir.norm()) * rightDir
  1374.         newVel = (velocity + sideVel)
  1375.         if (newVel.norm() != 0):
  1376.             oldNorm = max(velocity.norm(), sideVel.norm())
  1377.             newVel = newVel * (oldNorm/newVel.norm())
  1378.         self.camera.rotate(self.cameraRotVel)
  1379.         self.secondCamera.rotate(self.cameraRotVel)
  1380.         self.camera.translate(newVel)
  1381.         self.secondCamera.translate(newVel)
  1382.         if not self.cameraIsLegal():
  1383.             # move back!
  1384.             self.camera.translate(- newVel)
  1385.             self.secondCamera.translate(- newVel)
  1386.  
  1387.  
  1388.     def cameraIsLegal(self):
  1389.         # check that camera is no more than 1.5*self.cameraLength
  1390.         # from any wall
  1391.         cam1 = self.camera
  1392.         cam2 = self.secondCamera
  1393.         for seg in self.circularVisibleSegs:
  1394.             if (seg.withinDist(cam1.viewRay.eye,1.2*self.cameraLength) or
  1395.                 (seg.withinDist(cam2.viewRay.eye, 1.2*self.cameraLength))):
  1396.                 return False
  1397.         return True
  1398.  
  1399.     def isWin(self):
  1400.         # if in last cell, game is won
  1401.         lastX = self.maze.cols - 1
  1402.         lastY = self.maze.rows - 1
  1403.         if ((abs(self.camera.viewRay.eye.x - lastX) < 1) and
  1404.             (abs(self.camera.viewRay.eye.y - lastY) < 1)):
  1405.             self.isGameOver = True
  1406.  
  1407.     def mousePressed(self, event):
  1408.         pass
  1409.  
  1410.     def keyPressed(self, event):
  1411.         firstPersonModes = ["3D", "3DG"]
  1412.         topDownModes = ["2D"]
  1413.         if (self.mode in firstPersonModes):
  1414.             self.firstPersonKeyPressed(event)
  1415.         elif (self.mode in topDownModes):
  1416.             self.topDownKeyPressed(event)
  1417.         else:
  1418.             assert(False), "not a valid mode"
  1419.         if (event.keysym == "h"):
  1420.             # toggle help screen
  1421.             self.isHelp = not self.isHelp
  1422.         elif (event.keysym == "z"):
  1423.             trace()
  1424.         else:
  1425.             self.isHelp = False
  1426.         if (event.keysym == "r"):
  1427.             self.mode = "3D"
  1428.             self.init()
  1429.             self.isHelp = False
  1430.             # restart
  1431.         if (event.keysym == "1"):
  1432.             self.mode = "2D"
  1433.             self.cameraVel = Vector([0,0])
  1434.         elif (event.keysym == "2"):
  1435.             self.mode = "3D"
  1436.             self.cameraVel = 0
  1437.             self.sideCameraVel = 0
  1438.         elif (event.keysym == "3"):
  1439.             self.mode = "3DG"
  1440.             self.cameraVel = 0
  1441.             self.sideCameraVel = 0
  1442.  
  1443.  
  1444.     def firstPersonKeyPressed(self, event):
  1445.         viewDir = Vector([self.camera.viewRay.dx, self.camera.viewRay.dy])
  1446.         if ((event.keysym == "w") or (event.keysym=="Up")):
  1447.             self.cameraVel = self.speed
  1448.         elif ((event.keysym == "s") or (event.keysym=="Down")):
  1449.             self.cameraVel = -self.speed
  1450.         elif ((event.keysym == "d") or (event.keysym=="Right")):
  1451.             # clockwise
  1452.             self.cameraRotVel = self.rotateSpeed
  1453.         elif ((event.keysym == "a") or (event.keysym=="Left")):
  1454.             # counter-clockwise
  1455.             self.cameraRotVel = - self.rotateSpeed
  1456.         elif ((event.keysym == "comma") or (event.keysym=="Prior")):
  1457.             # Prior is page up
  1458.             # sidestep left
  1459.             self.sideCameraVel = self.speed
  1460.         elif ((event.keysym == "period") or (event.keysym=="Next")):
  1461.             # Next is page down
  1462.             self.sideCameraVel = -self.speed
  1463.  
  1464.     def topDownKeyPressed(self, event):
  1465.         up = ["Up", "w"]
  1466.         down = ["Down", "s"]
  1467.         left = ["Left", "a"]
  1468.         right = ["Right", "d"]
  1469.         if (event.keysym in up):
  1470.             # up is down in TkInter
  1471.             self.cameraVel = self.speed * Vector([0,-1])
  1472.         elif (event.keysym in down):
  1473.             # up is down in TkInter
  1474.             self.cameraVel = self.speed * Vector([0,1])
  1475.         elif (event.keysym in right):
  1476.             self.cameraVel = self.speed * Vector([1,0])
  1477.         elif (event.keysym in left):
  1478.             self.cameraVel = self.speed * Vector([-1,0])
  1479.         # ensure no more rotation
  1480.         self.cameraRotVel = 0
  1481.  
  1482.     def keyReleased(self, event):
  1483.         firstPersonModes = ["3D", "3DG"]
  1484.         topDownModes = ["2D"]
  1485.         if (self.mode in firstPersonModes):
  1486.             return self.firstPersonKeyReleased(event)
  1487.         elif (self.mode in topDownModes):
  1488.             return self.topDownKeyReleased(event)
  1489.         else:
  1490.             assert(False), "not a valid mode"
  1491.  
  1492.     def firstPersonKeyReleased(self, event):
  1493.         translations = ["w", "s", "Up", "Down"]
  1494.         sideSteps = ["comma","period","Prior","Next"]
  1495.         rotations = ["a", "d", "Left", "Right"]
  1496.         if (event.keysym in translations):
  1497.             self.cameraVel = 0
  1498.         elif (event.keysym in sideSteps):
  1499.             self.sideCameraVel = 0
  1500.         elif (event.keysym in rotations):
  1501.             self.cameraRotVel = 0
  1502.  
  1503.     def topDownKeyReleased(self, event):
  1504.         translations = ["Up", "Down", "Left", "Right",
  1505.                         "w", "a", "s", "d"]
  1506.         if (event.keysym in translations):
  1507.             self.cameraVel = Vector([0,0])
  1508.  
  1509.  
  1510. ######################
  1511. ######## View ########
  1512. ######################
  1513.  
  1514.     def drawGameOver(self):
  1515.         cx = self.width/2
  1516.         cy = self.height/2
  1517.         self.canvas.create_text(cx, cy, text="You Win!",
  1518.                                 font="Helvetica 36 bold")
  1519.  
  1520.     def drawHelp(self):
  1521.         cx = self.width/2
  1522.         leftcx = self.width/3
  1523.         rightcx = (2*self.width)/3
  1524.         cy = self.height/2
  1525.         self.canvas.create_text(cx,cy*(2./9.),text="3D Maze!",
  1526.                                 font="Helvetica 28",fill="white")
  1527.         self.canvas.create_text(cx,cy/2,
  1528.                                 text="""Find the far corner (the white cell)
  1529. The maze will get greener""",
  1530.                                 font="Helvetica 24",fill="white",
  1531.                                 justify=CENTER)
  1532.         self.canvas.create_text(leftcx, cy, text="""
  1533.         To move
  1534.         To sidestep
  1535.         To switch to 2D mode
  1536.                            3D mode
  1537.                            3D with glasses
  1538.         To toggle help
  1539.         To restart""", font="Helvetica 18",fill="white")
  1540.         self.canvas.create_text(rightcx, cy, text="""
  1541.         WASD or arrow keys
  1542.         ,/. or PgUp/PgDown
  1543.         1
  1544.         2
  1545.         3
  1546.         h
  1547.         r""", font="Helvetica 18",fill="white")
  1548.  
  1549.        
  1550.  
  1551.  
  1552.     def redrawAll(self):
  1553.         self.canvas.delete(ALL)
  1554.         if (self.mode == "2D"):
  1555.             self.redraw2D()
  1556.         elif (self.mode == "3D"):
  1557.             self.redraw3D()
  1558.         elif (self.mode == "3DG"):
  1559.             self.redraw3DG()
  1560.         else:
  1561.             assert(False), "no valid mode"
  1562.         if (self.isHelp):
  1563.             self.drawHelp()
  1564.         elif (self.isGameOver):
  1565.             if (self.mode == "3DG"):
  1566.                 self.draw3DGGameOver()
  1567.             else:
  1568.                 self.drawGameOver()
  1569.  
  1570.     def redraw2D(self):
  1571.         eye = self.camera.viewRay.eye
  1572.         segs = self.circularVisibleSegs
  1573.         cx = self.width/2
  1574.         cy = self.height/2
  1575.         left = cx - (CELL_SIZE*(self.mazeCols - 1))/2
  1576.         top = cy - (CELL_SIZE*(self.mazeRows - 1))/2
  1577.         self.draw2DEye(left, top)
  1578.         for s in segs:
  1579.             self.canvas.create_line(left + CELL_SIZE*s.p1.x,
  1580.                                     top + CELL_SIZE*s.p1.y,
  1581.                                     left + CELL_SIZE*s.p2.x,
  1582.                                     top + CELL_SIZE*s.p2.y,
  1583.                                     fill=s.color, width=2)
  1584.         if (DEBUG):
  1585.             for s in self.maze.segs:
  1586.                 self.canvas.create_line(left + CELL_SIZE*s.p1.x,
  1587.                                         top + CELL_SIZE*s.p1.y,
  1588.                                         left + CELL_SIZE*s.p2.x,
  1589.                                         top + CELL_SIZE*s.p2.y,
  1590.                                         fill="black", width=1)
  1591.  
  1592.     def draw2DEye(self, left, top):
  1593.         eye = self.camera.viewRay.eye
  1594.         target = self.camera.viewRay.target
  1595.         x1 = left + CELL_SIZE*eye.x
  1596.         y1 = top + CELL_SIZE*eye.y
  1597.         x2 = left + CELL_SIZE*target.x
  1598.         y2 = top + CELL_SIZE*target.y
  1599.         self.canvas.create_line(x1, y1, x2, y2, arrow="last", fill="blue")
  1600.  
  1601.     def drawBackground(self):
  1602.         background = hexColor(255,255,255) # better for red/cyan anaglyph
  1603.         self.canvas.create_rectangle(0, 0, self.width, self.height,
  1604.                                      fill=background, width=0)
  1605.     def drawGround(self):
  1606.         brown = hexColor(123, 112, 0)
  1607.         #brown = hexColor(163, 130, 41) # alternative
  1608.         cy = self.height/2
  1609.         self.canvas.create_rectangle(0, cy, self.width, self.height,
  1610.                                      fill=brown, width=0)
  1611.  
  1612.     def drawSky(self):
  1613.         #blue = hexColor(130,202,250) # light sky blue
  1614.         blue = hexColor(112,172,255) # sky blue
  1615.         #blue = hexColor(160,191,235) # alternative
  1616.         cy = self.height/2
  1617.         self.canvas.create_rectangle(0, 0, self.width, cy,
  1618.                                      fill=blue, width=0)
  1619.  
  1620.     def redraw3D(self):
  1621.         cx = self.width/2
  1622.         cy = self.height/2
  1623.         scaleX = (self.width / CAM_WIDTH)
  1624.         scaleY = (self.height / CAM_HEIGHT)
  1625.         self.drawGround()
  1626.         self.drawSky()
  1627.         for s in self.screenSegs:
  1628.             left = cx - s.x1*scaleX
  1629.             right = cx - s.x2*scaleX
  1630.             leftTop = cy + s.h1*scaleY
  1631.             leftBot = cy - s.h1*scaleY
  1632.             rightTop = cy + s.h2*scaleY
  1633.             rightBot = cy - s.h2*scaleY
  1634.             self.canvas.create_polygon(left, leftTop, right, rightTop,
  1635.                                        right, rightBot, left, leftBot,
  1636.                                        fill=s.color,
  1637.                                        outline="black")
  1638.  
  1639.     def draw3DGChannel(self, channel):
  1640.         # the wireframe 3DG idea was Nick Goman's
  1641.         cx = self.width/2
  1642.         cy = self.height/2
  1643.         scaleX = (self.width / CAM_WIDTH)
  1644.         scaleY = (self.height / CAM_HEIGHT)
  1645.         if (channel == "right"):
  1646.             screenSegs = self.screenSegs
  1647.             shift = "0,0"
  1648.         else:
  1649.             screenSegs = self.secScreenSegs
  1650.             shift = "0,1"
  1651.         for s in screenSegs:
  1652.             left = cx - s.x1*scaleX
  1653.             right = cx - s.x2*scaleX
  1654.             leftTop = cy + s.h1*scaleY
  1655.             leftBot = cy - s.h1*scaleY
  1656.             rightTop = cy + s.h2*scaleY
  1657.             rightBot = cy - s.h2*scaleY
  1658.             if (channel == "right"):
  1659.                 outlineColor = hexColor(255,0,0)
  1660.             else:
  1661.                 outlineColor = hexColor(0,255,255)
  1662.             if (s.color == "#ffffff"):
  1663.                 endColor = "#bbffbb"
  1664.                 self.canvas.create_polygon(left, leftTop, right, rightTop,
  1665.                                            right, rightBot, left, leftBot,
  1666.                                            width=0, fill=endColor)
  1667.             self.canvas.create_line(left, leftTop, right, rightTop,
  1668.                                     stipple="gray50", offset=shift,
  1669.                                     fill=outlineColor,width=3)
  1670.             self.canvas.create_line(right, rightTop, right, rightBot,
  1671.                                     stipple="gray50", offset=shift,
  1672.                                     fill=outlineColor,width=3)
  1673.             self.canvas.create_line(right, rightBot, left, leftBot,
  1674.                                     stipple="gray50", offset=shift,
  1675.                                     fill=outlineColor,width=3)
  1676.             self.canvas.create_line(left, leftBot, left, leftTop,
  1677.                                     stipple="gray50", offset=shift,
  1678.                                     fill=outlineColor,width=3)
  1679.  
  1680.  
  1681.     def redraw3DG(self):
  1682.         self.drawBackground() # white
  1683.         self.draw3DGChannel("left")
  1684.         self.draw3DGChannel("right")
  1685.  
  1686. game = MazeGame(12, 1366, 768)
  1687. game.run()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement