' based on code from https://www.youtube.com/watch?v=8OK8_tHeCIA

CLEAR ALL
RANDOMIZE TIMER

DIM tetromino$(7)
nFieldWidth= 12
nFieldHeight= 18
DIM pField(nFieldWidth*nFieldHeight)

nScreenWidth= 40   ' Tilemap size in columns
nScreenHeight= 25  ' Tilemap size in rows
xOffset=INT((nScreenWidth-nFieldWidth)/2)
yOffset=INT((nScreenHeight-nFieldHeight)/2)

FUNCTION CustomCharBlock(ch,c)
    PALETTE ASC("X"),c
    PALETTE ASC("+"),LIGHTEN(c,0.25)
    PALETTE ASC("-"),DARKEN(c,0.25)
    CUSTOM TILE ch,0,"++++++++"
    CUSTOM TILE ch,1,"+      -"
    CUSTOM TILE ch,2,"+ XXXX -"
    CUSTOM TILE ch,3,"+ XXXX -"
    CUSTOM TILE ch,4,"+ XXXX -"
    CUSTOM TILE ch,5,"+ XXXX -"
    CUSTOM TILE ch,6,"+      -"
    CUSTOM TILE ch,7,"+-------"
END FUNCTION

FUNCTION CustomCharEqual(ch,c)
    PALETTE ASC("X"),c
    CUSTOM TILE ch,0,"        "
    CUSTOM TILE ch,1,"        "
    CUSTOM TILE ch,2,"XXXXXXXX"
    CUSTOM TILE ch,3,"        "
    CUSTOM TILE ch,4,"        "
    CUSTOM TILE ch,5,"XXXXXXXX"
    CUSTOM TILE ch,6,"        "
    CUSTOM TILE ch,7,"        "
END FUNCTION

FUNCTION RotatePiece(px,py,r)
    SELECT CASE r MOD 4
        CASE 0
            RotatePiece=py*4+px
        CASE 1
            RotatePiece=12+py-(px*4)
        CASE 2
            RotatePiece=15-(py*4)-px
        CASE 3
            RotatePiece=3-py+(px*4)
    END SELECT
END FUNCTION

FUNCTION DoesPieceFit(nTetromino,nRotation,nPosX,nPosY)
    SHARE tetromino$()
    SHARE nFieldWidth,nFieldHeight
    SHARE pField()
    ' All playfield cells > 0 are occupied
    FOR px=0 TO 3
        FOR py=0 TO 3
            ' Get index into piece
            CALL RotatePiece(px,py,nRotation) TO idx
            ' Get index into field
            fi=(nPosY+py)*nFieldWidth+(nPosX+px)

            ' Check that test is in bounds. Note out of bounds does
            ' not necessarily mean a fail, as the long vertical piece
            ' can have cells that lie outside the boundary, so we'll
            ' just ignore them
            IF nPosX+px>=0 AND nPosX+px<nFieldWidth THEN
                IF nPosY+py>=0 AND nPosY+py<nFieldHeight THEN
                    ' In bounds so do collision check
                    IF MID$(tetromino$(nTetromino),idx+1,1)<>"." AND pField(fi)<>0 THEN
                        DoesPieceFit=FALSE
                        EXIT FUNCTION
                    END IF
                END IF
            END IF
        NEXT py
    NEXT px
    DoesPieceFit=TRUE
END FUNCTION

' Create a new Tilemap screen
TILEMAP 1 NEW nScreenWidth,nScreenHeight
SPRITE 1 BIND TILEMAP 1 SHOW FIT
TARGET TILEMAP 1
DOUBLE BUFFER

' Create custom characters
PALETTE ".",RGB(0,0,0)
CustomCharBlock(TCHAR("A"),RGB(0,128,255))
CustomCharBlock(TCHAR("B"),RGB(128,0,255))
CustomCharBlock(TCHAR("C"),RGB(255,255,0))
CustomCharBlock(TCHAR("D"),RGB(255,0,0))
CustomCharBlock(TCHAR("E"),RGB(0,255,0))
CustomCharBlock(TCHAR("F"),RGB(255,128,0))
CustomCharBlock(TCHAR("G"),RGB(0,0,255))
CustomCharBlock(TCHAR("#"),RGB(128,128,128))
CustomCharEqual(TCHAR("="),RGB(255,255,255))

' Tetronimos 4x4
tetromino$(0)="..X...X...X...X."
tetromino$(1)="..X..XX...X....."
tetromino$(2)=".....XX..XX....."
tetromino$(3)="..X..XX..X......"
tetromino$(4)=".X...XX...X....."
tetromino$(5)=".X...X...XX....."
tetromino$(6)="..X...X..XX....."

' Playfield
FOR x=0 TO nFieldWidth-1
    FOR y=0 TO nFieldHeight-1
        IF x=0 OR x=nFieldWidth-1 OR y=nFieldHeight-1 THEN
            pField(y*nFieldWidth+x)=9
        ELSE
            pField(y*nFieldWidth+x)=0
        END IF
    NEXT y
NEXT x

' Game logic
DIM bKey(3)
nNextPiece=RANGE(0,6)
nCurrentPiece=RANGE(0,6)
nCurrentRotation=0
nCurrentX=nFieldWidth/2-2
nCurrentY=0
nSpeed=20
nSpeedCount=0
bForceDown=FALSE
bRotateHold=TRUE
nPieceCount=0
nScore=0
DIM vLines(nFieldHeight):vp=0
bGameOver=FALSE

' Print instructions
LOCATE ,TEXT.ROWS-1
PRINT "Use the arrow keys to move, 'Z' to rotate, Esc to quit."

WHILE NOT bGameOver
    ' Timing =======================
    PAUSE 50
    nSpeedCount=nSpeedCount+1
    bForceDown=(nSpeedCount=nSpeed)

    ' Input ========================
    ' L R D Z
    bKey(0)=KEY.DOWN(VK_LEFT)
    bKey(1)=KEY.DOWN(VK_RIGHT)
    bKey(2)=KEY.DOWN(VK_DOWN)
    bKey(3)=KEY.DOWN(ASC("z"))

    IF KEY.PRESSED(VK_ESC) THEN bGameOver=TRUE:EXIT WHILE

    ' Game Logic ===================

    ' Handle player movement
    IF bKey(0) AND DoesPieceFit(nCurrentPiece,nCurrentRotation,nCurrentX-1,nCurrentY) THEN nCurrentX=nCurrentX-1
    IF bKey(1) AND DoesPieceFit(nCurrentPiece,nCurrentRotation,nCurrentX+1,nCurrentY) THEN nCurrentX=nCurrentX+1
    IF bKey(2) AND DoesPieceFit(nCurrentPiece,nCurrentRotation,nCurrentX,nCurrentY+1) THEN nCurrentY=nCurrentY+1

    ' Rotate, but latch to stop wild spinning
    IF bKey(3) THEN
        IF bRotateHold AND DoesPieceFit(nCurrentPiece,nCurrentRotation+1,nCurrentX,nCurrentY) THEN nCurrentRotation=nCurrentRotation+1
        bRotateHold=FALSE
    ELSE
        bRotateHold=TRUE
    END IF

    ' Force the piece down the playfield if it's time
    IF bForceDown THEN
        ' Update difficulty every 50 pieces
        nSpeedCount=0
        nPieceCount=nPieceCount+1
        IF nPieceCount MOD 50=0 THEN IF nSpeed>=10 THEN nSpeed=nSpeed-1

        ' Test if piece can be moved down
        IF DoesPieceFit(nCurrentPiece,nCurrentRotation,nCurrentX,nCurrentY+1) THEN
            ' It can, so do it!
            nCurrentY=nCurrentY+1
        ELSE
            ' It can't! Lock the piece in place
            FOR px=0 TO 3
                FOR py=0 TO 3
                    r=RotatePiece(px,py,nCurrentRotation)
                    IF MID$(tetromino$(nCurrentPiece),r+1,1)<>"." THEN pField((nCurrentY+py)*nFieldWidth+(nCurrentX+px))=nCurrentPiece+1
                NEXT py
            NEXT px

            ' Check for completed lines
            FOR py=0 TO 3
                IF nCurrentY+py<nFieldHeight-1 THEN
                    bLine=TRUE
                    FOR px=1 TO nFieldWidth-2
                        bLine=bLine AND (pField((nCurrentY+py)*nFieldWidth+px))<>0
                    NEXT px
                    IF bLine THEN
                        ' Remove line, set to =
                        FOR px=1 TO nFieldWidth-2
                            pField((nCurrentY+py)*nFieldWidth+px)=8
                        NEXT px
                        vLines(vp)=nCurrentY+py
                        vp=vp+1
                    END IF
                END IF
            NEXT py

            nScore=nScore+25
            IF vp>0 THEN nScore=nScore+(1 SHL vp)*100

            ' Pick new piece
            nCurrentX=nFieldWidth/2-2
            nCurrentY=0
            nCurrentRotation=0
            nCurrentPiece=nNextPiece
            nNextPiece=RANGE(0,6)

            ' If piece does not fit straight away, game over!
            bGameOver=NOT DoesPieceFit(nCurrentPiece,nCurrentRotation,nCurrentX,nCurrentY)
        END IF
    END IF

    ' Display ======================

    CLEAR TILEMAP

    ' Draw playfield
    FOR x=0 TO nFieldWidth-1
        FOR y=0 TO nFieldHeight-1
            PUT TILE x+xOffset,y+yOffset,TCHAR(ASC(" ABCDEFG=#",pField(y*nFieldWidth+x)+1))
        NEXT y
    NEXT x

    ' Draw current piece
    FOR px=0 TO 3
        FOR py=0 TO 3
            r=RotatePiece(px,py,nCurrentRotation)
            IF MID$(tetromino$(nCurrentPiece),r+1,1)<>"." THEN
                PUT TILE nCurrentX+px+xOffset,nCurrentY+py+yOffset,TCHAR(nCurrentPiece+65)
            END IF
        NEXT py
    NEXT px

    ' Draw next piece
    FOR px=0 TO 3
        FOR py=0 TO 3
            r=RotatePiece(px,py,0)
            IF MID$(tetromino$(nNextPiece),r+1,1)<>"." THEN
                PUT TILE px+xOffset+nFieldWidth+2,py+yOffset,TCHAR(nNextPiece+65)
            ELSE
                PUT TILE px+xOffset+nFieldWidth+2,py+yOffset,TCHAR(32)
            END IF
        NEXT py
    NEXT px

    ' Swap buffers
    SWAP BUFFERS

    ' Draw score
    LOCATE 1,1
    PRINT "Score: ";nScore

    ' Animate line completion
    IF vp>0 THEN
        ' Display frame
        PAUSE 400

        ' Remove the completed lines
        FOR i=0 TO vp-1
            v=vLines(i)
            FOR px=1 TO nFieldWidth-2
                FOR py=v TO 1 STEP -1
                    pField(py*nFieldWidth+px)=pField((py-1)*nFieldWidth+px)
                NEXT py
                pField(px)=0
            NEXT px
        NEXT i
        vp=0
    END IF
WEND

PRINT "Game Over!! Score: ";nScore
END
