I don't know which is most concise way to make a ground platform here... because random platforms generation going from right-to-left, and if you add some constantly generated ground platform at the bottom, then the rest of random platforms will disappear simply. maybe if you could somehow separate process random and ground platforms generation... any ideas? if you add some statement to random platform generation function and adjust it to be solid at the bottom?: class Platform {
constructor(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
collidesWith(obj) {
if (obj.y + obj.h <= this.y) return false;
if (obj.y >= this.y + this.h) return false;
if (obj.x + obj.w <= this.x) return false;
if (obj.x >= this.x + this.w) return false;
const objAbove = obj.y + obj.h - obj.vy <= this.y;
const objBelow = obj.y - obj.vy >= this.y + this.h;
const objLeft = obj.x + obj.w - obj.vx <= this.x;
const objRight = obj.x - obj.vx >= this.x + this.w;
if (obj.vy > 0 && objAbove && !objBelow) {
obj.y = this.y - obj.h;
obj.vy = 0;
obj.jumping = false;
return true;
}
if (obj.vy < 0 && !objAbove && objBelow) {
obj.y = this.y + this.h;
obj.vy = 0;
return true;
}
if (obj.vx < 0 && objRight) {
obj.x = this.x + this.w;
obj.vx = 0;
return true;
}
if (obj.vx > 0 && objLeft) {
obj.x = this.x - obj.w;
obj.vx = 0;
return true;
}
return false;
}
}

class Player {
constructor(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.vx = 0;
this.vy = 0;
this.jumping = false;
}
move(keys) {
const friction = .8;
const gty = 0.8;
if (keys[87] && !this.jumping) {
this.vy -= 10;
this.jumping = true;
}
if (keys[68]) {
this.vx += 0.8;
}
if (keys[65]) {
this.vx -= 0.8;
}
this.vx *= friction;
this.vy += gty;
this.x += this.vx;
this.y += this.vy;
if (this.x < 0) {
this.x = 0;
}
if (this.y < 0) {
this.y = 0;
}
if (this.x + this.w > canvas.width) {
this.x = canvas.width - this.w;
this.vx = 0;
}
if (this.y + this.h > canvas.height) {
this.y = canvas.height - this.h;
this.vy = 0;
this.jumping = false;
}
}
}

class Projectile {
constructor(x, y, vx, vy) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.radius = 3;
this.color = "red";
}
update() {
this.x += this.vx;
this.y += this.vy;
}
draw(ctx) {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}



class Entity {
constructor() {
this.x = canvas.width;
this.y = Math.random() * canvas.height;
this.w = 10;
this.h = 10;  

this.vy = 0;
this.jumping = false;
this.projectiles = [];
this.color = "blue";


}
jump() {
if (!this.jumping) {
this.vy -= 25 - Math.random() * 25;
this.jumping = true;
}
}
update(platforms) {
for (let i = 0; i < platforms.length; i++) {
platforms[i].collidesWith(this);
}
const friction = .99;
const gty = 1;
this.vx = friction;
this.vy += gty;
this.x += this.vx;
this.y += this.vy;this.period = Math.PI;
this.minVx = -2 + Math.sin(Math.random() * 2 * Math.PI) * 2;
this.maxVx = -5 + Math.cos(Math.random() * 2 * Math.PI) * -10;
  this.vx = this.vxForce = randomRange(this.minVx, this.maxVx);
    const t = performance.now() / 1000;
    const wave = Math.sin(t * Math.PI / this.period);
  this.vxForce = lerp(this.maxVx, this.minVx, (wave + 5) / 200, 10); 
  this.x += this.vxForce;      
if (Math.random() < .01) {
this.projectiles.push(new Projectile(this.x, this.y, -2 - Math.random() * 6, -2 + Math.random() * 8));
}
for (let i = 0; i < this.projectiles.length; i++) {
this.projectiles[i].update();
if (this.projectiles[i].x < 0 || this.projectiles[i].y < 0 || this.projectiles[i].x > canvas.width || this.projectiles[i].y > canvas.height) {
this.projectiles.splice(i, 1);
i;
}
}
if (Math.random() < .01) {
this.jump();
}
}
draw(ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
for (let i = 0; i < this.projectiles.length; i++) {
this.projectiles[i].draw(ctx);
}
}
}

function randomRange(min, max) {
return Math.random() * (max - min) + min;
}

function lerp(a, b, t, d) {
  return a * (1 - t / d) + b * t / d;
}

class Game {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.platforms = [];
this.player = new Player(10, 10, 10, 10);
this.scrollSpeed = 0.5;
this.entities = [];
this.entitySpawnRate = .1;
this.entitySpawnTimer = 1;
this.entityIncreaseFactor = .1;
this.keys = {};
  for (let i = 0; i < 10; i++) {
this.createRandomPlatform();
}
document.addEventListener("keydown", evt => {
this.keys[evt.keyCode] = true;
});
document.addEventListener("keyup", evt => {
delete this.keys[evt.keyCode];
});
requestAnimationFrame(this.update.bind(this));
}
createRandomPlatform() {
const x = this.canvas.width;
const y = Math.random() * this.canvas.height;
const w = 20 + Math.random() * 50;
const h = 5;
this.platforms.push(new Platform(x, y, w, h));
}
update() {
this.player.move(this.keys);
for (let i = 0; i < this.platforms.length; i++) {
this.platforms[i].collidesWith(this.player);
this.platforms[i].x -= this.scrollSpeed;
}
for (let i = 0; i < this.entities.length; i++) {
if (this.entities[i]) {
this.entities[i].update(this.platforms);
if (this.entities[i].x < 0) {
this.entities.splice(i, 1);
i;
for (let j = 0; j < this.entityIncreaseFactor; j++) {
this.entities.push(new Entity());
}
} else {
for (let j = 0; j < this.entities[i].projectiles.length; j++) {
if (this.entities[i].projectiles[j].x > this.player.x && this.entities[i].projectiles[j].x < this.player.x + this.player.w && this.entities[i].projectiles[j].y > this.player.y && this.entities[i].projectiles[j].y < this.player.y + this.player.h) {
this.player.vy -= 20;
this.player.jumping = true;
this.entities[i].projectiles.splice(j, 1);
j;
}
}
this.entities[i].draw(this.ctx);
}
}
}
this.player.x -= this.scrollSpeed;
if (this.platforms[this.platforms.length - 1].x < this.canvas.width - 20) {
this.createRandomPlatform();
}
this.entitySpawnTimer++;
if (this.entitySpawnTimer >= 60 / this.entitySpawnRate) {
this.entitySpawnTimer = 0;
this.entities.push(new Entity());
this.entitySpawnRate += .001;
this.entityIncreaseFactor = Math.min(this.entityIncreaseFactor + .001, .001);
}
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
for (let i = 0; i < this.platforms.length; i++) {
let p = this.platforms[i];
this.ctx.fillRect(p.x, p.y, p.w, p.h);
}
for (let i = 0; i < this.entities.length; i++) {
this.entities[i].draw(this.ctx);
}
this.ctx.fillRect(this.player.x, this.player.y, this.player.w, this.player.h);
requestAnimationFrame(this.update.bind(this));
}
}

let canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
document.body.appendChild(canvas);
new Game(canvas);
