'-------------------------------------------------------------------------------------------------------------- ' ' This is a reworked version of my HUG CLines game for the canvas context. ' ' The intention of this program is: ' - test mouse interaction with the canvas ' - experiment with Immediate mode GUI approach ' ' The implementation follows the Immediate-Mode GUI approach, instead of the traditional event-driven approach. ' As a result, your system needs sufficient CPU power to render the GUI. ' ' August 2019, Peter van Eerten - MIT License. ' '--------------------------------------------------------------------------------------------------------------- INCLUDE canvas OPTION BASE 1 CONST PATH_VAL = 100 CONST EMPTY = 0 CONST HSFILE$ = GETENVIRON$("HOME") & "/.clines.txt" DECLARE field[10][10] DECLARE Score, Hiscore '--------------------------------------------------------------------------------------------------------- SUB Clear_Field LOCAL x, y FOR y = 1 TO 10 FOR x = 1 TO 10 field[x][y] = EMPTY NEXT NEXT END SUB '--------------------------------------------------------------------------------------------------------- SUB Print_Field LOCAL x, y INK(0, 0, 100, 255) GRID(1, 1, WIDTH-1, HEIGHT-65, 10, 10) FOR y = 1 TO 10 FOR x = 1 TO 10 IF field[x][y] > 15 THEN INK(200, 200, 0, 255) PAINT((x-1)*40+11, (y-1)*40+11) ENDIF SELECT (field[x][y] & 15) CASE 1 INK(0, 0, 255, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, TRUE) INK(0, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, FALSE) CASE 2 INK(0, 255, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, TRUE) INK(0, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, FALSE) CASE 3 INK(255, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, TRUE) INK(0, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, FALSE) CASE 4 INK(255, 0, 255, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, TRUE) INK(0, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, FALSE) CASE 5 INK(255, 255, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, TRUE) INK(0, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, FALSE) CASE 6 INK(128, 128, 128, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, TRUE) INK(0, 0, 0, 255) CIRCLE((x-1)*40+21, (y-1)*40+21, 17, 16, FALSE) END SELECT NEXT NEXT END SUB '--------------------------------------------------------------------------------------------------------- FUNCTION Find_Connection(NUMBER x, NUMBER y, NUMBER p, NUMBER q) IF x = p AND y = q THEN RETURN TRUE IF x < 1 OR x > 10 OR y < 1 OR y > 10 OR field[x][y] = 2 THEN RETURN FALSE IF field[x][y] <> EMPTY THEN RETURN FALSE field[x][y] = PATH_VAL IF Find_Connection(x+1, y, p, q) THEN RETURN TRUE IF Find_Connection(x-1, y, p, q) THEN RETURN TRUE IF Find_Connection(x, y+1, p, q) THEN RETURN TRUE IF Find_Connection(x, y-1, p, q) THEN RETURN TRUE RETURN FALSE END FUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION Find_Path(NUMBER x, NUMBER y, NUMBER p, NUMBER q) LOCAL result IF Find_Connection(x+1, y, p, q) THEN result = TRUE IF Find_Connection(x-1, y, p, q) THEN result = TRUE IF Find_Connection(x, y+1, p, q) THEN result = TRUE IF Find_Connection(x, y-1, p, q) THEN result = TRUE FOR y = 1 TO 10 FOR x = 1 TO 10 IF field[x][y] = PATH_VAL THEN field[x][y] = EMPTY NEXT NEXT RETURN result END FUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION Check_Five LOCAL x, y, val, org, result, count result = FALSE ' Horizontal 5 or more FOR y = 1 TO 10 org = 0 count = 0 FOR x = 1 TO 10 val = field[x][y] IF val = org THEN INCR count ELSE org = val count = 0 END IF IF count > 3 AND org > 0 THEN FOR z = x TO x-count STEP -1 field[z][y] = EMPTY NEXT INCR Score, (count+1)*10 result = TRUE END IF NEXT NEXT ' Vertical 5 or more FOR x = 1 TO 10 org = 0 count = 0 FOR y = 1 TO 10 val = field[x][y] IF val = org THEN INCR count ELSE org = val count = 0 END IF IF count > 3 AND org > 0 THEN FOR z = y TO y-count STEP -1 field[x][z] = EMPTY NEXT INCR Score, (count+1)*10 result = TRUE END IF NEXT NEXT RETURN result END FUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION Check_Space(NUMBER spaces) LOCAL x, y, total, result ' Check on empty space FOR x = 1 TO 10 FOR y = 1 TO 10 IF field[x][y] = EMPTY THEN INCR total NEXT NEXT ' No space left, end game IF total < spaces THEN result = 3 IF Hiscore < Score THEN Hiscore = Score SAVE STR$(Score) TO GETENVIRON$("HOME") & "/.clines.txt" result = 4 END IF END IF RETURN result ENDFUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION New_Balls LOCAL xpos, ypos, x, result ' Check space for 3 new balls result = Check_Space(3) IF result = 0 THEN FOR x = 1 TO 3 REPEAT xpos = RANDOM(10)+1 ypos = RANDOM(10)+1 UNTIL field[xpos][ypos] = 0 field[xpos][ypos] = RANDOM(6)+1 NEXT Check_Five() Print_Field() END IF ' Check space for player IF result = 0 THEN result = Check_Space(1) RETURN result ENDFUNCTION '--------------------------------------------------------------------------------------------------------- FUNCTION User_Click LOCAL length, xpos, ypos, button, state, result LOCAL org_x = 0, org_y = 0, clicked = 0 TYPE static int xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) ' Mouse click event handling IF xpos BETWEEN 1 AND WIDTH-1 AND ypos BETWEEN 1 AND HEIGHT-65 THEN IF state = 1 AND button = 1 THEN clicked = TRUE ELIF clicked = TRUE THEN xpos = xpos / 40 + 1 ypos = ypos / 40 + 1 IF org_x = 0 THEN org_x = xpos IF org_y = 0 THEN org_y = ypos IF field[xpos][ypos] > 0 THEN field[org_x][org_y] = (field[org_x][org_y] & 15) org_x = xpos org_y = ypos INCR field[org_x][org_y], 16 ELSE IF field[org_x][org_y] > 16 THEN IF Find_Path(org_x, org_y, xpos, ypos) THEN field[xpos][ypos] = (field[org_x][org_y] & 15) field[org_x][org_y] = EMPTY IF NOT(Check_Five()) THEN result = New_Balls() org_x = 0 org_y = 0 ELSE result = 2 DECR field[org_x][org_y], 16 org_x = 0 org_y = 0 END IF END IF END IF clicked = FALSE ENDIF ENDIF RETURN result ENDFUNCTION '--------------------------------------------------------------------------------------------------------- SUB Init_Game LOCAL x, xpos, ypos Clear_Field() FOR x = 1 TO 3 REPEAT xpos = RANDOM(10)+1 ypos = RANDOM(10)+1 UNTIL field[xpos][ypos] = 0 field[xpos][ypos] = RANDOM(5)+1 NEXT Score = 0 ENDSUB '--------------------------------------------------------------------------------------------------------- FUNCTION Draw_Dialog(msg$) LOCAL length, xpos, ypos, button, state, pressed, pos LOCAL clicked = 0 TYPE static int LOCAL x, y, rw, rh LOCAL line$ xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) x = WIDTH/2 y = HEIGHT/2-32+160 rw = 60 rh = 20 ' Mouse click event handling IF xpos BETWEEN x-rw AND x+rw AND ypos BETWEEN y-rh AND y+rh THEN IF state = 1 THEN IF button = 1 THEN INCR x, 2 INCR y, 2 clicked = TRUE ENDIF ELSE IF clicked = TRUE THEN pressed = TRUE clicked = FALSE ENDIF ENDIF ENDIF ' Draw the dialog INK(255, 255, 255, 255) SQUARE(WIDTH/2, HEIGHT/2-32, WIDTH/2-24, 180, TRUE) INK(0, 0, 100, 255) SQUARE(WIDTH/2, HEIGHT/2-32, WIDTH/2-24, 180, FALSE) SCALE(0.7) FOR line$ IN msg$ STEP NL$ TEXT(line$, WIDTH/2-TEXTLEN(line$)/2, HEIGHT/2-36-AMOUNT(msg$, NL$)*12+pos*24) INCR pos NEXT ' Draw the button SCALE(0.8) SQUARE(x, y, rw, rh, 0) TEXT("Close", x-TEXTLEN("Close")/2, y) SCALE(1.0) RETURN pressed ENDFUNCTION '--------------------------------------------------------------------------------------------------------- SUB Draw_Label(caption$, x, y) SCALE(0.8) INK(0,0,100,255) TEXT(caption$, x, y) SCALE(1.0) ENDSUB '--------------------------------------------------------------------------------------------------------- SUB Draw_Frame(caption$, x, y, rw, rh) LOCAL length INK(0,0,100,255) LINE(x-rw, y-rh, x-rw, y+rh) LINE(x-rw, y+rh, x+rw, y+rh) LINE(x+rw, y+rh, x+rw, y-rh) IF LEN(caption$) THEN LINE(x-rw, y-rh, x-rw+10, y-rh) SCALE(0.75) length = TEXTLEN(caption$) TEXT(caption$, x-rw+15, y-rh) SCALE(1.0) LINE(x+rw, y-rh, x-rw+20+length, y-rh) ELSE LINE(x+rw, y-rh, x-rw, y-rh) ENDIF ENDSUB '--------------------------------------------------------------------------------------------------------- FUNCTION Draw_Button(id, caption$, x, y, rw, rh) LOCAL length, xpos, ypos, button, state, pressed LOCAL clicked[16] = { 0 } TYPE static int xpos = MOUSE(0):ypos = MOUSE(1):button = MOUSE(2):state = MOUSE(3) ' Mouse click event handling IF xpos BETWEEN x-rw AND x+rw AND ypos BETWEEN y-rh AND y+rh THEN IF state = 1 THEN IF button = 1 THEN INCR x, 2 INCR y, 2 clicked[id] = TRUE ENDIF ELSE IF clicked[id] = TRUE THEN pressed = TRUE clicked[id] = FALSE ENDIF ENDIF ENDIF ' Draw button here INK(0,0,100,255) SQUARE(x, y, rw, rh, 0) SCALE(0.8) length = TEXTLEN(caption$) TEXT(caption$, x-length/2, y) SCALE(1.0) RETURN pressed ENDFUNCTION '--------------------------------------------------------------------------------------------------------- SUB Main_Loop LOCAL Game_State = 0 TYPE static int INK(255, 255, 255, 255) CLS ' Frames Draw_Frame("Score", 50, HEIGHT-25, 49, 23) Draw_Label(STR$(Score), 10, HEIGHT-20) Draw_Frame("High", 160, HEIGHT-25, 49, 23) Draw_Label(STR$(Hiscore), 120, HEIGHT-20) ' Draw buttons IF Draw_Button(1, "Help", WIDTH-131, HEIGHT-25, 40, 23) THEN Game_State = 1 IF Draw_Button(2, "Quit", WIDTH-41, HEIGHT-25, 40, 23) THEN QUIT ' Process user input IF Game_State = 0 THEN Game_State = User_Click() ' Draw current situation Print_Field() ' Decide on dialog SELECT Game_State CASE 1 IF Draw_Dialog(ALIGN$(help$, 23, 2)) THEN Game_State = 0 CASE 2 IF Draw_Dialog(ALIGN$(err$, 18, 2)) THEN Game_State = 0 CASE 3 IF Draw_Dialog(ALIGN$(end$, 18, 2)) THEN Game_State = 0 Init_Game() ENDIF CASE 4 IF Draw_Dialog(ALIGN$(hi$, 18, 2)) THEN Game_State = 0 Init_Game() ENDIF ENDSELECT ENDSUB '--------------------------------------------------------------------------------------------------------- ' Define some dialog messages CONST help$ = "This is a remake of the classic 'Clines' game. Goal is to get 5 or more stones of the same color in a row. " \ "Move existing stones by clicking on it, and then by clicking on the new position. No barrier may be in between! What highscore can you reach?" CONST err$ = "You cannot move that stone because there is a barrier in between! Please make sure no obstacles are in your way." CONST end$ = "The game has finished!" CONST hi$ = "Congratulations! You have reached a new high score!" WINDOW("Canvas Clines", 400, 466) FONTALIGN(1) Init_Game() ' Get former hiscore IF FILEEXISTS(HSFILE$) THEN Hiscore = VAL(CHOP$(LOAD$(HSFILE$))) CALLBACK(50, Main_Loop) WAITKEY