Reactor
- Package
- purescript-grid-reactors
- Repository
- Eugleo/purescript-grid-reactors
As the Pyret documentation puts it, a reactor is a value enabling the creation of time-based animations, simulations, and interactive programs. During the creation of a reactor, the user supplies a function for handling clock ticks, and functions for handling mouse and keyboard input events. The reactor calls these event handlers whenever the respective event occurs. From within the event handlers, the reactor's state — or, the world, as we call it — can be updated. After each world update, the reactor renders the new world with the supplied drawing function.
You can read more in the documentation of the type Reactor
below, under the documentation for the module Reactor.Types
.
The base Reactor module re-exports some useful functions, apart
from the base color pallete which is best imported by import Reactor.Graphics.Colors as Color
.
In our implementation, the rendering is done using a hooks-based Halogen component.
Also, the world needs to have at least a boolean field named paused
, that signalizes
to the reactor whether the internal clock should be ticking or not.
#runReactor Source
runReactor :: forall world. Reactor Aff { paused :: Boolean | world } -> Configuration -> Effect Unit
Start a Reactor
and render it asynchronously as a Halogen component with the given Configuration
.
The reactor's world is required to have a field named paused
that is used to decide
whether the reactor's clock should be running (paused: false
) or not (paused: true
).
Re-exports from Reactor.Action
#utilities Source
utilities :: forall world m. Action m world Utilities
Get a record of the following:
bound :: CoordinateSystem Point -> CoordinateSystem Point
, a function that bounds the given point in the grid or the canvas, meaning the point won't be beyond the bounds of its coordinate system.height :: Int
,width :: Int
, the dimensions of the gridcellSize :: Int
the size of one grid cell, in points. The size is set internally, and this is the only way to its value.
#togglePause Source
togglePause :: forall world m. Action m { paused :: Boolean | world } Unit
Toggle the paused
attribute of the world from false
to true
and vice versa.
A shorthand for
modify_ \w -> w { paused = not w.paused }
#preventDefaultBehavior Source
preventDefaultBehavior :: forall world m. Action m { paused :: Boolean | world } DefaultBehavior
Prevent the execution of the default behavior associated with the event.
You can read more in the documentation of Reactor.Events
.
Usually, this or executeDefaultBehavior
is the last thing you'll call in
your onMouse
and onKey
handlers. This one will usually get called in
the events you handle (i.e. that have some functionality associated with them in your game).
#executeDefaultBehavior Source
executeDefaultBehavior :: forall world m. Action m { paused :: Boolean | world } DefaultBehavior
After handling the event, execute the default behavior as well.
You can read more in the documentation of Reactor.Events
.
Usually, this or preventDefaultBehavior
is the last thing you'll call in
your onMouse
and onKey
handlers. This one will usually get called in
the events you only let pass through (i.e. when pressing 'J' doesn't 'do anything' in your game).
Re-exports from Reactor.Events
#TickEvent Source
newtype TickEvent
This event is fired on every clock-tick in the reactor.
The attribute delta
is the time from the last tick, in seconds.
There's approximately 60 ticks per second, however, the number can vary depending on the browser's current available resources.
For the smoothest motion, you should calculate the traveled distance based on the
speed of the entity and the delta
.
Constructors
#MouseEvent Source
data MouseEvent
This event is fired whenever the user presses a button on their mouse, or moves the mouse over the rendering grid. Explanation of each of the fields:
type
is the type of the event (drag, button up, etc.)x
andy
are the coordinates of the event. They are relative to the rendering grid, and denote the cell where the event hapened.control
,meta
,shift
, andalt
denote whether any modifier keys were pressed when the event happenedbutton
is an identifier of the button that has been pressed during the event, if applicable. See this entry in MDN Web Docs for more details. You pattern-match on this event in theonMouse
handler.
Constructors
MouseEvent { alt :: Boolean, button :: Int, control :: Boolean, meta :: Boolean, shift :: Boolean, type :: MouseEventType, x :: Int, y :: Int }
Instances
#KeypressEvent Source
data KeypressEvent
This event is fired whenever the user presses down a key on the keyboard.
The key is passed in the event as a String
, with the following rules:
- Numbers, letters, and symbols all have intuitive codes (type A → get "A", type ů → get "ů").
Notably, pressing space produces an event with a literal space,
" "
. - Any modifier keys that were pressed simultaneously with the main key are passed in the record
{ shift, control, alt, meta }
. Ameta
key is the Windows button on Windows, and the Command button on the Mac. - Common special keys have intuitive names. For example:
ArrowLeft
,ArrowRight
,ArrowUp
,ArrowDown
. Full list can be seen on MDN Web Docs. You pattern-match on this event in theonKey
handler.
Constructors
Re-exports from Reactor.Graphics.CoordinateSystem
#wrt Source
wrt :: forall a b. a -> (a -> CoordinateSystem b) -> CoordinateSystem b
With respect to. Used to wrap things in a coordinate system in a more pleasant way than what would be possible with the constructors. For example
{ x: 1, y: 1 } `wrt` grid == RelativeToGrid { x: toNumber 1, y: toNumber 1}
#withCoords Source
withCoords :: forall a b. CoordinateSystem a -> (a -> b) -> b
Unwrap the wrapped thing and pass it to a function.
#grid Source
grid :: forall a b. HMap (Int -> Number) a b => a -> CoordinateSystem b
To be used with wrt
, usually { some record }
wrtgrid. Automatically converts
the numbers in
{ some record }from
Intto
Number`.
#canvas Source
canvas :: forall a. a -> CoordinateSystem a
To be used with wrt
, usually { some record }
wrt` canvas.
Re-exports from Reactor.Graphics.Drawing
Re-exports from Reactor.Page
#component Source
component :: forall world q i o m. MonadEffect m => Reactor m { paused :: Boolean | world } -> Configuration -> Component q i o m
Defines the Halogen component that runs and renders a reactor. The component is based on Halogen hooks.
Usually you don't need to call this yourself; instead, you should use Reactor.runReactor
that calls this internally.
Re-exports from Reactor.Types
#Reactor Source
type Reactor :: (Type -> Type) -> Type -> Type
type Reactor m world = { draw :: world -> Drawing, init :: world, onKey :: KeypressEvent -> Action m world DefaultBehavior, onMouse :: MouseEvent -> Action m world DefaultBehavior, onTick :: TickEvent -> Action m world Unit }
The reactor is a simple record. A reactor is is parametrized over the following type variables:
m
signifies which monad will be used to run the event handlers, which is mostly an implementation detail. Usuallym
isEffect
orAff
(i.e. asynchronousEffect
). You can safely ignore this most of the time.world
denotes what is the type of the internal state of the reactor. This is where the state of your game or simulation is saved.
The fields in the record are the following:
init
is the initial state of the reactor's worlddraw
is a function used to render the world anytime it is changedonTick
is a function called on every tick of the reactor's clock. It is called around 60 times a second, provided the reactor is not paused.onKey
is an event handler for keyboard input events. It receives the pressed key and does someAction
, which usually involves updating the world based on the received input.onMouse
is an event handler for mouse input events (button clicks, draggging, moving). Similarly to theonKey
function, it receives the details of the event and does someAction
, which usually involves updating the world based on the event.
For example, when ran, the following reactor would render a player at the initial position, and would allow the user to move the player by pressing arrow keys. The clock is paused, and mouse events are ignored.
import Reactor.Action
(executeDefaultBehavior, modify_, preventDefaultBehavior, utilities)
import Reactor.Events (KeypressEvent(..))
import Reactor.Graphics.Colors as Color
import Reactor.Graphics.CoordinateSystem
(CoordinateSystem, grid, moveDown, moveLeft, moveRight, moveUp, wrt)
import Reactor.Graphics.Drawing (fill, cell)
import Reactor.Types (Reactor)
type World =
{ player :: CoordinateSystem { x :: Number, y :: Number }
, paused :: Boolean
}
reactor :: forall m. Reactor m World
reactor =
{ init: { player: { x: 0, y: 0 } `wrt` grid, paused: true }
, draw: \{ player } -> fill Color.blue400 $ cell player
, onTick: \_ -> pure unit
, onKey: \(KeypressEvent key _) -> do
{ bound } <- utilities
case key of
"ArrowLeft" -> do
modify_ \w@{ player } ->
w { player = bound $ moveLeft player }
preventDefaultBehavior
"ArrowRight" -> do
modify_ \w@{ player } ->
w { player = bound $ moveRight player }
preventDefaultBehavior
"ArrowDown" -> do
modify_ \w@{ player } ->
w { player = bound $ moveDown player }
preventDefaultBehavior
"ArrowUp" -> do
modify_ \w@{ player } ->
w { player = bound $ moveUp player }
preventDefaultBehavior
_ -> executeDefaultBehavior
, onMouse: \_ -> executeDefaultBehavior
}
#Configuration Source
type Configuration = { height :: Int, title :: String, width :: Int }
Configuration for the Halogen component that renders the reactor. Although a reactor is a general structure, our reactors focus on grid-based games and simulations. The component thus needs a little more info than just what is in already the reactor, namely:
title
, the title of the webpage where the reactor is renderedwidth
, the width of the grid, in cellsheight
the height of the grid, in cells Notably, the size of the rendered cells isn't customizable and is set to 30pts.