Module

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 grid
  • cellSize :: 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).

#modify_ Source

modify_ :: forall world m. (world -> world) -> Action m world Unit

Modify the current value of the world by passing in a (world -> world) updating function. The 'world' is the current state of the reactor. Doesn't return anything, as opposed to the modify action.

#get Source

get :: forall world m. Action m world world

Obtain the current value of the world.

#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 and y are the coordinates of the event. They are relative to the rendering grid, and denote the cell where the event hapened.
  • control, meta, shift, and alt denote whether any modifier keys were pressed when the event happened
  • button 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 the onMouse handler.

Constructors

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 }. A meta 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 the onKey handler.

Constructors

Re-exports from Reactor.Graphics.CoordinateSystem

#CoordinateSystem Source

#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 }fromInttoNumber`.

#canvas Source

canvas :: forall a. a -> CoordinateSystem a

To be used with wrt, usually { some record }wrt` canvas.

Re-exports from Reactor.Graphics.Drawing

#fill Source

fill :: Color -> Shape -> Drawing

Fill a shape with a color.

#cell Source

cell :: CoordinateSystem Point -> Shape

A 1-square cell on the given point in the grid.

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 -> Typetype 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. Usually m is Effect or Aff (i.e. asynchronous Effect). 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 world
  • draw is a function used to render the world anytime it is changed
  • onTick 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 some Action, 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 the onKey function, it receives the details of the event and does some Action, 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 rendered
  • width, the width of the grid, in cells
  • height the height of the grid, in cells Notably, the size of the rendered cells isn't customizable and is set to 30pts.