diff --git a/blob/index.html b/blob/index.html index a420abb..1948455 100644 --- a/blob/index.html +++ b/blob/index.html @@ -1,34 +1,33 @@ - + + + Blob + + + - - Blob - - - - - -
- + +
+ + diff --git a/blob/script.js b/blob/script.js index ff48072..7f430b9 100644 --- a/blob/script.js +++ b/blob/script.js @@ -1,3 +1,75 @@ +/** + * @typedef {Object} SetTarget + * @prop {number} x + * @prop {number} y + */ + +/** + * @typedef {Object} ChangeAppearance + * @prop {number} body_hue + * @prop {number} iris_hue + */ + +/** + * @typedef {Object} SyncRot + * @prop {number} rot + */ + +/** + * @typedef {Object} SetTargetReq + * @prop {SetTarget} SetTarget + */ + +/** + * @typedef {Object} ChangeAppearanceReq + * @prop {ChangeAppearance} ChangeAppearance + */ + +/** + * @typedef {Object} SyncRotReq + * @prop {SyncRot} SyncRot + */ + +/** + * @typedef {SetTargetReq | ChangeAppearanceReq} ClientRequest + */ + +/** + * @typedef {Object} ClientLeftRes + * @prop {Object} ClientLeft + */ + +/** + * @typedef {Object} ClientJoinedRes + * @prop {ClientJoined} ClientJoined + */ + +/** + * @typedef {Object} ClientJoined + * @prop {ClientState} state + * @prop {boolean} is_local + */ + +/** + * @typedef {Object} ClientResponse + * @prop {number} id + * @prop {ClientRequest | ClientLeftRes | ClientJoinedRes} msg + */ + +/** + * @typedef {Object} ClientState + * @prop {number} x + * @prop {number} y + * @prop {number} tx + * @prop {number} ty + * @prop {number} rot + * @prop {number} body_hue + * @prop {number} iris_hue + */ + +/** + * @type {Object.} + */ const states = {}; let scale = 1.0; @@ -12,24 +84,44 @@ visualViewport.onresize = () => { const sx = document.body.clientWidth / 1280; const sy = document.body.clientHeight / 720; setScale(Math.min(sx, sy)); -} +}; visualViewport.onresize(); +/** + * @param {string} id + * @param {ClientState} state + */ function spawnCharacter(id, state) { const tmpl = document.getElementById("char-tmpl"); const elemFrag = tmpl.content.cloneNode(true); const charElem = elemFrag.querySelector(".character"); charElem.id = `char-${id}`; - charElem.style.setProperty("--hue", state.hue); - charElem.style.setProperty("--iris-hue", state.irisHue); viewportElem.appendChild(elemFrag); blinkLoop(id); states[id] = structuredClone(state); + changeAppearance(id, state); + charElem.style.setProperty("--rotation", `${state.rot}deg`); +} + +/** + * @param {string} id + * @param {ChangeAppearance} state + */ +function changeAppearance(id, state) { + const charElem = getCharElem(id); + charElem.style.setProperty("--hue", state.body_hue); + charElem.style.setProperty("--iris-hue", state.iris_hue); +} + +function despawnCharacter(id) { + delete states[id]; + document.getElementById(`char-${id}`).remove(); } function getCharElem(id) { const elem = document.getElementById(`char-${id}`); - if (!elem) throw new Error("Could not find element for character with ID " + id); + if (!elem) + throw new Error("Could not find element for character with ID " + id); return elem; } @@ -47,6 +139,7 @@ function blink(id) { } function blinkLoop(id) { + if (!(id in states)) return; blink(id); setTimeout(() => blinkLoop(id), Math.random() * 10000); } @@ -54,56 +147,33 @@ function blinkLoop(id) { function setPosition(id, x, y) { const elem = getCharElem(id); elem.style.setProperty("--pos-x", `${x}px`); - elem.style.setProperty("--pos-y", `${y - 30}px`); + elem.style.setProperty("--pos-y", `${y - 50}px`); elem.style.zIndex = Math.round(y); } -function createState(pos, rot, hue, irisHue) { - return { - x: pos[0], - y: pos[1], - tx: pos[0], - ty: pos[1], - rot, - hue, - irisHue - }; -} - - -const localId = 0; -// spawnCharacter(localId, createState([150, 150], 0, 120, Math.random() * 360)); - -for (let i = 0; i < 10; i++) { - spawnCharacter(i, createState([Math.random() * 1280, Math.random() * 720], Math.random() * 360, Math.random() * 360, Math.random() * 360)); -} - let prevEt = 0; const speed = 0.1; +let localId; function updateOuter() { requestAnimationFrame((et) => { const dt = et - prevEt; - for (const idStr in states) { - const id = Number(idStr); - // console.log(id); - const state = states[idStr]; + for (const id in states) { + const state = states[id]; let dx = state.tx - state.x; let dy = state.ty - state.y; let dl = Math.sqrt(dx * dx + dy * dy); if (dl <= speed * dt) { + if (localId === id && (state.x !== state.tx || state.y !== state.ty)) { + sendMessage({ SyncRot: { rot: state.rot } }); + } state.x = state.tx; state.y = state.ty; - - if (id !== localId && Math.random() > 0.99) { - state.tx = Math.random() * 1280; - state.ty = Math.random() * 720; - } } else { - state.x += dx / dl * dt * speed; - state.y += dy / dl * dt * speed; + state.x += (dx / dl) * dt * speed; + state.y += (dy / dl) * dt * speed; state.rot += dt * 0.3 * (dx / dl); getCharElem(id).style.setProperty("--rotation", `${state.rot}deg`); } @@ -117,8 +187,61 @@ function updateOuter() { updateOuter(); -document.onclick = (ev) => { - console.log(ev); - states[localId].tx = ev.x / scale; - states[localId].ty = ev.y / scale; +/** + * @param {ClientRequest} msg + */ +function sendMessage(msg) { + ws.send(JSON.stringify(msg)); } + +viewportElem.onclick = (ev) => { + sendMessage({ + SetTarget: { + x: Math.round(ev.x / scale), + y: Math.round(ev.y / scale), + }, + }); +}; + +const ws = new WebSocket("ws://abc.matthewmeeks.xyz:8088/ws"); +ws.onmessage = (ev) => { + /** + * @type {ClientResponse} + */ + const response = JSON.parse(ev.data); + + if ("ClientJoined" in response.msg) { + spawnCharacter(response.id, { + ...response.msg.ClientJoined.state, + tx: response.msg.ClientJoined.state.x, + ty: response.msg.ClientJoined.state.y, + }); + if (response.msg.ClientJoined.is_local) { + localId = response.id; + } + } else if ("SetTarget" in response.msg) { + const state = states[response.id]; + if (!state) return; // TODO + state.tx = response.msg.SetTarget.x; + state.ty = response.msg.SetTarget.y; + } else if ("ClientLeft" in response.msg) { + despawnCharacter(response.id); + } else if ("ChangeAppearance" in response.msg) { + changeAppearance(response.id, response.msg.ChangeAppearance); + } else if ("SyncRot" in response.msg) { + const state = states[response.id]; + if (!state) return; // TODO + state.rot = response.msg.SyncRot.rot; + getCharElem(response.id).style.setProperty("--rotation", `${state.rot}deg`); + } +}; + +document.getElementById("btn-appearance").onclick = (ev) => { + sendMessage({ + ChangeAppearance: { + body_hue: Math.trunc(Math.random() * 360), + iris_hue: Math.trunc(Math.random() * 360), + }, + }); + ev.stopPropagation(); +};