Browse Source

Added samples.

blitz-research 11 years ago
parent
commit
f3f749af43
100 changed files with 6655 additions and 0 deletions
  1. 6 0
      samples/aaronkoolen/AStar/Callback.bmx
  2. 805 0
      samples/aaronkoolen/AStar/astar_demo.bmx
  3. 259 0
      samples/aaronkoolen/AStar/astar_graph_walker.bmx
  4. 117 0
      samples/aaronkoolen/AStar/astar_node.bmx
  5. BIN
      samples/aaronkoolen/AStar/map0
  6. BIN
      samples/aaronkoolen/AStar/map1
  7. BIN
      samples/aaronkoolen/AStar/map2
  8. BIN
      samples/aaronkoolen/AStar/map3
  9. BIN
      samples/aaronkoolen/AStar/map4
  10. BIN
      samples/aaronkoolen/AStar/map5
  11. BIN
      samples/aaronkoolen/AStar/map6
  12. BIN
      samples/aaronkoolen/AStar/map7
  13. BIN
      samples/aaronkoolen/AStar/mountain.png
  14. BIN
      samples/aaronkoolen/AStar/nums.png
  15. 161 0
      samples/aaronkoolen/AStar/priority_queue.bmx
  16. BIN
      samples/aaronkoolen/AStar/road.png
  17. BIN
      samples/aaronkoolen/AStar/tree.png
  18. BIN
      samples/aaronkoolen/AStar/water.png
  19. 443 0
      samples/birdie/games/tempest/tempest.bmx
  20. BIN
      samples/birdie/games/tiledrop/media/back.png
  21. BIN
      samples/birdie/games/tiledrop/media/blocks.png
  22. BIN
      samples/birdie/games/tiledrop/media/part.png
  23. BIN
      samples/birdie/games/tiledrop/media/pointer.PNG
  24. BIN
      samples/birdie/games/tiledrop/media/shine.png
  25. 373 0
      samples/birdie/games/tiledrop/tiledrop.bmx
  26. 771 0
      samples/birdie/games/zombieblast/game.bmx
  27. BIN
      samples/birdie/games/zombieblast/media/HitSpace.png
  28. BIN
      samples/birdie/games/zombieblast/media/Title.png
  29. BIN
      samples/birdie/games/zombieblast/media/cloud.png
  30. BIN
      samples/birdie/games/zombieblast/media/mud.png
  31. BIN
      samples/birdie/games/zombieblast/media/sand.png
  32. BIN
      samples/birdie/games/zombieblast/media/scan.png
  33. BIN
      samples/birdie/games/zombieblast/media/ship1.png
  34. BIN
      samples/birdie/games/zombieblast/media/shot.png
  35. BIN
      samples/birdie/games/zombieblast/media/shot2.PNG
  36. BIN
      samples/birdie/games/zombieblast/media/smoke.png
  37. BIN
      samples/birdie/games/zombieblast/media/zombie_0.png
  38. BIN
      samples/birdie/games/zombieblast/media/zombie_1.png
  39. BIN
      samples/birdie/games/zombieblast/media/zombie_2.PNG
  40. BIN
      samples/birdie/games/zombieblast/media/zombie_3.PNG
  41. BIN
      samples/birdie/games/zombieblast/media/zombie_4.PNG
  42. BIN
      samples/birdie/games/zombieblast/media/zombie_5.PNG
  43. BIN
      samples/birdie/games/zombieblast/media/zombie_6.png
  44. BIN
      samples/birdie/games/zombieblast/media/zombie_7.PNG
  45. 56 0
      samples/birdie/misc/filmclip/main.bmx
  46. BIN
      samples/birdie/misc/filmclip/media/B-Max.png
  47. BIN
      samples/birdie/misc/filmclip/media/Thumbs.db
  48. BIN
      samples/birdie/misc/filmclip/media/flmstp.png
  49. 282 0
      samples/birdie/misc/glblur/glblurr.bmx
  50. 67 0
      samples/birdie/misc/lightImage/main.bmx
  51. BIN
      samples/birdie/misc/lightImage/media/B-Max.png
  52. BIN
      samples/birdie/misc/lightImage/media/fl.png
  53. BIN
      samples/birdie/misc/lightImage/media/light.png
  54. 334 0
      samples/breakout/breakout.bmx
  55. BIN
      samples/breakout/media/B-Max.png
  56. BIN
      samples/breakout/media/back1.png
  57. BIN
      samples/breakout/media/back2.png
  58. BIN
      samples/breakout/media/ball.png
  59. BIN
      samples/breakout/media/paddle.png
  60. BIN
      samples/breakout/media/pipes.png
  61. BIN
      samples/breakout/media/tiles.png
  62. 90 0
      samples/digesteroids/Digesteroids.ppf
  63. 60 0
      samples/digesteroids/MathUtil.bmx
  64. 1496 0
      samples/digesteroids/digesteroids.bmx
  65. 203 0
      samples/digesteroids/dynamicgame.bmx
  66. BIN
      samples/digesteroids/graphics/bullet1.png
  67. BIN
      samples/digesteroids/graphics/bullet2.png
  68. BIN
      samples/digesteroids/graphics/digestive.png
  69. BIN
      samples/digesteroids/graphics/lower.png
  70. BIN
      samples/digesteroids/graphics/options.png
  71. BIN
      samples/digesteroids/graphics/piece1.png
  72. BIN
      samples/digesteroids/graphics/piece2.png
  73. BIN
      samples/digesteroids/graphics/piece3.png
  74. BIN
      samples/digesteroids/graphics/pop.png
  75. BIN
      samples/digesteroids/graphics/ship.png
  76. BIN
      samples/digesteroids/graphics/sparkle.png
  77. BIN
      samples/digesteroids/graphics/stars.png
  78. BIN
      samples/digesteroids/graphics/title.png
  79. 72 0
      samples/digesteroids/minitimer.bmx
  80. 593 0
      samples/digesteroids/simplephysics.bmx
  81. BIN
      samples/digesteroids/sounds/crash.wav
  82. BIN
      samples/digesteroids/sounds/ending.ogg
  83. BIN
      samples/digesteroids/sounds/fire.wav
  84. BIN
      samples/digesteroids/sounds/impactlarge.wav
  85. BIN
      samples/digesteroids/sounds/menu.ogg
  86. BIN
      samples/digesteroids/sounds/teleport.wav
  87. BIN
      samples/digesteroids/sounds/thrust.wav
  88. BIN
      samples/firepaint/bullet.png
  89. 276 0
      samples/firepaint/color.bmx
  90. 191 0
      samples/firepaint/firepaint.bmx
  91. BIN
      samples/firepaint/player.png
  92. BIN
      samples/firepaint/shoot.wav
  93. BIN
      samples/firepaint/stars.png
  94. BIN
      samples/flameduck/circlemania/anim0.png
  95. BIN
      samples/flameduck/circlemania/anim1.png
  96. BIN
      samples/flameduck/circlemania/anim10.png
  97. BIN
      samples/flameduck/circlemania/anim11.png
  98. BIN
      samples/flameduck/circlemania/anim12.png
  99. BIN
      samples/flameduck/circlemania/anim13.png
  100. BIN
      samples/flameduck/circlemania/anim14.png

+ 6 - 0
samples/aaronkoolen/AStar/Callback.bmx

@@ -0,0 +1,6 @@
+' Simple object to handle callbacks
+' Is there a way in BlitzMax to pass pointers to functions??
+
+Type Callback
+	Method callback() Abstract
+End Type

+ 805 - 0
samples/aaronkoolen/AStar/astar_demo.bmx

@@ -0,0 +1,805 @@
+Strict
+'
+' Simple demo program of an astar path finding algorithm
+' The GUI is pretty crappy
+' There are quite a few comments in the areas of importance. Sometimes this makes
+' the code a little harder to read, but this was done for instructional purposes foremost
+
+Import "astar_graph_walker.bmx"
+Import "Callback.bmx"
+
+Global nums:TImage
+
+Local demo:AStarDemo = New AStarDemo
+demo.run()
+End
+
+
+Private
+' Small class that encapsulates a position on the map
+Type MapPos
+	Method isAtPos(otherX, otherY)
+		Return otherX = x And otherY = y
+	End Method
+	Field x,y
+End Type
+
+
+' Class that defines the terrain types that the demo uses
+Type Terrain
+	Method set(weight:Int, filename:String)
+		_weight = weight
+		_image = LoadImage(filename)
+	End Method
+	Field _colour_r,_color_g,_color_b
+	Field _weight:Int
+	Field _image:TImage
+End Type
+
+' Main application class
+Type AStarDemo
+
+' Map stuff
+	Const blockAreaWidth:Int = 600
+	Const blockAreaHeight:Int = 600
+	Const mapHeight:Int = 20
+ 	Const mapWidth:Int = 20
+	Const blockWidth:Int = 600 / 20	
+	Const blockHeight:Int = 600 / 20
+' Block map
+	Field map:Int[mapWidth, mapHeight]
+
+' Node version of map
+	Field nodeMap:AStarNode[mapWidth, mapHeight]
+	Field startPos:MapPos
+	Field endPos:MapPos
+	Field currentMap = 0
+
+' Terrain information
+	Const numTerrainTypes = 4
+	Field terrainFilenames:String[] = [ "road.png", "mountain.png", "water.png", "tree.png" ]
+	Field terrainWeights:Int[] = [ 1, -1, 4, 2 ]
+	Field terrains:Terrain[numTerrainTypes]
+	Field currentTerrainIndex:Int = 1
+	Field terrainLegendBlockX:Int
+	Field terrainLegendBlockY:Int
+
+' Path finding stuff
+' Distance
+	Const numDistanceFunctions = 4	
+' Is there a DATA like statement in Blitz?
+	Field distanceNames:String[] = [ 	"(D) Euclidean Sqr(dx*dx+dy*dy)", ..
+									 	"(D) Pseudo Euclidean dx*dx+dy*dy", ..
+										"(D) Manhatten dx+dy", ..
+										"(D) Diagonal Shortcut dx>dy ? 1.4*dy + (dx-dy) : 1.4*dx + (dy-dx)" ]
+
+	Field distanceFunction = AStarGraphWalker.distanceEuclidean	
+
+' Whether we're allowed to chage the costs with the "Q" and "W" keys
+	Field costChangeAllowed:Int 	= 0
+' Is the pathfinder allowed to cruise diagonals?
+	Field allowDiagonalPaths:Int 	= 0
+	Field diagonalsAreLonger:Int = 1
+' Time in millisecs that the last path fund took
+	Field lastRunTime:Int = 0
+	Field path:TList = Null			' The last path that was found
+
+	Field heuristicMultiplier:Float = 1.0
+	Field showProgress:Int = False	' Do we want to show the progress
+
+' UI stuff
+	Field mapButtonBlockX:Int = 0
+	Field mapButtonBlockY:Int = mapHeight + 1
+
+	Field showCosts:Int = 1
+	Field flagRedraw:Int = 1
+	Field textScale:Float = 800.0/1024.0
+	Field message:String = ""				' Any stat messages that are to be displayed
+
+' Input stuff
+' Used to detect mouse movements
+	Field oldBlockX:Int = -1
+	Field oldBlockY:Int = -1
+' Help
+	Field help:String[] = [ ..
+		"A - Allow/Disallow diagonals", ..
+		"Q - Divide terrain costs by 5", ..
+		"W - Multiply terrain costs by 5", ..
+		"D - Cycle distance functions", ..
+		"V - Toggle diagonal multiplier", ..
+		"S - Point mouse and press to set start node", ..
+		"E - Point mouse and press to set end node", ..
+		"P - Run AStar, showing progress", ..
+		"H - Click and drag on 'H' to change heuristic mult.", ..
+		"Click white square to load map of that number", ..
+		"Click red 'S' to save map as map # 'Current Map'", ..
+		"ESC - Quit program", ..
+		"", ..
+		"In progress mode:", ..
+		"Top Number    = Cost to get to that node", ..
+		"Mid Number    = Heurisitc from node to end", ..
+		"Bottom Number = Cost from start to end", ..
+		"                Through that node", ..
+		"Press 'P' to pause", ..
+		"ESC to quit progress mode", ..
+		"", ..
+		"Handy Hints", ..
+		"If DistanceFunc * heuristic Multiplier = ", ..
+		"True distance to goal, fastest path generated", ..
+		"", ..
+		"Heuristic Mult = 0 then optimal path for", ..
+		"chosen Distance Func "..
+	]
+
+' This is the main entry point for the class
+	Method run()
+	
+' Setup the graphics stuff
+		Graphics 1024,768,32
+		
+		nums = LoadAnimImage("nums.png",5,8,0,12)
+		Assert nums<> Null,"Can't load number graphic nums.png"
+		
+' Initialise dimensions of maps and other useful data
+		initialise()
+		
+' Load the first map by default
+		loadMap(currentMap)
+		
+' Redraw 
+		redraw()
+		
+		While Not KeyDown(27)
+
+			Local m:Int = MouseDown(1)
+
+			Local blockX = MouseX() / blockWidth
+			Local blockY = MouseY() / blockHeight
+
+			If m
+				handleMouseDown(blockX, blockY)
+			EndIf
+
+			processStartPlaced(blockX, blockY)
+			processEndPlaced(blockX, blockY)
+			processAllowDiagonalPaths()
+			processShowProgress()
+			processCycleDistanceFunction()
+			processDiagonalsAreLonger()
+			processDecreaseTerrainCost()
+			processIncreaseTerrainCost()
+
+			If flagRedraw 
+				oldBlockX = -1				' Mark for refresh of mouse movement
+				lastRunTime = runAStar()
+				redraw()
+				flagRedraw = False
+			EndIf
+
+		Wend
+		EndGraphics
+	End Method
+
+'-------------------------------------
+' Input handling functions
+'-------------------------------------
+
+	Method processIncreaseTerrainCost()
+		If KeyHit(KEY_W)
+			Local t
+			For t = 0 To numTerrainTypes - 1
+				terrains[t]._weight = terrains[t]._weight * 5
+			Next
+			setRedraw(True)
+			costChangeAllowed = costChangeAllowed + 1
+		EndIf
+	End Method
+
+	Method processDecreaseTerrainCost()
+		If KeyHit(KEY_Q) And costChangeAllowed > 0
+			Local t
+			For t = 0 To numTerrainTypes - 1
+				terrains[t]._weight = terrains[t]._weight / 5
+			Next
+			setRedraw(True)
+			costChangeAllowed = costChangeAllowed - 1
+		EndIf
+	End Method
+
+	Method processDiagonalsAreLonger()
+		If KeyHit(KEY_V)
+			diagonalsAreLonger = 1 - diagonalsAreLonger
+			setRedraw(True)
+		EndIf
+	End Method
+
+' Cycling the distance function to use?
+	Method processCycleDistanceFunction()
+		If KeyHit(KEY_D)
+			distanceFunction = (distanceFunction + 1) Mod numDistanceFunctions
+			setRedraw(True);
+		EndIf
+	End Method
+
+' Clear the last calculated path?
+	Method processClearPath()
+		If KeyHit(KEY_P)
+			path = Null
+			setRedraw(True)
+		EndIf
+	End Method
+
+	Method processShowProgress()
+		If KeyHit(KEY_P)
+			showProgress = True
+			runAStar()
+			showProgress = False
+		EndIf
+	End Method
+
+' Allow diagonal toggled?
+	Method processAllowDiagonalPaths()
+		If KeyHit(KEY_A)
+			allowDiagonalPaths = 1 - allowDiagonalPaths
+			setRedraw(True)
+		EndIf
+	End Method
+
+' Check if they want to move the start node
+	Method processStartPlaced(blockX:Int, blockY:Int)
+			If KeyHit(KEY_S)
+				If blockInMapArea(blockX, blockY)
+'					setMapBlock(blockX, blockY,0)
+					startPos.x = blockX
+					startPos.y = blockY
+					setRedraw(True)
+				EndIf					
+			EndIf
+	End Method
+
+' Check if they want to move the end node
+	Method processEndPlaced(blockX:Int, blockY:Int)
+			If KeyHit(KEY_E)
+				If blockInMapArea(blockX, blockY)
+'					setMapBlock(blockX, blockY,0)
+					endPos.x = blockX
+					endPos.y = blockY
+					setRedraw(True)
+				EndIf					
+			EndIf
+	End Method
+
+
+
+' Call this to tell the main engine to redraw
+	Method setRedraw(doRedraw:Int)
+		flagRedraw = doRedraw
+	End Method
+
+' Use this to change a map block while editing, so that redraws are sped up
+	Method setMapBlock(blockX, blockY, block)
+		map[blockX, blockY] = block
+	End Method
+
+' Redraws the map and last found path. You can tell it to draw more than once for double buffer purposes
+	Method redraw(times:Int = 1, flipIt:Int = 1)
+		While times > 0
+			drawMap()
+			drawPath()
+			drawTerrains()
+			drawInfo()
+			drawMapButtons()
+			If flipIt Then Flip
+			times :- 1
+		Wend
+	End Method
+
+	Method saveMap:String(mapNumber:Int)
+		Local stream:TStream = WriteStream("littleendian::map"+mapNumber)		
+		If stream = Null
+			Return "Error saving map"
+		EndIf
+		Local x,y;
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1
+				WriteInt(stream,map[x,y])
+			Next
+		Next
+		WriteInt stream, startPos.x
+		WriteInt stream, startPos.y
+		WriteInt stream, endPos.x
+		WriteInt stream, endPos.y
+		CloseStream stream
+		Return "Done"
+	End Method
+
+	Method loadMap(mapNumber:Int)
+		Local stream:TStream = ReadStream("littleendian::map"+mapNumber)
+		If stream = Null
+			Return
+		EndIf
+		Local x,y;
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1
+				map[x,y] = Readint(stream)
+			Next
+		Next		
+		startPos.x = Readint(stream)
+		startPos.y = Readint(stream)
+		endPos.x = Readint(stream)
+		endPos.y = Readint(stream)
+		CloseStream stream
+	End Method
+
+
+' Handle mouse click
+	Method handleMouseDown(blockX:Int, blockY:Int)
+
+		If blockX = oldBlockX And blockY = oldBLockY
+			setRedraw(False)
+			Return
+		EndIf
+		oldBlockX = blockX
+		oldBlockY = blockY
+
+' Make sure they can't edit over the start and end positions
+		If startPos.isAtPos(blockX, blockY)
+			setRedraw(False)
+			Return
+		EndIf
+		If endPos.isAtPos(blockX, blockY)
+			setRedraw(False)
+			Return
+		EndIf
+
+' See if they are selecting a new terrain
+		If blockX = terrainLegendBlockX And blockY >= terrainLegendBlockY And blockY < terrainLegendBlockY + numTerrainTypes
+			currentTerrainIndex = blockY - terrainLegendBlockY 
+			Return
+		EndIf
+
+		If blockInMapArea(blockX, blockY)
+			map[blockX, blockY] = currentTerrainIndex
+			setRedraw(True)
+			Return
+		EndIf
+
+' See if we have selected a new map
+		If blockX >= mapButtonBlockX And blockY = mapButtonBlockY
+			Local block:Int = blockX - mapButtonBlockX
+			Select block
+				Case 8
+					message = saveMap(currentMap)
+				Case 9
+					handleHeuristic()
+				Default
+					If block < 8 
+						currentMap = block
+						loadMap(currentMap)
+					EndIf
+			End Select
+		EndIf
+		setRedraw(True)
+		Return			' Signal that we've moved the mouse and done something
+	End Method
+
+
+	Method handleHeuristic()
+		Local oldX:Int = MouseX()
+		Local oldY:Int = MouseY()
+
+		While MouseDown(1)
+			Local x:Int = MouseX()
+			Local y:Int = MouseY()
+			If  x <> oldX
+				heuristicMultiplier = heuristicMultiplier + (x - oldX) / 10.0
+				If heuristicMultiplier < 0 heuristicMultiplier = 0
+				If heuristicMultiplier > 10 heuristicMultiplier = 10
+				runAStar()
+				redraw(2)
+				oldX:Int = x
+				oldY:Int = y
+			EndIf
+		Wend
+	End Method
+
+' Are the block coordinates with in the map area?
+	Method blockInMapArea(blockX:Int, blockY:Int)
+		Return blockX >= 0 And blockY >= 0 And blockX < mapWidth And blockY < mapHeight 
+	End Method
+
+
+	Method drawPath()
+		If path <> Null
+			Local a:Object
+			For a:Object = EachIn path
+				Local node:AStarNode = AStarNode(a)
+				SetColor 255,255,0
+				DrawRect node._x * blockWidth, node._y * blockHeight, blockWidth - 1, blockHeight - 1
+			Next
+		EndIf
+	End Method
+
+' Draw the information text in the editor
+	Method drawInfo()
+' Clear 
+		Local y = mapButtonBlockY * blockHeight + blockHeight
+		SetColor 0,0,0
+		DrawRect 0, y, blockAreaWidth, blockHeight*5
+
+' Draw the distance function used
+		SetColor 255,255,255
+		DrawText distanceNames[distanceFunction], 0, y 
+
+' Whether diagonals are allowed or not
+		Local path:String
+		If allowDiagonalPaths
+			path = "(A) Diagonals allowed"
+		Else
+			path = "(A) Diagonals not allowed"
+		EndIf
+		DrawText path, 0, y + TextHeight(path)
+		
+' How long last path walk took		
+'		Local percentOfFrame:Float = lastRunTime * (60.0/1000.0) * 100.0
+'		DrawText "Milli:" + lastRunTime + "   % of 60th:" + percentOfFrame, 0, y + FontHeight() * 5
+
+'	
+		Local diagonals:String;
+		If diagonalsAreLonger 
+			diagonals = "(V) Diagonals are longer than straights"
+		Else
+			diagonals = "(V) Diagonals are same as straights"
+		EndIf
+		DrawText diagonals, 0, y + TextHeight(diagonals) * 2
+
+		DrawText "Heuristic:" + heuristicMultiplier, 0, y + TextHeight(heuristicMultiplier) * 4
+		SetColor 255,0,0
+		DrawText message, 0, y + TextHeight(message) * 5
+		SetColor 255,255,255
+
+' Draw help
+		Local a:String
+		Local count:Int = 0
+		For a:String = EachIn help
+			DrawText a, blockAreaWidth, blockAreaHeight / 4 + count * TextHeight(a)
+			count = count + 1
+		Next
+	End Method
+
+	Method drawMapButtons()
+		Local y = mapButtonBlockY * blockHeight
+		Local x = mapButtonBlockX * blockWidth
+		Local startX = x
+		Local t
+		Local output:String
+		For t = 0 To 9
+			If t < 8 
+				SetColor 255,255,255
+				output = t
+			Else If t = 8
+				SetColor 255,0,0
+				output = "S"
+			Else If t = 9
+				SetColor 0,255,0
+				output = "H"
+			EndIf
+			DrawRect x, y, blockWidth - 1, blockHeight
+			SetColor 0,0,0
+			DrawText output, x, y
+			x = x + blockWidth
+		Next
+' Current map
+		y = mapButtonBlockY * blockHeight  - blockHeight
+		x = mapButtonBlockX * blockWidth
+		SetColor 0,0,0
+		DrawRect x,y,blockAreaWidth,TextHeight(" ")
+		SetColor 255,255,255
+		DrawText "Current map:" +  currentMap, x, y
+
+	End Method
+
+	Method drawTerrains()
+		Local t
+		For t = 0 To numTerrainTypes - 1
+			Local terrain:Terrain = terrains[t]
+			Local x = terrainLegendBlockX * blockWidth
+			Local y = (terrainLegendBlockY + t)* blockHeight
+
+			SetColor 0,0,0
+			DrawRect x, y, blockWidth * 5, blockHeight
+
+			SetColor 255,255,255
+			SetScale blockWidth / 32.0, blockHeight / 32.0
+			DrawImage terrain._image, x, y
+			SetScale 1,1
+	
+			If showCosts
+				SetColor 255,255,255
+				DrawText terrain._weight, x + blockWidth , y
+			EndIf
+	Next
+	End Method
+
+	Method drawMap()
+		SetColor 0,0,0
+		DrawRect 0, 0, blockAreaWidth, blockAreaHeight
+		Local x,y;
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1
+				drawMapBlock(x,y)
+			Next
+		Next		
+	End Method
+
+	Method drawMapBlock(x:Int, y:Int)
+		SetColor 255,255,255
+		If startPos.isAtPos(x,y)
+			SetColor 255,255,0
+		Else If endPos.isAtPos(x,y)
+			SetColor 255,0,0
+		Else
+'			SetColor(terrains[map[x,y]]._colour)
+		EndIf
+		SetScale blockWidth / 32.0, blockHeight / 32.0
+		DrawImage terrains[map[x,y]]._image, x * blockWidth, y * blockHeight
+		SetScale 1,1
+	End Method
+
+	Method drawOnMap(x:Int, y:Int, r,g,b, margin:Int)
+		SetColor r,g,b
+		DrawRect x * blockWidth + margin, y * blockHeight + margin, blockWidth - margin * 2, blockHeight - margin * 2
+	End Method
+
+	Method printOnMap(x:Int, y:Int, text:String, offset:Int)
+		SetColor 255,255,255
+		SetScale textScale,textScale
+		DrawText text, x * blockWidth, y * blockHeight + offset
+		SetScale 1,1
+	End Method
+
+	Method printNums(s:String, x:Int, y:Int, offset:Int)
+		x = x * blockWidth + blockWidth / 2 - (Len(s) * 5)/2
+		y = y * blockHeight + offset
+		SetColor 255,255,255
+		SetMaskColor 255,0,255
+		SetScale 1,1
+		Local t
+		For t = 0 To Len(s) - 1
+			DrawImage nums, x, y, Byte(s[t]) - 46
+			x:+5
+		Next
+	End Method
+
+
+' Initialise the map and edit stuff
+	Method initialise()
+	
+'		blockWidth = blockAreaWidth / mapWidth
+'		blockHeight = blockAreaHeight / mapHeight
+
+' Setup other interactive pieces
+		terrainLegendBlockX = mapWidth + 1
+		terrainLegendBlockY = 0
+	
+'		map = Array:Int[mapWidth, mapHeight]
+'		nodeMap = Array:AStarNode[mapWidth, mapHeight]
+
+' Initialise terrain types
+		Local t
+		For t = 0 To numTerrainTypes - 1
+			Local newTerrain:Terrain = New Terrain
+			newTerrain.set(terrainWeights[t], terrainFilenames[t])
+			terrains[t] = newTerrain
+		Next
+
+		startPos = New MapPos
+		startPos.x = 1
+		startPos.y = 1
+	
+		endPos = New MapPos
+		endPos.x = mapWidth - 2
+		endPos.y = mapHeight - 2
+		
+' Initialise the map
+		SeedRnd MilliSecs()
+		Local y
+		Local x
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1
+				Local value:Int = 0'Rand(0,numTerrainTypes - 1)
+
+				If y = 0 Or y = mapHeight - 1 Or x = 0 Or x = mapWidth - 1
+					value = 1
+				EndIf
+
+				map[x,y] = value 'Readint(stream)
+			Next
+		Next
+	End Method
+
+' This runs AStar with the current setup
+
+	Method runAStar()
+
+' The first thing you need to do before using the AStarGraphWalker is to create your nodes, and link them up
+' with edges. What I do is first make an array of the nodes, this makes it easy to map nodes to map blocks if
+' I need to and other things.
+' Then I go over the node array and create links to the neighbouring nodes.
+'
+' NOTE: In my implementation you will notice that between two blocks, two edges are created.
+' Block 1 has an edge to block 2 (which is it's neighbour) and viceversa. If you wanted to
+' simplify this, you could, but you'd have to change the datastructure a little. I prefer my way (Except of course 
+' that more memory is consumed) because I can have different costs to get from A to B than from B to A, for example
+' if A was at the top of a hill and B at the bottom. In that case, going from A to B is generally easier than B to A
+		Local x:Int
+		Local y:Int
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1
+				nodeMap[x,y] = New AStarNode
+				Local node:AStarNode = nodeMap[x,y]
+				node._x = x			' AStar needs these positions for distance estimation
+				node._y = y
+			Next
+		Next
+' This is just an array of offsets to give us our neighbours
+		Const offsetCount:Int = 8
+		Local xOffset:Int[] = [ 0,1,1,1,0,-1,-1,-1 ]
+		Local yOffset:Int[] = [ -1,-1,0,1,1,1,0,-1 ]
+
+' How we move through the offsets, so we can support "no diagnoal" paths
+		Local offsetStep:Int = 1
+		If Not allowDiagonalPaths
+			offsetStep = 2
+		EndIf
+
+' This loop builds up all the neighbour lists for a node		
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1		
+				If map[x,y] = 1
+					Continue		' We don't worry about this node if we aren't passable
+				EndIf
+				Local node:AStarNode = nodeMap[x,y]
+' Now look around the map for neighbours and make nodes 
+' Joining the current one with a neighbour
+				Local off = 0
+				While off < offsetCount
+					Local neighbourX = x + xOffset[off]
+					Local neighbourY = y + yOffset[off]		
+' Check that the neighbour position is within the map bounds and is actually
+' not block 1 which I've designated as a block we can't go through at all so no point
+' making a neighbour of it
+					If map[neighbourX, neighbourY] <> 1 And neighbourX >= 0 And neighbourX < mapWidth And neighbourY >=0 And neighbourY < mapHeight
+						Local value:Int = terrains[map[x,y]]._weight
+						Local neighbourValue:Int = terrains[map[neighbourX,neighbourY]]._weight
+
+' Here is where you calculate the costs of the edge between two nodes. Because our map is square and the neighbour
+' I'm looking at is adjacent to the current node, then I can just take an average of the costs of each block.
+' E.G If the current block is water, and the neighbour is forest, I'd be walking half in water and half in forest
+' This isn't really necessary but I do it anyway
+						Local edgeCost:Float = (value + neighbourValue) / 2.0
+' Now if it's a diagonal we might want to make the cost of this edge higher to signify that
+' travelling diagonally in a suare environment like ours is a longer distance
+						If diagonalsAreLonger And xOffset[off] <> 0 And yOffset[off] <> 0
+							edgeCost = edgeCost * 1.4;
+						EndIf		
+						node.addNeighbour(nodeMap[neighbourX, neighbourY], edgeCost)
+					EndIf		
+					off = off + offsetStep
+				Wend
+			Next
+		Next		
+
+' We have our node, so we can set up our AStarGraphWalker now.
+		Local walker:AStarGraphWalker = New AStarGraphWalker
+		
+		walker.setParameters(nodeMap[startPos.x,startPos.y], nodeMap[endPos.x,endPos.y], mapWidth * mapHeight)
+		walker.setDistanceFunction(distanceFunction)		' If not called, default is distanceEuclidean
+		walker.setHeuristicMultiplier(heuristicMultiplier)			' If not called, default to 1
+
+' Set up our callback information. The callback is called after one node has been popped and processed
+		Local callback:WalkerCallback = New WalkerCallback
+		callback._walker = walker
+		callback._application = Self
+		If showProgress 
+			walker.setCallback(callback)
+		EndIf
+
+		Local start:Int = MilliSecs()
+		Local res:Int = walker.walk()		
+		Local time:Int = MilliSecs() - start
+
+		If res
+			path = walker.getFinalPath()		
+			If showProgress 
+				drawPath()
+				Flip
+			EndIf
+		Else
+			path = Null
+		EndIf
+'EndRem
+
+' Tidy up after ourselves
+	walker.setCallback(Null)	
+
+' Need to make sure memory gets freed because we have references to objects all over
+' the place
+		For y = 0 To mapHeight - 1
+			For x = 0 To mapWidth - 1		
+				Local node:AStarNode = nodeMap[x,y]				
+				Local neighbour:AStarNeighbourInfo
+				For neighbour:AStarNeighbourInfo = EachIn node.getNeighbours()
+					neighbour.free()
+				Next
+				walker.resetNode(node)
+				nodeMap[x,y] = Null
+			Next
+		Next
+		Return time
+	End Method
+EndType
+
+' This is our personal callback function so that we can draw the map. In a real implementation you might not have this
+' or maybe the callback allows you to check to see if the algorithm is taking too long and so pause for a frame and continue
+' next frame. You'd need some more work that though
+Type WalkerCallback Extends AStarCallback
+
+	Method New()
+		_waitForKey = True			' If ever set to false, then we are ignoring the callback so we just return
+	End Method
+
+	Method callback()
+			If _waitForKey = False 			' If not waiting for key, then we've finished
+				While KeyDown(27) Wend
+				_application.setRedraw(True)
+				Return
+			EndIf
+			Local currentNode:AStarNode = node
+		
+			_application.redraw(1,0)
+			Local x:Int
+			Local y:Int
+
+			For y = 0 To _application.mapHeight - 1
+				For x = 0 To _application.mapWidth - 1
+					Local node:AStarNode = _application.nodeMap[x,y]
+					Local col_r=128,col_g=128,col_b=128
+					Local do:Int = 0;
+					If node.inClosed() Or Not node.inOpen() Then 
+						Continue
+					EndIf
+					If node.inOpen() Then 
+						col_r=0 ; col_g=0 ;col_b=255
+						do = 1
+					EndIf
+					If do Then 
+						_application.drawOnMap(node._x, node._y, col_r, col_g, col_b , 2 )
+						_application.printNums(oneDec(node.costToGetHere()), node._x , node._y, 4 )
+						_application.printNums(oneDec(node._goalDistance), node._x , node._y, 11 )
+						_application.printNums(oneDec(node._key), node._x , node._y, 18 )
+					EndIf
+				Next
+			Next	
+			Flip
+			
+			If KeyHit(KEY_P)
+				WaitKey
+			EndIf
+			If KeyDown(27)
+				_waitForKey = False
+			EndIf
+
+	End Method
+
+' Makes a single decimal place string from a number - ugly....
+	Method oneDec:String(number:Float)
+		Return String(Int(number)) + "." + Int((number-Int(number))*10)
+	End Method
+
+	Field _walker:AStarGraphWalker;
+	Field _application:AStarDemo;
+	Field _waitForKey:Int
+EndType
+
+

+ 259 - 0
samples/aaronkoolen/AStar/astar_graph_walker.bmx

@@ -0,0 +1,259 @@
+' Class for walking an astar graph
+' It's up to you to make the graph yourself.
+
+
+Import "astar_node.bmx"
+Import "priority_queue.bmx"
+Import "Callback.bmx"
+
+' Customised callback class
+Type AStarCallback Extends Callback
+	Field node:AStarNode;
+	Field queue:PriorityQueue
+End Type
+
+Type AStarGraphWalker
+
+' public
+' Constructor
+	Method New()
+		_queue = New PriorityQueue
+		_finalPath = New TList
+	End Method
+
+' public
+' Sets the parameters for walking
+' startNode - The first node in the graph where searching will start from
+' endNode 	- The node you're trying to find the path to
+' maxNodes 	- The maximum number of nodes in the graph you want to walk
+
+	Method setParameters(startNode:AStarNode, endNode:AStarNode, maxNodes:Int )
+		Assert startNode <> Null,"startNode Null"
+		Assert endNode <> Null,"endNode Null"
+		Assert maxNodes > 0,"maxNodes <= 0"
+		_start = startNode;				' The start of the graph
+		_end = endNode;				' The start of the graph
+		_queue.setMaxSize(maxNodes)
+		_parametersSet = True;
+	End Method
+
+' public
+' Sets the callback function
+' callback - A object with a callback() mtehod. Derive from Callback Type
+	Method setCallback(callback:AStarCallback)
+		_callback = callback
+	End Method
+
+	Method setDistanceFunction(func:Int)
+		_distanceFunction = func
+	End Method
+
+	Method setHeuristicMultiplier(heuristic:Float)
+		_heuristicMultiplier = heuristic
+	End Method
+
+' public
+' Returns the final path after a path find
+	Method getFinalPath:TList()
+		Assert _lastWalkSucceeded, "Can't return path as last path walk failed"
+		Return _finalPath
+	End Method
+
+' private
+' Makes the list of successors that will be searched next
+' ARGUMENTS:
+' node 		- The node who's successors we're looking at
+' endNode	- The destination node we're trying to get to
+	Method _makeSuccessors(node:AStarNode, endNode:AStarNode)
+		Local done:Int = False
+		For neighbour:AStarNeighbourInfo = EachIn node.getNeighbours()
+			Local neighbourNode:AStarNode = neighbour.getNode()
+			Assert neighbourNode,"Node is NULL"
+
+' Only look at neighbours that aren't the start node and also aren't in 
+' the closed list (We'd be backtracking)
+			If neighbourNode <> _start And Not neighbourNode.inClosed()
+' Calculate total cost to get to this neighbour based on edge cost to neighbour +
+' current cost to get to us
+				Local cost:Float = neighbour.edgeCost() + node.costToGetHere()
+' Estimate a distance to the goal from the neighbour
+				Local goalDistance:Float = _distanceTo(neighbourNode, endNode)
+' If heuristic was 0 then we'd have an optimal search...
+				goalDistance = goalDistance * _heuristicMultiplier
+' What we guess the total cost would be to get from start to finish through
+' this neighbour
+				Local totalCostOfNeighbourPath:Float = goalDistance + cost
+
+' If we haven't visited this neighbour node yet at all, save it for later visiting				
+' This line used to be	If Not neighbourNode.inClosed() And Not neighbourNode.inOpen()
+' Don't need it as now we have the optimisation above that doesn't enter here if they
+' neighbour is in the closed list
+'				If Not neighbourNode.inClosed()And  Not neighbourNode.inOpen()
+				If Not neighbourNode.inOpen()
+' Assume we'll go from us to neighbour by setting us as the parent
+					neighbourNode.setParent(node)
+' Set the PQueue key as the total cost of this path to the goal
+' Queue is sorted smallest first so we always look at shortest distances
+					neighbourNode.setKey(totalCostOfNeighbourPath)	' Goes in queue based on total distance
+' Save what we calculated that the cost was to get to this neighbour
+					neighbourNode.setCostToGetHere(cost)						' What we consider it's cost is
+					neighbourNode.setGoalDistance(goalDistance)						' What we consider it's cost is
+					neighbourNode.setInOpen(True)
+					_queue.insert(neighbourNode)
+				Else
+' OK, neighbour is in a list (Actually must be in Open list at this point)
+' so see if we have a better path to it by seeing if the cost to get 
+' to this neighbour the other way is more than the cost from our node 
+' to this neighbour. 
+' If it is, then our path is better 
+					If neighbourNode.costToGetHere() > cost 
+' If it was in the closed list, then we're going to put it in the open list
+' cause we want to now be able to look at it again as a possible path
+'						If neighbourNode.inClosed()
+'							neighbourNode.setInClosed(False)
+'						EndIf
+' Above is removed because of optimisation
+						neighbourNode.setParent(node)
+						neighbourNode.setKey(totalCostOfNeighbourPath)	' Goes in queue based on total distance
+						neighbourNode.setGoalDistance(goalDistance)		' Estimate to get to goal
+						neighbourNode.setCostToGetHere(cost)			' What we consider it's cost is
+'TODO: Optimise this. Rather than remove and add, we could shift in the queue if
+' we knew it's index.
+' Removed if below because optimisation allows us to know that we must be in the open list to get here
+'						If neighbourNode.inOpen()
+							pos:Int = _queue.find(neighbourNode)
+							Assert pos > 0, "Was going to remove item that wasn't in queue!"
+							_queue.remove(pos)
+							neighbourNode.setInOpen(False)
+'						EndIf
+						_queue.insert(neighbourNode)
+						neighbourNode.setInOpen(True)						
+					EndIf
+				EndIf
+			EndIf
+'			If _callback <> Null
+'				_callback.node = node
+'				_callback.queue = _queue
+'				_callback.callback()
+'				Flip;WaitKey
+'			EndIf
+		Next
+	End Method
+
+
+' public
+' Method to walk the graph, finding the shortest path
+' 
+' RETURNS:
+' False - Failed to find path to the end node
+' True 	- Found a path to the end
+' PRE: Must have called setParameters first
+	Method walk()
+		Assert _parametersSet,"Must call setParameters() first"
+
+		_lastWalkSucceeded = False
+
+		Local startNode:AStarNode = _start
+		Local endNode:AStarNode =_end
+		
+		' Initialise starting node's information
+		Local distanceToGoal:Float = _distanceTo(_start, _end)
+		startNode.setCostToGetHere(0)
+		startNode.setKey(distanceToGoal * _heuristicMultiplier + startNode.costToGetHere())
+		startNode.setParent(Null)
+		startNode.setInOpen(True)
+		_queue.insert(startNode)
+		
+		While _queue.size() > 0
+			Local node:AStarNode = AStarNode(_queue.remove())
+'			node.setInOpen(False)
+			node.setInClosed(True)
+
+' Have we found our destination???
+			If node = endNode Then
+				Local currentNode:AStarNode = node
+				While currentNode <> Null
+					_finalPath.AddFirst(currentNode)
+					currentNode = currentNode.getParent()
+				Wend
+				_lastWalkSucceeded = True
+				_queue = Null
+				Return True
+			EndIf
+
+			_makeSuccessors(node, endNode)
+			If _callback <> Null
+				_callback.node = node
+				_callback.queue = _queue
+				_callback.callback()
+			EndIf
+' temp
+
+		Wend
+		Return False
+	End Method
+
+' Resets a node so that it's ready for a new path find. Call this from whatever manages the nodes 
+' as AStarGraphWalker, doesn't actually know how many, or what nodes you have, but it does know how
+' to reset one
+	Method resetNode(node:AStarNode)
+		node._parentNode = Null
+		node.setInClosed(False)
+		node.setInOpen(False)
+	End Method
+
+'private
+
+' Returns an estimated distance between two nodes
+	Method _distanceTo:Float(startNode:AStarNode, endNode:AStarNode)
+		Local startX = startNode._x
+		Local startY = startNode._y
+		Local endX = endNode._x
+		Local endY = endNode._y
+		Local dx = Abs(endX - startX)
+		Local dy = Abs(endY - startY)
+'TODO: I had distanceFunction without the _ below and Blitz Didn't complain
+		Select _distanceFunction
+			Case distanceEuclidean
+				Return Sqr( dx * dx + dy * dy )
+			Case distancePseudoEuclidean
+				Return dx * dx + dy * dy
+			Case distanceManhatten
+				Return dx + dy
+			Case distanceDiagonalShortcut
+				If dx > dy
+				     Return 1.4*dy + (dx-dy)
+				Else
+				     Return 1.4*dx + (dy-dx)
+				End If
+			Default
+				Assert 0,"Bad distance function"
+		End Select
+	End Method
+
+' Fields
+
+' Possible ways to calculate distance. It's good to specify the edge costs between nodes
+' relative to your distance calculations because as they are related. For instance, if you calculate edge costs
+' using simple Euclidean distance, so that two adjacent blocks would be 1 away or 1.4 (if diagonal)
+' multiplied by some small "difficulty factor", say 1 for normal roads, or 2 for water
+' Then distanceEuclidean is a good estimator of distance and distancePseudoEuclidean
+' tends to override the edgecosts and the pathfinder sort of busts through them. 
+' This can be a good thing as it could provide a simple way to make a unit "dumber"
+	Const distanceEuclidean = 0
+	Const distancePseudoEuclidean = 1
+	Const distanceManhatten = 2
+	Const distanceDiagonalShortcut = 3
+
+	Field _heuristicMultiplier = 1			' 0 should generate "optimal" path
+	Field _start:AStarNode
+	Field _end:AStarNode
+	Field _distanceMode:Int = distanceEuclidean
+	Field _queue:PriorityQueue
+	Field _parametersSet = False
+	Field _finalPath:TList
+	Field _callback:AStarCallback = Null
+	Field _distanceFunction = distanceEuclidean
+	Field _lastWalkSucceeded = False
+
+EndType

+ 117 - 0
samples/aaronkoolen/AStar/astar_node.bmx

@@ -0,0 +1,117 @@
+' This is an implementation of an AStar Algorithm
+
+Strict
+Import "priority_queue.bmx"
+
+'Private
+' Each AStar node has a list of neighbours that it connects to
+' This is the AStarNeighbourInfo type. AStarNeighbourInfo
+' holds both a reference to the neighbouring nodes, but also information
+' about the cost to get to that node
+Type AStarNeighbourInfo
+	Method getNode:AStarNode()
+		Return _neighbour
+	End Method
+
+	Method edgeCost:Float()
+		Return _edgeCost
+	End Method
+
+	Method free()
+		_neighbour = Null
+	End Method
+' private
+	Field _neighbour:AStarNode
+	Field _edgeCost:Float			' Cost to get to this neighbour	
+EndType
+
+Public
+' These are the nodes in the AStar graph
+Type AStarNode Extends PriorityQueueNode
+' public
+
+' Call this to add a fully constructed neighbour node to this node
+	Method addNeighbour(neighbourNode:AStarNode, edgeCost:Float)
+		Local neighbourInfo:AStarNeighbourInfo = New AStarNeighbourInfo
+
+		neighbourInfo._neighbour 	= neighbourNode
+		neighbourInfo._edgeCost 	= edgeCost
+
+		_neighbours.AddLast(neighbourInfo)
+	End Method
+
+	Method getNeighbours:TList()
+		Return _neighbours
+	End Method
+
+	Method setGoalDistance(goalDistance:Float)
+		_goalDistance = goalDistance
+	End Method
+
+	Method goalDistance:Float()
+		Return _goalDistance
+	End Method
+
+	Method setCostToGetHere(cost:Float)
+		_costFromStart = cost
+	End Method
+
+	Method costToGetHere:Float()
+		Return _costFromStart
+	End Method
+
+' Yes using PTR here is what we want
+	Method setParent(parent:AStarNode)
+		_parentNode = parent
+	End Method
+
+	Method getParent:AStarNode()
+		Return _parentNode
+	End Method
+
+' Sets whether the node is in the open list or not
+	Method setInOpen(inOpen:Int)
+		_inOpen = inOpen
+	End Method
+
+	Method inOpen()
+		Return _inOpen
+	End Method
+
+' Sets whether the node is in the closed list or not
+	Method setInClosed(inClosed:Int)
+		_inClosed = inClosed
+	End Method
+
+	Method inClosed()
+		Return _inClosed
+	End Method
+
+' Initialise new nodes to default values
+	Method New()
+		_costFromStart 	= 0
+		_parentNode		= Null
+		_neighbours 	= New TList
+		_inOpen	 		= False
+		_inClosed 		= False
+		_goalDistance   = 0
+	End Method
+
+' private
+	Field _costFromStart:Float			' We keep track of cost to get to this node
+	Field _goalDistance:Float			' Goal to distance
+	Field _parentNode:AStarNode			' Used to trace back from the end to the start of the finished path
+	Field _neighbours:TList				' The neighbours of this node
+
+	Field _inOpen:Int					' Keep track of what list the node is in
+	Field _inClosed:Int
+
+' TODO:
+' These are required in order to calculate distance
+' What would be better is to provide something like an AStarPos object
+' with a method to get distance between them that way you could provide a more
+' generic mechanism for distanc calculation
+	Field _x:Int
+	Field _y:Int		
+
+End Type

BIN
samples/aaronkoolen/AStar/map0


BIN
samples/aaronkoolen/AStar/map1


BIN
samples/aaronkoolen/AStar/map2


BIN
samples/aaronkoolen/AStar/map3


BIN
samples/aaronkoolen/AStar/map4


BIN
samples/aaronkoolen/AStar/map5


BIN
samples/aaronkoolen/AStar/map6


BIN
samples/aaronkoolen/AStar/map7


BIN
samples/aaronkoolen/AStar/mountain.png


BIN
samples/aaronkoolen/AStar/nums.png


+ 161 - 0
samples/aaronkoolen/AStar/priority_queue.bmx

@@ -0,0 +1,161 @@
+Strict
+'Module PUB.PriorityQueue
+
+'ModuleInfo "Author: Aaron Koolen"
+'ModuleInfo "Version: 1.0"
+
+Rem
+This is an implementation of a priority queue implemented as
+a heap stored in an array.
+Note, it might not be the most optimised code, as I don't know some
+of the BlitzMax tricks that I might be able to do here.
+Let me know if you see anything simple too slow or crappy. 
+End Rem
+
+
+' You should derive your nodes from this class
+'
+Type PriorityQueueNode
+	Method setKey(key:Float)
+		_key = key;
+	End Method
+	Field _key:Float			' I don't think blitz max has the concept of templated types?
+End Type
+
+
+' Main PriorityQueue class
+Type PriorityQueue
+' private
+	Field _contents:PriorityQueueNode[]
+	Field _size:Int = 0
+	Field _sizeSet:Int = 0
+
+' public
+
+' Must call this first before you do anything
+	Method setMaxSize(newSize:Int)
+		Assert newSize > 0, "Must set size of queue > 0"
+		_contents = New PriorityQueueNode[newSize + 1]	' We don't add any, you have to add them
+		_size = 0
+		_sizeSet = newSize
+	End Method
+
+'---------------------------------------
+' Queue manipulation
+'---------------------------------------
+
+' Insert a node into the queue
+	Method insert(newNode:PriorityQueueNode)
+		Assert _sizeSet > 0,"Must call setMaxSize first with size > 0"
+		Assert _size < _sizeSet, "Queue is full" 
+		_size = _size + 1
+		_contents[_size] = newNode
+		_reheapUp(_size)
+	End Method
+
+' Remove and return a node from the queue and re-sort queue
+	Method remove:PriorityQueueNode(index:Int = 1)
+		Local node:PriorityQueueNode = _contents[index]
+		_contents[index] = _contents[_size]
+		_size = _size - 1
+		_reheapDown(index, _size)
+		Return node
+	End Method
+
+'---------------------------------------
+' Queue searching
+'---------------------------------------
+	Method find:Int(node:PriorityQueueNode)
+		Local t
+		For t = 1 To _size
+			If node = _contents[t]
+				Return t
+			EndIf
+		Next
+		Return 0
+	End Method
+' Return the current size of the queue
+	Method size:Int()
+		Return _size
+	End Method
+
+' Helper function for returning the nodes as an ordered list
+	Method returnList:TList()
+		Local list:Tlist = New TList
+		Local a:PriorityQueueNode
+		For a = EachIn _contents
+			list.AddLast(remove(1))		' The '1' here removes the top item
+		Next
+		Return list
+	End Method
+
+'------------------------------------------------
+' PRIVATE
+'------------------------------------------------
+' Reheap an inserted item
+' ARGUMENTS:
+' index - The Index of the newly inserted item
+	Method _reheapUp(index:Int)
+		Local parentIndex = _parent(index)
+		Local ok = False
+		While( index > 1 And ok = False )
+			If _contents[parentIndex]._key < _contents[index]._key Then
+				ok = True
+			Else
+				Local temp:PriorityQueueNode = _contents[parentIndex]
+				_contents[parentIndex] = _contents[index]
+				_contents[index] = temp
+				index = parentIndex
+				parentIndex = _parent(index)
+			EndIf
+		Wend
+		Return index
+	End Method
+
+' Reheaps downward - Called after a delete of a node
+' ARGUMENTS:
+' root 		- Index of the root (Top of tree) item
+' bottom 	-  The index of the last item in the tree
+	Method _reheapDown(root:Int, bottom:Int)
+		Local ok = False
+		Local maxChild = 0
+		
+		While _left(root) <= bottom And ok = False
+			If _left(root) = bottom
+				maxChild = _left(root)
+			Else
+				If _contents[_left(root)]._key < _contents[_right(root)]._key 
+					maxChild = _left(root)
+				Else
+					maxChild = _right(root)
+				EndIf				
+			EndIf
+			If Not (_contents[root]._key < _contents[maxChild]._key) Then
+				Local t:PriorityQueueNode = _contents[root]
+				_contents[root] = _contents[maxChild]
+				_contents[maxChild] = t
+				root = maxChild
+			Else
+				ok = True
+			EndIf
+		Wend		
+		Return root
+	End Method
+
+' Returns the index of the parent node to a child node
+	Method _parent:Int(childIndex:Int)
+		Return childIndex / 2
+	End Method
+
+' Returns the index of the left child of a node
+	Method _left:Int(siblingIndex:Int)
+		Return siblingIndex * 2
+	End Method
+
+' Returns the index of the right child of a node
+	Method _right:Int(siblingIndex:Int)
+		Return siblingIndex * 2 +1
+	End Method
+
+End Type
+

BIN
samples/aaronkoolen/AStar/road.png


BIN
samples/aaronkoolen/AStar/tree.png


BIN
samples/aaronkoolen/AStar/water.png


+ 443 - 0
samples/birdie/games/tempest/tempest.bmx

@@ -0,0 +1,443 @@
+'Tempest
+'Coded by David Bird
+Strict
+
+Const CWidth#=640
+Const CHeight#=480
+Const K# = 50
+
+Global CCenterX#=CWidth/2.0
+Global CCenterY#=(CHeight/3.0)'*2
+
+Global SHOT_LIST:TList = New TList
+Global theLevel:Level
+
+Function TFormSZ#(x#, z#)
+  z:+5
+  Return (x/(z/K))
+EndFunction
+
+Function TForm(x#, y#, z#, x2d# Var, y2d# Var )
+  z:+5
+  y:+100
+  x2d = CCenterX+(x/(z/K))
+  y2d = CCenterY+(y/(z/K))
+EndFunction
+
+'Setup Graphics mode
+Graphics CWidth,CHeight,32
+
+HideMouse
+
+
+Global MainPlayer:Player
+
+Type Player
+  Field e_Index        'the edge the player is on
+  Field zPos
+
+  Field scl#=0.5          'where on the edge 0-1
+
+  Method SetEdge(index)
+    e_index = index
+  EndMethod
+
+  Method AddShot()
+    Shot.Create(theLevel.edges[e_Index])
+  EndMethod
+
+  Method ShiftLeft()
+    If e_Index=0
+      e_Index = theLevel.e_Cnt-1
+    Else
+      e_Index:-1
+    EndIf
+  EndMethod
+
+  Method ShiftRight()
+    If e_Index=theLevel.e_cnt-1
+      e_Index = 0
+    Else
+      e_Index:+1
+    EndIf
+  EndMethod
+
+  Method Update()
+    'Control it
+    If KeyHit(KEY_SPACE) self.AddShot()
+
+    If KeyDown(KEY_LEFT)
+      scl:-0.1
+      If scl<0 Then
+        self.ShiftLeft()
+        scl:+1
+      EndIf
+    EndIf
+    If KeyDown(KEY_RIGHT)
+      scl:+0.1
+      If scl>1 Then
+        self.ShiftRight()
+        scl:-1
+      EndIf
+    EndIf
+    SetRotation 0
+    'Draw it
+    SetColor 255,255,0
+    Local zz#
+    Local x#[3],y#[3]
+    Select theLevel.state
+      Case Level_Begin
+        zz# = theLevel.position
+      Case Level_Ready
+        zpos=theLevel.position
+        zz = zpos
+      Case Level_Complete
+        zz = zpos
+    EndSelect
+
+
+    Local zh# = 4
+    Local e:Edge = theLevel.edges[e_Index]
+    TForm e.p1.x, e.p1.y, zz,x[0],y[0]
+
+    TForm e.p2.x+ ( (e.p1.x - e.p2.x) * scl ), e.p2.y+ ( (e.p1.y - e.p2.y) * scl ),zz-zh,x[1],y[1]
+
+    TForm e.p2.x, e.p2.y, zz,x[2],y[2]
+    DrawLine x[0],y[0],x[1],y[1]
+    DrawLine x[1],y[1],x[2],y[2]
+  EndMethod
+
+
+  Function Create:Player()
+    Local p:Player = New Player
+
+    Return p
+  EndFunction
+EndType
+
+Type Point
+  Field x#,y#
+  Field e0:edge
+  Field e1:edge
+
+  Function Create:Point( x#, y# )
+    Local p:Point = New Point
+    p.x=x
+    p.y=y
+    Return p
+  EndFunction
+
+EndType
+
+Type Edge
+  Field p1:point
+  Field p2:point
+
+  Field xx#,yy#
+
+  Method Draw( zd1#, zd2# )
+    If zd1<1 zd1=1
+    If zd2<1 zd2=1
+
+    'draw the edge at zero position,
+    'the depth line and the far point
+    Local x#[4],y#[4]
+    TForm p1.x,p1.y,zd1, x[0],y[0]
+    TForm p1.x,p1.y,zd2, x[1],y[1]
+
+    TForm p2.x,p2.y,zd1, x[2],y[2]
+    TForm p2.x,p2.y,zd2, x[3],y[3]
+
+    DrawLine x[0],y[0],x[1],y[1]
+    DrawLine x[0],y[0],x[2],y[2]
+    DrawLine x[1],y[1],x[3],y[3]
+    DrawLine x[3],y[3],x[2],y[2]
+  EndMethod
+
+  Function Create:Edge(p1:Point, p2:Point)
+    Local e:Edge = New edge
+
+    'assign the points
+    e.p1=p1
+    e.p2=p2
+
+    'linkem up
+    p1.e1=e
+    p2.e0=e
+    
+    'store the midpoint for speeding up
+    e.xx =( ( p2.x - p1.x ) / 2.0 ) + p1.x
+    e.yy =( ( p2.y - p1.y ) / 2.0 ) + p1.y
+
+    Return e
+  EndFunction
+EndType
+
+Const Level_Begin    = 0
+Const Level_Complete = 1
+Const Level_Ready    = 2
+
+Type Level
+  Field depth#    = 400
+  Field position# = 1500
+  Field move#     = 0
+  Field state     = Level_Begin
+
+  Field points:TList
+  Field edges:Edge[10],e_cnt,e_cap=10
+
+  Method AddPoint:Point(x#,y#)
+    Local p:Point = Point.Create( x, y )
+    points.AddLast( p )
+    Return p
+  EndMethod
+
+  Method AddEdge:Edge( p1:Point, p2:point )
+    Local e:Edge = Edge.Create( p1, p2 )
+    If e_cnt>=e_cap
+      e_cap:+10
+      edges=edges[..e_cap]
+    EndIf
+    edges[e_cnt] = e
+    e_cnt:+1
+    Return e
+  EndMethod
+
+  Method Update()
+    Select state
+      Case Level_Begin
+        If position>50
+          position:-10
+        Else
+          state=Level_Ready
+        EndIf
+      Case Level_Ready
+
+      Case Level_Complete
+        position:-10
+    EndSelect
+
+  EndMethod
+
+  Method Draw()
+    Local a=0
+    Select state
+      Case Level_Begin
+
+      Case Level_Ready
+
+      Case Level_Complete
+    EndSelect
+    SetRotation 0
+    SetColor 0,0,100
+    For a=0 Until e_cnt
+      edges[a].Draw(position,position+depth)
+    Next
+  EndMethod
+
+  Function Create:Level()
+    Local l:Level = New Level
+    l.points = New TList
+
+    Return l
+  EndFunction
+EndType
+
+Type Shot
+  Field e:edge  ' the edge its on
+  Field z#      ' its position
+  Field r#      ' rotation
+  Field xx#,yy#
+
+  Method Draw()
+    SetColor Rand(255),Rand(255),Rand(255)
+    Local zz = z+theLevel.position
+    Local sz = TFormSZ(10,zz)
+    Local pxx#,pyy#
+
+    TForm(xx,yy,zz,pxx,pyy)
+
+    For Local a=0 Until 360 Step 45
+      SetRotation r+a
+      DrawLine pxx+sz,pyy,pxx-sz,pyy
+    Next
+    r:+15
+    SetRotation 0
+  EndMethod
+
+  Method Update()
+    z:+5
+    Local bad:Baddies
+    If z>theLevel.depth
+      SHOT_LIST.Remove(Self)
+      Return
+    Else
+      'check for collisions
+      For bad = EachIn BaddieList
+        If bad.CheckColl(e,z)
+          SHOT_LIST.Remove(Self)
+          Return
+        EndIf
+      Next
+    EndIf
+
+  EndMethod
+
+  Function Create:Shot(e:Edge)
+    Local ns:Shot = New Shot
+    ns.e = e
+    ns.xx =e.xx
+    ns.yy =e.yy
+    ns.z  = -5
+    SHOT_LIST.AddLast( ns )
+    Return ns
+  EndFunction
+EndType
+
+Function UpdateShots()
+  Local s:shot
+  For s=EachIn SHOT_LIST
+    s.Update
+  Next
+  For s=EachIn SHOT_LIST
+    s.Draw()
+  Next
+EndFunction
+
+Global BaddieList:Tlist = New TList
+
+Function UpdateBaddies()
+  Local b:Baddies
+  For b = EachIn Baddielist
+    b.Update()
+  Next
+  For b = EachIn Baddielist
+    b.Draw()
+  Next
+EndFunction
+
+Type Baddies
+  Field OnEdge:Edge
+
+  Method Update() Abstract
+  Method Draw() Abstract
+  Method CheckColl(e:edge,z#) Abstract
+EndType
+
+Type Crawler Extends Baddies
+  Field EdgeIndex 'used to traverse theLevel edgelist
+  Field typ       '0 just slide up the tube
+                  '1 rolls round the tube
+
+  Field Pause     'the pause before changing edge
+  Field dir       'direction left or right
+  Field angle     'the angle when changing lanes
+
+  Method Update()
+  EndMethod
+  Method Draw()
+  EndMethod
+  Method CheckColl(e:edge,z#)
+  EndMethod
+
+  Function Create:Crawler(index,typ = 0)
+    Local c:Crawler = New Crawler
+    c.OnEdge = theLevel.edges[index]
+    c.typ    = typ
+    BaddieList.AddLast c
+    Return c
+  EndFunction
+EndType
+
+Type Spikes Extends Baddies
+  Field height#=0
+  Field grow_speed#
+
+  Method CheckColl(e:edge,z#)
+    'check to see if any will hit
+    Local sp# = theLevel.Depth-height
+    If e = OnEdge
+      If z>sp
+        height:-40
+        If height<0
+          BaddieList.Remove(Self)
+          Return True
+        EndIf
+        Return True
+      EndIf
+    EndIf
+    Return False
+  EndMethod
+
+  Method Draw()
+    Local xx#,yy#,zz1#,zz2#
+    zz1 = theLevel.position+theLevel.depth
+    zz2 = zz1 - height
+
+    Local x#[2],y#[2]
+    xx =( ( OnEdge.p2.x - OnEdge.p1.x ) / 2.0 ) + OnEdge.p1.x
+    yy =( ( OnEdge.p2.y - OnEdge.p1.y ) / 2.0 ) + OnEdge.p1.y
+    TForm(xx,yy,zz1,x[0],y[0])
+    TForm(xx,yy,zz2,x[1],y[1])
+    SetColor 0,255,0
+    DrawLine x[0],y[0],x[1],y[1]
+    SetColor 255,0,0
+    Plot x[1],y[1]
+  EndMethod
+
+  Method Update()
+    If height<theLevel.Depth
+      height:+grow_speed
+    Else
+      height = theLevel.Depth
+    EndIf
+  EndMethod
+
+  Function Create:Spikes(index,speed#)
+    Local s:Spikes = New Spikes
+    s.OnEdge=theLevel.Edges[index]
+    s.height=10
+    s.grow_speed# = speed
+    BaddieList.AddLast s
+    Return s
+  EndFunction
+EndType
+
+
+Local a
+Local p1:Point
+Local p2:Point
+Local fp:Point
+theLevel = Level.Create()
+MainPlayer = Player.Create()
+
+For a=0 Until 360 Step 30
+  p1 = theLevel.AddPoint( Cos(a)*280, Sin(a)*180 )
+  If p2
+    theLevel.AddEdge(p1,p2)
+  Else
+    fp = p1
+  EndIf
+  p2=p1
+Next
+Local lastedge:edge = theLevel.AddEdge(fp,p2)
+MainPlayer.SetEdge( 0 )
+
+For a=0 Until 10
+  spikes.Create( a ,Rnd(0.5,1.0))
+Next
+
+'main loop
+While Not KeyDown(KEY_ESCAPE)
+  Cls
+  theLevel.Update()
+  theLevel.Draw()
+  UpdateShots()
+  UpdateBaddies()
+
+  MainPlayer.Update()
+
+  Flip
+Wend
+End
+

BIN
samples/birdie/games/tiledrop/media/back.png


BIN
samples/birdie/games/tiledrop/media/blocks.png


BIN
samples/birdie/games/tiledrop/media/part.png


BIN
samples/birdie/games/tiledrop/media/pointer.PNG


BIN
samples/birdie/games/tiledrop/media/shine.png


+ 373 - 0
samples/birdie/games/tiledrop/tiledrop.bmx

@@ -0,0 +1,373 @@
+Strict
+Incbin "media/back.png"
+Incbin "media/blocks.png"            
+Incbin "media/part.png"
+Incbin "media/pointer.PNG"
+Incbin "media/shine.png"
+
+Graphics 640,480
+Global backIm:TImage = LoadImage("incbin::media/back.png")
+AutoMidHandle 1
+Global blocks:TImage = LoadAnimImage("incbin::media/blocks.png",32,32,0,16)
+Global partImg:TImage = LoadImage("incbin::media/part.png")
+Global mousePoint:TImage= LoadImage("incbin::media/pointer.PNG")
+Global shine_img:TImage = LoadImage("incbin::media/shine.png")
+AutoMidHandle 0
+Global Map[8,14]
+Global t1x,t1y,t2x,t2y
+Global mouse_left_state
+Global selection_done
+Global rotation# = 0
+Global Center_X#
+Global Center_Y#
+Global mt1,mt2
+Global Tile_Rad#
+Global THE_Axis
+Global FLIPSPEED=8
+Global Scn_Flash#=0
+Global shine_pos#=0
+
+HideMouse
+While Not KeyDown(KEY_ESCAPE)
+  Cls
+  DrawLayout()
+  If selection_done =0
+    FillGrid()
+  EndIf
+
+  DrawGrid()
+
+  If selection_done
+    do_swap_tiles()
+  Else
+    UpdateSelection()
+  EndIf
+
+  SetBlend MASKBLEND
+  DrawText mouse_left_state,0,0
+  DrawText t1x+","+t1y,0,12
+  DrawText t2x+","+t2y,0,23
+  DrawImage mousePoint,MouseX(),MouseY()
+  Flip
+Wend
+
+End
+Function UpdateSelection()
+  Local x=MouseX()-192
+  Local y=MouseY()-24
+  Local dx,dy
+
+  If MouseDown(1)
+    Select mouse_left_state
+      Case 0
+        If x>=0 And x<256
+          If y>=0 And y<448
+            'select tile1
+            t1x=Floor(x/32.0)
+            t1y=Floor(y/32.0)
+          EndIf
+        EndIf
+        'get tile 1
+        mouse_left_state=1
+      Case 2
+        If x>=0 And x<256
+          If y>=0 And y<448
+            'select tile1
+            t2x=Floor(x/32.0)
+            t2y=Floor(y/32.0)
+          EndIf
+        EndIf
+        mouse_left_state=3
+    EndSelect
+  Else
+    Select mouse_left_state
+      Case 1
+        mouse_left_state=2
+      Case 3
+        'check that only 1 tile away and not diag
+        dx=Abs(t2x-t1x)
+        dy=Abs(t2y-t1y)
+        If dx=1 And dy=0
+'          Switch(t1x,t1y,t2x,t2y)
+          selection_done=1
+        ElseIf dx=0 And dy=1
+'          Switch(t1x,t1y,t2x,t2y)
+          selection_done=1
+        EndIf
+          mouse_left_state=0
+      Case 99
+        mouse_left_state=0
+    EndSelect
+  EndIf
+
+EndFunction
+
+'add tiles to array at the top
+Function FillGrid()
+  Local x,y,tl,tlc
+  For x=0 Until 8
+    If map[x,0]=0
+      map[x,0]=1+Rnd(1)*7'15
+    EndIf
+  Next
+  'Fall
+  For y=12 To 0 Step -1
+    For x=0 Until 8
+      If map[x,y]>0
+        If map[x,y+1]=0
+          map[x,y+1]=map[x,y]
+          map[x,y]=0
+        EndIf
+      EndIf
+    Next
+  Next
+
+  For y=0 Until 14
+    For x=0 Until 8
+      tl=map[x,y]
+      If tl>0
+        If Counttile(x,y,tl,0,tlc)
+          KillTiles(x,y,tlc,0)
+        ElseIf Counttile(x,y,tl,1,tlc)
+          KillTiles(x,y,tlc,1)
+        ElseIf Counttile(x,y,tl,2,tlc)
+          KillTiles(x,y,tlc,2)
+        ElseIf Counttile(x,y,tl,3,tlc)
+          KillTiles(x,y,tlc,3)
+        EndIf
+
+      EndIf
+    Next
+  Next
+EndFunction
+
+Function KillTiles(x,y,c,dir)
+  Local d
+  For d=0 Until c
+    Select dir
+      Case 0
+        map[x,y]=0
+        y:-1
+      Case 1
+        map[x,y]=0
+        x:+1
+      Case 2
+        map[x,y]=0
+        y:+1
+      Case 3
+        map[x,y]=0
+        x:-1
+    EndSelect
+  Next
+EndFunction
+
+Function CountTile(x,y,ty,dir, cn Var)
+  cn = 0
+  Select dir
+    Case 0 ' Up
+      While y>0
+        If map[x,y]=ty
+          cn:+1
+        Else
+          Return cn>2
+        EndIf
+        y=y-1
+      Wend
+      Return cn>2
+    Case 1 ' Right
+      While x<8
+        If map[x,y]=ty
+          cn:+1
+        Else
+          Return cn>2
+        EndIf
+        x:+1
+      Wend
+      Return cn>2
+    Case 2 ' Down
+      While y<14
+        If map[x,y]=ty
+          cn:+1
+        Else
+          Return cn>2
+        EndIf
+        y:+1
+      Wend
+      Return cn>2
+    Case 3 ' Left
+      While x>0
+        If map[x,y]=ty
+          cn:+1
+        Else
+          Return cn>2
+        EndIf
+        x:-1
+      Wend
+      Return cn>2
+  EndSelect
+  cn = 0
+  Return 0
+EndFunction
+
+Function DrawGrid()
+  SetColor 255,255,255
+  Local x,y
+  For y=0 Until 14
+    For x=0 Until 8
+      If selection_done
+        If map[x,y]>0
+          If (x=t1x And y=t1y) Or (x=t2x And y=t2y)
+          Else
+            SetBlend MASKBLEND
+            DrawImage blocks,208+x*32,32+y*32,map[x,y]-1
+          EndIf
+        EndIf
+      Else
+        If map[x,y]>0
+          DrawImage blocks,208+x*32,32+y*32,map[x,y]-1
+        EndIf
+      EndIf
+    Next
+  Next
+  SetViewport 0,0,640,480
+EndFunction
+
+Function DrawLayout()
+  Cls
+  If Scn_Flash<0
+    Scn_Flash:+0.2
+  Else
+    Scn_Flash=0
+  EndIf
+    SetBlend SOLIDBLEND
+    SetColor 255,255,255
+  TileImage backIm,0,0
+  SetColor 128,128,128
+  SetViewport 192,24,256,448
+  Cls
+  TileImage backIm,0,0
+  If shine_pos#=0  SetViewport 0,0,640,480
+EndFunction
+
+Function CausesPop()
+  Local x,y,tl,tlc
+  For y=0 Until 14
+    For x=0 Until 8
+      tl=map[x,y]
+      If tl>0
+        If Counttile(x,y,tl,0,tlc)
+          Return 1
+        ElseIf Counttile(x,y,tl,1,tlc)
+          Return 1
+        ElseIf Counttile(x,y,tl,2,tlc)
+          Return 1
+        ElseIf Counttile(x,y,tl,3,tlc)
+          Return 1
+        EndIf
+      EndIf
+    Next
+  Next
+  Return 0
+EndFunction
+
+Function Switch(x0,y0,x1,y1)
+  Local a
+  a=map[x0,y0]
+  map[x0,y0]=map[x1,y1]
+  map[x1,y1]=a
+  If CausesPop() Return 1
+  a=map[x0,y0]
+  map[x0,y0]=map[x1,y1]
+  map[x1,y1]=a
+  Return 0
+EndFunction
+
+Function Do_Swap_Tiles()
+  Local x1,x2,y1,y2
+  Local size1#,size2#
+  Select selection_done
+    Case 1
+      'setup tile swap
+      rotation=180
+      selection_done = 2
+      'calculate center
+      x1 = 208+t1x*32
+      x2 = 208+t2x*32
+      y1 = 32+t1y*32
+      y2 = 32+t2y*32
+      Center_X = ((x2-x1)/2)+x1
+      Center_Y = ((y2-y1)/2)+y1
+      Tile_Rad = 16
+      'check side
+      If t1y=t2y
+        If t1x>t2x
+          mt1 = map[t2x,t2y]
+          mt2 = map[t1x,t1y]
+        Else
+          mt1 = map[t1x,t1y]
+          mt2 = map[t2x,t2y]
+        EndIf
+      Else
+        If t1y>t2y
+          mt1 = map[t2x,t2y]
+          mt2 = map[t1x,t1y]
+        Else
+          mt1 = map[t1x,t1y]
+          mt2 = map[t2x,t2y]
+        EndIf
+      EndIf
+
+      THE_Axis = 1
+      If t1y=t2y THE_Axis = 0
+    Case 2
+      If rotation<0
+        If Switch(t1x,t1y,t2x,t2y)
+          selection_done = 0
+        Else
+          selection_done = 3
+        EndIf
+      Else
+        rotation:-FLIPSPEED
+        size2# = 0.80+0.20*Sin(rotation)
+        size1# = 0.80+0.20*Sin(-rotation)
+        Select THE_Axis
+          Case 0 'x
+            SetScale size1,size1
+            DrawImage blocks, Center_X+Tile_Rad*Cos(rotation), Center_Y, mt1-1
+            SetScale size2,size2
+            DrawImage blocks, Center_X-Tile_Rad*Cos(rotation), Center_Y, mt2-1
+            SetScale 1,1
+          Case 1 ' y
+            SetScale size1,size1
+            DrawImage blocks, Center_X, Center_Y+Tile_Rad*Cos(rotation), mt1-1
+            SetScale size2,size2
+            DrawImage blocks, Center_X, Center_Y-Tile_Rad*Cos(rotation), mt2-1
+            SetScale 1,1
+        EndSelect
+      '  DrawImage blocks,192+t2x*32,24+t2y*32,mt2
+      EndIf
+    Case 3 '
+      If rotation>=180
+        selection_done = 0
+      Else
+        rotation:+FLIPSPEED
+        size2# = 0.80+0.20*Sin(rotation)
+        size1# = 0.80+0.20*Sin(-rotation)
+        Select THE_Axis
+          Case 0 'x
+            SetScale size1,size1
+            DrawImage blocks, Center_X+Tile_Rad*Cos(rotation), Center_Y, mt1-1
+            SetScale size2,size2
+            DrawImage blocks, Center_X-Tile_Rad*Cos(rotation), Center_Y, mt2-1
+            SetScale 1,1
+          Case 1 ' y
+            SetScale size1,size1
+            DrawImage blocks, Center_X, Center_Y+Tile_Rad*Cos(rotation), mt1-1
+            SetScale size2,size2
+            DrawImage blocks, Center_X, Center_Y-Tile_Rad*Cos(rotation), mt2-1
+            SetScale 1,1
+        EndSelect
+      EndIf
+  EndSelect
+
+EndFunction

+ 771 - 0
samples/birdie/games/zombieblast/game.bmx

@@ -0,0 +1,771 @@
+Strict
+'
+' Game Demo Coded By David Bird (Birdie)
+'
+Incbin "media/sand.png"
+Incbin "media/ship1.png"
+Incbin "media/cloud.png"
+Incbin "media/shot.png"
+Incbin "media/shot2.PNG"
+Incbin "media/mud.png"
+Incbin "media/smoke.png"
+Incbin "media/scan.png"
+Incbin "media/zombie_0.png"
+Incbin "media/zombie_1.png"
+Incbin "media/zombie_2.PNG"
+Incbin "media/zombie_3.PNG"
+Incbin "media/zombie_4.PNG"
+Incbin "media/zombie_5.PNG"
+Incbin "media/zombie_6.png"
+Incbin "media/zombie_7.PNG"
+Incbin "media/Title.png"
+Incbin "media/HitSpace.png"
+
+Global C_ScreenWidth# = 640, C_ScreenHeight# = 480
+Global C_ScreenMidX# =C_ScreenWidth/2
+Global C_ScreenMidY# =C_ScreenHeight/2
+Global MapPosition_X#=400, MapPosition_Y#=400
+Global WorldSize = 8192
+Global Scanner_Rot#
+Global Scanner_X#= C_ScreenWidth - 80
+Global Scanner_Y#= 80
+Global Scanner_Scale# = 0.5
+Global ObjectList:TList = New TList
+Global DetailList:TList = New TList
+Global CloudList:TList  = New TList
+Global ScanList:TList   = New TList
+
+Global TitleWidth#,TitleHeight#
+Graphics C_ScreenWidth, C_ScreenHeight ,32
+HideMouse
+AutoImageFlags MASKEDIMAGE|FILTEREDIMAGE|MIPMAPPEDIMAGE
+
+'media globals
+Global media_sand:TImage
+Global media_ship1:TImage
+Global media_cloud:TImage
+Global media_shots:TImage
+Global media_shot2:TImage
+Global media_mud:TImage
+Global media_smoke:TImage
+Global Media_scan:TImage
+Global media_Title:TImage
+Global media_HitSP:TImage
+
+Global Media_zombie:TImage[8]
+LoadMedia()
+
+Local P1:Player = New Player
+ObjectList.AddLast p1
+
+'title screen
+
+While Not KeyDown(KEY_SPACE)
+  Cls
+  SetBlend SOLIDBLEND
+  SetColor 255,255,255
+  TileImage Media_sand,MapPosition_X,MapPosition_Y
+  MapPosition_X:+1
+  SetRotation 0
+  SetBlend ALPHABLEND
+  SetColor 255,255,255
+  SetAlpha 1
+  SetScale TitleWidth,TitleHeight
+  DrawImage Media_Title,C_ScreenMidX,C_ScreenMidY-24
+  SetScale 1,1
+  SetAlpha 0.5
+  DrawImage media_HitSP, C_ScreenMidX, C_ScreenHeight - media_HitSP.height
+  Flip
+Wend
+
+Local a
+For a=0 Until 100
+  cloud.Create( Rnd(-WorldSize,WorldSize), Rnd(-WorldSize,WorldSize) )
+Next
+For a=0 Until 250
+  Baddie.Create(Rnd(-1000,1000), Rnd(-1000,1000), 0)
+Next
+
+While Not KeyDown(KEY_ESCAPE)
+  Cls
+  UpdateObjects()
+
+  DrawLevel(p1)
+  DrawObjects()
+  DrawScanner( P1 )
+
+  Flip
+Wend
+End
+
+Function LoadMedia()
+  SetHandle 0.5,0.5
+  AutoMidHandle True
+  media_sand  = LoadImage("incbin::media/sand.png")
+  media_ship1 = LoadImage("incbin::media/ship1.png")
+  media_cloud = LoadImage("incbin::media/cloud.png")
+  media_shots = LoadImage("incbin::media/shot.png")
+  media_shot2 = LoadImage("incbin::media/shot2.PNG")
+  media_scan  = LoadImage("incbin::media/scan.png")
+  media_mud   = LoadImage("incbin::media/mud.png")
+  media_smoke = LoadImage("incbin::media/smoke.png")
+  media_Title = LoadImage("incbin::media/Title.png")
+  media_HitSP = LoadImage("incbin::media/HitSpace.png")
+  
+  'scale to fullscreen
+
+  TitleWidth = media_title.width / C_ScreenWidth
+  TitleHeight= media_title.height / (C_ScreenHeight+48)
+  Media_zombie[0] = LoadAnimImage("incbin::media/zombie_0.png",32,64,0,17)
+  Media_zombie[1] = LoadAnimImage("incbin::media/zombie_1.png",32,64,0,17)
+  Media_zombie[2] = LoadAnimImage("incbin::media/zombie_2.PNG",32,64,0,17)
+  Media_zombie[3] = LoadAnimImage("incbin::media/zombie_3.PNG",32,64,0,17)
+  Media_zombie[4] = LoadAnimImage("incbin::media/zombie_4.PNG",32,64,0,17)
+  Media_zombie[5] = LoadAnimImage("incbin::media/zombie_5.PNG",32,64,0,17)
+  Media_zombie[6] = LoadAnimImage("incbin::media/zombie_6.png",32,64,0,17)
+  Media_zombie[7] = LoadAnimImage("incbin::media/zombie_7.PNG",32,64,0,17)
+
+EndFunction
+
+Function DrawLevel(o:Entity)
+  'using the object{o} position and direction to draw the map
+'  MapPosition_X = MapPosition_X + ((o.x - MapPosition_X)*0.25)
+'  MapPosition_Y = MapPosition_Y + ((o.y - MapPosition_Y)*0.25)
+  MapPosition_X = o.x
+  MapPosition_Y = o.y
+  SetBlend SOLIDBLEND
+  SetColor 255,255,255
+  SetScale 1,1
+  TileImage Media_sand,MapPosition_X,MapPosition_Y
+
+EndFunction
+
+Function UpdateObjects()
+  Local e:Entity
+  For e=EachIn ObjectList
+    e.Update()
+  Next
+EndFunction
+
+Function DrawObjects()
+  Local e:Entity
+  Local c:Cloud
+  For c=EachIn CloudList
+    c.Update()
+  Next
+
+  'Draw Shadows
+  For e=EachIn ObjectList
+    e.DrawShadow()
+  Next
+  'Draw Details
+  Local d:Detail
+  For d=EachIn DetailList
+    d.Update(MapPosition_X, MapPosition_Y)
+  Next
+
+
+  'Draw objects without shadows
+  For e=EachIn ObjectList
+    e.DrawBody()
+  Next
+
+  'DrawClouds
+
+  For c=EachIn CloudList
+    c.DrawShadow()
+  Next
+  For c=EachIn CloudList
+    c.DrawBody()
+  Next
+
+EndFunction
+
+Type Entity
+  Field x#,y#
+  Field height#
+  Field rotation#
+  Field spd#=1
+
+  Method Update() Abstract
+
+  Method DrawBody() Abstract
+  Method DrawShadow() Abstract
+EndType
+
+Type Player Extends Entity
+  Field RotationSpd#
+  Field liftSpd#
+  Field thrust#
+  Field osc#
+
+  Method Update()
+    If KeyDown(KEY_UP)
+      thrust:+0.01
+      If thrust>0.5
+        thrust = 0.5
+      EndIf
+    EndIf
+    If KeyDown(KEY_DOWN)
+      thrust:-0.05
+    EndIf
+    spd:+thrust
+
+    If thrust<0
+      thrust = 0
+    EndIf
+
+    If spd>8
+      spd=8
+    EndIf
+    If spd<0.5 spd = 0.5
+
+    If KeyHit(KEY_SPACE)
+      Local a,thei#
+      thei = height +(3.5*Cos(osc) )
+
+      Shots.Create x, y, spd+5, thei, rotation
+      For a=0 Until 3
+        Shots.Create x, y, spd+5, thei, rotation+Rnd(-1.75,1.75)
+      Next
+    EndIf
+    Local sz#=14*(1+(height/140.0))
+    Local sd#=16
+    Local cs#= Cos(rotation-90)
+    Local sn#= Sin(rotation-90)
+    Local ddx# = sz
+    Local ddy# = 9
+
+    Local tx1# = ddx*cs + ddy*sn
+    Local ty1# = ddx*sn - ddy*cs
+    ddx=-ddx
+    Local tx2# = ddx*cs + ddy*sn
+    Local ty2# = ddx*sn - ddy*cs
+
+    Local thei#
+    thei = height +(3.5*Cos(osc) )
+    Local deltasm#= ( 0.5 * (spd/8.0))
+    Trail.Create( x+tx1, y+ty1, thei, deltasm, rotation )
+    Trail.Create( x+tx2, y+ty2, thei, deltasm, rotation )
+
+    Local tx#= 20 * Cos(rotation)
+    Local ty#= 20 * Sin(rotation)
+    Local rd= 255*thrust*2
+    Local gn= rd*0.7
+    Local bl= gn*0.5
+    ColTrail.Create( x-tx, y-ty, thei, 0.3, rotation, [rd, gn, bl] ,2.0)
+
+    ColTrail.Create( x-tx, y-ty, thei, 0.3, rotation, [rd*2, 0, 0] ,1.0)
+
+    If KeyDown(KEY_RIGHT)
+      rotationSpd:+0.25
+      If rotationSpd>2
+        rotationSpd=2
+      EndIf
+    EndIf
+    If KeyDown(KEY_LEFT)
+      rotationSpd:-0.25
+      If rotationSpd<-2
+        rotationSpd=-2
+      EndIf
+    EndIf
+    rotation:+rotationSpd
+    rotationSpd:*0.95
+
+    If KeyDown(KEY_A)
+      liftSpd:-0.02
+      If liftSpd<-1
+        liftSpd=-1
+      EndIf
+    EndIf
+    If KeyDown(KEY_Z)
+      liftSpd:+0.02
+      If liftSpd>1
+        liftSpd=1
+      EndIf
+    EndIf
+    height:+liftSpd
+
+    liftspd:*0.985
+
+    If height<4
+      height = 4
+      liftspd = 0
+    EndIf
+    If height>50
+      height = 50
+      liftspd=0
+    EndIf
+    x=x+(spd*Cos(rotation))
+    y=y+(spd*Sin(rotation))
+    If x>WorldSize x=-WorldSize
+    If x<-WorldSize x=WorldSize
+    If y>WorldSize y=-WorldSize
+    If y<-WorldSize y=WorldSize
+    osc:+1
+  EndMethod
+
+  Method DrawShadow()
+    Local dx#,dy#,sz#,thei#
+    thei = height +(3.5*Cos(osc) )
+    sz =(0.002*thei)
+    SetRotation rotation+90
+    SetBlend ALPHABLEND
+    SetScale 0.25+sz,0.25+sz
+    SetColor 0,0,0
+    SetAlpha 0.5
+    DrawImage media_ship1,C_ScreenMidX + (x-MapPosition_X), C_ScreenMidY + ( y - MapPosition_Y )
+  EndMethod
+
+  Method DrawBody()
+    Local dx#,dy#,sz#,thei#
+    thei = height +(3.5*Cos(osc) )
+    sz =(0.002*thei)
+    SetAlpha 1
+    SetRotation rotation+90
+    SetBlend MASKBLEND
+    SetColor 255,255,255
+    dx=thei/3.0
+    dy=thei
+    SetScale 0.25+sz, 0.25+sz
+    DrawImage media_ship1,(x-MapPosition_X+dx) + C_ScreenMidX,(y-MapPosition_Y+dy) + C_ScreenMidY
+  EndMethod
+  Function Create:Player(x#,y#)
+    Local p:Player = New Player
+    p.x=x
+    p.y=y
+    p.height = 4
+    p.spd=1
+    ObjectList.AddFirst p
+    Return p
+  EndFunction
+EndType
+
+Type Cloud
+  Field x#,y#,height#, rotation
+
+  Method Update()
+    x:+1
+    y:+0.1
+    If x>WorldSize x=-WorldSize
+    If x<-WorldSize x=WorldSize
+    If y>WorldSize y=-WorldSize
+    If y<-WorldSize y=WorldSize
+  EndMethod
+
+  Method DrawBody()
+    Local dx#,dy#
+    dx=height/2.0
+    dy=height
+    SetBlend LIGHTBLEND
+    SetAlpha 1
+    SetScale 2.4,2.4
+    SetRotation rotation
+    SetColor 255,255,255
+    DrawImage media_cloud,(MapPosition_X+dx-x) + C_ScreenMidX,(MapPosition_Y+dy-y) + C_ScreenMidY
+  EndMethod
+
+  Method DrawShadow()
+    SetBlend ALPHABLEND
+    SetColor 0,0,0
+    SetAlpha 0.2
+    SetScale 4, 4
+    SetRotation rotation
+    DrawImage media_cloud,(MapPosition_X-x) + C_ScreenMidX,(MapPosition_Y-y) + C_ScreenMidY
+  EndMethod
+
+  Function Create:Cloud( x#, y# )
+    Local c:Cloud = New Cloud
+    c.x= x
+    c.y= y
+    c.rotation = Rnd(360)
+    c.height = 75
+    CloudList.AddLast c
+    Return c
+  EndFunction
+EndType
+
+Type Shots Extends Entity
+  Field life
+  Method DrawBody()
+    Local dx#,dy#
+    dx=height/3.0
+    dy=height
+    SetBlend MASKBLEND
+    SetColor 255,255,255
+    SetAlpha 1
+    SetScale 1, 1
+    SetRotation rotation + 90
+    DrawImage media_shots,(MapPosition_X+dx-x) + C_ScreenMidX,(MapPosition_Y+dy-y) + C_ScreenMidY
+  EndMethod
+
+  Method DrawShadow()
+    SetBlend ALPHABLEND
+    SetColor 0,0,0
+    SetAlpha 0.2
+    SetScale 1, 1
+    SetRotation rotation + 90
+    DrawImage media_shots,(MapPosition_X-x) + C_ScreenMidX,(MapPosition_Y-y) + C_ScreenMidY
+  EndMethod
+  Method Update()
+    x=x+(spd*Cos(rotation))
+    y=y+(spd*Sin(rotation))
+    If x>WorldSize x=-WorldSize
+    If x<-WorldSize x=WorldSize
+    If y>WorldSize y=-WorldSize
+    If y<-WorldSize y=WorldSize
+    If life<80
+      height:-0.75
+    EndIf
+    Trail.Create( x, y, height, 0.3, rotation )
+    If height<0
+      ObjectList.Remove Self
+      Mud.Create(x, y, rotation)
+      Local ee#
+      ee=1
+      SmokeEmitter.create( x#,y#, 0,ee, ee, ee )
+    Else
+      life:-1
+      If life<0
+        ObjectList.Remove Self
+      EndIf
+    EndIf
+  EndMethod
+
+  Function Create:Shots(x#,y#,spd#,hei#,rot#)
+    Local s:Shots = New Shots
+    s.x=x
+    s.y=y
+    s.rotation = rot
+    s.spd = spd
+    s.height = hei
+    s.life = 100
+    ObjectList.AddFirst s
+    Return s
+  EndFunction
+EndType
+
+Type Detail
+  Field life
+  Field x#,y#,rot#,alp#
+  Field col[3]
+
+  Method Update( wx#, wy# ) Abstract
+EndType
+
+Type Trail Extends Detail
+  Field height#
+  Field size#
+
+  Method Update( wx#, wy# )
+    Local dx#, dy#
+    alp:-0.005
+    If alp<0
+      life = 0
+    EndIf
+
+    life:-1
+    If life<0
+      DetailList.Remove Self
+    Else
+      dx=height/3.0
+      dy=height
+      SetAlpha alp
+      SetBlend alphablend
+      SetRotation rot
+      SetColor 255,255,255
+      size:+0.05
+      SetScale 1,size
+      DrawRect (wx-x+dx)+C_ScreenMidX,(wy-y+dy)+C_ScreenMidY,10,1
+    EndIf
+  EndMethod
+
+  Function Create:Trail(x#,y#,hei#,alp#,rot#)
+    Local t:Trail = New Trail
+    t.x=x
+    t.y=y
+    t.height= hei
+    t.alp = alp
+    t.rot = rot
+    t.life= 100
+    t.size= 1
+    DetailList.AddLast t
+  EndFunction
+EndType
+
+Type ColTrail Extends Detail
+  Field height#,size#
+
+  Method Update( wx#, wy# )
+    Local dx#, dy#
+    alp:-0.01
+    size:*0.9
+    If alp<0
+      life = 0
+    EndIf
+
+    life:-1
+    If life<0
+      DetailList.Remove Self
+    Else
+      dx=height/3.0
+      dy=height
+      SetAlpha alp
+      SetBlend LIGHTBLEND
+      SetRotation rot
+      SetColor Col[0],Col[1],Col[2]
+      SetScale size,size
+      DrawRect (wx-x+dx)+C_ScreenMidX,(wy-y+dy)+C_ScreenMidY,10,4
+    EndIf
+  EndMethod
+
+  Function Create:ColTrail(x#,y#,hei#,alp#,rot#,col[],size#)
+    Local t:ColTrail = New ColTrail
+    t.x=x
+    t.y=y
+    t.height= hei
+    t.alp = alp
+    t.rot = rot
+    t.life= 100
+    t.size=size
+    t.col = col
+    DetailList.AddLast t
+  EndFunction
+EndType
+
+Type Mud Extends Detail
+
+  Method Update( wx#, wy# )
+    SetRotation rot
+    SetBlend ALPHABLEND
+    If life<50
+      alp = Float(life)/100.0
+    EndIf
+    SetAlpha alp
+    SetColor 0,0,0
+    SetScale 0.5,0.5
+
+    DrawImage media_mud,(wx-x)+C_ScreenMidX,(wy-y)+C_ScreenMidY
+    life:-1
+    If life<0
+      DetailList.Remove Self
+    EndIf
+  EndMethod
+
+  Function Create:Mud(x#,y#, rot#)
+    Local m:Mud = New Mud
+    m.x = x
+    m.y = y
+    m.rot = rot
+    m.alp = 0.5
+    m.life = 200
+    DetailList.AddLast M
+  EndFunction
+EndType
+
+Type smoke_prt
+  Field x#,y#,height#  'local to emitter
+  Field scl#
+  Field alp#
+  Field dx#,dy#
+  Field life
+  Field rot#, rotdir#
+
+  Method Draw( tx#, ty# )
+    y:-dy
+    x:+dx
+    life:-1
+    alp:-0.005
+    scl:+0.017
+    If alp<=0
+      life =0
+      Return
+    EndIf
+    SetBlend LightBlend
+    SetAlpha alp
+    SetScale scl,scl
+    SetRotation rot
+    rot:+rotdir
+    DrawImage media_smoke, tx+x, ty+y
+  EndMethod
+
+  Function Create:smoke_prt(scl#,alp#,x#,y#,dx#,dy#,life)
+    Local s:Smoke_prt = New Smoke_prt
+    s.x=x
+    s.y=y
+    s.dx=dx
+    s.dy=dy
+    s.life=life
+    s.alp=alp
+    s.scl=scl
+    s.rotdir = Rnd(-4,4)
+    s.rot = Rnd(360)
+    Return s
+  EndFunction
+EndType
+
+Type SmokeEmitter Extends Detail
+  Field smlist:TList = New TList
+
+  Field rd#,gn#,bl#, max_cnt=50, cur_cnt
+
+  Method Update( wx#, wy# )
+    Local sp:smoke_prt
+    SetColor 255*rd,255*gn,255*bl
+    For sp=EachIn smlist
+      sp.Draw((wx-x)+C_ScreenMidX, (wy-y)+C_ScreenMidY)
+      If sp.life<0
+        'remove it and add another
+        smlist.remove sp
+        cur_cnt:-1
+      EndIf
+    Next
+    If cur_cnt<max_cnt
+      smlist.addfirst( smoke_prt.Create( 0.2, 0.3, 0, 0, Rnd( -0.1, 0.1 ), Rnd(0.1,1), 40 ) )
+      cur_cnt:+1
+    EndIf
+    life:-1
+    If life<0
+      DetailList.Remove Self
+      smlist.clear()
+    EndIf
+  EndMethod
+
+  Function Create:SmokeEmitter(x#,y#, rot#,cr#,cg#,cb#)
+    Local sm:SmokeEmitter = New SmokeEmitter
+    sm.x = x
+    sm.y = y
+    sm.rot = rot
+    sm.alp = 0.20
+    sm.life = 50
+    sm.rd=cr
+    sm.gn=cg
+    sm.bl=cb
+
+    DetailList.AddLast sm
+  EndFunction
+EndType
+
+Type Baddie Extends Entity
+  Field frame = 0
+  Field frm_p = 2
+  Field frm_t = 0
+  Field direct= 0
+  Field life
+
+  Method Update()
+    frm_t:+1
+    If frm_t>frm_p
+      frm_t= 0
+      frame:+1
+      If frame = 17 frame = 0
+    EndIf
+    x=x+(spd*Cos(rotation-90))
+    y=y+(spd*Sin(rotation-90))
+    If x>WorldSize x=-WorldSize
+    If x<-WorldSize x=WorldSize
+    If y>WorldSize y=-WorldSize
+    If y<-WorldSize y=WorldSize
+
+    spd=0.3
+    rotation:+1
+    If rotation<0 rotation:+360
+    If rotation>=360 rotation:-360
+    direct = rotation / 45
+  EndMethod
+  Method DrawShadow()
+    Local dx = -12
+    Local dy = -4
+    SetBlend alphaBlend
+    SetColor 0,0,0
+    SetAlpha 0.3
+    SetRotation -30
+    SetScale 0.43,0.47
+    DrawImage Media_zombie[direct],(MapPosition_X-x) + C_ScreenMidX+dx,(MapPosition_Y-y) + C_ScreenMidY+dy, frame
+
+  EndMethod
+  Method DrawBody()
+    SetBlend alphaBlend
+    SetColor 255,255,255
+    SetAlpha 1
+    SetRotation 0
+    SetScale 0.4,0.4
+    DrawImage Media_zombie[direct],(MapPosition_X-x) + C_ScreenMidX,(MapPosition_Y-y) + C_ScreenMidY, frame
+
+  EndMethod
+
+  Function Create:Baddie(x#,y#,spd#)
+    Local s:Baddie = New Baddie
+    s.x=x
+    s.y=y
+    s.rotation = Rand(350)
+    s.spd = Rnd(1,2)
+    s.height = 0
+    s.life = 100
+    ObjectList.AddFirst s
+    Return s
+  EndFunction
+
+EndType
+
+Type Scan_Object
+  Field alp#,x#,y#,typ
+  Method Update(cx#, cy#)
+    alp:-0.01
+    If alp<0
+      ScanList.Remove Self
+    Else
+      SetAlpha alp
+      DrawRect cx+x,cy+y,5,5
+    EndIf
+  EndMethod
+  Function Create:Scan_Object(x#, y#, typ )
+    Local so:Scan_Object = New Scan_Object
+    so.x=x
+    so.y=y
+    so.typ=typ
+    so.alp=0.4
+    ScanList.AddLast so
+  EndFunction
+EndType
+
+Function DrawScanner(p:Player) 'according to a certain player
+  SetBlend LightBlend
+  SetColor 255,255,255
+  SetAlpha 0.5
+  SetRotation Scanner_Rot+90
+  Scanner_rot:-5
+  If scanner_rot<0 scanner_rot:+360
+
+  SetScale Scanner_Scale,Scanner_Scale
+  DrawImage media_scan,Scanner_X,Scanner_Y
+  Local b:Baddie
+  Local so:Scan_Object
+  Local dx#,dy#,ang#,ln#
+
+  For b=EachIn ObjectList
+    'get angle to object
+    dx=p.x-b.x
+    dy=p.y-b.y
+    ln=Sqr(dx*dx+dy*dy)
+    If ln<1200
+      ang = ATan2(dx,dy)
+      If ang<0 ang=360+ang
+      If Abs(ang-(Scanner_rot))<2
+        'add a new dot on the scanner
+        Scan_Object.Create(dx/20.0, dy/20.0, 0 )
+      EndIf
+    EndIf
+  Next
+  SetBlend LightBlend
+  SetAlpha 0.25
+  SetColor 48,64,48
+  SetRotation 0
+  DrawRect Scanner_X-64,Scanner_Y-64,256,256
+  SetColor 0,255,0
+  For so=EachIn ScanList
+    so.Update(scanner_x,Scanner_Y)
+  Next
+EndFunction
+
+

BIN
samples/birdie/games/zombieblast/media/HitSpace.png


BIN
samples/birdie/games/zombieblast/media/Title.png


BIN
samples/birdie/games/zombieblast/media/cloud.png


BIN
samples/birdie/games/zombieblast/media/mud.png


BIN
samples/birdie/games/zombieblast/media/sand.png


BIN
samples/birdie/games/zombieblast/media/scan.png


BIN
samples/birdie/games/zombieblast/media/ship1.png


BIN
samples/birdie/games/zombieblast/media/shot.png


BIN
samples/birdie/games/zombieblast/media/shot2.PNG


BIN
samples/birdie/games/zombieblast/media/smoke.png


BIN
samples/birdie/games/zombieblast/media/zombie_0.png


BIN
samples/birdie/games/zombieblast/media/zombie_1.png


BIN
samples/birdie/games/zombieblast/media/zombie_2.PNG


BIN
samples/birdie/games/zombieblast/media/zombie_3.PNG


BIN
samples/birdie/games/zombieblast/media/zombie_4.PNG


BIN
samples/birdie/games/zombieblast/media/zombie_5.PNG


BIN
samples/birdie/games/zombieblast/media/zombie_6.png


BIN
samples/birdie/games/zombieblast/media/zombie_7.PNG


+ 56 - 0
samples/birdie/misc/filmclip/main.bmx

@@ -0,0 +1,56 @@
+
+
+Graphics 640,480,32
+
+AutoMidHandle True
+Global BMX01IMG:TImage = LoadImage("media/B-Max.png",FILTEREDIMAGE|DYNAMICIMAGE)
+ConvertToBW BMX01IMG,0
+Global FLM01IMG:TImage = LoadAnimImage("media/flmstp.png",126,66,0,10)
+
+While Not KeyDown(KEY_ESCAPE)
+  Cls
+
+  SetColor 255,255,255
+  SetBlend ALPHABLEND          
+  SetScale 1,1
+  SetAlpha Rnd(0.75,0.95)
+  DrawImage bmx01img,320+Rnd(-1,1),240+Rnd(-1,1),0
+  If Rand(40)=5
+    SetColor 128,128,128
+    SetBlend addativeBlend
+    x=Rnd(640)
+    DrawLine x,0,x+Rnd(-5,5),Rnd(400,480)
+    EndIf
+  SetBlend MASKBLEND
+  SetColor 255,255,255
+  SetScale 6.5,7.5
+  DrawImage FLM01IMG,320,240,a
+  a:+1
+  a=a Mod 10
+  Flip
+Wend
+
+
+Function ConvertToBW(i:TImage,frame)
+  Local col,a,r,g,b,cc,x=0,y=0
+  Local pix:TPixmap
+  
+  pix=LockImage(i,frame)
+  While y<i.height
+    x=0
+    While x<i.width
+      col = ReadPixel( pix, x, y )
+      a = ( col & $ff000000)
+      r = ( col & $ff0000 ) Shr 16
+      g = ( col & $ff00 ) Shr 8
+      b = ( col & $ff )
+      cc= (r+g+b)/3
+      col = a | (cc Shl 16) | (cc Shl 8) | cc
+      WritePixel( pix, x, y, col )
+      x=x+1
+    Wend
+    y=y+1
+  Wend
+  UnlockImage i,frame
+EndFunction
+

BIN
samples/birdie/misc/filmclip/media/B-Max.png


BIN
samples/birdie/misc/filmclip/media/Thumbs.db


BIN
samples/birdie/misc/filmclip/media/flmstp.png


+ 282 - 0
samples/birdie/misc/glblur/glblurr.bmx

@@ -0,0 +1,282 @@
+Rem
+ NEHE OpenGL Lesson 36:  Radial Blur & Rendering To A Texture
+ converted to blitzmax by David Bird
+EndRem
+Strict
+
+'User Defined Variables
+Global C_WIDTH = 640
+Global C_HEIGHT= 480
+Global	angle# 						 'Used To Rotate The Helix
+Global 	vertexes#[4,3]     'Holds Float Info For 4 Sets Of Vertices
+Global	normal#[3]         'An Array To Store The Normal Data
+Global 	BlurTexture        'An Unsigned Int To Store The Texture Number
+Global tSize=256
+Const RAD_TO_DEG! =  57.2957795130823208767981548141052
+
+Function Cos_R!(rads!)
+  Return Cos(rads * RAD_TO_DEG)
+EndFunction
+
+Function Sin_R!(rads!)
+  Return Sin(rads * RAD_TO_DEG)
+EndFunction
+
+Function EmptyTexture()											' Create An Empty Texture
+	Local txtnumber											' Texture ID
+	Local data[tSize*tSize*4]
+	glGenTextures(1, Varptr txtnumber )								' Create 1 Texture
+	glBindTexture(GL_TEXTURE_2D, txtnumber)					' Bind The Texture
+	glTexImage2D(GL_TEXTURE_2D, 0, 4, 128, 128, 0,..
+		GL_RGBA, GL_UNSIGNED_BYTE, data)						' Build Texture Using Information In data
+	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR)
+	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR)
+	Return txtnumber											' Return The Texture ID
+EndFunction
+
+Function ReduceToUnit(vector:Float Ptr)								' Reduces A Normal Vector (3 Coordinates)
+	Local length#
+	' Calculates The Length Of The Vector
+	length = Sqr((vector[0]*vector[0]) + (vector[1]*vector[1]) + (vector[2]*vector[2]))
+	If length = 0.0 length=1.0						' Prevents Divide By 0 Error By Providing
+
+	vector[0]:/ length										' Dividing Each Element By
+	vector[1]:/ length										' The Length Results In A
+	vector[2]:/ length										' Unit Normal Vector.
+End Function
+
+Function ProcessHelix()												' Draws A Helix
+  Local a
+	Local x#													' Helix x Coordinate
+	Local y#													' Helix y Coordinate
+	Local z#													' Helix z Coordinate
+	Local phi#												' Angle
+	Local theta#											' Angle
+	Local v#,u#												' Angles
+	Local r#													' Radius Of Twist
+	Local twists = 5												' 5 Twists
+	Local glfMaterialColor#[]=[0.4#,0.2#,0.8#,1.0#]			' Set The Material Color
+	Local specular#[]=[1.0#,1.0#,1.0#,1.0#]					' Sets Up Specular Lighting
+  Local tv1#[3],tv2#[3]
+
+	glLoadIdentity()											' Reset The Modelview Matrix
+	gluLookAt(0, 5, 50, 0, 0, 0, 0, 1, 0)						' Eye Position (0,5,50) Center Of Scene (0,0,0), Up On Y Axis
+
+	glPushMatrix()												' Push The Modelview Matrix
+
+	glTranslatef(0,0,-50)										' Translate 50 Units Into The Screen
+	glRotatef(angle/2.0,1,0,0)								' Rotate By angle/2 On The X-Axis
+	glRotatef(angle/3.0,0,1,0)								' Rotate By angle/3 On The Y-Axis
+  glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT_AND_DIFFUSE,glfMaterialColor)
+	glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,specular)
+
+	r=1.5														' Radius
+
+	glBegin(GL_QUADS)											' Begin Drawing Quads
+	For phi=0 Until 360 Step 20.0							' 360 Degrees In Steps Of 20
+		For theta=0 Until (360*twists) Step 20.0 	' 360 Degrees * Number Of Twists In Steps Of 20
+			v=(phi/180.0*3.142)								' Calculate Angle Of First Point	(  0 )
+			u=(theta/180.0*3.142)							' Calculate Angle Of First Point	(  0 )
+
+			x=(cos_r(u)*(2.0+cos_r(v) ))*r					' Calculate x Position (1st Point)
+			y=(sin_r(u)*(2.0+cos_r(v) ))*r					' Calculate y Position (1st Point)
+			z=((( u-(2.0*3.142)) + sin_r(v) )* r)		' Calculate z Position (1st Point)
+
+			vertexes[0,0]=x									' Set x Value Of First Vertex
+			vertexes[0,1]=y									' Set y Value Of First Vertex
+			vertexes[0,2]=z									' Set z Value Of First Vertex
+
+			v=(phi/180.0*3.142)								' Calculate Angle Of Second Point	(  0 )
+			u=((theta+20)/180.0*3.142)						' Calculate Angle Of Second Point	( 20 )
+
+			x=(cos_r(u)*(2.0+cos_r(v) ))*r					' Calculate x Position (2nd Point)
+			y=(sin_r(u)*(2.0+cos_r(v) ))*r					' Calculate y Position (2nd Point)
+			z=((( u-(2.0*3.142)) + sin_r(v) ) * r)		' Calculate z Position (2nd Point)
+
+			vertexes[1,0]=x									' Set x Value Of Second Vertex
+			vertexes[1,1]=y									' Set y Value Of Second Vertex
+			vertexes[1,2]=z									' Set z Value Of Second Vertex
+
+			v=((phi+20)/180.0*3.142)							' Calculate Angle Of Third Point	( 20 )
+			u=((theta+20)/180.0*3.142)						' Calculate Angle Of Third Point	( 20 )
+
+			x=(cos_r(u)*(2.0+cos_r(v) ))*r					' Calculate x Position (3rd Point)
+			y=(sin_r(u)*(2.0+cos_r(v) ))*r					' Calculate y Position (3rd Point)
+			z=((( u-(2.0*3.142)) + sin_r(v) ) * r)		' Calculate z Position (3rd Point)
+
+			vertexes[2,0]=x									' Set x Value Of Third Vertex
+			vertexes[2,1]=y									' Set y Value Of Third Vertex
+			vertexes[2,2]=z									' Set z Value Of Third Vertex
+
+			v=((phi+20)/180.0*3.142)							' Calculate Angle Of Fourth Point	( 20 )
+			u=((theta)/180.0*3.142)							' Calculate Angle Of Fourth Point	(  0 )
+
+			x=Float(cos_r(u)*(2.0+cos_r(v) ))*r					' Calculate x Position (4th Point)
+			y=Float(sin_r(u)*(2.0+cos_r(v) ))*r					' Calculate y Position (4th Point)
+			z=Float((( u-(2.0*3.142)) + sin_r(v) ) * r)		' Calculate z Position (4th Point)
+
+			vertexes[3,0]=x									' Set x Value Of Fourth Vertex
+			vertexes[3,1]=y									' Set y Value Of Fourth Vertex
+			vertexes[3,2]=z									' Set z Value Of Fourth Vertex
+
+      For a=0 Until 3
+      	' Calculate The Vector From Point 1 To Point 0
+      	tv1[a] = vertexes[0,a] - vertexes[1,a]									' Vector 1.x=Vertex[0].x-Vertex[1].x
+    	  tv2[a] = vertexes[1,a] - vertexes[2,a]									' Vector 2.x=Vertex[0].x-Vertex[1].x
+        ' Compute The Cross Product To Give Us A Surface Normal
+      Next
+      normal[0] = tv1[1]*tv2[2] - tv1[2]*tv2[1]
+      normal[1] = tv1[2]*tv2[0] - tv1[0]*tv2[2]
+      normal[2] = tv1[0]*tv2[1] - tv1[1]*tv2[0]
+    	ReduceToUnit(normal)											' Normalize The Vectors
+
+    	glNormal3f(normal[0],normal[1],normal[2])			' Set The Normal
+
+			' Render The Quad
+			glVertex3f(vertexes[0,0],vertexes[0,1],vertexes[0,2])
+			glVertex3f(vertexes[1,0],vertexes[1,1],vertexes[1,2])
+			glVertex3f(vertexes[2,0],vertexes[2,1],vertexes[2,2])
+			glVertex3f(vertexes[3,0],vertexes[3,1],vertexes[3,2])
+		Next
+	Next
+	glEnd()													' Done Rendering Quads
+
+	glPopMatrix()												' Pop The Matrix
+EndFunction
+
+Function ViewOrtho()												' Set Up An Ortho View
+	glMatrixMode(GL_PROJECTION)								' Select Projection
+	glPushMatrix()												' Push The Matrix
+	glLoadIdentity()											' Reset The Matrix
+	glOrtho( 0, C_WIDTH ,C_HEIGHT , 0, -10, 10 )							' Select Ortho Mode (640x480)
+	glMatrixMode(GL_MODELVIEW)									' Select Modelview Matrix
+	glPushMatrix()												' Push The Matrix
+	glLoadIdentity()											' Reset The Matrix
+EndFunction
+
+Function ViewPerspective()											' Set Up A Perspective View
+	glMatrixMode( GL_PROJECTION )								' Select Projection
+	glPopMatrix()												' Pop The Matrix
+	glMatrixMode( GL_MODELVIEW )								' Select Modelview
+	glPopMatrix()												' Pop The Matrix
+EndFunction
+
+Function RenderToTexture()											' Renders To A Texture
+	glViewport(0,0,tSize,tSize)									' Set Our Viewport (Match Texture Size)
+	ProcessHelix()												' Render The Helix
+
+	glBindTexture(GL_TEXTURE_2D,BlurTexture)					' Bind To The Blur Texture
+	' Copy Our ViewPort To The Blur Texture (From 0,0 To 128,128... No Border)
+	glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 0, 0, tSize, tSize, 0)
+	glClearColor(0.0, 0.0, 0.25, 0.5)						' Set The Clear Color To Medium Blue
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)			' Clear The Screen And Depth Buffer
+	glViewport(0 , 0,C_WIDTH ,C_HEIGHT)									' Set Viewport (0,0 to 640x480)
+EndFunction
+
+Function DrawBlur(times, inc#)								' Draw The Blurred Image
+  Local num
+	Local spost# = 0.0											' Starting Texture Coordinate Offset
+  Local alphainc# = 0.9 / Float(times)								' Fade Speed For Alpha Blending
+	Local alpha# = 0.1											' Starting Alpha Value
+
+	' Disable AutoTexture Coordinates
+	glDisable(GL_TEXTURE_GEN_S)
+	glDisable(GL_TEXTURE_GEN_T)
+
+	glEnable(GL_TEXTURE_2D)									' Enable 2D Texture Mapping
+	glDisable(GL_DEPTH_TEST)									' Disable Depth Testing
+	glBlendFunc(GL_SRC_ALPHA,GL_ONE)							' Set Blending Mode
+	glEnable(GL_BLEND)											' Enable Blending
+	glBindTexture(GL_TEXTURE_2D,BlurTexture)					' Bind To The Blur Texture
+	ViewOrtho()						      						' Switch To An Ortho View
+  alphainc = alpha / times									' alphainc=0.2f / Times To Render Blur
+
+	glBegin(GL_QUADS)											' Begin Drawing Quads
+		For num = 0 Until  times						' Number Of Times To Render Blur
+			glColor4f(1.0, 1.0, 1.0, alpha)					' Set The Alpha Value (Starts At 0.2)
+			glTexCoord2f(0+spost,1-spost)						' Texture Coordinate	( 0, 1 )
+			glVertex2f(0,0)									' First Vertex		(   0,   0 )
+
+			glTexCoord2f(0+spost,0+spost)						' Texture Coordinate	( 0, 0 )
+			glVertex2f(0,C_HEIGHT)									' Second Vertex	(   0, 480 )
+
+			glTexCoord2f(1-spost,0+spost)						' Texture Coordinate	( 1, 0 )
+			glVertex2f(C_WIDTH,C_HEIGHT)								' Third Vertex		( C_WIDTH, 480 )
+
+			glTexCoord2f(1-spost,1-spost)						' Texture Coordinate	( 1, 1 )
+			glVertex2f(C_WIDTH,0)									' Fourth Vertex	( C_WIDTH,   0 )
+
+			spost:+ inc										' Gradually Increase spost (Zooming Closer To Texture Center)
+			alpha = alpha - alphainc							' Gradually Decrease alpha (Gradually Fading Image Out)
+		Next
+	glEnd()													' Done Drawing Quads
+
+	ViewPerspective()											' Switch To A Perspective View
+
+	glEnable(GL_DEPTH_TEST)									' Enable Depth Testing
+	glDisable(GL_TEXTURE_2D)									' Disable 2D Texture Mapping
+	glDisable(GL_BLEND)										' Disable Blending
+
+
+	glBindTexture(GL_TEXTURE_2D,0)								' Unbind The Blur Texture
+EndFunction
+
+Function Initialize ()
+	' Start Of User Initialization
+	angle		= 0.0											' Set Starting Angle To Zero
+	BlurTexture = EmptyTexture()								' Create Our Empty Texture
+	glViewport(0 , 0,C_WIDTH ,C_HEIGHT)	' Set Up A Viewport
+	glMatrixMode(GL_PROJECTION)								' Select The Projection Matrix
+	glLoadIdentity()											' Reset The Projection Matrix
+	gluPerspective(50, Float(C_WIDTH)/Float(C_HEIGHT), 5,  2000) ' Set Our Perspective
+	glMatrixMode(GL_MODELVIEW)									' Select The Modelview Matrix
+	glLoadIdentity()											' Reset The Modelview Matrix
+	glEnable(GL_DEPTH_TEST)									' Enable Depth Testing
+
+	Local global_ambient#[]=[0.2#, 0.2#,  0.2#, 1.0#]		' Set Ambient Lighting To Fairly Dark Light (No Color)
+	Local light0pos#[]=     [0.0#, 5.0#, 10.0#, 1.0#]		' Set The Light Position
+	Local light0ambient#[]= [0.2#, 0.2#,  0.2#, 1.0#]		' More Ambient Light
+	Local light0diffuse#[]= [0.3#, 0.3#,  0.3#, 1.0#]		' Set The Diffuse Light A Bit Brighter
+	Local light0specular#[]=[0.8#, 0.8#,  0.8#, 1.0#]		' Fairly Bright Specular Lighting
+
+	Local lmodel_ambient#[]=[ 0.2#,0.2#,0.2#,1.0#]			' And More Ambient Light
+	glLightModelfv(GL_LIGHT_MODEL_AMBIENT,lmodel_ambient)		' Set The Ambient Light Model
+
+	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient)		' Set The Global Ambient Light Model
+	glLightfv(GL_LIGHT0, GL_POSITION, light0pos)				' Set The Lights Position
+	glLightfv(GL_LIGHT0, GL_AMBIENT, light0ambient)			' Set The Ambient Light
+	glLightfv(GL_LIGHT0, GL_DIFFUSE, light0diffuse)			' Set The Diffuse Light
+	glLightfv(GL_LIGHT0, GL_SPECULAR, light0specular)			' Set Up Specular Lighting
+	glEnable(GL_LIGHTING)										' Enable Lighting
+	glEnable(GL_LIGHT0)					   					' Enable Light0
+	glShadeModel(GL_SMOOTH)									' Select Smooth Shading
+	glMateriali(GL_FRONT, GL_SHININESS, 128)
+	glClearColor(0.0, 0.0, 0.0, 0.5)						' Set The Clear Color To Black
+	Return 1												' Return TRUE (Initialization Successful)
+EndFunction
+
+Function Update(milliseconds#)								' Perform Motion Updates Here
+	If KeyDown(KEY_ESCAPE) End
+	angle:+(milliseconds/5.0)						' Update angle Based On The Clock
+EndFunction
+
+Function Draw()												' Draw The Scene
+	glClearColor(0.0, 0.0, 0.0, 0.25)						' Set The Clear Color To Black
+	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)		' Clear Screen And Depth Buffer
+	glLoadIdentity()											' Reset The View
+
+	RenderToTexture()											' Render To A Texture
+	ProcessHelix()												' Draw Our Helix
+	DrawBlur(25,.02)											' Draw The Blur Effect
+	glFlush ()													' Flush The GL Rendering Pipeline
+EndFunction
+
+GLGraphics C_WIDTH,C_HEIGHT,32
+
+Initialize()
+
+Repeat
+    Update(25)
+    Draw()
+	Flip
+Forever

+ 67 - 0
samples/birdie/misc/lightImage/main.bmx

@@ -0,0 +1,67 @@
+'
+' Using Lightblend/image as a light
+'
+Strict
+
+'Include media in the final file
+Incbin "media/fl.png"
+Incbin "media/light.png"
+Incbin "media/B-Max.png"
+
+'set the graphics mode
+Graphics 640, 480, 32
+
+'Hide the pointer
+HideMouse
+
+'All images will be handled from the center
+AutoMidHandle True
+
+'Load images in now. incbin points the file to the included images
+Global backImage:TImage = LoadImage("incbin::media/fl.png")
+Global logoImage:TImage = LoadImage("incbin::media/B-Max.png")
+Global lightImage:TImage = LoadImage("incbin::media/light.png")
+
+'setup some vars now
+Local x#, y#, tim#
+Local mx, my
+Local lightcolor[] = [255,255,255]
+Local BackColor[]  = [128,128,128]
+
+'Main Loop
+While Not KeyDown(KEY_ESCAPE)
+  x:+5*Sin(tim)
+  y:+1
+  tim:+4
+  mx = MouseX()
+  my = MouseY()
+
+  'draw the backgound
+  SetScale 1,1
+  SetColor BackColor[0],BackColor[1],BackColor[2]
+  TileImage backImage,x,y
+
+  'draw the logo
+  SetBlend MaskBlend
+  SetColor 0,0,0
+  DrawImage logoimage,340,260
+  SetColor 100,100,100
+  DrawImage logoimage,320,240
+
+  'Draw the light
+  SetBlend LightBlend
+  SetColor LightColor[0],LightColor[1],LightColor[2]
+  SetScale 1.5+1*Cos(tim), 1.5+1*Cos(tim)
+  DrawImage lightimage, mx, my
+  
+  'draw the mousepointer
+  SetBlend SolidBlend
+  SetColor 255,255,255
+  SetScale 1,1
+  DrawLine mx-5,my,mx+5,my
+  DrawLine mx,my-5,mx,my+5
+  Flip
+Wend
+
+End
+

BIN
samples/birdie/misc/lightImage/media/B-Max.png


BIN
samples/birdie/misc/lightImage/media/fl.png


BIN
samples/birdie/misc/lightImage/media/light.png


+ 334 - 0
samples/breakout/breakout.bmx

@@ -0,0 +1,334 @@
+
+Strict
+
+Const WIDTH = 640,HEIGHT = 480, DEPTH = 32
+Const ShadowOn   = 1
+Const ShadowSize = 10
+
+Global gtime
+Global Pipes_img:TImage
+Global Tiles_img:TImage
+Global logo_img:TImage
+Global paddle:TImage
+Global ballvis:TImage
+'Setup the level
+Global Tilelist:TList
+Global Balllist:TList
+Global playerX#,PlayerY#
+Global Score
+
+Private
+  Global ballcount=0
+
+  Function Minf#(a#,b#)
+    If a<b Return a
+    Return b
+  EndFunction
+  Function Maxf#(a#,b#)
+    If a>b Return a
+    Return b
+  EndFunction
+Public
+
+Type ball
+  Field x#,y#
+  Field dx#,dy#,spd#,rot#=0
+
+  Field visual
+
+  Method Update()
+    x:+ (dx * spd)
+    y:+ (dy * spd)
+    If x<34 Or x>606
+      dx=-dx
+    EndIf
+    If y<50
+      dy=-dy
+    EndIf
+    If y>Height-8
+      ballcount:-1
+      BallList.Remove(Self)
+    Else
+      If dy>0
+        If y>playery-8
+          If x>playerx-32 And x<playerx+32
+            dy=dy*-1
+          EndIf
+        EndIf
+      EndIf
+      rot:+10
+    EndIf
+  EndMethod
+
+  Method Draw(offx,offy)
+    SetRotation rot
+    DrawImage ballvis,x+offx,y+offy
+    SetRotation 0
+  EndMethod
+
+  Function Create:Ball(x=Width/2 , y=Height/2)
+    Local b:Ball = New Ball
+    ballcount:+1
+    b.x = x
+    b.y = y
+    b.dx = Rnd(-2, 2)
+    b.dy = Rnd(-2, 2)
+    b.spd = 4'0.1
+    Return b
+  EndFunction
+EndType
+
+'all tiles are a standard size so
+Type Tile
+  Field x#,y#
+  Field typ = 0
+  Field state = 0
+  Field rot#=0,size#=1
+
+  Method Draw(offx,offy)
+    Select state
+      Case 0
+        SetRotation rot
+        If size>1
+          SetScale size,size
+          size=size*0.9
+        Else
+          size = 1
+          SetScale 0.95+(0.05*Cos(gTime)),0.95+(0.05*Sin(gTime))
+        EndIf
+      Case 1
+        SetRotation rot
+        SetScale size,size
+    EndSelect
+    Select typ
+      Case 0
+        DrawImage tiles_img,x+offx,y+offy+(2*Sin(gtime)),0
+      Case 1
+        DrawImage tiles_img,x+offx,y+offy+(2*Sin(gtime)),1
+      Case 2
+        DrawImage tiles_img,x+offx,y+offy+(2*Sin(gtime)),2
+      Case 3
+        DrawImage tiles_img,x+offx,y+offy+(2*Sin(gtime)),3
+      Case 4
+        DrawImage tiles_img,x+offx,y+offy+(2*Sin(gtime)),4
+    EndSelect
+
+    SetScale 1,1
+    SetRotation 0
+  EndMethod
+
+  Method Update()
+    Local c
+    Local b:Ball
+    If state = 0
+      'Check this tile for collision with all of the balls
+      For b=EachIn BallList
+        If b.x>x-4 And b.x<x+24
+          If b.y>y-4 And b.y<y+24
+            b.dy=-b.dy
+            Select typ
+              Case 1
+                If ballcount=1
+                  For c=0 Until 2
+                    BallList.AddLast(ball.Create(b.x,b.y))
+                  Next
+                EndIf
+                state = 1
+                size = 1
+              Case 2
+                typ = 3
+                size=1.5
+              Case 3
+                typ = 4
+                size=1.5
+              Default
+                Score:+((1+typ)*100)
+                state = 1
+            EndSelect
+            Return
+          EndIf
+        EndIf
+      Next
+    Else
+      y:+4
+      rot:+5
+      size:-.005
+      If y>HEIGHT
+        BallList.Remove(b)
+      EndIf
+    EndIf
+  EndMethod
+
+
+  Function Create:Tile(x=0,y=0,typ=0)
+    Local t:Tile = New Tile
+      t.x=x
+      t.y=y
+      t.typ = typ
+      Return t
+  EndFunction
+EndType
+
+Graphics WIDTH,HEIGHT,DEPTH
+
+AutoMidHandle True
+
+'Media
+Global back:TImage[2]
+back[0] = LoadImage("media\back1.png")
+back[1] = LoadImage("media\back2.png")
+Pipes_img=LoadAnimImage("media\pipes.png",32,32,0,4)
+Tiles_img=LoadAnimImage("media\tiles.png",32,20,0,5)
+paddle = LoadImage("media\paddle.png")
+ballvis = LoadImage("media\ball.png")
+logo_img=LoadImage("media\B-Max.png")
+
+
+Tilelist:TList = New TList
+Balllist:TList = New TList
+playerX# = Width/2
+PlayerY# = Height-40
+Score=0
+
+ResetGame()
+
+
+
+HideMouse
+While Not KeyDown(KEY_ESCAPE)
+
+	'Update Players Position
+	playerx = minf(574,maxf(64,MouseX()))
+	'Update Balls
+	UpdateBalls()
+	'Update Tiles
+	UpdateTiles()
+	'Draw Level
+	DrawLevel()
+
+	gTime:+10
+
+	SetAlpha .75
+	SetColor 0,0,255
+	DrawRect 0,0,Width,20
+
+	SetBlend ALPHABLEND
+
+	SetAlpha 0.5
+	SetColor 0,0,0
+	DrawText "Score:"+Score,4,4
+
+	SetAlpha 1
+	SetColor 255,255,255
+	DrawText "Score:"+Score+" "+ballcount,2,2
+
+	Flip
+Wend
+
+End
+
+
+Function DrawLevel()
+  Local w,aa#
+  TileImage back[1],0,gTime/20
+  SetBlend ALPHABLEND
+  DrawImage logo_img,width/2,height/2
+  aa#=0.5+(0.5*Cos(gtime/50))
+  SetBlend AlphaBLEND
+  SetAlpha aa
+  TileImage back[0],0,gTime/10
+
+  If ShadowOn
+    SetColor 0,0,0
+    SetBlend AlphaBLEND
+    SetAlpha 0.5
+    DrawPipes ShadowSize+16,ShadowSize+16
+
+    DrawTiles ShadowSize+16,ShadowSize+10
+    DrawPlayer ShadowSize,ShadowSize
+    DrawBalls ShadowSize,ShadowSize
+  EndIf
+
+  SetColor 255,255,255
+  SetBlend MASKBLEND
+  SetAlpha 1
+  DrawPipes()
+  DrawTiles()
+  DrawPlayer()
+  DrawBalls()
+EndFunction
+
+Function ResetGame()
+  TileList = New TList
+  BallList = New TList
+  Local x,y
+  For y=0 Until 5
+    For x=0 Until 18
+        Tilelist.AddLast(Tile.Create(38+x*32,(y*24)+66,4-Y))
+    Next
+  Next
+
+  BallList.AddLast(Ball.Create())
+EndFunction
+
+Function DrawPipes(x=16,y=16)
+  Local tmp
+
+  'top
+  For tmp=0 Until 18
+    DrawImage Pipes_img,x+32+(tmp*32),y+16,3
+  Next
+
+  'sides
+  For tmp=0 Until 14
+    DrawImage Pipes_img,x,y+48+(tmp*32),2
+    DrawImage Pipes_img,x+Width-32,y+48+(tmp*32),2
+  Next
+
+  'Corners
+  DrawImage Pipes_img,x,y+16 ,0
+  DrawImage Pipes_img,x+Width-32,y+16,1
+
+EndFunction
+
+Function DrawTiles(x_off=10, y_off=10)
+	Local tl:Tile
+	Local any=0
+  For tl=EachIn TileList
+		tl.Draw(x_off, y_off)
+		any=1
+	Next
+	If Not any 
+	 ResetGame()
+	 score:+10000
+	EndIf
+EndFunction
+
+Function DrawBalls(x_off=0, y_off=0)
+	Local bl:Ball
+	For bl=EachIn balllist
+		bl.Draw(x_off, y_off)
+	Next
+EndFunction
+
+Function UpdateBalls()
+  If ballcount=0
+    BallList.AddLast(Ball.Create(Width/2,Height/2))
+  Else
+  	Local bl:Ball
+  	For bl = EachIn BallList
+  		bl.Update()
+  	Next
+  EndIf
+EndFunction
+
+Function UpdateTiles()
+	Local tl:Tile
+	For tl=EachIn tilelist
+		tl.Update()
+	Next
+EndFunction
+
+Function DrawPlayer(x_off=0,y_off=0)
+  DrawImage paddle, playerx+x_off, playery+y_off
+End Function

BIN
samples/breakout/media/B-Max.png


BIN
samples/breakout/media/back1.png


BIN
samples/breakout/media/back2.png


BIN
samples/breakout/media/ball.png


BIN
samples/breakout/media/paddle.png


BIN
samples/breakout/media/pipes.png


BIN
samples/breakout/media/tiles.png


+ 90 - 0
samples/digesteroids/Digesteroids.ppf

@@ -0,0 +1,90 @@
+<?xml version='1.0'?>
+<!--This project file was generated by Protean
+Editing it may render it inoperable. You have been warned!-->
+<xml>
+  <Project GUID="2EF9C725-F0F3-40F8-AFF9-155E7F196071" Expanded="True">
+    <Version>
+      <Formatting Short="%major%.%minor%" Long="%major%.%minor%.%build%.%revision%" />
+      <Values Major="0" Minor="1" Build="11" Revision="1113" />
+      <Rules RuleData="0,0,1,0,0,0,0,0,0,0,0,1,5000,0,0,0,0,0,0,0,0" />
+    </Version>
+    <Configurations>
+      <Configuration Name="Release">
+        <Property Name="Blitz_BeforeEXE_Options">True,True,True</Property>
+        <Property Name="Blitz_OverrideDebug">False</Property>
+        <Property Name="Blitz_IncludeLongVersion">True</Property>
+        <Property Name="Blitz_Compiler">Blitz Basic</Property>
+        <Property Name="Blitz_AfterCompile_Options">True,True,True</Property>
+        <Property Name="Blitz_IncludeShortVersion">True</Property>
+        <Property Name="Blitz_DebugEnabled">False</Property>
+        <Property Name="Blitz_ShortVersionVariable">VersionShort</Property>
+        <Property Name="Blitz_BeforeCheck_Options">True,True,True</Property>
+        <Property Name="Blitz_AfterEXE_Options">True,True,True</Property>
+        <Property Name="Blitz_LongVersionVariable">VersionLong</Property>
+        <Property Name="Blitz_BeforeCompile_Options">True,True,True</Property>
+        <Property Name="Blitz_CompileType">0</Property>
+        <Property Name="Blitz_AfterCheck_Options">True,True,True</Property>
+        <Property Name="Blitz_CommandLineEnabled">True</Property>
+      </Configuration>
+    </Configurations>
+    <Options>
+      <WorkingDirectory>D:\BlitzMax\robhutchinson\digesteroids</WorkingDirectory>
+      <SelectedConfiguration>Release</SelectedConfiguration>
+    </Options>
+    <File GUID="473AF815-1E27-48BD-9630-70D541441EB9" Open="True" Line="0" Column="0" Name="MathUtil.bmx">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="B02F06E0-4386-49CC-B5F8-35AB8BF082C8" Open="True" Line="0" Column="0" Name="simplephysics.bmx">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="5ED55F16-174B-42BD-A47A-1F99E39E1DF1" Open="True" Line="0" Column="0" Name="dynamicgame.bmx">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="9AF2C6A5-D96E-4391-9CA8-911FE7253975" Open="True" Line="901" Column="57" Name="digesteroids.bmx">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="31498CEA-525C-457E-A80B-754134C4A5F5" Open="False" Line="0" Column="0" Name="minitimer.bmx">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="9B56E13A-1332-43FE-BB98-B1218E5F4BEA" Open="False" Line="0" Column="0" Name="graphics\bullet1.png">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="8569E9CB-E52A-4E82-81BA-C3A58B63B620" Open="False" Line="0" Column="0" Name="graphics\bullet2.png">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="EA76DE94-7251-4DE9-B305-DDA538BBE239" Open="False" Line="0" Column="0" Name="graphics\digestive.png">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="B3632217-1AAA-45AE-90F4-D7D6113B69D7" Open="False" Line="0" Column="0" Name="graphics\ship.png">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <File GUID="45A32819-5298-4540-A707-E135C65C7672" Open="False" Line="0" Column="0" Name="graphics\stars.png">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </File>
+    <Directory Expanded="False" Name="graphics">
+      <Configurations>
+        <Configuration Name="Release" />
+      </Configurations>
+    </Directory>
+  </Project>
+</xml>

+ 60 - 0
samples/digesteroids/MathUtil.bmx

@@ -0,0 +1,60 @@
+' *******************************************************************
+' Source: Math Util
+' Version: 1.00
+' Author: Rob Hutchinson 2004
+' Email: [email protected]
+' WWW: http://www.proteanide.co.uk/
+' -------------------------------------------------------------------
+' Some generic functions. Certainly nothing to do with Math. HURRAH!
+' -------------------------------------------------------------------
+' Required:
+'  - Nothing.
+' *******************************************************************
+
+Type MathUtil
+
+'#Region Method: QWrapF
+	' Wraps only once, if the value is out of the range by twice as much, this function will return incorrect values,
+	' However, it is faster than Wrap. Use Wrap for accurate results.
+	Function QWrapF:Float(Value:Float,Minimum:Float,Maximum:Float)
+		If Value > Maximum
+			Return Minimum + (Value Mod Maximum)
+		ElseIf Value < Minimum
+			Return Maximum - Abs(Minimum - Value)
+		EndIf
+		Return Value
+	End Function
+'#End Region
+'#Region Method: QWrapDegrees
+	Function QWrapDegrees:Double(Value:Double)
+		If Value > 359.0
+			Return (0.0 + (Value Mod 359.0))
+		ElseIf Value < 0.0
+			Return (359.0 - Abs(0.0 - Value))
+		EndIf
+		Return Value
+	End Function
+'#End Region
+'#Region Method: Wrap
+	Function Wrap:Float(Value:Float,Minimum:Float,Maximum:Float)
+		Local Difference:Float = Maximum - Minimum
+		While ((Value < Minimum) Or (Value => Maximum))
+			If Value => Maximum
+				Value:- Difference
+			Else
+				If Value < Minimum
+					Value:+ Difference
+				EndIf
+			EndIf
+		Wend
+		Return value
+	End Function
+'#End Region
+'#Region Method: CurveValue
+	Function CurveValue:Float(Current:Float,Destination:Float,Curve:Int)
+		Current = Current + ( (Destination - Current) /Curve)
+		Return Current
+	End Function
+'#End Region
+
+End Type

+ 1496 - 0
samples/digesteroids/digesteroids.bmx

@@ -0,0 +1,1496 @@
+' *******************************************************************
+' Digesteroids - V1.0
+' --------------------------------------------------------
+' An evil biscuit corporation are using their army of
+' sweet snack food to take over the world. Use your ship
+' to take down the evil invading corporation before 
+' they infest the world with dunkables sweetmeal biscuits.
+' REQUIRES 1024x768x32! @75Hz
+' --------------------------------------------------------
+' Author: Rob Hutchinson 2004
+' Email: [email protected]
+' Written entirely in Protean IDE: 
+' WWW: http://www.proteanide.co.uk/
+' --------------------------------------------------------
+' Yep, there's stuff in here, I didnt get time to implement
+' Pickups, Weapons and Vortexes.
+' *******************************************************************
+	
+Strict
+
+' Import various utilities.
+Import "simplephysics.bmx"
+Import "dynamicgame.bmx"
+Import "MathUtil.bmx"
+
+' Screen settings.
+Const WIDTH       = 1024    ' Width of the screen.
+Const HEIGHT      = 768     ' Height of the screen.
+Const DEPTH       = 32      ' Depth of the screen.
+Const REFRESHRATE = 75      ' How often to update the screen (Per second).
+
+' Game Settings
+Const DYNAMICTIMING = True  ' If true then the game will try to keep up with
+Const DESIREDFPS    = 75    ' The Desired FPS of the game, can be independant of Refresh Rate.
+
+Function ScreenPan(OffsetX:Float, OffsetY:Float)
+	Local Width,Height,Depth,Hz
+	Width=GraphicsWidth()
+	Height=GraphicsHeight()
+	Depth=GraphicsDepth()
+	Hz=GraphicsHertz()
+    glMatrixMode GL_PROJECTION
+    glLoadIdentity
+    glOrtho 0,Width,Height,0,-1,1
+    glTranslatef OffsetX, OffsetY,0
+    glMatrixMode GL_MODELVIEW
+EndFunction
+
+Function QFlushKeys()
+	' Quick implementation of flushkeys as BMX doesnt have one at time of writing, sure it
+	' will be added though.
+	For Local Key:Int = 0 To 255
+		KeyHit(Key)
+		KeyDown(Key)
+	Next
+End Function
+
+'#Region Scene: MainMenu
+	' The main menu scene.
+	Type MainMenuScene Extends T2DDynamicGameScene
+
+'#Region DrawTextCentered
+	Function DrawTextCentered:Int(Text:String,Y:Int)
+		Local Out:Int = (WIDTH / 2) - (TextWidth(Text) / 2)
+		DrawText(Text, Out, Y)
+		Return Out
+	End Function
+'#End Region
+
+'#Region Declarations
+		Const STARS_PER_SECOND = 6
+		Field BackColor:Float
+		Field Direction:Float = 0.01
+		Field ExitGame:Int = False
+		Field Music:TSound
+		Field MusicChannel:TChannel
+		Field Title:TImage
+		Field LowerTitle:TImage
+		Field Options:TImage
+		Field Craft:TImage
+		Field EnteredText:String = ""
+		Field LastHighScore:Int
+		Field ShowCredits:Int = False
+
+		Field Viewing:Int = VIEW_MENU
+		Field HoverOver:Int = VIEW_START
+
+		Const VIEW_MENU         = 0
+		Const VIEW_START        = 0
+		Const VIEW_INSTRUCTIONS = 1
+		Const VIEW_HIGHSCORES   = 2
+		Const VIEW_QUIT         = 3
+		Const VIEW_ENTERHIGH    = 4
+
+		Field Stars:TPhysicsProviderCollection = New TPhysicsProviderCollection
+		Field World:TWorldPhysicsProvider = TWorldPhysicsProvider.Create(0.0, 0.0, 0.0, True)
+'#End Region
+
+'#Region Method: FinishScoreEntry
+		Method FinishScoreEntry()
+			Self.ViewMenu(VIEW_HIGHSCORES)
+			Scores.Add(Self.LastHighScore, Self.EnteredText)
+
+			' Add this to the high scores..
+			Self.LastHighScore = 0
+			Self.EnteredText = ""
+		End Method
+'#End Region
+'#Region Method: ViewMenu
+		Method ViewMenu(Menu:Int)
+			Self.Viewing = Menu
+			Self.HoverOver = Menu
+			QFlushKeys()
+		End Method
+'#End Region
+
+'#Region Method: Update
+		Method Update()
+			Self.Stars.ApplyPhysics()
+
+			' Add new stars..
+			For Local CountStars:Int = 0 To STARS_PER_SECOND
+				Local Star:TMenuStar = TMenuStar.Create(World, Self, 10.0)
+				Self.Stars.AddLast(Star)
+			Next
+
+			' Do enter key checking..
+			Select Self.Viewing
+				Case VIEW_MENU
+					If KeyHit(KEY_ENTER) Then 
+						Select Self.HoverOver
+							Case VIEW_START
+								Self.TerminateMainLoop = True
+		
+							Case VIEW_INSTRUCTIONS
+								Self.ViewMenu(VIEW_INSTRUCTIONS)
+		
+							Case VIEW_HIGHSCORES
+								Self.ViewMenu(VIEW_HIGHSCORES)
+		
+							Case VIEW_QUIT
+								Self.TerminateMainLoop = True
+								Self.ExitGame = True
+		
+						End Select
+					EndIf
+
+					If KeyHit(KEY_UP)
+						Self.HoverOver :- 1
+						If Self.HoverOver < VIEW_START Then Self.HoverOver = VIEW_START
+					EndIf
+					If KeyHit(KEY_DOWN)
+						Self.HoverOver :+ 1
+						If Self.HoverOver > VIEW_QUIT Then Self.HoverOver = VIEW_QUIT
+					EndIf
+
+					If KeyHit(KEY_ESCAPE) Then 
+						Self.TerminateMainLoop = True
+						Self.ExitGame = True
+					Else
+						If KeyHit(KEY_C)
+							Self.TerminateMainLoop = True
+							Self.ShowCredits = True
+						EndIf
+					EndIf
+
+				Case VIEW_INSTRUCTIONS, VIEW_HIGHSCORES
+					If KeyHit(KEY_ENTER) Or KeyHit(KEY_ESCAPE)
+						Self.Viewing = VIEW_MENU
+					EndIf
+
+				Case VIEW_ENTERHIGH
+					' Check for alpha keys.
+					For Local Count:Int = KEY_A To KEY_Z
+						If KeyHit(Count) Then Self.EnteredText = Self.EnteredText + Chr(Count)
+					Next
+					' Check for number key hits
+					For Local Count:Int = KEY_0 To KEY_9
+						If KeyHit(Count) Then Self.EnteredText = Self.EnteredText + Chr(Count)
+					Next
+					' Special case, check for space bar
+					If KeyHit(KEY_SPACE)
+						Self.EnteredText = Self.EnteredText + " "
+					EndIf
+					' Remove a character when user hits backspace.
+					If KeyHit(KEY_BACKSPACE)
+						If Len(Self.EnteredText) > 0
+							Self.EnteredText = Mid(Self.EnteredText,0,Len(Self.EnteredText))
+						EndIf
+					EndIf
+					' If 5 characters are entered, end high score entry.
+					If Len(Self.EnteredText) = 5 Then Self.FinishScoreEntry()
+			
+					If KeyHit(KEY_ENTER)
+						Self.FinishScoreEntry()
+					EndIf
+
+			End Select
+
+		End Method
+'#End Region
+'#Region Method: Render
+		Method Render()
+			Cls
+
+			ScreenPan(0,0)
+
+			SetTransform
+			SetAlpha 1.0
+			SetBlend(SOLIDBLEND)
+			Self.Stars.Draw()
+
+			SetColor 255,255,255
+			SetBlend(ALPHABLEND)
+			DrawImage(Self.Title,0,40)
+			DrawImage(Self.LowerTitle,0,660)
+
+			Local StartPosX:Int = 150
+			Local StartPosY:Int = 245
+
+			Select Self.Viewing
+				Case VIEW_MENU
+					' Showing main menu
+					DrawImage(Self.Options,450,330)
+					SetRotation -90
+					Select Self.HoverOver
+						Case VIEW_START
+							DrawImage(Self.Craft,416,352)
+							
+						Case VIEW_INSTRUCTIONS
+							DrawImage(Self.Craft,416,352 + 37)
+		
+						Case VIEW_HIGHSCORES  
+							DrawImage(Self.Craft,416,352 + 74)
+		
+						Case VIEW_QUIT        
+							DrawImage(Self.Craft,416,352 + 145)
+		
+					End Select
+
+				Case VIEW_HIGHSCORES
+					Scores.Render(385,310)
+
+				Case VIEW_ENTERHIGH
+					DrawTextCentered("CONGRATULATIONS!",StartPosY)
+					DrawTextCentered("You have a new high score!",StartPosY+50)
+					DrawTextCentered("Please enter your name (5 characters)",StartPosY+65)
+					Local TextX:Int = DrawTextCentered(Self.EnteredText,StartPosY+250)
+					DrawRect(TextX + TextWidth(Self.EnteredText), StartPosY+250, 12, 15)
+
+				Case VIEW_INSTRUCTIONS
+					DrawText("Welcome to Digesteroids!",StartPosX,StartPosY)
+					DrawText("------------------------",StartPosX,StartPosY+20)
+					DrawText("An evil biscuit corporation are using their army of sweet snack food to take over the world.",StartPosX,StartPosY+50)
+					DrawText("Fortunately, the ingredients the evil corporation used are vulnerable to big sweaty laser",StartPosX,StartPosY+70)
+					DrawText("blasts. Use your ship's cannon to take out the corporation before they infest the world with",StartPosX,StartPosY+90)
+					DrawText("sweetmeal dunkables biscuits. But be careful, space is small (just big enough to fit inside",StartPosX,StartPosY+110)
+					DrawText("your screen), it is also cyclical, so beware of incoming oval shaped objects.",StartPosX,StartPosY+130)
+
+					DrawText("Keys:",StartPosX,StartPosY+180)
+					DrawText(" - UP CURSOR ARROW     = Thrust",StartPosX,StartPosY+200)
+					DrawText(" - DOWN CURSOR ARROW   = Teleport",StartPosX,StartPosY+220)
+					DrawText(" - LEFT CURSOR ARROW   = Rotate Ship Left",StartPosX,StartPosY+240)
+					DrawText(" - RIGHT CURSOR ARROW  = Rotate Ship Right",StartPosX,StartPosY+260)
+					DrawText(" - ESCAPE              = QUIT",StartPosX,StartPosY+280)
+
+					DrawText("Yes, that's right, it's a glorified asteroids game!",StartPosX,StartPosY+320)
+					SetColor 255,0,0
+					DrawText("Note: Your score is based on the speed at which your ship is travelling when it fired.",StartPosX,StartPosY+340)
+
+			End Select
+		End Method
+'#End Region
+'#Region Method: Start
+		Method Start()
+			Self.MusicChannel = AllocChannel()
+			If Self.Music = Null Then Self.Music = LoadSound("sounds\menu.ogg",True)
+			PlaySound(Self.Music,Self.MusicChannel)
+			SetChannelVolume MusicChannel,1.0
+
+			' Load GFX..
+			If Self.Title = Null Then Self.Title = LoadImage("graphics\title.png")
+			If Self.LowerTitle = Null Then Self.LowerTitle = LoadImage("graphics\lower.png")
+			If Self.Options = Null Then Self.Options = LoadImage("graphics\options.png")
+			If Self.Craft = Null Then Self.Craft = LoadImage("graphics\ship.png")
+
+			' Add a screen full of stars.
+			For Local CountStars:Int = 0 To 1000
+				Local Star:TMenuStar = TMenuStar.Create(World, Self, 10.0)
+				Star.X = Rnd(WIDTH)
+				Self.Stars.AddLast(Star)
+			Next
+		End Method
+'#End Region
+'#Region Method: Finish
+		Method Finish()
+			Self.Stars.Clear()
+			Self.MusicChannel.Stop()
+			Self.MusicChannel = Null
+		End Method
+'#End Region
+	
+	End Type
+'#End Region
+'#Region Scene: MainGame
+	' Main Game scene
+	Type MainGameScene Extends T2DDynamicGameScene
+
+'#Region Declarations
+		Const STARS_PER_SECOND = 3
+		Const FINISH_AT_LEVEL = 6
+		Field Level:Int = 1
+		Field Completed:Int = False
+		
+		Field DigestiveImage:TImage
+		Field ShipImage:TImage
+		Field StarsImage:TImage
+
+		Field LargeImpactSound:TSound
+
+		Field World:TWorldPhysicsProvider = TWorldPhysicsProvider.Create(0.0, 0.0, 0.0, True)
+
+		Field Digestives:TPhysicsProviderCollection = New TPhysicsProviderCollection
+		Field Bullets:TPhysicsProviderCollection = New TPhysicsProviderCollection
+		Field Stars:TPhysicsProviderCollection = New TPhysicsProviderCollection
+
+		Field Chunks:TFountain
+		Const CHUNK_COUNT = 2
+		Field ChunkImages:TImage[CHUNK_COUNT + 1]
+
+		Field Player:TPlayer
+		Field Shake:Float
+
+		Field Pickups:TList = New TList
+'#End Region
+
+'#Region Method: Update
+		Method Update()
+			If KeyHit(KEY_ESCAPE) Then Self.TerminateMainLoop = True
+
+			CheckEndOfLevel()  ' Check if we need to move to the next level (IE, all digestives gone)
+
+			' Add new stars..
+			For Local CountStars:Int = 0 To STARS_PER_SECOND
+				Local Speed:Float = Self.Player.Speed()
+				If Speed < 1.0 Then Speed = 1.0
+				Local Star:TStar = TStar.Create(World, Self, Speed)
+				Self.Stars.AddLast(Star)
+			Next
+
+			' Update screenshake...
+			Self.Shake :/ 1.02
+			If Self.Shake < 0.0 Then Self.Shake = 0.0
+			If Self.Shake > 6.0 Then Self.Shake = 6.0
+
+			' Update all the stars.
+			Self.Stars.ApplyPhysics()
+
+			' Update the chunks
+			Self.Chunks.UpdateWithFriction(TPhysicsProvider.AxisX | TPhysicsProvider.AxisY)
+
+			' Update all the digestives.
+			Self.Digestives.ApplyPhysics()
+
+			' Update all the bullets.
+			Self.Bullets.ApplyPhysics()
+
+			' Update the player.	
+			Self.Player.Update()
+			Self.Player.ApplyFriction(TPhysicsProvider.AxisX | TPhysicsProvider.AxisY)
+			Self.Player.ApplyPhysics()
+		End Method
+'#End Region
+'#Region Method: Render
+		Method Render()
+			SetClsColor 0,0,0
+			Cls
+		
+			If Self.Shake > 0.0 Then
+				ScreenPan(Rnd(Shake),Rnd(Shake))
+			EndIf
+
+			' Draw the spazzy psychodelic effect if it is enabled.
+			Self.DrawPsychodelic()
+	
+			SetAlpha 1
+			SetTransform
+			' Draw all the stars.	
+			Self.Stars.Draw()
+
+			Self.Chunks.Draw()
+
+			' Draw the digestives.
+			SetColor 255,255,255
+			SetBlend(ALPHABLEND)
+			Digestives.Draw()
+
+			' Draw the bullets
+			SetBlend(LIGHTBLEND)
+			Bullets.Draw()
+		
+			' Draw the player.
+			SetBlend(ALPHABLEND)
+			Player.Draw()
+
+			' Draw the hud		
+			DrawHUD()
+		End Method
+'#End Region
+'#Region Method: Start
+		Method Start()
+			AutoMidHandle True
+
+			Self.Shake = 0		
+			Self.Chunks = TFountain.Create(Self.World,TRectangle.Create(0,0,WIDTH,HEIGHT))
+
+			' Load in the required Images...		
+			If Self.DigestiveImage = Null Then Self.DigestiveImage = LoadImage("graphics\digestive.png")
+			If Self.ShipImage = Null Then Self.ShipImage = LoadImage("graphics\ship.png")
+			If Self.StarsImage = Null Then Self.StarsImage = LoadImage("graphics\stars.png")
+
+			For Local LoadCount:Int = 0 To CHUNK_COUNT
+				Self.ChunkImages[LoadCount] = LoadImage("graphics\piece" + String(LoadCount + 1) + ".png")
+			Next
+
+			' Set up the player
+			Self.Player = TPlayer.Create(Self.World, Self.ShipImage,Self)
+			Self.Player.LoadAssets()
+
+			' Load up the sounds.
+			If Self.LargeImpactSound = Null Then Self.LargeImpactSound = LoadSound("Sounds\ImpactLarge.wav")
+
+			' Reset the game..
+			Self.ResetGame()
+
+			' Create all the weapons..
+			Self.Pickups.Clear()
+			Local Bullet1Image:TImage = LoadImage("graphics\bullet1.png")
+			Local Fire1Sound:TSound = LoadSound("sounds\fire.wav")
+
+'			Local Blaster:TWeapon = TWeapon.Create(Speed,Graphic,Size,Radius,Veer,FireRate,MainGame,Sound)
+			Local Blaster:TWeapon = TWeapon.Create(10, Bullet1Image, 0.65, 4.0, 0,30, Self, Fire1Sound, Bullet1Image, "Blaster")
+			Local FastBlaster:TWeapon = TWeapon.Create(9, Bullet1Image, 0.3, 4.0, 0.0, 15, Self, Fire1Sound, Bullet1Image, "Blaster Mk2")
+			Local SprayBlaster:TWeapon = TWeapon.Create(8, Bullet1Image, 0.35, 4.0, 5.0, 1, Self, Fire1Sound, Bullet1Image, "Blaster Mk3")
+
+			Local BlasterPickup:TPickup = TPickup.Create(TPickup.MODIFIER_WEAPON,0,0,True,Blaster,Bullet1Image,0.01,Bullet1Image)
+			Local FastBlasterPickup:TPickup = TPickup.Create(TPickup.MODIFIER_WEAPON,0,0,True,FastBlaster,Bullet1Image,0.01,Bullet1Image)
+			Local SprayBlasterPickup:TPickup = TPickup.Create(TPickup.MODIFIER_WEAPON,0,0,True,SprayBlaster,Bullet1Image,0.008,Bullet1Image)
+
+			Local Score1000Pickup:TPickup = TPickup.Create(TPickup.MODIFIER_SCORE,1000,0,True,Null,Null,0.02,Bullet1Image)
+			Local Score500Pickup:TPickup = TPickup.Create(TPickup.MODIFIER_SCORE,500,0,True,Null,Null,0.03,Bullet1Image)
+
+			Local RotationPickup:TPickup = TPickup.Create(TPickup.MODIFIER_ROTATION,1.1,0,True,Null,Null,0.01,Bullet1Image)
+			Local PsychodelicPickup:TPickup = TPickup.Create(TPickup.MODIFIER_PSYCHODELIC,0,1000,False,Null,Null,0.04,Bullet1Image)
+
+			Local LivesPickup:TPickup = TPickup.Create(TPickup.MODIFIER_LIVES,1,0,True,Null,Null,0.001,Bullet1Image)
+
+			Self.Player.Weapon = Blaster
+
+		End Method
+'#End Region
+'#Region Method: Finish
+		Method Finish()
+			Self.Player.ThrusterChannel.Stop
+			Self.Player.ThrusterChannel = Null
+		End Method
+'#End Region
+	
+'#Region Method: CheckEndOfLevel
+	Method CheckEndOfLevel()
+		If Digestives.IsEmpty() = True
+			' No more digestives..
+			Level :+ 1
+			If Level = FINISH_AT_LEVEL Then	
+				' This was the last level.
+				Self.Completed = True
+				Self.TerminateMainLoop = True
+			Else
+				' Move to the next level
+				GotoLevel(Level)
+			EndIf
+		EndIf
+	End Method
+'#End Region
+'#Region Method: DrawHUD
+	Method DrawHUD()
+		SetColor 255,255,255
+		SetAlpha 1.0
+		SetRotation 0
+		DrawText "Level: " + Level,0,0
+		Local Lives:String = "Lives: " + Player.Lives
+		Local Score:String = "Score: " + Player.Score
+		DrawText Score,(WIDTH / 2) - (TextWidth(Score) / 2) ,0
+		DrawText Lives,(WIDTH - TextWidth(Lives)) ,0
+		
+		Local fr$="000"+Int(Self.Player.Speed()*1000) Mod 1000
+		Local sp$=Int(Self.Player.Speed()*1000)/1000+"."+fr[fr.length-3..]
+		
+		DrawText "Speed: " + sp,0,15
+		DrawText "Teleports: " + Self.Player.Teleports,0,30
+	End Method
+'#End Region
+'#Region Method: ResetGame
+	Method ResetGame()
+		Digestives.Clear()
+		Bullets.Clear()
+		Stars.Clear()
+		Chunks.Particles.Clear()
+		Player.Lives = 5
+		Player.Reset()
+		GotoLevel(1)
+	End Method
+'#End Region
+'#Region Method: GotoLevel
+	Method GotoLevel(ToLevel:Int)
+		Level = ToLevel
+		For Local Count:Int = 1 To ToLevel
+			Local Digestive:TDigestive = TDigestive.Create(World, DigestiveImage,Self)
+			Digestive.X = Rnd(0,WIDTH)
+			Digestive.Y = Rnd(0,HEIGHT)
+			Digestive.Rotation = Rnd(360)
+			Digestive.Weight = 5
+			Digestive.SetVelocityFromAngle(Rnd(360.0),Rnd(0.5,1.5))
+			Digestive.HitSound = Self.LargeImpactSound
+			Digestives.AddLast(Digestive)
+		Next
+	End Method
+'#End Region
+'#Region Method: DrawPsychodelic
+	Field PsychoAngle:Float = 0
+	Field Psycho:Int = False
+	
+	Method DrawPsychodelic()
+		If Psycho = True
+			SetColor Rnd(0.0,255),Rnd(0.0,255),Rnd(0.0,255)
+			PsychoAngle :+ 8
+			SetBlend(SOLIDBLEND)
+			TileImage(StarsImage,Sin(PsychoAngle) * 200,Cos(PsychoAngle) * 200)
+		Else
+			SetColor 255,255,255
+		EndIf
+	End Method
+'#End Region
+'#Region Method: FindSafeLocation
+	Method FindSafeLocation:TPointD(Radius:Float)
+		While True
+			Local X:Int = Rnd(0,WIDTH)
+			Local Y:Int = Rnd(0,HEIGHT)
+			If Self.IsLocationSafe(X,Y,Radius)
+				Local Out:TPointD = New TPointD
+				Out.X = X
+				Out.Y = Y
+				Return Out
+			EndIf
+		Wend
+	End Method
+'#End Region
+'#Region Method: IsLocationSafe
+	Method IsLocationSafe:Int(X:Int,Y:Int,Radius:Float)
+		Local TryCircle:TCircle = New TCircle
+		TryCircle.X = X
+		TryCircle.Y = Y
+		TryCircle.Radius = Radius
+		For Local Item:TDigestive = EachIn Self.Digestives
+			If Item.Circle.CollidesWith(TryCircle)
+				Return False
+			EndIf
+		Next
+		Return True
+	End Method
+'#End Region
+
+	End Type
+'#End Region
+'#Region Scene: Ending
+	' The ending scene.
+	Type EndingScene Extends T2DDynamicGameScene
+	
+'#Region Declarations
+		Const STARS_PER_SECOND = 0
+		Const MAGNET_COUNT = 5
+		Field ScrollPoint:Float
+		Field Music:TSound
+		Field MusicChannel:TChannel
+		Field Title:TImage
+		Field Time:Int = 1000000
+
+		Field TEXTS_COUNT = 23
+		Field Texts:String[TEXTS_COUNT]
+
+		Field Stars:TPhysicsProviderCollection = New TPhysicsProviderCollection
+		Field World:TWorldPhysicsProvider = TWorldPhysicsProvider.Create(0.2, 0.0, 0.0, True)
+'#End Region
+
+'#Region Method: Update
+		Method Update()
+			Self.Stars.ApplyPhysics()
+
+			If KeyHit(KEY_ESCAPE) Then 
+				Self.TerminateMainLoop = True
+			EndIf
+
+			Self.Time :+ 1
+			If Self.Time > 100 Then
+				Self.Time = 0
+				' Add a new magnet
+				Local ThisMagnet:TEndMagnet = New TEndMagnet
+				ThisMagnet.X = Rnd(WIDTH)
+				ThisMagnet.Y = Rnd(HEIGHT)
+				ThisMagnet.Radius = Rnd(200,500)
+				ThisMagnet.Polarity = TMagnet.PositivePolarity
+				ThisMagnet.Strength = Rnd(0.1,0.5)
+				Self.World.Magnets.AddLast(ThisMagnet)
+				Self.World.ApplyMagnets = True
+			EndIf
+
+
+			Local X:Int = WIDTH/2
+			Local Y:Int = HEIGHT/2
+			For Local Magnet:TEndMagnet = EachIn Self.World.Magnets
+				For Local Count:Int = 0 To STARS_PER_SECOND
+					Local ThisStar:TEndingStar = TEndingStar.Create(Self.World, Self, Rnd(1.0,5.0), Magnet.X + (Sin(Rnd(360.0)) * 5.0), Magnet.Y + (Cos(Rnd(360.0)) * 5.0))
+					ThisStar.SetVelocityFromAngle(Rnd(360),Rnd(1.0,5.0))
+					Self.Stars.AddLast(ThisStar)
+					Magnet.Stars :+ 1
+				Next
+				If Magnet.Stars > 500 Then
+					Self.World.Magnets.Remove(Magnet)
+				EndIf
+			Next
+
+			' Update the scoller
+			Self.ScrollPoint :- 0.5
+			If Self.Scrollpoint	< -((TEXTS_COUNT * 25) + 100)
+				Self.ScrollPoint = HEIGHT + 20
+			EndIf
+			
+		End Method
+'#End Region
+'#Region Method: Render
+		Method Render()
+			Cls
+
+			Self.Stars.Draw()
+
+			SetColor 255,255,255
+			For Local Count:Int = 0 To TEXTS_COUNT - 1
+				MainMenuScene.DrawTextCentered(Self.Texts[Count],Self.ScrollPoint + (25 * Count))
+			Next
+		End Method
+'#End Region
+'#Region Method: Start
+		Method Start()
+			Self.MusicChannel = AllocChannel()
+			If Self.Music = Null Then Self.Music = LoadSound("sounds\ending.ogg",True)
+			If Self.MusicChannel <> Null
+				PlaySound(Self.Music,Self.MusicChannel)
+			EndIf
+
+			Self.Texts[0] = "Cast of Characters"
+			Self.Texts[1] = "------------------"
+			Self.Texts[2] = ""
+			Self.Texts[3] = "Space Craft.......................Intrepid"
+			Self.Texts[4] = "Digestive...............Sweet Meal Biscuit"
+			Self.Texts[5] = "Star...............................Himself"
+			Self.Texts[6] = ""
+			Self.Texts[7] = "Written by Rob Hutchinson in roughly 5 days."
+			Self.Texts[8] = "Email: [email protected]"
+			Self.Texts[9] = "Web: http://www.proteanide.co.uk/"
+			Self.Texts[10] = ""
+			Self.Texts[11] = "Special Thanks To"
+			Self.Texts[12] = "-----------------"
+			Self.Texts[13] = "Richard Makepeace.............Inspiration"
+			Self.Texts[14] = "James Readman.................Inspiration"
+			Self.Texts[15] = ""
+			Self.Texts[16] = "Greets"
+			Self.Texts[17] = "------"
+			Self.Texts[18] = "Pickup, Cabsy, Peters, ZanaX, Compona, Helen, + all other family and friends."
+			Self.Texts[19] = ""
+			Self.Texts[20] = ""
+			Self.Texts[21] = ""
+			Self.Texts[22] = "Thank you for playing."
+
+			Self.ScrollPoint = HEIGHT + 20
+		End Method
+'#End Region
+'#Region Method: Finish
+		Method Finish()
+			Self.Stars.Clear()
+			If Self.MusicChannel <> Null Then
+				Self.MusicChannel.Stop()
+				Self.MusicChannel = Null
+			EndIf
+		End Method
+'#End Region
+
+	End Type
+'#End Region
+
+Type TCircle
+	
+	Field X:Int
+	Field Y:Int
+	Field Radius:Float
+	
+	Method CollidesWith:Int(Circle:TCircle) 
+		Local Distance:Double = TPhysicsUtility.DistanceBetweenPoints(Self.X,Self.Y,Circle.X,Circle.Y)
+		If Distance < (Self.Radius + Circle.Radius) Then Return True
+		Return False
+	End Method
+
+	Method Draw()
+		SetBlend(ALPHABLEND)
+		SetAlpha(0.5)
+		SetRotation 0
+		DrawCircle(X,Y,Int(Radius))
+	End Method
+
+	Function DrawCircle(X,Y,Radius)
+		DrawOval(X - Radius,Y - Radius,Radius * 2,Radius * 2)
+	End Function
+
+End Type
+Type TDigestive Extends TPhysicsProvider
+	Field Image:TImage
+	Field Alpha:Float = 1.0
+	Field Rotation:Float = 0
+	Field Scale:Float = 1.0
+	Field Circle:TCircle = New TCircle
+	Field GameScene:MainGameScene
+	Const InitialRadius:Float = 70.0
+	Field HitSound:TSound
+
+	Method SplitIntoChunks(X:Int,Y:Int,Multiplier:Float)
+		GameScene.Digestives.Remove(Self)
+
+		Local Channel:TChannel = AllocChannel()
+		If Channel <> Null
+			Channel.SetVolume(Self.Scale)
+			PlaySound(Self.HitSound, Channel)
+		EndIf
+
+		Self.GameScene.Chunks.X = X
+		Self.GameScene.Chunks.Y = Y
+		
+		Self.GameScene.Player.Score :+ Int(((Self.Scale * 100.0) + 4.0) * Multiplier)
+
+		Self.GameScene.Shake :+ (Self.Scale * 3.5)
+		
+
+		If Self.Scale > 0.2
+			For Local Count:Int = 0 To Int(Self.Scale * 5)
+				Local Digestive:TDigestive = TDigestive.Create(Self.World, Self.Image, Self.GameScene)
+				Digestive.X = Self.X
+				Digestive.Y = Self.Y
+				Digestive.Rotation = Rnd(360)
+				Digestive.Weight = 5
+				Digestive.SetVelocityFromAngle(Rnd(360.0),Rnd(0.5,1.5))
+				Digestive.SetDrawScale(Self.Scale / 2)
+				Digestive.HitSound = Self.HitSound
+				GameScene.Digestives.AddLast(Digestive)
+			Next
+
+			For Local ChunkCount:Int = 0 To 4 * (Self.Scale * 10)
+				Local Particle:TStaticParticle = Self.GameScene.Chunks.AddStaticParticle(Self.GameScene.ChunkImages[(Int(Rnd(0,MainGameScene.CHUNK_COUNT)))], Rnd(360.0), Rnd(0.5,10.0), 0, 0.01, [255,255,255])
+				Particle.Friction = 0.97
+				Particle.Size = Rnd(0.5,1.0)
+				Particle.Rotation = Rnd(-5.0,5.0)
+			Next
+		Else
+			For Local ChunkCount2:Int = 0 To Rnd(3,8)
+				Local Particle:TStaticParticle = Self.GameScene.Chunks.AddStaticParticle(Self.GameScene.ChunkImages[(Int(Rnd(0,MainGameScene.CHUNK_COUNT)))], Rnd(360.0), Rnd(0.5,10.0), 0, 0.01, [255,255,255])
+				Particle.Friction = 0.97
+				Particle.Size = Rnd(0.2,0.5)
+				Particle.Rotation = Rnd(-5.0,5.0)
+			Next
+		End If
+	End Method
+
+	Method Draw()
+		SetAlpha Self.ALPHA
+		SetRotation Self.Rotation
+		SetScale Self.Scale,Self.Scale
+		DrawImage(Self.Image, Self.X, Self.Y)
+		SetScale 1,1
+	End Method
+
+	Method SetDrawScale(Scale:Float)
+		Self.Scale = Scale
+		Self.Circle.Radius = Self.InitialRadius * Self.Scale
+	End Method
+
+	Method PhysicsApplied()
+		Rotation:+2
+		Local HalfWidth:Int = ((Image.Width * Self.Scale) / 2)
+		Local HalfHeight:Int = ((Image.Height * Self.Scale) / 2)
+		If X < -HalfWidth Then X = WIDTH + HalfWidth
+		If Y < -HalfHeight Then Y = HEIGHT + HalfHeight
+		If X > WIDTH + HalfWidth Then X = -HalfWidth
+		If Y > HEIGHT + HalfHeight Then Y = -HalfHeight
+		Self.Circle.X = Self.X
+		Self.Circle.Y = Self.Y
+	End Method
+
+	Function Create:TDigestive(World:TWorldPhysicsProvider,Image:TImage,GameScene:MainGameScene)
+		Local Out:TDigestive = New TDigestive
+		Out.GameScene = GameScene
+		Out.Circle.Radius = InitialRadius
+		Out.World = World
+		Out.Image = Image
+		Return Out
+	End Function
+
+End Type
+Type TPlayer Extends TPhysicsProvider
+
+	Field Lives:Int
+	Field Score:Int
+	Field Weapon:TWeapon
+	Field Dead:Int = True
+	Field Teleports:Int = 2
+
+	Field Image:TImage
+	Field Rotation:Float
+	Field Acceleration:Float
+	Field MaxAcceleration:Float = 4.0
+	Field Motion:Float = 0.001
+	Field AccelerationDropMultiplier:Float = 8
+	Field Circle:TCircle = New TCircle
+	Field GameScene:MainGameScene
+
+	Field FireRateCount:Float = 0
+
+	Field Thruster:TFountain
+	Field Death:TFountain
+
+	Field ThrusterImage:TImage
+	Field DeathImage:TImage
+	Field SparkleImage:TImage
+	Field ThrusterSound:TSound
+	Field CrashSound:TSound
+	Field ThrusterChannel:TChannel
+	Field TeleportSound:TSound
+
+	Method Draw()
+		SetTransform
+		' Render the thruster...
+		SetBlend LIGHTBLEND
+		SetScale 0.6, 0.6
+		SetAlpha 1.0
+		Self.Thruster.Draw()
+		Self.Death.Draw()
+
+		If Not Self.Dead Then
+			' Render the player.
+			SetBlend ALPHABLEND
+			SetAlpha 1.0
+			SetScale 1.0,1.0
+			SetColor 255,255,255
+			SetRotation 360 - Self.Rotation
+			DrawImage(Self.Image, Self.X, Self.Y)
+		EndIf
+	End Method
+
+	Method PhysicsApplied()
+		Local HalfWidth:Int = (Image.Width / 2)
+		Local HalfHeight:Int = (Image.Height / 2)
+		If X < -HalfWidth Then X = WIDTH + HalfWidth
+		If Y < -HalfHeight Then Y = HEIGHT + HalfHeight
+		If X > WIDTH + HalfWidth Then X = -HalfWidth
+		If Y > HEIGHT + HalfHeight Then Y = -HalfHeight
+		Self.Circle.X = Self.X
+		Self.Circle.Y = Self.Y
+	End Method
+
+	Method Update()
+		' Update thruster and death particles.
+		Local Particle:TStaticParticle = Self.Thruster.AddStaticParticle(Self.ThrusterImage, Self.Rotation + 180 + Rnd(-5.0,5.0), 1.0, 1, 0.04, [255,255,255])
+	
+		Local ActualRotation:Float = Self.Rotation + 180
+
+		Self.Thruster.X = Self.X + (Sin(ActualRotation) * 10) 
+		Self.Thruster.Y = Self.Y + (Cos(ActualRotation) * 10) 
+
+		Local ThisSpeed:Float = Self.Speed()
+		
+		If ThisSpeed < 0 Then ThisSpeed = 0 
+		If ThisSpeed > 10.0 Then ThisSpeed = 10.0
+		
+		' Add particles to the thruster..
+		Particle.Size = (ThisSpeed / 10)
+		Self.ThrusterChannel.SetVolume(ThisSpeed / 10)
+		Self.ThrusterChannel.SetPan(Float(Self.X / Float(WIDTH / 2.0) - 1.0))
+		Self.GameScene.Shake:+ 0.10 * (ThisSpeed / 10.0)
+
+		Self.Thruster.Update()
+		Self.Death.UpdateWithFriction(TPhysicsProvider.AxisX | TPhysicsProvider.AxisY)
+	
+		If Self.Dead
+			' Player is currently dead, wait for a nice chance to put them back down.
+			If Self.Lives < 1 Then
+				' Wait for the death particles to become zeroed.
+				If Self.Death.Particles.IsEmpty() Then
+					Self.GameScene.TerminateMainLoop = True
+				EndIf
+			Else
+				' Check to see if the center spot is safe to plop the ship on.
+				If Self.GameScene.IsLocationSafe(WIDTH/2, HEIGHT/2, 70)
+					Self.Dead = False
+					For Local Pops:Int = 0 To 200
+						Local Particle:TStaticParticle = Self.Death.AddStaticParticle(Self.SparkleImage, Rnd(360.0), Rnd(0.5,2.0), 0, Rnd(0.01, 0.03), [255,255,255])
+						Particle.X = Self.X
+						Particle.Y = Self.Y
+						Particle.Rotation = Rnd(-1.0,1.0)
+						Particle.Friction = 0.97
+						Particle.Size = Rnd(0.1,0.2)
+					Next
+				EndIf
+			EndIf
+		Else
+			' Update fire rate counter...
+			Self.FireRateCount :- 1
+			
+			If KeyDown(KEY_UP)
+				' Thrust
+				Self.Acceleration :+ Self.Motion
+				' Clamp the acceleration
+				If Self.Acceleration > Self.MaxAcceleration Then Self.Acceleration = Self.MaxAcceleration
+				' Add acceleration.
+				Self.IncreaseVelocityFromAngle(Self.Rotation,Self.Acceleration)
+			Else
+				Self.Acceleration :- (Self.Motion * Self.AccelerationDropMultiplier)
+				If Self.Acceleration < 0.0 Then Self.Acceleration = 0
+			EndIf
+	
+			If KeyDown(KEY_LEFT)
+				' RotateLeft
+				Self.Rotation :+ 3
+			EndIf
+	
+			If KeyDown(KEY_RIGHT)
+				' RotateLeft
+				Self.Rotation :- 3
+			EndIf
+	
+			If KeyDown(KEY_SPACE)
+				' FIRE! 
+				If Self.FireRateCount <= 0 Then
+					Self.FireRateCount = Self.Weapon.FireRate
+					Self.Weapon.Fire(Self)
+				End If
+			EndIf
+	
+			If KeyHit(KEY_DOWN)
+				' TELEPORT
+				If Self.Teleports > 0 Then
+					Self.Teleports :- 1
+					Local Location:TPointD = Self.GameScene.FindSafeLocation(20)
+
+					PlaySound(Self.TeleportSound)
+					Self.Death.X = Self.X
+					Self.Death.Y = Self.Y
+
+					For Local Pops:Int = 0 To 50
+						Local Particle:TStaticParticle = Self.Death.AddStaticParticle(Self.SparkleImage, Rnd(360.0), Rnd(0.5,10.0), 0, 0.02, [255,255,255])
+						Particle.X = Self.X
+						Particle.Y = Self.Y
+						Particle.Rotation = Rnd(-1.0,1.0)
+						Particle.Friction = 0.97
+						Particle.Size = Rnd(0.1,0.2)
+					Next
+
+					Self.X = Int(Location.X)
+					Self.Y = Int(Location.Y)
+					Self.Death.X = Self.X
+					Self.Death.Y = Self.Y
+
+					For Local Pops:Int = 0 To 20
+						Local Particle:TStaticParticle = Self.Death.AddStaticParticle(Self.SparkleImage, Rnd(360.0), Rnd(0.5,2.0), 0, 0.02, [255,255,255])
+						Particle.X = Self.X
+						Particle.Y = Self.Y
+						Particle.Rotation = Rnd(-1.0,1.0)
+						Particle.Friction = 0.97
+						Particle.Size = Rnd(0.1,0.2)
+					Next
+
+				EndIf
+			EndIf
+	
+			' COLLISION DETECT AGAINST DIGESTIVES!
+			' ---------------------------------------------------------
+			For Local Item:TDigestive = EachIn Self.GameScene.Digestives
+				If Item.Circle.CollidesWith(Self.Circle)
+					Self.Lives :- 1
+					Self.Dead = True
+					PlaySound(Self.CrashSound)
+	
+					Local PlayerSpeed:Double = Self.Speed()
+
+					Self.GameScene.Shake :+ PlayerSpeed / 1.5
+					If PlayerSpeed > 10.0
+						Item.SplitIntoChunks(Self.X,Self.Y,Self.Speed())
+					EndIf
+
+					Self.Death.X = Self.X
+					Self.Death.Y = Self.Y
+					For Local Pops:Int = 0 To 100
+						If PlayerSpeed > 10 Then PlayerSpeed = 10
+						Local Angle:Double = Self.Angle() + Rnd(-(80 - (8.0 * PlayerSpeed)), 80 - (8.0 * PlayerSpeed))
+						Local Particle:TStaticParticle = Self.Death.AddStaticParticle(Self.DeathImage, Angle, Rnd(0.1, Self.Speed()), 0, 0.03, [255,255,255])
+						Particle.Friction = 0.98
+						Particle.Size = Rnd(0.1,0.7)
+						Particle.Color = [Rand(80,255),24,Rand(24,128)]
+					Next
+
+					For Local Pops:Int = 0 To 300
+						Local Particle:TStaticParticle = Self.Death.AddStaticParticle(Self.DeathImage, Rnd(360.0), Rnd(0.1, 7.0), 0, Rnd(0.01,0.04), [255,255,255])
+						Particle.Friction = 0.99
+						Particle.Size = Rnd(0.1,0.4)
+						Particle.Color = [Rand(80,255),24,Rand(24,200)]
+					Next
+
+					Self.Reset()
+				EndIf
+			Next
+		EndIf
+
+	End Method
+
+	Method Reset()
+		Self.X = WIDTH / 2
+		Self.Y = HEIGHT / 2
+		Self.Acceleration = 0
+		Self.VelocityX = 0
+		Self.VelocityY = 0
+		Self.Teleports = 2
+		Self.Dead = 2
+	End Method
+
+	Method LoadAssets()
+		If Self.ThrusterImage = Null Then Self.ThrusterImage = LoadImage("graphics\bullet1.png")
+		If Self.DeathImage = Null Then Self.DeathImage = LoadImage("graphics\pop.png")
+		If Self.SparkleImage = Null Then Self.SparkleImage = LoadImage("graphics\sparkle.png")
+
+		' Load in the sounds...
+		If Self.ThrusterSound = Null Then Self.ThrusterSound = LoadSound("sounds\thrust.wav",True)
+		If Self.CrashSound = Null Then Self.CrashSound = LoadSound("sounds\crash.wav",False)
+		If Self.TeleportSound = Null Then Self.TeleportSound = LoadSound("sounds\teleport.wav",False)
+		
+		Self.ThrusterChannel = AllocChannel()
+		Self.ThrusterChannel.SetVolume(0)
+		PlaySound(Self.ThrusterSound,Self.ThrusterChannel)
+	End Method
+
+	Function Create:TPlayer(World:TWorldPhysicsProvider, Image:TImage, GameScene:MainGameScene)
+		Local Out:TPlayer = New TPlayer
+		Out.Thruster = TFountain.Create(World, TRectangle.Create(0,0,WIDTH,HEIGHT))
+		Out.Death = TFountain.Create(World, TRectangle.Create(0,0,WIDTH,HEIGHT))
+		Out.GameScene = GameScene
+		Out.Circle.Radius = 10
+		Out.World = World
+		Out.Image = Image
+		Out.Friction = 0.985
+		Out.Reset()
+		Return Out
+	End Function
+
+End Type
+Type TBullet Extends TPhysicsProvider
+
+	Field Image:TImage
+	Field Circle:TCircle = New TCircle
+	Field GameScene:MainGameScene
+	Field Size:Float = 1.0
+	Field ScoreMultiplier:Float = 1
+	Field TTL:Int
+	Field Fading:Float = 1.0
+
+	Const TIME_TO_LIVE = 100
+
+	Method Draw()
+		SetScale(Self.Size,Self.Size)
+		SetAlpha(Self.Fading)
+		DrawImage(Self.Image, Self.X, Self.Y)
+	End Method
+
+	Method PhysicsApplied()
+		Local HalfWidth:Int = (Image.Width / 2)
+		Local HalfHeight:Int = (Image.Height / 2)
+		If X < -HalfWidth Then X = WIDTH + HalfWidth
+		If Y < -HalfHeight Then Y = HEIGHT + HalfHeight
+		If X > WIDTH + HalfWidth Then X = -HalfWidth
+		If Y > HEIGHT + HalfHeight Then Y = -HalfHeight
+
+		' Shall we kill it off?
+		Self.TTL :+ 1
+		If Self.TTL > TIME_TO_LIVE Then
+			Self.Fading :- 0.015
+			If Self.Fading < 0.05
+				Self.GameScene.Bullets.Remove(Self)
+			EndIf
+		EndIf
+		
+'		If X < -HalfWidth Or Y < -HalfHeight Or X > WIDTH + HalfWidth Or Y > HEIGHT + HalfHeight Then 
+	'		' Bullet went off't screen
+		'	Self.GameScene.Bullets.Remove(Self)
+	'	EndIf
+
+		Self.Circle.X = Self.X
+		Self.Circle.Y = Self.Y
+
+		' COLLISION DETECT AGAINST DIGESTIVES!
+		' ---------------------------------------------------------
+		For Local Item:TDigestive = EachIn Self.GameScene.Digestives
+			If Item.Circle.CollidesWith(Self.Circle)
+				If Self.Fading > 0.5
+					Self.GameScene.Bullets.Remove(Self)
+					Item.SplitIntoChunks(Self.Circle.X,Self.Circle.Y,Self.ScoreMultiplier)
+					Exit
+				EndIf
+			EndIf
+		Next
+	End Method
+
+	Function Create:TBullet(World:TWorldPhysicsProvider, Image:TImage, Player:TPlayer, Speed:Float, GameScene:MainGameScene, Size:Float, Radius:Float, Veer:Float)
+		Local Out:TBullet = New TBullet
+		Out.GameScene = GameScene
+		Out.SetVelocityFromAngle(Player.Rotation + Rnd(-Veer,Veer),Speed)
+		Out.Size = Size
+		Out.Circle.Radius = Radius * Size
+		Out.X = Player.X
+		Out.Y = Player.Y
+		Out.World = World
+		Out.Image = Image
+		Return Out
+	End Function
+
+End Type
+Type TStar Extends TPhysicsProvider
+
+	Field Color[]
+	Field GameScene:MainGameScene
+	
+	Method Draw()
+		SetColor Self.Color[0],Self.Color[1],Self.Color[2]
+		DrawRect(X,Y,1,1)
+	End Method
+
+	Method PhysicsApplied()
+		Self.VelocityX :* 1.01
+		Self.VelocityY :* 1.01
+	
+		Local HalfWidth:Int = 5
+		Local HalfHeight:Int = 5
+		If X < -HalfWidth Or Y < -HalfHeight Or X > WIDTH + HalfWidth Or Y > HEIGHT + HalfHeight Then 
+			' Bullet went off't screen
+			Self.GameScene.Stars.Remove(Self)
+		EndIf
+	End Method
+
+	Function Create:TStar(World:TWorldPhysicsProvider,GameScene:MainGameScene, Speed:Float)
+		Local Out:TStar = New TStar
+		Local Pigment = Rand(24,255)
+		Out.GameScene = GameScene
+		Out.Color = [Pigment,Pigment,Pigment]
+		Out.SetVelocityFromAngle(Rnd(360),Rnd(0.2,0.9) * Speed)
+		Out.X = WIDTH / 2
+		Out.Y = HEIGHT / 2
+		Out.World = World
+		Return Out
+	End Function
+
+End Type
+Type TMenuStar Extends TPhysicsProvider
+
+	Field Color[]
+	Field MenuScene:MainMenuScene
+	
+	Method Draw()
+		SetColor Self.Color[0],Self.Color[1],Self.Color[2]
+		DrawRect(X,Y,1,1)
+	End Method
+
+	Method PhysicsApplied()
+'		Self.VelocityX :* 1.01
+'		Self.VelocityY :* 1.01
+	
+		Local HalfWidth:Int = 5
+		Local HalfHeight:Int = 5
+		If X < -HalfWidth Or Y < -HalfHeight Or X > WIDTH + HalfWidth Or Y > HEIGHT + HalfHeight Then 
+			' Bullet went off't screen
+			Self.MenuScene.Stars.Remove(Self)
+		EndIf
+	End Method
+
+	Function Create:TMenuStar(World:TWorldPhysicsProvider,MenuScene:MainMenuScene, Speed:Float)
+		Local Out:TMenuStar = New TMenuStar
+		Local Pigment = Rand(24,255)
+		Out.MenuScene = MenuScene
+		Out.Color = [Pigment,Pigment,Pigment]
+		Out.SetVelocityFromAngle(90,Rnd(0.2,0.9) * Speed)
+		Out.X = 0
+		Out.Y = Rnd(HEIGHT)
+		Out.World = World
+		Return Out
+	End Function
+
+End Type
+Type TEndingStar Extends TPhysicsProvider
+
+	Field Color[]
+	Field EndScene:EndingScene
+	
+	Method Draw()
+		SetColor Self.Color[0],Self.Color[1],Self.Color[2]
+		DrawRect(X,Y,1,1)
+	End Method
+
+	Method PhysicsApplied()
+'		Self.VelocityX :* 1.01
+'		Self.VelocityY :* 1.01
+	
+		Local HalfWidth:Int = 5
+		Local HalfHeight:Int = 5
+		If X < -HalfWidth Or Y < -HalfHeight Or X > WIDTH + HalfWidth Or Y > HEIGHT + HalfHeight Then 
+			' Bullet went off't screen
+			Self.EndScene.Stars.Remove(Self)
+		EndIf
+	End Method
+
+	Function Create:TEndingStar(World:TWorldPhysicsProvider, EndScene:EndingScene, Speed:Float, X:Int, Y:Int)
+		Local Out:TEndingStar = New TEndingStar
+		Local Pigment = Rand(24,255)
+		Out.EndScene = EndScene
+		Out.Color = [Pigment,Pigment,Pigment]
+		Out.X = X
+		Out.Y = Y
+		Out.World = World
+		Return Out
+	End Function
+
+End Type
+Type TPickup Extends TLink
+
+	Const MODIFIER_SCORE = 0
+	Const MODIFIER_LIVES = 1 
+	Const MODIFIER_ROTATION = 2 
+	Const MODIFIER_PSYCHODELIC = 3
+	Const MODIFIER_WEAPON = 4
+
+	Field Modifies:Int = MODIFIER_SCORE
+	Field ByValue:Float
+	Field LastsFor:Int
+	Field Time:Int
+	Field IsPermanent:Int = False
+	Field Weapon:TWeapon
+	Field Icon:TImage
+	Field Probability:Double
+	Field OwnedIcon:TImage
+
+	Method Clone:TPickup()
+		Local Out:TPickup = New TPickup
+		Out.Modifies = Self.Modifies
+		Out.ByValue = Self.ByValue
+		Out.LastsFor = Self.LastsFor
+		Out.IsPermanent = Self.IsPermanent
+		Out.Weapon = Self.Weapon
+		Out.Icon = Self.Icon
+		Out.Probability = Self.Probability
+		Out.OwnedIcon = Out.OwnedIcon
+		Return Out
+	End Method
+
+	Function Create:TPickup(Modifies:Int,ByValue:Float,LastsFor:Int,IsPermanent:Int,Weapon:TWeapon,Icon:TImage,Probability:Double,OwnedIcon:TImage)
+		Local Out:TPickup = New TPickup
+		Out.Modifies = Modifies
+		Out.ByValue = ByValue
+		Out.LastsFor = LastsFor
+		Out.IsPermanent = IsPermanent
+		Out.Weapon = Weapon
+		Out.Icon = Icon
+		Out.Probability = Probability
+		Out.OwnedIcon = OwnedIcon
+		Return Out
+	End Function
+
+End Type
+Type TWeapon Extends TLink
+
+	Field Speed:Float
+	Field Graphic:TImage
+	Field Size:Float
+	Field Radius:Float
+	Field Veer:Float
+	Field FireRate:Int
+	Field MainGame:MainGameScene
+	Field Sound:TSound
+	Field Name:String
+	Field Icon:TImage
+
+	Method Fire(Player:TPlayer)
+		Local Shot:TBullet = TBullet.Create(MainGame.World, Self.Graphic, Player, Self.Speed + Player.Speed(), Self.MainGame, Self.Size, Self.Radius, Self.Veer)
+		Shot.ScoreMultiplier = (Player.Speed() * 4.0)
+		If Shot.ScoreMultiplier < 1.0 Then Shot.ScoreMultiplier = 1.0
+		Self.MainGame.Bullets.AddLast(Shot)
+		PlaySound(Self.Sound)
+	End Method
+
+	Function Create:TWeapon(Speed:Float,Graphic:TImage,Size:Float,Radius:Float,Veer:Float,FireRate:Int,MainGame:MainGameScene,Sound:TSound,Icon:TImage,Name:String)
+		Local Out:TWeapon = New TWeapon
+		Out.Speed = Speed
+		Out.Graphic = Graphic
+		Out.Size = Size
+		Out.Radius = Radius
+		Out.Veer = Veer
+		Out.FireRate = FireRate
+		Out.MainGame = MainGame
+		Out.Sound = Sound
+		Out.Icon = Icon
+		Out.Name = Name
+		Return Out
+	End Function
+
+End Type
+Type TEndMagnet Extends TMagnet
+	
+	Field Stars:Int
+	
+End Type
+Type THighScores
+
+	Const SCORE_COUNT = 10
+	Field Scores:Int[SCORE_COUNT]
+	Field Names:String[SCORE_COUNT]
+
+	Method Render(X:Int, Y:Int)
+		For Local Count:Int = 0 To SCORE_COUNT - 1
+			DrawText(Self.Names[Count], X,Y + (Count * 20))
+		Next
+
+		For Local Count2:Int = 0 To SCORE_COUNT - 1
+			DrawText(Self.Scores[Count2], X + 200,Y + (Count2 * 20))
+		Next
+	End Method
+
+	Method IsHighScore:Int(Score:Int)
+		For Local Count:Int = 0 To SCORE_COUNT - 1
+			If Score > Self.Scores[Count] Then
+				Return True
+			EndIf
+		Next
+		Return False
+	End Method
+	
+	Method Add(Score:Int,Name:String)
+		' Find out where we should put the score..
+		Local PlaceAt:Int = SCORE_COUNT - 1
+		For Local Count:Int = SCORE_COUNT - 1 To 0 Step -1
+			If Score > Self.Scores[Count] Then
+				PlaceAt = Count
+			EndIf
+		Next
+
+		' Shuffle them all down..
+		For Local Shuffle:Int = SCORE_COUNT - 2 To PlaceAt Step -1
+			Self.Scores[Shuffle + 1] = Self.Scores[Shuffle]
+			Self.Names[Shuffle + 1] = Self.Names[Shuffle]
+		Next
+
+		Self.Scores[PlaceAt] = Score
+		Self.Names[PlaceAt] = Name
+	End Method
+
+	Method Save(File:String)
+		Local Out:TStream = OpenStream(File,False,True)
+		If Out <> Null
+			For Local Count:Int = 0 To SCORE_COUNT - 1
+				WriteInt(Out,Self.Scores[Count])
+				WriteLine(Out,Self.Names[Count])
+			Next
+			CloseStream(Out)
+		EndIf
+	End Method
+
+	Function Load:THighScores(File:String)
+		Local In:TStream = OpenStream(File,True,False)
+		Local Out:THighScores = New THighScores
+		If In = Null
+			' Fill it full of high scores..
+			Out.Names[0] = "Loki"
+			Out.Names[1] = "Booty"
+			Out.Names[2] = "Rix"
+			Out.Names[3] = "Jamez"
+			Out.Names[4] = "Pies"
+			Out.Names[5] = "Bobny"
+			Out.Names[6] = "Berty"
+			Out.Names[7] = "Billy"
+			Out.Names[8] = "Bonny"
+			Out.Names[9] = "Boxer"
+
+			Out.Scores[0] = 100000
+			Out.Scores[1] = 80000
+			Out.Scores[2] = 50000
+			Out.Scores[3] = 40000
+			Out.Scores[4] = 30000
+			Out.Scores[5] = 20000
+			Out.Scores[6] = 10000
+			Out.Scores[7] = 5000
+			Out.Scores[8] = 4000
+			Out.Scores[9] = 1000
+		Else
+			For Local Count:Int = 0 To SCORE_COUNT - 1
+				Out.Scores[Count] = ReadInt(In)
+				Out.Names[Count] = ReadLine(In)
+			Next
+			CloseStream(In)
+		EndIf
+		Return Out
+	End Function
+
+End Type
+
+' Main game setup.
+' ------------------------
+Local Game:T2DDynamicGame = T2DDynamicGame.Create(WIDTH, HEIGHT, DEPTH, REFRESHRATE)
+Global Scores:THighScores = THighScores.Load("hi.dat")
+Game.DynamicTiming = DYNAMICTIMING
+Game.DesiredFPS = DESIREDFPS
+Game.Initialize()	' Go into graphics mode.
+
+HideMouse
+
+' Create the main game scene.
+Local MenuScene:MainMenuScene = New MainMenuScene
+Local GameScene:MainGameScene = New MainGameScene
+Local EndScene:EndingScene = New EndingScene
+
+Local QuitFlag:Int = False
+
+While Not QuitFlag
+	' Attach The Game Scene To The Dynamic Game.
+	Game.Setscene(Menuscene)
+	
+	' Start The Main Loop.
+	Game.Mainloop()
+
+	If Not MenuScene.Exitgame
+		If MenuScene.ShowCredits
+			MenuScene.ShowCredits = False
+			Game.SetScene(EndScene)
+			Game.Mainloop()
+		Else
+			Game.Setscene(GameScene)
+			Game.Mainloop()
+
+			QFlushKeys()
+
+			If GameScene.Completed
+				GameScene.Completed = False
+				Game.SetScene(EndScene)
+				Game.Mainloop()
+			EndIf
+
+			QFlushKeys()
+
+			' Check for high score.
+			If Scores.IsHighScore(GameScene.Player.Score) Then
+				' We got a high score.
+				MenuScene.ViewMenu(MenuScene.VIEW_ENTERHIGH)
+				MenuScene.LastHighScore = GameScene.Player.Score
+				MenuScene.EnteredText = ""
+			EndIf
+
+		EndIf
+	Else
+		QuitFlag = True
+	EndIf
+Wend
+
+Scores.Save("hi.dat")
+
+' Clean up after we shut down.
+Game.ShutDown()
+
+' Fin
+
+Const DEBUG = True
+Function DebugLog(Text:String)
+	If DEBUG Then Print Text
+End Function
+
+
+
+
+
+

+ 203 - 0
samples/digesteroids/dynamicgame.bmx

@@ -0,0 +1,203 @@
+' *******************************************************************
+' Source: Dynamic Game 
+' Version: 1.00
+' Author: Rob Hutchinson 2004
+' Email: [email protected]
+' WWW: http://www.proteanide.co.uk/
+' -------------------------------------------------------------------
+' This include provides an OO approach to game creation. You must
+' first instantiate the T2DDynamicGame class with the Create()
+' method. This controls the game itself, including the main loop.
+' You do not need to inherit T2DDynamicGame. Instead, create types
+' and inherit from T2DDynamicGameScene, this class is used to process
+' a single game scene, such as the main menu or the game itself. You
+' might have types that inherit T2DDynamicGame for the Main Menu,
+' Levels of your game, Bonus levels, credits, menu screens, etc. 
+' The functionality is built into each type in the Update and Render
+' methods. Always perform logic operations in the Update method and
+' draw your graphics in the standard way inside Render(). This way
+' your game will automatically benefit from dynamic game timing. To
+' Swap the scene from say, the Main Menu to the game itself, simply 
+' call the SetScene() Method on T2DDynamicGame with your new scene.
+' -------------------------------------------------------------------
+' Benefits/Features:
+'  - The T2DDynamicGame class handles pretty much everything to do
+'    with the game loop for you, it has a dynamic timing routine 
+'    built into it which will catch up with missing frames.
+'  - Allows you to easily run at a specific visual framerate 
+'    (DesiredFPS) regardless of the refresh rate.
+' -------------------------------------------------------------------
+' Required:
+'  - minitimer.bmx   - Timer framework.
+' *******************************************************************
+
+Import "minitimer.bmx"
+
+Type T2DDynamicGame
+
+	' PRIVATE
+	Field Scene:T2DDynamicGameScene
+	Field EndMainLoop:Int = False
+	Field DesiredFPS:Int = 60
+	Field TerminateMainLoop:Int = False
+	Field DynamicTiming:Int = True       ' If true dynamic timing is uses, else frame limited timing is used.
+
+	' PUBLIC
+	Field Width:Int = 1024
+	Field Height:Int = 768
+	Field Depth:Int = 32
+	Field RefreshRate:Int = 60
+
+'#Region Constructor: Create
+	Function Create:T2DDynamicGame(Width:Int,Height:Int,Depth:Int,RefreshRate:Int)
+		Local Out:T2DDynamicGame = New T2DDynamicGame
+		Out.Width = Width
+		Out.Height = Height
+		Out.Depth = Depth
+		Out.RefreshRate = RefreshRate
+		Return Out
+	End Function
+'#End Region
+
+'#Region Method: Initialize
+	Method Initialize()
+		' Set up the graphics.
+		Graphics(Self.Width, Self.Height, Self.Depth, Self.RefreshRate)
+	End Method
+'#End Region
+'#Region Method: ShutDown
+	Method ShutDown()
+		' Close down the graphics.
+		Self.FlushScene()
+		EndGraphics()
+	End Method
+'#End Region
+'#Region Method: SetScene
+	Method SetScene(Scene:T2DDynamicGameScene)
+		' Set a new scene into the game.
+		Self.FlushScene()
+		Self.Scene = Scene
+		Self.Scene.Start()
+	End Method
+'#End Region
+'#Region Method: MainLoop
+	Method MainLoop()
+		' Repeat the main loop until termination is required.
+		' Taken from my .NET game framework codenamed: Lita, for more info drop me a line! :) </PLUG>
+		If Self.DynamicTiming = True
+			' Dynamic timing.
+	       	Local WaitUntil:Int
+			Local Timer:MiniTimer = New MiniTimer
+	        Timer.Reset()
+
+			Local Period:Int = 1000 / Self.DesiredFPS
+	        Local Gap:Int
+	        Local UpdatesUntil:Float
+	
+	        WaitUntil = MilliSecs() + Period
+	        While (Not TerminateMainLoop) And (Not Self.IsTerminated())
+	
+	            ' Loop until time has passed.
+	            Repeat
+	            Until MilliSecs() > WaitUntil
+
+	            ' Update for as many times frames were missed.
+				' First we need to calculate some stats.
+	            Gap = (MilliSecs() - Timer.TimeStarted)
+	            UpdatesUntil = Float(Gap) / Float(Period)
+
+				' Perform the updates.
+	            If UpdatesUntil > 1.0 Then
+	                For Local Count:Int = 1 To Int(UpdatesUntil)
+	                    Self.Update()
+	                Next
+				End If
+	
+				' Reset our timer to start the next run.
+	            Timer.Reset()
+	            WaitUntil = MilliSecs() + Period
+	            Self.Render()
+				Flip()
+'				GCCollect
+	            'Application.DoEvents()
+	        Wend
+		Else
+			' Frame limited tbiming.
+	        While (Not TerminateMainLoop) And (Not Self.IsTerminated())
+				Self.Update()
+				Self.Render()
+				Flip()
+'				GCCollect
+	        Wend
+		EndIf
+		Self.TerminateMainLoop = False
+
+	End Method
+'#End Region
+'#Region Method: FlushScene
+	Method FlushScene()
+		' If there is a scene then we need to kill it off..
+		Self.Finish()
+		Self.Scene = Null
+	End Method
+'#End Region
+'#Region Method: Start
+	Method Start()
+		' Call start on the scene.
+		If Self.Scene <> Null Then Self.Scene.Start()
+	End Method
+'#End Region
+'#Region Method: Finish
+	Method Finish()
+		' Call start on the scene.
+		If Self.Scene <> Null Then 
+			Self.Scene.Finish()
+		EndIf
+	End Method
+'#End Region
+'#Region Method: Update
+	Method Update()
+		' Call update on the scene.
+		If Self.Scene <> Null Then Self.Scene.Update()
+	End Method
+'#End Region
+'#Region Method: Render
+	Method Render()
+		' Call render on the scene.
+		If Self.Scene <> Null Then Self.Scene.Render()
+	End Method
+'#End Region
+'#Region Method: IsTerminated
+	Method IsTerminated:Int()
+		' Check to see if the scene wants to terminate the main loop.
+		If Self.Scene <> Null Then
+ 			If Self.Scene.TerminateMainLoop Then
+				Self.Scene.TerminateMainLoop = False
+				Return True
+			EndIf
+			Return False
+		EndIf
+		Return True
+	End Method
+'#End Region
+
+End Type
+
+Type T2DDynamicGameScene
+
+	Field TerminateMainLoop:Int = False	
+
+	Method Update() Abstract
+	Method Render() Abstract
+	Method Start() Abstract
+	Method Finish() Abstract
+
+End Type
+
+
+
+
+
+
+
+

BIN
samples/digesteroids/graphics/bullet1.png


BIN
samples/digesteroids/graphics/bullet2.png


BIN
samples/digesteroids/graphics/digestive.png


BIN
samples/digesteroids/graphics/lower.png


BIN
samples/digesteroids/graphics/options.png


BIN
samples/digesteroids/graphics/piece1.png


BIN
samples/digesteroids/graphics/piece2.png


BIN
samples/digesteroids/graphics/piece3.png


BIN
samples/digesteroids/graphics/pop.png


BIN
samples/digesteroids/graphics/ship.png


BIN
samples/digesteroids/graphics/sparkle.png


BIN
samples/digesteroids/graphics/stars.png


BIN
samples/digesteroids/graphics/title.png


+ 72 - 0
samples/digesteroids/minitimer.bmx

@@ -0,0 +1,72 @@
+' *******************************************************************
+' Source: Mini Timer 
+' Version: 1.00
+' Author: Rob Hutchinson 2004
+' Email: [email protected]
+' WWW: http://www.proteanide.co.uk/
+' -------------------------------------------------------------------
+' This include provides a class for a timer object. The class works
+' in milliseconds. First of all the object can be enabled and 
+' disabled at will by setting the Enabled field
+' to true or false. The Interval field can be set
+' to mark a milliseconds interval that, when reached, IntervalReached
+' will become true. You can use the Reset() method to reset the timer
+' and MiillisecondsElapsed() will tell you the number of milliseconds
+' that have passed since you called Reset. Enabled field only has
+' any effect on the IntervalReached function of the timer. If false
+' then the method will always return false.
+' Ported directly from my .NET Framework game library: Lita.
+' -------------------------------------------------------------------
+' Required:
+'  - Nothing.
+' *******************************************************************
+
+Type MiniTimer
+
+'#Region Declarations
+        Field TimeStarted:Int
+        Field Interval:Int
+        Field Enabled:Int = True
+'#End Region
+
+'#Region Method: Reset
+        '''-----------------------------------------------------------------------------
+        ''' <summary>
+        ''' Resets the timer.
+        ''' </summary>
+        '''-----------------------------------------------------------------------------
+        Method Reset()
+            Self.TimeStarted = MilliSecs()
+        End Method
+'#End Region
+'#Region Method: MiillisecondsElapsed
+        '''-----------------------------------------------------------------------------
+        ''' <summary>
+        ''' Gets the number of milliseconds that have passed since a call to Reset.
+        ''' </summary>
+        '''-----------------------------------------------------------------------------
+        Method MiillisecondsElapsed:Int()
+            If Self.TimeStarted = 0 Then Return 0
+            Local TimeNow:Int = MilliSecs()
+            Return TimeNow - Self.TimeStarted
+        End Method
+'#End Region
+'#Region Method: IntervalReached
+        '''-----------------------------------------------------------------------------
+        ''' <summary>
+        ''' Returns true if the given interval has been reached.
+        ''' </summary>
+        '''-----------------------------------------------------------------------------
+        Method IntervalReached:Int()
+            If Self.Enabled Then Return (Self.MiillisecondsElapsed() > Self.Interval)
+        End Method
+'#End Region
+
+End Type
+
+
+
+
+
+
+

+ 593 - 0
samples/digesteroids/simplephysics.bmx

@@ -0,0 +1,593 @@
+' Simple Physics Engine - V1.0
+' --------------------------------------------------------
+' Written By: Rob Hutchinson 2004
+
+Strict
+
+Type TRectangle
+
+'#Region Declarations
+	Field X:Int,Y:Int,Width:Int,Height:Int
+'#End Region
+
+'#Region Method: Bottom
+	Method Bottom:Int()
+		Return Y + Height
+	End Method
+'#End Region
+'#Region Method: Right
+	Method Right:Int()
+		Return X + Width
+	End Method
+'#End Region
+'#Region Method: Create
+	Function Create:TRectangle(X:Int,Y:Int,Width:Int,Height:Int)
+		Local Out:TRectangle = New TRectangle
+		Out.X = X
+		Out.Y = Y
+		Out.Width = Width
+		Out.Height = Height
+		Return Out
+	End Function
+'#End Region
+
+'#Region Method: IntersectsWith
+	Method IntersectsWith:Int(Rectangle:TRectangle)
+		If (((Rectangle.X < (Self.X + Self.Width)) And (Self.X < (Rectangle.X + Rectangle.Width))) And (Rectangle.Y < (Self.Y + Self.Height))) Then
+ 			Return (Self.Y < (Rectangle.Y + Rectangle.Height)) 
+		End If
+		Return False
+	End Method
+'#End Region
+
+End Type
+
+Type TPhysicsUtility
+
+'#Region Method: DistanceBetweenPoints
+    Function DistanceBetweenPoints:Double(X1:Int, Y1:Int, X2:Int, Y2:Int)
+        Local DeltaX:Int = X2 - X1
+        Local DeltaY:Int = Y2 - Y1
+        Local Calculation:Int = ((DeltaX * DeltaX) + (DeltaY * DeltaY))
+        Return Sqr(Double(Calculation))
+    End Function
+'#End Region
+
+    Function DegreesBetweenPoints(X1:Double, Y1:Double, X2:Double, Y2:Double)
+		Local Out:Double = ATan2(X2 - X1,-(Y2 - Y1))
+        Return 180.0 - Out
+    End Function
+
+End Type
+
+Type TPointD
+	
+'#Region Declarations
+	Field X:Double = 0
+	Field Y:Double = 0
+'#End Region
+
+End Type
+
+Type TMagnetCollection Extends TList
+
+'#Region Method: Draw
+	Method Draw()
+		Local Item:TMagnet
+		For Item=EachIn Self
+			Item.Draw()
+		Next
+	End Method	
+'#End Region
+
+End Type
+
+Type TPhysicsProviderCollection Extends TList
+
+'#Region Method: Draw
+	Method Draw()
+		Local Item:TPhysicsProvider
+		For Item=EachIn Self
+			Item.Draw()
+		Next
+End Method
+'#End Region
+'#Region Method: ApplyPhysics
+	Method ApplyPhysics:Int()
+		Local Item:TPhysicsProvider
+		Local Count:Int
+		For Item=EachIn Self
+			Item.ApplyPhysics()
+			Count:+1
+		Next
+		Return Count
+	End Method
+'#End Region
+'#Region Method: ApplyPhysicsAndFriction
+	Method ApplyPhysicsAndFriction:Int(Axis:Int)
+		Local Item:TPhysicsProvider
+		Local Count:Int
+		For Item=EachIn Self
+            Item.ApplyFriction(Axis)
+			Item.ApplyPhysics()
+			Item.Draw()
+			Count:+1
+		Next
+		Return Count
+	End Method
+'#End Region
+'#Region Method: ApplyPhysicsAndDraw
+	Method ApplyPhysicsAndDraw()
+		Local Item:TPhysicsProvider
+		For Item=EachIn Self
+			Item.ApplyPhysics()
+			Item.Draw()
+		Next
+End Method
+'#End Region
+
+End Type
+
+Type TMagnet
+'#Region Declarations
+	Field X:Int, Y:Int
+	Field Radius:Double
+
+	Const NegativePolarity = -1
+	Const PositivePolarity = 1
+
+	Field Strength:Double
+	Field Polarity:Int = 1
+'#End Region
+
+
+
+'#Region Method: GetStrengthOfPull
+	Method GetStrengthOfPull:Double(Orbit:Double)
+        If Orbit > Self.Radius Then Return 0
+
+        ' First work out the percentage...
+        Local PercentOfPull:Double = (Orbit / Self.Radius) * 100
+        Return Self.Strength - ((PercentOfPull / 100) * Self.Strength)
+	End Method
+'#End Region
+'#Region Method: GetForces
+	Method GetForces:TPointD(X:Int, Y:Int)
+        Local Out:TPointD=New TPointD
+		Out.X = 0
+		Out.Y = 0
+
+		' Get Distance Between Points...
+		Local Distance:Double = TPhysicsUtility.DistanceBetweenPoints(X, Y, Self.X, Self.Y)
+		Local Strength:Double = Self.GetStrengthOfPull(Distance)
+        If Strength = 0 Then Return Out
+
+		' Get the degrees between points..
+		Local Angle:Double = TPhysicsUtility.DegreesBetweenPoints(X, Y,Self.X, Self.Y)
+
+		' Reverse strength if using negative polarity.        
+        If Self.Polarity = NegativePolarity Then Strength = -Strength
+
+		Out.X = Sin(Angle) * Strength
+        Out.Y = Cos(Angle) * Strength
+
+        Return Out
+	End Method
+'#End Region
+'#Region Method: Draw
+    Method Draw()
+		Local Degrees
+		SetColor 255,255,255
+		For Degrees = 0 To 360
+			DrawRect Self.X + (Sin(Degrees) * Self.Radius), Self.Y + (Cos(Degrees) * Self.Radius),1,1
+		Next
+    End Method
+'#End Region
+'#Region Method: Create
+	Function Create:TMagnet(X:Int,Y:Int,Radius:Double,Polarity:Int,Strength:Double)
+		Local Out:TMagnet = New TMagnet
+		Out.X = X
+		Out.Y = Y
+		Out.Radius = Radius
+		Out.Polarity = Polarity
+		Out.Strength = Strength
+		Return Out
+	End Function
+'#End Region
+'#Region Method: SetPosition
+	Method SetPosition(X:Int,Y:Int)
+		Self.X = X
+		Self.Y = Y
+	End Method
+'#End Region
+
+End Type
+
+Type TWorldPhysicsProvider 
+
+	Field Loc = 0
+
+'#Region Declarations
+	Field Gravity:Double
+	Field Wind:Double
+	Field Drag:Double
+	Field ApplyMagnets:Int = True
+	Field Magnets:TMagnetCollection = New TMagnetCollection
+'#End Region
+
+'#Region Method: Initialize
+	Method Initialize(Gravity:Double, Wind:Double = 0.0, Drag:Double = 0.0, ApplyMagnets:Int = False)
+		Self.Gravity = Gravity
+		Self.Wind = Wind
+		Self.Drag = Drag
+		Self.ApplyMagnets = ApplyMagnets
+	End Method
+'#End Region
+'#Region Method: Create
+	Function Create:TWorldPhysicsProvider(Gravity:Double, Wind:Double = 0.0, Drag:Double = 0.0, ApplyMagnets:Int = False)
+		Local Out:TWorldPhysicsProvider = New TWorldPhysicsProvider
+		Out.Gravity = Gravity
+		Out.Wind = Wind
+		Out.Drag = Drag
+		Out.ApplyMagnets = ApplyMagnets
+		Return Out
+	End Function
+'#End Region
+
+End Type
+
+Type TPhysicsProvider Extends TLink
+'#Region Declarations
+	' Location.
+	Field X:Double, Y:Double
+	Field World:TWorldPhysicsProvider = New TWorldPhysicsProvider
+
+	' Terminal Velocity
+	Field TerminalVelocityX:Double = 40.0, TerminalVelocityY:Double = 40.0
+
+	' Velocity
+	Field VelocityX:Double, VelocityY:Double
+
+	' Weight
+	Field Weight:Double = 10.0
+	Field SurfaceArea:Double = 10.0
+
+	' Bounce Values.
+    Field BounceCoefficientX:Double = 1.0
+    Field BounceCoefficientY:Double = 0.75
+
+	' The friction being applied to this object.
+    Field Friction:Double = 1.0
+
+	' Whether or not to apply magnets to the individual object.
+	Field ApplyMagnets:Int = True
+
+	Const AxisX:Int = 1
+	Const AxisY:Int = 2
+
+	Const VerticalPlane = 1
+	Const HorizontalPlane = 2
+'#End Region
+
+'	Method remove()
+'		World.remove Self
+'	End Method
+
+'#Region Method: Double
+	Method Speed:Double()
+		Return Sqr((Self.VelocityX * Self.VelocityX) + (Self.VelocityY * Self.VelocityY))
+	End Method
+'#End Region
+'#Region Method: Momentum
+	Method Momentum:Double()
+		Return Self.Speed() * Self.Weight
+	End Method
+'#End Region
+'#Region Method: ReverseVelocityX
+	Method ReverseVelocityX()
+		Self.VelocityX = -Self.VelocityX
+	End Method
+'#End Region
+'#Region Method: ReverseVelocityY
+	Method ReverseVelocityY()
+		Self.VelocityY = -Self.VelocityY
+	End Method
+'#End Region
+'#Region Method: SnapWithinRectangle
+	Method SnapWithinRectangle(Area:TRectangle)
+        If Self.X < Area.X Then Self.X = Area.X
+        If Self.Y < Area.Y Then Self.Y = Area.Y
+		Local Right:Int  = Area.X + Area.Width
+		Local Bottom:Int = Area.Y + Area.Height
+        If Self.X > Right Then Self.X = Right
+        If Self.Y > Bottom Then Self.Y = Bottom
+	End Method
+'#End Region
+'#Region Method: SnapWithinRectangleAndReverseVelocity
+	Method SnapWithinRectangleAndReverseVelocity:Int(Area:TRectangle)
+		Local Out:Int = False
+        If Self.X < Area.X Then
+            Self.X = Area.X
+            Self.ReverseVelocityX()
+            Out = True
+        End If
+        If Self.Y < Area.Y Then
+            Self.Y = Area.Y
+            Self.ReverseVelocityY()
+            Out = True
+        End If
+		Local Right:Int  = Area.X + Area.Width
+		Local Bottom:Int = Area.Y + Area.Height
+        If Self.X > Right Then
+            Self.X = Right
+            Self.VelocityX = -Self.VelocityX
+            Out = True
+        End If
+        If Self.Y > Bottom Then
+            Self.Y = Bottom
+            Self.VelocityY = -Self.VelocityY
+            Out = True
+        End If
+        Return Out
+	End Method
+'#End Region
+'#Region Method: ApplyFriction
+	Method ApplyFriction(Axis:Int)
+        If ((Axis & AxisX) = AxisX) Then Self.VelocityX:*Self.Friction
+        If ((Axis & AxisY) = AxisY) Then Self.VelocityY:*Self.Friction
+	End Method
+'#End Region
+'#Region Method: ApplyFrictionX
+	Method ApplyFrictionX()
+		Self.VelocityX:*Self.Friction
+	End Method
+'#End Region
+'#Region Method: ApplyFrictionY
+	Method ApplyFrictionY()
+		Self.VelocityY:*Self.Friction
+	End Method
+'#End Region
+'#Region Method: Bounce
+	Method Bounce(Planes:Int)
+        If ((Planes & HorizontalPlane) = HorizontalPlane)
+            Self.VelocityY = -Self.VelocityY
+            Self.VelocityY:*Self.BounceCoefficientY
+        End If
+
+        If ((Planes & VerticalPlane) = VerticalPlane)
+            Self.VelocityX = -Self.VelocityX
+            Self.VelocityX:*Self.BounceCoefficientX
+        End If
+	End Method
+'#End Region
+'#Region Method: Draw
+	Method Draw() Abstract
+'#End Region
+'#Region Method: PhysicsApplied
+	Method PhysicsApplied() Abstract
+'#End Region
+'#Region Method: ApplyPhysics
+	Method ApplyPhysics()
+        ' Gravity
+        Self.VelocityY:+Self.World.Gravity - (Self.World.Drag * Self.SurfaceArea)
+
+        ' Wind
+        Self.VelocityX:+(Self.World.Wind / Self.Weight) * Self.SurfaceArea
+
+        ' Apply the magnets?
+        If (Self.World.ApplyMagnets = True) And (Self.ApplyMagnets = True) Then
+			Local Magnet:TMagnet
+			For Magnet=EachIn Self.World.Magnets
+				Local Pull:TPointD = Magnet.GetForces(Self.X,Self.Y)
+
+				Self.VelocityX = Self.VelocityX + Pull.X
+				Self.VelocityY = Self.VelocityY + Pull.Y
+			Next
+        End If
+
+        ' Terminal Velocity
+        If Self.VelocityX > Self.TerminalVelocityX Then Self.VelocityX = Self.TerminalVelocityX
+        If Self.VelocityY > Self.TerminalVelocityY Then Self.VelocityY = Self.TerminalVelocityY
+        If Self.VelocityX < -Self.TerminalVelocityX Then Self.VelocityX = -Self.TerminalVelocityX
+        If Self.VelocityY < -Self.TerminalVelocityY Then Self.VelocityY = -Self.TerminalVelocityY
+
+        ' Update
+        Self.Y:+Self.VelocityY
+        Self.X:+Self.VelocityX
+
+        ' Raise the event...
+        Self.PhysicsApplied()
+
+	End Method
+'#End Region
+'#Region Method: SetVelocityFromAngle
+    Method SetVelocityFromAngle(Angle!, Speed!)
+        Self.VelocityX = Sin(Angle) * Speed
+        Self.VelocityY = Cos(Angle) * Speed
+    End Method
+'#End Region
+'#Region Method: IncreaseVelocityFromAngle
+    Method IncreaseVelocityFromAngle(Angle!, Speed!)
+        Self.VelocityX :+ Sin(Angle) * Speed
+        Self.VelocityY :+ Cos(Angle) * Speed
+    End Method
+'#End Region
+'#Region Method: Angle
+    Method Angle!()
+		Return TPhysicsUtility.DegreesBetweenPoints(Self.X, Self.Y, Self.X + Self.VelocityX, Self.Y + Self.VelocityY)
+    End Method
+'#End Region
+
+End Type
+
+' Particle Engine.
+Type TParticleEmitter Extends TLink
+
+'#Region Declarations
+    Field World:TWorldPhysicsProvider
+    Field X:Int, Y:Int
+	Field Particles:TPhysicsProviderCollection = New TPhysicsProviderCollection
+'#End Region
+
+'#Region Method: Update
+	Method Update()
+        Self.Particles.ApplyPhysics()
+	End Method
+'#End Region
+'#Region Method: UpdateWithFriction
+    Method UpdateWithFriction(Axis:Int)
+        Self.Particles.ApplyPhysicsAndFriction(Axis)
+    End Method
+'#End Region
+'#Region Method: Draw
+    Method Draw()
+        Self.Particles.Draw()
+    End Method
+'#End Region
+'#Region Method: UpdateAndDraw
+    Method UpdateAndDraw()
+        Self.Particles.ApplyPhysics()
+        Self.Particles.Draw()
+    End Method
+'#End Region
+'#Region Method: AddParticle
+	Method AddParticle(Particle:IParticle)
+		Self.Particles.AddLast(Particle)
+	End Method
+'#End Region
+'#Region Method: RemoveParticle
+	Method RemoveParticle(Particle:IParticle)
+		Self.Particles.Remove(Particle)
+	End Method
+'#End Region
+
+End Type
+Type IParticle Extends TPhysicsProvider
+
+	Field Angle:Float
+	Field Speed:Float
+	Field Region:TRectangle
+
+    Method Initialize() Abstract
+
+End Type
+Type TStaticParticle Extends IParticle
+
+'#Region Declarations
+    Field LastX:Int
+    Field LastY:Int
+	Field Emitter:TParticleEmitter
+
+    Field LifeTime:Int = -1
+    Field LifeAmount:Int
+    Field FadeSpeed:Float = -1.0
+    Field Color[] = [255,255,255]
+    Field FadeAmount:Float
+	Field Size:Float = 1.0
+	Field ActualRotation:Float = 0.0
+	Field Rotation:Float = 0.0
+
+    Field Graphic:TImage
+'#End Region
+'#Region Constructor
+	Function Create:TStaticParticle(World:TWorldPhysicsProvider, Graphic:TImage, Region:TRectangle)
+		Local Out:TStaticParticle = New TStaticParticle
+        Out.Graphic = Graphic
+        Out.Region = Region
+		Return Out
+    End Function
+'#End Region
+
+'#Region Method: Draw
+    Method Draw()
+		SetColor(Self.Color[0],Self.Color[1],Self.Color[2])
+		SetScale(Self.Size, Self.Size)
+		SetAlpha(1.0 - Self.FadeAmount)
+		Self.ActualRotation :+ Self.Rotation
+		SetRotation(Self.ActualRotation)
+		DrawImage(Self.Graphic, Int(Self.X), Int(Self.Y))
+    End Method
+'#End Region
+
+'#Region Method: Initialize
+    Method Initialize()
+        Self.SetVelocityFromAngle(Self.Angle, Self.Speed)
+    End Method
+'#End Region
+
+'#Region Override: PhysicsApplied
+    Method PhysicsApplied()
+        Local ImageRect:TRectangle = New TRectangle
+		ImageRect.Width = Self.Graphic.Width
+		ImageRect.Height = Self.Graphic.Height
+        ImageRect.X = Int(Self.X - Self.Graphic.handle_x)
+        ImageRect.Y = Int(Self.Y - Self.Graphic.handle_y)
+
+		Local RemoveThis:Int = False
+        If Not Self.Region.IntersectsWith(ImageRect) Then 
+			RemoveThis = True
+		Else
+	        If Self.LifeTime > -1 Then
+	            If Self.LifeAmount >= Self.LifeTime Then
+	                ' Start fade out?
+	                If Self.FadeSpeed > -1 Then
+	                    Self.FadeAmount :+ FadeSpeed
+	                    If Self.FadeAmount >= 1.0 Then
+							RemoveThis = True
+	                    EndIf
+	                Else
+						RemoveThis = True
+	                EndIf
+	            Else
+	                Self.LifeAmount :+ 1 ' its lived a bit longer now.
+		        EndIf
+			Else
+				RemoveThis = True
+	        EndIf
+		EndIf	
+
+		If RemoveThis Then
+            Self.Emitter.RemoveParticle(Self)
+			Self.Emitter = Null
+		EndIf
+  	End Method
+'#End Region
+	
+End Type
+Type TFountain Extends TParticleEmitter
+
+'#Region Declarations
+	Field Region:TRectangle
+'#End Region
+'#Region Constructor
+    Function Create:TFountain(World:TWorldPhysicsProvider, Region:TRectangle)
+        Local Out:TFountain = New TFountain
+		Out.World = World
+        Out.Region = Region
+		Return Out
+    End Function
+'#End Region
+
+'#Region Method: AddStaticParticle
+    Method AddStaticParticle:TStaticParticle(Image:TImage, Angle:Float, Velocity:Float, LifeTime:Int, FadeSpeed:Float, Color[])
+        Local Particle:TStaticParticle = TStaticParticle.Create(Self.World, Image, Self.Region)
+        Particle.X = Self.X
+        Particle.Y = Self.Y
+        Particle.Angle = Angle
+        Particle.Speed = Velocity
+		Particle.Emitter = Self
+		Particle.FadeSpeed = FadeSpeed
+		Particle.LifeTime = LifeTime
+		Particle.Color = Color
+        Particle.Initialize()
+        Self.AddParticle(Particle)
+        Return Particle
+    End Method
+'#End Region
+
+End Type
+
+
+
+
+

BIN
samples/digesteroids/sounds/crash.wav


BIN
samples/digesteroids/sounds/ending.ogg


BIN
samples/digesteroids/sounds/fire.wav


BIN
samples/digesteroids/sounds/impactlarge.wav


BIN
samples/digesteroids/sounds/menu.ogg


BIN
samples/digesteroids/sounds/teleport.wav


BIN
samples/digesteroids/sounds/thrust.wav


BIN
samples/firepaint/bullet.png


+ 276 - 0
samples/firepaint/color.bmx

@@ -0,0 +1,276 @@
+
+Strict
+
+Type TColor
+
+	Method RGBColor:TRGBColor() Abstract
+	Method CMYColor:TCMYColor() Abstract
+	Method HSVColor:THSVColor() Abstract
+
+End Type
+
+Type TRGBColor Extends TColor
+
+	Field _red#,_grn#,_blu#
+
+	Method RGBColor:TRGBColor()
+		Return Self
+	End Method
+
+	Method CMYColor:TCMYColor()
+		Return TCMYColor.CreateCMY( 1-_red,1-_grn,1-_blu )
+	End Method
+
+	Method HSVColor:THSVColor()
+		Local hmin#=_red
+		If _grn<hmin hmin=_grn
+		If _blu<hmin hmin=_blu
+		Local hmax#=_red
+		If _grn>hmax hmax=_grn
+		If _blu>hmax hmax=_blu
+		If hmax-hmin=0 Return THSVColor.CreateHSV( 0,0,hmax )
+		Local hue#,delta#=hmax-hmin
+		Select hmax
+		Case _red hue=(_grn-_blu)/delta
+		Case _grn hue=2+(_blu-_red)/delta
+		Case _blu hue=4+(_red-_grn)/delta
+		End Select
+		hue=hue*60
+		If hue<0 hue=hue+360
+		Return THSVColor.CreateHSV( hue,delta/hmax,hmax )
+	End Method
+
+	Method RED#()
+		Return _red
+	End Method
+
+	Method GREEN#()
+		Return _grn
+	End Method
+
+	Method BLUE#()
+		Return _blu
+	End Method
+	
+	Method Set(r#,g#,b#)
+		_red=r
+		_grn=g
+		_blu=b
+	End Method
+
+	Function CreateRGB:TRGBColor( RED#,grn#,blu# )
+		Local color:TRGBColor=New TRGBColor
+		color._red=RED
+		color._grn=grn
+		color._blu=blu
+		Return color
+	End Function
+
+End Type
+
+Type TCMYColor Extends TColor
+	
+	Field _cyn#,_mag#,_yel#
+
+	Method RGBColor:TRGBColor()
+		Return TRGBColor.CreateRGB( 1-_cyn,1-_mag,1-_yel )
+	End Method
+
+	Method CMYColor:TCMYColor()
+		Return Self
+	End Method
+
+	Method HSVColor:THSVColor()
+		Return RGBColor().HSVColor()
+	End Method
+
+	Method CYAN#()
+		Return _cyn
+	End Method
+
+	Method MAGENTA#()
+		Return _mag
+	End Method
+
+	Method YELLOW#()
+		Return _yel
+	End Method
+
+	Function CreateCMY:TCMYColor( cyn#,mag#,yel# )
+		Local color:TCMYColor=New TCMYColor
+		color._cyn=cyn
+		color._mag=mag
+		color._yel=yel
+		Return color
+	End Function
+
+End Type
+
+Type THSVColor Extends TColor
+
+	Field _hue#,_sat#,_val#
+
+	Method RGBColor:TRGBColor()
+		If _sat<=0 Return TRGBColor.CreateRGB( _val,_val,_val )
+		Local h#=_hue/60
+		Local i#=Floor( h )
+		Local f#=h-i
+		Local p#=_val*(1-_sat)
+		Local q#=_val*(1-(_sat*f))
+		Local t#=_val*(1-(_sat*(1-f)))
+		Select Int(i)
+		Case 0 Return TRGBColor.CreateRGB( _val,t,p )
+		Case 1 Return TRGBColor.CreateRGB( q,_val,p )
+		Case 2 Return TRGBColor.CreateRGB( p,_val,t )
+		Case 3 Return TRGBColor.CreateRGB( p,q,_val )
+		Case 4 Return TRGBColor.CreateRGB( t,p,_val )
+		Case 5 Return TRGBColor.CreateRGB( _val,p,q )
+		End Select
+	End Method
+
+	Method CMYColor:TCMYColor()
+		Return RGBColor().CMYColor()
+	End Method
+
+	Method HSVColor:THSVColor()
+		Return Self
+	End Method
+
+	Method Hue#()
+		Return _hue
+	End Method
+
+	Method Saturation#()
+		Return _sat
+	End Method
+
+	Method Value#()
+		Return _val
+	End Method
+
+	Function CreateHSV:THSVColor( hue#,sat#,val# )
+		If hue<0 hue=hue+360
+		If hue>=360 hue=hue-360
+		Local color:THSVColor=New THSVColor
+		color._hue=hue
+		color._sat=sat
+		color._val=val
+		Return color
+	End Function
+
+End Type
+
+Global RED:TColor=RGBColor( 1,0,0 )
+Global GREEN:TColor=RGBColor( 0,1,0 )
+Global BLUE:TColor=RGBColor( 0,0,1 )
+
+Global ORANGE:TColor=RGBColor( 1,1,0 )
+
+Global CYAN:TColor=CMYColor( 1,0,0 )
+Global MAGENTA:TColor=CMYColor( 0,1,0 )
+Global YELLOW:TColor=CMYColor( 0,0,1 )
+
+Global BLACK:TColor=HSVColor( 0,0,0 )
+Global WHITE:TColor=HSVColor( 0,0,1 )
+Global GRAY:TColor=HSVColor( 0,0,.5 )
+Global DARKGRAY:TColor=HSVColor( 0,0,.25 )
+Global LIGHTGRAY:TColor=HSVColor( 0,0,.75 )
+
+Rem
+bbdoc: Create a red, green, blue color
+returns: A new color object
+about: @red, @grn and @blu should be in the range 0 to 1.
+End Rem
+Function RGBColor:TRGBColor( RED#,grn#,blu# )
+	Return TRGBColor.CreateRGB( RED,grn,blu )
+End Function
+
+Rem
+bbdoc: Create a cyan, magenta, yellow color
+returns: A new color object
+about: @cyn, @mag and @yel should be in the range 0 to 1.
+End Rem
+Function CMYColor:TCMYColor( cyn#,mag#,yel# )
+	Return TCMYColor.CreateCMY( cyn,mag,yel )
+End Function
+
+Rem
+bbdoc: Create a hue, saturation, value color
+returns: A new color object
+about: @hue should be in the range 0 to 360, @sat and @val should be in the range 0 to 1.
+End Rem
+Function HSVColor:THSVColor( hue#,sat#,val# )
+	Return THSVColor.CreateHSV( hue,sat,val )
+End Function
+
+Rem
+bbdoc: Get red component of a color
+returns: Red component of @color in the range 0 to 1
+End Rem
+Function ColorRed#( color:TColor )
+	Return color.RGBColor().RED()
+End Function
+
+Rem
+bbdoc: Get green component of a color
+returns: Green component of @color in the range 0 to 1
+End Rem
+Function ColorGreen#( color:TColor )
+	Return color.RGBColor().GREEN()
+End Function
+
+Rem
+bbdoc: Get blue component of a color
+returns: Blue component of @color in the range 0 to 1
+End Rem
+Function ColorBlue#( color:TColor )
+	Return color.RGBColor().BLUE()
+End Function
+
+Rem
+bbdoc: Get cyan component of a color
+returns: Cyan component of @color in the range 0 to 1
+End Rem
+Function ColorCyan#( color:TColor )
+	Return color.CMYColor().CYAN()
+End Function
+
+Rem
+bbdoc: Get magenta component of a color
+returns: Magenta component of @color in the range 0 to 1
+End Rem
+Function ColorMagenta#( color:TColor )
+	Return color.CMYColor().MAGENTA()
+End Function
+
+Rem
+bbdoc: Get yellow component of a color
+returns: Yellow component of @color in the range 0 to 1
+End Rem
+Function ColorYellow#( color:TColor )
+	Return color.CMYColor().YELLOW()
+End Function
+
+Rem
+bbdoc: Get hue component of a color
+returns: Hue component of @color in the range 0 to 360
+End Rem
+Function ColorHue#( color:TColor )
+	Return color.HSVColor().Hue()
+End Function
+
+Rem
+bbdoc: Get saturation component of a color
+returns: Saturation component of @color in the range 0 to 1
+End Rem
+Function ColorSaturation#( color:TColor )
+	Return color.HSVColor().Saturation()
+End Function
+
+Rem
+bbdoc: Get value component of a color
+returns: Value component of @color in the range 0 to 1
+End Rem
+Function ColorValue#( color:TColor )
+	Return color.HSVColor().Value()
+End Function

+ 191 - 0
samples/firepaint/firepaint.bmx

@@ -0,0 +1,191 @@
+Rem
+
+Firepaint demo:
+
+Hold down mouse button to emit *FIRE*!
+
+EndRem 
+
+Strict
+
+'For minimal build...
+Rem
+Framework BRL.D3D7Max2D
+Import BRL.Basic
+Import BRL.System
+Import BRL.PNGLoader 
+Import BRL.FreeAudioAudio
+Import BRL.WAVLoader
+End Rem
+
+Import "color.bmx"
+
+Incbin "stars.png"
+Incbin "player.png"
+Incbin "bullet.png"
+Incbin "shoot.wav"
+
+Const WIDTH=640,HEIGHT=480
+Const DEPTH=32,HERTZ=60
+
+Const GRAVITY#=.15,SPARKS_PER_FRAME=55
+
+Global sparks:TList=New TList
+Global bullets:TList=New TList
+
+Type TEntity
+
+	Field link:TLink
+
+	Method remove()
+		link.remove
+	End Method
+
+	Method AddLast( list:TList )
+		link=list.AddLast( Self )
+	End Method
+
+	Method Update() Abstract
+
+End Type
+
+Type TSpark Extends TEntity
+
+	Field x#,y#,xs#,ys#
+	Field color[3],rot#,rots#
+
+	Method Update()
+
+		ys:+GRAVITY
+		x:+xs
+		y:+ys
+
+		If x<0 Or x>=WIDTH Or y>=HEIGHT
+			remove
+			Return
+		EndIf
+
+		rot=rot+rots
+		SetHandle 8,8
+		SetRotation rot#
+		SetAlpha 1-y/HEIGHT
+		SetColor color[0],color[1],color[2]
+		DrawRect x,y,17,17
+		SetHandle 0,0
+
+	End Method
+
+	Function CreateSpark:TSpark( x#,y#,color[] )
+		Local spark:TSpark=New TSpark
+		Local an#=Rnd(360),sp#=Rnd(3,5)
+		spark.x=x
+		spark.y=y
+		spark.xs=Cos(an)*sp
+		spark.ys=Sin(an)*sp
+		spark.rots=Rnd(-15,15)
+		spark.color=color
+		spark.AddLast sparks
+		Return spark
+	End Function
+
+End Type
+
+Type TBullet Extends TEntity
+
+	Field x#,y#,ys#
+	Field rot#,img:TImage
+
+	Method Update()
+		ys:-.01
+		y:+ys
+		If y<0
+			remove
+			Return
+		EndIf
+		rot:+3
+		SetRotation rot
+		DrawImage img,x,y
+	End Method
+
+	Function CreateBullet:TBullet( x#,y#,img:TImage )
+		Local bullet:TBullet=New TBullet
+		bullet.x=x
+		bullet.y=y
+		bullet.ys=-1 
+		bullet.img=img
+		bullet.AddLast bullets
+		Return bullet
+	End Function
+
+End Type
+
+Function UpdateEntities( list:TList )
+	For Local entity:TEntity=EachIn list
+		entity.Update
+	Next
+End Function
+
+Graphics WIDTH,HEIGHT,DEPTH,HERTZ
+
+AutoMidHandle True
+
+Local fire:TSound=LoadSound( "incbin::shoot.wav" )
+Local dude:TImage=LoadImage( "incbin::player.png" ),dude_x=WIDTH/2,dude_y=HEIGHT-30
+Local bull:TImage=LoadImage( "incbin::bullet.png" ),bull_x,bull_y
+Local stars:TImage=LoadImage( "incbin::stars.png" ),stars_x,stars_y
+
+Local show_debug,color_rot#
+
+While Not KeyHit( KEY_ESCAPE )
+
+	Cls
+	
+	stars_y:+1
+	SetBlend MASKBLEND
+	TileImage stars,stars_x,stars_y
+	TileImage stars,stars_x+7,stars_y*2
+	TileImage stars,stars_x+7,stars_y*3
+	
+	If KeyDown( KEY_LEFT )
+		dude_x:-5
+	Else If  KeyDown( KEY_RIGHT )
+		dude_x:+5
+	EndIf
+
+	SetBlend MASKBLEND
+	DrawImage dude,dude_x,dude_y
+
+	If KeyHit( KEY_SPACE )
+		PlaySound fire
+		TBullet.CreateBullet dude_x,dude_y-16,bull
+	EndIf
+
+	If MouseDown(1)
+		color_rot:+1.5
+		color_rot:Mod 360
+		Local color:TRGBColor=HSVColor( color_rot,1,1 ).RGBColor()
+		Local rgb[]=[Int(color.Red()*255),Int(color.Green()*255),Int(color.Blue()*255)]
+		For Local k=1 To SPARKS_PER_FRAME
+			TSpark.CreateSpark MouseX(),MouseY(),rgb
+		Next
+	EndIf
+
+	SetBlend MASKBLEND
+	UpdateEntities bullets
+	SetRotation 0
+
+	SetBlend LIGHTBLEND
+	UpdateEntities sparks
+	SetAlpha 1
+	SetRotation 0
+	SetColor 255,255,255
+	
+	If KeyHit( Asc("D") ) show_debug=1-show_debug
+	
+	If show_debug
+		DrawText "MemAlloced="+GCMemAlloced(),0,0
+	EndIf
+
+	Flip
+	
+Wend

BIN
samples/firepaint/player.png


BIN
samples/firepaint/shoot.wav


BIN
samples/firepaint/stars.png


BIN
samples/flameduck/circlemania/anim0.png


BIN
samples/flameduck/circlemania/anim1.png


BIN
samples/flameduck/circlemania/anim10.png


BIN
samples/flameduck/circlemania/anim11.png


BIN
samples/flameduck/circlemania/anim12.png


BIN
samples/flameduck/circlemania/anim13.png


BIN
samples/flameduck/circlemania/anim14.png


Some files were not shown because too many files changed in this diff