356 lines
11 KiB
Python
Executable File
356 lines
11 KiB
Python
Executable File
#
|
|
# 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() |