Last active
May 30, 2019 22:27
-
-
Save dmitryshelomanov/4fecee32fbc7c7bbb17f14f3a1763675 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open Webapi.Dom; | |
open Webapi.Canvas.Canvas2d; | |
open Webapi.Canvas.CanvasElement; | |
[@bs.set] external setWidth: (Dom.element, int) => unit = "width"; | |
[@bs.set] external setHeight: (Dom.element, int) => unit = "height"; | |
type point = (float, float); | |
type shape('s, 'a) = { | |
mutable state: 's, | |
reducer: ('a, 's) => 's, | |
tick: shape('s, 'a) => unit, | |
}; | |
module Utils = { | |
let moveToTarget = ((x, y): point, (x1, y1): point, speed) => { | |
let dirX = x1 -. x; | |
let dirY = y1 -. y; | |
let distance = Js.Math.sqrt(dirX *. dirX +. dirY *. dirY); | |
if (int_of_float(distance) <= 1) { | |
(x, y); | |
} else { | |
(dirX /. distance *. speed +. x, dirY /. distance *. speed +. y); | |
}; | |
}; | |
let getMaxMass = (mass, max) => | |
if (mass < max) { | |
mass; | |
} else { | |
max; | |
}; | |
let send = (action, shape) => { | |
shape.state = shape.reducer(action, shape.state); | |
}; | |
}; | |
module Draw = { | |
let circle = (~pos: point, ~r, ~color, ctx) => { | |
let (x, y) = pos; | |
beginPath(ctx); | |
arc( | |
~x, | |
~y, | |
~r, | |
~startAngle=0.0, | |
~endAngle=Js.Math._PI *. 2.0, | |
~anticw=false, | |
ctx, | |
); | |
setFillStyle(ctx, String, color); | |
fill(ctx); | |
}; | |
}; | |
type state = { | |
position: point, | |
targetPosition: point, | |
color: string, | |
mass: float, | |
}; | |
type action = | |
| Move(point) | |
| MassUpdate(float) | |
| TargetUpdate(point); | |
let makeShape = (~pos, ~color, ~mass, callback) => { | |
state: { | |
position: pos, | |
targetPosition: pos, | |
color, | |
mass, | |
}, | |
reducer: (action, state) => | |
switch (action) { | |
| Move(position) => {...state, position} | |
| TargetUpdate(targetPosition) => {...state, targetPosition} | |
| MassUpdate(mass) => {...state, mass} | |
}, | |
tick: callback, | |
}; | |
let wordWidth = ElementRe.clientWidth(DocumentRe.documentElement(document)); | |
let wordHeight = | |
ElementRe.clientHeight(DocumentRe.documentElement(document)); | |
let clearCanvas = (canvas, ctx) => { | |
clearRect( | |
~x=0.0, | |
~y=0.0, | |
~w=float_of_int(ElementRe.clientWidth(canvas)), | |
~h=float_of_int(ElementRe.clientHeight(canvas)), | |
ctx, | |
); | |
}; | |
let main = () => { | |
let canvas = | |
switch (Document.getElementById("app", document)) { | |
| Some(element) => element | |
| None => failwith("canvas not found") | |
}; | |
setWidth(canvas, wordWidth); | |
setHeight(canvas, wordHeight); | |
let ctx = getContext2d(canvas); | |
let foods = ref([||]); | |
for (_ in 0 to 10) { | |
let x = Js.Math.random() *. float_of_int(wordWidth); | |
let y = Js.Math.random() *. float_of_int(wordHeight); | |
let food = | |
makeShape(~pos=(x, y), ~color="green", ~mass=1.0, self => | |
Draw.circle( | |
~pos=self.state.position, | |
~r=10.0, | |
~color=self.state.color, | |
ctx, | |
) | |
); | |
Js.Array.push(food, foods^); | |
}; | |
let player = | |
makeShape( | |
~pos=(150.0, 150.0), | |
~color="red", | |
~mass=7.0, | |
self => { | |
let computedPos = | |
Utils.moveToTarget( | |
self.state.position, | |
self.state.targetPosition, | |
Js.Math.abs_float( | |
20.0 /. Utils.getMaxMass(self.state.mass, 20.0), | |
), | |
); | |
Utils.send(Move(computedPos), self); | |
Draw.circle( | |
~pos=self.state.position, | |
~r=self.state.mass *. 5.0, | |
~color=self.state.color, | |
ctx, | |
); | |
}, | |
); | |
let mouseHandler = e => { | |
let x = float_of_int(MouseEvent.offsetX(e)); | |
let y = float_of_int(MouseEvent.offsetY(e)); | |
Utils.send(TargetUpdate((x, y)), player); | |
}; | |
ElementRe.addMouseMoveEventListener(mouseHandler, canvas); | |
let than = ref(0.0); | |
let rec loop = _t => { | |
if (Js.Date.now() -. than^ >= 0.0) { | |
let foodsTmp = ref(Js_array.copy(foods^)); | |
than := Js.Date.now(); | |
let (plx, ply) = player.state.position; | |
Js_array.forEachi( | |
(food, index) => { | |
let (x, y) = food.state.position; | |
let dirX = x -. plx; | |
let dirY = y -. ply; | |
let distance = Js.Math.sqrt(dirX *. dirX +. dirY *. dirY); | |
if (distance <= player.state.mass *. 5.0 +. 10.0) { | |
foodsTmp := Js_array.filteri((_, idx) => idx !== index, foods^); | |
Utils.send( | |
MassUpdate(player.state.mass +. food.state.mass), | |
player, | |
); | |
}; | |
}, | |
foods^, | |
); | |
foods := foodsTmp^; | |
clearCanvas(canvas, ctx); | |
Js_array.forEach(food => food.tick(food), foods^); | |
player.tick(player); | |
}; | |
Webapi.requestAnimationFrame(loop); | |
}; | |
Webapi.requestAnimationFrame(loop); | |
}; | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment