def __init__(self, GAME, floorConfig=None, loop=True, cartridgeReader=False, init=True):
        self.cartridgeReader = cartridgeReader
        self.loop = loop
        self.wait = time.sleep
        if floorConfig is None:
            conf = LSFloorConfig()
            conf.selectConfig()
        else:
            conf = LSFloorConfig(floorConfig)
        if conf.containsVirtual() is True:
            self.REAL_FLOOR = False
        else:
            self.REAL_FLOOR = True

        self.audio = LSAudio(initSound=init)
        self.display = LSDisplay(conf=conf, eventCallback = self.handleTileStepEvent, initScreen=init)

        self.ROWS = conf.rows
        self.COLUMNS = conf.cols
        os.system('cls' if os.name == 'nt' else 'clear')
        print("Board size is {:d}x{:d}".format(self.ROWS, self.COLUMNS))
            
        self.GAME = GAME
        self.moves = []
        self.sensorMatrix = defaultdict(lambda: defaultdict(int))
        self.currentGame = None
        self.newGame(self.GAME)

        #these are for bookkeeping
        self.frames = 0
        self.frameRenderTime = 0
        
        # This lock prevents handleTileStepEvent() from being run by polling loops before init is complete
        self.initLock.set()
class LSGameEngine():
    initLock = threading.Event()
    SIMULATED_FLOOR = True
    CONSOLE = False
    numPlays = numLoops = 0
    _warnings = []

    def __init__(self, GAME, floorConfig=None, loop=True, cartridgeReader=False, init=True):
        self.cartridgeReader = cartridgeReader
        self.loop = loop
        self.wait = time.sleep
        if floorConfig is None:
            conf = LSFloorConfig()
            conf.selectConfig()
        else:
            conf = LSFloorConfig(floorConfig)
        if conf.containsVirtual() is True:
            self.REAL_FLOOR = False
        else:
            self.REAL_FLOOR = True

        self.audio = LSAudio(initSound=init)
        self.display = LSDisplay(conf=conf, eventCallback = self.handleTileStepEvent, initScreen=init)

        self.ROWS = conf.rows
        self.COLUMNS = conf.cols
        os.system('cls' if os.name == 'nt' else 'clear')
        print("Board size is {:d}x{:d}".format(self.ROWS, self.COLUMNS))
            
        self.GAME = GAME
        self.moves = []
        self.sensorMatrix = defaultdict(lambda: defaultdict(int))
        self.currentGame = None
        self.newGame(self.GAME)

        #these are for bookkeeping
        self.frames = 0
        self.frameRenderTime = 0
        
        # This lock prevents handleTileStepEvent() from being run by polling loops before init is complete
        self.initLock.set()

    def newGame(self, Game):

        self.lastGame = self.currentGame

        try: # Game is a list of game classes, pick one at random
            GAME = random.choice(Game)
        except: # Game was specified
            GAME = Game
        self.currentGame = GAME.__name__

        print("LSGameEngine: Starting {:s}...".format(self.currentGame))
        self.game = GAME(self.display, self.audio, self.ROWS, self.COLUMNS, self.cartridgeReader)
        self.startGame = time.time()
        self.game.sensors = self.sensorMatrix
        self.numLoops += 1
        if not isinstance(self.game, LSScreenSaver):
            self.numPlays += 1
        try:
            self.game.init()
        except AttributeError as e:
        #    raise(e)    # Debugging
            self._warnOnce("{:s} has no init() method.".format(self.currentGame))
        
    def beginLoop(self, plays = 0):
        self.fpsLog = dict()
        while True:
            if plays is not 0 and self.numPlays <= plays:
                self.enterFrame()
            elif plays is 0:
                self.enterFrame()
            else:
                self.insertCoin()

    def insertCoin(self):
        self.gameOver()
        # TODO: Instead of quitting, go to game/demo screen and wait for someone to reset

    def gameOver(self):
        print(" G A M E  O V E R ")
        self.display.clearAll()
        self.display.setMessage(int(self.display.rows/2)-1,"GAME", start=int(self.display.cols/2)-2)
        self.display.setMessage(int(self.display.rows/2), "OVER", start=int(self.display.cols/2)-2)
        self.display.heartbeat()
        input("--Press any key to exit--\n")
#        self.display.floor.saveAndExit(0)

    def handleTileStepEvent(self, row, col, sensorPcnt):
        self.initLock.wait()
        if int(sensorPcnt) is 0:
            try:
                self.game.stepOff(row, col)
            except AttributeError as e:   # Game has no stepOff() method
                if "object has no attribute 'stepOff'" in str(e):
                    self._warnOnce("{:s} has no stepOff() method.".format(self.currentGame))
                else:
                    raise(e)
         #   print("stepOff: ({:d},{:d})".format(row, col)) # Debugging
            self.moves = [x for x in self.moves if x[0] is not row and x[1] is not col]
        else:
            if self.sensorMatrix[row][col] is 0:
                if sensorPcnt > _SENSOR_THRESHOLD:# Only trigger > n%, hack to guard against phantom sensors
                                            # TODO: This but better
             #   if sensorPcnt > 0:
                    try:
                        self.game.stepOn(row, col)
                    except AttributeError as e:   # Game has no stepOn() method
                        if "object has no attribute 'stepOn'" in str(e):
                            self._warnOnce("{:s} has no stepOn() method.".format(self.currentGame))
                        else:
                            raise(e)
                 #   print("stepOn: ({:d},{:d})".format(row, col)) # Debugging
                    m = (row, col)
                    self.moves.append(m)
        self.sensorMatrix[row][col] = int(sensorPcnt)

    def pauseGame (self):
        print("Game is paused.")
        while self.game.frameRate < 1:
            pass
        print("Game resuming...")

    def enterFrame(self):
        if self.game.duration is not 0:
            playTime = (time.time() - self.startGame)
            if playTime > self.game.duration:
                self.newGame(self.GAME)
        startEnterFrame = time.time()
        if not self.game.ended:
            self.game.heartbeat(self.moves)
            self.display.heartbeat()
     #       self.audio.heartbeat()
        else:
            self.newGame(SAVERS)    # Super hacky, should be in gameOver
         #   self.newGame(self.GAME)
        frameRenderTime = (time.time() - startEnterFrame)
        self.wait(self.padFrame(frameRenderTime))

    def padFrame(self, renderTime):
        if self.game.frameRate is 0:
            self.pauseGame()
        spaces = " " * (52)
        fps = 1.0/renderTime
        fpsEntry = int(fps) if fps < self.game.frameRate else int(self.game.frameRate)
        if self.numLoops in self.fpsLog.keys():
            self.fpsLog[self.numLoops].append(fpsEntry)
        else:
            if self.numLoops > 1:
                try:
                    data = self.fpsLog[self.numLoops-1]
                    avg = int(sum(data)/len(data))
                    mod = max(set(data), key=data.count)
                    mrg = int(min(data) + max(data)/2)
                 #   print(data) # Debugging
                    del(data)
                    print("\nStats for: {:s}".format(self.lastGame))
                    statsString="  FrameRate: (average: {:d}fps) (mode: {:d}fps) (midrange: {:d}fps)"
                    print(statsString.format(avg, mod, mrg))
                    print("")
                except KeyError:
                    pass
            self.fpsLog[self.numLoops] = [fpsEntry]
        if fps < self.game.frameRate or self.game.frameRate < 0:
            print("{1:s}{0:.4f} FPS".format(1.0/renderTime, spaces), end="\r")
            return(0)
        else:
            print("{1:s}{0:.4f} FPS".format(self.game.frameRate, spaces), end="\r")
            return((1.0/self.game.frameRate)-renderTime)
        print(spaces * 2, end="\r")

    def _warnOnce(self, warning):
        if warning not in self._warnings:
            print("WARNING: {:s}".format(warning))
            self._warnings.append(warning)