# # requires Windows (for input.py to work) # Copyright (c) 2016 Alexander Pruss. MIT License. # # # Make sure to have input.py and text.py in the same directory. # from mc import * from time import sleep,time from random import randint import input import text from fonts import FONTS FONT = 'thin9pt' #metrix7pt HEIGHT = 20 WIDTH = 10 BORDER = WOOL_BLACK BACKGROUND = STAINED_GLASS_BLACK DELAYS = ( 0.5, 0.45, 0.4, 0.35, 0.3, 0.25, 0.2, 0.15, 0.1, 0.05) PIECES = ( (('XXXX',), ('.X','.X','.X','.X')), (('XX','XX'),), (('XXX', '..X'), ('.X', '.X', 'XX'), ('X','XXX'), ('XX', 'X', 'X')), (('XXX', 'X'), ('XX', '.X', '.X'), ('..X', 'XXX'), ('X.', 'X.', 'XX')), (('XX', '.XX'), ('.X','XX', 'X.')), (('.XX', 'XX'), ('X.', 'XX', '.X')) ) def inputMoveDown(): return input.wasPressedSinceLast(input.DOWN) def inputMoveLeft(): return input.wasPressedSinceLast(input.LEFT) def inputMoveRight(): return input.wasPressedSinceLast(input.RIGHT) def inputRotateLeft(): return input.wasPressedSinceLast(input.PRIOR) def inputRotateRight(): return input.wasPressedSinceLast(input.NEXT) def inputNext(): return input.wasPressedSinceLast(ord('N')) def inputLevelUp(): return input.wasPressedSinceLast(ord('L')) def inputPause(): return input.wasPressedSinceLast(ord('P')) def answerYes(): input.clearPressBuffer(ord('Y')) input.clearPressBuffer(ord('N')) while True: if input.wasPressedSinceLast(ord('Y')): return True if input.wasPressedSinceLast(ord('N')): return False sleep(0.1) def clearInput(): for k in (input.DOWN, input.LEFT, input.RIGHT, input.PRIOR, input.NEXT, input.UP, ord('N'), ord('L'), ord('P'), ord('Y')): input.clearPressBuffer(k) def drawBoard(): mc.setBlocks(left-1, bottom-1, plane, left+WIDTH, bottom-1, plane, BORDER) mc.setBlocks(left-1, bottom+HEIGHT, plane, left+WIDTH, bottom+HEIGHT, plane, BORDER) mc.setBlocks(left-1, bottom, plane, left, bottom+HEIGHT-1, plane, BORDER) mc.setBlocks(left+WIDTH, bottom, plane, left+WIDTH, bottom+HEIGHT-1, plane, BORDER) mc.setBlocks(left-1, bottom-1, plane-1, left+WIDTH, bottom+HEIGHT, plane-1, BACKGROUND) mc.setBlocks(left, bottom, plane, left+WIDTH-1, bottom+HEIGHT-1, plane, AIR) def pieceWidth(piece): return max((len(a) for a in piece)) def enumeratePiece(x, y, piece): for row in range(len(piece)): if y-row < HEIGHT: for col in range(len(piece[row])): if piece[row][col] == 'X': yield (x+col,y-row) def movePiece(oldX, oldY, oldPiece, x, y, piece, color): new = set(enumeratePiece(x, y, piece)) if oldPiece: old = set(enumeratePiece(oldX, oldY, oldPiece)) for (x,y) in old-new: mc.setBlock(x+left, y+bottom, plane, AIR) new = new - old for (x,y) in new: mc.setBlock(x+left, y+bottom, plane, color) def eraseNext(): mc.setBlocks(left+WIDTH+2,bottom+3,plane,left+WIDTH+2+3,bottom+6,plane,AIR) def drawNext(): eraseNext() for (x,y) in enumeratePiece(WIDTH+2, 6, nextFamily[nextOrientation]): mc.setBlock(x+left, y+bottom, plane, nextColor) def drawBuffer(buffer): for x,y in buffer: mc.setBlock(x,y,plane,buffer[(x,y)]) def makeNext(): global nextFamily, nextColor, nextOrientation n = randint(0, len(PIECES)-1) nextFamily = PIECES[n] nextColor = Block(WOOL.id, (n+1) % 16) nextOrientation = randint(0, len(nextFamily)-1) def placePiece(): global color, family, orientation, descendDelay, droppedFrom, didShowNext family = nextFamily orientation = nextOrientation color = nextColor makeNext() piece = family[orientation] x = WIDTH // 2 - pieceWidth(piece) y = HEIGHT + len(piece) - 2 descendDelay = currentDescendDelay droppedFrom = None didShowNext = showNext if showNext: drawNext() return (x,y) def fit(x, y, piece): for (xx,yy) in enumeratePiece(x, y, piece): if yy < 0 or xx >= WIDTH or xx < 0 or board[xx][yy] is not None: return False return True def descend(): global descendTimer if descendTimer + descendDelay <= time(): descendTimer += descendDelay return True return False def hide(): mc.setBlocks(left, bottom, plane, left+WIDTH-1, bottom+HEIGHT-1, plane, GLASS) text.drawText(mc, FONTS['nicefontbold'], Vec3(left+WIDTH//2,bottom+5,plane), Vec3(1,0,0), Vec3(0,1,0), "P", SEA_LANTERN, align=text.ALIGN_CENTER) def restore(x, y): for xx in range(WIDTH): for yy in range(HEIGHT): mc.setBlock(xx+left,yy+bottom,plane,board[xx][yy] or AIR) movePiece(None, None, None, x, y, family[orientation], color) def addPiece(x, y, piece, color): global score,level,totalDropped for (xx,yy) in enumeratePiece(x, y, piece): board[xx][yy] = color dropCount = 0 while True: foundRow = False for y in range(HEIGHT): full = True for x in range(WIDTH): if board[x][y] is None: full = False break if full: dropCount += 1 foundRow = True for y2 in range(y, HEIGHT-1): for x in range(WIDTH): b = board[x][y2+1] board[x][y2] = b mc.setBlock(left+x,bottom+y2,plane,b if b is not None else AIR) for x in range(WIDTH): board[x][HEIGHT-1] = None mc.setBlock(left+x,bottom+HEIGHT-1,plane,AIR) if not foundRow: break if didShowNext: score += 3 + (3*(level-1))//2 + droppedFrom else: score += 5 + 2*(level-1) + droppedFrom if dropCount: totalDropped += dropCount level = 1 + totalDropped // 10 + extraLevels updateScoreAndLevel() def updateText(buffer,x,y,s,align): newBuffer = {} if s is not None: text.drawText(mc, FONTS['thin9pt'], Vec3(x,y,plane), Vec3(1,0,0), Vec3(0,1,0), s, SEA_LANTERN, background=None, align=align, buffer=newBuffer) for pos in buffer: if pos not in newBuffer: mc.setBlock(pos, AIR) for pos in newBuffer: if pos not in buffer: mc.setBlock(pos, SEA_LANTERN) return newBuffer def updateScoreAndLevel(): global scoreBuffer, levelBuffer, currentDescendDelay, level if level > 10: level = 10 scoreBuffer = updateText(scoreBuffer,left+WIDTH+2,bottom+HEIGHT-10,str(score),text.ALIGN_LEFT) levelBuffer = updateText(levelBuffer,left-1,bottom+HEIGHT-10,str(level),text.ALIGN_RIGHT) currentDescendDelay = DELAYS[level-1] def clearScoreAndLevel(): global scoreBuffer, levelBuffer, currentDescendDelay, level scoreBuffer = updateText(scoreBuffer,left+WIDTH+2,bottom+HEIGHT-10,None,text.ALIGN_LEFT) levelBuffer = updateText(levelBuffer,left-1,bottom+HEIGHT-10,None,text.ALIGN_RIGHT) def game(): global score, level, extraLevels, totalDropped, scoreBuffer, levelBuffer, showNext, didShowNext global orientation, board, descendTimer, droppedFrom, descendDelay board = [[None for i in range(HEIGHT)] for j in range(WIDTH)] drawBoard() score = 0 level = 1 extraLevels = 0 totalDropped = 0 scoreBuffer = {} levelBuffer = {} showNext = False updateScoreAndLevel() makeNext() newPiece = True while True: if newPiece: x,y = placePiece() oldPiece = None if not fit(x, y, family[orientation]): break draw = True newPiece = False fall = False clearInput() descendTimer = time() else: oldPiece = family[orientation] draw = False oldX = x oldY = y if inputPause(): t0 = time() hide() while not inputPause(): sleep(0.025) clearInput() restore(x, y) descendTimer += time() - t0 if not fall: if inputLevelUp(): extraLevels += 1 level += 1 updateScoreAndLevel() descendDelay = currentDescendDelay if inputMoveLeft() and fit(x-1, y, family[orientation]): x -= 1 draw = True if inputMoveRight() and fit(x+1, y, family[orientation]): x += 1 draw = True if inputRotateLeft() and fit(x, y, family[(orientation-1)%len(family)]): orientation = (orientation-1)%len(family) draw = True if inputRotateRight() and fit(x, y, family[(orientation+1)%len(family)]): orientation = (orientation+1)%len(family) draw = True if inputMoveDown(): fall = True droppedFrom = y+1-len(family[orientation]) descendDelay = 0.05 if inputNext(): showNext = not showNext if showNext: didShowNext = True drawNext() else: eraseNext() if descend(): if not fit(x, y-1, family[orientation]): if droppedFrom is None: droppedFrom = y+1-len(family[orientation]) addPiece(x, y, family[orientation], color) newPiece = True else: draw = True y -= 1 if draw: movePiece(oldX, oldY, oldPiece, x, y, family[orientation], color) sleep(0.025) return score mc = Minecraft() mc.postToChat("Left/Right arrow: move") mc.postToChat("Up: rotate right") mc.postToChat("PageUp/PageDown: rotate left/right") mc.postToChat("N: toggle view next") mc.postToChat("P: pause") mc.postToChat("L: next level") playerPos = mc.player.getTilePos() mc.player.setRotation(180) mc.player.setPitch(-26) mc.player.setTilePos(playerPos.x, playerPos.y, playerPos.z + 14) left = playerPos.x - WIDTH // 2 plane = playerPos.z bottom = playerPos.y + 1 while True: s = game() mc.postToChat("Game Over: You got %d points" % s) mc.postToChat("Play again? (Y/N)") if not answerYes(): mc.postToChat("Goodbye!") break clearScoreAndLevel()