' LOSE YOUR MARBLES ' ' AN ISOMETRIC 3D PLATFORM GAME ' ' WRITTEN ENTIRELY ON AN IPHONE! ' ' CODE & GRAPHICS COPYRIGHT (C) 2019 NAT PRYCE ' LICENSE: CC BY-NC-SA 4.0 ' TODO ' - DON'T DRAW OFFSCREEN ACTORS ' - WARP OUT ANIMATION ON LEVEL COMPLETION ' - SCROLLING ' - MORE LEVELS! ' - MORE ENEMIES & OBSTACLES ' -- CHASE THE PLAYER ' - CUSTOM FONT GLOBAL TRUE, FALSE TRUE = -1 FALSE = 0 GLOBAL NONE NONE = -1 GLOBAL _X, _Y, _Z _X = 0 _Y = 1 _Z = 2 GLOBAL GRAVITY GRAVITY = 0.0125 SUB FAIL(FNAME$, MSG$) BG 0 ATTR (,0,0,1) SCROLL 0, 0, 0 TEXT 0,1, "ERROR!" TEXT 1,2, FNAME$ TEXT 1,3, MSG$ END END SUB '--------------------------------------------' CENTERED TEXT DIM GLOBAL TITLE_LINE_OFFSETS(16) SUB TITLE_INIT SPRITE OFF CLS FOR I = 0 TO 16 TITLE_LINE_OFFSETS(I) = 0 NEXT I ON RASTER CALL TITLE_CENTER_LINES END SUB SUB TITLE_CENTER_LINES L = RASTER/8 SCROLL 0, TITLE_LINE_OFFSETS(L), 0 END SUB SUB TITLE_END ON RASTER OFF END SUB SUB CTEXT(Y, L$) BG 0 BG FILL 0, Y TO 19, Y CHAR 0 TEXT 0, Y, L$ TITLE_LINE_OFFSETS(Y) = -(20-LEN(L$))*4 Y = Y + 1 END SUB SUB BLANK(Y) Y = Y + 1 END SUB SUB CLT(Y) BG 0 BG FILL 0, Y TO 19, Y CHAR 0 TITLE_LINE_OFFSETS(Y) = 0 Y = Y + 1 END SUB '-------------------------------------------- ' ISOMETRIC 3D PROJECTION SUB ISO(P3(), SCR()) ' ISOMETRIC PROJECTION OF A POINT IN ' 3D WORLD COORDINATES TO A POINT IN ' 2D VIRTUAL SCREEN COORDINATES CALL ISOC(P3(_X), P3(_Y), P3(_Z), SCR()) END SUB SUB ISOE(A(), I, SCR()) ' ISOMETRIC PROJECTION OF A WORLD POINT ' THAT IS A SLICE IN A MULTIDIMENSIONAL ' ARRAY CALL ISOC(A(I,_X),A(I,_Y),A(I,_Z), SCR()) END SUB SUB ISOC(X,Y,Z, SCREEN()) ' ISOMETRIC PROJECTION WITH EACH WORLD ' COORDINATE PASSED INDIVIDUALLY SCREEN(_X) = X*8 SCREEN(_Y) = 4*Z - 8*Y END SUB '-------------------------------------------- ' DEFORMABLE 2D MAP STORED IN WORKING MEMORY TILE_MAX = 4 DIM GLOBAL TILE_FRICTION(TILE_MAX) DIM GLOBAL TILE_HARDNESS(TILE_MAX) DEFAULT_FRICTION = 0.025 DEFAULT_HARDNESS = 1 TILE_FRICTION(1) = DEFAULT_FRICTION TILE_HARDNESS(1) = DEFAULT_HARDNESS TILE_FRICTION(2) = DEFAULT_FRICTION TILE_HARDNESS(2) = DEFAULT_HARDNESS TILE_FRICTION(3) = DEFAULT_FRICTION TILE_HARDNESS(3) = DEFAULT_HARDNESS TILE_FRICTION(4) = DEFAULT_FRICTION*6 TILE_HARDNESS(4) = DEFAULT_HARDNESS/6 GLOBAL MAP_WIDTH GLOBAL MAP_DEPTH GLOBAL MAP_END_ADDR GLOBAL START_X, START_Z SUB MAP_INIT(W, D) MAP_WIDTH = W MAP_DEPTH = D FILL $A000, W*D, 0 MAP_END_ADDR = $A000 + W*D END SUB SUB MAP_ADDR(X, Z, AOUT) AOUT = $A000 + MAP_WIDTH*INT(Z) + INT(X) END SUB SUB MAP_GET(X, Z, VOUT) IF X<0 OR X>=MAP_WIDTH THEN VOUT = 0 ELSE IF Z<0 OR Z>=MAP_DEPTH THEN VOUT = 0 ELSE A = NONE CALL MAP_ADDR(X, Z, A) VOUT = PEEK(A) END IF END SUB SUB MAP_GET_PATCH(X, Z, PATCH()) FOR DZ = 0 TO 3 FOR DX = 0 TO 2 PX = X-1+DX PZ = Z-1+DZ CALL MAP_GET(PX, PZ, PATCH(DX,DZ)) NEXT DX NEXT DZ END SUB SUB MAP_SET(X, Z, V) A = -1 CALL MAP_ADDR(X, Z, A) POKE A, V END SUB SUB MAP_SETRECT(MINX, MINZ, MAXX, MAXZ, V) FOR X = MINX TO MAXX FOR Z = MINZ TO MAXZ CALL MAP_SET(X, Z, V) NEXT Z NEXT X END SUB '-------------------------------------------- ' DRAW THE MAP SUB MAP_DRAW 'DISPLAY (,0,0,,) CALL MAP_DRAW_OFFSCREEN BG 1 BG COPY 0,0, 20,16 TO 0,0 'DISPLAY (,1,1,,) END SUB SUB MAP_DRAW_OFFSCREEN BG SOURCE MAP_END_ADDR,MAP_WIDTH,MAP_DEPTH ATTR (,,,1,) FOR X = 0 TO MAP_WIDTH-1 FOR Z = 0 TO MAP_DEPTH-2 STEP 2 C = 0 A = 0 CALL MAP_GET_TILE(X, Z, C, A) ATTR (A) MCELL X, Z\2, C NEXT Z NEXT X END SUB SUB MAP_GET_TILE(X, Z, C, A) DIM P(2,3) CALL MAP_GET_PATCH(X, Z, P()) IF P(1,1)=0 AND P(1,2)=0 THEN A = 0 CALL MAP_TILE_SPACE(P(), C) ELSE IF P(1,1)=0 AND P(1,2)=1 THEN A = 0 CALL MAP_TILE_LOWER_HALF(P(), C) ELSE IF P(1,1)=1 AND P(1,2)=0 THEN A = 0 CALL MAP_TILE_UPPER_HALF(P(), C) ELSE IF P(1,1)=1 AND P(1,2)=1 THEN A = 0 CALL MAP_TILE_BOTH(P(), C) ELSE IF P(1,1)=2 OR P(1,2)=2 THEN A = 2 CALL MAP_TILE_FINISHLINE(P(), C) ELSE IF P(1,1)=3 OR P(1,2)=3 THEN A = 2 CALL MAP_TILE_SPIKES(P(), C) ELSE IF P(1,1)=4 OR P(1,2)=4 THEN A = 5 CALL MAP_TILE_SOFT(P(), C) END IF END SUB SUB MAP_TILE_SPACE(P(), C) IF P(1,0) <> 0 THEN IF P(0,0)<>0 AND P(2,0)<>0 THEN C=17 ELSE IF P(0,0)<>0 THEN C=21 ELSE IF P(2,0)<>0 THEN C=20 END IF ELSE C = 0 END IF END SUB SUB MAP_TILE_BOTH(P(), C) IF P(1,3) = 0 THEN IF P(0,2)=0 AND P(0,3)=0 THEN C = 4 ELSE IF P(2,2)=0 AND P(2,3)=0 THEN C = 5 ELSE C = 1 END IF ELSE IF P(1,0) = 0 THEN IF P(0,0)=0 AND P(0,1)=0 THEN C = 2 ELSE IF P(2,0)=0 AND P(2,1)=0 THEN C = 3 ELSE C = 1 END IF ELSE C = 1 END IF END SUB SUB MAP_TILE_LOWER_HALF(P(), C) IF P(0,1)=0 AND P(0,2)=0 THEN C = 9 ELSE IF P(2,1)=0 AND P(2,2)=0 THEN C = 11 ELSE C = 10 END IF END SUB SUB MAP_TILE_UPPER_HALF(P(), C) IF P(0,1)=0 AND P(0,2)=0 THEN C = 6 ELSE IF P(2,1)=0 AND P(2,2)=0 THEN C = 8 ELSE C = 7 END IF END SUB SUB MAP_TILE_FINISHLINE(P(), C) CALL MAP_TILE_SURFACE(P(), 32, C) END SUB SUB MAP_TILE_SPIKES(P(), C) CALL MAP_TILE_SURFACE(P(), 35, C) END SUB SUB MAP_TILE_SOFT(P(), C) CALL MAP_TILE_SURFACE(P(), 38, C) END SUB SUB MAP_TILE_SURFACE(P(), BASEC, C) IF P(1,1)=1 THEN C=BASEC+2 ELSE IF P(1,2)=1 THEN C=BASEC+1 ELSE C=BASEC END IF END SUB '-------------------------------------------- ' DYNAMICALLY ALLOCATED ACTORS ' ' 0 = THE PLAYER'S BALL ' 1..ENEMY_MAX = ENEMIES ' ENEMY_MAX+1..ACTOR_MAX = PARTICLES GLOBAL ACTOR_MAX ACTOR_MAX = 31 DIM GLOBAL ACTOR_TYPE(ACTOR_MAX) DIM GLOBAL ACTOR_NEXT(ACTOR_MAX) DIM GLOBAL ACTOR_PREV(ACTOR_MAX) DIM GLOBAL ACTOR_P(ACTOR_MAX, 2) DIM GLOBAL ACTOR_V(ACTOR_MAX, 2) DIM GLOBAL ACTOR_RADIUS(ACTOR_MAX) DIM GLOBAL ACTOR_FRAME(ACTOR_MAX) DIM GLOBAL ACTOR_COLOR(ACTOR_MAX) DIM GLOBAL ACTOR_SHADOW(ACTOR_MAX) GLOBAL ACTORS_LIVE GLOBAL ACTORS_FREE ' ACTOR TYPES GLOBAL T_BALL T_BALL = 1 GLOBAL T_ROBOT_NS T_ROBOT_NS = 2 GLOBAL T_ROBOT_EW T_ROBOT_EW = 3 ' PARTICLE AFFECTED BY GRAVITY GLOBAL T_GPARTICLE T_GPARTICLE = 4 SUB ACTOR_INIT SPRITE OFF ACTOR_PREV(0) = NONE FOR A = 1 TO ACTOR_MAX ACTOR_NEXT(A-1) = A ACTOR_PREV(A) = A-1 NEXT A ACTOR_NEXT(ACTOR_MAX) = NONE ACTORS_LIVE = NONE ACTORS_FREE = 0 END SUB SUB ACTOR_ADD(LIST, A) ACTOR_NEXT(A) = LIST IF LIST <> NONE THEN ACTOR_PREV(LIST) = A END IF LIST = A END SUB SUB ACTOR_UNLINK(LIST, A) IF LIST < 0 THEN CALL FAIL("ACTOR_UNLINK", "LIST EMPTY") END IF N = ACTOR_NEXT(A) P = ACTOR_PREV(A) IF N >= 0 THEN ACTOR_PREV(N) = P END IF IF P >= 0 THEN ACTOR_NEXT(P) = N ELSE LIST = N END IF ACTOR_PREV(A) = NONE ACTOR_NEXT(A) = NONE END SUB SUB ACTOR_ALLOC(TYPE, A) A = ACTORS_FREE CALL ACTOR_UNLINK(ACTORS_FREE, A) CALL ACTOR_ADD(ACTORS_LIVE, A) ACTOR_TYPE(A) = TYPE END SUB SUB ACTOR_RELEASE(A) CALL ACTOR_UNLINK(ACTORS_LIVE, A) CALL ACTOR_ADD(ACTORS_FREE, A) SPRITE OFF A END SUB SUB SORT(REFS(), N, ROWS(), SORTCOL) I = 1 WHILE I < N J = I DO IF J <= 0 THEN GOTO ENDINSERT END IF A = ROWS(REFS(J-1), SORTCOL) B = ROWS(REFS(J), SORTCOL) IF A >= B THEN GOTO ENDINSERT END IF SWAP REFS(J), REFS(J-1) J = J - 1 LOOP ENDINSERT: I = I + 1 WEND END SUB SUB ACTORS_TO_ARRAY(ARR(), COUNT) A = ACTORS_LIVE COUNT = 0 WHILE A <> NONE ARR(COUNT) = A A = ACTOR_NEXT(A) COUNT = COUNT + 1 WEND END SUB SUB DRAW_ACTORS DIM ORDER(ACTOR_MAX) COUNT=0 CALL ACTORS_TO_ARRAY(ORDER(), COUNT) CALL SORT(ORDER(), COUNT, ACTOR_P(), _Z) SPRITE OFF S = 0 DIM P2(1) FOR I = 0 TO COUNT-1 A = ORDER(I) AX = ACTOR_P(A,_X) AY = ACTOR_P(A,_Y) AZ = ACTOR_P(A,_Z) AC = ACTOR_COLOR(A) AF = ACTOR_FRAME(A) CALL ISOC(AX, AY, AZ, P2()) PRIORITY = 0 - (AY >= 0) SPRITE S, P2(_X)-4, P2(_Y)-4, AF SPRITE.A S, (AC,,,PRIORITY,) S = S + 1 IF ACTOR_SHADOW(A) AND AY >= 0 THEN IF TIMER MOD 2 = 0 THEN CALL ISOC(AX, 0, AZ, P2()) SPRITE S, P2(_X)-4, P2(_Y)-4, AF+16 SPRITE.A S, (6,,,1,) S = S + 1 END IF END IF NEXT I END SUB SUB ACTOR_ANIM(A, BASE, NFRAMES, FRAMET) F = (GTIMER/FRAMET) MOD NFRAMES ACTOR_FRAME(A) = BASE + F END SUB SUB ACTOR_SETPOS(A, X, Y, Z) ACTOR_P(A,_X) = X ACTOR_P(A,_Y) = Y ACTOR_P(A,_Z) = Z END SUB SUB ACTOR_SETVEL(A, X, Y, Z) ACTOR_V(A,_X) = X ACTOR_V(A,_Y) = Y ACTOR_V(A,_Z) = Z END SUB SUB ACTOR_MOVEV(A) FOR I = 0 TO 2 ACTOR_P(A,I) = ACTOR_P(A,I)+ACTOR_V(A,I) NEXT I END SUB SUB ACTOR_ACCEL(A, DX, DY, DZ) ACTOR_V(A,_X) = ACTOR_V(A,_X) + DX ACTOR_V(A,_Y) = ACTOR_V(A,_Y) + DY ACTOR_V(A,_Z) = ACTOR_V(A,_Z) + DZ END SUB SUB ACTOR_MOVE_ALL A = ACTORS_LIVE WHILE A <> NONE NEXTA = ACTOR_NEXT(A) ISDONE = FALSE CALL ACTOR_MOVE(A, ISDONE) IF ISDONE THEN CALL ACTOR_RELEASE(A) A = NEXTA WEND END SUB SUB ACTOR_MOVE(A, ISDONE) TYPE = ACTOR_TYPE(A) IF TYPE = T_BALL THEN CALL BALL_MOVE(A) ELSE IF TYPE = T_ROBOT_NS THEN CALL ROBOT_MOVE(A) ELSE IF TYPE = T_ROBOT_EW THEN CALL ROBOT_MOVE(A) ELSE IF TYPE = T_GPARTICLE THEN CALL GPARTICLE_MOVE(A, ISDONE) ELSE BG 1 ATTR (0,0,0,1) TEXT 0,0, "UNKNOWN ACTOR TYPE" + STR$(A) END END IF END SUB SUB ACTOR_TRACE(A) BG 0 ATTR (7) X = ACTOR_P(A,_X) Y = ACTOR_P(A,_Y) Z = ACTOR_P(A,_Z) C = NONE CALL MAP_GET(X, Z, C) BG FILL 0,0 TO 15,3 CHAR 0 TEXT 0,0,"X:"+STR$(X) TEXT 0,1,"Y:"+STR$(Y) TEXT 0,2,"Z:"+STR$(Z) ADDR = NONE CALL MAP_ADDR(X, Z, ADDR) TEXT 0,3,"A:"+HEX$(ADDR) END SUB '-------------------------------------------- ' ENEMIES SUB ROBOT_NEW(TYPE, X, Z) A = NONE CALL ACTOR_ALLOC(TYPE, A) ACTOR_RADIUS(A) = 0.5 CALL ACTOR_SETPOS(A, X, ACTOR_RADIUS(A), Z) ACTOR_FRAME(A) = 65 ACTOR_COLOR(A) = 3 ACTOR_SHADOW(A) = TRUE SPEED = 1/16 IF TYPE = T_ROBOT_NS THEN CALL ACTOR_SETVEL(A, 0, 0, SPEED) ELSE IF TYPE = T_ROBOT_EW THEN CALL ACTOR_SETVEL(A, SPEED, 0, 0) ELSE CALL FAIL("ROBOT_NEW", STR$(TYPE)) END IF END SUB SUB ROBOT_MOVE(A) TYPE = ACTOR_TYPE(A) X = ACTOR_P(A,_X) Z = ACTOR_P(A,_Z) VX = ACTOR_V(A,_X) VZ = ACTOR_V(A,_Z) ' REVERSE AT EDGE OF PLATFORM MX = X + SGN(VX)/2 MZ = Z + SGN(VZ)/2 C = NONE CALL MAP_GET(MX, MZ, C) IF C = 0 THEN ACTOR_V(A,_X) = VX * -1 ACTOR_V(A,_Z) = VZ * -1 END IF CALL ACTOR_MOVEV(A) CALL ACTOR_ANIM(A, 65, 3, 10) END SUB '-------------------------------------------- ' PARTICLES SUB GPARTICLE_NEW(A) CALL ACTOR_ALLOC(T_GPARTICLE, A) ACTOR_RADIUS(A) = 0 ACTOR_FRAME(A) = 96 ACTOR_SHADOW(A) = TRUE END SUB SUB GPARTICLE_MOVE(A, ISDONE) CALL ACTOR_MOVEV(A) IF ACTOR_P(A,_Y) <= 0 THEN ISDONE = TRUE EXIT SUB END IF IF RND < 0.05 THEN ACTOR_FRAME(A) = ACTOR_FRAME(A)+1 IF ACTOR_FRAME(A) > 98 THEN ISDONE = TRUE EXIT SUB END IF END IF CALL ACTOR_ACCEL(A, 0, -GRAVITY/4, 0) END SUB '-------------------------------------------- ' BALL GLOBAL BALL GLOBAL BALL_STARTED GLOBAL BALL_FALLEN SUB BALL_NEW(A) CALL ACTOR_ALLOC(T_BALL, A) ACTOR_RADIUS(A) = 0.5 ACTOR_COLOR(A) = 1 ACTOR_FRAME(A) = 64 ACTOR_SHADOW(A) = TRUE BALL_STARTED = 0 END SUB SUB BALL_LAUNCH(A) CALL ACTOR_SETPOS(A, START_X, 8, START_Z) ACTOR_V(A,_X) = 0 ACTOR_V(A,_Y) = 0 ACTOR_V(A,_Z) = 0 BALL_FALLEN = FALSE END SUB SUB BALL_MOVE(A) CALL ACTOR_MOVEV(A) PX = ACTOR_P(A,_X) PY = ACTOR_P(A,_Y) PZ = ACTOR_P(A,_Z) ' BOTTOM OF THE BALL BY = PY - ACTOR_RADIUS(A) VY = ACTOR_V(A,_Y) C = -1 CALL MAP_GET(INT(PX), INT(PZ), C) TF = TILE_FRICTION(C) TH = TILE_HARDNESS(C) ROLLING=BY<0.05 AND ABS(VY)<0.05 AND C<>0 IF ROLLING THEN IF C = 3 THEN CALL EXPLODE(A) CALL BALL_LAUNCH(A) ELSE ACTOR_P(A,_Y) = ACTOR_RADIUS(A) ACTOR_V(A,_Y) = 0 ' APPLY SURFACE FRICTION CALL APPLY_FRICTION(A, TF) CALL CONTROL_BALL(A, TH) END IF ELSE IF BY < 0 THEN IF BALL_FALLEN THEN IF BY <= -12 THEN CALL BALL_LAUNCH(A) END IF ELSE ' NOT BALL_FALLEN IF C = 3 THEN CALL EXPLODE(A) CALL BALL_LAUNCH(A) ELSE IF C <> 0 THEN ' BOUNCE ACTOR_P(A,_Y) = PY - 2*BY ACTOR_V(A,_Y) = VY * -0.6 * TH ' APPLY SURFACE & ELASTIC FRICTION CALL APPLY_FRICTION(A, TF*5) CALL CONTROL_BALL(A, TH) ELSE BALL_FALLEN = TRUE END IF END IF END IF CALL ACTOR_ACCEL(A, 0, -GRAVITY, 0) END IF END SUB SUB APPLY_FRICTION(A, COEFF) VX = ACTOR_V(A,_X) VZ = ACTOR_V(A,_Z) V = SQR(VX^2 + VZ^2) IF V = 0 THEN EXIT SUB F = COEFF * V ' EMERGENCY BRAKE IF BUTTON(0,1) THEN F = F * 5 END IF ' UNIT VECTOR UX = VX / V UZ = VZ / V CALL ACTOR_ACCEL(A, -UX*F, 0, -UZ*F) END SUB SUB CONTROL_BALL(A, TILE_HARDNESS) DX = LEFT(0) - RIGHT(0) DZ = UP(0) - DOWN(0) THRUST = 0.005 CALL ACTOR_ACCEL(A, DX*THRUST, 0, DZ*THRUST) ' JUMP IF BUTTON(0,0) THEN ACTOR_V(A,_Y) = 0.25 * TILE_HARDNESS END IF IF BALL_STARTED = 0 THEN IF DX OR DZ OR BUTTON(0) THEN BALL_STARTED = GTIMER CALL CLEAR_TIP END IF END IF END SUB SUB BALL_AT_END(A, RESULT) X = ACTOR_P(A,_X) Y = ACTOR_P(A,_Y) - ACTOR_RADIUS(A) Z = ACTOR_P(A,_Z) VY = ACTOR_V(A,_Y) ROLLING = Y=0 AND VY=0 C = NONE CALL MAP_GET(X, Z, C) RESULT = (C = 2 AND ROLLING) END SUB '-------------------------------------------- ' LEVELS GLOBAL LEVEL_TIP_MAX LEVEL_TIP_MAX = 3 DIM GLOBAL LEVEL_TIP$(LEVEL_TIP_MAX) SUB FLOOR(MINX, MINY, MAXX, MAXY) CALL MAP_SETRECT(MINX, MINY, MAXX, MAXY, 1) END SUB SUB FINISH(MINX, MINY, MAXX, MAXY) CALL MAP_SETRECT(MINX, MINY, MAXX, MAXY, 2) END SUB SUB SPIKES(MINX, MINY, MAXX, MAXY) CALL MAP_SETRECT(MINX, MINY, MAXX, MAXY, 3) END SUB SUB SOFT(MINX, MINY, MAXX, MAXY) CALL MAP_SETRECT(MINX, MINY, MAXX, MAXY, 4) END SUB SUB TIP(I, S$) LEVEL_TIP$(I) = S$ END SUB SUB START(X, Z) START_X = X START_Z = Z END SUB SUB LEVEL00 CALL MAP_INIT(20, 32) CALL TIP(0, "USE THE GAMEPAD TO") CALL TIP(1, "MOVE THE BALL ONTO") CALL TIP(2, "THE CHECKERED AREA") CALL FLOOR(2, 4, 7, 13) CALL FLOOR(8, 7, 12, 10) CALL FLOOR(13, 4, 18, 13) CALL FINISH(17, 4, 17, 13) CALL START(3, 5) END SUB SUB LEVEL01 CALL MAP_INIT(20, 32) CALL TIP(0, "USE BUTTON A") CALL TIP(1, "TO JUMP GAPS") CALL FLOOR(2, 4, 7, 13) CALL FLOOR(8, 7, 9, 10) CALL FLOOR(11, 7, 12, 10) CALL FLOOR(13, 4, 18, 13) CALL FINISH(17, 4, 17, 13) CALL START(3, 5) END SUB SUB LEVEL02 CALL MAP_INIT(20, 32) CALL TIP(0, "USE BUTTON B") CALL TIP(1, "TO BRAKE") CALL FLOOR(5, 4, 7, 11) CALL FLOOR(2, 18, 4, 25) CALL FLOOR(8, 7, 16, 8) CALL FLOOR(15, 9, 16, 22) CALL FLOOR(5, 21, 16, 22) CALL START(6, 5) CALL FINISH(3, 18, 3, 25) END SUB SUB LEVEL03 CALL MAP_INIT(20, 32) CALL TIP(0, "NOT ALL SURFACES") CALL TIP(1, "ARE SAFE") CALL FLOOR(2, 4, 7, 13) CALL FLOOR(8, 6, 12, 11) CALL SPIKES(9, 6, 11, 11) CALL FLOOR(13, 4, 18, 13) CALL FINISH(17, 4, 17, 13) CALL START(3, 5) END SUB SUB LEVEL04 CALL MAP_INIT(20, 32) CALL TIP(0, "AVOID ENEMIES OR") CALL TIP(1, "JUMP OVER THEM") CALL FLOOR(2, 7, 8, 10) CALL FLOOR(9, 4, 18, 13) CALL FLOOR(13, 14, 15, 22) CALL FINISH(13, 21, 15, 21) CALL ROBOT_NEW(T_ROBOT_NS, 9.5, 4.5) CALL ROBOT_NEW(T_ROBOT_EW, 9.5, 13.5) CALL START(3, 9) END SUB SUB LEVEL05 CALL MAP_INIT(20, 32) CALL TIP(0, "SOFT GROUND SLOWS") CALL TIP(1, "YOU DOWN AND STOPS") CALL TIP(2, "YOU BOUNCING") CALL FLOOR(1, 3, 6, 15) CALL FLOOR(7, 5, 7, 7) CALL FLOOR(7, 11, 7, 13) CALL FLOOR(13, 2, 17, 17) CALL SOFT(13, 10, 15, 15) CALL FINISH(16, 2, 16, 17) CALL START(2, 6.5) END SUB SUB LEVEL_INIT(N) FOR I = 0 TO LEVEL_TIP_MAX LEVEL_TIP$(I) = "" NEXT I IF N = 0 THEN CALL LEVEL00 ELSE IF N = 1 THEN CALL LEVEL01 ELSE IF N = 2 THEN CALL LEVEL02 ELSE IF N = 3 THEN CALL LEVEL03 ELSE IF N = 4 THEN CALL LEVEL04 ELSE IF N = 5 THEN CALL LEVEL05 ELSE CALL FAIL("LEVEL", "INVALID: "+STR$(N)) END IF END SUB GLOBAL LEVEL_MAX LEVEL_MAX = 5 DIM GLOBAL LEVEL_BEST(LEVEL_MAX) DIM GLOBAL LEVEL_LAST(LEVEL_MAX) '-------------------------------------------- ' MAIN SUB SHOW_TIP BG 0 ATTR (4) FOR I = 0 TO LEVEL_TIP_MAX CALL CTEXT(13+I, LEVEL_TIP$(I)) NEXT I END SUB SUB CLEAR_TIP FOR I = 0 TO 2 CALL CLT(13+I) NEXT I END SUB SUB FORMAT_TIME(T, T$) T$ = STR$((T\6)/10) IF LEFT$(RIGHT$(T$,2),1)<>"." THEN T$ = T$ + ".0" END IF T$ = T$ + " S" END SUB SUB TITLE_SCREEN(STARTL) CALL TITLE_INIT BG 0 ATTR (1) CALL CTEXT(1, "LOSE YOUR MARBLES!") ATTR (4) CALL CTEXT(4, "STARTING LEVEL") ATTR(4) CALL CTEXT(13, "PRESS ANY BUTTON") CALL CTEXT(14, "TO PLAY") REPEAT ATTR(4) CALL CTEXT(6, "< "+STR$(STARTL+1)+" >") ATTR (0) BEST = LEVEL_BEST(STARTL) IF BEST > 0 THEN BEST$ = "" CALL FORMAT_TIME(BEST, BEST$) CALL CTEXT(9, "BEST: "+BEST$) ELSE CALL CTEXT(9, "NO BEST TIME YET") END IF LAST = LEVEL_LAST(STARTL) IF LAST > 0 THEN LAST$ = "" CALL FORMAT_TIME(LAST, LAST$) CALL CTEXT(10, "LAST: "+LAST$) ELSE CALL CLT(10) END IF WAIT VBL DL = LEFT TAP(0) - RIGHT TAP(0) NEXTL = STARTL + LEVEL_MAX + 1 + DL STARTL = NEXTL MOD (LEVEL_MAX + 1) UNTIL BUTTON TAP(0) CALL TITLE_END CLS END SUB SUB MENUITEM(Y, SELECTED, T$) IF SELECTED THEN ATTR (4) ELSE ATTR (0) END IF CALL CTEXT(Y, T$) END SUB SUB PAUSE_MENU(OPT) SPRITE OFF CLS ATTR(1) CALL CTEXT(2, "PAUSED") OPT = 0 REPEAT CALL MENUITEM(6, OPT=0, "RESUME GAME") CALL MENUITEM(8, OPT=1, "RESTART LEVEL") CALL MENUITEM(10, OPT=2, "RETURN TO TITLE") OPT = (OPT+UP TAP(0)-DOWN TAP(0)) MOD 3 WAIT VBL UNTIL BUTTON TAP(0) CLS END SUB GLOBAL GTIMER SUB PLAY_LEVEL(N, OPT, TIME) CALL TITLE_INIT CALL ACTOR_INIT BALL = NONE CALL BALL_NEW(BALL) CALL LEVEL_INIT(N) CALL MAP_DRAW CALL SHOW_TIP MAX_ENEMY = ACTORS_FREE - 1 LEVEL_COMPLETE = FALSE CALL BALL_LAUNCH(BALL) REPEAT IF PAUSE THEN CALL PAUSE_MENU(OPT) IF OPT = 0 THEN CALL MAP_DRAW IF NOT BALL_STARTED THEN CALL SHOW_TIP END IF ELSE EXIT SUB END IF END IF IF BALL_STARTED THEN TIME = GTIMER - BALL_STARTED TIME$ = "" CALL FORMAT_TIME(TIME, TIME$) ATTR (4,,,1,) CALL CTEXT(15, TIME$+" ") END IF ' ENSURE RANDOM ENEMY BEHAVIOUR IS ' ACTUALLY PREDICTABLE RANDOMIZE GTIMER CALL ACTOR_MOVE_ALL CALL CHECK_COLLISIONS(BALL, MAX_ENEMY) CALL DRAW_ACTORS CALL BALL_AT_END(BALL, LEVEL_COMPLETE) WAIT VBL GTIMER = GTIMER + 1 UNTIL LEVEL_COMPLETE END SUB SUB CHECK_COLLISIONS(BALL, MAX_ENEMY) FOR E = BALL+1 TO MAX_ENEMY COLLISION = FALSE CALL CHECK_COLLISION(BALL, E, COLLISION) IF COLLISION THEN CALL EXPLODE(BALL) CALL BALL_LAUNCH(BALL) EXIT SUB END IF NEXT E END SUB SUB EXPLODE(A) X = ACTOR_P(A,_X) Y = ACTOR_P(A,_Y) Z = ACTOR_P(A,_Z) SPEED = 1/8 WHILE ACTORS_FREE <> NONE P = NONE CALL GPARTICLE_NEW(P) ACTOR_COLOR(P) = ACTOR_COLOR(A) CALL ACTOR_SETPOS(P, X, Y, Z) ' NOT UNIFORMLY DISTRIBUTED BUT ' GOOD ENOUGH DX = (RND-0.5)*2*SPEED DY = (RND-0.5)*2*SPEED DZ = (RND-0.5)*2*SPEED CALL ACTOR_SETVEL(P, DX, DY, DZ) WEND END SUB SUB CHECK_COLLISION(A, B, RESULT) DX = ACTOR_P(A,_X) - ACTOR_P(B,_X) DY = ACTOR_P(A,_Y) - ACTOR_P(B,_Y) DZ = ACTOR_P(A,_Z) - ACTOR_P(B,_Z) D = SQR(DX^2 + DY^2 + DZ^2) RA = ACTOR_RADIUS(A) RB = ACTOR_RADIUS(B) RESULT = D <= (RA+RB) END SUB SUB WAIT_TAP REPEAT WAIT VBL UNTIL BUTTON TAP(0) END SUB SUB PLAY_GAME(START_LEVEL) FOR L = START_LEVEL TO LEVEL_MAX LEVEL_TIME = 0 BG FILL 0,0 TO 0, 20 CHAR 0 REPEAT OPT = 0 CALL PLAY_LEVEL(L, OPT, LEVEL_TIME) UNTIL OPT <> 1 IF OPT = 2 THEN EXIT SUB BG 0 ATTR (4) CALL CTEXT(6, "SUCCESS") BEST = LEVEL_BEST(L) IF BEST = 0 OR LEVEL_TIME < BEST THEN CALL CTEXT(8, "BEST TIME!") LEVEL_BEST(L) = LEVEL_TIME END IF LEVEL_LAST(L) = LEVEL_TIME CALL CTEXT(12, "PRESS ANY BUTTON") REPEAT CALL DRAW_ACTORS WAIT VBL UNTIL BUTTON TAP(0) CLS 0 NEXT L CALL CTEXT(7, "THAT'S ALL FOLKS!") CALL WAIT_TAP END SUB GAMEPAD 1 PAUSE OFF START_LEVEL = 0 DO CALL TITLE_SCREEN(START_LEVEL) CALL PLAY_GAME(START_LEVEL) LOOP #1:MAIN PALETTES 002D1814003F3020003F1815003F2A15 002F1B0600392410003F1500003F2A15 #2:MAIN CHARACTERS 00000000000000000000000000000000 0000000000000000FFFFFFFFFFFFFFFF 00000000000000007FFFFFFFFFFFFFFF 0000000000000000FEFFFFFFFFFFFFFF 0000000000000080FFFFFFFFFFFFFFFF 0000000000000001FFFFFFFFFFFFFFFF 00000080FF7F0000FFFFFFFFFF7F0000 00000000FFFF0000FFFFFFFFFFFF0000 00000001FFFE0000FFFFFFFFFFFE0000 0000000000000000000000007FFFFFFF 000000000000000000000000FFFFFFFF 000000000000000000000000FEFFFFFF 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 FFFF000000000000FFFF000000000000 00000000000000000000000000000000 00000000000000000000000000000000 FF7F000000000000FF7F000000000000 FFFE000000000000FFFE000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 FFFFFFFFFFFFFFFF0F0FF0F00F0FF0F0 FFFFFFFF000000000F0FF0F0FFFFFFFF 00000000FFFFFFFFFFFFFFFF0F0FF0F0 888822228888222277FFDDFF77FFDDFF 888822220000000077FFDDFFFFFFFFFF 0000000088882222FFFFFFFF77FFDDFF AA005500AA00550077FFBBFF77FFEEFF AA0055000000000077FFBBFFFFFFFFFF 00000000AA005500FFFFFFFF77FFEEFF 3CFFFF3C00000000FFFFFFFFFFFFFFFF 000000003CFFFF3CFFFFFFFFFFFFFFFF 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 0030614303071E3C3C4E9FBFFFFF7E3C 0C3663835F0600003C4E9FFFFF7E0000 0C3663036F0600003C4E9FFFFF7E0000 0C366383370600003C4E9FFFFF7E0000 7EFFFF7FA743A742000000815FBF5F3E 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00007EFFFF7E000000007EFFFF7E0000 00007EFFFF7E000000007EFFFF7E0000 00007EFFFF7E000000007EFFFF7E0000 00007EFFFF7E000000007EFFFF7E0000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00001028100000000000103810000000 00000018180000000000001818000000 00000010000000000000001000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000038000000000000003800000000 00000000180000000000000018000000 00000000100000000000000010000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 00000000000000000000000000000000 183C3C3C3C183C181824242424182418 6CFEFE7E240000006C92925A24000000 247EFF7E7EFF7E24245A815A5A815A24 083E7F7E3F7F3E080836414631413608 62F7FE7C3E7FEF4662959A742E59A946 1C3E7E7EFFFE7F3A1C224A46919A453A 183C3C78300000001824244830000000 0C1E3C78783C1E0C0C1224484824120C 30783C1E1E3C78303048241212244830 00247E7EFF7E7E2400245A6681665A24 00183C7EFF7E3C180018246681662418 000000183C3C78300000001824244830 0000007EFF7E00000000007E817E0000 00000000183C3C180000000018242418 060F1E3C78F0E040060912244890A040 3C7EFFFFFFFF7E3C3C4299918999423C 183C7C3C3C7EFF7E182444242466817E 3C7EFF7E3C7EFF7E3C429972244E817E 3C7EFF7E6FFF7E3C3C4299726999423C 66FFFFFF7F0F0F066699998179090906 7EFFFEFE7F7FFE7C7E819E827979827C 1C3E7CFEFFFF7E3C1C224C829999423C 7EFF7F1E3C7878307E81791224484830 3C7EFF7EFFFF7E3C3C4299429999423C 3C7EFF7F7FFF7E3C3C4299417999423C 0000183C183C18000000182418241800 0000183C183C78300000182418244830 000C1E3C783C1E0C000C12244824120C 00007EFF7EFF7E0000007E817E817E00 0030783C1E3C78300030482412244830 3C7EFF7E3C183C183C42997224182418 3C7EFFFFFFFE7E3C3C429991919E423C 183C7EFFFFFFFF661824429981999966 7CFEFFFEFFFFFE7C7C8299829999827C 3C7EFFF6F6FF7E3C3C4299969699423C 78FCFEFFFFFEFC787884929999928478 7EFFFEFCF8FEFF7E7E819E84989E817E 7EFFFEFCF8F0F0607E819E8498909060 3C7EFEFFFFFF7E3C3C429E919999423C 66FFFFFFFFFFFF666699998199999966 3C7E3C3C3C3C7E3C3C4224242424423C 1E3F1F0F6FFF7E3C1E2119096999423C 66FFFEFCFCFEFF666699928484929966 60F0F0F0F0FEFF7E60909090909E817E 42E7FFFFFFFFFF6642A5998181999966 66FFFFFFFFFFFF666699898191999966 3C7EFFFFFFFF7E3C3C4299999999423C 7CFEFFFEFCF0F0607C8299829C909060 3C7EFFFFFFFE7F3E3C4299999592413E 7CFEFFFEFCFEFF667C82998284929966 3E7FFE7E3F7FFE7C3E419E423979827C 7EFF7E3C3C3C3C187E81662424242418 66FFFFFFFFFF7E3C669999999999423C 66FFFFFFFF7E3C186699999999422418 66FFFFFFFFFFE742669999818199A542 66FF7E3C7EFFFF666699422442999966 66FFFF7E3C3C3C186699994224242418 7EFF7E3C78FEFF7E7E817224489E817E 3C7E7C78787C7E3C3C424C48484C423C 60F0783C1E0F07026090482412090502 3C7E3E1E1E3E7E3C3C4232121232423C 183C7EFF660000001824429966000000 00000000007EFF7E00000000007E817E