'
' This is a remake of the classic 80's game "Qix".
'
' http://en.wikipedia.org/wiki/Qix
'
' The rendering is done via GTK using the HUG abstraction layer. As GTK is not really 
' a game platform, the performance may vary on low-end systems. The game is playable on
' an EEEPc of 1Ghz. In order to achieve a better performance, please use HUG 0.85 or higher.
'
' Hope you enjoy this game! Greetings - PvE, April 2013.
'
' Version 1.0 - Initial release
' Version 1.1 - Compile options for better performance, set noscaling,
'               increased gamespeed, code simplification
' Version 1.2 - Better coloring variations, better enemy positioning
'-----------------------------------------------------------------------------------

INCLUDE "hug.bac", INIT, HUGOPTIONS, WINDOW, FRAME, CANVAS, KEY, TIMEOUT, PIXEL, MARK, TEXT, STOCK, ATTACH, DISPLAY, \
    CALLBACK, QUIT, OUT, SQUARE, GRAB$, SHOW, HIDE, MSGDIALOG

PRAGMA OPTIONS -O2 -s

CONST Xsize = 300
CONST Ysize = 300

CONST ENEMY_COL = 4
CONST TEMP_CHECK = -1
CONST TEMP_LINE = -2
CONST THRESHOLD = 75
CONST GAME_SPEED = 7
CONST GAME_STP = 0
CONST GAME_RUN = 1
CONST GAME_END = 2

DECLARE cach_board[Xsize][Ysize]
DECLARE real_board[Xsize][Ysize]
DECLARE me_board[Xsize][Ysize]
DECLARE enemy_board[Xsize][Ysize]

DECLARE xpos, ypos
DECLARE en_xpos[10], en_ypos[10], xoff[10], yoff[10]

' Gamestate: 0 = stop, 1 = go
DECLARE Game_State, Game_Level, Game_Lifes, Game_Score, Last_Color
DECLARE High_Score$

'-----------------------------------------------------------------------------------

SUB Initialize_Game

    LOCAL x, y

    ' Clean all boards
    FOR x = 0 TO Xsize-1
        FOR y = 0 TO Ysize-1
            real_board[x][y] = 0
            cach_board[x][y] = 0
            me_board[x][y] = 0
            enemy_board[x][y] = 0
        NEXT
    NEXT

    ' Clean the graphical board
    SQUARE("#FFFFFF", 0, 0, Xsize, Ysize, TRUE)

    ' Setup boundary
    FOR x = 2 TO Xsize-4
        real_board[x][2] = 1
        real_board[x][Ysize-4] = 1
    NEXT
    FOR y = 2 TO Ysize-4
        real_board[2][y] = 1
        real_board[Xsize-4][y] = 1
    NEXT

    ' My position
    xpos = Xsize/2
    ypos = Ysize-3

    ' Enemy settings
    FOR x = 0 TO Game_Level
        en_xpos[x] = RANDOM(Xsize-50)+25
        en_ypos[x] = RANDOM(50)+25
        xoff[x] = RANDOM(2)+1
        IF xoff[x] = 2 THEN xoff[x] = -1
        yoff[x] = RANDOM(2)+1
        IF yoff[x] = 2 THEN yoff[x] = -1
    NEXT

    CALL Draw_Me()
    CALL Draw_Board()
    Game_State = GAME_RUN

    TEXT(mark1, "0%")

END SUB

'-----------------------------------------------------------------------------------

SUB Draw_Board

    LOCAL x, y

    FOR x = 0 TO Xsize - 1
        FOR y = 0 TO Ysize - 1
            IF cach_board[x][y] <> real_board[x][y] THEN
                cach_board[x][y] = real_board[x][y]
                SELECT cach_board[x][y]
                    CASE 0
                        PIXEL("#FFFFFF", x, y)
                    CASE 1;
                    CASE TEMP_LINE
                        PIXEL("#000000", x, y)
                    CASE 2
                        PIXEL("#FF0000", x, y)
                    CASE 3
                        PIXEL("#00FF00", x, y)
                    CASE 4
                        PIXEL("#FFFF00", x, y)
                    CASE 5
                        PIXEL("#00FFFF", x, y)
                    CASE 6
                        PIXEL("#0000FF", x, y)
                    CASE 7
                        PIXEL("#FF00FF", x, y)
                    CASE 8
                        PIXEL("#FF0000", x, y)
                END SELECT
            END IF

            SELECT me_board[x][y]
                CASE 1
                    PIXEL("#000000", x, y)
                    cach_board[x][y] = 1
                CASE 2
                    PIXEL("#FF0000", x, y)
                    cach_board[x][y] = 2
            END SELECT

            SELECT enemy_board[x][y]
                CASE ENEMY_COL
                    PIXEL("#0000FF", x, y)
                    cach_board[x][y] = ENEMY_COL
            END SELECT
        NEXT
    NEXT

END SUB

'-----------------------------------------------------------------------------------

SUB Draw_Me

    LOCAL x, y

    FOR x = 0 TO Xsize-1
        FOR y = 0 TO Ysize-1
            me_board[x][y] = 0
        NEXT
    NEXT

    me_board[xpos-2][ypos-2] = 1
    me_board[xpos-1][ypos-2] = 1
    me_board[xpos][ypos-2] = 1
    me_board[xpos+1][ypos-2] = 1
    me_board[xpos+2][ypos-2] = 1

    me_board[xpos-2][ypos-1] = 1
    me_board[xpos-1][ypos-1] = 2
    me_board[xpos][ypos-1] = 2
    me_board[xpos+1][ypos-1] = 2
    me_board[xpos+2][ypos-1] = 1

    me_board[xpos-2][ypos] = 1
    me_board[xpos-1][ypos] = 2
    me_board[xpos][ypos] = 2
    me_board[xpos+1][ypos] = 2
    me_board[xpos+2][ypos] = 1

    me_board[xpos-2][ypos+1] = 1
    me_board[xpos-1][ypos+1] = 2
    me_board[xpos][ypos+1] = 2
    me_board[xpos+1][ypos+1] = 2
    me_board[xpos+2][ypos+1] = 1

    me_board[xpos-2][ypos+2] = 1
    me_board[xpos-1][ypos+2] = 1
    me_board[xpos][ypos+2] = 1
    me_board[xpos+1][ypos+2] = 1
    me_board[xpos+2][ypos+2] = 1

END SUB

'-----------------------------------------------------------------------------------

FUNCTION Fill_Area(NUMBER x, NUMBER y)

    IF x < 0 OR x > Xsize-1 OR y < 0 OR y > Ysize-1 OR enemy_board[x][y] = ENEMY_COL THEN RETURN FALSE

    IF real_board[x][y] <> 0 THEN
        RETURN TRUE
    ELSE
        real_board[x][y] = TEMP_CHECK
    END IF

    IF NOT(Fill_Area(x+1, y)) THEN RETURN FALSE
    IF NOT(Fill_Area(x-1, y)) THEN RETURN FALSE
    IF NOT(Fill_Area(x, y+1)) THEN RETURN FALSE
    IF NOT(Fill_Area(x, y-1)) THEN RETURN FALSE

    RETURN TRUE

END FUNCTION

'-----------------------------------------------------------------------------------

SUB Real_Fill(NUMBER color)

    LOCAL x, y, percentage

    FOR x = 0 TO Xsize-1
        FOR y = 0 TO Ysize-1
            IF real_board[x][y] = TEMP_CHECK THEN
                real_board[x][y] = color
            ELIF real_board[x][y] = TEMP_LINE THEN
                real_board[x][y] = 1
            ENDIF

            IF real_board[x][y] = 1 OR real_board[x][y] >= 3 THEN INCR percentage
        NEXT
    NEXT

    TEXT(mark1, STR$((percentage*100)/(Xsize*Ysize)) & "%")

END SUB

'-----------------------------------------------------------------------------------

SUB Start_Fill

    LOCAL color

    ' Make sure we have a different color each draw
    REPEAT
        color = RANDOM(6)+3
    UNTIL color <> Last_Color
    Last_Color = color

    ' Find area to fill
    IF NOT(Fill_Area(xpos-1, ypos-1)) THEN
        Real_Fill(0)
    ELSE
        Real_Fill(color)
    END IF
    IF NOT(Fill_Area(xpos+1, ypos-1)) THEN
        Real_Fill(0)
    ELSE
        Real_Fill(color)
    END IF
    IF NOT(Fill_Area(xpos-1, ypos+1)) THEN
        Real_Fill(0)
    ELSE
        Real_Fill(color)
    END IF
    IF NOT(Fill_Area(xpos+1, ypos+1)) THEN
        Real_Fill(0)
    ELSE
        Real_Fill(color)
    END IF

END SUB

'-----------------------------------------------------------------------------------

SUB Draw_Enemy

    LOCAL x, y, z

    FOR x = 0 TO Xsize-1
        FOR y = 0 TO Ysize-1
            enemy_board[x][y] = 0
        NEXT
    NEXT

    FOR z = 0 TO Game_Level

        INCR en_xpos[z], xoff[z]
        IF real_board[en_xpos[z]][en_ypos[z]] <> 0 THEN
            ' Detect collision with the actual drawing
            IF real_board[en_xpos[z]][en_ypos[z]] = TEMP_LINE THEN
                GOSUB Handle_Death
                BREAK
            ELSE
                DECR en_xpos[z], xoff[z]
                xoff[z] = -xoff[z]
            END IF
        END IF

        INCR en_ypos[z], yoff[z]
        IF real_board[en_xpos[z]][en_ypos[z]] <> 0 THEN
            ' Detect collision with the actual drawing
            IF real_board[en_xpos[z]][en_ypos[z]] = TEMP_LINE THEN
                GOSUB Handle_Death
                BREAK
            ELSE
                DECR en_ypos[z], yoff[z]
                yoff[z] = -yoff[z]
            END IF
        END IF

        enemy_board[en_xpos[z]][en_ypos[z]] = ENEMY_COL
        enemy_board[en_xpos[z]-1][en_ypos[z]] = ENEMY_COL
        enemy_board[en_xpos[z]+1][en_ypos[z]] = ENEMY_COL
        enemy_board[en_xpos[z]][en_ypos[z]-1] = ENEMY_COL
        enemy_board[en_xpos[z]][en_ypos[z]+1] = ENEMY_COL
    NEXT

    EXIT SUB

    ' A local GOSUB here, because we have to check x and y directions separately,
    '  but the handling of death is the same
    LABEL Handle_Death
        OUT("<i><b>You died!</b></i>", "#882222", "#FFFFFF", Xsize/2-15, 50)
        DECR Game_Lifes
        IF Game_Lifes >= 0 THEN
            Game_State = GAME_STP
            OUT("Press [r] to retry...", "#000000", "#FFFFFF", Xsize/2-35, 75)
        ELSE
            Game_State = GAME_END
            OUT("<big>GAME OVER!</big>", "#FF0000", "#FFFFFF", Xsize/2-35, 75)
            IF Game_Score > VAL(High_Score$) THEN
                OUT("Congrats, new high score!", "#000000", "#FFFFFF", Xsize/2-50, 100)
                OPEN GETENVIRON$("HOME") & "/.qix.txt" FOR WRITING AS hs
                    WRITELN STR$(Game_Score) TO hs
                CLOSE FILE hs
                High_Score$ = STR$(Game_Score)
                TEXT(mark5, High_Score$)
            END IF
            OUT("Start over again (y/n)?", "#000000", "#FFFFFF", Xsize/2-50, 125)
        END IF
        RETURN

END SUB

'-----------------------------------------------------------------------------------

FUNCTION Check_Event

    LOCAL former

    former = real_board[xpos][ypos]

    SELECT KEY()
        CASE 65362
            IF real_board[xpos][ypos-1] < 2 THEN DECR ypos
        CASE 65363
            IF real_board[xpos+1][ypos] < 2 THEN INCR xpos
        CASE 65364
            IF real_board[xpos][ypos+1] < 2 THEN INCR ypos
        CASE 65361
            IF real_board[xpos-1][ypos] < 2 THEN DECR xpos
        CASE 114
            IF Game_State = GAME_STP THEN
                Initialize_Game()
                TEXT(mark2, STR$(Game_Lifes))
                TEXT(mark4, STR$(Game_Level+1))
                TEXT(mark3, STR$(Game_Score))
            END IF
        CASE 121
            IF Game_State = GAME_END THEN
                Game_Lifes = 2
                Game_Score = 0
                Game_Level = 0
                Initialize_Game()
                TEXT(mark2, STR$(Game_Lifes))
                TEXT(mark4, STR$(Game_Level+1))
                TEXT(mark3, STR$(Game_Score))
            END IF
        CASE 110
            IF Game_State = GAME_END THEN
                OUT("<b>Thanks for playing!</b>", "#228822", "#FFFFFF", Xsize/2-40, 200)
            END IF
        CASE 65307
            QUIT
    END SELECT

    ' Check my position
    IF ypos < 2 THEN ypos = 2
    IF xpos > Xsize-4 THEN xpos = Xsize-4
    IF ypos > Ysize-4 THEN ypos = Ysize-4
    IF xpos < 2 THEN xpos = 2

    IF Game_State = GAME_RUN THEN
        IF former = TEMP_LINE THEN
            IF real_board[xpos][ypos] = 1 OR real_board[xpos][ypos] = TEMP_LINE THEN Start_Fill()
        END IF

        IF real_board[xpos][ypos] = 0 THEN real_board[xpos][ypos] = TEMP_LINE

        Draw_Me()
        Draw_Enemy()
        Draw_Board()
    END IF

    ' Check winning situation
    IF VAL(GRAB$(mark1)) >= THRESHOLD AND Game_State = GAME_RUN THEN
        OUT("<i><big>LEVEL COMPLETED!</big></i>", "#208020", "#FFFFFF", Xsize/2-60, 50)
        OUT("Press [r] for next level...", "#008000", "#FFFFFF", Xsize/2-55, 75)
        Game_State = GAME_STP
        INCR Game_Level
        INCR Game_Lifes
        INCR Game_Score, VAL(GRAB$(mark1))
        IF Game_Level = 10 THEN
            Game_State = GAME_END
            OUT("<big>GAME END</big>", "#22FF22", "#FFFFFF", Xsize/2-35, 75)
            IF Game_Score > VAL(High_Score$) THEN
                OUT("Congrats, new high score!", "#000000", "#FFFFFF", Xsize/2-50, 100)
                OPEN GETENVIRON$("HOME") & "/.qix.txt" FOR WRITING AS hs
                    WRITELN STR$(Game_Score) TO hs
                CLOSE FILE hs
                High_Score$ = STR$(Game_Score)
                TEXT(mark5, High_Score$)
            END IF
            OUT("Start over again (y/n)?", "#000000", "#FFFFFF", Xsize/2-50, 125)
        END IF
    END IF

    RETURN TRUE

END FUNCTION

'-----------------------------------------------------------------------------------

SUB Show_Help

    SHOW(Help_Dialog)

END SUB

'-----------------------------------------------------------------------------------

SUB Close_Help

    HIDE(Help_Dialog)

END SUB

'-----------------------------------------------------------------------------------

INIT

HUGOPTIONS("NOSCALING")

win = WINDOW("Qix", Xsize+120, Ysize+20)
PROPERTY(win, "icon-name", "gtk-select-color")

frame = FRAME(Xsize+10, Ysize+10)
ATTACH(win, frame, 5, 5)

canvas = CANVAS(Xsize, Ysize)
ATTACH(win, canvas, 10, 10)

' Score
frame1 = FRAME(95, 40)
TEXT(frame1, " Score ")
ATTACH(win, frame1, Xsize+20, 5)

mark1 = MARK("0%", 85, 30)
ATTACH(win, mark1, Xsize+25, 15)

' Total score
frame3 = FRAME(95, 40)
TEXT(frame3, " Total ")
ATTACH(win, frame3, Xsize+20, 55)

mark3 = MARK("0", 85, 30)
ATTACH(win, mark3, Xsize+25, 65)

' High score
frame5 = FRAME(95, 40)
TEXT(frame5, " High ")
ATTACH(win, frame5, Xsize+20, 105)

mark5 = MARK("0", 85, 30)
ATTACH(win, mark5, Xsize+25, 115)

IF FILEEXISTS(GETENVIRON$("HOME") & "/.qix.txt") THEN
    OPEN GETENVIRON$("HOME") & "/.qix.txt" FOR READING AS hs
    READLN High_Score$ FROM hs
    CLOSE FILE hs
    TEXT(mark5, High_Score$)
END IF

' Lifes
frame2 = FRAME(95, 40)
TEXT(frame2, " Lifes ")
ATTACH(win, frame2, Xsize+20, 155)

mark2 = MARK("2", 85, 30)
ATTACH(win, mark2, Xsize+25, 165)

' Level
frame4 = FRAME(95, 40)
TEXT(frame4, " Level ")
ATTACH(win, frame4, Xsize+20, 205)

mark4 = MARK("1", 85, 30)
ATTACH(win, mark4, Xsize+25, 215)

' Help button
help_but = STOCK("gtk-help", 95, 40)
ATTACH(win, help_but, Xsize+20, Ysize-25)
CALLBACK(help_but, Show_Help)

' Create help dialog
txt$ = "This is a remake of the classic 'Qix' game." & NL$ & NL$ \
& "Goal is to capture at least 75%% of the area." & NL$ & NL$ \
& " Steer your turtle by using the cursor keys, " & NL$ \
& "    and use the left CTRL to hold still." & NL$ & NL$ \
& "      Can you survive all 10 levels??"
Help_Dialog = MSGDIALOG(txt$, 400, 250, 0, 2)
CALLBACK(Help_Dialog, Close_Help)

' Initialize
Game_Lifes = 2
Initialize_Game()

TIMEOUT(GAME_SPEED, Check_Event)

DISPLAY