Skip to content

Instantly share code, notes, and snippets.

@Raynos
Last active December 14, 2015 10:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Raynos/5075037 to your computer and use it in GitHub Desktop.
Save Raynos/5075037 to your computer and use it in GitHub Desktop.
var coreduction = require("coreduction")
var map = require("reducers/map")
var reductions = require("reducers/reductions")
var fold = require("reducers/fold")
// These are inputs. Inputs are Streams / Events /
// Signals / Behaviours
// Inputs aren't hard to write, they just wrap lower level host
// environments and create a streaming representation of their
// current state
var KeyboardArrows = require("../lib/keyboard-arrows")
var WindowDimension = require("../lib/window-dimension")
var fps = require("../lib/fps")
// named color variables
var skyblue = rgb(174, 238, 238)
var grassgreen = rgb(74, 163, 41)
/* Your app has a state representation. This is something that
gets passed to update and update returns a new representation
of current state based on input changes.
This state representation also get's passed to display
which maps it to a data structure for Renderable display
items.
*/
// ---- Model ----
var mario = { x: 0, y: 0, vx: 0, vy: 0 }
/* Your actual app consists of constructing the input sources
for your app.
Calling foldp(input, update, initialState) which will
return a stream of states by calling
`update(currentState, changeInInput)`
Thus `marioState` is a stream of current states.
We then lift two streams `WindowDimension` and `marioState`
through the display function which will transform the
current state into a renderable data structure. It also
depends on the window dimension so that the display
gets resized correctly.
We then call `render(main)` which does the hard machinery of
efficiently rendering our declarative rendering structures
by doing clever creation of DOM / SVG / Canvas and clever
partial updating.
*/
// ---- Game ----
var input = lift2(fps(30), KeyboardArrows, function (dt, arrows) {
return { dt: dt, arrows: arrows }
})
var marioState = foldp(input, update, mario)
var main = lift2(WindowDimension, marioState, display)
/* magic :D. Render will diff your current renderable data structure
with the last and do a partial mutable update */
render(main)
/* The display function is in charge turn current state into
something that can be rendered
In this case we grab a correct gif for how mario should look
and then render him in the correct position based on his
x & y.
We also render the sky and grass as rectangles
*/
// ---- Display ----
function display(windowDimension, mario) {
var w = windowDimension.w
var h = windowDimension.h
var verb = mario.y > 0 ? "jump" :
mario.vx !== 0 ? "walk" : "stand"
var direction = mario.vx < 0 ? "left" : "rigth"
var src = "/imgs/mario/" + verb + "/" + direction + ".gif"
var largeRect = rect(h, { x: half(w), y: half(h) })
var smallRect = rect(50, { x: half(w), y: 25 })
var sky = filled(largeRect, skyblue)
var grass = filled(smallRect, grassgreen)
var marioImage = image(35, 35, src)
var marioForm = toForm(marioImage, {
x: mario.x
, y: (h - 63) - mario.y
})
return collage(w, h, [sky, grass, marioForm])
function half(n) {
return n / 2
}
}
/* The update logic determines how the model / state changes
based on inputs
It consists of applying the jumping logic which just sets
mario's y velocity if he's allowed to jump.
Then applying gravity which decrements his y velocity for
every tick of the fps counter
Then we apply the walking logic which just sets his x velocity
based on whether left or right is pressed.
Then finally we move him which updates his x, y position based
on velocities
*/
// ---- Update ----
function update(mario, tuple) {
var dt = tuple.dt
var arrows = tuple.dt
var jumpedMario = jump(mario, arrows)
var gravityMario = gravity(jumpedMario, dt)
var walkedMario = walk(gravityMario, arrows)
return move(walkedMario, dt)
}
function gravity(mario, dt) {
return {
vy: mario.vy - (dt / 700)
, vx: mario.vx
, x: mario.x
, y: mario.y
}
}
function jump(mario, arrows) {
if (arrows.y > 0 && mario.y === 0) {
return {
vy: 0.5
, vx: mario.vx
, x: mario.x
, y: mario.y
}
} else {
return mario
}
}
function walk(mario, arrows) {
return {
vx: arrows.x / 20
, vy: mario.vy
, x: mario.x
, y: mario.y
}
}
function move(mario, dt) {
return {
x: mario.x + dt * mario.vx
, y: max(0, mario.y + dt * mario.yx)
, vx: mario.vx
, vy: mario.vy
}
}
/* Returns a data structure for rendering image */
function image(width, height, src) {
// figure out a good data structure for an image
}
/* Returns a form data structure to render an arbitary elem at
that co-ordinate
*/
function toForm(elem, coords) {
// figure out a good data structure for Form & Element
}
/* Returns a data structure that can be rendered as a rectangle
*/
function rect(size, center) {
// figure out a good data structure for Rect (which is a Form)
}
/* Return a shape filled in with said color
*/
function filled(shape, color) {
// figure out how to set color on Shape data structure
}
/* collage creates a scene of size width x height and renders
the list of shapes inside it.
*/
function collage(width, height, shapes) {
// WORK IN PROGRESS
// Figure out good data structure for a Collage of { height, width, manyForms }
return {
values: [width, height]
, render: render
, update: update
, children: [shapes]
, is: "Renderable"
}
function render(width, height) {
/*global document*/
var surface = document.createElement("svg")
surface.width = width
surface.height = height
return surface
}
function update(surface, width, height) {
surface.width = width
surface.height = height
}
}
/* main is a stream of lazy elements, shapes and forms.
It's `render`'s job to efficiently render them
It should just do a diff between curr and prev and either create
or update things
*/
function render(main) {
// WORK IN PROGRESS
// THIS NEEDS TO RENDER EVERY RENDERABLE DATA STRUCTURE
// IT ALSO NEEDS TO DO CLEVER PARTIAL UPDATES
// THIS IS HARD
var mains = reductions(main, function (prev, curr) {
var render = curr[0]
var update = curr[1]
}, null)
fold(mains, function () {})
}
function foldp(input, f, initial) {
return reductions(input, f, initial)
}
function lift2(a, b, mapper) {
return map(coreduction(a, b), function (tuple) {
return mapper(tuple[0], tuple[1])
})
}
function max(x, y) {
return x > y ? x : y
}
function rgb(r, g, b) {
return { r: r, g: g, b: b }
}
@refset
Copy link

refset commented Mar 3, 2013

Hmm. I'm lost at understanding how curr[0] and curr[1] get assigned. I don't suppose curr[2] is marioState?

Also, on line 78 you've got "rigth"

I don't have enough smarts to actually help with this just yet :/

@refset
Copy link

refset commented Mar 3, 2013

It may actually be quite straightforward to reuse the logic from core-js/Graphics/Render.js and core-js/Graphics/Collage.js

All those single-space tabbed files make me :O

@Raynos
Copy link
Author

Raynos commented Mar 3, 2013

@jez0990 curr[0] and curr[1] are not things. I don't know what the data structure should look like.

And yes most of Graphics could be ported but then you have to port the entire thing + commonJS it.

I should talk to evan about taking Graphics out as a seperate open source JS thing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment