Compare commits
No commits in common. "master" and "socketio" have entirely different histories.
|
@ -15,6 +15,6 @@
|
||||||
"no-console": 0,
|
"no-console": 0,
|
||||||
"indent": ["error", "tab"],
|
"indent": ["error", "tab"],
|
||||||
"no-tabs": 0,
|
"no-tabs": 0,
|
||||||
"max-len": 0
|
"max-len": [2, {"code": 400, "tabWidth": 4, "ignoreUrls": true}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
/*
|
|
||||||
const inflect = require('inflect');
|
const inflect = require('inflect');
|
||||||
const tensify = require('tensify');
|
const tensify = require('tensify');
|
||||||
|
|
||||||
|
const dictionary = require('./dictionary.json');
|
||||||
|
|
||||||
function getTenses(word) {
|
function getTenses(word) {
|
||||||
try {
|
try {
|
||||||
const { past, past_participle: participle } = tensify(word);
|
const { past, past_participle: participle } = tensify(word);
|
||||||
|
@ -15,40 +15,26 @@ function getTenses(word) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
const dictionary = require('./dictionary.json');
|
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
/*
|
|
||||||
const formsDictionary = Object.fromEntries(Object.entries(dictionary).flatMap(([word, definition]) => {
|
const formsDictionary = Object.fromEntries(Object.entries(dictionary).flatMap(([word, definition]) => {
|
||||||
const plural = inflect.pluralize(word);
|
const plural = inflect.pluralize(word);
|
||||||
const { past, participle } = getTenses(word);
|
const { past, participle } = getTenses(word);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
[word, definition],
|
[word, definition],
|
||||||
...(plural && !dictionary[plural] ? [[plural, definition]] : []),
|
...(plural && !dictionary[plural] ? [[plural, `Plural of ${word}`]] : []),
|
||||||
...(past && !dictionary[past] ? [[past, definition]] : []),
|
...(past && !dictionary[past] ? [[past, `Past tense of ${word}`]] : []),
|
||||||
...(participle && !dictionary[participle] ? [[past, definition]] : []),
|
...(participle && !dictionary[participle] ? [[past, `Past participle of ${word}`]] : []),
|
||||||
];
|
];
|
||||||
}));
|
}));
|
||||||
*/
|
|
||||||
|
|
||||||
const validWords = Object.entries(dictionary).filter(([word]) => /^[a-zA-Z]+$/.test(word));
|
const validWords = Object.entries(formsDictionary).filter(([word]) => /^[a-zA-Z]+$/.test(word));
|
||||||
|
|
||||||
const sortedWords = validWords.reduce((acc, [rawWord, fullDefinition]) => {
|
const sortedWords = validWords.reduce((acc, [rawWord, fullDefinition]) => {
|
||||||
const word = rawWord.toLowerCase();
|
const word = rawWord.toLowerCase();
|
||||||
const anagram = word.split('').sort().join('');
|
const anagram = word.split('').sort().join('');
|
||||||
const definitions = fullDefinition
|
const definitions = fullDefinition?.split(/\d+\.\s+/).filter(Boolean).map((definition) => definition.split('.')[0].toLowerCase());
|
||||||
?.split(/\d+\.\s+/).filter(Boolean).map((definition) => {
|
|
||||||
const splitIndex = definition.indexOf('.', 16); // split after n characters to avoid splitting on e.g. abbreviated categories at the start of the definition: (Anat.)
|
|
||||||
|
|
||||||
if (splitIndex > -1) {
|
|
||||||
return definition.slice(0, splitIndex).trim().toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
return definition.toLowerCase();
|
|
||||||
}) || [];
|
|
||||||
|
|
||||||
if (!acc[anagram.length]) {
|
if (!acc[anagram.length]) {
|
||||||
acc[anagram.length] = {};
|
acc[anagram.length] = {};
|
||||||
|
|
File diff suppressed because one or more lines are too long
102260
assets/dictionary_old.json
102260
assets/dictionary_old.json
File diff suppressed because one or more lines are too long
|
@ -1,31 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const fs = require('fs').promises;
|
|
||||||
|
|
||||||
const words = require('./words.json');
|
|
||||||
const wordsBlacklist = require('./words_blacklist.json');
|
|
||||||
const dictionary = require('./dictionary.json');
|
|
||||||
|
|
||||||
// mainly lowercase names
|
|
||||||
const blacklist = new Set(wordsBlacklist);
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
const notNameWords = words.filter((word) => word.charAt(0) === word.charAt(0).toLowerCase() && !blacklist.has(word)); // don't include names for places, products, people, etc.
|
|
||||||
|
|
||||||
const definitions = Object.fromEntries(notNameWords.map((word) => {
|
|
||||||
const normalizedWord = word.normalize('NFD').replace(/\p{Diacritic}/ug, '').toLowerCase().trim();
|
|
||||||
const definition = dictionary[normalizedWord];
|
|
||||||
const singular = normalizedWord.replace(/s$/, '');
|
|
||||||
const singularDefinition = dictionary[singular];
|
|
||||||
|
|
||||||
return [normalizedWord, definition || singularDefinition || null];
|
|
||||||
}));
|
|
||||||
|
|
||||||
const string = JSON.stringify(definitions, null, 4);
|
|
||||||
|
|
||||||
await fs.writeFile('./dictionary.json', string);
|
|
||||||
|
|
||||||
console.log(`Wrote ${Object.keys(definitions).length} words to ./dictionary.json`);
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
89686
assets/words.json
89686
assets/words.json
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +0,0 @@
|
||||||
[
|
|
||||||
"ioctl",
|
|
||||||
"xterm"
|
|
||||||
]
|
|
|
@ -27,7 +27,6 @@ module.exports = {
|
||||||
'chat',
|
'chat',
|
||||||
'mash',
|
'mash',
|
||||||
'trivia',
|
'trivia',
|
||||||
'wordle',
|
|
||||||
'letters',
|
'letters',
|
||||||
'numbers',
|
'numbers',
|
||||||
'hunt',
|
'hunt',
|
||||||
|
@ -134,12 +133,6 @@ module.exports = {
|
||||||
z: 1,
|
z: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wordle: {
|
|
||||||
minLength: 3,
|
|
||||||
length: 5,
|
|
||||||
bonusDictionaryThreshold: 1000, // minimum dictionary size to assign bonus points, prevent people from scoring full bonus points from 1-word dictionaries
|
|
||||||
mode: 'easy',
|
|
||||||
},
|
|
||||||
numbers: {
|
numbers: {
|
||||||
length: 6,
|
length: 6,
|
||||||
timeout: 90,
|
timeout: 90,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "schat2-clive",
|
"name": "schat2-clive",
|
||||||
"version": "1.30.7",
|
"version": "1.26.12",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "schat2-clive",
|
"name": "schat2-clive",
|
||||||
"version": "1.30.7",
|
"version": "1.26.12",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^8.3.0",
|
"better-sqlite3": "^8.3.0",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "schat2-clive",
|
"name": "schat2-clive",
|
||||||
"version": "1.30.7",
|
"version": "1.26.12",
|
||||||
"description": "Game host for SChat 2-powered chat sites",
|
"description": "Game host for SChat 2-powered chat sites",
|
||||||
"main": "src/app.js",
|
"main": "src/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -37,7 +37,7 @@ function onCommand(args, context) {
|
||||||
return `${style.yellow(`(${coinFaces[result - 1]})`)} ${style.bold(result === 1 ? 'heads' : 'tails')}`; // eslint-disable-line no-irregular-whitespace
|
return `${style.yellow(`(${coinFaces[result - 1]})`)} ${style.bold(result === 1 ? 'heads' : 'tails')}`; // eslint-disable-line no-irregular-whitespace
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${style.grey(dieFaces[result - 1] || '⬡')} ${style.bold(result)}`; // eslint-disable-line no-irregular-whitespace
|
return `${style.grey(dieFaces[result - 1] || '☐')} ${style.bold(result)}`; // eslint-disable-line no-irregular-whitespace
|
||||||
});
|
});
|
||||||
|
|
||||||
context.sendMessage(results.join(style.grey(' | ')), context.room.id, { label: type });
|
context.sendMessage(results.join(style.grey(' | ')), context.room.id, { label: type });
|
||||||
|
|
|
@ -91,20 +91,13 @@ async function play(context) {
|
||||||
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(settings.timeout))} seconds, let's start!`, context.room.id);
|
context.sendMessage(`${getBoard(context)} You have ${style.bold(style.green(settings.timeout))} seconds, let's start!`, context.room.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
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) * 2)} 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 / 3) * 1000, null, { signal: game.ac.signal });
|
||||||
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 / 3)} seconds`))} left`, context.room.id);
|
||||||
|
|
||||||
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);
|
stop(context);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// abort expected, probably not an error
|
// abort expected, probably not an error
|
||||||
|
|
|
@ -35,7 +35,7 @@ function start(length, context, attempt = 0) {
|
||||||
|
|
||||||
if (answers.some((answer) => answer.word === anagram)) {
|
if (answers.some((answer) => answer.word === anagram)) {
|
||||||
if (attempt >= 10) {
|
if (attempt >= 10) {
|
||||||
context.sendMessage(`Sorry, I did not find a mashable ${length}-letter word`, context.room.id);
|
context.sendMessage(`Sorry, I did not find a mashable ${length}-letter word`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,9 +81,7 @@ function play(rawWord, context, shouted) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (answer) {
|
if (answer) {
|
||||||
const definition = answer.definitions[0]
|
const definition = answer.definitions[0] ? `: ${style.italic(`${answer.definitions[0].slice(0, 100)}${mash.answers[0].definitions[0].length > 100 ? '...' : ''}`)}` : '';
|
||||||
? `: ${style.italic(`${answer.definitions[0].slice(0, 100)}${answer.definitions[0].length > 100 ? '...' : ''}`)}`
|
|
||||||
: '';
|
|
||||||
|
|
||||||
context.sendMessage(mash.answers.length === 1
|
context.sendMessage(mash.answers.length === 1
|
||||||
? `${style.bold(style.yellow(word))} is the right answer${definition}, ${style.bold(style.cyan(`${config.usernamePrefix}${context.user.username}`))} now has ${style.bold(`${context.user.points + 1} ${context.user.points === 0 ? 'point' : 'points'}`)}! There were no other options for ${style.bold(mash.anagram)}.`
|
? `${style.bold(style.yellow(word))} is the right answer${definition}, ${style.bold(style.cyan(`${config.usernamePrefix}${context.user.username}`))} now has ${style.bold(`${context.user.points + 1} ${context.user.points === 0 ? 'point' : 'points'}`)}! There were no other options for ${style.bold(mash.anagram)}.`
|
||||||
|
@ -146,19 +144,10 @@ function define(word, context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (answer) {
|
context.sendMessage(`No definition available for ${style.bold(word)}, ${config.usernamePrefix}${context.user.username}`, context.room.id);
|
||||||
context.sendMessage(`${style.bold(word)} is in my dictionary, but I cannot provide a definition, ${config.usernamePrefix}${context.user.username}`, context.room.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
context.sendMessage(`${style.bold(word)} is not in my dictionary, ${config.usernamePrefix}${context.user.username}`, context.room.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeDefinition(definition, answers) {
|
function sanitizeDefinition(definition, answers) {
|
||||||
if (!definition) {
|
|
||||||
return 'No definition';
|
|
||||||
}
|
|
||||||
|
|
||||||
return definition.replaceAll(/\w+/g, (word) => {
|
return definition.replaceAll(/\w+/g, (word) => {
|
||||||
if (answers.some((answer) => answer.word.includes(word))) {
|
if (answers.some((answer) => answer.word.includes(word))) {
|
||||||
return '*'.repeat(word.length);
|
return '*'.repeat(word.length);
|
||||||
|
|
|
@ -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);
|
context.sendMessage('Let\'s play the numbers! Would you like a large number or a small one?', context.room.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function calc(calculation, context) {
|
function solve(calculation, context) {
|
||||||
try {
|
try {
|
||||||
const parsed = math.parse(calculation.replace(/`/g, '')); // backticks may be used to prevent the expression from being expanded by Markdown in SChat
|
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();
|
const answer = parsed.evaluate();
|
||||||
|
@ -347,51 +347,6 @@ function calc(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) {
|
function onCommand(args, context) {
|
||||||
if (context.subcommand === 'stop') {
|
if (context.subcommand === 'stop') {
|
||||||
stop(context, true);
|
stop(context, true);
|
||||||
|
@ -403,12 +358,7 @@ function onCommand(args, context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['calculate', 'calc'].includes(context.subcommand || context.command)) {
|
if (['calculate', 'calc', 'solve'].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
|
solve(args.join(' '), context); // two from the top, four from the bottom, please Rachel
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,245 +0,0 @@
|
||||||
'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 = config.platform === 'irc' ? '' : style.grey(style.code('['));
|
|
||||||
|
|
||||||
const middle = letters.map((letter) => {
|
|
||||||
if (letter === null) {
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? style.bgsilver(' ? ')
|
|
||||||
: style.code(' '); // em space, U+2003, charcode 8195
|
|
||||||
}
|
|
||||||
|
|
||||||
if (letter[1] === true) {
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? style.bggreen(` ${letter[0].toUpperCase()}${style.green('*')}`)
|
|
||||||
: style.green(style.bold(style.code(letter[0].toUpperCase())));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (letter[1] === false) {
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? style.bgyellow(` ${letter[0].toUpperCase()}${style.yellow('?')}`)
|
|
||||||
: style.orange(style.bold(style.code(letter[0].toUpperCase())));
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? style.bgsilver(` ${letter[0].toUpperCase()} `)
|
|
||||||
: style.grey(style.bold(style.code(letter[0].toUpperCase())));
|
|
||||||
}).join(config.platform === 'irc' ? '' : style.grey(style.code('|')));
|
|
||||||
|
|
||||||
const suffix = config.platform === 'irc' ? '' : `${style.grey(style.code(']'))}`;
|
|
||||||
|
|
||||||
if (showLetters) {
|
|
||||||
const letterBoard = Array.from(wordle.letters).map(([letter, state]) => {
|
|
||||||
if (state === true) {
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? style.bggreen(`${letter}${style.green('*')}`)
|
|
||||||
: style.green(style.bold(letter));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state === false) {
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? style.bgyellow(`${letter}${style.yellow('?')}`)
|
|
||||||
: style.orange(style.bold(letter));
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.platform === 'irc'
|
|
||||||
? letter
|
|
||||||
: style.grey(letter);
|
|
||||||
}).join(config.platform === 'irc' ? ' ' : ' '); // regular space vs em space
|
|
||||||
|
|
||||||
return `${prefix}${middle}${suffix}${wordle.mode === 'hard' ? ' (Hard)' : ''} Letters: ${letterBoard}`; // eslint-disable-line no-irregular-whitespace
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${prefix}${middle}${suffix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function start(length = settings.length, mode = settings.mode, 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,
|
|
||||||
mode,
|
|
||||||
definitions: word.definitions,
|
|
||||||
letters: new Map(alphabet.map((letter) => [letter, null])),
|
|
||||||
guesses: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (config.platform === 'irc') {
|
|
||||||
context.sendMessage(`${getBoard(Array.from({ length }, () => null), false, context)}${mode === 'hard' ? ' (Hard)' : ''} guess the word with ${config.prefix}w [word]! ${style.bgsilver(' X ')} not in word; ${style.bgyellow(` X${style.yellow('?')}`)} wrong position; ${style.bggreen(` X${style.green('*')}`)} correct position.`, context.room.id); // eslint-disable-line no-irregular-whitespace
|
|
||||||
} else {
|
|
||||||
context.sendMessage(`${getBoard(Array.from({ length }, () => null), false, context)}${mode === 'hard' ? ' (Hard)' : ''} 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 occurrences = wordle.word.split('').reduce((acc, letter) => acc.set(letter, (acc.get(letter) || 0) + 1), new Map());
|
|
||||||
|
|
||||||
const guessLetters = upperGuess.split('');
|
|
||||||
const check = guessLetters.map((letter) => [letter, null]);
|
|
||||||
const prevGuess = wordle.guesses.at(-1)?.[1];
|
|
||||||
|
|
||||||
if (wordle.mode === 'hard' && prevGuess) {
|
|
||||||
const prevLetters = prevGuess.split('');
|
|
||||||
|
|
||||||
const valid = prevLetters.every((letter, index) => {
|
|
||||||
if (wordle.letters.get(letter) === false && !guessLetters.includes(letter)) {
|
|
||||||
context.sendMessage(`(Hard) Your guess must include the letter ${config.platform === 'irc'
|
|
||||||
? style.bgyellow(` ${letter} `)
|
|
||||||
: `${style.grey(style.code('['))}${style.orange(style.bold(style.code(letter)))}${style.grey(style.code(']'))}`
|
|
||||||
}.`, context.room.id);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wordle.letters.get(letter) === true && letter === wordle.word[index] && letter !== guessLetters[index]) {
|
|
||||||
context.sendMessage(`(Hard) Your guess must include the letter ${config.platform === 'irc'
|
|
||||||
? style.bggreen(` ${letter} `)
|
|
||||||
: `${style.grey(style.code('['))}${style.green(style.bold(style.code(letter)))}${style.grey(style.code(']'))}`
|
|
||||||
} in the correct position.`, context.room.id);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// correct
|
|
||||||
guessLetters.forEach((letter, index) => {
|
|
||||||
if (wordle.word[index] === letter) {
|
|
||||||
wordle.letters.set(letter, true);
|
|
||||||
check[index] = [letter, true];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// wrong place
|
|
||||||
guessLetters.forEach((letter, index) => {
|
|
||||||
if (wordle.word.includes(letter)) {
|
|
||||||
if (wordle.letters.get(letter) !== true) {
|
|
||||||
wordle.letters.set(letter, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
const marks = check.filter(([checkLetter, status]) => checkLetter === letter && status !== null).length;
|
|
||||||
|
|
||||||
if (check[index][1] !== true && marks < occurrences.get(letter)) {
|
|
||||||
check[index] = [letter, false];
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
wordle.letters.delete(letter);
|
|
||||||
});
|
|
||||||
|
|
||||||
wordle.guesses = wordle.guesses.concat([[context.user.username, upperGuess]]);
|
|
||||||
|
|
||||||
if (upperGuess === wordle.word) {
|
|
||||||
const assignBonusPoints = wordle.wordList.length > config.wordle.bonusDictionaryThreshold;
|
|
||||||
const points = assignBonusPoints
|
|
||||||
? Math.max((wordle.word.length + 1) - wordle.guesses.length, 1)
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
const definition = wordle.definitions[0] ? `: ${style.italic(`${wordle.definitions[0].slice(0, 100)}${wordle.definitions[0].length > 100 ? '...' : ''}`)}` : '';
|
|
||||||
|
|
||||||
context.setPoints(context.user, points, { key: wordle.mode === 'hard' ? 'hardle' : 'wordle' });
|
|
||||||
|
|
||||||
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'}${assignBonusPoints ? '. ' : ` (${wordle.word.length}-letter dictionary too small for bonus points). `}${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 = args.map((arg) => Number(arg)).find(Boolean) || settings.length;
|
|
||||||
const mode = args.find((arg) => arg === 'easy' || arg === 'hard')
|
|
||||||
|| (context.command === 'hardle' && 'hard')
|
|
||||||
|| settings.mode;
|
|
||||||
|
|
||||||
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, mode, 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', 'hardle', 'lingo', 'guess', 'w'],
|
|
||||||
help: `Guess the ${settings.length}-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. Change the numbers of letters or switch to hard mode with ~wordle [length] [hard] or ~hardle [length].`,
|
|
||||||
onCommand,
|
|
||||||
// onMessage,
|
|
||||||
};
|
|
|
@ -117,11 +117,6 @@ async function getGames(bot, identifier) {
|
||||||
const sendMessage = (body, roomId, options, recipient) => {
|
const sendMessage = (body, roomId, options, recipient) => {
|
||||||
const curatedBody = curateMessageBody(body, game, key, options);
|
const curatedBody = curateMessageBody(body, game, key, options);
|
||||||
|
|
||||||
if (!roomId && !recipient) {
|
|
||||||
logger.error(`Missing room ID or recipient for message: ${body}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.platform === 'irc') {
|
if (config.platform === 'irc') {
|
||||||
bot.client.say(/^#/.test(roomId) ? roomId : recipient, curatedBody);
|
bot.client.say(/^#/.test(roomId) ? roomId : recipient, curatedBody);
|
||||||
}
|
}
|
||||||
|
|
75
src/schat.js
75
src/schat.js
|
@ -3,7 +3,8 @@
|
||||||
const config = require('config');
|
const config = require('config');
|
||||||
const { setTimeout: delay } = require('timers/promises');
|
const { setTimeout: delay } = require('timers/promises');
|
||||||
const bhttp = require('bhttp');
|
const bhttp = require('bhttp');
|
||||||
const WebSocket = require('ws');
|
// const WebSocket = require('ws');
|
||||||
|
const io = require('socket.io-client');
|
||||||
const logger = require('simple-node-logger').createSimpleLogger();
|
const logger = require('simple-node-logger').createSimpleLogger();
|
||||||
const { argv } = require('yargs');
|
const { argv } = require('yargs');
|
||||||
|
|
||||||
|
@ -17,9 +18,10 @@ const instance = process.env.NODE_APP_INSTANCE || 'main';
|
||||||
logger.setLevel(argv.level || 'info');
|
logger.setLevel(argv.level || 'info');
|
||||||
|
|
||||||
async function auth() {
|
async function auth() {
|
||||||
|
const httpSession = bhttp.session();
|
||||||
const username = config.uniqueUsername ? `${config.user.username}-${new Date().getTime().toString().slice(-5)}` : config.user.username;
|
const username = config.uniqueUsername ? `${config.user.username}-${new Date().getTime().toString().slice(-5)}` : config.user.username;
|
||||||
|
|
||||||
const res = await bhttp.post(`${config.api}/session`, {
|
const res = await httpSession.post(`${config.api}/session`, {
|
||||||
...config.user,
|
...config.user,
|
||||||
username,
|
username,
|
||||||
}, {
|
}, {
|
||||||
|
@ -34,17 +36,13 @@ async function auth() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: res.body,
|
user: res.body,
|
||||||
// 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
|
httpSession,
|
||||||
sessionCookie: res.headers['set-cookie'][0].replace(/Domain=.*;/, ''),
|
sessionCookie: res.headers['set-cookie'][0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWsId(sessionCookie) {
|
async function getWsId(httpSession) {
|
||||||
const res = await bhttp.get(`${config.api}/socket`, {
|
const res = await httpSession.get(`${config.api}/socket`);
|
||||||
headers: {
|
|
||||||
cookie: sessionCookie,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res.statusCode !== 200) {
|
if (res.statusCode !== 200) {
|
||||||
throw new Error(`Failed to retrieve WebSocket ID: ${res.body.toString()}`);
|
throw new Error(`Failed to retrieve WebSocket ID: ${res.body.toString()}`);
|
||||||
|
@ -130,8 +128,6 @@ function handleError(error, socket, domain, data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const pongRegex = /^pong:\d+$/;
|
|
||||||
|
|
||||||
async function connect(bot, games) {
|
async function connect(bot, games) {
|
||||||
const socket = {
|
const socket = {
|
||||||
ws: { readyState: 0 },
|
ws: { readyState: 0 },
|
||||||
|
@ -140,27 +136,37 @@ async function connect(bot, games) {
|
||||||
|
|
||||||
socket.connect = async () => {
|
socket.connect = async () => {
|
||||||
try {
|
try {
|
||||||
const { user, sessionCookie } = await auth();
|
const { user, httpSession, sessionCookie } = await auth();
|
||||||
const wsCreds = await getWsId(sessionCookie);
|
const wsCreds = await getWsId(httpSession);
|
||||||
|
|
||||||
bot.user = user;
|
bot.user = user;
|
||||||
|
bot.httpSession = httpSession;
|
||||||
|
|
||||||
logger.info(`Attempting to connect to ${config.socket}`);
|
logger.info(`Attempting to connect to ${config.socket}`);
|
||||||
|
|
||||||
socket.ws = new WebSocket(`${config.socket}?${new URLSearchParams({ v: wsCreds.wsId, t: wsCreds.timestamp }).toString()}`, [], {
|
const { origin, pathname } = new URL(config.socket);
|
||||||
headers: {
|
|
||||||
|
socket.io = io(origin, {
|
||||||
|
transports: ['websocket'],
|
||||||
|
path: pathname,
|
||||||
|
query: {
|
||||||
|
v: wsCreds.wsId,
|
||||||
|
t: wsCreds.timestamp,
|
||||||
|
},
|
||||||
|
extraHeaders: {
|
||||||
cookie: sessionCookie,
|
cookie: sessionCookie,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.ws.on('message', async (msgData) => {
|
socket.io.on('connect', () => {
|
||||||
const msg = msgData.toString();
|
logger.info(`Connected to ${config.socket}`);
|
||||||
|
});
|
||||||
|
|
||||||
if (typeof msg === 'string' && pongRegex.test(msg)) {
|
socket.io.on('connect_error', (error) => {
|
||||||
logger.debug(`Received pong ${msg.split(':')[1]}`);
|
logger.info(`Failed to connect to ${config.socket}: ${error}`);
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
|
socket.io.on('_', async (msg) => {
|
||||||
const [domain, data] = JSON.parse(msg);
|
const [domain, data] = JSON.parse(msg);
|
||||||
|
|
||||||
logger.debug(`Received ${domain}: ${JSON.stringify(data)}`);
|
logger.debug(`Received ${domain}: ${JSON.stringify(data)}`);
|
||||||
|
@ -174,18 +180,12 @@ async function connect(bot, games) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.ws.on('close', async (info) => {
|
socket.io.on('close', async (info) => {
|
||||||
logger.error(`WebSocket closed, reconnecting in ${config.reconnectDelay} seconds: ${info}`);
|
logger.error(`WebSocket closed, reconnecting in ${config.reconnectDelay} seconds: ${info}`);
|
||||||
|
|
||||||
await delay(config.reconnectDelay * 1000);
|
await delay(config.reconnectDelay * 1000);
|
||||||
socket.connect();
|
socket.connect();
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.ws.on('error', async (error) => {
|
|
||||||
logger.error(`WebSocket error: ${error.message}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info(`Connected to ${config.socket}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Failed to connect, retrying in ${config.reconnectDelay} seconds: ${error.message}`);
|
logger.error(`Failed to connect, retrying in ${config.reconnectDelay} seconds: ${error.message}`);
|
||||||
|
|
||||||
|
@ -195,24 +195,9 @@ async function connect(bot, games) {
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.transmit = (domain, data) => {
|
socket.transmit = (domain, data) => {
|
||||||
socket.ws.send(JSON.stringify([domain, data]));
|
socket.io.emit('_', 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();
|
socket.connect();
|
||||||
|
|
||||||
return socket;
|
return socket;
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
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();
|
|
|
@ -1,35 +0,0 @@
|
||||||
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,7 +42,6 @@ const styleMethods = (() => {
|
||||||
return {
|
return {
|
||||||
...styles,
|
...styles,
|
||||||
forest: styles.green,
|
forest: styles.green,
|
||||||
orange: styles.yellow,
|
|
||||||
code: bypass,
|
code: bypass,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue