Compare commits
29 Commits
socketio
...
402dbc3923
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
402dbc3923 | ||
|
|
54968f3fb4 | ||
|
|
1228928592 | ||
|
|
6a35049609 | ||
|
|
a29eba70f0 | ||
|
|
064b4eb0d4 | ||
|
|
5e396a4abe | ||
|
|
453a3b1b42 | ||
|
|
a1a9d698e2 | ||
|
|
48cf433a5c | ||
|
|
61e935b925 | ||
|
|
9a7ef61d93 | ||
|
|
bad5dc52f7 | ||
|
|
d510caa123 | ||
|
|
b6cc3d716b | ||
|
|
6a36df3593 | ||
|
|
69bc1c9e6e | ||
|
|
aeb405967b | ||
|
|
73e60b81f1 | ||
|
|
c9b985f768 | ||
|
|
cb5de62e1c | ||
|
|
b3cab90810 | ||
|
|
47484ba7e2 | ||
|
|
4017f1cd07 | ||
|
|
03dfe8e437 | ||
|
|
1a992a6026 | ||
|
|
9f8f503f13 | ||
|
|
d460ba13c5 | ||
|
|
ed5337d828 |
@@ -27,6 +27,7 @@ module.exports = {
|
||||
'chat',
|
||||
'mash',
|
||||
'trivia',
|
||||
'wordle',
|
||||
'letters',
|
||||
'numbers',
|
||||
'hunt',
|
||||
@@ -133,6 +134,11 @@ module.exports = {
|
||||
z: 1,
|
||||
},
|
||||
},
|
||||
wordle: {
|
||||
minLength: 3,
|
||||
defaultLength: 5,
|
||||
highlightRepeat: false, // in wordle, if the I in the guess HINDI is in the wrong place, only the first I is orange
|
||||
},
|
||||
numbers: {
|
||||
length: 6,
|
||||
timeout: 90,
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "schat2-clive",
|
||||
"version": "1.26.12",
|
||||
"version": "1.29.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "schat2-clive",
|
||||
"version": "1.26.12",
|
||||
"version": "1.29.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^8.3.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "schat2-clive",
|
||||
"version": "1.26.12",
|
||||
"version": "1.29.0",
|
||||
"description": "Game host for SChat 2-powered chat sites",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -91,13 +91,20 @@ async function play(context) {
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(settings.timeout))} seconds, let's start!`, context.room.id);
|
||||
|
||||
try {
|
||||
await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal });
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(`${Math.round((settings.timeout / 3) * 2)} seconds`))} left`, context.room.id);
|
||||
await timers.setTimeout((settings.timeout / 5) * 1000, null, { signal: game.ac.signal });
|
||||
|
||||
await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal });
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(`${Math.round(settings.timeout / 3)} seconds`))} left`, context.room.id);
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(`${Math.round((settings.timeout / 5) * 4)} seconds`))} left`, context.room.id);
|
||||
await timers.setTimeout((settings.timeout / 5) * 1000, null, { signal: game.ac.signal });
|
||||
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(`${Math.round((settings.timeout / 5) * 3)} seconds`))} left`, context.room.id);
|
||||
await timers.setTimeout((settings.timeout / 5) * 1000, null, { signal: game.ac.signal });
|
||||
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(`${Math.round((settings.timeout / 5) * 2)} seconds`))} left`, context.room.id);
|
||||
await timers.setTimeout((settings.timeout / 5) * 1000, null, { signal: game.ac.signal });
|
||||
|
||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(`${Math.round(settings.timeout / 5)} seconds`))} left`, context.room.id);
|
||||
await timers.setTimeout((settings.timeout / 5) * 1000, null, { signal: game.ac.signal });
|
||||
|
||||
await timers.setTimeout((settings.timeout / 3) * 1000, null, { signal: game.ac.signal });
|
||||
stop(context);
|
||||
} catch (error) {
|
||||
// abort expected, probably not an error
|
||||
|
||||
@@ -336,7 +336,7 @@ function start(context, numbers) {
|
||||
context.sendMessage('Let\'s play the numbers! Would you like a large number or a small one?', context.room.id);
|
||||
}
|
||||
|
||||
function solve(calculation, context) {
|
||||
function calc(calculation, context) {
|
||||
try {
|
||||
const parsed = math.parse(calculation.replace(/`/g, '')); // backticks may be used to prevent the expression from being expanded by Markdown in SChat
|
||||
const answer = parsed.evaluate();
|
||||
@@ -347,6 +347,51 @@ function solve(calculation, context) {
|
||||
}
|
||||
}
|
||||
|
||||
async function solve(equation, context) {
|
||||
const [numberString, targetString] = equation.split('=');
|
||||
const numbers = numberString?.split(' ').map((string) => Number(string)).filter(Boolean);
|
||||
const target = Number(targetString);
|
||||
|
||||
if (!numberString || !target) {
|
||||
context.sendMessage('The input should be in the form 1 2 3 4 5 6 7 = 10', context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (numbers.length < 2 || numbers.length > 7) {
|
||||
context.sendMessage('The selection should contain at least 2 and at most 7 numbers', context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (target < 100 || target > 999) {
|
||||
context.sendMessage('The target must be a number between 100 and 999', context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.from(games.entries()).some(([roomId, game]) => roomId === context.room.id || game.target === target)) {
|
||||
context.sendMessage('Nice try! Please wait for this numbers round to end :)', context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const solutions = await solveAll(numbers, target, 3);
|
||||
const bestSolution = solutions.reduce((closest, solution) => (!closest || Math.abs(target - solution.answer) < Math.abs(target - closest.answer) ? solution : closest), null);
|
||||
|
||||
if (!bestSolution) {
|
||||
context.sendMessage(`I could not find a solution for ${numbers.join(' ')} = ${target} :(`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bestSolution.answer === target) {
|
||||
context.sendMessage(`My best solution for ${numbers.join(' ')} = ${target} is: ${style.bold(bestSolution.solution)}`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
context.sendMessage(`I could not find an exact solution for ${numbers.join(' ')} = ${target}. My closest solution is: ${style.bold(bestSolution.solution)} = ${style.italic(bestSolution.answer)}`, context.room.id);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function onCommand(args, context) {
|
||||
if (context.subcommand === 'stop') {
|
||||
stop(context, true);
|
||||
@@ -358,7 +403,12 @@ function onCommand(args, context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (['calculate', 'calc', 'solve'].includes(context.subcommand || context.command)) {
|
||||
if (['calculate', 'calc'].includes(context.subcommand || context.command)) {
|
||||
calc(args.join(' '), context); // two from the top, four from the bottom, please Rachel
|
||||
return;
|
||||
}
|
||||
|
||||
if (['solve'].includes(context.subcommand || context.command)) {
|
||||
solve(args.join(' '), context); // two from the top, four from the bottom, please Rachel
|
||||
return;
|
||||
}
|
||||
|
||||
180
src/games/wordle.js
Normal file
180
src/games/wordle.js
Normal file
@@ -0,0 +1,180 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const style = require('../utils/style');
|
||||
const words = require('../../assets/mash-words.json');
|
||||
|
||||
const settings = { ...config.wordle };
|
||||
const wordles = new Map();
|
||||
|
||||
const alphabet = Array.from({ length: 26 }, (value, index) => String.fromCharCode(65 + index));
|
||||
|
||||
function getBoard(letters, showLetters, context) {
|
||||
const wordle = wordles.get(context.room.id);
|
||||
const prefix = style.grey(style.code('['));
|
||||
|
||||
const middle = letters.map((letter) => {
|
||||
if (letter === null) {
|
||||
return style.code(' '); // em space, U+2003, charcode 8195
|
||||
}
|
||||
|
||||
if (letter[1] === true) {
|
||||
return style.green(style.bold(style.code(letter[0].toUpperCase())));
|
||||
}
|
||||
|
||||
if (letter[1] === false) {
|
||||
return style.orange(style.bold(style.code(letter[0].toUpperCase())));
|
||||
}
|
||||
|
||||
return style.grey(style.bold(style.code(letter[0].toUpperCase())));
|
||||
}).join(style.grey(style.code('|')));
|
||||
|
||||
const suffix = `${style.grey(style.code(']'))}`;
|
||||
|
||||
if (showLetters) {
|
||||
const letterBoard = Array.from(wordle.letters).map(([letter, state]) => {
|
||||
if (state === true) {
|
||||
return style.green(style.bold(letter));
|
||||
}
|
||||
|
||||
if (state === false) {
|
||||
return style.orange(style.bold(letter));
|
||||
}
|
||||
|
||||
return style.grey(letter);
|
||||
}).join(' ');
|
||||
|
||||
return `${prefix}${middle}${suffix} ${letterBoard}`; // eslint-disable-line no-irregular-whitespace
|
||||
}
|
||||
|
||||
return `${prefix}${middle}${suffix}`;
|
||||
}
|
||||
|
||||
function start(length = settings.defaultLength, context) {
|
||||
const wordPool = words[length];
|
||||
|
||||
if (length < settings.minLength) {
|
||||
context.sendMessage(`Wordle must be at least ${settings.minLength} letters long.`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wordPool) {
|
||||
context.sendMessage(`No words with ${length} letters available.`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const wordList = Object.values(wordPool).flat();
|
||||
const word = wordList[crypto.randomInt(wordList.length)];
|
||||
|
||||
wordles.set(context.room.id, {
|
||||
word: word.word.toUpperCase(),
|
||||
wordList,
|
||||
definitions: word.definitions,
|
||||
letters: new Map(alphabet.map((letter) => [letter, null])),
|
||||
guesses: [],
|
||||
});
|
||||
|
||||
context.sendMessage(`${getBoard(Array.from({ length }, () => null), false, context)} guess the word with ${config.prefix}w [word]! ${style.grey(style.bold('X'))} not in word; ${style.orange(style.bold('X'))} wrong position; ${style.green(style.bold('X'))} correct position.`, context.room.id);
|
||||
|
||||
context.logger.info(`Wordle started: ${word.word.toUpperCase()}`);
|
||||
}
|
||||
|
||||
function play(guess, context) {
|
||||
const wordle = wordles.get(context.room.id);
|
||||
|
||||
if (!wordle) {
|
||||
context.sendMessage(`There's no wordle going on. Start one with ${config.prefix}wordle [length]!`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!guess || guess.length !== wordle.word.length) {
|
||||
context.sendMessage(`Your guess needs to be ${wordle.word.length} letters.`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wordle.wordList.some((definition) => definition.word.toLowerCase() === guess.toLowerCase())) {
|
||||
context.sendMessage(`The word '${guess}' is not in my dictionary.`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
const upperGuess = guess.toUpperCase();
|
||||
const processed = new Set();
|
||||
|
||||
const check = upperGuess.split('').map((letter, index) => {
|
||||
const alreadySeen = settings.highlightRepeat ? false : processed.has(letter);
|
||||
|
||||
processed.add(letter);
|
||||
|
||||
if (wordle.word[index] === letter) {
|
||||
wordle.letters.set(letter, true);
|
||||
return [letter, true];
|
||||
}
|
||||
|
||||
if (wordle.word.includes(letter)) {
|
||||
if (wordle.letters.get(letter) !== true) {
|
||||
wordle.letters.set(letter, false);
|
||||
}
|
||||
|
||||
return [letter, alreadySeen ? null : false]; // repeating letter in the wrong place is not highlighted
|
||||
}
|
||||
|
||||
wordle.letters.delete(letter);
|
||||
|
||||
return [letter, null];
|
||||
});
|
||||
|
||||
wordle.guesses = wordle.guesses.concat([[context.user.username, upperGuess]]);
|
||||
|
||||
if (upperGuess === wordle.word) {
|
||||
const points = Math.max((wordle.word.length + 1) - wordle.guesses.length, 1);
|
||||
const definition = wordle.definitions[0] ? `: ${style.italic(`${wordle.definitions[0].slice(0, 100)}${wordle.definitions[0].length > 100 ? '...' : ''}`)}` : '';
|
||||
|
||||
context.setPoints(context.user, points);
|
||||
context.sendMessage(`${getBoard(check, false, context)} is correct in ${wordle.guesses.length} guesses! ${style.bold(style.cyan(`${config.usernamePrefix}${context.user.username}`))} gets ${points} ${points > 1 ? 'points' : 'point'}. ${style.bold(wordle.word)}${definition}`, context.room.id);
|
||||
|
||||
wordles.delete(context.room.id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
context.sendMessage(`${getBoard(check, true, context)}`, context.room.id);
|
||||
}
|
||||
|
||||
function onCommand(args, context) {
|
||||
const wordle = wordles.get(context.room.id);
|
||||
const length = Number(args[0]) || settings.defaultLength;
|
||||
|
||||
if (['guess', 'w'].includes(context.command)) {
|
||||
play(args[0], context);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.subcommand === 'stop') {
|
||||
if (wordle) {
|
||||
context.sendMessage(`The game was stopped by ${config.usernamePrefix}${context.user.username}. The word was ${style.bold(wordle.word)}.`, context.room.id);
|
||||
wordles.delete(context.room.id);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
context.sendMessage(`The there is no wordle going on, ${config.usernamePrefix}${context.user.username}, start one with ${config.prefix}wordle [length]!`, context.room.id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wordle && context.room.id) {
|
||||
start(length, context);
|
||||
return;
|
||||
}
|
||||
|
||||
context.sendMessage(`There is already a ${wordle.word.length}-letter wordle going on, guess with ${config.prefix}w [word]!`, context.room.id);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
name: 'Wordle',
|
||||
commands: ['wordle', 'guess', 'w'],
|
||||
help: `Guess the ${settings.defaultLength}-letter word on the board. Submit a guess with ${config.prefix}w [word]. If the letter is green, it is in the correct place. If it is yellow, it is part of the word, but not in the correct place. The number of letters in the word is the highest score you can get. You lose 1 point per guess, down to 1 point.`,
|
||||
onCommand,
|
||||
// onMessage,
|
||||
};
|
||||
73
src/schat.js
73
src/schat.js
@@ -3,8 +3,7 @@
|
||||
const config = require('config');
|
||||
const { setTimeout: delay } = require('timers/promises');
|
||||
const bhttp = require('bhttp');
|
||||
// const WebSocket = require('ws');
|
||||
const io = require('socket.io-client');
|
||||
const WebSocket = require('ws');
|
||||
const logger = require('simple-node-logger').createSimpleLogger();
|
||||
const { argv } = require('yargs');
|
||||
|
||||
@@ -18,10 +17,9 @@ const instance = process.env.NODE_APP_INSTANCE || 'main';
|
||||
logger.setLevel(argv.level || 'info');
|
||||
|
||||
async function auth() {
|
||||
const httpSession = bhttp.session();
|
||||
const username = config.uniqueUsername ? `${config.user.username}-${new Date().getTime().toString().slice(-5)}` : config.user.username;
|
||||
|
||||
const res = await httpSession.post(`${config.api}/session`, {
|
||||
const res = await bhttp.post(`${config.api}/session`, {
|
||||
...config.user,
|
||||
username,
|
||||
}, {
|
||||
@@ -36,13 +34,17 @@ async function auth() {
|
||||
|
||||
return {
|
||||
user: res.body,
|
||||
httpSession,
|
||||
sessionCookie: res.headers['set-cookie'][0],
|
||||
// auth may return an explicit auth cookie domain, but we connect through the VPN domain that would break the cookie, so don't use a bhttp session and strip the domain
|
||||
sessionCookie: res.headers['set-cookie'][0].replace(/Domain=.*;/, ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function getWsId(httpSession) {
|
||||
const res = await httpSession.get(`${config.api}/socket`);
|
||||
async function getWsId(sessionCookie) {
|
||||
const res = await bhttp.get(`${config.api}/socket`, {
|
||||
headers: {
|
||||
cookie: sessionCookie,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.statusCode !== 200) {
|
||||
throw new Error(`Failed to retrieve WebSocket ID: ${res.body.toString()}`);
|
||||
@@ -136,37 +138,27 @@ async function connect(bot, games) {
|
||||
|
||||
socket.connect = async () => {
|
||||
try {
|
||||
const { user, httpSession, sessionCookie } = await auth();
|
||||
const wsCreds = await getWsId(httpSession);
|
||||
const { user, sessionCookie } = await auth();
|
||||
const wsCreds = await getWsId(sessionCookie);
|
||||
|
||||
bot.user = user;
|
||||
bot.httpSession = httpSession;
|
||||
|
||||
logger.info(`Attempting to connect to ${config.socket}`);
|
||||
|
||||
const { origin, pathname } = new URL(config.socket);
|
||||
|
||||
socket.io = io(origin, {
|
||||
transports: ['websocket'],
|
||||
path: pathname,
|
||||
query: {
|
||||
v: wsCreds.wsId,
|
||||
t: wsCreds.timestamp,
|
||||
},
|
||||
extraHeaders: {
|
||||
socket.ws = new WebSocket(`${config.socket}?${new URLSearchParams({ v: wsCreds.wsId, t: wsCreds.timestamp }).toString()}`, [], {
|
||||
headers: {
|
||||
cookie: sessionCookie,
|
||||
},
|
||||
});
|
||||
|
||||
socket.io.on('connect', () => {
|
||||
logger.info(`Connected to ${config.socket}`);
|
||||
});
|
||||
socket.ws.on('message', async (msgData) => {
|
||||
const msg = msgData.toString();
|
||||
|
||||
socket.io.on('connect_error', (error) => {
|
||||
logger.info(`Failed to connect to ${config.socket}: ${error}`);
|
||||
});
|
||||
if (typeof msg === 'string' && msg.includes('pong')) {
|
||||
logger.debug(`Received pong ${msg.split(':')[1]}`);
|
||||
return;
|
||||
}
|
||||
|
||||
socket.io.on('_', async (msg) => {
|
||||
const [domain, data] = JSON.parse(msg);
|
||||
|
||||
logger.debug(`Received ${domain}: ${JSON.stringify(data)}`);
|
||||
@@ -180,12 +172,18 @@ async function connect(bot, games) {
|
||||
}
|
||||
});
|
||||
|
||||
socket.io.on('close', async (info) => {
|
||||
socket.ws.on('close', async (info) => {
|
||||
logger.error(`WebSocket closed, reconnecting in ${config.reconnectDelay} seconds: ${info}`);
|
||||
|
||||
await delay(config.reconnectDelay * 1000);
|
||||
socket.connect();
|
||||
});
|
||||
|
||||
socket.ws.on('error', async (error) => {
|
||||
logger.error(`WebSocket error: ${error.message}`);
|
||||
});
|
||||
|
||||
logger.info(`Connected to ${config.socket}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to connect, retrying in ${config.reconnectDelay} seconds: ${error.message}`);
|
||||
|
||||
@@ -195,9 +193,24 @@ async function connect(bot, games) {
|
||||
};
|
||||
|
||||
socket.transmit = (domain, data) => {
|
||||
socket.io.emit('_', JSON.stringify([domain, data]));
|
||||
socket.ws.send(JSON.stringify([domain, data]));
|
||||
};
|
||||
|
||||
function ping() {
|
||||
setTimeout(() => {
|
||||
if (socket.ws && socket.ws?.readyState === socket.ws?.OPEN) {
|
||||
const now = Date.now();
|
||||
|
||||
socket.ws.send(`ping:${now}`);
|
||||
logger.debug(`Sent ping ${now}`);
|
||||
}
|
||||
|
||||
ping();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
ping();
|
||||
|
||||
socket.connect();
|
||||
|
||||
return socket;
|
||||
|
||||
25
src/tools/merge-point-files.js
Normal file
25
src/tools/merge-point-files.js
Normal file
@@ -0,0 +1,25 @@
|
||||
const path = require('path');
|
||||
const args = require('yargs').argv;
|
||||
|
||||
function init() {
|
||||
const scoresA = require(path.resolve(__dirname, args.a));
|
||||
const scoresB = require(path.resolve(__dirname, args.b));
|
||||
|
||||
const sum = {};
|
||||
|
||||
[scoresA, scoresB].forEach((record) => {
|
||||
Object.entries(record).forEach(([game, scores]) => {
|
||||
if (!sum[game]) {
|
||||
sum[game] = {};
|
||||
}
|
||||
|
||||
Object.entries(scores).forEach(([id, score]) => {
|
||||
sum[game][id] = (sum[game][id] || 0) + score;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
console.log(JSON.stringify(sum, null, 4));
|
||||
}
|
||||
|
||||
init();
|
||||
35
src/tools/merge-point-users.js
Normal file
35
src/tools/merge-point-users.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const path = require('path');
|
||||
const args = require('yargs').argv;
|
||||
const fs = require('fs').promises;
|
||||
|
||||
async function init() {
|
||||
await fs.copyFile(args.file, `${path.basename(args.file, path.extname(args.file))}_backup_${Date.now()}.json`);
|
||||
|
||||
const filepath = path.join(process.cwd(), args.file);
|
||||
const points = require(filepath); // eslint-disable-line
|
||||
|
||||
Object.entries(points).forEach(([game, scores]) => {
|
||||
const originKey = Object.keys(scores).find((userKey) => userKey.includes(`:${args.origin}`));
|
||||
const originScore = scores[originKey];
|
||||
|
||||
const targetKey = Object.keys(scores).find((userKey) => userKey.includes(`:${args.target}`));
|
||||
const targetScore = scores[targetKey];
|
||||
|
||||
if (typeof originScore === 'undefined' || typeof targetScore === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const totalScore = targetScore + originScore;
|
||||
|
||||
points[game][targetKey] = targetScore + originScore;
|
||||
delete points[game][originKey];
|
||||
|
||||
console.log(`${game} ${targetScore} (${args.target}) + ${originScore} (${args.origin}) = ${totalScore}`);
|
||||
});
|
||||
|
||||
await fs.writeFile(filepath, JSON.stringify(points, null, 4));
|
||||
|
||||
console.log(`Saved ${filepath}`);
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -42,6 +42,7 @@ const styleMethods = (() => {
|
||||
return {
|
||||
...styles,
|
||||
forest: styles.green,
|
||||
orange: styles.yellow,
|
||||
code: bypass,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user