fix player not show to other and make game on all of the screen and also make player have gravity and jump

client.js
import kaplay from "kaplay";
import "kaplay/global";

const k = kaplay({
    width: 800, // Increased width to accommodate larger map
    height: 600, // Increased height to accommodate larger map
    scale: 2,
});

const tileSize = 36; // Increased tile size for larger map
const serverHost = 'localhost';
const serverPort = 25566;

let mapLayout = [];
let players = {};
let localPlayer = { 
    position: { x: 0, y: 0 }, 
    color: { r: 255, g: 255, b: 255 }, 
    name: "Player" + Math.floor(Math.random() * 1000) // Dynamic name
};
let socket;

let tileSprites = [];
let playerSprites = {};

// Utility to deserialize data from server
function deserialize(data) {
    try {
        return JSON.parse(data);
    } catch (error) {
        console.error("Deserialization error:", error);
        return null;
    }
}

// Load sprites
loadSprite("tile", "/sprites/grass.png");
loadSprite("player", "/sprites/player.png");

scene("game", () => {
    function drawMap() {
        tileSprites.forEach(tile => tile.destroy()); // Clear previous tiles
        tileSprites = [];  // Reset tile sprites array

        mapLayout.forEach((row, y) => {
            row.forEach((tile, x) => {
                if (tile === 1) {
                    const tileSprite = add([
                        sprite("tile"),
                        pos(x * tileSize, y * tileSize),
                        area(),
                        layer("tiles")
                    ]);
                    tileSprites.push(tileSprite);  // Store the tile sprite
                }
            });
        });
    }

    function drawPlayers() {
        // Update existing player sprites or add new ones
        Object.values(players).forEach(player => {
            if (playerSprites[player.id]) {
                // Update position and color
                playerSprites[player.id].pos = vec2(player.position.x, player.position.y);
                playerSprites[player.id].color = rgb(player.color.r, player.color.g, player.color.b);
            } else {
                // Add new player sprite
                playerSprites[player.id] = add([
                    sprite("player"),
                    pos(player.position.x, player.position.y),
                    color(player.color.r, player.color.g, player.color.b),
                    scale(2), // Increased scale for larger player
                    area(),
                    layer("players")
                ]);
            }
        });

        // Remove sprites of disconnected players
        for (let id in playerSprites) {
            if (!players[id]) {
                destroy(playerSprites[id]);
                delete playerSprites[id];
            }
        }
    }

    // Connect to the server
    connectToServer();

    // Player movement
    onKeyDown("left", () => {
        localPlayer.position.x -= tileSize;
        sendPlayerUpdate();
    });
    onKeyDown("right", () => {
        localPlayer.position.x += tileSize;
        sendPlayerUpdate();
    });
    onKeyDown("up", () => {
        localPlayer.position.y -= tileSize;
        sendPlayerUpdate();
    });
    onKeyDown("down", () => {
        localPlayer.position.y += tileSize;
        sendPlayerUpdate();
    });

    // Send local player position to server
    function sendPlayerUpdate() {
        if (socket.readyState === WebSocket.OPEN) {
            socket.send(JSON.stringify({
                position: localPlayer.position,
            }));
        }
    }

    // Update player positions and redraw the map on each frame
    onUpdate(() => {
        drawPlayers();
    });

    // Connect to the server function
    function connectToServer() {
        socket = new WebSocket(`ws://${serverHost}:${serverPort}`);

        socket.addEventListener("open", () => {
            console.log("Connected to server");
            socket.send(JSON.stringify({ name: localPlayer.name }));  // Send player name
        });

        socket.addEventListener("message", (event) => {
            const data = deserialize(event.data);  // Directly use event.data
            if (!data) return;

            if (data.map_layout) {
                mapLayout = data.map_layout;
                drawMap();  // Call drawMap() after updating the map layout
            }
            if (data.players) {
                players = data.players || players;
                drawPlayers();  // Call drawPlayers() after updating player data
            }
        });

        socket.addEventListener("close", () => {
            console.log("Disconnected from server");
        });
    }
});

// Start the game
go("game");


server.js
const WebSocket = require('ws');
const { randomInt } = require('crypto'); // For generating random integers
const { promisify } = require('util');
const setTimeoutPromise = promisify(setTimeout);

const PORT = 25566;
const players = {};
const tileSize = 36; // Updated to match client
const mapLayouts = [
    // Define map layouts here, example:
    [
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
    ],
    [
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
        [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
    ]
];

let currentMapLayout = mapLayouts[0];
const connections = [];

// Function to serialize data for sending to clients
function serialize(data) {
    return JSON.stringify(data);
}

// Function to get a random spawn position
function getRandomSpawnPosition() {
    const x = randomInt(0, currentMapLayout[0].length);
    const y = randomInt(0, currentMapLayout.length);
    return { x: x * tileSize, y: y * tileSize };
}

// Function to generate a random color
function generateColor() {
    return {
        r: randomInt(0, 256),
        g: randomInt(0, 256),
        b: randomInt(0, 256)
    };
}

// Function to change map layout periodically
async function changeMapLayoutPeriodically() {
    let mapIndex = 1; // Start from the second map since currentMapLayout is mapLayouts[0]
    while (true) {
        await setTimeoutPromise(60000); // Change map layout every 60 seconds
        currentMapLayout = mapLayouts[mapIndex];
        mapIndex = (mapIndex + 1) % mapLayouts.length;

        console.log("Map layout changed.");

        // Send the new map layout to all connected clients
        broadcast({ players, map_layout: currentMapLayout });
    }
}

// Broadcast utility
function broadcast(data) {
    const message = serialize(data);
    connections.forEach(ws => {
        if (ws.readyState === WebSocket.OPEN) {
            ws.send(message);
        }
    });
}

// Create WebSocket server
const wss = new WebSocket.Server({ port: PORT });

wss.on('connection', (ws) => {
    console.log('Client connected');
    connections.push(ws);

    // Assign a unique ID to each WebSocket connection
    ws.id = generateUniqueID();

    // Handle incoming messages from this client
    ws.on('message', (data) => {
        try {
            const message = JSON.parse(data);

            if (message.name) {
                // Initial connection message with player name
                const playerName = message.name;

                // Generate a unique spawn position
                const spawnPosition = getRandomSpawnPosition();

                // Initialize new player with a unique color and position
                players[ws.id] = { // Use the unique identifier as key
                    id: ws.id,
                    name: playerName,
                    position: spawnPosition,
                    color: generateColor()
                };

                // Send initial positions and map layout to the new client
                ws.send(serialize({ players, map_layout: currentMapLayout }));

                // Broadcast to others about the new player
                broadcast({ players, map_layout: currentMapLayout });

            } else if (message.position) {
                // Position update message
                if (players[ws.id]) {
                    players[ws.id].position = message.position;

                    // Broadcast updated positions to all clients
                    broadcast({ players, map_layout: currentMapLayout });
                }
            }

        } catch (e) {
            console.error(`Error processing data: ${e.message}`);
        }
    });

    ws.on('close', () => {
        console.log('Client disconnected');
        // Remove player from players object
        if (players[ws.id]) {
            delete players[ws.id];
        }
        connections.splice(connections.indexOf(ws), 1);

        // Inform remaining clients about the disconnected player
        broadcast({ players });
    });
});

// Function to generate a unique ID (simple incrementing counter or UUID)
let currentID = 1;
function generateUniqueID() {
    return currentID++;
}

// Start server and change map layout periodically
console.log(`WebSocket server running on ws://localhost:${PORT}`);
changeMapLayoutPeriodically();



migrate from kaboom to kaplayjs
Migrating from v2000 to v3000
obj._id is renamed to obj.id
const obj = add([pos(300, 200), sprite("bean"), area()]);

// before
console.log(obj._id);

// v3000
console.log(obj.id);
origin() is renamed to anchor()
// before
add([sprite("bean"), origin("center")]);

// v3000
add([sprite("bean"), anchor("center")]);
obj.onHover() in area() comp is renamed to obj.onHoverUpdate(), obj.onHover() now only runs once when obj is hovered
const obj = add([pos(300, 200), sprite("bean"), area()]);

// before
obj.onHover(
    () => {
        console.log("this will print every frame when obj is hovered");
    },
    () => {
        console.log("this will print every frame when obj is not hovered");
    },
);

// v3000
obj.onHover(() => {
    console.log("this will run once when obj is hovered");
});

obj.onHoverUpdate(() => {
    console.log("this will run every frame when obj is hovered");
});

obj.onHoverEnd(() => {
    console.log("this will run once when obj stopped being hovered");
});
obj.pushOut() is renamed to obj.resolveCollision()
const player = add([sprite("bean"), pos(300, 200), area()]);

// before
player.pushOut(rock);

// v3000
player.resolveCollision(rocker);
solid() comp becomes an option in body({ isStatic: true })
// before
add([sprite("bean"), area(), body(), solid()]);

// v3000
add([sprite("bean"), area(), body({ isStatic: true })]);
gravity now needs to be manually enabled, gravity() is renamed to setGravity() and getGravity()
// before, gravity will be enabled by body() component
add([pos(100, 100), sprite("bean"), area(), body()]);

// v3000, use gravity() to manually enable gravity
setGravity(1600);

add([pos(100, 100), sprite("bean"), area(), body()]);
body.weight is renamed to body.gravityScale
// before
add([body({ weight: 2 })]);

// before
add([body({ gravityScale: 2 })]);
body.doubleJump() is removed in favor of new doubleJump() component
const obj = add([pos(100, 100), sprite("bean"), area(), body()]);

obj.doubleJump();

// after
const obj = add([pos(100, 100), sprite("bean"), area(), body(), doubleJump()]);

obj.doubleJump();
body.onFall() is renamed to body.onFallOff(), body.onFall() now runs when body is in the air and starts to fall
gravity(1600);

const obj = add([pos(100, 100), sprite("bean"), area(), body()]);

// before
obj.onFall(() => {
    console.log("this will print when object falls off a platform");
});

// v3000
obj.onFallOff(() => {
    console.log("this will print when object falls off a platform");
});

obj.onFall(() => {
    console.log("this will print when object is in the air and starts falling");
});
removed outview() in favor of offscreen(), which is less accurate but much faster
// before
add([sprite("flower"), outview({ hide: true })]);

// v3000
add([
    sprite("flower"),
    // will hide itself when its position is 64 pixels offscreen
    offscreen({ hide: true, distance: 64 }),
]);
removed cleanup() in favor of offscreen({ destroy: true })
// before
add([pos(player.pos), sprite("bullet"), cleanup()]);

// v3000
add([pos(player.pos), sprite("bullet"), offscreen({ destroy: true })]);
sprite.flipX and sprite.flipY becomes properties instead of functions
const bean = add([sprite("bean")]);

// before
bean.flipX(true);

// v3000
bean.flipX = true;
sprite.onAnimStart() and sprite.onAnimEnd() now triggers on any animation
const bean = add([sprite("bean")]);

// before
bean.onAnimStart("walk", () => {
    // do something
});

// before
bean.onAnimStart((anim) => {
    if (anim === "walk") {
        // do something
    }
});
obj.scale now is always a Vec2
scale(2); // scale is vec2(2, 2)
obj.scale; // vec2(2, 2)
loadFont() now only loads .ttf, .otf, .woff etc fonts that browser support, use loadBitmapFont() to load bitmap fonts
// before
loadFont("unscii", "/examples/fonts/unscii_8x8.png", 8, 8);

// v3000
loadBitmapFont("unscii", "/examples/fonts/unscii_8x8.png", 8, 8);
loadFont("apl386", "/examples/fonts/apl386.ttf");
removed builtin fonts apl386, apl386o, sink and sinko, using browser built-in monospace font as default font now
// v3000, manually load these fonts if you need them
loadFont("apl386", "/examples/fonts/apl386.ttf");
loadBitmapFont("sink", "/examples/fonts/sink_6x8.png");

// use outline option for "apl386o"
loadFont("apl386", "/examples/fonts/apl386.ttf", {
    outline: 3,
});
changed vertex format from vec3 to vec2 (only applied in shaders)
// before
loadShader(
    "test",
    null,
    `
vec4 frag(vec3 pos, vec2 uv, vec4 color, sampler2D tex) {
	return def_frag();
}
`,
);

// v3000
loadShader(
    "test",
    null,
    `
vec4 frag(vec2 pos, vec2 uv, vec4 color, sampler2D tex) {
	return def_frag();
}
`,
);
anchor (previously origin) no longer controls text alignment (only controls the anchor of the whole text area), use text({ align: "left" }) option for text alignment
// before
add([pos(center()), origin("center"), text("oh hi")]);

// v3000
add([pos(center()), anchor("center"), text("oh hi", { align: "center" })]);
changed text styling syntax to bbcode
const textOpts = {
    styles: {
        green: {
            color: rgb(128, 128, 255),
        },
        wavy: (idx, ch) => ({
            color: hsl2rgb((time() * 0.2 + idx * 0.1) % 1, 0.7, 0.8),
            pos: vec2(0, wave(-4, 4, time() * 6 + idx * 0.5)),
        }),
    },
};

// before
add([text("[oh hi].green here's some [styled].wavy text", textOpts)]);

// v3000
add([
    text("[green]oh hi[/green] here's some [wavy]styled[/wavy] text", textOpts),
]);
changed all event handlers to return an EventController object, instead of a function to cancel
// before
const cancel = onUpdate(() => {
    /* ... */
});
cancel();

// v3000
const ev = onUpdate(() => {
    /* ... */
});
ev.paused = true;
ev.cancel();
changed the interface for addLevel()
// before
addLevel(["@  ^ $$", "======="], {
    width: 32,
    height: 32,
    "=": () => [sprite("grass"), area(), body({ isStatic: true })],
    $: () => [sprite("coin"), area(), "coin"],
    any: (symbol) => {
        if (symbol === "@") {
            return [
                /* ... */
            ];
        }
    },
});

// v3000
addLevel(["@  ^ $$", "======="], {
    tileWidth: 32,
    tileHeight: 32,
    tiles: {
        "=": () => [sprite("grass"), area(), body({ isStatic: true })],
        $: () => [sprite("coin"), area(), "coin"],
    },
    wildcardTile: (symbol) => {
        if (symbol === "@") {
            return [
                /* ... */
            ];
        }
    },
});