Wednesday, August 2, 2017

Monkey-X - Zombies, Turrets on Random Maps - code example

Import mojo

Global screenwidth:Int,screenheight:Int

' This is a class that holds x and y variables.
Class pathnode
    Field x:Int,y:Int
    Method New(x:Int,y:Int)
        Self.x = x
        Self.y = y
    End Method
End Class

Class bullet
    ' bullet x and y and radius
    Field bx:Float,by:Float,br:Int=4
    Field angle:Int
    Field bulletspeed:Float=3
    Field bulletmaxdist:Int
    Field bullettraveled:Int
    Field deleteme:Bool=False
    ' start location and target location for getting angle
    Method New(x1:Int,y1:Int,x2:Int,y2:Int)
        bx = x1
        by = y1
        'set the bullet radius
        br = mymap.tw/4
        ' set the max bullet distance
        bulletmaxdist = mymap.tw*3
        angle = getangle(x1,y1,x2,y2)
    End Method
    Method update()
        ' update the bullet position
        bx += Cos(angle)*bulletspeed
        by += Sin(angle)*bulletspeed
        ' If the bullet hits a wall then delete the bullet
        If mymap.map[bx/mymap.tw][by/mymap.th] = 0 Then deleteme = True
        
        ' if distance to long then flag bullet for removal
        bullettraveled+=1
        If bullettraveled > bulletmaxdist Then deleteme = True
        ' check collision with zombies
        For Local i:=Eachin myzombie
            If circleoverlap(bx,by,br,i.zx,i.zy,i.zr)
                deleteme = True
                i.hitpoints-=1
                ' Bounce them a bit back
                'i.zx += Cos(angle+Rnd(-20,20))*Rnd(5,13)
                'i.zy += Sin(angle+Rnd(-20,20))*Rnd(5,13)
                i.flash = True
                ' If they are dead then flag them
                If i.hitpoints<=0 Then
                    i.deleteme = True
                    ' increase our counter
                    zombieskilled+=1
                End If
            End If
        Next
    End Method
    Method draw()
        SetColor 255,255,0
        DrawCircle bx,by,br
    End Method
End Class

Class turret
    Field id:Int
    ' turret x and y and radius
    Field tx:Int,ty:Int,tr:Int=16
    'our target x and y
    Field targetx:Int,targety:Int
    ' if to be deleted on the next run
    Field deleteme:Bool=False
    ' delay counter between shots
    Field shootdelay:Int
    ' maximum shoot delay
    Field maxshootdelay:Int
    ' Our current angle and the angle where we want to shoot
    Field currentangle:Int,shootangle:Int
    ' if we have no target
    Field notarget:Bool=True
    ' how fast can we turn
    Field turnspeed:Int
    ' Shaking variables
    Field shakex:Int,shakey:Int
    Field shaketime:Int
    ' The path towards the turret
    Field pathmap:Int[][]     
    Field maxrange:Int=10 'turrent tile range
    Method New(x:Int,y:Int)

        'give the turret a unique id number
        Repeat
            Local nid:Int=Rnd(18972313)
            Local change:Bool=True
            For Local i:=Eachin myzombie
                If nid = id Then change=False
            Next
            If change = True
                Self.id = nid
                Exit
            End If
        Forever

        tx = x*mymap.tw
        ty = y*mymap.th
        'make sure below our new turret is no wall... turn into open
        mymap.map[tx/mymap.tw][ty/mymap.th] = 1
        ' set the turret radius
        tr = mymap.tw/2
        ' a random turnspeed
        turnspeed = Rnd(1,10)
        ' How fast can we shoot
        maxshootdelay = Rnd(3,15)
        
        pathmap = New Int[mymap.mw][]
        For Local i:=0 Until mymap.mw
            pathmap[i] = New Int[mymap.mh]
        Next

        ' create the path towards the turret
        createpathmap()
    End Method
    Method update()
        shootdelay += 1
        ' shooting here
        ' check if there are zombies on the map
        If Not myzombie.IsEmpty             
            Local ntx:Int=-1,nty:Int=-1  ' New target x And y
            Local sdist:Int=1000 'shortest distance
            ' find zombie closest to turret
            For Local i:=Eachin myzombie
                Local d:Int=distance(tx,ty,i.zx,i.zy)
                ' if within the turret range
                If d<mymap.tw*maxrange
                If d<sdist And clearshot(i.zx,i.zy) Then
                    sdist = d
                    ntx = i.zx
                    nty = i.zy
                End If
                End If
            Next
            If ntx<>-1
            shootangle = getangle(tx,ty,ntx,nty)+Rnd(-5,5)            
            targetx = ntx
            targety = nty
            notarget = False
            Else
            notarget = True
            End If
            Else
            notarget = True            
        End If
        'if we have a target
        If notarget = False Then
            'turn the turrent to target
            For Local i:=0 Until turnspeed
                If currentangle <> shootangle
                    currentangle += turndirection(shootangle)
                    If currentangle>180 Then currentangle = -180
                    If currentangle<-180 Then currentangle = 180
                End If
            Next

            ' if we are aimed and reloaded then shoot
            If shootdelay > maxshootdelay
                shootdelay = 0 
                If currentangle = shootangle
                    ' add a shake in the opposite direction
                    ' of the barrel.
                    shakex = -Cos(currentangle)*2
                    shakey = -Sin(currentangle)*2
                    shaketime = 4
                    mybullet.AddLast(New bullet(tx+Cos(shootangle)*tr,ty+Sin(shootangle)*tr,targetx,targety))
                End If
            End If
        End If
    End Method
    ' Do we have a clear shot at the zombie
    ' no walls in the way...
    Method clearshot:Bool(x2:Int,y2:Int)
        Local x1:Float=tx
        Local y1:Float=ty
        Local angle:Int = getangle(x1,y1,x2,y2)
        For Local i:=0 Until 320
            x1+=Cos(angle)*1
            y1+=Sin(angle)*1
            Local x3:Int=x1/mymap.tw
            Local y3:Int=y1/mymap.th
            ' if outside map then skip
            If x3<0 Or y3<0 Or x3>=mymap.mw Or y3>=mymap.mh Then Continue
            ' if we touch a wall then return false
            If mymap.map[x3][y3] = 0 Then Return False
            ' if we touch the zombie then return true
            If circleoverlap(x1,y1,4,x2,y2,4) Then Return True
            '??
            If i>200 Then Return True
        Next
        Return False
    End Method
    ' returns -1(left) or 1 right to turn towards the closest turn
    Method turndirection(destangle:Int)
        If destangle<-180 Then destangle=-180
        If destangle>180 Then destangle=180
        Local myangle:Int=currentangle
        Local d1:Int
        'turn left
        While myangle <> destangle
            myangle-=1
            If myangle<-180 Then myangle = 180
            d1+=1
        Wend
        myangle = currentangle
        Local d2:Int
        While myangle <> destangle
            myangle+=1
            If myangle>180 Then myangle = -180
            d2+=1
        Wend
        If d2>d1 Then Return -1 Else Return 1
    End Method
    ' Here we create a floodfill map for pathfinding
    Method createpathmap()
        Local sx:Int=tx/mymap.tw
        Local sy:Int=ty/mymap.th    
         'flood the map with distance from
         'sx and sy
         ' Create a list with a class inside it (the class has
         ' the x and y variables)
         Local ol:List<pathnode> = New List<pathnode>
         ' Add the start position on the list
         ol.AddLast(New pathnode(sx,sy))
         ' set the cloes map at the start position to distance 1
         pathmap[sx][sy] = 1
         ' some helper arrays. We can determine the top,right,and bottom
         ' and left position cells with these numbers.
         Local dx:Int[] = [0,1,0,-1]
         Local dy:Int[] = [-1,0,1,0]
         ' While there is contents in the list
         While ol.Count <> 0
             ' Get the current location
             Local x1:Int=ol.First.x
             Local y1:Int=ol.First.y
             ' Remove the current location from the list
             ol.RemoveFirst
             ' Get 4 new positions around the current positions
            For Local i:=0 Until 4
                ' Set new x and y
                Local nx:Int=x1+dx[i]
                Local ny:Int=y1+dy[i]
                ' If the coordinates are inside the map
                If nx>=0 And ny>=0 And nx<mymap.mw And ny<mymap.mh
                ' If the closedmap is not written to yet
                 If pathmap[nx][ny] = 0 And mymap.map[nx][ny] = 1
                     ' Set the new distance based on the current distance
                     pathmap[nx][ny] = pathmap[x1][y1] + 1
                     ' Add new position to the list
                     ol.AddLast(New pathnode(nx,ny))
                 End If
                 End If
            Next
         Wend        
    End Method
    Method draw()
        Local tx:Int = tx+shakex
        Local ty:Int = ty+shakey
        shaketime-=1
        If shaketime < 0 Then shakex = 0 ; shakey = 0
         'draw the turret
        SetColor 0,255,255
        DrawCircle tx,ty,tr
        'draw the barrel
        Local x2:Int,y2:Int
        x2 = Cos(currentangle)*tr
        y2 = Sin(currentangle)*tr
        DrawCircle tx+x2,ty+y2,tr/2
        
    End Method
End Class

Class zombie
    Field id:Int
    Field hastarget:Bool=False 'is he heading towards a target
    ' zombie x and y and radius
    Field zx:Float,zy:Float,zr:Int=16
    Field tx:Int,ty:Int
    Field angle:Int
    Field movementspeed:Float=.5
    Field deleteme:Bool=False
    Field hitpoints:Int
    Field flash:Bool=False
    Field flashtime:Int
    Field pathmap:Int[][]
    Method New(x:Int,y:Int)
        zx = x*mymap.tw
        zy = y*mymap.th
        'set the zombie radius
        zr = mymap.tw/2

        hitpoints = Rnd(1,4)
        If Rnd(100)<2 Then hitpoints*=5
        movementspeed = Rnd(0.15,0.35)
        ' This holds the path for the zombie (he 
        ' moves towards the smaller number
        pathmap = New Int[mymap.mw][]
        For Local i:=0 Until mymap.mw
            pathmap[i] = New Int[mymap.mh]
        Next
    End Method
    Method update()
        Local done:Bool=False
        ' if there is a turret on the map or more
        If Not myturret.IsEmpty And hastarget=False
            Local targetid:Int=-1
            Local ntx:Int,nty:Int
            Local cdist:Int=10000
            ' find the closest
            For Local i:=Eachin myturret
                Local d:Int=distance(zx,zy,i.tx,i.ty)
                If d<cdist
                    cdist = d
                    targetid = i.id
                    hastarget = True
                    ntx = i.tx
                    nty = i.ty
                End If
            Next
            ' copy the pathmap from this turrent to 
            ' the pathmap of the zombie
            For Local i:=Eachin myturret
                If targetid = i.id
                    For Local y:=0 Until mymap.mh
                    For Local x:=0 Until mymap.mw
                        pathmap[x][y] = i.pathmap[x][y]
                    Next
                    Next
                End If
            Next
            
           tx = zx
           ty = zy
'          angle = getangle(zx,zy,ntx,nty)             
        End If
        
        'move the zombie to new grid if he arived 
        'at the current grid
        If circleoverlap(zx,zy,2,tx,ty,2)
            Local cn:Int=pathmap[zx/mymap.tw][zy/mymap.th]
            ' above/right/bottom/left (4 directions)
            Local mx:Int[] = [0,1,0,-1]
            Local my:Int[] = [-1,0,1,0]                     
            ' store the floodvalues around the zombie
            Local myx:Stack<Int> = New Stack<Int>
            Local myy:Stack<Int> = New Stack<Int>
            Local myv:Stack<Int> = New Stack<Int>                        
            ' read around the zombie for direction (pathmapflood)
            For Local i:=0 Until 4
                Local x3:Int=(zx/mymap.tw)+mx[i]
                Local y3:Int=(zy/mymap.th)+my[i]
                If x3<0 Or y3<0 Or x3>=mymap.mw Or y3>=mymap.mh Then Continue
                   Local d:Int=pathmap[x3][y3]                
                   If d>0 Then 
                       myx.Push(zx+(mx[i]*mymap.tw))
                    myy.Push(zy+(my[i]*mymap.th))
                    myv.Push(d)
                   End If
            Next
             'choose lowest new direction
            Local lowest:Int=1414
            For Local i:=0 Until myx.Length()
                If myv.Get(i)<lowest Then
                    lowest = myv.Get(i)
                    tx = myx.Get(i)
                    ty = myy.Get(i)
                End If
            Next
            ' set the angle we are going to head in
            angle = getangle(zx,zy,tx,ty)
        End If
        zx += Cos(angle) * movementspeed
        zy += Sin(angle) * movementspeed
        'if the zombie collides with a turrent then
        ' flag turret for deletion
        For Local i:=Eachin myturret
            If circleoverlap(zx,zy,zr,i.tx,i.ty,i.tr)
                i.deleteme = True
                ' move the turret far away so the zombies can pick
                ' the next nearest target
                 i.tx = -1000
                 i.ty = -1000
                 For Local ii:=Eachin myzombie
                     ii.hastarget = False
                 Next
            End If
        Next
    End Method
    Method draw()
        If flash = False
            SetColor 255,0,0
            flashtime = 4
            Else
            SetColor 255,255,255
            flashtime -= 1
            If flashtime < 0 Then flash = False
        End If
        DrawCircle zx,zy,zr     
        
        Return
        SetColor 255,255,255
        For Local y1:=-2 To 2
        For Local x1:=-2 To 2
            Local x2:Int=zx/mymap.tw+x1
            Local y2:Int=zy/mymap.th+y1
            DrawText pathmap[x2][y2],zx+(x1*mymap.tw),zy+(y1*mymap.th)
        Next
        Next        
    End Method
End Class

Class map
    Field map:Int[][]
    Field mw:Int,mh:Int,tw:Float,th:Float
    Method New(mw:Int,mh:Int)
        Self.mw = mw
        Self.mh = mh
        tw = Float(screenwidth) / Float(mw)
        th = Float(screenheight) / Float(mh)
        map = New Int[mw][]
        For Local i:=0 Until mw
            map[i] = New Int[mh]
        Next
        newmap()
    End Method
    Method newmap()
        For Local y=0 Until mh
        For Local x=0 Until mw
            map[x][y] = 0
        Next
        Next
        drawrectinmap(Rnd(10,mw-10),Rnd(10,mh-10),5,3)
        For Local i=0 Until 50
            makeroom()
        Next
        makewalls()
    
    End Method
    Method makewalls()
        ' put walls on the map
        For Local y=1 Until mh-1
        For Local x=1 Until mw-1
            If map[x][y] = 0
                If map[x+1][y] = 1
                    map[x+1][y] = 2
                End If                
            End If
            If map[x][y] = 1
                If map[x+1][y] = 0
                    map[x][y] = 2
                End If
            End If
            If map[x][y] = 1
                If map[x][y+1] = 0
                    map[x][y] = 2
                End If
            End If
            If map[x][y] = 0
                If map[x][y+1] = 1
                    map[x][y+1] = 2
                End If
            End If
        Next
        Next
    End Method
    Method makeroom:Bool()
        'find suitable place to make room
        Local exitloop:Bool=False
        Local cnt:Int=0
        While exitloop = False
            cnt+=1
            If cnt>8000 Then exitloop=True
            Local x:Int=Rnd(5,mw-8)
            Local y:Int=Rnd(5,mh-8)
            Local roomw:Int=Rnd(4,8)
            Local roomh:Int=Rnd(4,8)
            Local pass1:Bool=True
            For Local y1=0 Until roomh
            For Local x1=0 Until roomh
                If map[x1+x][y1+y] = 1 Then pass1=False
            Next
            Next
            Local pass2:Bool=False
            If pass1=True Then
                For Local y1=3 To roomh-3
                    If map[x-1][y+y1] = 1 Then pass2=True
                    If map[x+roomw][y+y1] = 1 Then pass2=True
                Next
                 For Local x1=3 To roomw-3
                    If map[x+x1][y-1] = 1 Then pass2 = True
                    If map[x+x1][y+roomh] = 1 Then pass2 = True
                Next
            End If
            If pass2 = True Then
                drawrectinmap(x,y,roomw,roomh)
                Return
            End If
        Wend
    End Method
    Method issuitable:Bool(x:Int,y:Int,w:Int,h:Int)
        For Local y1=0 Until h
        For Local x1=0 Until w
            If map[x+x1][y+y1] = 1 Then Return False
        Next
        Next
        Return True
    End Method
    Method drawrectinmap(x:Int,y:Int,w:Int,h:Int)
        For Local y1=0 Until h
        For Local x1=0 Until w
            map[x+x1][y+y1] = 1
        Next
        Next
    End Method
    Method draw()
        For Local y:=0 Until mh
        For Local x:=0 Until mw
            If map[x][y] = 0 Then 
                SetColor 100,100,100
                DrawRect x*tw,y*th,tw+1,th+1
            End If
        Next
        Next
    End Method
End Class

Global zombieskilled:Int=0
Global mymap:map
Global myturret:List<turret>
Global myzombie:List<zombie>
Global mybullet:List<bullet>

Class MyGame Extends App
    Field difficulty:Int=2
    Method OnCreate()
        SetUpdateRate(60) 'speed of the refresh
        Seed = GetDate[4]+GetDate[5] ' random values based on the date
        screenwidth = DeviceWidth
        screenheight = DeviceHeight
        newmap() 'create a new map
    End Method
    Method OnUpdate()
        ' update the zombie and bullet and turrets        
        For Local i:=Eachin myzombie
            i.update()
        Next
        For Local i:=Eachin mybullet
            i.update()
        Next

        For Local i:=Eachin myturret
            i.update()
        Next
        ' delete from the lists those that were destroyed
        For Local i:=Eachin myzombie
            If i.deleteme = True Then myzombie.Remove(i)
        Next
        For Local i:=Eachin mybullet
            If i.deleteme = True Then mybullet.Remove(i)
        Next
        For Local i:=Eachin myturret
            If i.deleteme = True Then myturret.Remove(i)
        Next
        ' if pressed mouse or space then reset difficulty
        ' and create new map
        If MouseHit(MOUSE_LEFT) Or KeyHit(KEY_SPACE) Then 
            difficulty = 2 
            newmap
        End If
        ' spawn new zombie
        If Rnd(200)<difficulty Then placezombie()
        ' Increase difficulty
        If Rnd(500)<2 Then difficulty+=1
        ' if there are no more turrets then start new map
        If myturret.IsEmpty Then difficulty = 2 ; newmap()

    End Method
    Method OnRender()
        Cls 0,0,0 
        mymap.draw()
        For Local i:=Eachin myturret
            i.draw()
        Next
        For Local i:=Eachin myzombie
            i.draw()
        Next
        For Local i:=Eachin mybullet
            i.draw()
        Next

        SetColor 255,255,255
        DrawText "Zombies and Turrets on random maps - Press Space or Mouse to create new map",0,0
        DrawText "Zombies Killed : "+zombieskilled,0,20
    End Method
End Class

Function newmap()
    ' reset our variable that holds track 
    ' of the zombies killed
    zombieskilled = 0
    ' create a variable with a value to create map with
    Local s:Int=Rnd(30,60)
    mymap = New map(s,s)
    ' (re)create the turret zombie and bullet classes
    myturret = New List<turret>
    myzombie = New List<zombie>
    mybullet = New List<bullet>  
    ' add a number of turrets
    Local numturrets:Int=Rnd(3,s/5)
    For Local i:=0 Until numturrets
        placeturret()
    Next

End Function
' Here we place a new zombie on the map
Function placezombie()
    Repeat
        ' create a x and y value
        Local x:Int=Rnd(mymap.mw)
        Local y:Int=Rnd(mymap.mh)
        ' if on the map under these new
        ' coordinates there is no wall
        If mymap.map[x][y] = 1
            ' be sure it is not placed to near
            ' a turret.
            Local notnearturret:Bool=True
            For Local i:=Eachin myturret
                If distance(i.tx,i.ty,x*mymap.tw,y*mymap.th) < mymap.tw*6 Then notnearturret=False
            Next
            If notnearturret = True
                myzombie.AddLast(New zombie(x,y))
                Exit
            End If
        End If
    Forever
End Function

''
' Place a turret on the map
Function placeturret()
    Repeat
        
        Local x:Int=Rnd(mymap.mw)
        Local y:Int=Rnd(mymap.mh)
        ' if not on a wall then make him
        If mymap.map[x][y] = 1
            myturret.AddLast(New turret(x,y))
            Exit
        End If
    Forever
End Function



Function distance:Int(x1:Int,y1:Int,x2:Int,y2:Int)
    Return Abs(x2-x1)+Abs(y2-y1)
End Function

Function getangle:Int(x1:Int,y1:Int,x2:Int,y2:Int)
    Local dx = x2 - x1
    Local dy = y2 - y1
    Return ATan2(dy,dx)+360 Mod 360
End Function 

Function circleoverlap:Bool(x1:Int,y1:Int,r1:Int,x2:Int,y2:Int,r2:Int)
    Local dx:Int = x1-x2
    Local dy:Int = y1-y2
    Local r:Int = r1+r2
    If dx*dx+dy*dy <= r*r Then Return True Else Return False
End
Function Main()
    New MyGame()
End Function

Tuesday, August 1, 2017

Monkey-X - Turrets and Zombies - code example


Import mojo

Class bullet
    ' bullet x and y and radius
    Field bx:Float,by:Float,br:Int=4
    Field angle:Int
    Field bulletspeed:Float=3
    Field bulletmaxdist:Int=1200
    Field bullettraveled:Int
    Field deleteme:Bool=False
    ' start location and target location for getting angle
    Method New(x1:Int,y1:Int,x2:Int,y2:Int)
        bx = x1
        by = y1
        angle = getangle(x1,y1,x2,y2)
    End Method
    Method update()
        ' update the bullet position
        bx += Cos(angle)*bulletspeed
        by += Sin(angle)*bulletspeed
        ' if distance to long then flag bullet for removal
        bullettraveled+=1
        If bullettraveled > bulletmaxdist Then deleteme = True
        ' check collision with zombies
        For Local i:=Eachin myzombie
            If circleoverlap(bx,by,br,i.zx,i.zy,i.zr)
                deleteme = True
                i.hitpoints-=1
                ' Bounce them a bit back
                i.zx += Cos(angle+Rnd(-20,20))*Rnd(5,13)
                i.zy += Sin(angle+Rnd(-20,20))*Rnd(5,13)
                i.flash = True
                ' If they are dead then flag them
                If i.hitpoints<=0 Then
                    i.deleteme = True
                End If
            End If
        Next
    End Method
    Method draw()
        SetColor 255,255,0
        DrawCircle bx,by,br
    End Method
End Class

Class turret
    ' turret x and y and radius
    Field tx:Int,ty:Int,tr:Int=16
    Field targetx:Int,targety:Int
    Field deleteme:Bool=False
    Field shootdelay:Int
    Field maxshootdelay:Int=10
    Field currentangle:Int,shootangle:Int
    Field notarget:Bool=True
    Field turnspeed:Int=3 'turn speed of the turret
    Field shakex:Int,shakey:Int
    Field shaketime:Int
    Method New(x:Int,y:Int)
        tx = x
        ty = y
        turnspeed = Rnd(1,10)
    End Method
    Method update()
        shootdelay += 1
        ' shooting here
        ' check if there are zombies on the map
        If Not myzombie.IsEmpty             
            Local ntx:Int,nty:Int  ' New target x And y
            Local sdist:Int=1000 'shortest distance
            ' find zombie closest to turret
            For Local i:=Eachin myzombie
                Local d:Int=distance(tx,ty,i.zx,i.zy)
                If d<sdist Then
                    sdist = d
                    ntx = i.zx
                    nty = i.zy
                End If
            Next
            shootangle = getangle(tx,ty,ntx,nty)            
            targetx = ntx
            targety = nty
            notarget = False
            Else
            notarget = True
        End If

        'if we have a target
        If notarget = False Then
            'turn the turrent to target
            For Local i:=0 Until turnspeed
                If currentangle <> shootangle
                    currentangle += turndirection(shootangle)
                    If currentangle>180 Then currentangle = -180
                    If currentangle<-180 Then currentangle = 180
                End If
            Next
            ' if we are aimed and reloaded then shoot
            If shootdelay > maxshootdelay
                shootdelay = 0 
                If currentangle = shootangle
                    ' add a shake in the opposite direction
                    ' of the barrel.
                    shakex = -Cos(currentangle)*2
                    shakey = -Sin(currentangle)*2
                    shaketime = 4
                    mybullet.AddLast(New bullet(tx+Cos(shootangle)*16,ty+Sin(shootangle)*16,targetx,targety))
                End If
            End If
        End If
    End Method
    ' returns -1(left) or 1 right to turn towards the closest turn
    Method turndirection(destangle:Int)
        Local myangle:Int=currentangle
        Local d1:Int
        'turn left
        While myangle <> destangle
            myangle-=1
            If myangle<-180 Then myangle = 180
            d1+=1
        Wend
        myangle = currentangle
        Local d2:Int
        While myangle <> destangle
            myangle+=1
            If myangle>180 Then myangle = -180
            d2+=1
        Wend
        If d2>d1 Then Return -1 Else Return 1
    End Method
    Method draw()
        Local tx:Int = tx+shakex
        Local ty:Int = ty+shakey
        shaketime-=1
        If shaketime < 0 Then shakex = 0 ; shakey = 0
        SetColor 0,255,255
        DrawCircle tx,ty,tr
        Local x2:Int,y2:Int
        x2 = Cos(currentangle)*20
        y2 = Sin(currentangle)*20
        DrawCircle tx+x2,ty+y2,10
    End Method
End Class

Class zombie
    ' zombie x and y and radius
    Field zx:Float,zy:Float,zr:Int=16
    Field tx:Int,ty:Int
    Field angle:Int
    Field movementspeed:Float=.5
    Field deleteme:Bool=False
    Field hitpoints:Int
    Field flash:Bool=False
    Field flashtime:Int
    Method New(x:Int,y:Int)
        zx = x
        zy = y
        hitpoints = Rnd(1,4)
        If Rnd(100)<2 Then hitpoints*=5
        movementspeed = Rnd(0.1,0.3)
    End Method
    Method update()
        If Not myturret.IsEmpty 
            Local ntx:Int,nty:Int
            Local cdist:Int=1000
            For Local i:=Eachin myturret
                Local d:Int=distance(zx,zy,i.tx,i.ty)
                If d<cdist
                    d = cdist
                    ntx = i.tx
                    nty = i.ty
                End If
            Next
            tx = ntx
            ty = nty
            angle = getangle(zx,zy,ntx,nty)             
        End If
        'move the zombie
        zx += Cos(angle) * movementspeed
        zy += Sin(angle) * movementspeed
    End Method
    Method draw()
        If flash = False
            SetColor 255,0,0
            flashtime = 4
            Else
            SetColor 255,255,255
            flashtime -= 1
            If flashtime < 0 Then flash = False
        End If
        DrawCircle zx,zy,zr
    End Method
End Class

Global myturret:List<turret> = New List<turret>
Global myzombie:List<zombie> = New List<zombie>
Global mybullet:List<bullet> = New List<bullet>

Class MyGame Extends App

    Method OnCreate()
        Seed = GetDate[4] + GetDate[5]
        SetUpdateRate(60)
        myturret.AddLast(New turret(DeviceWidth/2,DeviceHeight/2))
        myturret.AddLast(New turret(DeviceWidth/2-50,DeviceHeight/2))
        myturret.AddLast(New turret(DeviceWidth/2+50,DeviceHeight/2))

    End Method
    Method OnUpdate()        
        ' If press space then reset level
        If KeyHit(KEY_SPACE) Or MouseHit(MOUSE_LEFT) Then restart()
        'update the turrents zombies and bullets
        For Local i:=Eachin myturret
            i.update()
        Next
        For Local i:=Eachin myzombie
            i.update()
        Next
        For Local i:=Eachin mybullet
            i.update()
        Next
        ' remove any that are gone
        For Local i:=Eachin myturret
            If i.deleteme = True Then myturret.Remove(i)
        Next
        For Local i:=Eachin myzombie
            If i.deleteme = True Then myzombie.Remove(i)
        Next
        For Local i:=Eachin mybullet
            If i.deleteme = True Then mybullet.Remove(i)
        Next
        ' add zombies to the map
        If Rnd(200)<5 Then addzombie(DeviceWidth,DeviceHeight)
        '
    End Method
    Method OnRender()
        Cls 0,0,0 

        For Local i:=Eachin myturret
            i.draw()
        Next
        For Local i:=Eachin myzombie
            i.draw()
        Next
           For Local i:=Eachin mybullet
            i.draw()
        Next


        SetColor 255,255,255
        DrawText "Zombies and Turrent - Press Space/Mouse to reset map",0,0
    End Method
End Class

Function restart()
    myturret = New List<turret>
    myzombie = New List<zombie>
    mybullet = New List<bullet>
    myturret.AddLast(New turret(DeviceWidth/2,DeviceHeight/2))
    myturret.AddLast(New turret(DeviceWidth/2-50,DeviceHeight/2))
    myturret.AddLast(New turret(DeviceWidth/2+50,DeviceHeight/2))
End Function

Function addzombie(Width:Int,Height:Int)
    Local l:Int=Rnd(4)
    Local x:Int,y:Int
    Select l
    Case 0
    x = 0
    y = Rnd(Height)
    Case 1
    x = Width
    y = Rnd(Height)
    Case 2
    x = Rnd(Width)
    y = 0
    Case 3
    x = Rnd(Width)
    y = Height
    End Select
    myzombie.AddLast(New zombie(x,y))
End Function

Function Main()
    New MyGame()
End Function

Function distance:Int(x1:Int,y1:Int,x2:Int,y2:Int)
    Return Abs(x2-x1)+Abs(y2-y1)
End Function

Function getangle:Int(x1:Int,y1:Int,x2:Int,y2:Int)
    Local dx = x2 - x1
    Local dy = y2 - y1
    Return ATan2(dy,dx)+360 Mod 360
End Function 

Function circleoverlap:Bool(x1:Int,y1:Int,r1:Int,x2:Int,y2:Int,r2:Int)
    Local dx:Int = x1-x2
    Local dy:Int = y1-y2
    Local r:Int = r1+r2
    If dx*dx+dy*dy <= r*r Then Return True Else Return False
End

Monday, July 31, 2017

Monkey-X - Diffusion Limited Aggregation - Map Generator - code example


'diffusion limited aggregation (dla)

' This basically works like this.
' You create random walkers(dots) They move into a random direction.
' On the map there is a solid part and if the walker touches it he turns
' into a solid part. This is it.


Import mojo

' The random walker class. We only use it to store
' the x and y coordinates
Class walker
    Field x:Int,y:Int
    Field deleteme:Bool
    Method New(x:Int,y:Int)
        Self.x = x
        Self.y = y
    End Method
End Class

Class dla
    ' map width/height screen width/height
    ' tile width/height
    ' map containing the solids parts. 
    ' The walker list containing the walkers
    Field mw:Int,mh:Int,sw:Int,sh:Int
    Field tw:Float,th:Float
    Field map:Int[][]
    Field mywalker:List<walker>
    Field walkerradius:Int=1
    Field finished:Bool=False
    Method New(sw:Int,sh:Int,mw:Int,mh:Int)
        Self.sw = sw
        Self.sh = sh
        Self.mw = mw
        Self.mh = mh
        Self.tw = Float(sw) / Float(mw)
        Self.th = Float(sh) / Float(mh)
        ' create our map array
        map = New Int[mw][]
        For Local i:=0 Until mw
            map[i] = New Int[mh]
        Next
        ' create the solid part
        mapcircle(mw/2,mh/2)
        ' create the walker list
        mywalker = New List<walker>
        'add some walkers to the list
        For Local i:=0 Until mw*mh/20
            addwalker(Rnd(4))
        Next
    End Method    
    ' Here we update the walkers. We keep adding them to 
    ' the screen until they have created a solid part 
    ' on the most part of the screen.
    Method update()
        ' speed it up    
        For Local speed:Int=0 Until 20
            ' loop through each walker on the list
            For Local i:=Eachin mywalker
                ' step in one of 8 directions    
                Local x:Int=Rnd(-2,2)
                Local y:Int=Rnd(-2,2)
                ' if we are not moving outside of the map 
                ' then update position
                If i.x+x >= 0 And i.x+x < mw Then i.x += x
                If i.y+y >= 0 And i.y+y < mh Then i.y += y
                ' if we touch a solid then
                If map[i.x][i.y] = 1 Then
                    ' flag for deletion
                    i.deleteme = True
                    ' if the last position was not near the edge of the screen
                    If i.x > 10 And i.x < mw-10 And i.y > 10 And i.y < mh-10
                        ' add new walker to the border of screen
                        addwalker(Rnd(4))
                    End If
                    ' Draw the new solid
                    mapcircle(i.x,i.y)
                End If
            Next
            ' Go through the walker list and see if
            ' we can delete any walkers from the list
            For Local i:=Eachin mywalker
                If i.deleteme = True Then mywalker.Remove(i)
            Next
        Next
        If mywalker.IsEmpty Then finished = True
    End Method
    ' Add a walker to the list at a random edge location
    Method addwalker(loc:Int)
        Select loc
            Case 0'top
                mywalker.AddLast(New walker(Rnd(mw),0))
            Case 1'bottom
                mywalker.AddLast(New walker(Rnd(mw),mh-1))
            Case 2'left
                mywalker.AddLast(New walker(0,Rnd(mh)))
            Case 3'"right"
                mywalker.AddLast(New walker(mw-1,Rnd(mh)))
        End Select
    End Method
    Method mapcircle(x:Int,y:Int)
        For Local y2:=-walkerradius To walkerradius
        For Local x2:=-walkerradius To walkerradius
            Local x3:Int=x+x2
            Local y3:Int=y+y2
            If x3<0 Or y3<0 Or x3>=mw Or y3>=mh Then Continue
            map[x3][y3] = 1
        Next
        Next
    End Method
    ' Draw the solid and walkers
    Method draw()
        For Local y:=0 Until mh
        For Local x:=0 Until mw
            If map[x][y] = 1 Then
                SetColor 255,255,255
                DrawRect x*tw,y*th,tw+1,th+1
            End If
        Next
        Next
        For Local i:=Eachin mywalker
            DrawCircle i.x*tw,i.y*th,walkerradius*th
        Next
        
    End Method
End Class

Class MyGame Extends App
    Field mydla:dla
    Field count:Int=200
    Method OnCreate()
        SetUpdateRate(60)
        Seed = GetDate[4] + GetDate[5]
        mydla = New dla(DeviceWidth(),DeviceHeight(),100,100)
    End Method
    Method OnUpdate()
        mydla.update()        
        If MouseHit(MOUSE_LEFT) Then mydla.finished = True ; count=0
        If mydla.finished = True Then
            count-=1
            If count<0 
            Local s:Int=Rnd(50,256)
            mydla = New dla(DeviceWidth(),DeviceHeight(),s,s)
            count=200
            End If
        End If
    End Method
    Method OnRender()
        Cls 0,0,0 
        mydla.draw()
        SetColor 255,255,255
        DrawText "Diffusion Limited Aggregation map generator - click = new",0,0
    End Method
End Class


Function Main()
    New MyGame()
End Function

Sunday, July 23, 2017

Monkey-X - Circle Line Segment Collision - code example


'
' Circle Line Segment Collision 
'
Import mojo

Class MyGame Extends App

    Method OnCreate()
        SetUpdateRate(30)        
    End Method
    Method OnUpdate()        
    End Method
    Method OnRender()
        Cls 0,0,0 
        SetColor 255,255,255        
        ' the mouse location for circle to line collision
        Local cx:Float=MouseX(),cy:Float=MouseY()
        Local cr:Int=20 'circle radius
        'draw the circle
        DrawCircle cx,cy,cr
        ' line coordinates
        Local l1:Int[]=[100,100,200,200] ' line x1,y1,x2,y2
        Local l2:Int[]=[250,100,290,300]
        ' draw the lines
        SetColor 100,100,100
        DrawLine l1[0],l1[1],l1[2],l1[3] 'x1,y1,x2,y2
        DrawLine l2[0],l2[1],l2[2],l2[3] ',,
     
         SetColor 255,255,255
         ' check the collisions
         If circlelinecollide(l1[0],l1[1],l1[2],l1[3],cx,cy,cr)
             DrawText "Collide line 1",0,20
         Else
             DrawText "No Collide line 1",0,20
         End If
         If circlelinecollide(l2[0],l2[1],l2[2],l2[3],cx,cy,cr)
             DrawText "Collide line 2",0,40
         Else
             DrawText "No Collide line 2",0,40
         End If
         
         DrawText "Move Mouse around to check collisions with lines.",0,0
         
    End Method
End Class

'
' Line(segment) to Circle Collision
'
Function circlelinecollide:Bool(sx1:Int, sy1:Int, sx2:Int, sy2:Int, cx:Int, cy:Int,cr:Float)
    Local xDelta:Float = sx2 - sx1
    Local yDelta:Float = sy2 - sy1
    Local px:Int,py:Int
    Local u:Float

    If ((xDelta = 0) And (yDelta = 0))    
      Error("Segment start equals segment end")
    End If

    u = ((cx - sx1) * xDelta + (cy - sy1) * yDelta) / (xDelta * xDelta + yDelta * yDelta)

    If (u < 0)
         px = sx1
          py = sy1
    Else If (u > 1)
        px = sx2
        py = sy2
    Else
        px = Int(Floor(sx1 + u * xDelta))
        py = Int(Floor(sy1 + u * yDelta))
    End If
    ' If the distance of the circle to the closest point in the line
    ' is less then the radius then there is a collision
    Local d:Int= Sqrt( Pow(px - cx,2) + Pow(py - cy,2))
    If d<=(cr) Then Return True
    Return False
End Function


Function Main()
    New MyGame()
End Function

Friday, July 21, 2017

Monkey-X - 4 Way Flood Fill Pathfinding - code example


Import mojo

' This is a class that holds x and y variables.
Class pathnode
    Field x:Int,y:Int
    Method New(x:Int,y:Int)
        Self.x = x
        Self.y = y
    End Method
End Class

Class MyGame Extends App
    'Our map. it holds the walls(-1) and the distance
    'from the start to end position.
    Field cmap:Int[][]
    ' map width and height
    Field mw:Int=20,mh:Int=20
    ' tile width and height
    Field tw:Float=22,th:Float=18
    ' our start and end position
    Field sx:Int,sy:Int,ex:Int,ey:Int
    ' this holds our path
    Field path:List<pathnode>
    Method OnCreate()
        SetUpdateRate(1)
        ' create a new map array
        cmap = New Int[mw][]
        For Local i:=0 Until mw
            cmap[i] = New Int[mh]
        Next
        ' set the tile width and height.
        tw = DeviceWidth() / Float(mw)
        th = DeviceHeight() / Float(mh)
        ' get a random seed so every time we run
        ' this program we get a different result.
        Seed = GetDate[4] + GetDate[5]
    End Method
    Method OnUpdate()
        'erase the map
        For Local y:=0 Until mh
        For Local x:=0 Until mw
            cmap[x][y] = 0
        Next
        Next        
 
        'draw some contents on the map
        For Local x:=0 Until mw Step 5
        For Local y:=2 Until mh-2
            cmap[x][y] = -1
        Next
        Next
        For Local i:=0 Until 5
            Local x1:Int=Rnd(0,mw-4)
            Local y1:Int=Rnd(0,mh-4)
            For Local x2:=0 Until 4
            For Local y2:=0 Until 4
                cmap[x1+x2][y1+y2] = -1
            Next
            Next
        Next
 
        For Local y:=0 Until mh Step 5
            For Local x:=0 Until mw
                cmap[x][y] = 0
            Next
        Next
 
 
 
         'find start and end position
         Repeat
             sx = Rnd(0,mw)
             sy = Rnd(0,mh)
             ex = Rnd(0,mw)
             ey = Rnd(0,mh)
             If cmap[sx][sy] = 0 And cmap[ex][ey] = 0 
                 If sx<>ex And sy<>ey
                     Exit
                 End If
             End If
         Forever
 
         'flood the map with distance from
         'sx and sy
         ' Create a list with a class inside it (the class has
         ' the x and y variables)
         Local ol:List<pathnode> = New List<pathnode>
         ' Add the start position on the list
         ol.AddLast(New pathnode(sx,sy))
         ' set the cloes map at the start position to distance 1
         cmap[sx][sy] = 1
         ' some helper arrays. We can determine the top,right,and bottom
         ' and left position cells with these numbers.
         Local dx:Int[] = [0,1,0,-1]
         Local dy:Int[] = [-1,0,1,0]
         ' While there is contents in the list
         While ol.Count <> 0
             ' Get the current location
             Local x1:Int=ol.First.x
             Local y1:Int=ol.First.y
             ' Remove the current location from the list
             ol.RemoveFirst
             ' Get 4 new positions around the current positions
            For Local i:=0 Until 4
                ' Set new x and y
                Local nx:Int=x1+dx[i]
                Local ny:Int=y1+dy[i]
                ' If the coordinates are inside the map
                If nx>=0 And ny>=0 And nx<mw And ny<mh
                ' If the closedmap is not written to yet
                 If cmap[nx][ny] = 0 
                     ' Set the new distance based on the current distance
                     cmap[nx][ny] = cmap[x1][y1] + 1
                     ' Add new position to the list
                     ol.AddLast(New pathnode(nx,ny))
                 End If
                 End If
            Next
         Wend
         
         ' Make the path. Here we start at the end position
         ' and find the lowest value around our current position
         ' and so on until we are at the start position.
         ' Get the current position
         Local x1:Int=ex,y1:Int=ey
         ' Reset our path list
         path = New List<pathnode>
         ' Set the first path contents(end x and y)
         path.AddLast(New pathnode(x1,y1))
         ' Little counter for if we enter an infinite loop
         Local cnt:Int=0
         ' While we are not near the map distance of 1
        While cmap[x1][y1] > 1        
            ' Get tge current poditions distance    
            Local lowest:Int=cmap[x1][y1]
            ' Create 2 new variables that hold the new location
            Local nx:Int,ny:Int
            ' If the position left of our current position is on the map
            ' and is not a wall and has a distance smaller that of our
            ' current position.
            If x1-1>=0 And cmap[x1-1][y1]>-1 And cmap[x1-1][y1] < lowest Then 
                ' set new position
                nx=x1-1
                ny=y1
                ' set new lowest variable
                lowest = cmap[nx][ny]
            End If
            ' If the position right of our current position is on the map
            ' and is not a wall and has a distance smaller that of our
            ' current position.
            If x1+1<mw And cmap[x1+1][y1]>-1 And cmap[x1+1][y1] < lowest
                nx=x1+1
                ny=y1
                lowest = cmap[nx][ny]
            End If
            ' If the position top of our current position is on the map
            ' and is not a wall and has a distance smaller that of our
            ' current position.
            If y1-1>=0 And cmap[x1][y1-1]>-1 And cmap[x1][y1-1] < lowest
                nx=x1
                ny=y1-1
                lowest = cmap[nx][ny]
            End If
            ' If the position at the bottom of our current position is on the map
            ' and is not a wall and has a distance smaller that of our
            ' current position.
            If y1+1 < mh And cmap[x1][y1+1]>-1 And cmap[x1][y1+1] < lowest
                nx=x1
                ny=y1+1
                lowest = cmap[nx][ny]
            End If
            ' Here we should have a new position so we put it into 
            ' our current position
            x1 = nx
            y1 = ny
            ' Add the next position to the bottom of our path list
            path.AddLast(New pathnode(x1,y1))
            ' If we can not go anywhere then exit this loop if we have done
            ' a thousand loops.
            cnt+=1
            If cnt>1000 Then Exit
        Wend
    End Method
    Method OnRender()
        Cls 0,0,0 
        SetColor 255,255,255
        'draw the map
        For Local y:=0 Until mh
        For Local x:=0 Until mw
            If cmap[x][y] = -1 Then
                DrawRect x*tw,y*th,tw,th
            End If
        Next
        Next
        'draw the path
        If path
        For Local i:=Eachin path
            SetColor 255,255,0
            DrawRect i.x*tw,i.y*th,tw,th
            SetColor 255,255,255
            DrawText cmap[i.x][i.y],i.x*tw,i.y*th
        Next
        End If
        'Draw the end and start position
        SetColor 255,0,0
        DrawRect sx*tw,sy*th,tw,th
        DrawRect ex*tw,ey*th,tw,th
        
        SetColor 255,255,255
        DrawText "FloodFill pathfinding",0,0
    End Method
End Class


Function Main()
    New MyGame()
End Function

Thursday, July 13, 2017

Monkey-X - Flow Fields Multiple Agents - rts - code example


Around each agent flow fields are placed direction agents who want to travel 
in their path around them. On the map itself random flowfields are placed.

The code does not work 100% correctly yet since sometimes agents go through 
each other.

Import mojo

Class flowfield
    Field mapwidth:Int,mapheight:Int
    Field tilewidth:Float,tileheight:Float
    Field screenwidth:Int,screenheight:Int
    Field map:Int[][]
    Field tempmap:Int[][]
    Field flowlinestartx:Int
    Field flowlinestarty:Int

    Method New(screenwidth:Int,screenheight:Int,mapwidth:Int,mapheight:Int)
        Self.screenwidth = screenwidth
        Self.screenheight = screenheight
        Self.tilewidth = Float(screenwidth)/Float(mapwidth)
        Self.tileheight = Float(screenheight)/Float(mapheight)
        Self.mapwidth = mapwidth
        Self.mapheight = mapheight
        ' make a array
        map = New Int[mapwidth][]
        tempmap = New Int[mapwidth][]        
        For Local i = 0 Until mapwidth
            map[i] = New Int[mapheight]
            tempmap[i] = New Int[mapheight]
        Next    
        ' -1 if no direction
        For Local y:=0 Until mapheight
        For Local x:=0 Until mapheight
            map[x][y] = -1
        Next
        Next

        '
        ' Here we create a number of points
        ' with which we draw the lines in between.
        '
        Seed = GetDate[5]
        Local lastx:Int=Rnd(2,mapwidth-2)
        Local lasty:Int=Rnd(2,mapheight-2)
        flowlinestartx = lastx
        flowlinestarty = lasty
        For Local i:=0 Until mapwidth*mapheight/10
            Local newx:Int=Rnd(2,mapwidth-2)
            Local newy:Int=Rnd(2,mapheight-2)
            flowline(lastx,lasty,newx,newy)
            lastx=newx
            lasty=newy
        Next
    End Method

    'copy temp into map
    Method refreshmap()
        For Local y:=0 Until mapheight
        For Local x:=0 Until mapwidth
            map[x][y] = tempmap[x][y]
        Next
        Next
    End Method
    
    ' Make a flowfield(line) between two points
    Method flowline(x1:Int,y1:Int,x2:Int,y2:Int)
        Local dx:Int, dy:Int, sx:Int, sy:Int, e:Int
          dx = Abs(x2 - x1)
          sx = -1
          If x1 < x2 Then sx = 1      
          dy = Abs(y2 - y1)
          sy = -1
          If y1 < y2 Then sy = 1
          If dx < dy Then 
             e = dx / 2 
          Else 
             e = dy / 2          
          End If
          Local exitloop:Bool=False
          While exitloop = False
            'SetColor 255,255,255
            'DrawPoint x1,y1
            If x1 = x2 
                If y1 = y2
                    exitloop = True
                End If
            End If
            For Local y:=-6 To 6
            For Local x:=-6 To 6
                If x1+x<0 Or x1+x>=mapwidth Or y1+y<0 Or y1+y>=mapheight Then continue
                map[x1+x][y1+y] = pointto(x1+x,y1+y,x1,y1)
            Next
            Next
            map[x1][y1] = pointto(x1,y1,x2,y2)
            If dx > dy Then
                x1 += sx ; e -= dy 
                  If e < 0 Then e += dx ; y1 += sy
            Else
                y1 += sy ; e -= dx 
                If e < 0 Then e += dy ; x1 += sx
            Endif
          Wend
          ' put contents in tempmap
        For Local y:=0 Until mapheight
        For Local x:=0 Until mapwidth
            tempmap[x][y] = map[x][y]
        Next
        Next
     End Method
    ' point the flow field direction to the x2,y2 from x1,y1
     Function pointto:Int(x1:Int,y1:Int,x2:Int,y2:Int)
        Local nd:Int=-1
        If x1<x2 Then nd=0
        If x1>x2 Then nd=4
        If y1<y2 Then nd=2
        If y1>y2 Then nd=6                    
        If x1<x2 And y1<y2 Then nd=1
        If x1>x2 And y1<y2 Then nd=3
        If x1<x2 And y1>y2 Then nd=7
        If x1>x2 And y1>y2 Then nd=5
        Return nd
     End Function

    Method draw()
        SetColor 255,255,255
        For Local y:=0 Until mapheight
        For Local x:=0 Until mapwidth
            Local direction:Int = map[x][y]
            If direction=-1 Then Continue
            Local x1:Float=Float(x)*tilewidth+tilewidth/2
            Local y1:Float=Float(y)*tileheight+tileheight/2
            Local ang:Int= (360/8*direction)
            Local x2:Float=x1+(Cos(ang)*tilewidth/2)
            Local y2:Float=y1+(Sin(ang)*tileheight/2)        
            Local x3:Float=x1+(Cos(ang+150)*tilewidth/4)
            Local y3:Float=y1+(Sin(ang+150)*tileheight/4)        
            Local x4:Float=x1+(Cos(ang-150)*tilewidth/4)
            Local y4:Float=y1+(Sin(ang-150)*tileheight/4)        
            
            DrawPoly([x2,y2,x3,y3,x4,y4])
        Next
        Next
    End Method
End Class

Class alien
    Field alienx:Float,alieny:Float
    Field speed:Float
    Method New(x:Int,y:Int)
        Self.alienx = x
        Self.alieny = y
        Self.speed = Rnd(0.1,1)
    End Method
    '
    ' here we modify the flowfield so that other units can move around the
    ' unit. We do this by placing arrows around the unit based on his direction.
    Method modifyflowmap()
        Local x:Int=alienx / myflowfield.tilewidth
        Local y:Int=alieny / myflowfield.tileheight
        If x-1<0 Or x+1>=myflowfield.mapwidth Or y-1<0 Or y+1>=myflowfield.mapheight Then Return
        Local d:Int=myflowfield.map[x][y]
        Local a:Int[8]
        Select d
            Case 2 'moving down
            a[0]=2;a[1]=1;a[2]=2;a[3]=7
            a[4]=0;a[5]=5;a[6]=2;a[7]=3
            Case 3 'moving left down
            a[0]=4;a[1]=3;a[2]=2;a[3]=3
            a[4]=4;a[5]=1;a[6]=2;a[7]=3
            Case 4 'moving left
            a[0]=4;a[1]=5;a[2]=4;a[3]=3
            a[4]=4;a[5]=5;a[6]=6;a[7]=3
            Case 5 'moving left up
            a[0]=4;a[1]=5;a[2]=6;a[3]=5
            a[4]=4;a[5]=5;a[6]=6;a[7]=3
            Case 6 ' moving up
            a[0]=4;a[1]=5;a[2]=6;a[3]=7
            a[4]=6;a[5]=5;a[6]=6;a[7]=7
            Case 7 ' moving right up
            a[0]=0;a[1]=5;a[2]=6;a[3]=7
            a[4]=0;a[5]=7;a[6]=6;a[7]=7
            Case 0 ' moving right
            a[0]=0;a[1]=1;a[2]=6;a[3]=7
            a[4]=0;a[5]=1;a[6]=0;a[7]=7
            Case 1 ' moving right down
            a[0]=0;a[1]=1;a[2]=2;a[3]=3
            a[4]=0;a[5]=1;a[6]=2;a[7]=7
            
            
            
            
            
        End Select
        
        myflowfield.map[x][y+1] = a[0]
        myflowfield.map[x-1][y+1] = a[1]
        myflowfield.map[x-1][y] = a[2]
        myflowfield.map[x-1][y-1] = a[3]
        myflowfield.map[x][y-1] = a[4]
        myflowfield.map[x+1][y-1] = a[5]
        myflowfield.map[x+1][y] = a[6]
        myflowfield.map[x+1][y+1] = a[7]
        
    End Method
    Method move()
        Local x2:Int=alienx/myflowfield.tilewidth
        Local y2:Int=alieny/myflowfield.tileheight
        
        Local d:Int=myflowfield.map[x2][y2]

        ' Move the alien based on the flowfield array's direction 0=right 1=rightdown 7=rightup
        Select d
            Case 0
            alienx+=speed
            Case 1
            alienx+=speed;alieny+=speed
            Case 2
            alieny+=speed
            Case 3
            alienx-=speed
            alieny+=speed
            Case 4
            alienx-=speed
            Case 5
            alienx-=speed
            alieny-=speed
            Case 6
            alieny-=speed
            Case 7
            alieny-=speed
            alienx+=speed
        End Select
        ' stay inside array(screen)
        If alienx+10>myflowfield.screenwidth Then alienx = myflowfield.screenwidth-10
        If alienx-10<0 Then alienx = 10
        If alieny+10>myflowfield.screenheight Then alieny = myflowfield.screenheight-10
        If alieny-10<0 Then alieny = 10
        
    End Method
    Method draw()
        SetColor 255,0,0
        DrawCircle(alienx,alieny,myflowfield.tilewidth/2)
    End Method
End Class

Global myflowfield:flowfield
Global myalien:List<alien> = New List<alien>


Class MyGame Extends App
    Field mapwidth:Int,mapheight:Int
    Method OnCreate()
        Seed = GetDate[5] + GetDate[4]
        SetUpdateRate(60)        
        myflowfield = New flowfield(DeviceWidth(),DeviceHeight(),20,20)
        For Local i:=0 Until 10
            myalien.AddLast(New alien(Rnd(DeviceWidth),Rnd(DeviceHeight)))
        Next
    End Method
    Method OnUpdate()    

        myflowfield.refreshmap()        
        For Local i:=Eachin myalien
            i.modifyflowmap
            i.move()
        Next            
           
        ' if pressed space or no move by alien then new flowfield
        If KeyHit(KEY_SPACE) Or MouseHit(MOUSE_LEFT)
            Seed = Millisecs()
            mapwidth  = Rnd(20,80)
            mapheight = mapwidth
            myflowfield = New flowfield(DeviceWidth(),DeviceHeight(),mapwidth,mapheight)
            myalien.Clear()
            For Local i:=0 Until mapwidth*mapheight/20
                Local x:Int=Rnd(DeviceWidth)
                Local y:Int=Rnd(DeviceHeight)
                myalien.AddLast(New alien(x,y))
            Next
        End If    
        
    End Method
    Method OnRender()
        Cls 0,0,0 
        SetColor 255,255,255
        
        myflowfield.draw()
        
        For Local i:=Eachin myalien
            i.draw
        Next

        SetColor 255,255,255
        DrawText("Flow Fields multiple agents - space(touch/lmb) new map.",0,0)
    End Method
End Class

Function rectsoverlap:Bool(x1:Int, y1:Int, w1:Int, h1:Int, x2:Int, y2:Int, w2:Int, h2:Int)
    If x1 >= (x2 + w2) Or (x1 + w1) <= x2 Then Return False
    If y1 >= (y2 + h2) Or (y1 + h1) <= y2 Then Return False
    Return True
End

Function Main()
    New MyGame()
End Function

Wednesday, July 12, 2017

Monkey-X - Flow Fields - Lines - code example


Import mojo

Class flowfield
    Field mapwidth:Int,mapheight:Int
    Field tilewidth:Float,tileheight:Float
    Field screenwidth:Int,screenheight:Int
    Field map:Int[][]
    Field flowlinestartx:Int
    Field flowlinestarty:Int

    Method New(screenwidth:Int,screenheight:Int,mapwidth:Int,mapheight:Int)
        Self.screenwidth = screenwidth
        Self.screenheight = screenheight
        Self.tilewidth = Float(screenwidth)/Float(mapwidth)
        Self.tileheight = Float(screenheight)/Float(mapheight)
        Self.mapwidth = mapwidth
        Self.mapheight = mapheight
        ' make a array
        map = New Int[mapwidth][]
        For Local i = 0 Until mapwidth
            map[i] = New Int[mapheight]
        Next    
        ' -1 if no direction
        For Local y:=0 Until mapheight
        For Local x:=0 Until mapheight
            map[x][y] = -1
        Next
        Next
        '
        ' Here we create a number of points
        ' with which we draw the lines in between.
        '
        Seed = GetDate[5]
        Local lastx:Int=Rnd(2,mapwidth-2)
        Local lasty:Int=Rnd(2,mapheight-2)
        flowlinestartx = lastx
        flowlinestarty = lasty
        For Local i:=0 Until 5
            Local newx:Int=Rnd(2,mapwidth-2)
            Local newy:Int=Rnd(2,mapheight-2)
            flowline(lastx,lasty,newx,newy)
            lastx=newx
            lasty=newy
        Next
    End Method
    ' Make a flowfield(line) between two points
    Method flowline(x1:Int,y1:Int,x2:Int,y2:Int)
        Local dx:Int, dy:Int, sx:Int, sy:Int, e:Int
          dx = Abs(x2 - x1)
          sx = -1
          If x1 < x2 Then sx = 1      
          dy = Abs(y2 - y1)
          sy = -1
          If y1 < y2 Then sy = 1
          If dx < dy Then 
             e = dx / 2 
          Else 
             e = dy / 2          
          End If
          Local exitloop:Bool=False
          While exitloop = False
            'SetColor 255,255,255
            'DrawPoint x1,y1
            If x1 = x2 
                If y1 = y2
                    exitloop = True
                End If
            End If
            For Local y:=-1 To 1
            For Local x:=-1 To 1
                map[x1+x][y1+y] = pointto(x1+x,y1+y,x1,y1)
            Next
            Next
            map[x1][y1] = pointto(x1,y1,x2,y2)
            If dx > dy Then
                x1 += sx ; e -= dy 
                  If e < 0 Then e += dx ; y1 += sy
            Else
                y1 += sy ; e -= dx 
                If e < 0 Then e += dy ; x1 += sx
            Endif
          Wend
     End Method
    ' point the flow field direction to the x2,y2 from x1,y1
     Function pointto:Int(x1:Int,y1:Int,x2:Int,y2:Int)
        Local nd:Int=-1
        If x1<x2 Then nd=0
        If x1>x2 Then nd=4
        If y1<y2 Then nd=2
        If y1>y2 Then nd=6                    
        If x1<x2 And y1<y2 Then nd=1
        If x1>x2 And y1<y2 Then nd=3
        If x1<x2 And y1>y2 Then nd=7
        If x1>x2 And y1>y2 Then nd=5
        Return nd
     End Function

    Method draw()
        SetColor 255,255,255
        For Local y:=0 Until mapheight
        For Local x:=0 Until mapwidth
            Local direction:Int = map[x][y]
            If direction=-1 Then Continue
            Local x1:Float=Float(x)*tilewidth+tilewidth/2
            Local y1:Float=Float(y)*tileheight+tileheight/2
            Local ang:Int= (360/8*direction)
            Local x2:Float=x1+(Cos(ang)*tilewidth/2)
            Local y2:Float=y1+(Sin(ang)*tileheight/2)        
            Local x3:Float=x1+(Cos(ang+150)*tilewidth/4)
            Local y3:Float=y1+(Sin(ang+150)*tileheight/4)        
            Local x4:Float=x1+(Cos(ang-150)*tilewidth/4)
            Local y4:Float=y1+(Sin(ang-150)*tileheight/4)        
            
            DrawPoly([x2,y2,x3,y3,x4,y4])
        Next
        Next
    End Method
End Class

Class MyGame Extends App
    Field myflowfield:flowfield
    Field alienx:Int=200,alieny:Int=200
    Field lastx:Int,lasty:Int
    Method OnCreate()
        SetUpdateRate(60)
        myflowfield = New flowfield(DeviceWidth(),DeviceHeight(),50,50)
        alienx = myflowfield.flowlinestartx * myflowfield.tilewidth
        alieny = myflowfield.flowlinestarty * myflowfield.tileheight
    End Method
    Method OnUpdate()    
        Local d:Int=myflowfield.map[alienx/myflowfield.tilewidth][alieny/myflowfield.tileheight]
        ' Move the alien based on the flowfield array's direction 0=right 1=rightdown 7=rightup
        Select d
            Case 0
            alienx+=1
            Case 1
            alienx+=1;alieny+=1
            Case 2
            alieny+=1
            Case 3
            alienx-=1
            alieny+=1
            Case 4
            alienx-=1
            Case 5
            alienx-=1
            alieny-=1
            Case 6
            alieny-=1
            Case 7
            alieny-=1
            alienx+=1
        End Select
    
        ' stay inside array(screen)
        If alienx+10>DeviceWidth() Then alienx = DeviceWidth()-10
        If alienx-10<0 Then alienx = 10
        If alieny+10>DeviceHeight() Then alieny = DeviceHeight()-10
        If alieny-10<0 Then alieny = 10

        ' if we press the left mouse then move the alien to mouse position
        If MouseHit(MOUSE_LEFT) Then
            alienx = MouseX
            alieny = MouseY
        End If
        
        ' if pressed space or no move by alien then new flowfield
        If KeyHit(KEY_SPACE) Or lastx = alienx And lasty = alieny
            myflowfield = New flowfield(DeviceWidth(),DeviceHeight(),50,50)
            alienx = myflowfield.flowlinestartx * myflowfield.tilewidth
            alieny = myflowfield.flowlinestarty * myflowfield.tileheight
        End If    
        
        lastx = alienx
        lasty = alieny
    End Method
    Method OnRender()
        Cls 0,0,0 
        SetColor 255,255,255
        myflowfield.draw()
        SetColor 255,0,0
        DrawCircle(alienx,alieny,20)
        SetColor 255,255,255
        DrawText("Flow Fields (lines)- Press lmb to place alien - space new map.",0,0)
    End Method
End Class

Function rectsoverlap:Bool(x1:Int, y1:Int, w1:Int, h1:Int, x2:Int, y2:Int, w2:Int, h2:Int)
    If x1 >= (x2 + w2) Or (x1 + w1) <= x2 Then Return False
    If y1 >= (y2 + h2) Or (y1 + h1) <= y2 Then Return False
    Return True
End

Function Main()
    New MyGame()
End Function