Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created June 29, 2026 16:14
Show Gist options
  • Select an option

  • Save EncodeTheCode/401fa6d39e2d043af46d4ebe241c3dec to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/401fa6d39e2d043af46d4ebe241c3dec to your computer and use it in GitHub Desktop.
class NetClient {
constructor(url) {
this.url = url;
this.ws = null;
this.connected = false;
this.authenticated = false;
this.seq = 0;
// keep-alive system
this.lastServerPing = Date.now();
this.heartbeatInterval = null;
this.reconnectAttempts = 0;
}
// =========================
// CONNECT WITH AUTO-RETRY
// =========================
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.connected = true;
this.reconnectAttempts = 0;
console.log("Connected");
this.startHeartbeat();
};
this.ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
this.onMessage(msg);
// server activity update (keep-alive tracking)
this.lastServerPing = Date.now();
};
this.ws.onclose = () => {
this.connected = false;
this.authenticated = false;
console.log("Disconnected — reconnecting...");
this.stopHeartbeat();
this.reconnect();
};
this.ws.onerror = () => {
this.ws.close();
};
}
// =========================
// AUTO RECONNECT (EXPONENTIAL BACKOFF)
// =========================
reconnect() {
const delay = Math.min(1000 * (2 ** this.reconnectAttempts), 10000);
setTimeout(() => {
this.reconnectAttempts++;
console.log("Reconnecting attempt:", this.reconnectAttempts);
this.connect();
}, delay);
}
// =========================
// SEND PACKET
// =========================
send(data) {
if (!this.connected) return;
this.ws.send(JSON.stringify(data));
}
// =========================
// HEARTBEAT SYSTEM (KEEP ALIVE)
// =========================
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
// send ping heartbeat
this.send({
type: "ping",
t: Date.now()
});
// detect server silence
if (Date.now() - this.lastServerPing > 5000) {
console.log("Server timeout detected, reconnecting...");
this.ws.close();
}
}, 1000);
}
stopHeartbeat() {
clearInterval(this.heartbeatInterval);
}
// =========================
// MESSAGE HANDLER (OVERWRITEABLE)
// =========================
onMessage(msg) {
if (msg.type === "auth_ok") {
this.authenticated = true;
console.log("Authenticated:", msg.user_id);
}
}
}
// =======================================================
// 🧍 PLAYER STATE
// =======================================================
class Player {
constructor() {
this.user_id = null;
this.x = this.y = this.z = 0;
this.px = this.py = this.pz = 0;
this.health = 100;
this.armor = 0;
this.weapon_id = 0;
this.primary_ammo = 30;
this.secondary_ammo = 90;
this.walking = false;
this.running = false;
}
applyServerState(s) {
this.px = this.x;
this.py = this.y;
this.pz = this.z;
this.x = s.x;
this.y = s.y;
this.z = s.z;
this.health = s.health;
this.armor = s.armor;
this.weapon_id = s.weapon_id;
this.primary_ammo = s.primary_ammo;
this.secondary_ammo = s.secondary_ammo;
this.walking = s.walking;
this.running = s.running;
this.user_id = s.user_id;
}
}
// =======================================================
// 🎮 GAME CLIENT (FULL SYSTEM)
// =======================================================
class GameClient {
constructor(serverUrl) {
this.net = new NetClient(serverUrl);
this.player = new Player();
this.world = {};
this.input = {};
}
start() {
this.net.connect();
this.setupNetworking();
this.startLoops();
this.gameLoop();
}
// =========================
// NETWORK HANDLER
// =========================
setupNetworking() {
this.net.onMessage = (msg) => {
if (msg.type === "world") {
this.world = msg.players;
for (const id in msg.players) {
const p = msg.players[id];
if (p.user_id === this.player.user_id) {
this.player.applyServerState(p);
}
}
}
if (msg.type === "auth_ok") {
this.net.authenticated = true;
this.player.user_id = msg.user_id;
}
};
}
// =========================
// SEND MOVEMENT
// =========================
sendMovement(dx, dy, dz) {
this.net.send({
type: "update",
seq: this.net.seq++,
dx, dy, dz,
walking: this.player.walking,
running: this.player.running
});
}
// =========================
// PING (EXTRA SAFETY LAYER)
// =========================
sendPing() {
this.net.send({
type: "ping",
t: Date.now()
});
}
// =========================
// GAME LOOP
// =========================
gameLoop() {
const loop = () => {
if (this.net.connected && this.net.authenticated) {
// example movement tick (replace with real input system)
this.sendMovement(0, 0, 0);
}
requestAnimationFrame(loop);
};
loop();
}
// =========================
// INTERVAL SYSTEMS
// =========================
startLoops() {
// keep-alive ping
setInterval(() => {
this.sendPing();
}, 1000);
}
}
// =======================================================
// 🚀 BOOT CLIENT
// =======================================================
const game = new GameClient("ws://localhost:4445");
game.start();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment