Microsoft Small Basic

Program Listing: PRV488-2
' A* Search
' Version 0.31
' Copyright © 2018 Nonki Takahashi. The MIT License.
' Last update 2018-11-27
' Program ID PRV488-2

title = "A* Search"
GraphicsWindow.Title = title
SB_Workaround()
Init()
While "True"
GenerateMaze()
InitNodes()
n = 0 ' parent of start node
m = startNode
Node2Pos()
Turtle.Speed = 9
Turtle.PenUp()
Turtle_MoveToWorkaround()
open = ""
close = ""
parent = ""
CalcH()
f = h
AddNodeToOpenList()
GraphicsWindow.BrushColor = "Orange"
rMark = rNode
found = "False"
While (open[0] <> "") And Not[found]
GetNearestNode()
If Array.ContainsValue(goalNodes, n) Then
found = "True"
EndIf
If Not[found] Then
m = n
CalcH()
g = fOpen[n] - h
RemoveNodeFromOpenList()
AddNodeToCloseList()
For iDir = 0 To 7
m = n + dir[iDir]
If (node[m] = "passage") Or (node[m] = "goal") Then
Node2Pos()
GraphicsWindow.FillEllipse(x - rMark, y - rMark, 2 * rMark, 2 * rMark)
CalcH()
fDash = g + cost[iDir] + h
CheckNodeInLists()
If Not[inOpen] And Not[inClose] Then
f = fDash
AddNodeToOpenList()
ElseIf inOpen Then
If fDash < fOpen[m] Then
RemoveNodeFromOpenList()
f = fDash
AddNodeToOpenList()
EndIf
ElseIf inClose Then
If fDash < fOpen[m] Then
RemoveNodeFromCloseList()
f = fDash
AddNodeToOpenList()
EndIf
EndIf
EndIf
EndFor
EndIf
EndWhile
If found Then
route = ""
i = 0
While node[n] <> "start"
i = i + 1
route[i] = n
n = parent[n]
EndWhile
GraphicsWindow.PenWidth = 2
GraphicsWindow.PenColor = "Black"
Turtle.PenDown()
For i = Array.GetItemCount(route) To 1 Step -1
m = route[i]
Node2Pos()
Turtle_MoveToWorkaround()
nTL = nTL + 1
EndFor
EndIf
Program.Delay(3000)
ClearTurtleTrails()
EndWhile

Sub AddNodeToCloseList
' param m - to add
close[m] = "True"
EndSub

Sub AddNodeToOpenList
' param n - parent node
' param m - node to add
' param f - f(node)
p = 0
If Array.ContainsValue(open, m) Then
TextWindow.WriteLine("AddNodeToOpenList Error: node " + m + " already exists.")
Else
While (open[p] <> "") And (fOpen[p] <= f)
p = open[p]
EndWhile
_m = open[p]
open[p] = m
fOpen[m] = f
open[m] = _m
parent[m] = n
EndIf
EndSub

Sub AddWallToList
' param col, row - cell
' return nWalls - number of wall list
' return iWalls - index of wall list
' return colWalls[], rowWalls[] - wall list
' return colOpp[], rowOpp[] - cell list (on the opposite side)
For d = 0 To 3
colw = col + colAdj[d]
roww = row + rowAdj[d]
If 1 < colw And colw < cols And 1 < roww And roww < rows And cell[roww][colw] = WALL Then
i = colw + (roww - 1) * cols
' find i in wall list
FindWallInList()
If i = iFound Then ' found
' remove the wall
nWalls = nWalls - 1
iWalls[iPrev] = iWalls[iFound]
Else
' add the wall to wall list
nWalls = nWalls + 1
iNext = iWalls[0]
iWalls[0] = i
iWalls[i] = iNext
colWall[i] = colw ' wall
rowWall[i] = roww
colOpp[i] = colw + colAdj[d] ' cell on the opposite side
rowOpp[i] = roww + rowAdj[d]
colPath[i] = col ' for graph
rowPath[i] = row ' for graph
EndIf
EndIf
EndFor
EndSub

Sub CalcH
' param m - node
' return h - minimum cost from node to goal
Node2Pos()
_x = x
_y = y
Stack.PushValue("local", m)
For iGoal = 1 To nGoal
m = goalNodes[iGoal]
Node2Pos()
xx = x - _x
yy = y - _y
_h = Math.SquareRoot(xx * xx + yy * yy)
If (iGoal = 1) Or (_h < h) Then
h = _h
EndIf
EndFor
m = Stack.PopValue("local")
EndSub

Sub CheckNodeInLists
' param m - node
' return inOpen - "True" if node m is in open list
' return inClose - "True" if node m is in close list
If Array.ContainsValue(open, m) Then
inOpen = "True"
Else
inOpen = "False"
EndIf
If close[m] Then
inClose = "True"
Else
' needed because close[m] is "True" or ""
inClose = "False"
EndIf
EndSub

Sub ClearMaze
' param x0, y0
' param colsPassage, rowsPassage
' param width
GraphicsWindow.BrushColor = "White"
GraphicsWindow.FillRectangle(0, 0, gw, gh)
col0 = 1 ' start cell of maze generate
row0 = 1
x = x0 + (col0 - 1) * pathw
y = y0 + (row0 - 1) * pathh
GraphicsWindow.BrushColor = colorPassage
GraphicsWindow.FillRectangle(x + (wallw / 2), y + (wallw / 2), pathw - wallw, pathh - wallw) ' start cell
GraphicsWindow.BrushColor = colorWall
GraphicsWindow.FillRectangle(x0 - (wallw / 2), y0 - (wallw / 2), pathw * colsPassage + wallw, wallw)
GraphicsWindow.FillRectangle(x0 - (wallw / 2), y0 + pathh * rowsPassage - (wallw / 2), pathw * colsPassage + wallw, wallw)
GraphicsWindow.FillRectangle(x0 - (wallw / 2), y0 - (wallw / 2), wallw, pathh * rowsPassage + wallw)
GraphicsWindow.FillRectangle(x0 + pathw * colsPassage - (wallw / 2), y0 - (wallw / 2), wallw, pathh * rowsPassage + wallw)
y1 = y0 + pathh * rowsPassage
For col = 1 To colsPassage - 1
x = x0 + pathw * col
GraphicsWindow.FillRectangle(x - (wallw / 2), y0 - (wallw / 2), wallw, y1 - y0)
EndFor
x1 = x0 + pathw * colsPassage
For row = 1 To rowsPassage - 1
y = y0 + pathh * row
GraphicsWindow.FillRectangle(x0 - (wallw / 2), y - (wallw / 2), x1 - x0, wallw)
EndFor
For row = 1 To rows
For col = 1 To cols
cell[row][col] = WALL
EndFor
EndFor
cell[2 * row0][2 * col0] = PASSAGE ' generate start
nWalls = 0
edge = "" ' sparse matrix for maze graph
DrawStartAndGoal()
EndSub

Sub ClearTurtleTrails
For iTL = sTL To nTL
Shapes.Remove("_turtleLine" + iTL)
EndFor
sTL = nTL + 1
EndSub

Sub DownArrow
' param bc - brush color
' param x, y - top left corner
' param width
' param height
GraphicsWIndow.BrushColor = bc
w = width * 0.45
GraphicsWindow.FillRectangle(x + (width - w) / 2, y, w, height / 2)
x1 = x
y1 = y + height / 2
x2 = x + width
y2 = y1
x3 = x + width / 2
y3 = y + height
GraphicsWindow.FillTriangle(x1, y1, x2, y2, x3, y3)
EndSub

Sub DrawStartAndGoal
x = 33
y = 6
width = 30
height = 60
bc = "Red"
DownArrow()
x = 535
y = 362
bc = "#0AB000"
DownArrow()
EndSub

Sub EraseNodes
For i = 1 To nodes
Shapes.Remove(ell[i])
ell[i] = ""
EndFor
nodes = 0
EndSub

Sub FindWallInList
' param i - index to remove of wall list
' return iPrev - previous index of i
' return iFound - i if found
iFound = 0
c = 1
While i <> iFound And c <= nWalls
c = c + 1
iPrev = iFound
iFound = iWalls[iFound]
EndWhile
EndSub

Sub GenerateMaze
' Generate maze with randomized Prim's algorithm
' param x0, y0
' param colsPassage, rowsPassage
' param width
' 1. Start with a grid full of walls.
ClearMaze()
col = 2
row = 2
colw = 2
roww = 1
KnockDownWall()
col = cols - 1
row = rows - 1
colw = cols - 1
roww = rows
KnockDownWall()
DrawStartAndGoal()
' 2. Pick a cell, mark it as part of the maze. Add the walls of the cell to the wall list.
col = 2 * col0
row = 2 * row0
AddWallToList()
' 3. While there are walls in the list:
While nWalls > 0
' 1. Pick a random wall from the list. If the cell on the opposite side isn't in the maze yet:
GetRandomIndex()
col = colOpp[i]
row = rowOpp[i]
If cell[row][col] = WALL Then
' 1. Make the wall a passage and mark the cell on the opposite side as part of the maze.
colw = colWall[i]
roww = rowWall[i]
RemoveWallFromList()
cell[roww][colw] = PASSAGE ' wall
cell[row][col] = PASSAGE ' cell on the opposite side
colp = colPath[i] ' for graph
rowp = rowPath[i] ' for graph
node1 = (rowp / 2 - 1) * colsPassage + colp / 2 ' node # for graph
node2 = (row / 2 - 1) * colsPassage + col / 2 ' node # for graph
edge[node1][node2] = "True" ' one direction edge to maze graph
edge[node2][node1] = "True" ' opposite direction edge to maze graph
node1 = node2 ' for graph
KnockDownWall()
' 2. Add the neighboring walls of the cell to the wall list.
AddWallToList()
Else
' 2. If the cell on the opposite side already was in the maze, remove the wall from the list.
RemoveWallFromList()
EndIf
EndWhile
DrawStartAndGoal()
EndSub

Sub GetNearestNode
' return n - nearest node
nCand = 1 ' number of candidate nodes
candNodes[nCand] = open[0]
candNodes[nCand + 1] = open[candNodes[nCand]]
While (candNodes[nCand + 1] <> "") And (fOpen[candNodes[nCand]] = fOpen[candNodes[nCand + 1]])
nCand = nCand + 1
candNodes[nCand + 1] = open[candNodes[nCand]]
EndWhile
n = candNodes[Math.GetRandomNumber(nCand)]
EndSub

Sub GetRandomIndex
' get random index of wall list
' return i - index
i = 0
For c = 1 To Math.GetRandomNumber(nWalls)
i = iWalls[i]
EndFor
EndSub

Sub Init
Not = "False=True;True=False;"
gw = 598
gh = 428
GraphicsWindow.Width = gw
GraphicsWindow.Height = gh
sTL = 1
colsPassage = 11
rowsPassage = 7
cols = 2 * colsPassage + 1
rows = 2 * rowsPassage + 1
pathw = 50 ' path width
pathh = 50 ' path height
wallw = 8 ' wall width (even number)
colorRoute = "Gray"
colorWall = "#444444"
colorPassage = "White"
x0 = (gw - colsPassage * pathw) / 2
y0 = (gh - rowsPassage * pathh) / 2
colAdj = "0=1;1=0;2=-1;3=0;"
rowAdj = "0=0;1=1;2=0;3=-1;"
WALL = "W"
PASSAGE = " "
' initialize direction array
dx = 16
dy = 16
sqr = Math.SquareRoot(dx * dx + dy * dy)
cost[0] = dx
distX[0] = dx
distY[0] = 0
cost[1] = sqr
distX[1] = dx
distY[1] = dy
cost[2] = dy
distX[2] = 0
distY[2] = dy
cost[3] = sqr
distX[3] = -dx
distY[3] = dy
cost[4] = dx
distX[4] = -dx
distY[4] = 0
cost[5] = sqr
distX[5] = -dx
distY[5] = -dy
cost[6] = dy
distX[6] = 0
distY[6] = -dy
cost[7] = sqr
distX[7] = dx
distY[7] = -dy
EndSub

Sub InitNodes
rNode = 4
GraphicsWindow.PenColor = "#66000000"
GraphicsWindow.PenWidth = 1
nRows = 0
EraseNodes()
For y = 0 To gh Step dy
nRows = nRows + 1
nCols = 0
For x = 0 To gw Step dx
nCols = nCols + 1
nodes = nodes + 1
Scan()
GraphicsWindow.BrushColor = color
ell[nodes] = Shapes.AddEllipse(2 * rNode, 2 * rNode)
Shapes.Move(ell[nodes], x - rNode, y - rNode)
EndFor
EndFor
dir[0] = 1
dir[1] = nCols + 1
dir[2] = nCols
dir[3] = nCols - 1
dir[4] = -1
dir[5] = -nCols - 1
dir[6] = -nCols
dir[7] = -nCols + 1
EndSub

Sub InitOpenList
open[0] = ""
EndSub

Sub KnockDownWall
' param colw, roww - wall
' param col, row - cell on the opposite side
x = x0 + (col / 2 - 1) * pathw
y = y0 + (row / 2 - 1) * pathh
GraphicsWindow.BrushColor = colorPassage
GraphicsWindow.FillRectangle(x + (wallw / 2), y + (wallw / 2), pathw - wallw, pathh - wallw)
If Math.Remainder(colw, 2) = 1 Then ' vertical wall
x = x0 + (colw - 1) / 2 * pathw
y1 = y0 + (roww / 2 - 1) * pathh
y2 = y0 + (roww / 2) * pathh
GraphicsWindow.FillRectangle(x - (wallw / 2), y1 + (wallw / 2), wallw, pathh - wallw)
ElseIf Math.Remainder(roww, 2) = 1 Then ' horizontal wall
x1 = x0 + (colw / 2 - 1) * pathw
x2 = x0 + (colw / 2) * pathw
y = y0 + (roww - 1) / 2 * pathh
GraphicsWindow.FillRectangle(x1 + (wallw / 2), y - (wallw / 2), pathw - wallw, wallw)
EndIf
EndSub

Sub Node2Pos
' param m - node
' return x, y - coordinate of the node position
x = Math.Remainder(m - 1, nCols) * dx
y = Math.Floor((m - 1) / nCols) * dy
EndSub

Sub RemoveNodeFromCloseList
' param m - node to remove
close[m] = ""
EndSub

Sub RemoveNodeFromOpenList
' param m - node to remove
p = 0
While (open[p] <> m)
p = open[p]
EndWhile
If p <> "" Then
open[p] = open[open[p]]
open[m] = ""
Else
TextWindow.WriteLine("RemoveNodeFromOpenList Error: node " + m + " not found.")
EndIf
EndSub

Sub RemoveWallFromList
' param i - index to remove of wall list
' param wall[] - wall list
' param nWalls - number of wall list
FindWallInList()
If i = iFound Then ' found
iWalls[iPrev] = iWalls[iFound]
iWalls[iFound] = ""
nWalls = nWalls - 1
EndIf
EndSub

Sub Scan
If x < 0 Or gw <= x Or y < 0 Or gh <= y Then
color = "#000000"
Else
If silverlight Then
rx = Math.Round(x)
ry = Math.Round(y)
color = GraphicsWindow.GetPixel(rx, ry)
Else
color = GraphicsWindow.GetPixel(x, y)
EndIf
EndIf
Color_ColorToRGB()
totalR = r
totalG = g
totalB = b
totalN = 1
For d = 0 To 7
_x = x + distX[d] / 3
_y = y + distY[d] / 3
If _x < 0 Or gw <=_x Or _y < 0 Or gh <= _y Then
color = "#000000"
Else
If silverlight Then
rx = Math.Round(_x)
ry = Math.Round(_y)
color = GraphicsWindow.GetPixel(rx, ry)
Else
color = GraphicsWindow.GetPixel(_x, _y)
EndIf
EndIf
Color_ColorToRGB()
totalR = totalR + r
totalG = totalG + g
totalB = totalB + b
totalN = totalN + 1
EndFor
r = Math.Round(totalR / totalN)
g = Math.Round(totalG / totalN)
b = Math.Round(totalB / totalN)
If (r = 255) And (g = 255) And (b = 255) Then
color = "Transparent"
node[nodes] = "passage"
ElseIf (g < r) And (b < r) And (160 < r) Then
color = "#FF0000"
node[nodes] = "start"
startNode = nodes
ElseIf (r < g) And (b < g) And (160 < g) Then
color = "#00CC00"
node[nodes] = "goal"
nGoal = nGoal + 1
goalNodes[nGoal] = nodes
Else
color = "#000000"
node[nodes] = "wall"
EndIf
EndSub

Sub Color_ColorToRGB
' Color | Convert color To RGB values
' param color - "#rrggbb" (hexadecimal values)
' return r, g, b - RGB values 0..255
If Text.GetLength(color) = 9 Then
offset = 2
Else
offset = 0
EndIf
sR = Text.GetSubText(color, offset + 2, 2)
sG = Text.GetSubText(color, offset + 4, 2)
sB = Text.GetSubText(color, offset + 6, 2)
hex = sR
Math_Hex2Dec()
r = dec
hex = sG
Math_Hex2Dec()
g = dec
hex = sB
Math_Hex2Dec()
b = dec
EndSub

Sub Math_CartesianToPolar
' Math | convert cartesian coodinate To polar coordinate
' param x, y - cartesian coordinate
' return r, a - polar coordinate
r = Math.SquareRoot(x * x + y * y)
If x = 0 And y > 0 Then
a = 90 ' [degree]
ElseIf x = 0 And y < 0 Then
a = -90
ElseIf x = 0 Then
a = 0
Else
a = Math.ArcTan(y / x) * 180 / Math.Pi
EndIf
If x < 0 Then
a = a + 180
ElseIf x > 0 And y < 0 Then
a = a + 360
EndIf
EndSub

Sub Math_Hex2Dec
' Math | Convert hexadecimal To decimal
' param hex
' return dec
dec = 0
len = Text.GetLength(hex)
For ptr = 1 To len
dec = dec * 16 + Text.GetIndexOf("123456789ABCDEF", Text.GetSubText(hex, ptr, 1))
EndFor
EndSub

Sub SB_Workaround
' Small Basic | Workaround For Silverlight
' returns silverlight - "True" If in remote
color = GraphicsWindow.GetPixel(0, 0)
If Text.GetLength(color) > 7 Then
silverlight = "True"
msWait = 300
Else
silverlight = "False"
EndIf
EndSub

Sub Turtle_MoveToWorkaround
' param x, y - destination point
Stack.PushValue("local", x)
Stack.PushValue("local", y)
x = x - Turtle.X
y = y - Turtle.Y
Math_CartesianToPolar()
a = Math.Remainder(a + 90 - Turtle.Angle, 360)
If a < -180 Then
a = a + 360
ElseIf 180 < a Then
a = a - 360
EndIf
Turtle.Turn(a)
Turtle.Move(r)
EndSub