A tutorial
Starting
require('space',sp)
require('logic',logic)
sp.init()
sp.start(500,{
rel update(x,y)
//draw
sp.rect(10,0,50,50)
//refresh screen
sp.refresh()
},10)
Here we make use of callbacks. Callbacks are relations/functions that will be called by the framework itself when the program runs.
We start by initializing the library with init and calling a relation start with update as a callback. start takes a time in milliseconds and an update relation. We’ve set update to be called every 500 milliseconds.
In update, we use sp.rect(10,0,50,50) to draw a rectangle and then refresh the screen.
See: what is a relation?
Moving a Rectangle
require('space',sp)
rel clear()
sp.setRGB(255,255,255) and sp.clear()
sp.start(500,{
rel update(x,y)
y=x+1
//clear
clear()
//draw
sp.setRGB(0,0,0)
sp.rect(x,0,50,50)
sp.test(a)
//end loop
sp.refresh()
},10)
Now, we set the rectangle to move by 1 pixel each call. It’s important to clear the area to be drawn and then refresh to get the result in the screen. We also set the RGB value of the rectangle.
The start relation takes a parameter x, 10, and passes it as the argument to update.
update(x,y)
Then we use the value passed to update as x in sp.rect(x,0,50,50) and increase it by 1 every time in y=x+1.
Declarative vs Destructive
Our update relation is very declarative. It simply passes state around. The value 10 is used as the parameter x which we pass to y, and y is used as the next parameter.
In general, a declarative language (either logic or functional) tries to keep destructive behavior to a minimum. Destructive behavior includes updating the screen.
The programmer may interpret rect(x,0,50,50) as a declarative statement that “a rectangle will be drawn…”. However, it’s more akin to a direct command to the computer telling it to make a rectangle. Although similar, there’s a subtle difference.
Either way, most of the program logic is kept declarative up to ocasionally calling the drawing interface.
Input
Let’s go a bit further and accept input. If the user presses a key, this changes the direction an image will move.
...
world={x=10 and dir='right'}
img=sp.loadImage('b1.png')
sp.start(500,{
rel mouseup(x,y,state,state)
print([x,y])
rel keypressed(key,initialState,followupState)
print(key)
if(key='right')
set(initialState,'dir',key,followupState)
true
elseif(key='left')
set(initialState,'dir',key,followupState)
elseif(key='escape')
logic.halt()
else
true
followupState=initialState
rel update(w,w2)
w.x=x
w.dir=dir
if(dir='left')
nx=o.x-1
else
nx=o.x+1
//clear
clear()
//draw
sp.setRGB(0,0,0)
sp.drawImage(img,o.x,100)
//end loop
sp.refresh()
set(o,'x',nx,o2)//!o.x=nx
true
},world)
We now add other callback relations and draw an image img instead of a rectangle.
The callback keypressed(key,initialState,followupState) gives the key, which we use to update the state (note that by leaving mouseup as mouseup(x,y,state,state) we’re asserting through the parameters that state stays the same, i.e. we’re not updating the state of mouseup). Furthermore, the program ends if the user presses the escape key.
We use a table as the state, with {x=10 and dir=’right’} being the initial state.
It’s important not to load the image in the update relation, as it’ll be called multiple times.
Be sure to include an image b1.png in the folder, of course.
World
A good way to represent our game world (or the world of our simulation, UI, etc.) is by using a table.
We then simply pass it to update and other callbacks.
If the user wishes for non-declarative state, they can use the mutable libraries, though we won’t cover this in this tutorial. It’s then possible to hold global variables and have a more traditional loop.
About
Cosmos and Space is open-source. If you feel like it, you can contribute to the project. It’s given without any warranty.