This is a little prototype game that I made to answer the question: What if tetris was played on a hill?
Tetris theme is as performed by me on a synth. You can download the game here (requires python and pygame), or view the hackish source code after the break.
#!/usr/bin/env python # # Sideways Tetris Proof of Concept # By Matt Mets, completed in 2009 # # This code is released into the public domain. Attribution is appreciated. import sys, pygame, random import numpy from pygame.locals import * pygame.init() # Seed the random number generator using the system time random.seed() # Set up the game screen size = width, height = 1024,768 screen = pygame.display.set_mode(size, DOUBLEBUF) pygame.display.set_caption('Sideways Tetris') # Start the background music pygame.mixer.init(22050, -16, 2, 3072) sound = pygame.mixer.Sound('theme.ogg') sound.play(-1) # This class abstracts drawing the pieces on the board class boardDrawer(): def __init__(self,screen): self.diamondSprites = [] self.diamondSprites.append(pygame.image.load("sprites/jewel_blue.png").convert_alpha()) self.diamondSprites.append(pygame.image.load("sprites/jewel_red.png").convert_alpha()) self.diamondSprites.append(pygame.image.load("sprites/jewel_green.png").convert_alpha()) self.diamondSprites.append(pygame.image.load("sprites/jewel_yellow.png").convert_alpha()) self.diamondSprites.append(pygame.image.load("sprites/jewel_purple.png").convert_alpha()) self.diamondSprites.append(pygame.image.load("sprites/jewel_orange.png").convert_alpha()) self.diamondSprites.append(pygame.image.load("sprites/jewel_gray.png").convert_alpha()) self.screen = screen # drawing specific things self.xOrigin = 520 self.yOrigin = 727 self.xOffset = 20 self.yOffset = 20 def drawDiamond(self, part, color): self.screen.blit(self.diamondSprites[color - 1], self.getCoords(part)) def getCoords(self, part): xPiecePos = self.xOrigin + self.yOffset*part[1] - self.xOffset*part[0] yPiecePos = self.yOrigin - self.yOffset*part[1] - self.xOffset*part[0] return [xPiecePos, yPiecePos] # This class holds all the loose parts that are not part of the player piece class looseParts(): def __init__(self, boardDrawer): self.boardDrawer = boardDrawer self.xMax = 8 self.yMax = 8 self.height = 20 self.partArrayA = self.xMax + self.yMax + 1 self.partArrayB = self.height # Note: The extra 1 is because we are cheating. The extra bit is to avoid # indexing off of the right hand side of the board when a part is # next to it. self.partArray = numpy.zeros([self.partArrayA + 1, self.partArrayB], dtype=numpy.int) def draw(self): for a in range(0, self.partArrayA): for b in range(0, self.partArrayB): if self.partArray[a,b] != 0: self.boardDrawer.drawDiamond(self.matrixToBoard(a,b), self.partArray[a,b]) def addParts(self, newParts): for part in newParts: self.partArray[self.boardToMatrix(part[0], part[1])] = part[2] self.checkLineCompleted() def checkLineCompleted(self): # Check if a line has been completed, and remove it. # Translation array. We store where things are going, then move # them at the end. translateArray = numpy.zeros([self.partArrayA, self.partArrayB], dtype=numpy.int) foundCompleted = False # Check for full Vs for b in range(0, self.partArrayB): valid = True for a in range(0, self.partArrayA): if (self.partArray[a, b] == 0): valid = False if (valid): foundCompleted = True print "found v to remove: ", b # Mark all rows above this one so they can fall down for B in range(b + 1, self.partArrayB): for A in range(0, self.partArrayA): translateArray[A,B] += 1 # Now, if there were any collisions, go through the board and do all the translations if (foundCompleted): for b in range(0, self.partArrayB): for a in range(0, self.partArrayA): if (translateArray[a,b] > 0): self.partArray[a,b-translateArray[a,b]]=self.partArray[a,b] self.partArray[a,b] = 0 def collides(self, part): collides = False # Check left hand side of board if (part[0] <= self.xMax): if (part[1] < 0): collides = True else: if (part[1] < (part[0] - self.xMax)): collides = True # And right hand side if (part[1] <= self.yMax): if (part[0] < 0): collides = True else: if (part[0] < (part[1] - self.yMax)): collides = True if (collides == False): # Check if it collides with any pieces on the board overlapX = part[0] % 1 overlapY = part[1] % 1 if (overlapX and overlapY): if (self.partArray[self.boardToMatrix(part[0]-overlapX, part[1]-overlapY)] != 0) or \ (self.partArray[self.boardToMatrix(part[0]-overlapX, part[1]+overlapY)] != 0) or \ (self.partArray[self.boardToMatrix(part[0]+overlapX, part[1]-overlapY)] != 0) or \ (self.partArray[self.boardToMatrix(part[0]+overlapX, part[1]+overlapY)] != 0): collides = True elif (overlapX): if (self.partArray[self.boardToMatrix(part[0]-overlapX, part[1])] != 0) or \ (self.partArray[self.boardToMatrix(part[0]+overlapX, part[1])] != 0): collides = True elif (overlapY): if (self.partArray[self.boardToMatrix(part[0], part[1]-overlapY)] != 0) or \ (self.partArray[self.boardToMatrix(part[0], part[1]+overlapY)] != 0): collides = True else: if (self.partArray[self.boardToMatrix(part[0], part[1])] != 0): collides = True return collides def boardToMatrix(self,x,y): # Convert board coordinates to partition matrix coordinates if (x >= y): return self.xMax - x + y, y else: return self.xMax - x + y, x def matrixToBoard(self,a,b): # Convert partition matrix coordinates to board coordinates if (a <= self.xMax): return self.xMax - a + b, b else: return b, a - self.xMax + b class tetrisBlock(): def __init__(self, boardDrawer, looseParts): self.boardDrawer = boardDrawer self.grid = looseParts self.orientation = 0 # 0=0 degrees, 1=90 degrees, etc self.xPos = 18 # Center of the piece by weight, in board coordinates self.yPos = 18 self.parts = None # Array of part coordinates, filled in by a subclass. self.color = 1 self.updatesPerDrop = 20 # Number of times update() must be called before the piece falls self.updateCount = 0 # Counter for above. self.valid = 1 # 1=valid, 0=invalid. Invalid parts are stuck and should be replaced. def tryMove(self, rightKey, leftKey, upKey, downKey): xChange = 0 yChange = 0 oChange = 0 # only one of these can happen if (rightKey and downKey): xChange = -1 elif (leftKey and downKey): yChange = -1 elif (rightKey): xChange = -.5 yChange = .5 elif (leftKey): xChange = .5 yChange = -.5 elif (downKey): xChange = -.5 yChange = -.5 elif (upKey): oChange = 1 # If there was a move, check that it is valid if (self.moveValid(xChange, yChange, oChange)): self.xPos += xChange self.yPos += yChange self.orientation = (self.orientation + oChange) %4 def moveValid(self, xChange, yChange, oChange): for part in self.parts: if (self.grid.collides(self.translatePartTest(part, self.xPos+ xChange, \ self.yPos + yChange, (self.orientation + oChange)%4))): return 0 return 1 def translatePart(self, part): return self.translatePartTest(part, self.xPos, self.yPos, self.orientation) def translatePartTest(self, part, xPos, yPos, orientation): if (orientation == 0): newPart = (part[0], part[1]) elif (orientation == 1): newPart = (-part[1], part[0]) elif (orientation == 2): newPart = (-part[0], -part[1]) else: newPart = (part[1], -part[0]) return (newPart[0] + xPos, newPart[1] + yPos) def update(self): # Check if the part should be dropped. self.updateCount += 1 if (self.updateCount >= self.updatesPerDrop): self.updateCount = 0 # If the part can be moved down, then do so; otherwise, stick it to the # board and make a new part. If it is stuck out of bounds, the game is over. if (self.moveValid(-.5,-.5,0)): self.xPos -= .5 self.yPos -= .5 else: self.valid = 0 def getParts(self): # Translate the relative coordinates into world coordinates and return that list. translated = [] for part in self.parts: translatedPart = self.translatePart(part) translated.append((translatedPart[0], translatedPart[1], self.color)) return translated def draw(self): for part in self.parts: self.boardDrawer.drawDiamond(self.translatePart(part), self.color) class squareBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (0,0),(0,-1),(-1,-1),(-1,0) # Blocks in the part self.color = 1 class longBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (0,-2),(0,-1),(0,0),(0,1) # Blocks in the part self.color = 2 class teeBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (0,-1),(0,0),(0,1),(1,0) # Blocks in the part self.color = 3 class rightSBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (0,-1),(0,0),(1,0),(1,1) # Blocks in the part self.color = 4 class leftSBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (-1,0),(0,0),(0,1),(1,1) # Blocks in the part self.color = 5 class rightLBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (0,1),(0,0),(0,-1),(-1,-1) # Blocks in the part self.color = 6 class leftLBlock(tetrisBlock): def __init__(self, board, grid): tetrisBlock.__init__(self, board, grid) self.parts = (0,1),(0,0),(0,-1),(1,-1) # Blocks in the part self.color = 7 def newBlock(board, parts): a = random.randint(0,7) if (a == 0): return longBlock(board, parts) elif (a == 1): return squareBlock(board, parts) elif (a == 2): return teeBlock(board, parts) elif (a == 3): return leftSBlock(board, parts) elif (a == 4): return rightSBlock(board, parts) elif (a == 5): return rightLBlock(board, parts) else: return leftLBlock(board, parts) clock = pygame.time.Clock() background = pygame.image.load("sprites/background.png").convert() board = boardDrawer(screen) parts = looseParts(board) block = newBlock(board, parts) # Make the keyboard do autorepeat pygame.key.set_repeat(100,30) # for screencap #loopcount = 0 while 1: # Check our event queue keyPressed = 0 # Just record that any game key was pressed, sort them out later. for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() if (hasattr(event, 'key') and event.type == KEYDOWN): if (event.key == K_RIGHT): keyPressed = 1 elif event.key == K_LEFT: keyPressed = 1 elif event.key == K_UP: keyPressed = 1 elif event.key == K_DOWN: keyPressed = 1 elif event.key == K_ESCAPE: sys.exit(0) if (keyPressed): # Ignore the event at this point, and just grab whatever keys are # are pressed down. The event seems to only repeat the last pressed # key for some reason. keystate = pygame.key.get_pressed() block.tryMove(keystate[K_RIGHT], keystate[K_LEFT], keystate[K_UP], keystate[K_DOWN]) # Update (drop) the block, if necessary block.update() # Check if the block got stuck. If it did, add the parts to the loose # parts list and then make a new one. if (not block.valid): parts.addParts(block.getParts()) # Check if there is a line completion block = newBlock(board, parts) # Draw the background screen.blit(background, [0,0]) # Then the parts at the bottom of the screen parts.draw() # Then the current block block.draw() # for screencap # pygame.image.save(screen, ("screen_cap/no_%04i.jpg" % loopcount)) # loopcount += 1 # And display pygame.display.flip() # Enforce a max of 30fps clock.tick_busy_loop(30) |
Not Tetris! I have nightmares playing that game where the walls keep getting moved out on me. I don’t need to have the nighttime stress of playing it sideways.
The sideways tetris board looks like it would be a stylish necktie!
@Robin: Oh no! I didn’t mean to give anyone nightmares. Pretend it is pokemon?
@Nancy: Yes, yes indeed!
Very slick! How long do you think it took you to write that?
@chris coleman
I’m not sure how long the game took to write, I think a few long nights of work.