Microsoft Small Basic

Program Listing: NGN884
'=====================================================================
'CREATES A MAZE - HOPEFULLY GUARANTEED THAT ALL REGIONS ARE CONNECTED, WHILE STILL RANDOM AND HARD TO TRAVERSE
'box[i][j][k] is 1 for a border and 0 for a hole: i is row, j is column, k=1(left) k=2(right) k= 3(top) k=4(bottom)
'=====================================================================

'=====================================================================
'KEY PARAMETERS AND SETUP - CHANGE THESE HERE OR DURING THE GAME TO CREATE LEVELS
'=====================================================================

nbox = 30 'Number of cells in the X and Y directions
wbox = 800/nbox 'Width of each cell in pixels
nFood = 10 'Number of food
nPill = nbox/4 'Number of pills
timeStep = 100 'ms time step interval (speed)
pillUpdate = 4 'Update pill position frequency (larger number is slower pills - minimum 1 (every time step))

initialise()

'=====================================================================
'MAIN GAME LOOP
'=====================================================================

While (playing = 1)
startTime = Clock.ElapsedMilliseconds
'Chaeck if pill updates are needed for this time step
ipill = ipill+1
updatePill = 0
If (Math.Remainder(ipill,pillUpdate) = 0) Then
updatePill = 1
EndIf
'Move balls
For i = 1 To 2
If (keyStatus[keyLeft[i]] And box[x[i]][y[i]][1] = 0) Then
x[i] = x[i]-1
EndIf
If (keyStatus[keyRight[i]] And box[x[i]][y[i]][2] = 0) Then
x[i] = x[i]+1
EndIf
If (keyStatus[keyUp[i]] And box[x[i]][y[i]][3] = 0) Then
y[i] = y[i]-1
EndIf
If (keyStatus[keyDown[i]] And box[x[i]][y[i]][4] = 0) Then
y[i] = y[i]+1
EndIf
If (keyStatus[keyJump[i]]) Then
x[i] = Math.GetRandomNumber(nbox)
y[i] = Math.GetRandomNumber(nbox)
numJump[i] = numJump[i]+1
Shapes.SetOpacity(ball[i],Math.Max(10,100-10*numJump[i])) 'To make it harder to jump a lot
keyStatus[keyJump[i]] = "False" 'Another keyDown needed to jump again
EndIf
'Draw movement
xCen = (x[i]-0.5)*wbox
yCen = (y[i]-0.5)*wbox
Shapes.Move(ball[i],xCen-radius,yCen-radius)
'Check for food Hit
For j = 1 To nFood
If (hitFood[j] = 0 And x[i] = xFood[j] And y[i] = yFood[j]) Then
Shapes.Remove(food[j])
score[i] = score[i]+1
hitFood[j] = 1
numFoodHit = numFoodHit+1
Sound.PlayChime()
GraphicsWindow.Title = "Red "+score[1]+" : Blue "+score[2]
EndIf
EndFor
'Check for pill Hit
If (updatePill = 1) Then
For j = 1 To nPill
If (x[i] = xPill[j] And y[i] = yPill[j]) Then
score[i] = score[i]-1
Sound.PlayBellRing()
GraphicsWindow.Title = "Red "+score[1]+" : Blue "+score[2]
EndIf
EndFor
EndIf
EndFor
'Move pills
'An interesting problem to get the pills moving like they have a purpose - half successsful here
If (updatePill = 1) Then
For j = 1 To nPill
moved = 0
While (moved = 0)
'Try to go the same direction as last time, and not back where we have just been
'Pure random motion doesn't get the pills moving very far
rand = Math.GetRandomNumber(100)
If (rand > 50) Then 'Same direction 50%
k = lastdir[j]
ElseIf (rand > 5) Then 'Change direction by 90 degrees 45%
If (lastdir[j] <=2) Then 'left/right last then up/down now
k = 2+Math.GetRandomNumber(2)
Else 'up/down last then left/right now
k = Math.GetRandomNumber(2)
EndIf
Else 'Reverse direction 5%
If (lastdir[j] <=2) Then
k = 3-lastdir[j]
Else
k = 7-lastdir[j]
EndIf
EndIf
If (box[xPill[j]][yPill[j]][k] = 0) Then
If (k = 1) Then
xPill[j] = xPill[j]-1
ElseIf (k = 2) Then
xPill[j] = xPill[j]+1
ElseIf (k = 3) Then
yPill[j] = yPill[j]-1
ElseIf (k = 4) Then
yPill[j] = yPill[j]+1
EndIf
xCen = (xPill[j]-0.5)*wbox
yCen = (yPill[j]-0.5)*wbox
Shapes.Animate(pill[j],xCen-radius,yCen-radius,timeStep*pillUpdate)
moved = 1
lastdir[j] = k
EndIf
EndWhile
EndFor
EndIf
'Check for end of game
If (numFoodHit = nFood Or keyStatus["Escape"]) Then
endOfGame()
EndIf
delayTime = timeStep - (Clock.ElapsedMilliseconds-startTime) 'Remove time used for the calculations
If (delayTime > 0) Then
Program.Delay(delayTime)
EndIf
EndWhile

'=====================================================================
'INITIALISATION AND FINISH SUBROUTINES
'=====================================================================

Sub splashScreen
GraphicsWindow.Clear()
GraphicsWindow.BrushColor = GraphicsWindow.BackgroundColor
bg = Shapes.AddRectangle(gw,gh)
op = 100
Shapes.SetOpacity(bg,op)

GraphicsWindow.FontSize = gw/40
GraphicsWindow.BrushColor = "Red"
GraphicsWindow.DrawText(gw/10,0.1*gh,"Player 1 is Red and uses controls")
GraphicsWindow.DrawText(gw/5,0.14*gh,"Left - Left Arrow")
GraphicsWindow.DrawText(gw/5,0.18*gh,"Right - Right Arrow")
GraphicsWindow.DrawText(gw/5,0.22*gh,"Up - Up Arrow")
GraphicsWindow.DrawText(gw/5,0.26*gh,"Down - Down Arrow")
GraphicsWindow.DrawText(gw/5,0.30*gh,"Random Jump - Right Control")
GraphicsWindow.BrushColor = "Blue"
GraphicsWindow.DrawText(gw/10,0.4*gh,"Player 2 is Blue and uses controls")
GraphicsWindow.DrawText(gw/5,0.44*gh,"Left - Z")
GraphicsWindow.DrawText(gw/5,0.48*gh,"Right - X")
GraphicsWindow.DrawText(gw/5,0.52*gh,"Up - Q")
GraphicsWindow.DrawText(gw/5,0.56*gh,"Down - A")
GraphicsWindow.DrawText(gw/5,0.60*gh,"Random Jump - Left Control")
GraphicsWindow.BrushColor = "Black"
GraphicsWindow.DrawText(gw/10,0.7*gh,"Collect the green food and avoid the yellow pills to win")
GraphicsWindow.DrawText(gw/10,0.74*gh,"Press ESCAPE to restart a game")
GraphicsWindow.DrawText(gw/10,0.78*gh,"Press SPACE to start")

GraphicsWindow.BrushColor = GraphicsWindow.BackgroundColor
While (GraphicsWindow.LastKey <> "Space")
op = op-1
Shapes.SetOpacity(bg,op)
Program.Delay(100)
EndWhile
EndSub

Sub initialise
'Maze creation
gw = nbox*wbox
gh = gw
GraphicsWindow.Width = gw
GraphicsWindow.Height = gh
GraphicsWindow.Top = 0
GraphicsWindow.Left = (Desktop.Width-gw)/2
GraphicsWindow.BackgroundColor = "LightBlue"
GraphicsWindow.Title = "Red "+0+" : Blue "+0
'GraphicsWindow.CanResize = "False"
splashScreen()
GraphicsWindow.Clear()
radius = wbox/2-1
createMaze()

'Food - create before players so they are the bottom shapes
GraphicsWindow.BrushColor = "Green"
GraphicsWindow.PenColor = "Green"
For i = 1 To nFood
Shapes.Remove(food[i]) 'Remove shape (if it exists from previous game) before creating a new one
food[i] = Shapes.AddEllipse(2*radius,2*radius)
xFood[i] = Math.GetRandomNumber(nbox)
yFood[i] = Math.GetRandomNumber(nbox)
xCen = (xFood[i]-0.5)*wbox
yCen = (yFood[i]-0.5)*wbox
Shapes.Move(food[i],xCen-radius,yCen-radius)
hitFood[i] = 0
EndFor
numFoodHit = 0

'Pills - create before players so they are behind player balls
GraphicsWindow.BrushColor = "Yellow"
GraphicsWindow.PenColor = "Yellow"
For i = 1 To nPill
Shapes.Remove(pill[i]) 'Remove shape (if it exists from previous game) before creating a new one
pill[i] = Shapes.AddEllipse(2*radius,2*radius)
xPill[i] = Math.GetRandomNumber(nbox)
yPill[i] = Math.GetRandomNumber(nbox)
xCen = (xPill[i]-0.5)*wbox
yCen = (yPill[i]-0.5)*wbox
Shapes.Move(pill[i],xCen-radius,yCen-radius)
lastdir[i] = Math.GetRandomNumber(4)
EndFor

'Player 1 Ball
GraphicsWindow.BrushColor = "Red"
GraphicsWindow.PenColor = "Red"
Shapes.Remove(ball[1])
ball[1] = Shapes.AddEllipse(2*radius,2*radius)
x[1] = Math.GetRandomNumber(nbox)
y[1] = Math.GetRandomNumber(nbox)
keyLeft[1] = "Left"
keyRight[1] = "Right"
keyUp[1] = "Up"
keyDown[1] = "Down"
keyJump[1] = "RightCtrl"
score[1] = 0
numJump[1] = 0

'Player 2 Ball
GraphicsWindow.BrushColor = "Blue"
GraphicsWindow.PenColor = "Blue"
Shapes.Remove(ball[2])
ball[2] = Shapes.AddEllipse(2*radius,2*radius)
x[2] = Math.GetRandomNumber(nbox)
y[2] = Math.GetRandomNumber(nbox)
keyLeft[2] = "Z"
keyRight[2] = "X"
keyUp[2] = "Q"
keyDown[2] = "A"
keyJump[2] = "LeftCtrl"
score[2] = 0
numJump[2] = 0

GraphicsWindow.KeyDown = OnKeyDown
GraphicsWindow.KeyUp = OnKeyUp
playing = 1
EndSub

Sub EndOfGame
GraphicsWindow.Clear()
GraphicsWindow.FontSize = gw/15
GraphicsWindow.BrushColor = "Red"
GraphicsWindow.DrawText(gw/10,0.2*gh,"Player 1 scores "+score[1])
GraphicsWindow.BrushColor = "Blue"
GraphicsWindow.DrawText(gw/10,0.4*gh,"Player 2 scores "+score[2])
GraphicsWindow.FontSize = gw/40
GraphicsWindow.BrushColor = "Black"
GraphicsWindow.DrawText(gw/10,0.6*gh,"Press SPACE to start")
While (GraphicsWindow.LastKey <> "Space")
Program.Delay(100)
EndWhile
initialise()
EndSub

'=====================================================================
'MAZE CREATION SUBROUTINES
'=====================================================================

Sub createMaze
'Start by all faces set to a border (barrier)
For i = 1 To nbox
For j = 1 To nbox
For k = 1 To 4
box[i][j][k] = 1
EndFor
EndFor
EndFor

'Make some holes in the box sides - not essential but gives a better grid faster
If ("True") Then
For i = 1 To nbox
For j = 1 To nbox
For l = 1 To 1 'Just one hole here
holeMade = 0
While (holeMade = 0) 'Keep trying until an internal hole is made
k = Math.GetRandomNumber(4)
makeHole()
EndWhile
EndFor
EndFor
EndFor
EndIf

'Make some random holes in blocks that have 3 or more borders - not essential but gives a better grid faster
If ("True") Then
For n = 1 To nbox*nbox
i = Math.GetRandomNumber(nbox)
j = Math.GetRandomNumber(nbox)
nborder = 0
For k = 1 To 4
nborder = nborder + box[i][j][k]
EndFor
If (nborder >= 3) Then
holeMade = 0
ntry = 0
While (holeMade = 0 And ntry < 10) 'Keep trying until an internal hole is made OR we cant
ntry = ntry+1
k = Math.GetRandomNumber(4)
While (box[i][j][k] = 0) 'Must be a face that is currently a border so keep having a go while we find faces with a hole
k = Math.GetRandomNumber(4)
EndWhile
makeHole()
EndWhile
EndIf
EndFor
EndIf

'Ensure all boxes are connected - add some more holes as required - some debugging available here
checkConnectivity()

'Draw the box borders
GraphicsWindow.PenColor = "Black"
For i = 1 To nbox
For j = 1 To nbox
xCen = (i-0.5)*wbox
yCen = (j-0.5)*wbox
For k = 1 To 4
If (box[i][j][1] = 1) Then
GraphicsWindow.DrawLine(xCen-0.5*wbox,yCen-0.5*wbox,xCen-0.5*wbox,yCen+0.5*wbox)
EndIf
If (box[i][j][2] = 1) Then
GraphicsWindow.DrawLine(xCen+0.5*wbox,yCen-0.5*wbox,xCen+0.5*wbox,yCen+0.5*wbox)
EndIf
If (box[i][j][3] = 1) Then
GraphicsWindow.DrawLine(xCen-0.5*wbox,yCen-0.5*wbox,xCen+0.5*wbox,yCen-0.5*wbox)
EndIf
If (box[i][j][4] = 1) Then
GraphicsWindow.DrawLine(xCen-0.5*wbox,yCen+0.5*wbox,xCen+0.5*wbox,yCen+0.5*wbox)
EndIf
EndFor
EndFor
EndFor
EndSub

Sub makeHole
holeMade = 0
'If a hole is made on one box, then it must also be a hole on its neighbour
'Don't make holes on the outside maze boundary
If (k = 1 And i > 1) Then
box[i][j][1] = 0
box[i-1][j][2] = 0
holeMade = 1
ElseIf (k = 2 And i < nbox) Then
box[i][j][2] = 0
box[i+1][j][1] = 0
holeMade = 1
ElseIf (k = 3 And j > 1) Then
box[i][j][3] = 0
box[i][j-1][4] = 0
holeMade = 1
ElseIf (k = 4 And j < nbox) Then
box[i][j][4] = 0
box[i][j+1][3] = 0
holeMade = 1
EndIf
EndSub

'Utility to check maze connectivity - the idea is to find all boxes in unconnected regions,
'then open up a connecting hole between unconnected regions
Sub checkConnectivity
'Initially no regions defined
For i = 1 To nbox
For j = 1 To nbox
conn[i][j] = 0
EndFor
EndFor
'Recursively find which unconnected region each box is in
connection = 0
'GraphicsWindow.FontSize = 10 'Debugging
For i = 1 To nbox
For j = 1 To nbox
If (conn[i][j] = 0) Then
connection = connection+1
'The stacks hold all the unprocessed blocks in the current connection region
'Unprocessed is those where all connected neighbours have not been found (and added to the stack for later processing)
'When the stack is empy there are no more connected blocks in this region so check for next block with no region set (it must be a new region)
Stack.PushValue("connectI",i)
Stack.PushValue("connectJ",j)
While (Stack.GetCount("connectI") > 0)
ii = Stack.PopValue("connectI")
jj = Stack.PopValue("connectJ")
conn[ii][jj] = connection
'xCen = (ii-0.5)*wbox 'Debugging
'yCen = (jj-0.5)*wbox 'Debugging
'GraphicsWindow.DrawText(xCen-(wbox/2-2),yCen-(wbox/2-2),connection) 'Debugging
If (box[ii][jj][1] = 0 And conn[ii-1][jj] = 0) Then
Stack.PushValue("connectI",ii-1)
Stack.PushValue("connectJ",jj)
EndIf
If (box[ii][jj][2] = 0 And conn[ii+1][jj] = 0) Then
Stack.PushValue("connectI",ii+1)
Stack.PushValue("connectJ",jj)
EndIf
If (box[ii][jj][3] = 0 And conn[ii][jj-1] = 0) Then
Stack.PushValue("connectI",ii)
Stack.PushValue("connectJ",jj-1)
EndIf
If (box[ii][jj][4] = 0 And conn[ii][jj+1] = 0) Then
Stack.PushValue("connectI",ii)
Stack.PushValue("connectJ",jj+1)
EndIf
EndWhile
EndIf
EndFor
EndFor
'Open holes between adjacent unconnected regions - only want to open the first connected to lower region number
'i.e connect region 2 to 1, 3 to (2 or 1), 4 to (3, 2, or 1) etc with only one connection (hole) opened between them
'GraphicsWindow.PenColor = "Red" 'Debugging
For iCon = 2 To connection
For i = 1 To nbox
For j = 1 To nbox
If (conn[i][j] = iCon) Then
'xCen = (i-0.5)*wbox 'Debugging
'yCen = (j-0.5)*wbox 'Debugging
'Any neighbour block with a different region number must have a barrier, so open it and move to the next region at 'nextCon'
If (i > 1 And conn[i-1][j] < iCon) Then
box[i][j][1] = 0
box[i-1][j][2] = 0
'GraphicsWindow.DrawLine(xCen-0.5*wbox,yCen-0.5*wbox,xCen-0.5*wbox,yCen+0.5*wbox) 'Debugging
Goto nextCon
EndIf
If (i < nbox And conn[i+1][j] < iCon) Then
box[i][j][2] = 0
box[i+1][j][1] = 0
'GraphicsWindow.DrawLine(xCen+0.5*wbox,yCen-0.5*wbox,xCen+0.5*wbox,yCen+0.5*wbox) 'Debugging
Goto nextCon
EndIf
If (j > 1 And conn[i][j-1] < iCon) Then
box[i][j][3] = 0
box[i][j-1][4] = 0
'GraphicsWindow.DrawLine(xCen-0.5*wbox,yCen-0.5*wbox,xCen+0.5*wbox,yCen-0.5*wbox) 'Debugging
Goto nextCon
EndIf
If (j < nbox And conn[i][j+1] < iCon) Then
box[i][j][4] = 0
box[i][j+1][3] = 0
'GraphicsWindow.DrawLine(xCen-0.5*wbox,yCen+0.5*wbox,xCen+0.5*wbox,yCen+0.5*wbox) 'Debugging
Goto nextCon
EndIf
EndIf
EndFor
EndFor
nextCon:
EndFor
EndSub

'=====================================================================
'KEYPRESS SUBROUTINES
'=====================================================================

Sub OnKeyDown
key = GraphicsWindow.LastKey
keyStatus[key] = "True"
EndSub

Sub OnKeyUp
key = GraphicsWindow.LastKey
keyStatus[key] = "False"
EndSub