write-ups-challenges-2020-2021/gotta_catch_em_all/backend/index.js
2022-11-24 18:03:20 +01:00

130 lines
5.3 KiB
JavaScript

const WebSocket = require('ws');
const pokemon = require('pokemon')
const pokeTypes = require('./poketypes.json')
const wss = new WebSocket.Server({
port: 5000
})
function flatten(obj) {
if(obj !== null && obj !== undefined) {
let ret = {};
for (let i in obj) {
ret[i] = obj[i];
}
return ret;
} else {
return obj
}
}
function badObjectCopy(values, object) {
if(values !== undefined && object !== undefined && values !== null && object !== null) {
for(let i in values) {
if(typeof values[i] === "object") {
badObjectCopy(values[i], object[i])
} else {
object[i] = values[i]
}
}
}
}
function flattenObject(obj) {
return Object.fromEntries(Object.entries(obj).map(([key, val]) => [key, flatten(val)]))
}
wss.on('connection', ws => {
try {
const timeStarted = new Date();
const Pokemon = {}
const types = {
grass: {type: 'grass' , __proto__: Pokemon},
fire: {type: 'fire' , __proto__: Pokemon},
water: {type: 'water' , __proto__: Pokemon},
poison: {type: 'poison' , __proto__: Pokemon},
flying: {type: 'flying' , __proto__: Pokemon},
bug: {type: 'bug' , __proto__: Pokemon},
normal: {type: 'normal' , __proto__: Pokemon},
electric: {type: 'electric', __proto__: Pokemon},
ground: {type: 'ground' , __proto__: Pokemon},
fairy: {type: 'fairy' , __proto__: Pokemon},
fighting: {type: 'fighting', __proto__: Pokemon},
psychic: {type: 'psychic' , __proto__: Pokemon},
rock: {type: 'rock' , __proto__: Pokemon},
steel: {type: 'steel' , __proto__: Pokemon},
ice: {type: 'ice' , __proto__: Pokemon},
ghost: {type: 'ghost' , __proto__: Pokemon},
dragon: {type: 'dragon' , __proto__: Pokemon}
}
const pokedex = Object.fromEntries(pokemon.all().slice(0, 151).map(poke => {
const type = types[pokeTypes[poke]]
return [poke, {__proto__: type}]
}))
const send = async(msg, data) => await ws.send(JSON.stringify({msg, data}))
const checkIfAllCaptured = async () => {
if(Object.values(pokedex).every(poke => poke["captured"])) {
const diff = new Date() - timeStarted;
if(diff < 60000) {
send("pokedexCompleted", {
msg: "I can't believe you beat my near impossible 60 second speedrun! It's impossible! I am the best trainer to have ever lived! Urgh... I guess I don't have a choice but to give you this item...\n\n*You obtained the key IGCTF{MissingNo}!*\n\nHave fun with it... I guess.\n\n-Gary"
})
} else {
send("pokedexCompleted", {
msg: "Hah, you did good, but not nearly as good as I did. You still got a long way to beat my 1 minute run, loser.\n\n-Gary"
})
}
ws.close()
}
}
let messagesReceived = 0;
let timeOfLastMessage = 0;
let poisonMessageSent = false;
ws.on('message', evt => {
messagesReceived++;
const currentTime = new Date()
console.log(currentTime - timeOfLastMessage)
if(currentTime - timeOfLastMessage < 500) {
send("error", {msg: "You are sending messages too fast to the backend. Please slow down."})
timeOfLastMessage = currentTime
return
} else {
timeOfLastMessage = currentTime
}
const {msg, data} = JSON.parse(evt)
switch (msg) {
case "initializePokedex": send('pokedexInitialized', flattenObject(pokedex)); break;
case "updatePokedex": {
const {pokemon, values} = data;
if(pokemon !== '__proto__') {
if(pokeTypes[pokemon] === 'poison') {
badObjectCopy(values, pokedex[pokemon].__proto__);
if(!poisonMessageSent) {
send("error", {msg: "Dear Pokémon trainer,\n\n There seems to be a bug when adding a Poison type Pokémon: it automatically registers all Poison type Pokémon in the Pokédex as captured! Somehow the poison seems to affect the shared prototype...\n\nYours truly,\nProfessor Oak"})
poisonMessageSent = true
}
} else {
badObjectCopy(values, pokedex[pokemon]);
}
} else {
send("error", {msg: "I read somewhere that letting the client change the __proto__ property isn't such a good idea, so I'll just add this check here. I sure hope that I didn't forget to check this anywhere else... \n\n -Professor Oak"})
}
send('pokedexUpdated', flattenObject(pokedex))
checkIfAllCaptured()
break;
}
}
});
} catch(e) {
console.error(e)
}
})