// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Belt_List from "@rescript/runtime/lib/es6/Belt_List.mjs";
import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.mjs";
import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.mjs";
import * as Primitive_float from "@rescript/runtime/lib/es6/Primitive_float.mjs";
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.mjs";
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.mjs";

function self_init() {
  
}

let int = ((max) => {
    return ((Math.random() * 100) | 0) % max;
  });

let Random = {
  self_init: self_init,
  int: int
};

let Actors = {};

let Dom_html = {};

function setup_sprite(loopOpt, bbox_offsetOpt, bbox_sizeOpt, img_src, max_frames, max_ticks, frame_size, src_offset) {
  let loop = loopOpt !== undefined ? loopOpt : true;
  let bbox_offset = bbox_offsetOpt !== undefined ? bbox_offsetOpt : [
      0,
      0
    ];
  let bbox_size = bbox_sizeOpt !== undefined ? bbox_sizeOpt : [
      0,
      0
    ];
  let bbox_size$1 = Primitive_object.equal(bbox_size, [
    0,
    0
  ]) ? frame_size : bbox_size;
  let img_src$1 = "./sprites/" + img_src;
  return {
    max_frames: max_frames,
    max_ticks: max_ticks,
    img_src: img_src$1,
    frame_size: frame_size,
    src_offset: src_offset,
    bbox_offset: bbox_offset,
    bbox_size: bbox_size$1,
    loop: loop
  };
}

function make_enemy(param) {
  let dir = param[1];
  switch (param[0]) {
    case "Goomba" :
      return setup_sprite(undefined, [
        1,
        1
      ], [
        14,
        14
      ], "enemies.png", 2, 10, [
        16,
        16
      ], [
        0,
        128
      ]);
    case "GKoopa" :
      if (dir === "Left") {
        return setup_sprite(undefined, [
          4,
          10
        ], [
          11,
          16
        ], "enemies.png", 2, 10, [
          16,
          27
        ], [
          0,
          69
        ]);
      } else {
        return setup_sprite(undefined, [
          1,
          10
        ], [
          11,
          16
        ], "enemies.png", 2, 10, [
          16,
          27
        ], [
          32,
          69
        ]);
      }
    case "RKoopa" :
      if (dir === "Left") {
        return setup_sprite(undefined, [
          4,
          10
        ], [
          11,
          16
        ], "enemies.png", 2, 10, [
          16,
          27
        ], [
          0,
          5
        ]);
      } else {
        return setup_sprite(undefined, [
          1,
          10
        ], [
          11,
          16
        ], "enemies.png", 2, 10, [
          16,
          27
        ], [
          32,
          5
        ]);
      }
    case "GKoopaShell" :
      return setup_sprite(undefined, [
        2,
        2
      ], [
        12,
        13
      ], "enemies.png", 4, 10, [
        16,
        16
      ], [
        0,
        96
      ]);
    case "RKoopaShell" :
      return setup_sprite(undefined, [
        2,
        2
      ], [
        12,
        13
      ], "enemies.png", 4, 10, [
        16,
        16
      ], [
        0,
        32
      ]);
  }
}

function make_particle(x) {
  switch (x) {
    case "GoombaSquish" :
      return setup_sprite(undefined, undefined, undefined, "enemies.png", 1, 0, [
        16,
        16
      ], [
        0,
        144
      ]);
    case "BrickChunkL" :
      return setup_sprite(undefined, undefined, undefined, "chunks.png", 1, 0, [
        8,
        8
      ], [
        0,
        0
      ]);
    case "BrickChunkR" :
      return setup_sprite(undefined, undefined, undefined, "chunks.png", 1, 0, [
        8,
        8
      ], [
        8,
        0
      ]);
    case "Score100" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        12,
        8
      ], [
        0,
        0
      ]);
    case "Score200" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        12,
        9
      ], [
        0,
        9
      ]);
    case "Score400" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        12,
        9
      ], [
        0,
        18
      ]);
    case "Score800" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        12,
        9
      ], [
        0,
        27
      ]);
    case "Score1000" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        14,
        9
      ], [
        13,
        0
      ]);
    case "Score2000" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        14,
        9
      ], [
        13,
        9
      ]);
    case "Score4000" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        14,
        9
      ], [
        13,
        18
      ]);
    case "Score8000" :
      return setup_sprite(undefined, undefined, undefined, "score.png", 1, 0, [
        14,
        9
      ], [
        13,
        27
      ]);
  }
}

function make_type(typ, dir) {
  switch (typ.TAG) {
    case "SPlayer" :
      let pt = typ._0;
      let spr_type = [
        typ._1,
        dir
      ];
      if (pt === "BigM") {
        let typ$1 = spr_type[0];
        if (spr_type[1] === "Left") {
          switch (typ$1) {
            case "Standing" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                13,
                25
              ], "mario-big.png", 1, 0, [
                16,
                27
              ], [
                16,
                5
              ]);
            case "Jumping" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                12,
                25
              ], "mario-big.png", 1, 0, [
                16,
                26
              ], [
                48,
                6
              ]);
            case "Running" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                13,
                25
              ], "mario-big.png", 4, 10, [
                16,
                27
              ], [
                0,
                37
              ]);
            case "Crouching" :
              return setup_sprite(undefined, [
                2,
                10
              ], [
                13,
                17
              ], "mario-big.png", 1, 0, [
                16,
                27
              ], [
                32,
                5
              ]);
          }
        } else {
          switch (typ$1) {
            case "Standing" :
              return setup_sprite(undefined, [
                1,
                1
              ], [
                13,
                25
              ], "mario-big.png", 1, 0, [
                16,
                26
              ], [
                16,
                69
              ]);
            case "Jumping" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                12,
                25
              ], "mario-big.png", 1, 0, [
                16,
                26
              ], [
                48,
                70
              ]);
            case "Running" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                13,
                25
              ], "mario-big.png", 4, 10, [
                16,
                27
              ], [
                0,
                101
              ]);
            case "Crouching" :
              return setup_sprite(undefined, [
                2,
                10
              ], [
                13,
                17
              ], "mario-big.png", 1, 0, [
                16,
                27
              ], [
                32,
                69
              ]);
          }
        }
      } else {
        let typ$2 = spr_type[0];
        if (spr_type[1] === "Left") {
          switch (typ$2) {
            case "Standing" :
              return setup_sprite(undefined, [
                3,
                1
              ], [
                11,
                15
              ], "mario-small.png", 1, 0, [
                16,
                16
              ], [
                0,
                0
              ]);
            case "Jumping" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                13,
                15
              ], "mario-small.png", 2, 10, [
                16,
                16
              ], [
                16,
                16
              ]);
            case "Running" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                12,
                15
              ], "mario-small.png", 3, 5, [
                16,
                16
              ], [
                16,
                0
              ]);
            case "Crouching" :
              return setup_sprite(undefined, [
                1,
                5
              ], [
                14,
                10
              ], "mario-small.png", 1, 0, [
                16,
                16
              ], [
                0,
                64
              ]);
          }
        } else {
          switch (typ$2) {
            case "Standing" :
              return setup_sprite(undefined, [
                1,
                1
              ], [
                11,
                15
              ], "mario-small.png", 1, 0, [
                16,
                16
              ], [
                0,
                32
              ]);
            case "Jumping" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                13,
                15
              ], "mario-small.png", 2, 10, [
                16,
                16
              ], [
                16,
                48
              ]);
            case "Running" :
              return setup_sprite(undefined, [
                2,
                1
              ], [
                12,
                15
              ], "mario-small.png", 3, 5, [
                16,
                16
              ], [
                16,
                32
              ]);
            case "Crouching" :
              return setup_sprite(undefined, [
                1,
                5
              ], [
                14,
                10
              ], "mario-small.png", 1, 0, [
                16,
                16
              ], [
                0,
                64
              ]);
          }
        }
      }
    case "SEnemy" :
      return make_enemy([
        typ._0,
        dir
      ]);
    case "SItem" :
      let x = typ._0;
      switch (x) {
        case "Mushroom" :
          return setup_sprite(undefined, [
            2,
            0
          ], [
            12,
            16
          ], "items.png", 1, 0, [
            16,
            16
          ], [
            0,
            0
          ]);
        case "FireFlower" :
          return setup_sprite(undefined, undefined, undefined, "items.png", 1, 0, [
            16,
            16
          ], [
            0,
            188
          ]);
        case "Star" :
          return setup_sprite(undefined, undefined, undefined, "items.png", 1, 0, [
            16,
            16
          ], [
            16,
            48
          ]);
        case "Coin" :
          return setup_sprite(undefined, [
            3,
            0
          ], [
            12,
            16
          ], "items.png", 3, 15, [
            16,
            16
          ], [
            0,
            80
          ]);
      }
    case "SBlock" :
      let x$1 = typ._0;
      if (typeof x$1 === "object") {
        return setup_sprite(undefined, undefined, undefined, "blocks.png", 4, 15, [
          16,
          16
        ], [
          0,
          16
        ]);
      }
      switch (x$1) {
        case "QBlockUsed" :
          return setup_sprite(undefined, undefined, undefined, "blocks.png", 1, 0, [
            16,
            16
          ], [
            0,
            32
          ]);
        case "Brick" :
          return setup_sprite(undefined, undefined, undefined, "blocks.png", 5, 10, [
            16,
            16
          ], [
            0,
            0
          ]);
        case "UnBBlock" :
          return setup_sprite(undefined, undefined, undefined, "blocks.png", 1, 0, [
            16,
            16
          ], [
            0,
            48
          ]);
        case "Cloud" :
          return setup_sprite(undefined, undefined, undefined, "blocks.png", 1, 0, [
            16,
            16
          ], [
            0,
            64
          ]);
        case "Panel" :
          return setup_sprite(undefined, undefined, undefined, "panel.png", 3, 15, [
            26,
            26
          ], [
            0,
            0
          ]);
        case "Ground" :
          return setup_sprite(undefined, undefined, undefined, "ground.png", 1, 0, [
            16,
            16
          ], [
            0,
            32
          ]);
      }
  }
}

function make_from_params(params, context) {
  let img = document.createElement("img");
  img.src = params.img_src;
  return {
    params: params,
    context: context,
    frame: {
      contents: 0
    },
    ticks: {
      contents: 0
    },
    img: img
  };
}

function make(spawn, dir, context) {
  let params = make_type(spawn, dir);
  return make_from_params(params, context);
}

function make_bgd(context) {
  let params = setup_sprite(undefined, undefined, undefined, "bgd-1.png", 1, 0, [
    512,
    256
  ], [
    0,
    0
  ]);
  return make_from_params(params, context);
}

function make_particle$1(ptyp, context) {
  let params = make_particle(ptyp);
  return make_from_params(params, context);
}

function transform_enemy(enemy_typ, spr, dir) {
  let params = make_enemy([
    enemy_typ,
    dir
  ]);
  let img = document.createElement("img");
  img.src = params.img_src;
  spr.params = params;
  spr.img = img;
}

function update_animation(spr) {
  let curr_ticks = spr.ticks.contents;
  if (curr_ticks >= spr.params.max_ticks) {
    spr.ticks.contents = 0;
    if (spr.params.loop) {
      spr.frame.contents = Primitive_int.mod_(spr.frame.contents + 1 | 0, spr.params.max_frames);
      return;
    } else {
      return;
    }
  } else {
    spr.ticks.contents = curr_ticks + 1 | 0;
    return;
  }
}

let Sprite = {
  setup_sprite: setup_sprite,
  make: make,
  make_bgd: make_bgd,
  make_particle: make_particle$1,
  transform_enemy: transform_enemy,
  update_animation: update_animation
};

function pair_to_xy(pair) {
  return {
    x: pair[0],
    y: pair[1]
  };
}

function make_type$1(typ, ctx) {
  switch (typ) {
    case "BrickChunkL" :
    case "BrickChunkR" :
      return {
        sprite: make_particle$1(typ, ctx),
        rot: 0,
        lifetime: 300
      };
    default:
      return {
        sprite: make_particle$1(typ, ctx),
        rot: 0,
        lifetime: 30
      };
  }
}

function make$1(velOpt, accOpt, part_type, pos, ctx) {
  let vel = velOpt !== undefined ? velOpt : [
      0,
      0
    ];
  let acc = accOpt !== undefined ? accOpt : [
      0,
      0
    ];
  let params = make_type$1(part_type, ctx);
  let pos$1 = pair_to_xy(pos);
  let vel$1 = pair_to_xy(vel);
  let acc$1 = pair_to_xy(acc);
  return {
    params: params,
    part_type: part_type,
    pos: pos$1,
    vel: vel$1,
    acc: acc$1,
    kill: false,
    life: params.lifetime
  };
}

function make_score(score, pos, ctx) {
  let t = score >= 801 ? (
      score >= 2001 ? (
          score !== 4000 ? (
              score !== 8000 ? "Score100" : "Score8000"
            ) : "Score4000"
        ) : (
          score !== 1000 ? (
              score >= 2000 ? "Score2000" : "Score100"
            ) : "Score1000"
        )
    ) : (
      score >= 201 ? (
          score !== 400 ? (
              score >= 800 ? "Score800" : "Score100"
            ) : "Score400"
        ) : (
          score !== 100 && score >= 200 ? "Score200" : "Score100"
        )
    );
  return make$1([
    0.5,
    -0.7
  ], undefined, t, pos, ctx);
}

function update_vel(part) {
  part.vel.x = part.vel.x + part.acc.x;
  part.vel.y = part.vel.y + part.acc.y;
}

function process(part) {
  part.life = part.life - 1 | 0;
  if (part.life === 0) {
    part.kill = true;
  }
  update_vel(part);
  part.pos.x = part.vel.x + part.pos.x;
  part.pos.y = part.vel.y + part.pos.y;
}

let Particle = {
  make: make$1,
  make_score: make_score,
  process: process
};

let id_counter = {
  contents: Pervasives.min_int
};

function setup_obj(has_gravityOpt, speedOpt, param) {
  let has_gravity = has_gravityOpt !== undefined ? has_gravityOpt : true;
  let speed = speedOpt !== undefined ? speedOpt : 1;
  return {
    has_gravity: has_gravity,
    speed: speed
  };
}

function set_vel_to_speed(obj) {
  let speed = obj.params.speed;
  let match = obj.dir;
  if (match === "Left") {
    obj.vel.x = - speed;
  } else {
    obj.vel.x = speed;
  }
}

function make_player() {
  return setup_obj(undefined, 2.8, undefined);
}

function make_type$2(x) {
  switch (x.TAG) {
    case "SPlayer" :
      return make_player();
    case "SEnemy" :
      let x$1 = x._0;
      switch (x$1) {
        case "GKoopaShell" :
        case "RKoopaShell" :
          return setup_obj(undefined, 3, undefined);
        default:
          return setup_obj(undefined, undefined, undefined);
      }
    case "SItem" :
      let x$2 = x._0;
      if (x$2 === "Coin") {
        return setup_obj(false, undefined, undefined);
      } else {
        return setup_obj(undefined, undefined, undefined);
      }
    case "SBlock" :
      return setup_obj(false, undefined, undefined);
  }
}

function new_id() {
  id_counter.contents = id_counter.contents + 1 | 0;
  return id_counter.contents;
}

function make$2($staropt$star, $staropt$star$1, spawnable, context, param) {
  let id = $staropt$star !== undefined ? Primitive_option.valFromOption($staropt$star) : undefined;
  let dir = $staropt$star$1 !== undefined ? $staropt$star$1 : "Left";
  let spr = make(spawnable, dir, context);
  let params = make_type$2(spawnable);
  let id$1 = id !== undefined ? id : new_id();
  let obj = {
    params: params,
    pos: {
      x: param[0],
      y: param[1]
    },
    vel: {
      x: 0.0,
      y: 0.0
    },
    id: id$1,
    jumping: false,
    grounded: false,
    dir: dir,
    invuln: 0,
    kill: false,
    health: 1,
    crouch: false,
    score: 0
  };
  return [
    spr,
    obj
  ];
}

function spawn(spawnable, context, param) {
  let match = make$2(undefined, undefined, spawnable, context, [
    param[0],
    param[1]
  ]);
  let obj = match[1];
  let spr = match[0];
  switch (spawnable.TAG) {
    case "SPlayer" :
      return {
        TAG: "Player",
        _0: spawnable._0,
        _1: spr,
        _2: obj
      };
    case "SEnemy" :
      set_vel_to_speed(obj);
      return {
        TAG: "Enemy",
        _0: spawnable._0,
        _1: spr,
        _2: obj
      };
    case "SItem" :
      return {
        TAG: "Item",
        _0: spawnable._0,
        _1: spr,
        _2: obj
      };
    case "SBlock" :
      return {
        TAG: "Block",
        _0: spawnable._0,
        _1: spr,
        _2: obj
      };
  }
}

function get_sprite(x) {
  return x._1;
}

function get_obj(x) {
  return x._2;
}

function is_player(x) {
  return x.TAG === "Player";
}

function is_enemy(x) {
  return x.TAG === "Enemy";
}

function equals(col1, col2) {
  return col1._2.id === col2._2.id;
}

function normalize_pos(pos, p1, p2) {
  let match = p1.bbox_offset;
  let match$1 = p2.bbox_offset;
  let match$2 = p1.bbox_size;
  let match$3 = p2.bbox_size;
  pos.x = pos.x - (match$3[0] + match$1[0]) + (match$2[0] + match[0]);
  pos.y = pos.y - (match$3[1] + match$1[1]) + (match$2[1] + match[1]);
}

function update_player(player, keys, context) {
  let prev_jumping = player.jumping;
  let prev_dir = player.dir;
  let prev_vx = Math.abs(player.vel.x);
  Belt_List.forEach(keys, extra => {
    let lr_acc = player.vel.x * 0.2;
    switch (extra) {
      case "CLeft" :
        if (!player.crouch) {
          if (player.vel.x > - player.params.speed) {
            player.vel.x = player.vel.x - (0.4 - lr_acc);
          }
          player.dir = "Left";
          return;
        } else {
          return;
        }
      case "CRight" :
        if (!player.crouch) {
          if (player.vel.x < player.params.speed) {
            player.vel.x = player.vel.x + (0.4 + lr_acc);
          }
          player.dir = "Right";
          return;
        } else {
          return;
        }
      case "CUp" :
        if (!player.jumping && player.grounded) {
          player.jumping = true;
          player.grounded = false;
          player.vel.y = Primitive_float.max(player.vel.y - (5.7 + Math.abs(player.vel.x) * 0.25), -6);
          return;
        } else {
          return;
        }
      case "CDown" :
        if (!player.jumping && player.grounded) {
          player.crouch = true;
          return;
        } else {
          return;
        }
    }
  });
  let v = player.vel.x * 0.9;
  let vel_damped = Math.abs(v) < 0.1 ? 0 : v;
  player.vel.x = vel_damped;
  let pl_typ = player.health <= 1 ? "SmallM" : "BigM";
  if (!prev_jumping && player.jumping) {
    return [
      pl_typ,
      make({
        TAG: "SPlayer",
        _0: pl_typ,
        _1: "Jumping"
      }, player.dir, context)
    ];
  } else if (prev_dir !== player.dir || prev_vx === 0 && Math.abs(player.vel.x) > 0 && !player.jumping) {
    return [
      pl_typ,
      make({
        TAG: "SPlayer",
        _0: pl_typ,
        _1: "Running"
      }, player.dir, context)
    ];
  } else if (prev_dir !== player.dir && player.jumping && prev_jumping) {
    return [
      pl_typ,
      make({
        TAG: "SPlayer",
        _0: pl_typ,
        _1: "Jumping"
      }, player.dir, context)
    ];
  } else if (player.vel.y === 0 && player.crouch) {
    return [
      pl_typ,
      make({
        TAG: "SPlayer",
        _0: pl_typ,
        _1: "Crouching"
      }, player.dir, context)
    ];
  } else if (player.vel.y === 0 && player.vel.x === 0) {
    return [
      pl_typ,
      make({
        TAG: "SPlayer",
        _0: pl_typ,
        _1: "Standing"
      }, player.dir, context)
    ];
  } else {
    return;
  }
}

function update_vel$1(obj) {
  if (obj.grounded) {
    obj.vel.y = 0;
    return;
  } else if (obj.params.has_gravity) {
    obj.vel.y = Primitive_float.min(obj.vel.y + 0.2 + Math.abs(obj.vel.y) * 0.01, 4.5);
    return;
  } else {
    return;
  }
}

function update_pos(obj) {
  obj.pos.x = obj.vel.x + obj.pos.x;
  if (obj.params.has_gravity) {
    obj.pos.y = obj.vel.y + obj.pos.y;
    return;
  }
}

function process_obj(obj, mapy) {
  update_vel$1(obj);
  update_pos(obj);
  if (obj.pos.y > mapy) {
    obj.kill = true;
    return;
  }
}

function normalize_origin(pos, spr) {
  let p = spr.params;
  let match = p.bbox_offset;
  let match$1 = p.bbox_size;
  pos.x = pos.x - match[0];
  pos.y = pos.y - (match[1] + match$1[1]);
}

function collide_block(check_xOpt, dir, obj) {
  let check_x = check_xOpt !== undefined ? check_xOpt : true;
  switch (dir) {
    case "North" :
      obj.vel.y = -0.001;
      return;
    case "South" :
      obj.vel.y = 0;
      obj.grounded = true;
      obj.jumping = false;
      return;
    case "East" :
    case "West" :
      break;
  }
  if (check_x) {
    obj.vel.x = 0;
    return;
  }
}

function opposite_dir(dir) {
  if (dir === "Left") {
    return "Right";
  } else {
    return "Left";
  }
}

function reverse_left_right(obj) {
  obj.vel.x = - obj.vel.x;
  obj.dir = opposite_dir(obj.dir);
}

function evolve_enemy(player_dir, typ, spr, obj, context) {
  switch (typ) {
    case "Goomba" :
      obj.kill = true;
      return;
    case "GKoopa" :
      let match = make$2(undefined, obj.dir, {
        TAG: "SEnemy",
        _0: "GKoopaShell"
      }, context, [
        obj.pos.x,
        obj.pos.y
      ]);
      let new_obj = match[1];
      let new_spr = match[0];
      normalize_pos(new_obj.pos, spr.params, new_spr.params);
      return {
        TAG: "Enemy",
        _0: "GKoopaShell",
        _1: new_spr,
        _2: new_obj
      };
    case "RKoopa" :
      let match$1 = make$2(undefined, obj.dir, {
        TAG: "SEnemy",
        _0: "RKoopaShell"
      }, context, [
        obj.pos.x,
        obj.pos.y
      ]);
      let new_obj$1 = match$1[1];
      let new_spr$1 = match$1[0];
      normalize_pos(new_obj$1.pos, spr.params, new_spr$1.params);
      return {
        TAG: "Enemy",
        _0: "RKoopaShell",
        _1: new_spr$1,
        _2: new_obj$1
      };
    case "GKoopaShell" :
    case "RKoopaShell" :
      break;
  }
  obj.dir = player_dir;
  if (obj.vel.x !== 0) {
    obj.vel.x = 0;
  } else {
    set_vel_to_speed(obj);
  }
}

function rev_dir(o, t, s) {
  reverse_left_right(o);
  let old_params = s.params;
  transform_enemy(t, s, o.dir);
  normalize_pos(o.pos, old_params, s.params);
}

function dec_health(obj) {
  let health = obj.health - 1 | 0;
  if (health === 0) {
    obj.kill = true;
    return;
  } else if (obj.invuln === 0) {
    obj.health = health;
    return;
  } else {
    return;
  }
}

function evolve_block(obj, context) {
  dec_health(obj);
  let match = make$2(undefined, undefined, {
    TAG: "SBlock",
    _0: "QBlockUsed"
  }, context, [
    obj.pos.x,
    obj.pos.y
  ]);
  return {
    TAG: "Block",
    _0: "QBlockUsed",
    _1: match[0],
    _2: match[1]
  };
}

function spawn_above(player_dir, obj, typ, context) {
  let item = spawn({
    TAG: "SItem",
    _0: typ
  }, context, [
    obj.pos.x,
    obj.pos.y
  ]);
  let item_obj = item._2;
  item_obj.pos.y = item_obj.pos.y - item._1.params.frame_size[1];
  item_obj.dir = opposite_dir(player_dir);
  set_vel_to_speed(item_obj);
  return item;
}

function get_aabb(obj) {
  let spr = obj._1.params;
  let obj$1 = obj._2;
  let match = spr.bbox_offset;
  let box = obj$1.pos.x + match[0];
  let boy = obj$1.pos.y + match[1];
  let match$1 = spr.bbox_size;
  let sy = match$1[1];
  let sx = match$1[0];
  return {
    center: {
      x: box + sx / 2,
      y: boy + sy / 2
    },
    half: {
      x: sx / 2,
      y: sy / 2
    }
  };
}

function col_bypass(c1, c2) {
  let o1 = c1._2;
  let o2 = c2._2;
  let ctypes;
  switch (c1.TAG) {
    case "Player" :
      ctypes = c2.TAG === "Enemy" ? c1._2.invuln > 0 : false;
      break;
    case "Enemy" :
      ctypes = c2.TAG === "Item";
      break;
    case "Item" :
      switch (c2.TAG) {
        case "Enemy" :
        case "Item" :
          ctypes = true;
          break;
        case "Player" :
        case "Block" :
          ctypes = false;
          break;
      }
      break;
    case "Block" :
      ctypes = false;
      break;
  }
  if (o1.kill || o2.kill) {
    return true;
  } else {
    return ctypes;
  }
}

function check_collision(c1, c2) {
  let b1 = get_aabb(c1);
  let b2 = get_aabb(c2);
  let o1 = c1._2;
  if (col_bypass(c1, c2)) {
    return;
  }
  let vx = b1.center.x - b2.center.x;
  let vy = b1.center.y - b2.center.y;
  let hwidths = b1.half.x + b2.half.x;
  let hheights = b1.half.y + b2.half.y;
  if (!(Math.abs(vx) < hwidths && Math.abs(vy) < hheights)) {
    return;
  }
  let ox = hwidths - Math.abs(vx);
  let oy = hheights - Math.abs(vy);
  if (ox >= oy) {
    if (vy > 0) {
      o1.pos.y = o1.pos.y + oy;
      return "North";
    } else {
      o1.pos.y = o1.pos.y - oy;
      return "South";
    }
  } else if (vx > 0) {
    o1.pos.x = o1.pos.x + ox;
    return "West";
  } else {
    o1.pos.x = o1.pos.x - ox;
    return "East";
  }
}

function kill(collid, ctx) {
  switch (collid.TAG) {
    case "Player" :
      return /* [] */0;
    case "Enemy" :
      let o = collid._2;
      let pos_0 = o.pos.x;
      let pos_1 = o.pos.y;
      let pos = [
        pos_0,
        pos_1
      ];
      let score = o.score > 0 ? ({
          hd: make_score(o.score, pos, ctx),
          tl: /* [] */0
        }) : /* [] */0;
      let remains;
      remains = collid._0 === "Goomba" ? ({
          hd: make$1(undefined, undefined, "GoombaSquish", pos, ctx),
          tl: /* [] */0
        }) : /* [] */0;
      return Pervasives.$at(score, remains);
    case "Item" :
      let o$1 = collid._2;
      if (collid._0 === "Mushroom") {
        return {
          hd: make_score(o$1.score, [
            o$1.pos.x,
            o$1.pos.y
          ], ctx),
          tl: /* [] */0
        };
      } else {
        return /* [] */0;
      }
    case "Block" :
      let o$2 = collid._2;
      let tmp = collid._0;
      if (typeof tmp === "object") {
        return /* [] */0;
      }
      if (tmp !== "Brick") {
        return /* [] */0;
      }
      let pos_0$1 = o$2.pos.x;
      let pos_1$1 = o$2.pos.y;
      let pos$1 = [
        pos_0$1,
        pos_1$1
      ];
      let p1 = make$1([
        -5,
        -5
      ], [
        0,
        0.2
      ], "BrickChunkL", pos$1, ctx);
      let p2 = make$1([
        -3,
        -4
      ], [
        0,
        0.2
      ], "BrickChunkL", pos$1, ctx);
      let p3 = make$1([
        3,
        -4
      ], [
        0,
        0.2
      ], "BrickChunkR", pos$1, ctx);
      let p4 = make$1([
        5,
        -5
      ], [
        0,
        0.2
      ], "BrickChunkR", pos$1, ctx);
      return {
        hd: p1,
        tl: {
          hd: p2,
          tl: {
            hd: p3,
            tl: {
              hd: p4,
              tl: /* [] */0
            }
          }
        }
      };
  }
}

let $$Object = {
  invuln: 60,
  dampen_jump: 4,
  get_sprite: get_sprite,
  get_obj: get_obj,
  spawn: spawn,
  equals: equals,
  is_player: is_player,
  is_enemy: is_enemy,
  normalize_origin: normalize_origin,
  normalize_pos: normalize_pos,
  kill: kill,
  process_obj: process_obj,
  update_player: update_player,
  check_collision: check_collision,
  evolve_enemy: evolve_enemy,
  evolve_block: evolve_block,
  dec_health: dec_health,
  rev_dir: rev_dir,
  reverse_left_right: reverse_left_right,
  collide_block: collide_block,
  spawn_above: spawn_above
};

function render_bbox(sprite, param) {
  let context = sprite.context;
  let match = sprite.params.bbox_offset;
  let match$1 = sprite.params.bbox_size;
  context.strokeStyle = "#FF0000";
  return context.strokeRect(param[0] + match[0], param[1] + match[1], match$1[0], match$1[1]);
}

function render(sprite, param) {
  let context = sprite.context;
  let match = sprite.params.src_offset;
  let match$1 = sprite.params.frame_size;
  let sw = match$1[0];
  let match$2 = sprite.params.frame_size;
  let sx = match[0] + sprite.frame.contents * sw;
  return context.drawImage(sprite.img, sx, match[1], sw, match$1[1], param[0], param[1], match$2[0], match$2[1]);
}

function draw_bgd(bgd, off_x) {
  render(bgd, [
    - off_x,
    0
  ]);
  return render(bgd, [
    bgd.params.frame_size[0] - off_x,
    0
  ]);
}

function clear_canvas(canvas) {
  let context = canvas.getContext("2d");
  let cwidth = canvas.width;
  let cheight = canvas.height;
  context.clearRect(0, 0, cwidth, cheight);
}

function hud(canvas, score, coins) {
  let score_string = score.toString();
  let coin_string = coins.toString();
  let context = canvas.getContext("2d");
  context.font = "10px 'Press Start 2P'";
  context.fillText("Score: " + score_string, canvas.width - 140, 18);
  context.fillText("Coins: " + coin_string, 120, 18);
}

function fps(canvas, fps_val) {
  let fps_str = fps_val.toFixed();
  let context = canvas.getContext("2d");
  context.fillText(fps_str, 10, 18);
}

function game_win(ctx) {
  ctx.rect(0, 0, 512, 512);
  ctx.fillStyle = "black";
  ctx.fill();
  ctx.fillStyle = "white";
  ctx.font = "20px 'Press Start 2P'";
  ctx.fillText("You win!", 180, 128);
  return Pervasives.failwith("Game over.");
}

function game_loss(ctx) {
  ctx.rect(0, 0, 512, 512);
  ctx.fillStyle = "black";
  ctx.fill();
  ctx.fillStyle = "white";
  ctx.font = "20px 'Press Start 2P'";
  ctx.fillText("GAME OVER. You lose!", 60, 128);
  return Pervasives.failwith("Game over.");
}

let Draw = {
  render: render,
  clear_canvas: clear_canvas,
  draw_bgd: draw_bgd,
  render_bbox: render_bbox,
  fps: fps,
  hud: hud,
  game_win: game_win,
  game_loss: game_loss
};

function make$3(param, param$1) {
  return {
    pos: {
      x: 0,
      y: 0
    },
    v_dim: {
      x: param[0],
      y: param[1]
    },
    m_dim: {
      x: param$1[0],
      y: param$1[1]
    }
  };
}

function calc_viewport_point(cc, vc, mc) {
  let vc_half = vc / 2;
  return Primitive_float.min(Primitive_float.max(cc - vc_half, 0), Primitive_float.min(mc - vc, Math.abs(cc - vc_half)));
}

function in_viewport(v, pos) {
  let v_min_x = v.pos.x - 32;
  let v_max_x = v.pos.x + v.v_dim.x;
  let v_min_y = v.pos.y - 32;
  let v_max_y = v.pos.y + v.v_dim.y;
  let x = pos.x;
  let y = pos.y;
  if (x >= v_min_x && x <= v_max_x && y >= v_min_y) {
    return y <= v_max_y;
  } else {
    return false;
  }
}

function out_of_viewport_below(v, y) {
  let v_max_y = v.pos.y + v.v_dim.y;
  return y >= v_max_y;
}

function coord_to_viewport(viewport, coord) {
  return {
    x: coord.x - viewport.pos.x,
    y: coord.y - viewport.pos.y
  };
}

function update(vpt, ctr) {
  let new_x = calc_viewport_point(ctr.x, vpt.v_dim.x, vpt.m_dim.x);
  let new_y = calc_viewport_point(ctr.y, vpt.v_dim.y, vpt.m_dim.y);
  let pos = {
    x: new_x,
    y: new_y
  };
  return {
    pos: pos,
    v_dim: vpt.v_dim,
    m_dim: vpt.m_dim
  };
}

let Viewport = {
  make: make$3,
  calc_viewport_point: calc_viewport_point,
  in_viewport: in_viewport,
  out_of_viewport_below: out_of_viewport_below,
  coord_to_viewport: coord_to_viewport,
  update: update
};

let pressed_keys = {
  left: false,
  right: false,
  up: false,
  down: false,
  bbox: 0
};

let collid_objs = {
  contents: /* [] */0
};

let particles = {
  contents: /* [] */0
};

let last_time = {
  contents: 0
};

function calc_fps(t0, t1) {
  let delta = (t1 - t0) / 1000;
  return 1 / delta;
}

function update_score(state, i) {
  state.score = state.score + i | 0;
}

function process_collision(dir, c1, c2, state) {
  let context = state.ctx;
  let exit = 0;
  let s1;
  let o1;
  let typ;
  let s2;
  let o2;
  let s1$1;
  let o1$1;
  let t2;
  let s2$1;
  let o2$1;
  let o1$2;
  let t2$1;
  let o2$2;
  switch (c1.TAG) {
    case "Player" :
      let o1$3 = c1._2;
      let s1$2 = c1._1;
      switch (c2.TAG) {
        case "Player" :
          return [
            undefined,
            undefined
          ];
        case "Enemy" :
          let o2$3 = c2._2;
          let s2$2 = c2._1;
          let typ$1 = c2._0;
          if (dir === "South") {
            s1 = s1$2;
            o1 = o1$3;
            typ = typ$1;
            s2 = s2$2;
            o2 = o2$3;
            exit = 1;
          } else {
            s1$1 = s1$2;
            o1$1 = o1$3;
            t2 = typ$1;
            s2$1 = s2$2;
            o2$1 = o2$3;
            exit = 2;
          }
          break;
        case "Item" :
          o1$2 = o1$3;
          t2$1 = c2._0;
          o2$2 = c2._2;
          exit = 3;
          break;
        case "Block" :
          let o2$4 = c2._2;
          let t = c2._0;
          if (dir === "North") {
            if (typeof t !== "object") {
              switch (t) {
                case "Brick" :
                  if (c1._0 === "BigM") {
                    collide_block(undefined, dir, o1$3);
                    dec_health(o2$4);
                    return [
                      undefined,
                      undefined
                    ];
                  } else {
                    collide_block(undefined, dir, o1$3);
                    return [
                      undefined,
                      undefined
                    ];
                  }
                case "Panel" :
                  game_win(state.ctx);
                  return [
                    undefined,
                    undefined
                  ];
                default:
                  collide_block(undefined, dir, o1$3);
                  return [
                    undefined,
                    undefined
                  ];
              }
            } else {
              let updated_block = evolve_block(o2$4, context);
              let spawned_item = spawn_above(o1$3.dir, o2$4, t._0, context);
              collide_block(undefined, dir, o1$3);
              return [
                spawned_item,
                updated_block
              ];
            }
          } else {
            let exit$1 = 0;
            if (typeof t !== "object") {
              if (t === "Panel") {
                game_win(state.ctx);
                return [
                  undefined,
                  undefined
                ];
              }
              exit$1 = 4;
            } else {
              exit$1 = 4;
            }
            if (exit$1 === 4) {
              if (dir === "South") {
                state.multiplier = 1;
                collide_block(undefined, dir, o1$3);
                return [
                  undefined,
                  undefined
                ];
              }
              collide_block(undefined, dir, o1$3);
              return [
                undefined,
                undefined
              ];
            }
          }
          break;
      }
      break;
    case "Enemy" :
      let o1$4 = c1._2;
      let s1$3 = c1._1;
      let t1 = c1._0;
      switch (c2.TAG) {
        case "Player" :
          let o1$5 = c2._2;
          let s1$4 = c2._1;
          if (dir === "North") {
            s1 = s1$4;
            o1 = o1$5;
            typ = t1;
            s2 = s1$3;
            o2 = o1$4;
            exit = 1;
          } else {
            s1$1 = s1$4;
            o1$1 = o1$5;
            t2 = t1;
            s2$1 = s1$3;
            o2$1 = o1$4;
            exit = 2;
          }
          break;
        case "Enemy" :
          let t2$2 = c2._0;
          let s2$3 = c2._1;
          let o2$5 = c2._2;
          let exit$2 = 0;
          switch (t1) {
            case "GKoopaShell" :
              switch (t2$2) {
                case "GKoopaShell" :
                case "RKoopaShell" :
                  exit$2 = 1;
                  break;
                default:
                  exit$2 = 2;
              }
              break;
            case "RKoopaShell" :
              switch (t2$2) {
                case "GKoopaShell" :
                case "RKoopaShell" :
                  exit$2 = 1;
                  break;
                default:
                  exit$2 = 2;
              }
              break;
            default:
              switch (t2$2) {
                case "GKoopaShell" :
                case "RKoopaShell" :
                  exit$2 = 3;
                  break;
                default:
                  let exit$3 = 0;
                  switch (dir) {
                    case "North" :
                    case "South" :
                      return [
                        undefined,
                        undefined
                      ];
                    case "East" :
                    case "West" :
                      exit$3 = 4;
                      break;
                  }
                  if (exit$3 === 4) {
                    rev_dir(o1$4, t1, s1$3);
                    rev_dir(o2$5, t2$2, s2$3);
                    return [
                      undefined,
                      undefined
                    ];
                  }
              }
          }
          switch (exit$2) {
            case 1 :
              dec_health(o1$4);
              dec_health(o2$5);
              return [
                undefined,
                undefined
              ];
            case 2 :
              if (o1$4.vel.x === 0) {
                rev_dir(o2$5, t2$2, s2$3);
                return [
                  undefined,
                  undefined
                ];
              } else {
                dec_health(o2$5);
                return [
                  undefined,
                  undefined
                ];
              }
            case 3 :
              if (o2$5.vel.x === 0) {
                rev_dir(o1$4, t1, s1$3);
                return [
                  undefined,
                  undefined
                ];
              } else {
                dec_health(o1$4);
                return [
                  undefined,
                  undefined
                ];
              }
          }
        case "Item" :
          return [
            undefined,
            undefined
          ];
        case "Block" :
          let o2$6 = c2._2;
          let t2$3 = c2._0;
          let exit$4 = 0;
          switch (dir) {
            case "North" :
            case "South" :
              collide_block(undefined, dir, o1$4);
              return [
                undefined,
                undefined
              ];
            case "East" :
            case "West" :
              exit$4 = 4;
              break;
          }
          if (exit$4 === 4) {
            let exit$5 = 0;
            let typ$2;
            switch (t1) {
              case "GKoopaShell" :
                if (typeof t2$3 !== "object") {
                  if (t2$3 === "Brick") {
                    dec_health(o2$6);
                    reverse_left_right(o1$4);
                    return [
                      undefined,
                      undefined
                    ];
                  }
                  exit$5 = 5;
                } else {
                  typ$2 = t2$3._0;
                  exit$5 = 6;
                }
                break;
              case "RKoopaShell" :
                if (typeof t2$3 !== "object") {
                  if (t2$3 === "Brick") {
                    dec_health(o2$6);
                    reverse_left_right(o1$4);
                    return [
                      undefined,
                      undefined
                    ];
                  }
                  exit$5 = 5;
                } else {
                  typ$2 = t2$3._0;
                  exit$5 = 6;
                }
                break;
              default:
                exit$5 = 5;
            }
            switch (exit$5) {
              case 5 :
                rev_dir(o1$4, t1, s1$3);
                return [
                  undefined,
                  undefined
                ];
              case 6 :
                let updated_block$1 = evolve_block(o2$6, context);
                let spawned_item$1 = spawn_above(o1$4.dir, o2$6, typ$2, context);
                rev_dir(o1$4, t1, s1$3);
                return [
                  updated_block$1,
                  spawned_item$1
                ];
            }
          }
          break;
      }
      break;
    case "Item" :
      let o2$7 = c1._2;
      switch (c2.TAG) {
        case "Player" :
          o1$2 = c2._2;
          t2$1 = c1._0;
          o2$2 = o2$7;
          exit = 3;
          break;
        case "Enemy" :
        case "Item" :
          return [
            undefined,
            undefined
          ];
        case "Block" :
          switch (dir) {
            case "North" :
            case "South" :
              collide_block(undefined, dir, o2$7);
              return [
                undefined,
                undefined
              ];
            case "East" :
            case "West" :
              reverse_left_right(o2$7);
              return [
                undefined,
                undefined
              ];
          }
      }
      break;
    case "Block" :
      return [
        undefined,
        undefined
      ];
  }
  switch (exit) {
    case 1 :
      o1.invuln = 10;
      o1.jumping = false;
      o1.grounded = true;
      switch (typ) {
        case "GKoopaShell" :
        case "RKoopaShell" :
          break;
        default:
          dec_health(o2);
          o1.vel.y = - 4;
          if (state.multiplier === 8) {
            update_score(state, 800);
            o2.score = 800;
            return [
              undefined,
              evolve_enemy(o1.dir, typ, s2, o2, context)
            ];
          }
          let score = 100 * state.multiplier | 0;
          update_score(state, score);
          o2.score = score;
          state.multiplier = (state.multiplier << 1);
          return [
            undefined,
            evolve_enemy(o1.dir, typ, s2, o2, context)
          ];
      }
      let r2 = evolve_enemy(o1.dir, typ, s2, o2, context);
      o1.vel.y = - 4;
      o1.pos.y = o1.pos.y - 5;
      return [
        undefined,
        r2
      ];
    case 2 :
      switch (t2) {
        case "GKoopaShell" :
        case "RKoopaShell" :
          break;
        default:
          dec_health(o1$1);
          o1$1.invuln = 60;
          return [
            undefined,
            undefined
          ];
      }
      let r2$1 = o2$1.vel.x === 0 ? evolve_enemy(o1$1.dir, t2, s2$1, o2$1, context) : (dec_health(o1$1), o1$1.invuln = 60, undefined);
      return [
        undefined,
        r2$1
      ];
    case 3 :
      let exit$6 = 0;
      switch (t2$1) {
        case "Mushroom" :
          dec_health(o2$2);
          if (o1$2.health === 2) {
            
          } else {
            o1$2.health = o1$2.health + 1 | 0;
          }
          o1$2.vel.x = 0;
          o1$2.vel.y = 0;
          update_score(state, 1000);
          o2$2.score = 1000;
          return [
            undefined,
            undefined
          ];
        case "FireFlower" :
        case "Star" :
          exit$6 = 4;
          break;
        case "Coin" :
          state.coins = state.coins + 1 | 0;
          dec_health(o2$2);
          update_score(state, 100);
          return [
            undefined,
            undefined
          ];
      }
      if (exit$6 === 4) {
        dec_health(o2$2);
        update_score(state, 1000);
        return [
          undefined,
          undefined
        ];
      }
      break;
  }
}

function broad_phase(collid, all_collids, state) {
  let obj = collid._2;
  return Belt_List.filter(all_collids, c => {
    if (in_viewport(state.vpt, obj.pos) || is_player(collid)) {
      return true;
    } else {
      return out_of_viewport_below(state.vpt, obj.pos.y);
    }
  });
}

function check_collisions(collid, all_collids, state) {
  if (collid.TAG === "Block") {
    return /* [] */0;
  }
  let broad = broad_phase(collid, all_collids, state);
  let _cs = broad;
  let _acc = /* [] */0;
  while (true) {
    let acc = _acc;
    let cs = _cs;
    if (cs === 0) {
      return acc;
    }
    let h = cs.hd;
    let c_obj = collid._2;
    let new_objs;
    if (equals(collid, h)) {
      new_objs = [
        undefined,
        undefined
      ];
    } else {
      let dir = check_collision(collid, h);
      new_objs = dir !== undefined && h._2.id !== c_obj.id ? process_collision(dir, collid, h, state) : [
          undefined,
          undefined
        ];
    }
    let o = new_objs[0];
    let acc$1;
    if (o !== undefined) {
      let o2 = new_objs[1];
      acc$1 = o2 !== undefined ? ({
          hd: o,
          tl: {
            hd: o2,
            tl: acc
          }
        }) : ({
          hd: o,
          tl: acc
        });
    } else {
      let o$1 = new_objs[1];
      acc$1 = o$1 !== undefined ? ({
          hd: o$1,
          tl: acc
        }) : acc;
    }
    _acc = acc$1;
    _cs = cs.tl;
    continue;
  };
}

function update_collidable(state, collid, all_collids) {
  let obj = collid._2;
  let spr = collid._1;
  obj.invuln = obj.invuln > 0 ? obj.invuln - 1 | 0 : 0;
  let viewport_filter = in_viewport(state.vpt, obj.pos) || is_player(collid) || out_of_viewport_below(state.vpt, obj.pos.y);
  if (!(!obj.kill && viewport_filter)) {
    return /* [] */0;
  }
  obj.grounded = false;
  process_obj(obj, state.map);
  let evolved = check_collisions(collid, all_collids, state);
  let vpt_adj_xy = coord_to_viewport(state.vpt, obj.pos);
  render(spr, [
    vpt_adj_xy.x,
    vpt_adj_xy.y
  ]);
  if (pressed_keys.bbox === 1) {
    render_bbox(spr, [
      vpt_adj_xy.x,
      vpt_adj_xy.y
    ]);
  }
  if (obj.vel.x !== 0 || !is_enemy(collid)) {
    update_animation(spr);
  }
  return evolved;
}

function translate_keys() {
  let ctrls_0 = [
    pressed_keys.left,
    "CLeft"
  ];
  let ctrls_1 = {
    hd: [
      pressed_keys.right,
      "CRight"
    ],
    tl: {
      hd: [
        pressed_keys.up,
        "CUp"
      ],
      tl: {
        hd: [
          pressed_keys.down,
          "CDown"
        ],
        tl: /* [] */0
      }
    }
  };
  let ctrls = {
    hd: ctrls_0,
    tl: ctrls_1
  };
  return Belt_List.reduceReverse(ctrls, /* [] */0, (a, x) => {
    if (x[0]) {
      return {
        hd: x[1],
        tl: a
      };
    } else {
      return a;
    }
  });
}

function run_update_collid(state, collid, all_collids) {
  if (collid.TAG === "Player") {
    let o = collid._2;
    let keys = translate_keys();
    o.crouch = false;
    let match = update_player(o, keys, state.ctx);
    let player;
    if (match !== undefined) {
      let new_spr = match[1];
      normalize_pos(o.pos, collid._1.params, new_spr.params);
      player = {
        TAG: "Player",
        _0: match[0],
        _1: new_spr,
        _2: o
      };
    } else {
      player = collid;
    }
    let evolved = update_collidable(state, player, all_collids);
    collid_objs.contents = Pervasives.$at(collid_objs.contents, evolved);
    return player;
  }
  let obj = collid._2;
  let evolved$1 = update_collidable(state, collid, all_collids);
  if (!obj.kill) {
    collid_objs.contents = {
      hd: collid,
      tl: Pervasives.$at(collid_objs.contents, evolved$1)
    };
  }
  let new_parts = obj.kill ? kill(collid, state.ctx) : /* [] */0;
  particles.contents = Pervasives.$at(particles.contents, new_parts);
  return collid;
}

function update_loop(canvas, param, map_dim) {
  let player = param[0];
  let ctx = canvas.getContext("2d");
  let cwidth = canvas.width / 1;
  let cheight = canvas.height / 1;
  let viewport = make$3([
    cwidth,
    cheight
  ], map_dim);
  let state = {
    bgd: make_bgd(ctx),
    ctx: ctx,
    vpt: update(viewport, player._2.pos),
    map: map_dim[1],
    score: 0,
    coins: 0,
    multiplier: 1,
    game_over: false
  };
  state.ctx.scale(1, 1);
  let update_helper = (time, state, player, objs, parts) => {
    if (state.game_over === true) {
      return game_win(state.ctx);
    }
    collid_objs.contents = /* [] */0;
    particles.contents = /* [] */0;
    let fps$1 = calc_fps(last_time.contents, time);
    last_time.contents = time;
    clear_canvas(canvas);
    let vpos_x_int = state.vpt.pos.x / 5 | 0;
    let bgd_width = state.bgd.params.frame_size[0] | 0;
    draw_bgd(state.bgd, Primitive_int.mod_(vpos_x_int, bgd_width));
    let player$1 = run_update_collid(state, player, objs);
    if (player$1._2.kill === true) {
      return game_loss(state.ctx);
    }
    let state$1 = {
      bgd: state.bgd,
      ctx: state.ctx,
      vpt: update(state.vpt, player$1._2.pos),
      map: state.map,
      score: state.score,
      coins: state.coins,
      multiplier: state.multiplier,
      game_over: state.game_over
    };
    Belt_List.forEach(objs, obj => {
      run_update_collid(state$1, obj, objs);
    });
    Belt_List.forEach(parts, part => {
      process(part);
      let x = part.pos.x - state$1.vpt.pos.x;
      let y = part.pos.y - state$1.vpt.pos.y;
      render(part.params.sprite, [
        x,
        y
      ]);
      if (!part.kill) {
        particles.contents = {
          hd: part,
          tl: particles.contents
        };
        return;
      }
    });
    fps(canvas, fps$1);
    hud(canvas, state$1.score, state$1.coins);
    requestAnimationFrame(t => update_helper(t, state$1, player$1, collid_objs.contents, particles.contents));
  };
  update_helper(0, state, player, param[1], /* [] */0);
}

function keydown(evt) {
  let match = evt.keyCode;
  if (match >= 41) {
    switch (match) {
      case 65 :
        pressed_keys.left = true;
        break;
      case 66 :
        pressed_keys.bbox = (pressed_keys.bbox + 1 | 0) % 2;
        break;
      case 68 :
        pressed_keys.right = true;
        break;
      case 83 :
        pressed_keys.down = true;
        break;
      case 87 :
        pressed_keys.up = true;
        break;
    }
  } else if (match >= 32) {
    switch (match) {
      case 33 :
      case 34 :
      case 35 :
      case 36 :
        break;
      case 37 :
        pressed_keys.left = true;
        break;
      case 32 :
      case 38 :
        pressed_keys.up = true;
        break;
      case 39 :
        pressed_keys.right = true;
        break;
      case 40 :
        pressed_keys.down = true;
        break;
    }
  }
  return true;
}

function keyup(evt) {
  let match = evt.keyCode;
  if (match >= 68) {
    if (match !== 83) {
      if (match !== 87) {
        if (match >= 69) {
          
        } else {
          pressed_keys.right = false;
        }
      } else {
        pressed_keys.up = false;
      }
    } else {
      pressed_keys.down = false;
    }
  } else if (match >= 41) {
    if (match !== 65) {
      
    } else {
      pressed_keys.left = false;
    }
  } else if (match >= 32) {
    switch (match) {
      case 33 :
      case 34 :
      case 35 :
      case 36 :
        break;
      case 37 :
        pressed_keys.left = false;
        break;
      case 32 :
      case 38 :
        pressed_keys.up = false;
        break;
      case 39 :
        pressed_keys.right = false;
        break;
      case 40 :
        pressed_keys.down = false;
        break;
    }
  }
  return true;
}

let Director = {
  update_loop: update_loop,
  keydown: keydown,
  keyup: keyup
};

function mem_loc(checkloc, _loclist) {
  while (true) {
    let loclist = _loclist;
    if (loclist === 0) {
      return false;
    }
    if (Primitive_object.equal(checkloc, loclist.hd[1])) {
      return true;
    }
    _loclist = loclist.tl;
    continue;
  };
}

function convert_list(lst) {
  if (lst === 0) {
    return /* [] */0;
  }
  let h = lst.hd;
  return Pervasives.$at({
    hd: [
      h[0],
      [
        h[1][0] * 16,
        h[1][1] * 16
      ]
    ],
    tl: /* [] */0
  }, convert_list(lst.tl));
}

function choose_enemy_typ(typ) {
  switch (typ) {
    case 0 :
      return "RKoopa";
    case 1 :
      return "GKoopa";
    case 2 :
      return "Goomba";
    default:
      return Pervasives.failwith("Shouldn't reach here");
  }
}

function choose_sblock_typ(typ) {
  switch (typ) {
    case 0 :
      return "Brick";
    case 1 :
      return "UnBBlock";
    case 2 :
      return "Cloud";
    case 3 :
      return {
        TAG: "QBlock",
        _0: "Mushroom"
      };
    case 4 :
      return "Ground";
    default:
      return Pervasives.failwith("Shouldn't reach here");
  }
}

function avoid_overlap(_lst, currentLst) {
  while (true) {
    let lst = _lst;
    if (lst === 0) {
      return /* [] */0;
    }
    let t = lst.tl;
    let h = lst.hd;
    if (!mem_loc(h[1], currentLst)) {
      return Pervasives.$at({
        hd: h,
        tl: /* [] */0
      }, avoid_overlap(t, currentLst));
    }
    _lst = t;
    continue;
  };
}

function trim_edges(_lst, blockw, blockh) {
  while (true) {
    let lst = _lst;
    if (lst === 0) {
      return /* [] */0;
    }
    let t = lst.tl;
    let h = lst.hd;
    let cx = h[1][0];
    let cy = h[1][1];
    let pixx = blockw * 16;
    let pixy = blockh * 16;
    if (!(cx < 128 || pixx - cx < 528 || cy === 0 || pixy - cy < 48)) {
      return Pervasives.$at({
        hd: h,
        tl: /* [] */0
      }, trim_edges(t, blockw, blockh));
    }
    _lst = t;
    continue;
  };
}

function generate_clouds(cbx, cby, typ, num) {
  if (num === 0) {
    return /* [] */0;
  } else {
    return Pervasives.$at({
      hd: [
        typ,
        [
          cbx,
          cby
        ]
      ],
      tl: /* [] */0
    }, generate_clouds(cbx + 1, cby, typ, num - 1 | 0));
  }
}

function generate_coins(_block_coord) {
  while (true) {
    let block_coord = _block_coord;
    let place_coin = int(2);
    if (block_coord === 0) {
      return /* [] */0;
    }
    let t = block_coord.tl;
    let h = block_coord.hd;
    if (place_coin === 0) {
      let xc = h[1][0];
      let yc = h[1][1];
      return Pervasives.$at({
        hd: [
          0,
          [
            xc,
            yc - 16
          ]
        ],
        tl: /* [] */0
      }, generate_coins(t));
    }
    _block_coord = t;
    continue;
  };
}

function choose_block_pattern(blockw, blockh, cbx, cby, prob) {
  if (cbx > blockw || cby > blockh) {
    return /* [] */0;
  }
  let block_typ = int(4);
  let stair_typ = int(2);
  let life_block_chance = int(5);
  let middle_block = life_block_chance === 0 ? 3 : stair_typ;
  switch (prob) {
    case 0 :
      if (blockw - cbx > 2) {
        return {
          hd: [
            stair_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: {
            hd: [
              middle_block,
              [
                cbx + 1,
                cby
              ]
            ],
            tl: {
              hd: [
                stair_typ,
                [
                  cbx + 2,
                  cby
                ]
              ],
              tl: /* [] */0
            }
          }
        };
      } else if (blockw - cbx > 1) {
        return {
          hd: [
            block_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: {
            hd: [
              block_typ,
              [
                cbx + 1,
                cby
              ]
            ],
            tl: /* [] */0
          }
        };
      } else {
        return {
          hd: [
            block_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: /* [] */0
        };
      }
    case 1 :
      let num_clouds = int(5) + 5 | 0;
      if (cby < 5) {
        return generate_clouds(cbx, cby, 2, num_clouds);
      } else {
        return /* [] */0;
      }
    case 2 :
      if (blockh - cby === 1) {
        let four_0 = [
          stair_typ,
          [
            cbx,
            cby
          ]
        ];
        let four_1 = {
          hd: [
            stair_typ,
            [
              cbx + 1,
              cby
            ]
          ],
          tl: {
            hd: [
              stair_typ,
              [
                cbx + 2,
                cby
              ]
            ],
            tl: {
              hd: [
                stair_typ,
                [
                  cbx + 3,
                  cby
                ]
              ],
              tl: /* [] */0
            }
          }
        };
        let four = {
          hd: four_0,
          tl: four_1
        };
        let three_0 = [
          stair_typ,
          [
            cbx + 1,
            cby - 1
          ]
        ];
        let three_1 = {
          hd: [
            stair_typ,
            [
              cbx + 2,
              cby - 1
            ]
          ],
          tl: {
            hd: [
              stair_typ,
              [
                cbx + 3,
                cby - 1
              ]
            ],
            tl: /* [] */0
          }
        };
        let three = {
          hd: three_0,
          tl: three_1
        };
        let two_0 = [
          stair_typ,
          [
            cbx + 2,
            cby - 2
          ]
        ];
        let two_1 = {
          hd: [
            stair_typ,
            [
              cbx + 3,
              cby - 2
            ]
          ],
          tl: /* [] */0
        };
        let two = {
          hd: two_0,
          tl: two_1
        };
        let one_0 = [
          stair_typ,
          [
            cbx + 3,
            cby - 3
          ]
        ];
        let one = {
          hd: one_0,
          tl: /* [] */0
        };
        return Pervasives.$at(four, Pervasives.$at(three, Pervasives.$at(two, one)));
      } else {
        return /* [] */0;
      }
    case 3 :
      if (stair_typ === 0 && blockh - cby > 3) {
        let three_0$1 = [
          stair_typ,
          [
            cbx,
            cby
          ]
        ];
        let three_1$1 = {
          hd: [
            stair_typ,
            [
              cbx + 1,
              cby
            ]
          ],
          tl: {
            hd: [
              stair_typ,
              [
                cbx + 2,
                cby
              ]
            ],
            tl: /* [] */0
          }
        };
        let three$1 = {
          hd: three_0$1,
          tl: three_1$1
        };
        let two_0$1 = [
          stair_typ,
          [
            cbx + 2,
            cby + 1
          ]
        ];
        let two_1$1 = {
          hd: [
            stair_typ,
            [
              cbx + 3,
              cby + 1
            ]
          ],
          tl: /* [] */0
        };
        let two$1 = {
          hd: two_0$1,
          tl: two_1$1
        };
        let one_0$1 = [
          stair_typ,
          [
            cbx + 5,
            cby + 2
          ]
        ];
        let one_1 = {
          hd: [
            stair_typ,
            [
              cbx + 6,
              cby + 2
            ]
          ],
          tl: /* [] */0
        };
        let one$1 = {
          hd: one_0$1,
          tl: one_1
        };
        return Pervasives.$at(three$1, Pervasives.$at(two$1, one$1));
      } else if (blockh - cby > 2) {
        let one_0$2 = [
          stair_typ,
          [
            cbx,
            cby
          ]
        ];
        let one_1$1 = {
          hd: [
            stair_typ,
            [
              cbx + 1,
              cby
            ]
          ],
          tl: /* [] */0
        };
        let one$2 = {
          hd: one_0$2,
          tl: one_1$1
        };
        let two_0$2 = [
          stair_typ,
          [
            cbx + 3,
            cby - 1
          ]
        ];
        let two_1$2 = {
          hd: [
            stair_typ,
            [
              cbx + 4,
              cby - 1
            ]
          ],
          tl: /* [] */0
        };
        let two$2 = {
          hd: two_0$2,
          tl: two_1$2
        };
        let three_0$2 = [
          stair_typ,
          [
            cbx + 4,
            cby - 2
          ]
        ];
        let three_1$2 = {
          hd: [
            stair_typ,
            [
              cbx + 5,
              cby - 2
            ]
          ],
          tl: {
            hd: [
              stair_typ,
              [
                cbx + 6,
                cby - 2
              ]
            ],
            tl: /* [] */0
          }
        };
        let three$2 = {
          hd: three_0$2,
          tl: three_1$2
        };
        return Pervasives.$at(one$2, Pervasives.$at(two$2, three$2));
      } else {
        return {
          hd: [
            stair_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: /* [] */0
        };
      }
    case 4 :
      if (cby + 3 - blockh === 2) {
        return {
          hd: [
            stair_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: /* [] */0
        };
      } else if (cby + 3 - blockh === 1) {
        return {
          hd: [
            stair_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: {
            hd: [
              stair_typ,
              [
                cbx,
                cby + 1
              ]
            ],
            tl: /* [] */0
          }
        };
      } else {
        return {
          hd: [
            stair_typ,
            [
              cbx,
              cby
            ]
          ],
          tl: {
            hd: [
              stair_typ,
              [
                cbx,
                cby + 1
              ]
            ],
            tl: {
              hd: [
                stair_typ,
                [
                  cbx,
                  cby + 2
                ]
              ],
              tl: /* [] */0
            }
          }
        };
      }
    case 5 :
      return {
        hd: [
          3,
          [
            cbx,
            cby
          ]
        ],
        tl: /* [] */0
      };
    default:
      return Pervasives.failwith("Shouldn't reach here");
  }
}

function generate_enemies(blockw, blockh, _cbx, _cby, acc) {
  while (true) {
    let cby = _cby;
    let cbx = _cbx;
    if (cbx > blockw - 32) {
      return /* [] */0;
    }
    if (cby > blockh - 1 || cbx < 15) {
      _cby = 0;
      _cbx = cbx + 1;
      continue;
    }
    if (mem_loc([
        cbx,
        cby
      ], acc) || cby === 0) {
      _cby = cby + 1;
      continue;
    }
    let prob = int(30);
    if (prob < 3 && blockh - 1 === cby) {
      let enemy_0 = [
        prob,
        [
          cbx * 16,
          cby * 16
        ]
      ];
      let enemy = {
        hd: enemy_0,
        tl: /* [] */0
      };
      return Pervasives.$at(enemy, generate_enemies(blockw, blockh, cbx, cby + 1, acc));
    }
    _cby = cby + 1;
    continue;
  };
}

function generate_block_enemies(_block_coord) {
  while (true) {
    let block_coord = _block_coord;
    let place_enemy = int(20);
    let enemy_typ = int(3);
    if (block_coord === 0) {
      return /* [] */0;
    }
    let t = block_coord.tl;
    let h = block_coord.hd;
    if (place_enemy === 0) {
      let xc = h[1][0];
      let yc = h[1][1];
      return Pervasives.$at({
        hd: [
          enemy_typ,
          [
            xc,
            yc - 16
          ]
        ],
        tl: /* [] */0
      }, generate_block_enemies(t));
    }
    _block_coord = t;
    continue;
  };
}

function generate_block_locs(blockw, blockh, _cbx, _cby, _acc) {
  while (true) {
    let acc = _acc;
    let cby = _cby;
    let cbx = _cbx;
    if (blockw - cbx < 33) {
      return acc;
    }
    if (cby > blockh - 1) {
      _cby = 0;
      _cbx = cbx + 1;
      continue;
    }
    if (mem_loc([
        cbx,
        cby
      ], acc) || cby === 0) {
      _cby = cby + 1;
      continue;
    }
    let prob = int(100);
    if (prob < 5) {
      let newacc = choose_block_pattern(blockw, blockh, cbx, cby, prob);
      let undup_lst = avoid_overlap(newacc, acc);
      let called_acc = Pervasives.$at(acc, undup_lst);
      _acc = called_acc;
      _cby = cby + 1;
      continue;
    }
    _cby = cby + 1;
    continue;
  };
}

function generate_panel(context, blockw, blockh) {
  return spawn({
    TAG: "SBlock",
    _0: "Panel"
  }, context, [
    blockw * 16 - 256,
    blockh * 16 * 2 / 3
  ]);
}

function generate_ground(blockw, blockh, _inc, _acc) {
  while (true) {
    let acc = _acc;
    let inc = _inc;
    if (inc > blockw) {
      return acc;
    }
    if (inc > 10) {
      let skip = int(10);
      let newacc = Pervasives.$at(acc, {
        hd: [
          4,
          [
            inc * 16,
            blockh * 16
          ]
        ],
        tl: /* [] */0
      });
      if (skip === 7 && blockw - inc > 32) {
        _inc = inc + 1;
        continue;
      }
      _acc = newacc;
      _inc = inc + 1;
      continue;
    }
    let newacc$1 = Pervasives.$at(acc, {
      hd: [
        4,
        [
          inc * 16,
          blockh * 16
        ]
      ],
      tl: /* [] */0
    });
    _acc = newacc$1;
    _inc = inc + 1;
    continue;
  };
}

function convert_to_block_obj(lst, context) {
  if (lst === 0) {
    return /* [] */0;
  }
  let h = lst.hd;
  let sblock_typ = choose_sblock_typ(h[0]);
  let ob = spawn({
    TAG: "SBlock",
    _0: sblock_typ
  }, context, h[1]);
  return Pervasives.$at({
    hd: ob,
    tl: /* [] */0
  }, convert_to_block_obj(lst.tl, context));
}

function convert_to_enemy_obj(lst, context) {
  if (lst === 0) {
    return /* [] */0;
  }
  let h = lst.hd;
  let senemy_typ = choose_enemy_typ(h[0]);
  let ob = spawn({
    TAG: "SEnemy",
    _0: senemy_typ
  }, context, h[1]);
  return Pervasives.$at({
    hd: ob,
    tl: /* [] */0
  }, convert_to_enemy_obj(lst.tl, context));
}

function convert_to_coin_obj(lst, context) {
  if (lst === 0) {
    return /* [] */0;
  }
  let ob = spawn({
    TAG: "SItem",
    _0: "Coin"
  }, context, lst.hd[1]);
  return Pervasives.$at({
    hd: ob,
    tl: /* [] */0
  }, convert_to_coin_obj(lst.tl, context));
}

function generate_helper(blockw, blockh, cx, cy, context) {
  let block_locs = generate_block_locs(blockw, blockh, 0, 0, /* [] */0);
  let converted_block_locs = trim_edges(convert_list(block_locs), blockw, blockh);
  let obj_converted_block_locs = convert_to_block_obj(converted_block_locs, context);
  let ground_blocks = generate_ground(blockw, blockh, 0, /* [] */0);
  let obj_converted_ground_blocks = convert_to_block_obj(ground_blocks, context);
  let block_locations = Pervasives.$at(block_locs, ground_blocks);
  let all_blocks = Pervasives.$at(obj_converted_block_locs, obj_converted_ground_blocks);
  let enemy_locs = generate_enemies(blockw, blockh, 0, 0, block_locations);
  let obj_converted_enemies = convert_to_enemy_obj(enemy_locs, context);
  let coin_locs = generate_coins(converted_block_locs);
  let undup_coin_locs = trim_edges(avoid_overlap(coin_locs, converted_block_locs), blockw, blockh);
  let converted_block_coin_locs = Pervasives.$at(converted_block_locs, coin_locs);
  let enemy_block_locs = generate_block_enemies(converted_block_locs);
  let undup_enemy_block_locs = avoid_overlap(enemy_block_locs, converted_block_coin_locs);
  let obj_enemy_blocks = convert_to_enemy_obj(undup_enemy_block_locs, context);
  let coin_objects = convert_to_coin_obj(undup_coin_locs, context);
  let obj_panel = generate_panel(context, blockw, blockh);
  return Pervasives.$at(all_blocks, Pervasives.$at(obj_converted_enemies, Pervasives.$at(coin_objects, Pervasives.$at(obj_enemy_blocks, {
    hd: obj_panel,
    tl: /* [] */0
  }))));
}

function generate(w, h, context) {
  let blockw = w / 16;
  let blockh = h / 16 - 1;
  let collide_list = generate_helper(blockw, blockh, 0, 0, context);
  let player = spawn({
    TAG: "SPlayer",
    _0: "SmallM",
    _1: "Standing"
  }, context, [
    100,
    224
  ]);
  return [
    player,
    collide_list
  ];
}

function init() {
  
}

let Procedural_generator = {
  init: init,
  generate: generate
};

let loadCount = {
  contents: 0
};

function load(param) {
  let canvas_id = "canvas";
  let el = document.getElementById(canvas_id);
  let canvas = el !== null ? el : (console.log("cant find canvas " + canvas_id), Pervasives.failwith("fail"));
  let context = canvas.getContext("2d");
  document.addEventListener("keydown", keydown, true);
  document.addEventListener("keyup", keyup, true);
  update_loop(canvas, generate(2400, 256, context), [
    2400,
    256
  ]);
  console.log("asd");
}

function inc_counter(param) {
  loadCount.contents = loadCount.contents + 1 | 0;
  if (loadCount.contents === 4) {
    return load();
  }
}

function preload(param) {
  return Belt_List.map({
    hd: "blocks.png",
    tl: {
      hd: "items.png",
      tl: {
        hd: "enemies.png",
        tl: {
          hd: "mario-small.png",
          tl: /* [] */0
        }
      }
    }
  }, img_src => {
    let img_src$1 = "sprites/" + img_src;
    let img = document.createElement("img");
    img.src = img_src$1;
    img.addEventListener("load", ev => {
      inc_counter();
      return true;
    }, true);
  });
}

window.onload = param => {
  preload();
  return true;
};

let Main = {
  Html: undefined,
  Pg: undefined,
  loadCount: loadCount,
  imgsToLoad: 4,
  level_width: 2400,
  level_height: 256,
  load: load,
  inc_counter: inc_counter,
  preload: preload
};

export {
  Random,
  Actors,
  Dom_html,
  Sprite,
  Particle,
  $$Object,
  Draw,
  Viewport,
  Director,
  Procedural_generator,
  Main,
}
/*  Not a pure module */
