prog8/examples/tehtriz.p8

461 lines
16 KiB
Plaintext
Raw Normal View History

2019-03-06 21:11:16 +00:00
; TehTriz - a Tetris clone.
;
2019-03-07 01:28:01 +00:00
2019-03-07 22:29:23 +00:00
; @todo: slow down joystick input
; @todo: holding a block
; @todo: show next 2 blocks instead of just 1
; @todo: deal with rotation when block is against a wall or another block (wall kicks) (if no rotate in place possible: move 1 left, try rotate, else move 1 right, try rotate, else no rotation possible)
2019-03-07 01:28:01 +00:00
; @todo: simple sound effects? slight click when moving, swish when rotating/dropping, soft explosion when lines are cleared, buzz at game over
2019-03-06 21:11:16 +00:00
~ main {
const ubyte boardOffsetX = 14
const ubyte boardOffsetY = 3
const ubyte boardWidth = 10
const ubyte boardHeight = 20
2019-02-14 01:23:59 +00:00
const ubyte startXpos = boardOffsetX + 3
const ubyte startYpos = boardOffsetY - 2
2019-03-07 22:29:23 +00:00
ubyte lines
uword score
ubyte xpos
ubyte ypos
ubyte nextBlock
sub start() {
2019-02-14 01:23:59 +00:00
@(650) = 128 ; set all keys to repeat
2019-03-07 22:29:23 +00:00
newGame()
drawBoard()
gameOver() ; @todo fix game corruption because loop is called multiple times (???)
newgame:
newGame()
drawBoard()
2019-02-14 01:23:59 +00:00
spawnNextBlock()
2019-03-07 22:29:23 +00:00
ubyte joystick_delay=1
2019-02-14 01:23:59 +00:00
waitkey:
if c64.TIME_LO==30 {
c64.TIME_LO = 0
if blocklogic.canMoveDown(xpos, ypos) {
2019-02-21 00:31:33 +00:00
2019-03-07 01:28:01 +00:00
; slowly move the block down
drawBlock(xpos, ypos, 32)
ypos++
drawBlock(xpos, ypos, 160)
2019-02-21 00:31:33 +00:00
2019-02-14 01:23:59 +00:00
} else {
; block can't move further down!
; check if the game area is full, if not, spawn the next block at the top.
2019-03-07 01:28:01 +00:00
if blocklogic.isGameOver(xpos, ypos) {
2019-03-07 22:29:23 +00:00
gameOver()
goto newgame
2019-02-14 01:23:59 +00:00
} else {
spawnNextBlock()
}
}
}
2019-03-07 22:29:23 +00:00
ubyte key=c64.GETIN()
ubyte joystick1 = c64.CIA1PRB
if key==0 and joystick1==255 goto waitkey
2019-02-14 01:23:59 +00:00
2019-03-07 22:29:23 +00:00
if joystick1!=255 {
joystick_delay--
if_nz goto waitkey
2019-02-14 01:23:59 +00:00
}
2019-03-07 22:29:23 +00:00
if key==157 or key==',' or not (joystick1 & 4) {
2019-03-07 01:28:01 +00:00
; move left
2019-02-14 01:23:59 +00:00
if blocklogic.canMoveLeft(xpos, ypos) {
drawBlock(xpos, ypos, 32)
2019-03-07 01:28:01 +00:00
xpos--
2019-02-14 01:23:59 +00:00
drawBlock(xpos, ypos, 160)
}
}
2019-03-07 22:29:23 +00:00
else if key==29 or key=='.' or not (joystick1 & 8) {
2019-03-07 01:28:01 +00:00
; move right
2019-02-14 01:23:59 +00:00
if blocklogic.canMoveRight(xpos, ypos) {
drawBlock(xpos, ypos, 32)
2019-03-07 01:28:01 +00:00
xpos++
2019-02-14 01:23:59 +00:00
drawBlock(xpos, ypos, 160)
}
}
2019-03-07 22:29:23 +00:00
else if key==17 or key=='m' or not (joystick1 & 2) {
2019-03-07 01:28:01 +00:00
; move down faster
2019-02-14 01:23:59 +00:00
if blocklogic.canMoveDown(xpos, ypos) {
drawBlock(xpos, ypos, 32)
2019-03-07 01:28:01 +00:00
ypos++
2019-02-14 01:23:59 +00:00
drawBlock(xpos, ypos, 160)
}
}
2019-03-07 22:29:23 +00:00
else if key==145 or key==' ' or not (joystick1 & 1) {
2019-02-14 01:23:59 +00:00
; drop down immediately
drawBlock(xpos, ypos, 32)
2019-03-07 01:28:01 +00:00
ypos = boardOffsetY+boardHeight-4 ; @todo determine proper y position
2019-02-14 01:23:59 +00:00
drawBlock(xpos, ypos, 160)
}
2019-03-07 22:29:23 +00:00
else if key=='z' { ; no joystick equivalent (there is only 1 fire button)
2019-03-07 01:28:01 +00:00
; rotate counter clockwise
if blocklogic.canRotateCCW(xpos, ypos) {
2019-02-21 00:31:33 +00:00
drawBlock(xpos, ypos, 32)
blocklogic.rotateCCW()
drawBlock(xpos, ypos, 160)
}
2019-02-14 01:23:59 +00:00
}
2019-03-07 22:29:23 +00:00
else if key=='x' or not (joystick1 & 16) {
2019-03-07 01:28:01 +00:00
; rotate clockwise
if blocklogic.canRotateCW(xpos, ypos) {
2019-02-21 00:31:33 +00:00
drawBlock(xpos, ypos, 32)
blocklogic.rotateCW()
drawBlock(xpos, ypos, 160)
}
2019-02-14 01:23:59 +00:00
}
2019-03-07 22:29:23 +00:00
joystick_delay = 140 ; this more or less slows down the joystick movements to the rate of what key repeats do
2019-03-07 01:28:01 +00:00
; @todo check if line(s) are full -> flash/clear line(s) + add score + move rest down
2019-02-14 01:23:59 +00:00
goto waitkey
}
2019-03-07 22:29:23 +00:00
sub gameOver() {
2019-03-07 01:28:01 +00:00
c64scr.PLOT(7, 7)
c64.CHROUT('U')
c64scr.print("────────────────────────")
c64.CHROUT('I')
c64scr.PLOT(7, 8)
c64scr.print("│*** g a m e o v e r ***│")
c64scr.PLOT(7, 9)
c64.CHROUT('J')
c64scr.print("────────────────────────")
c64.CHROUT('K')
2019-03-07 22:29:23 +00:00
c64scr.PLOT(7, 18)
c64.CHROUT('U')
c64scr.print("────────────────────────")
c64.CHROUT('I')
c64scr.PLOT(7, 19)
c64scr.print("│ f1 for new game │")
c64scr.PLOT(7, 20)
c64.CHROUT('J')
c64scr.print("────────────────────────")
c64.CHROUT('K')
while(c64.GETIN()!=133) {
; endless loop until user presses F1 to restart the game
2019-03-07 01:28:01 +00:00
}
}
2019-03-07 22:29:23 +00:00
sub newGame() {
lines = 0
score = 0
xpos = startXpos
ypos = startYpos
nextBlock = rnd() % 7
c64.CLEARSCR()
}
2019-02-14 01:23:59 +00:00
sub spawnNextBlock() {
c64.TIME_LO = 0
blocklogic.newCurrentBlock(nextBlock)
2019-03-07 01:28:01 +00:00
nextBlock = (rnd() + c64.RASTER) % 7
2019-02-14 01:23:59 +00:00
drawNextBlock()
xpos = startXpos
ypos = startYpos
drawBlock(xpos, ypos, 160)
}
sub drawBoard() {
2019-02-14 01:23:59 +00:00
c64.COLOR = 7
c64scr.PLOT(1,1)
2019-02-14 01:23:59 +00:00
c64scr.print("irmen's")
c64scr.PLOT(1,2)
c64scr.print("teh▁triz")
2019-02-14 01:23:59 +00:00
c64.COLOR = 5
c64scr.PLOT(28,3)
c64scr.print("next:")
c64scr.PLOT(28,10)
c64scr.print("lines:")
c64scr.PLOT(28,14)
c64scr.print("score:")
c64.COLOR = 12
2019-03-07 22:29:23 +00:00
c64scr.PLOT(28,18)
2019-02-14 01:23:59 +00:00
c64scr.print("controls:")
c64.COLOR = 11
2019-03-07 22:29:23 +00:00
c64scr.PLOT(27,19)
2019-02-14 01:23:59 +00:00
c64scr.print("z/x rotate")
2019-03-07 22:29:23 +00:00
c64scr.PLOT(27,20)
2019-02-14 01:23:59 +00:00
c64scr.print(",/. move")
2019-03-07 22:29:23 +00:00
c64scr.PLOT(27,21)
2019-02-14 01:23:59 +00:00
c64scr.print("spc drop")
2019-03-07 22:29:23 +00:00
c64scr.PLOT(27,22)
2019-02-14 01:23:59 +00:00
c64scr.print(" m descend")
2019-03-07 22:29:23 +00:00
c64scr.PLOT(27,23)
c64scr.print("or joystick1")
2019-02-14 01:23:59 +00:00
2019-02-21 00:31:33 +00:00
c64scr.setcc(boardOffsetX-1, boardOffsetY-2, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX-1, boardOffsetY-3, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-2, 255, 0) ; invisible barrier
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-3, 255, 0) ; invisible barrier
2019-02-14 01:23:59 +00:00
c64scr.setcc(boardOffsetX-1, boardOffsetY-1, 108, 12)
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-1, 123, 12)
2019-02-21 00:31:33 +00:00
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY-1, 123, 12)
c64scr.setcc(boardOffsetX-1, boardOffsetY+boardHeight, 124, 12)
c64scr.setcc(boardOffsetX+boardWidth, boardOffsetY+boardHeight, 126, 12)
ubyte i
2019-02-21 00:31:33 +00:00
for i in boardOffsetX+boardWidth-1 to boardOffsetX step -1 {
c64scr.setcc(i, boardOffsetY-3, 255, 0) ; invisible barrier
c64scr.setcc(i, boardOffsetY+boardHeight, 69, 11)
2019-02-21 00:31:33 +00:00
}
for i in boardOffsetY+boardHeight-1 to boardOffsetY step -1 {
c64scr.setcc(boardOffsetX-1, i, 89, 11)
c64scr.setcc(boardOffsetX+boardWidth, i, 84, 11)
}
2019-02-14 01:23:59 +00:00
2019-03-07 01:28:01 +00:00
for i in 7 to 0 step -1 {
blocklogic.newCurrentBlock(i)
drawBlock(3, 3+i*3, 102) ; 102 = stipple
2019-02-14 01:23:59 +00:00
}
drawScore()
}
2019-02-14 01:23:59 +00:00
sub drawScore() {
c64.COLOR=1
c64scr.PLOT(30,11)
c64scr.print_ub(lines)
c64scr.PLOT(30,15)
c64scr.print_uw(score)
}
sub drawNextBlock() {
for ubyte x in 31 to 28 step -1 {
c64scr.setcc(x, 5, ' ', 0)
c64scr.setcc(x, 6, ' ', 0)
}
; reuse the normal block draw routine (because we can't manipulate array pointers yet)
ubyte prev = blocklogic.currentBlockNum
blocklogic.newCurrentBlock(nextBlock)
2019-02-14 01:23:59 +00:00
drawBlock(28, 5, 160)
blocklogic.newCurrentBlock(prev)
2019-02-14 01:23:59 +00:00
}
sub drawBlock(ubyte x, ubyte y, ubyte character) {
for ubyte i in 15 to 0 step -1 {
ubyte c=blocklogic.currentBlock[i]
if c
c64scr.setcc((i&3)+x, (i/4)+y, character, c)
}
}
}
~ blocklogic {
ubyte currentBlockNum
ubyte[16] currentBlock
ubyte[16] rotated
2019-03-06 21:11:16 +00:00
; the 7 tetrominos
ubyte[16] blockI = [0,0,0,0, ; cyan ; note: special rotation (around matrix center)
2019-03-06 21:11:16 +00:00
3,3,3,3,
0,0,0,0,
0,0,0,0]
ubyte[16] blockJ = [6,0,0,0, ; blue
6,6,6,0,
0,0,0,0,
0,0,0,0]
ubyte[16] blockL = [0,0,8,0, ; orange
8,8,8,0,
0,0,0,0,
0,0,0,0]
ubyte[16] blockO = [0,7,7,0, ; yellow ; note: no rotation (square)
0,7,7,0,
0,0,0,0,
0,0,0,0]
ubyte[16] blockS = [0,5,5,0, ; green
5,5,0,0,
0,0,0,0,
0,0,0,0]
ubyte[16] blockT = [0,4,0,0, ; purple
4,4,4,0,
0,0,0,0,
0,0,0,0]
ubyte[16] blockZ = [2,2,0,0, ; red
0,2,2,0,
0,0,0,0,
0,0,0,0]
2019-03-06 21:11:16 +00:00
; @todo would be nice to have a pointer type, like so:
; uword[7] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
sub newCurrentBlock(ubyte block) {
currentBlockNum = block
2019-03-06 21:11:16 +00:00
if block==0
memcopy(blockI, currentBlock, len(currentBlock))
else if block==1
memcopy(blockJ, currentBlock, len(currentBlock))
else if block==2
memcopy(blockL, currentBlock, len(currentBlock))
else if block==3
memcopy(blockO, currentBlock, len(currentBlock))
else if block==4
memcopy(blockS, currentBlock, len(currentBlock))
else if block==5
memcopy(blockT, currentBlock, len(currentBlock))
else if block==6
memcopy(blockZ, currentBlock, len(currentBlock))
}
2019-02-14 01:23:59 +00:00
sub rotateCW() {
; rotates the current block clockwise.
if currentBlockNum==0 {
; the 'I' block rotates a 4x4 matrix around the center
rotated[0] = currentBlock[12]
rotated[1] = currentBlock[8]
rotated[2] = currentBlock[4]
rotated[3] = currentBlock[0]
rotated[4] = currentBlock[13]
rotated[5] = currentBlock[9]
rotated[6] = currentBlock[5]
rotated[7] = currentBlock[1]
rotated[8] = currentBlock[14]
rotated[9] = currentBlock[10]
rotated[10] = currentBlock[6]
rotated[11] = currentBlock[2]
rotated[12] = currentBlock[15]
rotated[13] = currentBlock[11]
rotated[14] = currentBlock[7]
rotated[15] = currentBlock[3]
}
2019-02-21 00:31:33 +00:00
else if currentBlockNum!=3 {
; rotate all blocks (except 3, the square) around their center square in a 3x3 matrix
memset(rotated, len(rotated), 0)
2019-02-21 00:31:33 +00:00
rotated[0] = currentBlock[8]
rotated[1] = currentBlock[4]
rotated[2] = currentBlock[0]
rotated[4] = currentBlock[9]
rotated[5] = currentBlock[5]
2019-02-21 00:31:33 +00:00
rotated[6] = currentBlock[1]
rotated[8] = currentBlock[10]
rotated[9] = currentBlock[6]
rotated[10] = currentBlock[2]
2019-02-14 01:23:59 +00:00
}
memcopy(rotated, currentBlock, len(currentBlock))
2019-02-14 01:23:59 +00:00
}
sub rotateCCW() {
; rotates the current block counterclockwise.
if currentBlockNum==0 {
; the 'I' block rotates a 4x4 matrix around the center
rotated[0] = currentBlock[3]
rotated[1] = currentBlock[7]
rotated[2] = currentBlock[11]
rotated[3] = currentBlock[15]
rotated[4] = currentBlock[2]
rotated[5] = currentBlock[6]
rotated[6] = currentBlock[10]
rotated[7] = currentBlock[14]
rotated[8] = currentBlock[1]
rotated[9] = currentBlock[5]
rotated[10] = currentBlock[9]
rotated[11] = currentBlock[13]
rotated[12] = currentBlock[0]
rotated[13] = currentBlock[4]
rotated[14] = currentBlock[8]
rotated[15] = currentBlock[12]
}
2019-02-21 00:31:33 +00:00
else if currentBlockNum!=3 {
; rotate all blocks (except 3, the square) around their center square in a 3x3 matrix
memset(rotated, len(rotated), 0)
2019-02-21 00:31:33 +00:00
rotated[0] = currentBlock[2]
rotated[1] = currentBlock[6]
rotated[2] = currentBlock[10]
rotated[4] = currentBlock[1]
rotated[5] = currentBlock[5]
2019-02-21 00:31:33 +00:00
rotated[6] = currentBlock[9]
rotated[8] = currentBlock[0]
rotated[9] = currentBlock[4]
rotated[10] = currentBlock[8]
2019-02-14 01:23:59 +00:00
}
memcopy(rotated, currentBlock, len(currentBlock))
2019-02-14 01:23:59 +00:00
}
2019-03-07 01:28:01 +00:00
; For movement checking it is not needed to clamp the x/y coordinates,
; because we have to check for brick collisions anyway.
; The full play area is bordered by (in)visible characters that will collide.
; Collision is determined by reading the screen data directly.
; This means the current position of the block on the screen has to be cleared first,
; and redrawn after the collision result has been determined.
sub canRotateCW(ubyte xpos, ubyte ypos) -> ubyte {
main.drawBlock(xpos, ypos, 32)
2019-02-21 00:31:33 +00:00
rotateCW()
2019-03-07 01:28:01 +00:00
ubyte collision = collides(xpos, ypos)
2019-02-21 00:31:33 +00:00
rotateCCW()
2019-03-07 01:28:01 +00:00
main.drawBlock(xpos, ypos, 160)
return not collision
2019-02-14 01:23:59 +00:00
}
2019-03-07 01:28:01 +00:00
sub canRotateCCW(ubyte xpos, ubyte ypos) -> ubyte {
main.drawBlock(xpos, ypos, 32)
2019-02-21 00:31:33 +00:00
rotateCCW()
2019-03-07 01:28:01 +00:00
ubyte collision = collides(xpos, ypos)
2019-02-21 00:31:33 +00:00
rotateCW()
2019-03-07 01:28:01 +00:00
main.drawBlock(xpos, ypos, 160)
return not collision
2019-02-14 01:23:59 +00:00
}
sub canMoveLeft(ubyte xpos, ubyte ypos) -> ubyte {
2019-02-21 00:31:33 +00:00
main.drawBlock(xpos, ypos, 32)
2019-03-07 01:28:01 +00:00
ubyte collision = collides(xpos-1, ypos)
2019-02-21 00:31:33 +00:00
main.drawBlock(xpos, ypos, 160)
2019-03-07 01:28:01 +00:00
return not collision
2019-02-14 01:23:59 +00:00
}
sub canMoveRight(ubyte xpos, ubyte ypos) -> ubyte {
2019-02-21 00:31:33 +00:00
main.drawBlock(xpos, ypos, 32)
2019-03-07 01:28:01 +00:00
ubyte collision = collides(xpos+1, ypos)
2019-02-21 00:31:33 +00:00
main.drawBlock(xpos, ypos, 160)
2019-03-07 01:28:01 +00:00
return not collision
2019-02-14 01:23:59 +00:00
}
sub canMoveDown(ubyte xpos, ubyte ypos) -> ubyte {
2019-03-07 01:28:01 +00:00
main.drawBlock(xpos, ypos, 32)
ubyte collision = collides(xpos, ypos+1)
main.drawBlock(xpos, ypos, 160)
return not collision
}
sub collides(ubyte xpos, ubyte ypos) -> ubyte {
return currentBlock[0] and c64scr.getchr(xpos, ypos)!=32
or currentBlock[1] and c64scr.getchr(xpos+1, ypos)!=32
or currentBlock[2] and c64scr.getchr(xpos+2, ypos)!=32
or currentBlock[3] and c64scr.getchr(xpos+3, ypos)!=32
or currentBlock[4] and c64scr.getchr(xpos, ypos+1)!=32
or currentBlock[5] and c64scr.getchr(xpos+1, ypos+1)!=32
or currentBlock[6] and c64scr.getchr(xpos+2, ypos+1)!=32
or currentBlock[7] and c64scr.getchr(xpos+3, ypos+1)!=32
or currentBlock[8] and c64scr.getchr(xpos, ypos+2)!=32
or currentBlock[9] and c64scr.getchr(xpos+1, ypos+2)!=32
or currentBlock[10] and c64scr.getchr(xpos+2, ypos+2)!=32
or currentBlock[11] and c64scr.getchr(xpos+3, ypos+2)!=32
or currentBlock[12] and c64scr.getchr(xpos, ypos+3)!=32
or currentBlock[13] and c64scr.getchr(xpos+1, ypos+3)!=32
or currentBlock[14] and c64scr.getchr(xpos+2, ypos+3)!=32
or currentBlock[15] and c64scr.getchr(xpos+3, ypos+3)!=32
2019-02-14 01:23:59 +00:00
}
2019-03-07 01:28:01 +00:00
sub isGameOver(ubyte xpos, ubyte ypos) -> ubyte {
return ypos==main.startYpos and not canMoveDown(xpos, ypos)
2019-02-14 01:23:59 +00:00
}
}