DECLARE SUB main () DECLARE SUB mapFromData () DECLARE SUB mapDraw () DECLARE FUNCTION actorMoveTarget% (iActor AS INTEGER, iDirection AS INTEGER) DECLARE SUB actorDraw (iSlot AS INTEGER) DECLARE FUNCTION getInput% () CONST cCellWidth = 16 CONST cCellHeight = 16 CONST cMaxCells = 512 CONST cBlockIsSolid = &H8000 ' Block is solid. CONST cMoveUp = 0 CONST cMoveRt = 1 CONST cMoveDn = 2 CONST cMoveLf = 3 TYPE blockType iColor AS INTEGER iFlags AS INTEGER END TYPE TYPE ActorType iX AS INTEGER ' Horizontal coordinate, in cells (column) iY AS INTEGER ' Vertical coordinate, in cells (rows) END TYPE DIM SHARED gBlock(15) AS blockType DIM SHARED gMapData(cMaxCells - 1) AS INTEGER DIM SHARED gMapWidth AS INTEGER DIM SHARED gMapHeight AS INTEGER DIM SHARED gActor(0) AS ActorType main ' Cells are 16x16 and mode 13 is 320x200. This means we can ' have a one-screen map of 20 columns by 12 rows. DATA 20, 12 ' This is our world data. Right now we only have two types of ' blocks: 0 (non-solid) and 1 (solid.) DATA "11000000000000000011" DATA "11000000000000000011" DATA "11000000000001111011" DATA "11000000000011110011" DATA "11000110000000000011" DATA "11000110000000000011" DATA "11111111111110000011" DATA "11000001100000001111" DATA "11000001100000011111" DATA "11001000000000111111" DATA "11000000001100000011" DATA "11111111111111111111" '' '' Display the actor on screen. '' SUB actorDraw (iSlot AS INTEGER) DIM uActor AS ActorType, iX AS INTEGER, iY AS INTEGER ' Shorthand uActor = gActor(iSlot) ' Compute the actor's current location. iX = uActor.iX * cCellWidth + 1 iY = uActor.iY * cCellHeight + 1 ' Draw actor in current location. LINE (iX, iY)-STEP(cCellWidth - 3, cCellHeight - 3), 4, B END SUB '' '' This function returns the address of the cell the actor may '' move to, or -1 if the actor is not allowed to go there. '' FUNCTION actorMoveTarget% (iActor AS INTEGER, iDirection AS INTEGER) DIM iTargetX AS INTEGER, iTargetY AS INTEGER DIM iOffsetX AS INTEGER, iOffsetY AS INTEGER DIM iTargetAdr AS INTEGER, iIsSolid AS INTEGER ' Get the coordinates of the destination cell. SELECT CASE (iDirection) CASE cMoveUp iOffsetX = 0 iOffsetY = -1 CASE cMoveLf iOffsetX = -1 iOffsetY = 0 CASE cMoveDn iOffsetX = 0 iOffsetY = 1 CASE cMoveRt iOffsetX = 1 iOffsetY = 0 END SELECT iTargetX = gActor(iActor).iX + iOffsetX iTargetY = gActor(iActor).iY + iOffsetY ' Out of bound check: return -1 if the cell is out of bounds. IF (iTargetX < 0) OR (iTargetX >= gMapWidth) OR (iTargetY < 0) OR (iTargetY >= gMapHeight) THEN actorMoveTarget% = -1 EXIT FUNCTION END IF ' Convert X, Y coordinates to address. iTargetAdr = iTargetX + iTargetY * gMapWidth ' Read the cell value to get the block type. If the block is ' solid, iIsSolid is True. Kind reminder: True is -1. iIsSolid = ((gBlock(gMapData(iTargetAdr)).iFlags AND cBlockIsSolid) <> 0) ' Return the address of the cell within the world if the actor ' can move there (iIsSolid is 0,) or -1 if it cannot (iIsSolid ' is -1.) actorMoveTarget% = iTargetAdr OR iIsSolid END FUNCTION '' '' Catch keyboard input via INKEY$, return an INTEGER. '' FUNCTION getInput% DIM sTemp AS STRING, iSize AS INTEGER sTemp = INKEY$ iSize = LEN(sTemp) IF (iSize = 1) THEN getInput% = ASC(sTemp) ELSEIF (iSize = 2) THEN getInput% = CVI(sTemp) END IF END FUNCTION '' '' Main routine. Use the arrow keys to move around, and escape '' to terminate the program. The trickier part is probably the '' timestepping; long story short: we measure the time elapsed '' since last game update. For every 0.01 second, we update the '' game to catch up and reduce the "temporal debt" by 0.01. It's '' not very useful right now but it will become necessary when '' we tackle smooth movement. '' SUB main CONST cKeyUp = &H4800 CONST cKeyDn = &H5000 CONST cKeyLf = &H4B00 CONST cKeyRt = &H4D00 CONST cKeyEsc = &H1B ' For timestepping... DIM fCurrent AS SINGLE, fPrevious AS SINGLE DIM fElapsed AS SINGLE, fLag AS SINGLE ' For clearing the screen DIM iBackX AS INTEGER, iBackY AS INTEGER DIM iBackImg(511) AS INTEGER ' User input, temporary target cell address. DIM iUser AS INTEGER, iAdr AS INTEGER ' Setup block types. gBlock(0).iColor = 0 gBlock(0).iFlags = 0 gBlock(1).iColor = 3 gBlock(1).iFlags = cBlockIsSolid ' Load map data mapFromData ' Set actor's current location. gActor(0).iX = 3 gActor(0).iY = 1 SCREEN 13 mapDraw ' Redraw actor (copy background) iBackX = gActor(0).iX: iBackY = gActor(0).iY GET (iBackX * cCellWidth, iBackY * cCellHeight)-STEP(cCellWidth - 1, cCellHeight - 1), iBackImg actorDraw 0 fPrevious = TIMER DO ' Update timer fCurrent = TIMER fElapsed = fCurrent - fPrevious fLag = fLag + fElapsed fPrevious = fCurrent ' Process game logic until we're caught up. WHILE (fLag > .01) ' Catch user input. iUser = getInput% IF (iUser) THEN SELECT CASE (iUser) CASE cKeyUp iAdr = actorMoveTarget%(0, cMoveUp) CASE cKeyDn iAdr = actorMoveTarget%(0, cMoveDn) CASE cKeyRt iAdr = actorMoveTarget%(0, cMoveRt) CASE cKeyLf iAdr = actorMoveTarget%(0, cMoveLf) CASE cKeyEsc EXIT SUB END SELECT ' Got a new target cell, convert the address to proper ' X, Y coordinates. IF (iAdr >= 0) THEN gActor(0).iX = iAdr MOD gMapWidth gActor(0).iY = iAdr \ gMapWidth END IF END IF ' One game tick is 0.01 second. fLag = fLag - .01 WEND ' Redraw actor (erase background as necessary) IF (iBackX <> gActor(0).iX) OR (iBackY <> gActor(0).iY) THEN PUT (iBackX * cCellWidth, iBackY * cCellHeight), iBackImg, PSET iBackX = gActor(0).iX: iBackY = gActor(0).iY GET (iBackX * cCellWidth, iBackY * cCellHeight)-STEP(cCellWidth - 1, cCellHeight - 1), iBackImg actorDraw 0 END IF LOOP END SUB '' '' Read data off gMapData() and display on-screen. The top-left '' corner of the map is located at 0, 0 with positive values '' going right and down. '' SUB mapDraw DIM iR AS INTEGER, iC AS INTEGER DIM iX AS INTEGER, iY AS INTEGER DIM iRow AS INTEGER DIM uBlock AS blockType ' Read map data. FOR iR = 0 TO gMapHeight - 1 ' Get the reading offset to the 1st cell of the row. iRow = iR * gMapWidth ' On-screen vertical offset. iY = iR * cCellHeight FOR iC = 0 TO gMapWidth - 1 ' On-screen horizontal offset. iX = iC * cCellWidth ' Get block type. uBlock = gBlock(gMapData(iRow + iC)) ' Draw the content of the cell. LINE (iX, iY)-STEP(cCellWidth - 1, cCellHeight - 1), uBlock.iColor, BF ' If the block is solid, cross it. IF (uBlock.iFlags AND cBlockIsSolid) THEN LINE (iX, iY)-STEP(cCellWidth - 1, cCellHeight - 1), 0 LINE (iX + cCellWidth - 1, iY)-STEP(-(cCellWidth - 1), cCellHeight - 1), 0 END IF NEXT iC NEXT iR END SUB '' '' Setup the world via DATA statements. '' SUB mapFromData DIM iR AS INTEGER, iC AS INTEGER DIM sDta AS STRING, iRow AS INTEGER ' Read map width and height in cells. If you use custom ' values, make sure they do not overflow the buffer we ' allocated for the map data earlier. READ gMapWidth, gMapHeight ' Start reading DATA statements. If you use custom values, ' make sure there's enough data available to fill the map or ' this will crash. FOR iR = 0 TO gMapHeight - 1 ' Read one row. READ sDta ' Get the writing offset to the 1st cell of the row. iRow = iR * gMapWidth ' Decode each cell and write to gMapData(). If you use ' custom values, make sure each DATA statement contains the ' proper number of cells or this may crash. FOR iC = 0 TO gMapWidth - 1 gMapData(iRow + iC) = VAL(MID$(sDta, 1 + iC, 1)) NEXT iC NEXT iR END SUB