Skip to content

Instantly share code, notes, and snippets.

@dmitryshelomanov
Last active May 30, 2019 22:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmitryshelomanov/4fecee32fbc7c7bbb17f14f3a1763675 to your computer and use it in GitHub Desktop.
Save dmitryshelomanov/4fecee32fbc7c7bbb17f14f3a1763675 to your computer and use it in GitHub Desktop.
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