''' TranscentrationRunGameV02.py The program that runs a game of Transcentration. Version 0.2: This version adds the following to what is in Version 0.1: (1) A class called Game, which has methods to arrange the cards at the beginning of the game and to enforce game rules and respond to user actions. The game has access to the board, and the board has access to the game. (2) A class called ControlPanel, which contains some subpanels and Swing widgets for controlling and showing the status of the game. (3) A class called TitlePanel, which simply displays the title of the game. Code that is new (added since V01) is mostly at the end in the class Game, but there are a few lines elsewhere, with the changes indicated as "# V02." if on the same line or "# V02:" if on the next line. The Game class maintains a list called cardArray which has one element for each place on the board. Some of the places might not be used, in which case a blank board item is placed there. In general, each place in the cardArray (which is really a one-dimensional list, but serves to hold the content of a conceptually two-dimensional array) holds an instance of the BoardItem structure. This structure is defined by the inner class (within the Game class) called BoardItem. The BoardItem object tells, for a given place on the board, whether it contains anything, whether what it contains is an original image or a transform image, the index of the image within its particular list (either originals of transforms), its own index in the cardArray list, and the index of its mate within the cardArray list. When the game is set up, a bunch of these BoardItem instances are made up so that the arrangement of "cards" on the board is random. The information is organized so that the program can determine, when the game is played, whether or not two cards selected by the user actually match each other or not. The game can be played in two modes. The "pix" mode is the default. In this mode, each corresponding pair of cards is displayed using images: one is the original and the other is a transformation of it. In the "formulas" mode, only the transformed image is shown as an image. The original is not shown, but the formula used to transform it is shown instead. Aug 1. Path edited to work on Poincare. Import statement changed to work around Jython embedded interp. but on Windows. Importing java subpackage imageio requires fully qualified class desc. ''' import javax.swing as swing import javax.imageio.ImageIO as ImageIO import java.util.Random as Random # V02. from java.awt import Color, BorderLayout, FlowLayout, GridLayout, Dimension, Font from java.awt.event import MouseAdapter import math # The PATH should lead from the default PixelMath user directory # to the folder where the Transcentration files and folders are. PATH = '../Transcentration/' # The following class specializes a Swing JPanel to represent # the game board. class Board(swing.JPanel): # Constructor for the class. def __init__(self, game): # V02. (adds Game param). self.game = game # V02. self.originalImages = [] self.transformedImages = [] self.formulas = [] # The following 3 members get created by other methods: #self.nr = 0 #self.nc = 0 #self.num_originals = 0 # The next call determines how big to paint the images. #self.setCardSize(200,170) self.setCardSize(250,200) # Next, associate a mouse handler with the board. self.addMouseListener(Board.BoardMouseAdapter()) # Set the width and height for the "cards" of the game. def setCardSize(self, w, h): self.w = w self.h = h # Determine with board's overall width and height. def getDimensions(self): return (int(self.w*self.nc), int(self.h*self.nr)) # Here we override the JPanel paintComponent method so that # we control how the board is rendered. def paintComponent(self, g): i = 0; j = 0 k = 0 # V02: (Contents of the loop have been changed, too) backSideColor = Color(128, 0, 192) borderColor = Color(0, 0, 128) cards = self.game.cardArray if cards==None: return for item in cards: if not item.isEmpty: if item.isVisible: if item.isXform: image = self.transformedImages[item.imageIDX] g.drawImage(image, j*self.w, i*self.h, self.w, self.h, self) else: if self.game.getMode()=='formulas': g.setColor(borderColor) g.drawString(self.game.getFormula(item.transIDX), j*self.w, int((i+0.5)*self.h)) else: image = self.originalImages[item.imageIDX] g.drawImage(image, j*self.w, i*self.h, self.w, self.h, self) else: # Show a colored rectangle, when the card is hidden. g.setColor(backSideColor) g.fillRect(j*self.w, i*self.h, self.w, self.h) g.setColor(borderColor) g.drawRect(j*self.w, i*self.h, self.w, self.h) k += 1 j += 1 if j== self.nc: i += 1; j = 0 # To load one image from a file, we use the Java ImageIO class. def loadImage(self, filename, whichList): try: f = open(filename,'r') image = ImageIO.read(f) whichList.append(image) #w = image.getWidth(b) #h = image.getHeight(b) # Note: we don't currently make use of the actual # width and height of each image. Instead we force # all the widths to be the same when we paint the images. # A more sophisticated way to show the images might involve # scaling and padding to maintain the original aspect ratios. except: print "Could not read an image named: "+filename print " There could be a path problem, "+\ "or the image may need to be created." # Read in all the images we need for a game of Transcentration. def loadAllImages(self): # Read in two files, and figure out the names of # all the images needed. # First read in a file containing a list of image originals. global PATH PATH = 'C:\\Users\\Steve\\Desktop\\PNP-Reference-Materials\\Transcentration\\' image_list_file =\ open(PATH+'ORIGINALS/active-images-list.txt', 'r') image_files = image_list_file.readlines() formula_list_file =\ open(PATH+'TRANSFORMED/active-formulas-list.txt', 'r') formulas = formula_list_file.readlines() i = 0 # index of current line in the formulas file. k = 0 # count of the number of originals for image_line in image_files: image_fn = image_line[:-1] # remove trailing newline character formula_line = formulas[i][:-1] # get one line and remove newline char. i += 1 # If this formula_line starts with '#' then # it is a comment and should be skipped. comment = True if formula_line[0]!='#': comment = False while comment: formula_line = formulas[i][:-1] i += 1 comment = True if formula_line[0]!='#': comment = False #print 'Processing formula line: '+formula_line pos = formula_line.find(':') # Determine where the formula name ends. formula_name = formula_line[:pos] # Pull out the formula name. formula = formula_line[pos+1:] # Pull out the formula itself. new_name = image_fn[:-4]+'-'+formula_name+'.png' # Synthesize the file name. # Read both the original and the transformed version. self.loadImage(PATH+'ORIGINALS/'+image_fn, self.originalImages) k += 1 self.loadImage(PATH+'TRANSFORMED/'+new_name, self.transformedImages) self.formulas.append(formula) # Used only in "formulas" mode. self.num_originals = k # Save number of originals total = 2*k # otal number of images read in. if total > 16: self.nc = 6 # Choose number of columns for the board. else: self.nc = 4 self.nr = math.ceil((0.0+total)/self.nc) # Compute number of rows. # A convenience method that combines the functionality of getDimensions, # the Swing method setSize, and returning the dimenions to the caller. def setAndReturnDimensions(self): dims = self.getDimensions() self.setPreferredSize(Dimension(dims[0],dims[1])) return dims # Here's a skeleton of a handler for mouse clicks. # It is an inner class of Board, with access to its members. class BoardMouseAdapter(MouseAdapter): def mouseClicked(self, event): x = event.getX() y = event.getY() board = event.getSource() g = board.game c = g.controlPanel if not g.playing: c.msg(['You are not currently playing a game.', 'To start a game, press "Start".']) return if g.turnPhase==2: c.msg(['You must click Continue.']) return col = x/board.w row = y/board.h idx = row*board.nc + col if idx >= 2*board.num_originals: return item = g.cardArray[idx] if item.isEmpty: c.msg(['There is no card there.', 'Try again.']) return if g.turnPhase==0: g.flipCard(idx) g.firstCard = idx if item.isXform: g.mateNeeded = item.origIDX else: g.mateNeeded = item.transIDX g.turnPhase=1 c.msg(['Now choose the card that', 'you think matches.']) board.repaint() return if g.turnPhase==1: g.secondCard = idx g.flipCard(idx) if idx==g.mateNeeded: g.playerJustMatched = True c.msg(['Good Job!']) g.scores[g.whoseTurn-1] += 1 c.updateScoreDisplay(None) board.repaint() g.cardsLeft -= 2 else: g.playerJustMatched = False c.msg(['Better luck next time.']) g.turnPhase=2 # The phase where we wait # for the user to click on the Continue button. c.changeButtonLabel('Continue') board.repaint() class Game: def __init__(self, c): self.controlPanel = c self.board = Board(self) # Create the board. self.cardArray = None self.board.loadAllImages() # Load all the images. # Set the board size so all images show. self.boardDimensions = self.board.setAndReturnDimensions() self.scores=[0,0,0,0] self.playing = False def dealTheCards(self): # Create the array of board items: blankItem = Game.BoardItem(False,0,0,0,False,True) self.blankItem = blankItem nOriginals = self.board.num_originals # nSlots represents how many of the board positions we will use. # There could be some unused ones, if the rows don't come out "even." nSlots = 2*nOriginals self.cardArray = int(self.board.nc*self.board.nr)*[blankItem] # Fill it with a randomized ordering of the cards: self.rg = Random() # Initialize a random number generator. # iOA is a list of the indexes of originals available. # iTA is a list of the indexes of transformed images available. iOA = range(nOriginals) iTA = range(nOriginals) # slotsLeft is a list of slots still available for cards. slotsLeft = range(nSlots) for i in range(nOriginals): # Choose a random place on the board for one card: slot1 = self.chooseAndRemove(slotsLeft) # Find a spot for its mate: slot2 = self.chooseAndRemove(slotsLeft) # Put in an item for the original in this slot. origItem = Game.BoardItem(False, i, slot1, slot2, False, False) self.cardArray[slot1] = origItem # Put in an item for the transform in the other slot. transItem = Game.BoardItem(True, i, slot1, slot2, False, False) self.cardArray[slot2] = transItem #print "slot1 = "+str(slot1)+"; sot2 = "+str(slot2) #print "slotsLeft: " + str(slotsLeft) self.cardsLeft = 2*nOriginals def startGame(self): g=self c=g.controlPanel g.scores = [0,0,0,0] # Initialize scores c.updateScoreDisplay(None) g.whoseTurn = 1 g.turnPhase = 0 g.playing = True g.dealTheCards() c.msg(['Game started.', 'Player 1 click on a card.']) self.board.repaint() def quitGame(self): self.playing = False self.controlPanel.msg(['You have Quit the game.', 'To play a new game, press Start.']) self.controlPanel.changeButtonLabel("Start") def proceedToNextTurn(self): g=self c=g.controlPanel c.changeButtonLabel("Quit") # Assume game continues. g.turnPhase = 0 if g.playerJustMatched: g.cardArray[g.firstCard] =g.blankItem g.cardArray[g.secondCard]=g.blankItem if g.cardsLeft == 0: g.playing = False highestScore = max(g.scores) winners = map(lambda w:w+1, indexes(g.scores, highestScore)) if len(winners)==1: c.msg(['Game Over','Congrats to Player '+str(winners[0])+'!']) else: winnerSequence = reduce(lambda w1,w2:str(w1)+', '+str(w2), winners) c.msg(['Game Over','Congrats to Players '+winnerSequence+'!']) c.changeButtonLabel("Start") return else: g.board.repaint() c.msg(['Take another turn!']) return else: # put back the cards turned over, and advance to the next turn. g.flipCard(g.firstCard) g.flipCard(g.secondCard) g.whoseTurn = (g.whoseTurn % c.getNPlayers()) + 1 g.board.repaint() c.msg(['Player '+str(g.whoseTurn),'Choose your first card.']) def getMode(self): return self.controlPanel.modeChoice.getSelectedItem() class BoardItem: def __init__(self, isXform, imageIDX, origIDX, transIDX, isVisible, isEmpty): self.isXform = isXform self.imageIDX = imageIDX self.origIDX = origIDX self.transIDX = transIDX self.isVisible = isVisible self.isEmpty = isEmpty # Randomly select an item from a list. # Remove it from the list and return the item. def chooseAndRemove(self, aList): index = self.rg.nextInt() % len(aList) # a random index within the range. elt = aList[index] aList.__delitem__(index) # The method __delitem__ is a built-in method for lists. return elt # FlipCard: def flipCard(self, idx): bi = self.cardArray[idx] bi.isVisible = not bi.isVisible # Retrieve the formula used to produce the # transformed image currently placed at itemIDX. # This is needed when playing in "formulas" mode. def getFormula(self, itemIDX): transItem = self.cardArray[itemIDX] # info about that card formulaIDX = transItem.imageIDX # index of its original # in the list of originals. # This is the same as the index of its formulas in # the list of formulas. Now get the formula: formula = self.board.formulas[formulaIDX] return formula class TitlePanel(swing.JPanel): def __init__(self): self.setPreferredSize(Dimension(500,80)) self.font = Font('Courier', Font.BOLD, 48) def paintComponent(self, g): g.setColor(Color(64, 64, 255)) g.setFont(self.font) g.drawString("Transcentration!", 200, 50) # We define ControlPanel in order to organize the controls. class ControlPanel(swing.JPanel): def __init__(self): self.setLayout(FlowLayout()) self.setPreferredSize(Dimension(500, 60)) self.setBackground(Color(192,192,212)) # Set up a small 2x2 subpanel on the left. self.leftPanel=swing.JPanel() self.leftPanel.setLayout(GridLayout(2,2)) self.leftPanel.setPreferredSize(Dimension(300,50)) label = swing.JLabel("Number of Players:") label.setPreferredSize(Dimension(100,20)) self.leftPanel.add(label,0,0) # Define the scorePanel now, because it is accessed # by the updateScoreDisplay callback needed next. self.scorePanel = ControlPanel.ScorePanel(self) choice = swing.JComboBox([1,2,3,4],\ itemStateChanged=self.updateScoreDisplay) choice.setPreferredSize(Dimension(65, 50)) self.leftPanel.add(choice,0,1) self.nplayersChoice = choice modeLabel = swing.JLabel("Game mode:") self.leftPanel.add(modeLabel,1,0) modeChoice = swing.JComboBox(['pix','formulas']) self.modeChoice=modeChoice self.leftPanel.add(modeChoice,1,1) self.add(self.leftPanel) # Now add the scorePanel to the control panel. self.add(self.scorePanel) # Set up a "dialog" panel where messages will be printed. dialog=ControlPanel.DialogPanel(['Welcome to Transcentration!']) dialog.setPreferredSize(Dimension(300,50)) self.dialog = dialog self.add(dialog) # Finally, set up a multipurpose button. theButton = swing.JButton("Start", actionPerformed=self.handleButton) theButton.setPreferredSize(Dimension(100, 50)) self.theButton = theButton self.add(theButton) # The following is needed because the control panel is constructed # before the game object. The linkage the other way is established # after both are created. def setGame(self, game): self.game = game def getNPlayers(self): return self.nplayersChoice.getSelectedItem() def msg(self, text): self.dialog.setText(text) def updateScoreDisplay(self,dummy): self.scorePanel.repaint() def handleButton(self, event): buttonLabel = event.getActionCommand() if buttonLabel=='Start': self.changeButtonLabel('Quit') self.game.startGame() if buttonLabel=='Quit': self.changeButtonLabel('Start') self.game.quitGame() if buttonLabel=='Continue': self.changeButtonLabel('Quit') self.game.proceedToNextTurn() def changeButtonLabel(self, newLabel): self.theButton.setLabel(newLabel) class ScorePanel(swing.JPanel): def __init__(self, cp): self.cp = cp self.setPreferredSize(Dimension(160,70)) self.backColor = Color(192,192,255) self.setBackground(self.backColor) self.setOpaque(True) def paintComponent(self, g): scoreColor = Color(128,0,0) g.setColor(self.backColor) g.fillRect(0,0,150,120) g.setColor(scoreColor) np = self.cp.nplayersChoice.getSelectedItem() scores = self.cp.game.scores for p in range(np): p1 = p + 1 g.drawString("Player "+str(p1)+"'s score: "+str(scores[p]), 20, 15*p1) class DialogPanel(swing.JPanel): def __init__(self, textLinesList): self.text = textLinesList self.bgColor=Color(212,212,230) self.setBackground(self.bgColor) self.textColor=Color(0,0,128) self.dialogFont = Font('Courier', Font.PLAIN, 12) def paintComponent(self, g): g.setColor(self.bgColor) g.fillRect(0,0,300,100); g.setColor(self.textColor) g.setFont(self.dialogFont) for i in range(len(self.text)): g.drawString(self.text[i], 20, 10+(20*i)) def setText(self, text): self.text = text self.repaint() # Helper function for finding the winner(s) of a game: def indexes(lst, elt): result=[] for i in range(len(lst)): if lst[i]==elt: result.append(i) return result # Here's most of the top-level executable code for this program: windowTitle = "The Game of Transcentration -- Version 0.1" j = swing.JFrame(windowTitle) # Create a window. # V02: (4 lines moved to Game.__init__) #b = Board(False) # Create the board. #b.loadAllImages() # Load all the images. # Set the board size so all images show. # boardDimensions = b.setAndReturnDimensions() # V02: (create the title panel, control panel and game) t = TitlePanel() c = ControlPanel() g = Game(c) c.setGame(g) j.setLayout(BorderLayout()) j.getContentPane().add(t, BorderLayout.NORTH) j.getContentPane().add(c, BorderLayout.CENTER) # V02: (access board via the game in the next 2-3 lines) j.getContentPane().add(g.board, BorderLayout.SOUTH) # Add the board to the window. # Set the window size so the whole board shows. j.setSize(g.boardDimensions[0],g.boardDimensions[1]+200) j.show() # Show the window.