OPTION STRICT 'REQUIRE VARIABLES BE DECLARED BEFORE USE
'THIS IS A PORT OF G-ZERO BY SMILEBOOM ORIGINALLY FOR PETIT COMPUTER ON THE NINTENDO DS
'
'IT WAS FOUND IN THE PETIT COMPUTER OFFICIAL STRATEGY TECHNIC
'STARTING ON PAGE 94 IN THE 100 LINE PROGRAM CHALLENGE SECTION.
'
'YOU RACE A SHIP AROUND A RACE TRACK COMPETING AGAINST YOUR OWN BEST TIME.
'
'CONTROLS.
'[LEFT] TURN THE SHIP LEFT
'[RIGHT] TURN THE SHIP RIGHT
'[A] ACCELERATE
'[Y] EXIT PROGRAM
'
VAR LAYER = 0 'WHAT BACKGROUND LAYER TO READ/WRITE TO
VAR X, Y, I, J 'ITERATE OVER A GRID
VAR FONT$ 'VARIABLE TO WHICH THE PIXEL PATTERN OF A LETTER WILL BE UNPACKED TO
VAR GAME_OVER 'FLAG TO SAY IF THE GAME SHOULD KEEP RUNNING.
VAR ANG 'CURRENT ANGLE OF THE SHIP
VAR DA 'HOW MUCH TO CHANGE THE ANGLE WHEN TURNING
VAR B 'USED TO STORE BUTTON STATE
VAR BX, BY 'BACKGROUND LAYER X AND Y COORDINATES
VAR START_LINE 'FLAG TO SAY IF YOU PASSED THE STARTING LINE
VAR TMP 'THROW AWAY TEMPORARY VARIABLE
VAR FRICTION 'SLOWLY REDUCE SPEED OVER TIME WHEN NO FORCE APPLIED
VAR SPEED 'HOW FAST THE SHIP IS MOVING (SQUARED ACTUALLY)
VAR SPEED_MAX 'TOP ALLOWABLE SPEED
VAR ANG_RAD 'CURRENT ANGLE ADJUSTED TO POINT THE SHIP CORRECTLY AND CONVERTED TO RADIANS
VAR T1, T2 'TIMER VARIABLES TO KEEP TRACK OF TIME SPENT RUNNING A LAP
VAR DX, DY 'HOW FAST WE ARE MOVING IN THE X AND Y DIRECTION
VAR EXHAUST_DIST 'DISTANCE OF THE EXHAUST FROM THE SHIP (MOVES BACK AND FORTH)
VAR MAP_W = 9, MAP_H = 9 'WIDTH AND HEIGHT OF THE MAP, CHANGE THE WIDTH TO 15 (WIDTH OF THAT MAP) IF YOU USE THE OPTIONAL MAP
VAR T_ROAD = 842 'TILE ID FOR THE ROAD. WE ACTUALLY USE A RANGE OF TILES STARTING HERE AND UP TO ID + 3
VAR T_WALL = 584 'TILE ID FOR THE WALL. WE ACTUALLY USE A RANGE OF TILES STARTING HERE AND UP TO ID + 3
VAR T_FINISH = 349 'TILE ID FOR THE FINISH LINE
VAR T_SHIP = 1217 'SPRITE ID FOR THE SHIP/PLAYER
VAR T_EXHAUST = 1347 'SPRITE ID OF THE EXHAUST FLAME
VAR SCREEN_W = 400, SCREEN_H = 240 'THE SCREEN WIDTH AND HEIGHT
VAR HALF_W = SCREEN_W / 2 'HALF OF THE SCREEN WIDTH (CONVENIENCE VARIABLE)
VAR HALF_H = SCREEN_H / 2 'HALF OF THE SCREEN HEIGHT (CONVENIENCE VARIABLE)
'FLAG TO COMBINE ALL OF THE DIRECTION BUTTONS TOGETHER IN A FLAG, USED TO SEE IF
'ANY DIRECTION KEY PRESSED.
VAR ANY_DIR = #UP OR #DOWN OR #LEFT OR #RIGHT
'---------- G-ZERO
'THE MAP WE ARE RACING ON. EACH PIXEL OF EACH CHARACTER BECOMES A TILE ON THE BACKGROUND.
DIM M$[MAP_H]
M$[0] = ""
M$[1] = " X "
M$[2] = " "
M$[3] = " "
M$[4] = " "
M$[5] = " "
M$[6] = " "
M$[7] = " "
M$[8] = ""
'ALTERNATE MAP (REMEMBER TO UPDATE MAP_W IF YOU USE THIS)
'M$[0] = ""
'M$[1] = " "
'M$[2] = " "
'M$[3] = " X "
'M$[4] = " "
'M$[5] = " "
'M$[6] = " "
'M$[7] = " "
'M$[8] = ""
'CLEAR OUT ANY OLD STUFF FROM THE SCREEN.
CLS
'SET UP THE BACKGROUND SCREEN
'EVERY PIXEL OF EACH CHARACTER COUNTS SO THE MAP IS 8 TIMES BIGGER DUE TO AN 8X8 FONT.
BGSCREEN LAYER, MAP_W * 8, MAP_H * 8
'SET DIRECTION TO STOPPED
DX = 0 : DY = 0
'DRAW THE MAP. X, Y ITERATE OVER THE CHARACTERS IN THE MAP DATA
'I, J ITERATE OVER THE PIXELS OF EACH CHARACTER.
FOR X = 0 TO MAP_W - 1 ' DRAW MAP
FOR Y = 0 TO MAP_H - 1
'GET THE X,Y CHARACTER OF THE MAP
FONT$ = CHRREAD(MID$(M$[Y], X, 1))
FOR J = 0 TO 7
FOR I = 0 TO 7
'READ THE I,J PIXEL FOR THAT CHARACTER
T1 = VAL(MID$(FONT$, I + (J * 8), 1))
'CALCULATE HOW THAT MAPS TO A BACKGROUND TILE
BX = I + X*8 : BY = J + Y*8
'GIVE THE MAP A BIT OF VARIETY BY OFFSETING THE ROAD TEXTURE 0 TO 3 TILES
TMP= RND(4) + T_ROAD 'ROAD
IF T1 == 1 THEN
'IF WE ARE WALL INSTEAD OF ROAD DO THE SAME THING FOR THE ROAD TILE ID
TMP = RND(4) + T_WALL 'WALL
ELSEIF T1 == 2 THEN
'THE FINISH LINE IS ALWAYS JUST THE ONE TILE.
TMP = T_FINISH 'FINISH LINE
IF DX == 0 AND DY == 0 THEN
'SINCE X AND Y ARE BUSY, STORE WHERE WE SHOULD START IN DX, DY
'WHEN WE FIND THE FINISH LINE
'8 IS FOR THE FONT WIDTH, 16 IS FOR THE TILE WIDTH AND THE 4*16 IS TO PUT IT IN THE CENTER OF THE FINISH LINE.
DX = X * 8 * 16 + (4 * 16)
DY = Y * 8 * 16 + (4 * 16)
ENDIF
ENDIF
'WRITE THE TILE OUT TO THE BACKGROUND LAYER
BGPUT LAYER, BX, BY, TMP
NEXT I
NEXT J
NEXT Y
NEXT X
'CLEAR OUT THE GRAPHICS SCREEN. WE MODIFIED IT WHEN CHECKING PIXELS IN A CHARACTER.
GCLS
'--------PLAYER INIT
'CLEAN UP ANY PREVIOUSLY SET UP SPRITES
SPCLR
'SPRITE ID = 0 WILL BE THE PLAYER SHIP.
'SET THE HOME TO 8,8 SO IT ROTATES AROUND ITS CENTER, THEN PLACE IT IN THE
'CENTER OF THE SCREEN.
SPSET 0, T_SHIP 'SHIP
SPHOME 0, 8, 8
SPOFS 0, HALF_W, HALF_H
'SPRITE ID = 1 WILL BE THE SHIP'S EXHAUST.
'SET THE HOME TO THE MIDDLE JUST LIKE THE SHIP AND FOR THE SAME REASON.
'WE PLACE IT THE SAME POSITION AS THE SHIP, BUT BACK A TILE WIDTH
SPSET 1, T_EXHAUST 'EXHAUST
SPHOME 1, 8, 8
SPOFS 1, HALF_W - 16, HALF_H
'LINK THE EXHAUST TO THE SHIP SO THAT IT MOVE/ROTATES WITH THE SHIP. IT SIMPLIFIES THINGS.
SPLINK 1, 0 'LINK TOGETHER
'THE EXHAUST IS AT THE ZERO DISTANCE FOR THE MOMENT AND WE ARE ON THE STARTING LINE
EXHAUST_DIST = 0 : START_LINE = 1
'MOVE THE CURRENT POSITION BACK INTO THE X AND Y VARIABLES
X = DX : Y = DY
'SET THE X,Y SPEED VARIABLES BACK TO ZERO.
DX = 0 : DY = 0
'WE ARE CURRENTLY POINTED AT 180 DEGREES, MOVE BY TWO DEGREES WHEN TURNING AND TOP SPEED IS 16 PIXELS/FRAME
ANG = 180 : DA = 2 : SPEED_MAX = 16
'WE SHOULD KEEP PLAYING, AND SET THE TIMER AT NOW.
GAME_OVER = FALSE : T1 = MILLISEC
'--------MAIN
'THIS IS THE MAIN GAME LOOP. KEEP GOING UNTIL THE GAME OVER FLAG IS SET TO FALSE (BY CLICKING Y)
WHILE GAME_OVER == FALSE
'GET BUTTON STATE
B = BUTTON()
'DEFAULT VALUE FOR FRICTION
FRICTION = 0.99
'CHECK IF WE SHOULD EXIT THE GAME
IF B AND #Y THEN GAME_OVER = TRUE
'CHANGE THE ANGLE IF TURNING LEFT OR RIGHT.
IF B AND #RIGHT THEN ANG = ANG + DA
IF B AND #LEFT THEN ANG = ANG - DA
'IF ANY DIRECTION IS PRESSED, REDUCE THE FRICTION TO 98%
IF B AND (ANY_DIR) THEN FRICTION = 0.98
'MAKE SURE THE ANGLE STAYS IN THE 0 TO 360 RANGE
IF ANG < 0 THEN ANG = ANG + 360
IF ANG > 360 THEN ANG = ANG - 360
'SLOW DOWN SPEED DUE TO FRICTION
DX = DX * FRICTION : DY = DY * FRICTION
'GET THE SQUARED MAGNITUDE OF THE SPEED.
SPEED = (DX * DX) + (DY * DY)
'IF WE HAVEN'T REACHED TOP SPEED AND A IS PRESSED INCREASE SPEED BY 1/20 OF UNIT VECTOR
IF ((B AND #A) != 0) AND SPEED < SPEED_MAX THEN
ANG_RAD = RAD((ANG + 180) MOD 360)
DX = DX + COS(ANG_RAD)/20
DY = DY + SIN(ANG_RAD)/20
ENDIF
'ROTATE THE SHIP TO MATCH THE CURRENT ANGLE.
SPROT 0, (ANG + 90) MOD 360
'MODIFY THE X, Y COORDINATES BY THE SPEED X, Y COMPONENTS
X = X + DX : Y = Y + DY
'DON'T LET THE SHIP LEAVE THE BACKGROUND
X = MAX(0, MIN(X, (MAP_W * 16 * 8) - 16))
Y = MAX(0, MIN(Y, (MAP_H * 16 * 8) - 16))
'GET THE BACKGROUND TILE WE ARE CURRENTLY LOCATED OVER
TMP = BGGET(LAYER, X / 16, Y / 16)
'BUMP BACK IF WE HIT A WALL
IF TMP >= T_WALL AND TMP <= T_WALL + 3 THEN
DX = -DX
DY = -DY
ENDIF
'FIND THE SPOT TO POSITION THE BACKGROUND LAYER TO KEEP THE
'SHIP AT THE MIDDLE OF THE SCREEN OR AT LEAST WITHIN THE BACKGROUND LAYER.
BX = MIN(MAX(0, X - HALF_W), (MAP_W * 16 * 8) - SCREEN_W)
BY = MIN(MAX(0, Y - HALF_H), (MAP_H * 16 * 8) - SCREEN_H)
'MOVE THE BACKGROUN TO THE CORRECT SPOT.
BGOFS LAYER, BX, BY
'IF WE ARE ON THE EDGE OF THE MAP WE WON'T BE AT THE CENTER OF THE SCREEN.
'CONVERT THE WORLD COORDINATES TO SCREEN CORRDINATES FOR THE SHIP
SPOFS 0, X - BX, Y - BY
'CHECK TO SEE IF WE ARE HOLDING [A] TO ACCELERATE AND ARE NOT AT TOP SPEED.
IF ((B AND #A) != 0) AND SPEED < SPEED_MAX THEN
'MOVE THE EXHAUST BACK FROM THE SHIP A BIT AND SCALE UP THE SPEED.
EXHAUST_DIST = EXHAUST_DIST + 1 : SPEED = SPEED * 700
'IF THE EXHAUST GOT TOO FAR AWAY MOVE IT BACK AND PLAY A SPEED UP SOUND.
IF EXHAUST_DIST > 16 THEN EXHAUST_DIST = 6 : BEEP 1
SPOFS 1, 0, -EXHAUST_DIST
SPSHOW 1
ELSE
'HIDE THE EXHAUST SPRITE WHEN WE AREN'T ACCELERATING.
SPHIDE 1
ENDIF
'CHECK TO SEE IF WE PASSED INTO THE FINISH LINE
TCHK X, Y
'LIMIT SPEED TO REFRESH RATE
VSYNC
WEND
END
'CHECK TO SEE IF WE ENTERED THE FINISH LINE. IF SO, PRINT THE LAP TIME AND PLAY A
'COIN SOUND.
'PARAMETERS:
'* X: LOCATION OF THE SHIP X-COORDINATE
'* Y: LOCATION OF THE SHIP Y-COORDINATE
'RETURNS:
'* NONE
'
DEF TCHK X, Y
VAR I, J 'BACKGROUND TILE COORDINATES
'------------TIME
'TRANSLATE THE WORLD COORDINATES INTO BACKGROUND COORDINATES BY
'DIVIDING BY 16 OR THE SIZE OF A TILE.
I = FLOOR(X / 16) : J = FLOOR(Y / 16)
'IF WE ARE NOT ON THE FINISH LINE SET THE FLAG SO THAT CROSSING INTO THE
'FINISH LINE COUNTS
IF BGGET(LAYER, I, J) != T_FINISH THEN
START_LINE = 0
RETURN
ENDIF
IF START_LINE != 0 THEN
'IF WE WERE ON THE FINISH LINE LAST FRAME, DON'T CHECK IT AGAIN.
RETURN
ELSE
'TO GET THIS FAR WE MUST BE ON THE FINISH LINE AND WEREN'T LAST TIME
'THE FUNCTION WAS CALLED, SO SET THE FLAG.
START_LINE = 1
ENDIF
BEEP 7 'COIN
'SAVE THE CURRENT TIME.
T2 = MILLISEC
'CONTINUE ONLY IF WE HAVE A STARTING TIME.
IF T1 THEN
LOCATE 15, 1
'PRINT OUT THE LAP TIME. NOTE THE FORMAT$ CALL IS TURNING THE NUMBER INTO A
'NICE STRING VALUE WITH A TWO DIGIT DECIMAL AMOUNT (SO 3.333333 WILL PRINT AS 3.33
PRINT "TIME: " + FORMAT$("%03.2F", (T2 - T1) / 1000) + " SEC ";
'NEW START TIME IS THE FINISH TIME
T1 = T2
ENDIF
END
'POLYFILL FOR PTC ONLY FUNCTION
'THIS GETS THE PATTERN OF PIXELS FOR A CHARACTER IN AN 8X8 FONT.
'PETIT COMPUTER HAD A NICE CALL FOR THIS THAT SMILEBASIC DOESN'T HAVE.
'TO WORK AROUND IT, I PRINT THE CHARACTER TO POSITION 0, 0 THEN READ
'EACH PIXEL IN THE 8X8 GRID AND RETURN IT AS A STRING OF 0, 1.
'UNLESS IT IS THE SPECIAL CHARACTER X THAT MARKS THE FINISH LINE, IN THAT
'CASE YOU JUST GET ALL 2'S.
'PARAMETERS:
'* CH$ - CHARACTER TO READ, ASSUMED TO BE ONE SINGLE CHARACTER.
'RETURNS:
'* A STRING OF 64 2'S IF THE SPECIAL CHARACTER X (START/FINISH LINE). OTHERWISE
' IT WILL RETURN EACH PIXEL OF THE CHARACTER WITH A 0 IF BLACK/EMPTY AND 1 IF FILLED. IT WILL GO LEFT TO RIGHT, TOP TO BOTTOM.
'
DEF CHRREAD(CH$)
VAR RET$ = "" 'RETURN VALUE
VAR I, J 'LOOP OVER THE X, Y COORDINATES OF THE CHARACTER
'CLEAR OUT OLD JUNK THE LAZY WAY.
GCLS
'WRITE THE CHARACTER TO POSITION 0, 0 ON THE SCREEN.
GPUTCHR 0, 0, CH$
FOR J = 0 TO 7
FOR I = 0 TO 7
IF CH$ == "X" THEN 'RETURN ALL 2'S IF THE START/FINISH LINE.
RET$ = RET$ + "2"
ELSE
'CHECK TO SEE IF THE PIXEL IS FILLED OR NOT ON THE SCREEN.
IF GSPOIT(I, J) != 0 THEN
'YES, WRITE A 1
RET$ = RET$ + "1"
ELSE
'NO, WRITE A 0
RET$ = RET$ + "0"
ENDIF
ENDIF
NEXT I
NEXT J
'RETURN THE ENCODED STRING TO THE CALLER.
RETURN RET$
END
Hopefully that works for you. I found a few bugs here and there for instance, the wall/road tiles were reversed. I just edited the text file locally, so if there are comments that are too long or are missing the comment marker, I apologize.
The function CHRREAD is something I added to cover up for a font reading function that Petit Computer had that Smile Basic apparently does not. The real trick with this one is that we have not just a tile per character, but we have a tile per pixel in each character. You have to use the box drawing characters to not have strange holes everywhere. To try to make things less monotonous, each road/wall tile is taken from the 0 to 3 spaces from id of the road/wall tile. If you play the game two times in a row, you should get a slightly different looking road/wall pattern. You can easily cheat in the game by going in circles in and out of the finish line. If you are playing fair, the trick is to drift through the turns by turning a bit early and having the trajectory slowly take you the correct path instead of just how you are pointed. If you touch the walls you get bumped back in the opposite direction which will cost you time. Oh, and I forgot if you use the new map I made, make sure to update the map_w and map_h variables to match. It should be fairly simple to make your own track if desired.