class GamePanel: ## Background color. BACKGROUND_COLOR = Color.GRAY ## Border color. BOUNDARY_COLOR = Color.GREY ## # Constructs a GamePanel with the given game associated ScorePanel. # @param game # the IGame instance for which this is the UI. # @param windowID graphical context for this panel. # @param scorePanel # panel for displaying scores associated with the game. # def __init__(self, game, windowID, scorePanel=None): ## Number of pixels each icon falls per frame when animating. self.fallPerFrame = 4 ## Interval between frames when animating falling icons, in milliseconds. self.fallingSpeed = GameMain.SPEED ## Number of times to flash when a pair is selected. self.numberOfFlashes = 5 ## Interval between flashes, in milliseconds. self.flashingSpeed = 150 ## State variable counts down to zero while flashing the cells to be collapsed. self.flashingState = 0 ## Cells about to be collapsed are flashed briefly before moving. self.cellsToCollapse = None ## Flag indicates whether we are currently moving cells down. self.collapsing = False ## Cells currently being moved down during a collapse. self.movingCells = None ## Flag indicates whether we are currently filling new cells. self.filling = False ## New cells filled from top. self.fillingCells = None ## Cell currently selected by a mouse down event. self.currentCell = None ## Cell currently selected by a mouse drag event. self.nextCell = None self.setStroke(1) ## Window running this panel. self.g = windowID glutSetWindow(self.g) ## Dictionary of colors. # @see https://www.2020colours.com self.colors = { Color.GRAY: [0.5, 0.5, 0.5], Color.GREY: [0.33, 0.33, 0.33], Color.RED: [1.0, 0.0, 0.0], Color.GREEN: [0.0, 1.0, 0.0], Color.YELLOW: [1.0, 1.0, 0.0], Color.BLUE: [0.0, 0.0, 1.0], Color.CYAN: [0.0, 1.0, 1.0], Color.MAGENTA: [1.0, 0.0, 1.0], Color.BURGUNDY: [0.5, 0.0, 0.13], Color.GARNET: [0.6, 0.02, 0.3], Color.PINK: [0.98, 0.68, 0.82], Color.AMETHYST: [0.6, 0.4, 0.8], Color.EMERALD: [0.31, 0.78, 0.47], Color.DIAMOND: [0.73, 0.95, 1.0], Color.TOPAZ: [1.0, 0.78, 0.49], Color.RUBY: [0.88, 0.07, 0.37], Color.SAPPHIRE: [0.06, 0.32, 0.73], Color.WHITE: [1.0, 1.0, 1.0], Color.BLACK: [0.0, 0.0, 0.0], Color.ORANGE: [1.0, 0.5, 0.0] } ## The IGame instance for which this is the UI. self.game = game ## Score panel associated with the game. self.scorePanel = scorePanel ## Background color. self.backgroundColor = self.colors[GamePanel.BACKGROUND_COLOR] + [1.0] ## Window height. self.h = self.game.getHeight() * GameMain.SIZE ## Window width. self.w = self.game.getWidth() * GameMain.SIZE ## using two viewports self.twoViewp = (self.g == self.scorePanel.g) glutMouseFunc(self.mousePressed) glutMotionFunc(self.mouseDragged) glutDisplayFunc(self.TimerCallback) glutReshapeFunc(self.reshape) glClearColor(*self.backgroundColor) ## Timer instance used to animate the UI. self.timer = Timer(self.fallingSpeed, glutPostRedisplay) # self.TimerCallback self.timer.run() ## Reshape callback. # # Since cell (0,0) is at the upper left corner of the grid, # we draw the Y axis upside down. # # @param w new window width. # @param h new window height. # def reshape(self, w, h): if self.twoViewp: w //= 2 aspect = float(self.game.getHeight()) / float(self.game.getWidth()) if w * aspect > h: GameMain.SIZE = h // self.game.getHeight() else: GameMain.SIZE = w // self.game.getWidth() self.h = self.game.getHeight() * GameMain.SIZE self.w = self.game.getWidth() * GameMain.SIZE self.d = abs(h - self.h) # (0,0) at upper left corner. xmin = w if self.twoViewp else 0 glViewport(xmin, self.d, self.w, self.h) glMatrixMode(GL_PROJECTION) glLoadIdentity() # Y axis upside down. glOrtho(0, self.w, self.h, 0, -1, 1) if GameMain.__DEBUG__: print('Resizing game window') ## Force a repaint of this panel. def repaint(self): glutSetWindow(self.g) self.paintComponent() # two viewports. if self.twoViewp: w = glutGet(GLUT_WINDOW_WIDTH) // 2 h = glutGet(GLUT_WINDOW_HEIGHT) # set the viewport for the panel. self.scorePanel.reshape(w, h) self.setColor(Color.WHITE) # clean only the score panel, drawing a rectangle if h > w: self.fillRect(0, 0, h, w) else: self.fillRect(0, 0, w, h) self.scorePanel.paintComponent(False) glutSwapBuffers() # restore the viewport for the game glViewport(w, self.d, self.w, self.h) glLoadIdentity() glOrtho(0, self.w, self.h, 0, -1, 1) else: self.scorePanel.paintComponent() glutSwapBuffers() ## Fill a rectangle. # # @param x coordinate of the upper left corner. # @param y coordinate of the upper left corner. # @param w width of the rectangle. # @param h height of the rectangle. # def fillRect(self, x, y, w, h): glBegin(GL_QUADS) glVertex2f(x, y) glVertex2f(x + w, y) glVertex2f(x + w, y + w) glVertex2f(x, y + w) glEnd() ## Draw the edges of a rectangle. # # @param x coordinate of the upper left corner. # @param y coordinate of the upper left corner. # @param w width of the rectangle. # @param h height of the rectangle. # def drawRect(self, x, y, w, h): glBegin(GL_LINE_LOOP) glVertex2f(x, y) glVertex2f(x + w, y) glVertex2f(x + w, y + w) glVertex2f(x, y + w) glEnd() ## Line width. def setStroke(self, w): ## Line width. self.stroke = w glLineWidth(w) ## Set current color. def setColor(self, c): ## Current color. self.color = self.colors[c] glColor3f(*self.color) ## # Maps a point from screen coordinates to WC. # If the game viewport origin in not at (0,0), this is necessary. # # @param x given point. # @param y given point. # @return pt in world coordinates. # def unProject(self, x, y): modelMatrix = glGetDoublev(GL_MODELVIEW_MATRIX) projMatrix = glGetDoublev(GL_PROJECTION_MATRIX) viewport = glGetIntegerv(GL_VIEWPORT) h = glutGet(GLUT_WINDOW_HEIGHT) y = h - y wcx, wcy, wcz = gluUnProject(x, y, 0, modelMatrix, projMatrix, viewport) return int(wcx), int(wcy) ## Callback for mouse button pressed. def mousePressed(self, button, button_state, cursor_x, cursor_y): if (self.flashingState > 0 or self.collapsing or self.filling): return if (button == GLUT_LEFT_BUTTON) and (button_state == GLUT_DOWN): cursor_x, cursor_y = self.unProject(cursor_x, cursor_y) row = cursor_y // GameMain.SIZE col = cursor_x // GameMain.SIZE if row >= self.game.getHeight() or col >= self.game.getWidth() or \ row < 0 or col < 0: return if GameMain.__DEBUG__: print("(row,col) = (%d,%d)" % (row, col)) self.currentCell = Cell(row, col, self.game.getIcon(row, col)) glutPostRedisplay() elif (button == GLUT_LEFT_BUTTON) and (button_state == GLUT_UP): # If we have selected two adjacent cells, then tell the game to try # to swap them. if (self.currentCell is not None): if (self.nextCell is not None): swapped = self.game.select( [self.currentCell, self.nextCell]) if GameMain.__DEBUG__: print(swapped) if swapped: # Successful move, start up the timer self.timer.setDelay(self.flashingSpeed) self.timer.restart() self.currentCell = None self.nextCell = None glutPostRedisplay() ## Callback for mouse button dragged. def mouseDragged(self, cursor_x, cursor_y): cursor_x, cursor_y = self.unProject(cursor_x, cursor_y) row = cursor_y // GameMain.SIZE col = cursor_x // GameMain.SIZE if row >= self.game.getHeight() or col >= self.game.getWidth() or \ row < 0 or col < 0: return if self.currentCell is not None: # if the cell is adjacent to the one in which mouse was pressed, # record it as the nextCell c = Cell(row, col, self.game.getIcon(row, col)) if self.currentCell.isAdjacent(c): self.nextCell = c else: self.nextCell = None glutPostRedisplay() ## The paintComponent is invoked whenever the panel needs # to be rendered on the screen. In this application, # repainting is normally triggered by the calls to the repaint() # method in the timer callback and the keyboard event handler (see below). def paintComponent(self): # clear background glClearColor(*self.backgroundColor) glClear(GL_COLOR_BUFFER_BIT) # paint occupied cells of the grid for row in range(self.game.getHeight()): for col in range(self.game.getWidth()): t = self.game.getIcon(row, col) if t is not None: self.paintOneCell(row, col, t) if self.currentCell is not None: self.highlightOneCell(self.currentCell.row(), self.currentCell.col()) if self.nextCell is not None: self.highlightOneCell(self.nextCell.row(), self.nextCell.col()) if self.flashingState > 0: # need to paint the cells, since they are nulled out in game for p in self.cellsToCollapse: self.paintOneCell(p.row(), p.col(), p.getIcon()) # if cells are collapsing, flash them if self.flashingState % 2 != 0: self.highlightOneCell(p.row(), p.col()) if self.collapsing: # first grey out the column where cells are moving # in order to match the background for c in self.movingCells: col = c.col() row = c.row() self.paintOneCellBG(row, col, GamePanel.BACKGROUND_COLOR) for c in self.movingCells: col = c.col() pixel = c.currentPixel self.paintOneCellByPixel(pixel, col, c.getIcon()) if self.filling: # first grey out the column where cells are moving for c in self.fillingCells: col = c.col() row = c.row() self.paintOneCellBG(row, col, GamePanel.BACKGROUND_COLOR) for c in self.fillingCells: col = c.col() pixel = c.currentPixel self.paintOneCellByPixel(pixel, col, c.getIcon()) ## # Renders a single cell of the grid. # # @param row y-coordinate of the cell to render # @param col x-coordinate of the cell to render # @param t the icon to render, normally used # to determine the color with which to render the cell # def paintOneCell(self, row, col, t): # scale everything up by the SIZE x = GameMain.SIZE * col y = GameMain.SIZE * row self.setColor(self.getColorForIcon(t)) self.fillRect(x, y, GameMain.SIZE, GameMain.SIZE) self.setColor(GamePanel.BOUNDARY_COLOR) self.drawRect(x, y, GameMain.SIZE - 1, GameMain.SIZE - 1) ## # Renders a single cell of the grid. # # @param row y-coordinate of the cell to render # @param col x-coordinate of the cell to render # @param color the color to render # def paintOneCellBG(self, row, col, color): # scale everything up by the SIZE x = GameMain.SIZE * col y = GameMain.SIZE * row self.setColor(color) self.fillRect(x, y, GameMain.SIZE, GameMain.SIZE) self.setColor(GamePanel.BACKGROUND_COLOR) self.drawRect(x, y, GameMain.SIZE - 1, GameMain.SIZE - 1) ## # Renders a single cell of the grid specifying its vertical # position in pixels. # # @param rowPixel y-coordinate of pixel at which to render the cell # @param col x-coordinate of the cell to render # @param t the icon to render, normally used # to determine the color with which to render the cell # def paintOneCellByPixel(self, rowPixel, col, t): # scale everything up by the SIZE x = GameMain.SIZE * col y = rowPixel self.setColor(self.getColorForIcon(t)) self.fillRect(x, y, GameMain.SIZE, GameMain.SIZE) self.setColor(GamePanel.BOUNDARY_COLOR) self.drawRect(x, y, GameMain.SIZE - 1, GameMain.SIZE - 1) ## # Draws a white border around one cell. # # @param row y-coordinate of the cell to highlight # @param col x-coordinate of the cell to highlight # def highlightOneCell(self, row, col): self.setColor(Color.WHITE) self.setStroke(2) self.drawRect(col * GameMain.SIZE, row * GameMain.SIZE, GameMain.SIZE, GameMain.SIZE) self.setStroke(1) ## # Listener for timer events. # # This is the display callback method, # which is invoked each time the timer fires and the call # to repaint() at the bottom of the method causes the panel # to be redrawn. # def TimerCallback(self): # State may be flashing, collapsing or filling. # Timer is not stopped until all cascading collapses are finished. if (self.flashingState == 0 and not self.collapsing and not self.filling): # Either we are just starting execution of the timer, or # have finished a previous collapse and fill and need to check # whether there is a cascading collapse. If so, # set the flashing state and fall through self.cellsToCollapse = self.game.findRuns(True) if len(self.cellsToCollapse) != 0: self.flashingState = self.numberOfFlashes * 2 self.timer.setDelay(self.flashingSpeed) self.timer.restart() # update the score too self.scorePanel.updateScore( self.game.getScore(), not self.game.debug and self.scorePanel.g is None) else: # nothing more to collapse self.cellsToCollapse = None self.timer.stop() if (self.flashingState > 0): self.flashingState -= 1 if (self.flashingState == 0): # Done flashing, start collapsing self.timer.setDelay(self.fallingSpeed) self.timer.restart() currentMovedCells = [] for col in range(self.game.getWidth()): currentMovedCells.extend(self.game.collapseColumn(col)) # go into collapsing state self.collapsing = True self.movingCells = [] for c in currentMovedCells: self.movingCells.append(AnimatedCell(c)) if self.collapsing: # see if there are still cells moving found = False for cell in self.movingCells: if not cell.done(): found = True cell.animate(self.fallPerFrame) if not found: # done collapsing, start filling self.collapsing = False self.movingCells = None self.initializeCellsToFill() self.filling = True if len(self.fillingCells) == 0: print( "WARNING: game returned collapsing cells but failed to return cells to fill columns" ) self.filling = False self.fillingCells = None if self.filling: # see if we're done found = False for cell in self.fillingCells: if not cell.done(): found = True cell.animate(self.fallPerFrame) if not found: self.filling = False self.fillingCells = None self.repaint() ## # Sets up the fillingCells list. # def initializeCellsToFill(self): self.fillingCells = [] for col in range(self.game.getWidth()): currentNewCells = self.game.fillColumn(col) for c in currentNewCells: self.fillingCells.append(AnimatedCell(c, -1)) ## Return the key corresponding to a certain value in a dictionary. get_key = lambda v, d: next(k for k in d if d[k] is v) ## # Returns a color for the given icon. # @param icon # @return # def getColorForIcon(self, icon): if icon is None: return Color.GRAY index = icon.getType() if (index < 0 or index > len(self.colors)): return Color.BLACK return Color(index)
class GamePanel: ## Background color. BACKGROUND_COLOR = Color.GRAY ## Border color. BOUNDARY_COLOR = Color.GREY ## # Constructs a GamePanel with the given game associated ScorePanel. # @param game # the IGame instance for which this is the UI. # @param root # root window. # @param canvas # canvas for drawing. # @param scorePanel # panel for displaying scores associated with the game. # def __init__(self, game, root, canvas, scorePanel=None): ## Number of pixels each icon falls per frame when animating. self.fallPerFrame = 9 ## Interval between frames when animating falling icons, in milliseconds. self.fallingSpeed = 20 ## Number of times to flash when a pair is selected. self.numberOfFlashes = 3 ## Interval between flashes, in milliseconds. self.flashingSpeed = 50 ## State variable counts down to zero while flashing the cells to be collapsed. self.flashingState = 0 ## Cells about to be collapsed are flashed briefly before moving. self.cellsToCollapse = None ## Flag indicates whether we are currently moving cells down. self.collapsing = False ## Cells currently being moved down during a collapse. self.movingCells = None ## Flag indicates whether we are currently filling new cells. self.filling = False ## New cells filled from top. self.fillingCells = None ## Cell currently selected by a mouse down event. self.currentCell = None ## Cell currently selected by a mouse drag event. self.nextCell = None ## MainPanel. self.root = root ## Canvas. self.g = canvas ## Line width. self.setStroke(1) ## Dictionary of colors. self.colors = { Color.GRAY: 'gray', Color.GREY: 'grey', Color.RED: 'red', Color.GREEN: 'green2', Color.CYAN: 'cyan', Color.YELLOW: 'yellow', Color.WHITE: 'white', Color.BLACK: 'black', Color.ORANGE: 'orange' } ## The IGame instance for which this is the UI. self.game = game ## Score panel associated with the game. self.scorePanel = scorePanel self.g.bind("<Button-1>", self.mousePressed) self.g.bind("<ButtonRelease-1>", self.mouseReleased) self.g.bind("<B1-Motion>", self.mouseDragged) ## Timer instance used to animate the UI. self.timer = Timer(self.root, self.fallingSpeed, self.TimerCallback) self.timer.run() ## Force a repaint of this panel. def repaint(self): self.paintComponent(self.g) self.scorePanel.paintComponent() self.root.update_idletasks() ## Fill a rectangle. def fillRect(self, x, y, w, h): self.g.create_rectangle(x, y, x + w, y + h, outline='black', fill=self.color) ## Draw a rectangle. def drawRect(self, x, y, w, h): self.g.create_rectangle(x, y, x + w, y + h, outline=self.color, width=self.stroke) ## Line width. def setStroke(self, w): self.stroke = w ## Set current color. def setColor(self, c): self.color = self.colors[c] ## Callback for mouse button pressed. def mousePressed(self, event): if (self.flashingState > 0 or self.collapsing or self.filling): return row = event.y // GameMain.SIZE col = event.x // GameMain.SIZE if GameMain.__DEBUG__: print("(row,col) = (%d,%d)" % (row, col)) self.currentCell = Cell(row, col, self.game.getIcon(row, col)) self.repaint() ## Callback for mouse button released. def mouseReleased(self, event): if (self.flashingState > 0 or self.collapsing or self.filling): return # If we have selected two adjacent cells, then tell the game to try # to swap them. if (self.currentCell is not None): if (self.nextCell is not None): swapped = self.game.select([self.currentCell, self.nextCell]) if GameMain.__DEBUG__: print(swapped) self.repaint() if swapped: # Successful move, start up the timer self.timer.setDelay(self.flashingSpeed) self.timer.restart() self.currentCell = None self.nextCell = None ## Callback for mouse button dragged. def mouseDragged(self, e): row = e.y // GameMain.SIZE col = e.x // GameMain.SIZE if self.currentCell is not None: # if the cell is adjacent to the one in which mouse was pressed, # record it as the nextCell c = Cell(row, col, self.game.getIcon(row, col)) if self.currentCell.isAdjacent(c): self.nextCell = c else: self.nextCell = None self.repaint() ## The paintComponent is invoked whenever the panel needs # to be rendered on the screen. In this application, # repainting is normally triggered by the calls to the repaint() # method in the timer callback and the keyboard event handler (see below). def paintComponent(self, g): # clear background self.setColor(GamePanel.BACKGROUND_COLOR) self.fillRect(0, 0, self.game.getWidth() * GameMain.GRID_SIZE, self.game.getHeight() * GameMain.GRID_SIZE) # paint occupied cells of the grid for row in range(self.game.getHeight()): for col in range(self.game.getWidth()): t = self.game.getIcon(row, col) if t is not None: self.paintOneCell(g, row, col, t) if self.currentCell is not None: self.highlightOneCell(g, self.currentCell.row(), self.currentCell.col()) if self.nextCell is not None: self.highlightOneCell(g, self.nextCell.row(), self.nextCell.col()) if self.flashingState > 0: # need to paint the cells, since they are nulled out in game for p in self.cellsToCollapse: self.paintOneCell(g, p.row(), p.col(), p.getIcon()) # if cells are collapsing, flash them if self.flashingState % 2 != 0: self.highlightOneCell(g, p.row(), p.col()) if self.collapsing: # first grey out the column where cells are moving # in order to match the background for c in self.movingCells: col = c.col() row = c.row() self.paintOneCellBG(g, row, col, GamePanel.BACKGROUND_COLOR) for c in self.movingCells: col = c.col() pixel = c.currentPixel self.paintOneCellByPixel(g, pixel, col, c.getIcon()) if self.filling: # first grey out the column where cells are moving for c in self.fillingCells: col = c.col() row = c.row() self.paintOneCellBG(g, row, col, GamePanel.BACKGROUND_COLOR) for c in self.fillingCells: col = c.col() pixel = c.currentPixel self.paintOneCellByPixel(g, pixel, col, c.getIcon()) ## # Renders a single cell of the grid. # # @param g graphics context # @param row y-coordinate of the cell to render # @param col x-coordinate of the cell to render # @param t the icon to render, normally used # to determine the color with which to render the cell # def paintOneCell(self, g, row, col, t): # scale everything up by the SIZE x = GameMain.SIZE * col y = GameMain.SIZE * row self.setColor(self.getColorForIcon(t)) self.fillRect(x, y, GameMain.SIZE, GameMain.SIZE) self.setColor(GamePanel.BOUNDARY_COLOR) self.drawRect(x, y, GameMain.SIZE - 1, GameMain.SIZE - 1) ## # Renders a single cell of the grid. # # @param g graphics context # @param row y-coordinate of the cell to render # @param col x-coordinate of the cell to render # @param color the color to render # def paintOneCellBG(self, g, row, col, color): # scale everything up by the SIZE x = GameMain.SIZE * col y = GameMain.SIZE * row self.setColor(color) self.fillRect(x, y, GameMain.SIZE, GameMain.SIZE) self.setColor(GamePanel.BACKGROUND_COLOR) self.drawRect(x, y, GameMain.SIZE - 1, GameMain.SIZE - 1) ## # Renders a single cell of the grid specifying its vertical # position in pixels. # # @param g graphics context # @param rowPixel y-coordinate of pixel at which to render the cell # @param col x-coordinate of the cell to render # @param t the icon to render, normally used # to determine the color with which to render the cell # def paintOneCellByPixel(self, g, rowPixel, col, t): # scale everything up by the SIZE x = GameMain.SIZE * col y = rowPixel self.setColor(self.getColorForIcon(t)) self.fillRect(x, y, GameMain.SIZE, GameMain.SIZE) self.setColor(GamePanel.BOUNDARY_COLOR) self.drawRect(x, y, GameMain.SIZE - 1, GameMain.SIZE - 1) ## # Draws a white border around one cell. # # @param g graphics context # @param row y-coordinate of the cell to highlight # @param col x-coordinate of the cell to highlight # def highlightOneCell(self, g, row, col): self.setColor(Color.WHITE) self.setStroke(2) self.drawRect(col * GameMain.SIZE, row * GameMain.SIZE, GameMain.SIZE, GameMain.SIZE) self.setStroke(1) ## # Listener for timer events. The actionPerformed method # is invoked each time the timer fires and the call to # repaint() at the bottom of the method causes the panel # to be redrawn. # def TimerCallback(self): # State may be flashing, collapsing or filling. # Timer is not stopped until all cascading collapses are finished. if (self.flashingState == 0 and not self.collapsing and not self.filling): # Either we are just starting execution of the timer, or # have finished a previous collapse and fill and need to check # whether there is a cascading collapse. If so, # set the flashing state and fall through self.cellsToCollapse = self.game.findRuns(True) if len(self.cellsToCollapse) != 0: self.flashingState = self.numberOfFlashes * 2 self.timer.setDelay(self.flashingSpeed) self.timer.restart() # update the score too if self.scorePanel is not None: self.scorePanel.updateScore(self.game.getScore()) else: # nothing more to collapse self.cellsToCollapse = None self.timer.stop() if (self.flashingState > 0): self.flashingState -= 1 if (self.flashingState == 0): # Done flashing, start collapsing self.timer.setDelay(self.fallingSpeed) self.timer.restart() currentMovedCells = [] for col in range(self.game.getWidth()): currentMovedCells.extend(self.game.collapseColumn(col)) # go into collapsing state self.collapsing = True self.movingCells = [] for c in currentMovedCells: self.movingCells.append(AnimatedCell(c)) if self.collapsing: # see if there are still cells moving found = False for cell in self.movingCells: if not cell.done(): found = True cell.animate(self.fallPerFrame) if not found: # done collapsing, start filling self.collapsing = False self.movingCells = None self.initializeCellsToFill() self.filling = True if len(self.fillingCells) == 0: print( "WARNING: game returned collapsing cells but failed to return cells to fill columns" ) self.filling = False self.fillingCells = None if self.filling: # see if we're done found = False for cell in self.fillingCells: if not cell.done(): found = True cell.animate(self.fallPerFrame) if not found: self.filling = False self.fillingCells = None self.repaint() ## # Sets up the fillingCells list. # def initializeCellsToFill(self): self.fillingCells = [] for col in range(self.game.getWidth()): currentNewCells = self.game.fillColumn(col) for c in currentNewCells: self.fillingCells.append(AnimatedCell(c, -1)) ## Return the key corresponding to a certain value in a dictionary. get_key = lambda v, d: next(k for k in d if d[k] is v) ## # Returns a color for the given icon. # @param icon # @return # def getColorForIcon(self, icon): if icon is None: return Color.GRAY index = icon.getType() if (index < 0 or index > len(self.colors)): return Color.BLACK return Color(index)