initial commit

This commit is contained in:
CodedSakura 2024-10-18 20:55:55 +03:00
commit 2597a364bd
15 changed files with 1376 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
out.txt
node_modules/
.yarn/

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DiscordProjectSettings">
<option name="show" value="ASK" />
<option name="description" value="" />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/avery-game-proto.iml" filepath="$PROJECT_DIR$/.idea/avery-game-proto.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

2
.yarnrc.yml Normal file
View File

@ -0,0 +1,2 @@
yarnPath: .yarn/releases/yarn-4.4.1.cjs
nodeLinker: node-modules

12
Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:22-slim
LABEL authors="codedsakura"
WORKDIR /app
COPY . .
RUN yarn install
ENV PORT=80
ENTRYPOINT ["yarn", "run", "start"]
EXPOSE 80

7
docker-compose.yaml Normal file
View File

@ -0,0 +1,7 @@
services:
app:
build: .
container_name: app
ports:
- "80:80"
restart: unless-stopped

148
index.ts Normal file
View File

@ -0,0 +1,148 @@
import express from "express";
import expressWs from "express-ws";
import type { WebSocket } from "ws";
import crypto from "crypto";
import fs from "fs/promises";
interface Host {
ws: WebSocket,
code: string,
messages: { from: "host"|"join", data: string }[],
join?: Join,
}
interface Join {
ws: WebSocket,
code: string,
host: Host,
}
const hosts: Host[] = [];
const joins: Join[] = [];
const app = expressWs(express()).app;
const port = process.env.PORT || 3000;
app.use(express.json());
app.use(express.static("public"));
app.ws("/", (ws) => {
ws.on("message", (msg: string) => {
console.log(msg);
if (msg === "host") {
if (hosts.some(({ ws: ows }) => ows == ws) || joins.some(({ ws: ows }) => ows == ws)) {
ws.send("failed");
return;
}
const code = crypto.randomBytes(4).toString("hex");
ws.send("code " + code);
hosts.push({ ws, code, messages: [] });
return;
}
if (msg.startsWith("join ")) {
if (hosts.some(({ ws: ows }) => ows == ws) || joins.some(({ ws: ows }) => ows == ws)) {
ws.send("failed");
return;
}
const code = msg.substring(5);
const host = hosts.find(({ code: oldCode }) => oldCode === code);
if (!host || host.join) {
ws.send("failed");
return;
}
ws.send("good");
const join: Join = { ws, code, host };
host.join = join;
joins.push(join);
}
if (msg.startsWith("send ")) {
const send = msg.substring(5);
const host = hosts.find(({ ws: ows }) => ows == ws);
const join = joins.find(({ ws: ows }) => ows == ws);
if (host) {
if (!host.join) {
ws.send("failed");
return;
}
host.join.ws.send("rcv " + translateText(send, host.code));
host.messages.push({ from: "host", data: send });
return;
}
if (join) {
join.host.ws.send("rcv " + translateText(send, join.code));
join.host.messages.push({ from: "join", data: send });
}
}
});
ws.on("close", () => {
let index = hosts.findIndex(({ ws: thisWs }) => thisWs == ws);
if (index > -1) {
const host = hosts[index];
fs.appendFile(
"./out.txt",
`${host.code} | ${new Date().toUTCString()}\n` +
host.messages.map(({ from, data }) => `${from}> ${data}`).join("\n") + "\n\n",
).catch(console.error);
const join = host.join;
hosts.splice(index, 1);
if (join) {
join.ws.send("disconnected");
index = joins.indexOf(join);
if (index > -1) {
joins.splice(index, 1);
}
}
}
index = joins.findIndex(({ ws: thisWs }) => thisWs == ws);
if (index > -1) {
const host = joins[index].host;
joins.splice(index, 1);
host.ws.send("disconnected");
}
});
});
function translateText(msg: string, code: string): string {
const shift = parseInt(code, 16);
return msg
.replaceAll(/[^\x20-\x7e]+/g, "")
.replaceAll(/\s+/g, " ")
.toLowerCase()
.substring(0, 1024)
.split(" ")
.map(w => {
let o = "";
let n = shift;
for (let i = 0; i < w.length; i++) {
const c = w[i];
const cc = c.charCodeAt(0);
n ^= (cc * shift) >> (i % 4);
n = Math.abs(n);
if ("a" <= c && c <= "z") {
o += String.fromCharCode(0x61 + (n % 26));
} else {
const nc = n % (32 + 6 + 4);
if (nc <= 32) {
o += String.fromCharCode(0x21 + nc);
} else if (nc <= 32 + 6) {
o += String.fromCharCode(0x5B + nc - 32);
} else {
o += String.fromCharCode(0x7B + nc - 32 - 6);
}
}
}
return o;
})
.join(" ");
}
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "avery-game-proto",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "ts-node index.ts"
},
"dependencies": {
"@types/express": "^4.17.21",
"@types/express-ws": "^3.0.5",
"express": "^5.0.0-beta.3",
"express-ws": "^5.0.2",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
},
"packageManager": "yarn@4.4.1"
}

112
public/index.html Normal file
View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Avery game prototype</title>
<style>
.msg.dir-in::before {
content: "< ";
}
.msg.dir-out::before {
content: "> ";
}
</style>
</head>
<body>
<div id="setup">
<div>
<label for="joinCode">Join game:</label>
<input id="joinCode" placeholder="Join Code" />
<button id="join">Join</button>
</div>
<div>
<button id="host">Host new game</button>
</div>
</div>
<div id="joinCodeNew"></div>
<form id="msg" style="display: none">
<input placeholder="message" id="msgBox" />
<button type="submit">send</button>
<hr />
<div id="msgs"></div>
</form>
<footer>
<hr />
<p>this is a basic prototype, some stuff might be janky</p>
<div>
rules:
<ul>
<li>all whitespace is merged into a single space</li>
<li>all letters in each word get lower-cased, and (consistently) jumbled around based on the previous character</li>
<li>all numbers and symbols get jumbled around, also based on their position</li>
<li>other symbols (outside printable ASCII) are voided</li>
<li>max message length: 1024 characters (after whitespace and other symbol striping)</li>
</ul>
</div>
<p>
idea by Avery [Kelly], prototype by CodedSakura
</p>
</footer>
<script>
const setup = document.getElementById("setup");
const form = document.getElementById("msg");
document.getElementById("join").addEventListener("click", () => {
const joinCode = document.getElementById("joinCode");
ws.send("join " + joinCode.value.toLowerCase());
});
document.getElementById("host").addEventListener("click", () => {
ws.send("host");
});
form.addEventListener("submit", e => {
e.preventDefault();
const data = document.getElementById("msgBox").value
.replaceAll(/[^\x20-\x7e]+/g, "")
.replaceAll(/\s+/g, " ")
.toLowerCase()
.substring(0, 1024);
ws.send("send " + data);
newMessage(data, "out");
document.getElementById("msgBox").value = "";
});
const ws = new WebSocket("ws://" + window.location.host);
ws.addEventListener("message", ({ data: msg }) => {
console.log(msg);
if (msg.startsWith("code ")) {
document.getElementById("joinCodeNew").innerText = "code: " + msg.substring(5);
setup.style.display = "none";
form.style.display = "";
const msgs = document.getElementById("msgs");
while (msgs.lastChild) msgs.removeChild(msgs.lastChild);
}
if (msg === "good") {
setup.style.display = "none";
form.style.display = "";
const msgs = document.getElementById("msgs");
while (msgs.lastChild) msgs.removeChild(msgs.lastChild);
}
if (msg.startsWith("rcv ")) {
const data = msg.substring(4);
newMessage(data, "in");
}
if (msg === "disconnected") {
setup.style.display = "";
form.style.display = "none";
document.getElementById("joinCodeNew").innerText = "";
}
});
function newMessage(data, direction) {
const elem = document.createElement("div");
elem.classList.add("msg", `dir-${direction}`);
elem.appendChild(document.createTextNode(data));
elem.title = new Date().toLocaleTimeString();
document.getElementById("msgs").prepend(elem);
}
</script>
</body>
</html>

10
tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "CommonJS",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}

1017
yarn.lock Normal file

File diff suppressed because it is too large Load Diff