Τρίτη 30 Μαΐου 2017

A game with Functional Programming in M2000

This is a simple game, written in M2000 code. We can run it in M2000 Environment, using:
Edit A
Now open editor in Module A so we paste from clipboard (or use drag and drop from this page) and press Esc
Now just write A and press enter
You can save it
Save Blast
So next time you run M2000.exe just do this to auto run it
Load Blast, A

Explain of closure in lambda function
Each lambda function is a variable with two parts, a function and a list of closure variables (or arrays or objects). When we call a lambda function then interpreter use this list to make local variables to function. At the exit of the call, all closures values refreshed with last values (some of them maybe are pointers to objects so no copy happen for those). So closures provide state to lambda functions. Here we didn't use closures to save state. We use them to feed lambda functions. So when a lambda function need to use another function, it is there, as local function.
Game_Loop is a lambda with all logic inside. So if we pass to stack this function as variable then we pass a tree of closures. We didn't use Move_Up function, but a closure of that function inside MyKey which is a closure in New_Score which is a closure in Game_Loop.
I/O commands/ functions are Print and Keypress().

State for game kept in stack. Stack isn't process stack, but a special object with a collection inside for values (but also for objects too). Lambda Functions are objects in M2000. We can't change closures from outside of a lambda function. We can't delete or add closures if we define a lambda function. But we can change the object, when simply assign a new lambda. In M2000 we can put lambda functions to arrays or inventories (lists with a key/value pairs) and we can use them with no copies. So if A(2) hold a lambda function then A(2)() call that function (with no parameters here), and A(2) return a copy of lambda function. Lambda objects not expose pointers to them in M2000. We can pass a reference to lambda as we pass reference to variable, using & at call, so we push a weak reference to stack and from calee we use same & at reading from stack, and Read command make weak reference as real reference. Weak references are strings in M2000 which are full names for variables or and arrays. There are standard functions (not lambda), and a lambda object also have a standard function, but anonymous. We can get a reference from lambda's function only, but we loose closures, because standard functions have no closures.
From inside a lambda function we can see only stack, if called in expression this is private, but if we call using Call that is the callers stack, so is like public. Inside Game_Loop we use private stack for Game_Loop as public stack when we call New_Score, and here is the point of using stateless call, state is on stack, which we provide at the one call to Game_Loop.



\\stateless paradigm
Move_Up=lambda (x)-> {
      
if x<10 then x++
      =x
}
Move_Down=lambda (x)-> {
      
if x>1 then x--
      =x
}
Move_No= Lambda (x)->x
\\ we pass as closures three functions to MyKey
MyKey=Lambda Move_No, Move_Up, Move_Down (x)-> {
      
Select Case keypress(0x26)+keypress(0x28)*2
      
Case -1
            =Move_Up(x)
      
Case -2
            =Move_Down(x)
      
Else
            =Move_No(x)
      
End select
}
Key_Esc=Lambda ->Keypress(32)
DispEnemy$=lambda$ (x)->{
      
if x<0 then { ="<->" } else =str$(x," 00")
}
\\ Here function DispEnemy$ is a closure to function Display_frame (a copy insert in lambda environment)
Display_frame=lambda DispEnemy$ ->{
                  \\ Over with one parameter push a copy of Nth element of stack
                  \\ Second parameter is the repeat value, so Over 4,4 copies 4 elements of stack, at top of stack
                  
Over 4,4
                  \\ Print Over clear line, and make temporary changes: set proportional printing, and one tab whole width
                  \\ $(6), set center justification
                  
Print Over $(6), format$("Player Pos {0}  Enemy Pos {1} Score {2}  frame id {3}", str$(Number,"00"), DispEnemy$(Number), str$(Number,"000"), Number)
                  \\ so now stack is unchanged
                  \\ we need to use Call Disp_Frame(), so we get back the stack
                  \\ this call pass stack to function and get stack from it
}
\\ Here we insert 2 functions as closures to New_Score
New_Score=lambda Display_frame, MyKey (a,b, x, f_id, f_id_out) -> {
      \\ Here we use variables, but we make current stack again as new feed
      \\ We read from stack here but we didn't see the command, but the command exist
      \\ There is a command:
      
rem : Read a,b, x, f_id, f_id_out
      \\ which interpreter insert at definition time using parameter list (a,b, x, f_id, f_id_out)
  
      \\ So now we have to create the new stack "frame"
      \\ Push feed the stack to head so MyKey(a) value is at the top of the stack
      
Push MyKey(a)
      
if b<0 then {
            
b++
            
if b=0 then b=random(1,10) : f_id_out=abs(a-b)*5+2
       }
else.if f_id_out=0 then b=-random(5)-4: f_id_out=1000
   
      
if stackitem(1)=b then {
      \\ Data feed stack to bottom, so 0 is the bottom value
            
Data -random(20)-4, x+1,f_id +1, 0
      }
Else {
            
Data b, x, f_id+1, f_id_out-1
      }
      
Call Display_frame()
}
\\ Also here we insert 2 closures to Game_Loop lambda
Game_Loop=lambda New_Score, Key_Esc ->{
      \\ values in stack:  player_pos, enemy_pos, score_sum, frame_id, frame_id_out
      \\ frame_id is stackitem(4)
      \\  a Call pass stack to New_Score()
      \\ 1000/60   is 1/60 second
      
Every 1000/60 {
            
Call New_Score()
            \\ now stack has new values
            
if stackitem(4)>=1000 or Key_Esc() then exit
      }
      =true
      \\ flag for loop now checked and if is true the a loop happen, but flag is turned to false
}

\\ Here game start. There is one function that hold the game
\\ Setting 22pt letters
Mode 22
Cursor 0,height/2-3
\\ calling a function as right expression we have a new stack
\\ 5 parameters needed: player_pos, enemy_pos, score_sum, frame_id, frame_id_out
\\ If player_pos is enemy_pos we get a point
\\ Report 2 use center justification
Report 2, {Blast Enemy v1.1
      use arrow keys Up or Down
      game end if we press spacebar or at frame id 1000
      }
Profiler
Game=Game_Loop(Random(1,10),Random(1,10),0,1,0)
\\ stack from Game_loop erased, because that happen when we call function in an expression
\\ just a print command to change line
Print
Print Over $(6), format$("Play Time: {0:0} seconds",Timecount/1000)
Print
Print "Done"



Δεν υπάρχουν σχόλια:

Δημοσίευση σχολίου

You can feel free to write any suggestion, or idea on the subject.