Skip to content
Snippets Groups Projects
Commit 4572fc95 authored by Rob Swindell's avatar Rob Swindell :speech_balloon:
Browse files

Merge branch 'gttrivia_post_scores_in_subboard' into 'master'

GT Trivia v1.02: If the JSON server can't be reached, scores can be written to a message sub-board (or more than one if needed)

See merge request !233
parents 33c42d38 7beb35ee
No related branches found
No related tags found
2 merge requests!463MRC mods by Codefenix (2024-10-20),!233GT Trivia v1.02: If the JSON server can't be reached, scores can be written to a message sub-board (or more than one if needed)
...@@ -2,6 +2,10 @@ ...@@ -2,6 +2,10 @@
numQuestionsPerPlay=30 numQuestionsPerPlay=30
numTriesPerQuestion=4 numTriesPerQuestion=4
maxNumPlayerScoresToDisplay=10 maxNumPlayerScoresToDisplay=10
; scoresMsgSubBoardsForPosting specifies a comma-separated list of internal sub-board
; codes for sub-boards to post user scores in, as a backup in case the server
; specified in the REMOTE_SERVER section can't be reached
scoresMsgSubBoardsForPosting=
[COLORS] [COLORS]
error=YH error=YH
...@@ -31,5 +35,11 @@ dirty_minds=AGE 18 ...@@ -31,5 +35,11 @@ dirty_minds=AGE 18
server=digitaldistortionbbs.com server=digitaldistortionbbs.com
port=10088 port=10088
; The SERVER section is for hosting game scores only
[SERVER] [SERVER]
deleteScoresOlderThanDays=182 deleteScoresOlderThanDays=182
; scoresMsgSubBoardsForReading specifies a comma-separated list of internal sub-board
; codes for sub-boards to read user scores from. To use this, set up an event in
; SCFG > External Programs > Timed Events to run gttrivia.js with the command-line
; parameter -read_scores_from_subboard
scoresMsgSubBoardsForReading=
...@@ -11,6 +11,10 @@ Date Author Description ...@@ -11,6 +11,10 @@ Date Author Description
also sysop functions to remove players and users from the hosted also sysop functions to remove players and users from the hosted
inter-BBS scores. Also, answer clues now don't mask spaces in the inter-BBS scores. Also, answer clues now don't mask spaces in the
answer. answer.
2022-12-08 Eric Oulashin Version 1.02
The game can now post scores in (networked) message sub-boards as
a backup to using a JSON DB server in case the server can't be
contacted.
*/ */
...@@ -37,11 +41,8 @@ if (system.version_num < 31500) ...@@ -37,11 +41,8 @@ if (system.version_num < 31500)
} }
// Version information // Version information
var GAME_VERSION = "1.01"; var GAME_VERSION = "1.02";
var GAME_VER_DATE = "2022-11-25"; var GAME_VER_DATE = "2022-12-08";
// Version of data written to the server, if applicable. This might not necessarily be the same as
// the version of the game.
var SERVER_DATA_VERSION = "1.01";
// Determine the location of this script (its startup directory). // Determine the location of this script (its startup directory).
// The code for figuring this out is a trick that was created by Deuce, // The code for figuring this out is a trick that was created by Deuce,
...@@ -141,18 +142,91 @@ var JSON_DB_LOCK_UNLOCK = -1; ...@@ -141,18 +142,91 @@ var JSON_DB_LOCK_UNLOCK = -1;
// Enable debugging if the first command-line parameter is -debug // Load the settings from the .ini file
var gDebug = false; var gSettings = loadSettings(gStartupPath);
if (argv.length > 0)
gDebug = (argv[0].toUpperCase() == "-DEBUG");
// Parse command-line arguments
var gCmdLineArgs = parseCmdLineArgs(argv);
// Display the program logo // If the command-line argument was specified to post or read scores in the configured message
displayProgramLogo(true, false); // sub-board, then do so and exit.
if (gCmdLineArgs.postScoresToSubBoard)
{
if (gSettings.behavior.scoresMsgSubBoardsForPosting.length == 0)
{
log(LOG_ERR, format("%s - Post scores to sub-boards specified, but scoresMsgSubBoardsForPosting is not set", GAME_NAME));
exit(2);
}
// Load the settings from the .ini file var exitCode = 0;
var gSettings = loadSettings(gStartupPath); for (var i = 0; i < gSettings.behavior.scoresMsgSubBoardsForPosting.length; ++i)
{
var subCode = gSettings.behavior.scoresMsgSubBoardsForPosting[i];
if (!msg_area.sub.hasOwnProperty(subCode))
{
log(LOG_ERR, format("%s - Sub-board-code %s does not exist (specified in %s)", GAME_NAME, subCode, "scoresMsgSubBoardsForPosting"));
exitCode = 3;
continue;
}
var postSuccessful = postGTTriviaScoresToSubBoard(subCode);
// For logging
var subBoardInfoStr = msg_area.sub[subCode].name + " - " + msg_area.sub[subCode].description;
// Write the status to the log
var logMsg = "";
var logLevel = LOG_INFO;
if (postSuccessful)
logMsg = format("%s - Successfully posted local scores to sub-board %s (%s)", GAME_NAME, subCode, subBoardInfoStr);
else
{
logLevel = LOG_ERR;
logMsg = format("%s - Posting scores to sub-board %s (%s) failed!", GAME_NAME, subCode, subBoardInfoStr);
exitCode = 3;
}
log(logLevel, logMsg);
}
exit(exitCode);
}
else if (gCmdLineArgs.readScoresFromSubBoard)
{
if (gSettings.server.scoresMsgSubBoardsForReading.length == 0)
{
log(LOG_ERR, format("%s - Read scores from sub-boards specified, but scoresMsgSubBoardsForReading is not set", GAME_NAME));
exit(2);
}
var exitCode = 0;
for (var i = 0; i < gSettings.server.scoresMsgSubBoardsForReading.length; ++i)
{
var subCode = gSettings.server.scoresMsgSubBoardsForReading[i];
if (subCode.length == 0 || !msg_area.sub.hasOwnProperty(subCode))
{
log(LOG_ERR, format("%s - Invalid sub-board code specified in scoresMsgSubBoardsForReading: %s", GAME_NAME, subCode));
continue;
}
var readSuccessful = readGTTriviaScoresFromSubBoard(subCode);
// For logging
var subBoardInfoStr = msg_area.sub[subCode].name + " - " + msg_area.sub[subCode].description;
// Write the status to the log
var logMsg = "";
var logLevel = LOG_INFO;
if (readSuccessful)
logMsg = format("%s - Successfully read scores from sub-board %s (%s)", GAME_NAME, subCode, subBoardInfoStr);
else
{
logLevel = LOG_ERR;
logMsg = format("%s - Reading scores from sub-board %s (%s) failed!", GAME_NAME, subCode, subBoardInfoStr);
exitCode = 4;
}
log(logLevel, logMsg);
}
exit(exitCode);
}
// Display the program logo
displayProgramLogo(true, false);
//console.clear("\x01n"); //console.clear("\x01n");
...@@ -296,7 +370,6 @@ function playTrivia() ...@@ -296,7 +370,6 @@ function playTrivia()
console.attributes = "N" + gSettings.colors.clue; console.attributes = "N" + gSettings.colors.clue;
console.print(partiallyHiddenStr(QAArray[i].answer, tryI-1) + "\x01n"); console.print(partiallyHiddenStr(QAArray[i].answer, tryI-1) + "\x01n");
console.crlf(); console.crlf();
} }
// Prompt for an answer // Prompt for an answer
console.attributes = "N" + gSettings.colors.answerPrompt; console.attributes = "N" + gSettings.colors.answerPrompt;
...@@ -360,8 +433,50 @@ function playTrivia() ...@@ -360,8 +433,50 @@ function playTrivia()
console.crlf(); console.crlf();
console.print("\x01b\x01hUpdating the scores file..."); console.print("\x01b\x01hUpdating the scores file...");
console.crlf(); console.crlf();
updateScoresFile(userPoints, qaFilenameInfo[chosenSectionIdx].sectionName); // Update the local scores file
var updateLocalScoresRetObj = updateScoresFile(userPoints, qaFilenameInfo[chosenSectionIdx].sectionName);
if (updateLocalScoresRetObj.succeeded)
{
// If there is a server configured, then send the user's score to the server too.
// If there are no server settings configured, or posting scores to the server fails,
// then if there's a sub-board configured, then write the user scores to the sub-board
var writeUserScoresToSubBoard = false;
if (gSettings.hasValidServerSettings())
{
writeUserScoresToSubBoard = !updateScoresOnServer(user.alias, updateLocalScoresRetObj.userScoresObj);
if (writeUserScoresToSubBoard)
{
var errorMsg = "\x01n" + attrCodeStr(gSettings.colors.error) + "Failed to update scores on the remote server.";
if (gSettings.behavior.scoresMsgSubBoardsForPosting.length > 0)
errorMsg += " Will post server scores in message area(s); server scores will be delayed.\x01n";
console.putmsg(errorMsg, P_WORDWRAP|P_NOATCODES);
console.attributes = "BH"; // As before
}
}
else
writeUserScoresToSubBoard = true;
if (writeUserScoresToSubBoard)
{
// If there are any message sub-boards configured, post the scores in there
for (var i = 0; i < gSettings.behavior.scoresMsgSubBoardsForPosting.length; ++i)
{
var subCode = gSettings.behavior.scoresMsgSubBoardsForPosting[i];
if (msg_area.sub.hasOwnProperty(subCode))
{
if (postGTTriviaScoresToSubBoard(subCode))
log(LOG_INFO, format("%s - Successfully posted scores in the sub-board", GAME_NAME));
else
log(LOG_INFO, format("%s - Posting scores in the sub-board failed!", GAME_NAME));
}
}
}
console.print("Done.\x01n"); console.print("Done.\x01n");
}
else
{
console.attributes = "N" + gSettings.colors.error;
console.print("Failed to save the scores!\x01n");
}
console.crlf(); console.crlf();
return 0; return 0;
} }
...@@ -384,6 +499,7 @@ function loadSettings(pStartupPath) ...@@ -384,6 +499,7 @@ function loadSettings(pStartupPath)
settings.colors = iniFile.iniGetObject("COLORS"); settings.colors = iniFile.iniGetObject("COLORS");
settings.category_ars = iniFile.iniGetObject("CATEGORY_ARS"); settings.category_ars = iniFile.iniGetObject("CATEGORY_ARS");
settings.remoteServer = iniFile.iniGetObject("REMOTE_SERVER"); settings.remoteServer = iniFile.iniGetObject("REMOTE_SERVER");
settings.server = iniFile.iniGetObject("SERVER");
// Ensure the actual expected setting name & color names exist in the settings // Ensure the actual expected setting name & color names exist in the settings
if (typeof(settings.behavior) !== "object") if (typeof(settings.behavior) !== "object")
...@@ -394,6 +510,8 @@ function loadSettings(pStartupPath) ...@@ -394,6 +510,8 @@ function loadSettings(pStartupPath)
settings.category_ars = {}; settings.category_ars = {};
if (typeof(settings.remoteServer) !== "object") if (typeof(settings.remoteServer) !== "object")
settings.remoteServer = {}; settings.remoteServer = {};
if (typeof(settings.server) !== "object")
settings.server = {};
if (typeof(settings.behavior.numQuestionsPerPlay) !== "number") if (typeof(settings.behavior.numQuestionsPerPlay) !== "number")
settings.behavior.numQuestionsPerPlay = 10; settings.behavior.numQuestionsPerPlay = 10;
...@@ -441,6 +559,9 @@ function loadSettings(pStartupPath) ...@@ -441,6 +559,9 @@ function loadSettings(pStartupPath)
if (typeof(settings.colors.answerAfterIncorrect) !== "string") if (typeof(settings.colors.answerAfterIncorrect) !== "string")
settings.colors.answerAfterIncorrect = "G"; settings.colors.answerAfterIncorrect = "G";
settings.behavior.scoresMsgSubBoardsForPosting = splitAndVerifyMsgSubCodes(settings.behavior.scoresMsgSubBoardsForPosting, "scoresMsgSubBoardsForPosting");
settings.server.scoresMsgSubBoardsForReading = splitAndVerifyMsgSubCodes(settings.server.scoresMsgSubBoardsForReading, "scoresMsgSubBoardsForReading");
// Sanity checking // Sanity checking
if (settings.behavior.numQuestionsPerPlay <= 0) if (settings.behavior.numQuestionsPerPlay <= 0)
settings.behavior.numQuestionsPerPlay = 10; settings.behavior.numQuestionsPerPlay = 10;
...@@ -465,11 +586,7 @@ function loadSettings(pStartupPath) ...@@ -465,11 +586,7 @@ function loadSettings(pStartupPath)
settings.remoteServer.gtTriviaScope = "GTTRIVIA"; settings.remoteServer.gtTriviaScope = "GTTRIVIA";
// JSON location: For the BBS name, use the QWK ID if available, but if not, use the system name and replace spaces // JSON location: For the BBS name, use the QWK ID if available, but if not, use the system name and replace spaces
// with underscores (since spaces may cause issues in JSON property names) // with underscores (since spaces may cause issues in JSON property names)
var BBS_ID = ""; var BBS_ID = getBBSIDForJSON();
if (system.qwk_id.length > 0)
BBS_ID = system.qwk_id;
else
BBS_ID = system.name.replace(/ /g, "_");
settings.remoteServer.scoresJSONLocation = "SCORES"; settings.remoteServer.scoresJSONLocation = "SCORES";
settings.remoteServer.BBSJSONLocation = settings.remoteServer.scoresJSONLocation + ".systems." + BBS_ID; settings.remoteServer.BBSJSONLocation = settings.remoteServer.scoresJSONLocation + ".systems." + BBS_ID;
settings.remoteServer.userScoresJSONLocationWithoutUsername = settings.remoteServer.BBSJSONLocation + ".user_scores"; settings.remoteServer.userScoresJSONLocationWithoutUsername = settings.remoteServer.BBSJSONLocation + ".user_scores";
...@@ -505,6 +622,39 @@ function genFullPathCfgFilename(pFilename, pDefaultPath) ...@@ -505,6 +622,39 @@ function genFullPathCfgFilename(pFilename, pDefaultPath)
} }
return fullyPathedFilename; return fullyPathedFilename;
} }
// Takes a comma-separated list of internal sub-board codes and splits them into an array,
// and also verifies they exist; the returned array will contain only ones that exist.
// Also ensures there are no duplicates in the array.
//
// Parameters:
// pSubCodeList: A comma-separated list of message sub-board codes (string)
// pSettingName: Optional string representing the configuration setting name, for logging
// invalid sub-board codes. If this is missing/null or empty, no logging will be done.
function splitAndVerifyMsgSubCodes(pSubCodeList, pSettingName)
{
if (typeof(pSubCodeList) !== "string")
return [];
var settingName = (typeof(pSettingName) === "string" ? pSettingName : "");
var subCodes = [];
var subCodesFromList = pSubCodeList.split(",");
for (var i = 0; i < subCodesFromList.length; ++i)
{
if (msg_area.sub.hasOwnProperty(subCodesFromList[i]))
{
if (!subCodes.indexOf(subCodesFromList[i]) > -1)
subCodes.push(subCodesFromList[i]);
}
else if (settingName.length > 0)
{
var errMsg = format("%s - For configuration setting %s, %s is an invalid sub-board code", GAME_NAME, settingName, subCodesFromList[i]);
log(LOG_ERR, errMsg);
}
}
//!msg_area.sub.hasOwnProperty(
return subCodes;
}
// Displays the program logo // Displays the program logo
// //
...@@ -814,10 +964,19 @@ function levenshteinDistance(pStr1, pStr2) ...@@ -814,10 +964,19 @@ function levenshteinDistance(pStr1, pStr2)
// Parameters: // Parameters:
// pUserCurrentGameScore: The user's score for their current game // pUserCurrentGameScore: The user's score for their current game
// pLastSectionName: The name of the last trivia section the user played // pLastSectionName: The name of the last trivia section the user played
//
// Return value: An object with the following properties:
// succeeded: Boolean: Whether or not saving the scores to the file succeeded
// userScoresObj: An object containing information on the user's scores
function updateScoresFile(pUserCurrentGameScore, pLastSectionName) function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
{ {
var retObj = {
succeeded: false,
userScoresObj: {}
};
if (typeof(pUserCurrentGameScore) !== "number") if (typeof(pUserCurrentGameScore) !== "number")
return false; return retObj;
var lastSectionName = (typeof(pLastSectionName) === "string" ? pLastSectionName : ""); var lastSectionName = (typeof(pLastSectionName) === "string" ? pLastSectionName : "");
...@@ -858,8 +1017,7 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -858,8 +1017,7 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
if (typeof(scoresObj) !== "object") if (typeof(scoresObj) !== "object")
scoresObj = {}; scoresObj = {};
var scoresForUser = {}; // Will store just the current user's score information retObj.succeeded = true;
// Add/update the user's score, and save the scores file // Add/update the user's score, and save the scores file
try try
{ {
...@@ -939,10 +1097,11 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -939,10 +1097,11 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
scoresObj[user.alias].last_score = pUserCurrentGameScore; scoresObj[user.alias].last_score = pUserCurrentGameScore;
scoresObj[user.alias].last_trivia_category = lastSectionName; scoresObj[user.alias].last_trivia_category = lastSectionName;
scoresObj[user.alias].last_time = currentTime; scoresObj[user.alias].last_time = currentTime;
scoresForUser = scoresObj[user.alias]; retObj.userScoresObj = scoresObj[user.alias];
} }
catch (error) catch (error)
{ {
retObj.succeeded = false;
console.print("* Line " + error.lineNumber + ": " + error); console.print("* Line " + error.lineNumber + ": " + error);
console.crlf(); console.crlf();
log(LOG_ERR, GAME_NAME + " - Updating trivia score object: Line " + error.lineNumber + ": " + error); log(LOG_ERR, GAME_NAME + " - Updating trivia score object: Line " + error.lineNumber + ": " + error);
...@@ -954,13 +1113,13 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -954,13 +1113,13 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
scoresFile.write(JSON.stringify(scoresObj)); scoresFile.write(JSON.stringify(scoresObj));
scoresFile.close(); scoresFile.close();
} }
else
retObj.succeeded = false;
// Delete the semaphore file // Delete the semaphore file
file_remove(SCORES_SEMAPHORE_FILENAME); file_remove(SCORES_SEMAPHORE_FILENAME);
// If there is a server configured, then send the user's score to the server too return retObj;
if (gSettings.hasValidServerSettings())
updateScoresOnServer(user.alias, scoresForUser);
} }
// Updates user scores on the server (if there is one configured) // Updates user scores on the server (if there is one configured)
...@@ -968,35 +1127,54 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName) ...@@ -968,35 +1127,54 @@ function updateScoresFile(pUserCurrentGameScore, pLastSectionName)
// Parameters: // Parameters:
// pUserNameForScores: The user's name as used for the scores // pUserNameForScores: The user's name as used for the scores
// pUserScoreInfo: An object containing user scores, as created by updateScoresFile() // pUserScoreInfo: An object containing user scores, as created by updateScoresFile()
//
// Return value: Boolean: Whether or not the update was successful
function updateScoresOnServer(pUserNameForScores, pUserScoreInfo) function updateScoresOnServer(pUserNameForScores, pUserScoreInfo)
{ {
// Make sure the settings have valid server settings and the user score info object is valid // Make sure the settings have valid server settings and the user score info object is valid
if (!gSettings.hasValidServerSettings()) if (!gSettings.hasValidServerSettings())
return; return false;
if (typeof(pUserNameForScores) !== "string" || pUserNameForScores.length == 0 || typeof(pUserScoreInfo) !== "object") if (typeof(pUserNameForScores) !== "string" || pUserNameForScores.length == 0 || typeof(pUserScoreInfo) !== "object")
return; return false;
var updateSuccessful = true;
try try
{ {
// You could lock for each individual write like this:
//
// var JSONLocation = gSettings.remoteServer.BBSJSONLocation + ".bbs_name";
// jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, system.name, JSON_DB_LOCK_WRITE);
//
// You can also call lock() to lock the JSON location you want to use, do your reads & writes, and then
// unlock at the end. The code here locks on the BBS ID JSON location and does its writes, so that
// readGTTriviaScoresFromSubBoard() can also lock on the same location to do its writes when importing
// scores from the messagebase.
var jsonClient = new JSONClient(gSettings.remoteServer.server, gSettings.remoteServer.port); var jsonClient = new JSONClient(gSettings.remoteServer.server, gSettings.remoteServer.port);
jsonClient.lock(gSettings.remoteServer.gtTriviaScope, gSettings.remoteServer.BBSJSONLocation, JSON_DB_LOCK_WRITE);
// Ensure the BBS name on the server has been set // Ensure the BBS name on the server has been set
var JSONLocation = gSettings.remoteServer.BBSJSONLocation + ".bbs_name"; var JSONLocation = gSettings.remoteServer.BBSJSONLocation + ".bbs_name";
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, system.name, JSON_DB_LOCK_WRITE); jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, system.name);
// Write the scores on the server // Write the scores on the server
JSONLocation = gSettings.remoteServer.userScoresJSONLocationWithoutUsername + "." + pUserNameForScores; JSONLocation = gSettings.remoteServer.userScoresJSONLocationWithoutUsername + "." + pUserNameForScores;
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, pUserScoreInfo, JSON_DB_LOCK_WRITE); jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, pUserScoreInfo);
// Write the client & version information in the user scores too // Write the client & version information in the user scores too
var gameInfo = format("%s version %s (%s)", GAME_NAME, GAME_VERSION, GAME_VER_DATE); var gameInfo = format("%s version %s (%s)", GAME_NAME, GAME_VERSION, GAME_VER_DATE);
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation + ".game_client", gameInfo, JSON_DB_LOCK_WRITE); JSONLocation += ".game_client";
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, gameInfo);
// Now that we're done, unlock and disconnect
jsonClient.unlock(gSettings.remoteServer.gtTriviaScope, gSettings.remoteServer.BBSJSONLocation);
jsonClient.disconnect(); jsonClient.disconnect();
} }
catch (error) catch (error)
{ {
updateSuccessful = false;
console.print("* Line " + error.lineNumber + ": " + error); console.print("* Line " + error.lineNumber + ": " + error);
console.crlf(); console.crlf();
log(LOG_ERR, GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error); log(LOG_ERR, GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error); bbs.log_str(GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error);
} }
return updateSuccessful;
} }
// Shows the saved scores - First the locally saved scores, and then if there is a // Shows the saved scores - First the locally saved scores, and then if there is a
...@@ -1125,7 +1303,7 @@ function showServerScores() ...@@ -1125,7 +1303,7 @@ function showServerScores()
showUserScoresArray(sortedScores, data.systems[BBS_ID].bbs_name); showUserScoresArray(sortedScores, data.systems[BBS_ID].bbs_name);
// If debugging is enabled, then also show the game_client property (game_client stores the name // If debugging is enabled, then also show the game_client property (game_client stores the name
// & version of the game that wrote the user score data for this player) // & version of the game that wrote the user score data for this player)
if (gDebug) if (gCmdLineArgs.debug)
{ {
if (data.systems[BBS_ID].user_scores[playerName].hasOwnProperty("game_client")) if (data.systems[BBS_ID].user_scores[playerName].hasOwnProperty("game_client"))
{ {
...@@ -1165,7 +1343,7 @@ function showUserScoresArray(pUserScoresArray, pBBSName) ...@@ -1165,7 +1343,7 @@ function showUserScoresArray(pUserScoresArray, pBBSName)
// Make the format string for printf() // Make the format string for printf()
var scoreWidth = 6; var scoreWidth = 6;
var dateWidth = 10; var dateWidth = 10;
var categoryWidth = 15; var categoryWidth = 25; //15;
var nameWidth = 0; var nameWidth = 0;
var formatStr = ""; var formatStr = "";
if (console.screen_columns >= 80) if (console.screen_columns >= 80)
...@@ -1559,3 +1737,294 @@ function doSysopMenu() ...@@ -1559,3 +1737,294 @@ function doSysopMenu()
} }
} }
} }
// Returns a BBS ID to use for JSON (the QWK ID if existing; otherwise, the BBS name with
// spaces converted to underscores)
function getBBSIDForJSON()
{
var BBS_ID = "";
if (system.qwk_id.length > 0)
BBS_ID = system.qwk_id;
else
BBS_ID = system.name.replace(/ /g, "_");
return BBS_ID;
}
// Posts all users' scores from the local scores file to a message sub-board
//
// Parameters:
// pSubCode: The internal code of the sub-board to post the scores to
function postGTTriviaScoresToSubBoard(pSubCode)
{
if (typeof(pSubCode) !== "string" || !msg_area.sub.hasOwnProperty(pSubCode))
return false;
// Prepare the user scores for posting in the message sub-board
// JSON location: For the BBS name, use the QWK ID if available, but if not, use the system name and replace spaces
// with underscores (since spaces may cause issues in JSON property names)
var BBS_ID = getBBSIDForJSON();
var scoresForThisBBS = {};
scoresForThisBBS[BBS_ID] = {};
scoresForThisBBS[BBS_ID].bbs_name = system.name;
scoresForThisBBS[BBS_ID].user_scores = {};
// Read the scores file to see if the user has an existing score in there already
var scoresFile = new File(SCORES_FILENAME);
if (file_exists(SCORES_FILENAME))
{
if (scoresFile.open("r"))
{
var scoreFileArray = scoresFile.readAll();
scoresFile.close();
var scoreFileContents = "";
for (var i = 0; i < scoreFileArray.length; ++i)
scoreFileContents += (scoreFileArray[i] + "\n");
try
{
scoresForThisBBS[BBS_ID].user_scores = JSON.parse(scoreFileContents);
}
catch (error)
{
scoresForThisBBS[BBS_ID].user_scores = {};
log(LOG_ERR, GAME_NAME + " - Loading scores: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Loading scores: Line " + error.lineNumber + ": " + error);
}
}
}
if (typeof(scoresForThisBBS[BBS_ID]) !== "object")
scoresForThisBBS[BBS_ID].user_scores = {};
if (Object.keys(scoresForThisBBS[BBS_ID].user_scores).length === 0)
return false;
var postSuccessful = false;
var dataMsgbase = new MsgBase(pSubCode);
if (dataMsgbase.open())
{
// Create the message header, and send the message.
var header = {
to: GAME_NAME, // "Good Time Trivia"
from: system.username(1),
from_ext: 1,
subject: system.name
//from_net_type: NET_NONE,
//to_net_type: NET_NONE
};
/*
if ((dataMsgbase.settings & SUB_QNET) == SUB_QNET)
{
header.from_net_type = NET_QWK;
header.to_net_type = NET_QWK;
}
else if ((dataMsgbase.settings & SUB_PNET) == SUB_PNET)
{
header.from_net_type = NET_POSTLINK;
header.to_net_type = NET_POSTLINK;
}
else if ((dataMsgbase.settings & SUB_FIDO) == SUB_FIDO)
{
header.from_net_type = NET_FIDO;
header.to_net_type = NET_FIDO;
}
else if ((dataMsgbase.settings & SUB_INET) == SUB_INET)
{
header.from_net_type = NET_INTERNET;
header.to_net_type = NET_INTERNET;
}
*/
//postSuccessful = dataMsgbase.save_msg(header, JSON.stringify(scoresForThisBBS));
var message = lfexpand(JSON.stringify(scoresForThisBBS, null, 1));
message += " --- " + GAME_NAME + " " + GAME_VERSION + " (" + GAME_VER_DATE + ")";
postSuccessful = dataMsgbase.save_msg(header, message);
dataMsgbase.close();
}
return postSuccessful;
}
// Reads trivia scores from a sub-board and posts on the host system (if configured)
//
// Parameters:
// pSubCode: An internal code of a sub-board to read the game scores from
//
// Return value: Boolean - Whether or not the score update succeeded
function readGTTriviaScoresFromSubBoard(pSubCode)
{
if (typeof(pSubCode) !== "string" || !msg_area.sub.hasOwnProperty(pSubCode))
return false;
// For logging
var subBoardInfoStr = msg_area.sub[subCode].name + " - " + msg_area.sub[subCode].description;
log(LOG_INFO, format("%s - Reading score posts from sub-board %s (%s)", GAME_NAME, pSubCode, subBoardInfoStr));
// For posting to the local JSON server, get the configured JSON service port number
var localJSONServicePort = getJSONSvcPortFromServicesIni();
if (localJSONServicePort <= 0)
{
log(LOG_ERR, format("%s - Local JSON service port is invalid (%d)", GAME_NAME, localJSONServicePort));
return false;
}
var scoreUpdateSucceeded = true;
var dataMsgbase = new MsgBase(pSubCode);
if (dataMsgbase.open())
{
try
{
// Create the JSON Client object for updating the scores on the local JSON DB server.
// For each user score in the JSON object, if their last time is after the current last
// time in the server's JSON, then post the user's score.
var jsonClient = new JSONClient("127.0.0.1", localJSONServicePort);
var to_crc = crc16_calc(GAME_NAME.toLowerCase());
var index = dataMsgbase.get_index();
for (var i = 0; index && i < index.length; i++)
{
var idx = index[i];
if ((idx.attr & MSG_DELETE) == MSG_DELETE || idx.to != to_crc)
continue;
var msgHdr = dataMsgbase.get_msg_header(true, idx.offset);
if (!msgHdr)
continue;
if (/*!msgHdr.from_net_type ||*/ msgHdr.to != GAME_NAME)
continue;
var msgBody = dataMsgbase.get_msg_body(msgHdr, false, false, false);
if (msgBody == null || msgBody.length == 0) //if (!msgBody)
continue;
log(LOG_INFO, "Scores message imported at " + strftime("%Y-%m-%d %H:%M:%S", msgHdr.when_imported_time));
// Clean up the message body so that it only has JSON
var txtIdx = msgBody.indexOf(" --- " + GAME_NAME);
if (txtIdx > 0)
msgBody = msgBody.substr(0, txtIdx);
// Parse the JSON from the message, and then go through the BBSes and users
// in it. For any user scores that are more recent than what's on the server,
// post those to the server.
try
{
var scoresObjFromMsg = JSON.parse(msgBody);
// For each user score, if their last time is after the current last time in the
// server's JSON, then post the user's score.
for (var BBS_ID in scoresObjFromMsg)
{
// Lock on the BBS name location in the JSON, do the writes, and unlock when we're done
jsonClient.lock(gSettings.remoteServer.gtTriviaScope, gSettings.remoteServer.BBSJSONLocation, JSON_DB_LOCK_WRITE);
// Ensure the BBS name on the server has been set
var JSONLocation = gSettings.remoteServer.scoresJSONLocation + ".systems." + BBS_ID + ".bbs_name";
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, system.name);
for (var userID in scoresObjFromMsg[BBS_ID].user_scores)
{
// For logging
var msgLastTimeFormatted = strftime("%Y-%m-%d %H:%M:%S", scoresObjFromMsg[BBS_ID].user_scores[userID].last_time);
var serverLastTimeFormatted = "";
// Read the current user's scores from the server and compare the user's last_time
// from the message in the sub-board with the one from the server, and only update
// if newer.
JSONLocation = gSettings.remoteServer.scoresJSONLocation + ".systems." + BBS_ID + ".user_scores." + userID;
var serverUserScoreData = jsonClient.read(gSettings.remoteServer.gtTriviaScope, JSONLocation);
var postUserScoresToServer = false;
if (typeof(serverUserScoreData) === "object")
{
postUserScoresToServer = (scoresObjFromMsg[BBS_ID].user_scores[userID].last_time > serverUserScoreData.last_time);
serverLastTimeFormatted = strftime("%Y-%m-%d %H:%M:%S", serverUserScoreData.last_time)
}
else
{
postUserScoresToServer = true;
serverLastTimeFormatted = "N/A";
}
// Log the user, BBS, and date of the scores seen
var logMsg = format("%s - Saw scores for %s on %s; in message: %s, on server: %s; will update server scores: %s",
GAME_NAME, userID, scoresObjFromMsg[BBS_ID].bbs_name, msgLastTimeFormatted, serverLastTimeFormatted,
postUserScoresToServer);
log(LOG_INFO, logMsg);
// If the scores from the message are newer, write the scores on the server
if (postUserScoresToServer)
{
JSONLocation = gSettings.remoteServer.scoresJSONLocation + ".systems." + BBS_ID + ".user_scores." + userID;
jsonClient.write(gSettings.remoteServer.gtTriviaScope, JSONLocation, scoresObjFromMsg[BBS_ID].user_scores[userID]);
}
// Now that we've written the user scores, unlock this BBS in the JSON
jsonClient.unlock(gSettings.remoteServer.gtTriviaScope, gSettings.remoteServer.BBSJSONLocation);
}
}
}
catch (error)
{
scoreUpdateSucceeded = false;
console.print("* Line " + error.lineNumber + ": " + error);
console.crlf();
log(LOG_ERR, GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Updating scores on server: Line " + error.lineNumber + ": " + error);
}
}
jsonClient.disconnect();
}
catch (error)
{
scoreUpdateSucceeded = false;
console.print("* Line " + error.lineNumber + ": " + error);
console.crlf();
log(LOG_ERR, GAME_NAME + " - Connecting to JSON DB server (for scores update): Line " + error.lineNumber + ": " + error);
bbs.log_str(GAME_NAME + " - Connecting to JSON DB server (for scores update): Line " + error.lineNumber + ": " + error);
}
dataMsgbase.close();
}
else
{
var errMsg = format("%s - Unable to open sub-board %s (%s)", GAME_NAME, pSubCode, subBoardInfoStr);
log(LOG_ERR, errMsg);
}
log(LOG_INFO, format("%s - End of reading score posts from sub-board %s (%s). All succeeded: %s", GAME_NAME, pSubCode,
subBoardInfoStr, scoreUpdateSucceeded));
return scoreUpdateSucceeded;
}
// Parses command-line arguments. Returns an object with settings/actions specified.
//
// Parameters:
// argv: The array of command-line arguments
//
// Return value: An object with the following properties:
// debug: Boolean: Whether or not to enable debugging
// postScoresToSubBoard: Boolean: Whether or not to post scores in the configured sub-board. If this is
// enabled, scores are to be posted and then the script should exit.
// readScoresFromSubBoard: Boolean: Whether or not to read scores from the configured sub-board.
// If this is enabled, scores are to be posted and then the script should exit.
function parseCmdLineArgs(argv)
{
var retObj = {
debug: false,
postScoresToSubBoard: false,
readScoresFromSubBoard: false
};
if (!Array.isArray(argv))
return retObj;
var postScoresToSubOpt = "POST_SCORES_TO_SUBBOARD";
var readScoresFromSubOpt = "READ_SCORES_FROM_SUBBOARD";
for (var i = 0; i < argv.length; ++i)
{
var argUpper = argv[i].toUpperCase();
if (argUpper == "DEBUG" || argUpper == "-DEBUG" || argUpper == "--DEBUG")
retObj.debug = true;
else if (argUpper == postScoresToSubOpt || argUpper == "-" + postScoresToSubOpt || argUpper == "--" + postScoresToSubOpt)
retObj.postScoresToSubBoard = true;
else if (argUpper == readScoresFromSubOpt || argUpper == "-" + readScoresFromSubOpt || argUpper == "--" + readScoresFromSubOpt)
retObj.readScoresFromSubBoard = true;
}
return retObj;
}
require("sbbsdefs.js", "K_NOCRLF"); require("sbbsdefs.js", "K_NOCRLF");
var inputFilename = "/mnt/data/SharedMedia/triviaQuestions/music_and_movies.txt"; var opts = parseCmdLine(argv);
var outputFilename = inputFilename + "-converted.txt";
print(""); print("");
print("Converting " + inputFilename);
if (opts.inputFilename.length == 0)
{
print("No input filename was specified.");
print("");
exit(1);
}
if (!file_exists(opts.inputFilename))
{
print("Specified file does not exist:");
print(opts.inputFilename);
print("");
exit(2);
}
print("Input filename:" + opts.inputFilename + ":");
print("");
var outputFilename = opts.inputFilename + "-converted.txt";
print("");
print("Converting " + opts.inputFilename);
print("Output: " + outputFilename); print("Output: " + outputFilename);
print(""); print("");
var inFile = new File(inputFilename); var inFile = new File(opts.inputFilename);
var outFile = new File(outputFilename); var outFile = new File(outputFilename);
if (inFile.open("r")) if (inFile.open("r"))
{ {
...@@ -112,7 +133,10 @@ if (inFile.open("r")) ...@@ -112,7 +133,10 @@ if (inFile.open("r"))
inFile.close(); inFile.close();
} }
else else
print("* Failed to open " + inputFilename + " for reading!"); {
print("* Failed to open " + opts.inputFilename + " for reading!");
exit(3);
}
...@@ -128,3 +152,34 @@ function QA(pQuestion, pAnswer, pNumPoints) ...@@ -128,3 +152,34 @@ function QA(pQuestion, pAnswer, pNumPoints)
this.answer = pAnswer; this.answer = pAnswer;
this.numPoints = pNumPoints; this.numPoints = pNumPoints;
} }
// Parses command line options
function parseCmdLine(argv)
{
var retObj = {
inputFilename: ""
};
if (!Array.isArray(argv))
return retObj;
for (var i = 0; i < argv.length; ++i)
{
if (argv[i].length == 0) continue;
if (argv[i].charAt(0) == "-")
{
if (i >= argv.length - 1) continue;
var paramNameUpper = argv[i].substr(1).toUpperCase();
if (paramNameUpper == "INPUTFILENAME" || paramNameUpper == "INPUT_FILENAME")
retObj.inputFilename = argv[i+1];
++i; // To avoid analyzing the next parameter, since the next one is the value for this one
}
else
{
if (i == 0)
retObj.inputFilename = argv[i];
}
}
return retObj;
}
\ No newline at end of file
...@@ -174,7 +174,7 @@ What is often seen as the smallest unit of memory? ...@@ -174,7 +174,7 @@ What is often seen as the smallest unit of memory?
Kilobyte Kilobyte
10 10
Which planet is the hottest in the solar system? Which planet is the hottest in Earth's solar system?
Venus Venus
10 10
...@@ -614,10 +614,6 @@ What was the first Disney animated film based on the life of a real person? ...@@ -614,10 +614,6 @@ What was the first Disney animated film based on the life of a real person?
Pocahontas Pocahontas
10 10
What character did Michael J. Fox play in 'Back to the Future'?
Marty McFly
10
What was the predecessor to the United Nations? What was the predecessor to the United Nations?
League of Nations League of Nations
10 10
...@@ -842,10 +838,6 @@ What grows from an acorn? ...@@ -842,10 +838,6 @@ What grows from an acorn?
Oak Tree Oak Tree
10 10
What prison film starring Tim Robbins was based on a story by Stephen King?
The Shawshank Redemption
10
Which U.S. state has "Garden State" as its nickname? Which U.S. state has "Garden State" as its nickname?
New Jersey New Jersey
10 10
...@@ -966,10 +958,6 @@ Rihanna banned fans from bringing what items to her U.K. concerts in 2008? ...@@ -966,10 +958,6 @@ Rihanna banned fans from bringing what items to her U.K. concerts in 2008?
Umbrellas Umbrellas
10 10
Who created the alien rock superstar Ziggy Stardust?
David Bowie
10
Which young girl helped drive the English from French soil in the 15th century? Which young girl helped drive the English from French soil in the 15th century?
Joan of Arc Joan of Arc
10 10
...@@ -979,7 +967,7 @@ Theodore Roosevelt ...@@ -979,7 +967,7 @@ Theodore Roosevelt
10 10
What is the biggest supermarket chain in the U.S.? What is the biggest supermarket chain in the U.S.?
Kroger Co. Kroger
10 10
On every continent there is a city named what? On every continent there is a city named what?
...@@ -1511,7 +1499,7 @@ Sense and Sensibility ...@@ -1511,7 +1499,7 @@ Sense and Sensibility
10 10
Which two countries have the longest shared international border? Which two countries have the longest shared international border?
Canada and the U.S. Canada and the US
10 10
What city hosted the 2014 Winter Olympics? What city hosted the 2014 Winter Olympics?
...@@ -1535,7 +1523,7 @@ Three ...@@ -1535,7 +1523,7 @@ Three
10 10
How many bones do sharks have? How many bones do sharks have?
Zero! 0
10 10
What is the deadliest mammal? What is the deadliest mammal?
...@@ -1612,7 +1600,7 @@ Des Moines ...@@ -1612,7 +1600,7 @@ Des Moines
What is the most commonly spoken language in Brazil? What is the most commonly spoken language in Brazil?
Portuguese Portuguese
10 5
In what country do more than half of people believe in elves? In what country do more than half of people believe in elves?
Iceland Iceland
...@@ -1706,8 +1694,8 @@ Where is Harvard University located? ...@@ -1706,8 +1694,8 @@ Where is Harvard University located?
Cambridge, Massachusetts Cambridge, Massachusetts
10 10
Which marine animals hold hands in their sleep to prevent drifting apart? Which marine animal species hold hands in their sleep to prevent drifting apart?
Sea otters, and my system is unable to process that level of cuteness. Sea otter
10 10
What famous document begins: "When in the course of human events..."? What famous document begins: "When in the course of human events..."?
...@@ -1853,3 +1841,183 @@ Washington Monument ...@@ -1853,3 +1841,183 @@ Washington Monument
Where is Mount Rushmore (City, State)? Where is Mount Rushmore (City, State)?
Keystone, South Dakota Keystone, South Dakota
5 5
What country has the highest life expectancy?
Hong Kong
10
Where would you be if you were standing on the Spanish Steps?
Rome
10
Which language has the more native speakers: English or Spanish?
Spanish
10
What is the most common surname in the United States?
Smith
10
What disease commonly spread on pirate ships?
Scurvy
10
Who was the Ancient Greek God of the Sun?
Apollo
10
What was the name of the crime boss who was head of the feared Chicago Outfit?
Al Capone
10
What year was the United Nations established?
1945
10
Who has won the most total Academy Awards?
Walt Disney
10
What artist has the most streams on Spotify?
Drake
10
How many minutes are in a full week?
10080
10
What car manufacturer had the highest revenue in 2020?
Volkswagen
10
How many elements are in the periodic table?
118
10
What company was originally called "Cadabra"?
Amazon
10
How many faces does a Dodecahedron have?
12
10
Queen guitarist Brian May is also an expert in what scientific field?
Astrophysics
10
Aureolin is a shade of what color?
Yellow
10
How many ghosts chase Pac-Man at the start of each game?
4
10
What Renaissance artist is buried in Rome's Pantheon?
Raphael
10
What shoe brand makes the "Mexico 66"?
Onitsuka Tiger
15
What game studio makes the Red Dead Redemption series?
Rockstar Games
10
Who was the last Tsar of Russia?
Nicholas II
10
What country drinks the most coffee per capita?
Finland
10
What is the 4th letter of the Greek alphabet?
Delta
5
What sports car company manufactures the 911?
Porsche
2
What city is known as "The Eternal City"?
Rome
10
The first person to reach the South Pole was Roald Amundsen. Where was he from?
Norway
10
Who discovered that the earth revolves around the sun (last name)?
Copernicus
10
What company was initially known as "Blue Ribbon Sports"?
Nike
10
What art form is described as "decorative handwriting or handwritten lettering"?
Calligraphy
10
Which planet in Earth's solar system has the most moons?
Saturn
10
What country has won the most World Cups?
Brazil
10
Kratos is the main character of what video game series?
God of War
10
In what country would you find Mount Kilimanjaro?
Tanzania
10
A group of pandas is known as a what?
Embarrassment
10
What European country experienced the highest rate of population decline from 2015 - 2020?
Lithuania
10
How many bones do we have in an ear?
3
10
Who famously crossed the Alps with elephants on the way to war with the Romans?
Hannibal
10
True or False: Halloween originated as an ancient Irish festival.
True
5
What Netflix show had the most streaming views in 2021?
Squid Game
10
What software company is headquartered in Redmond, Washington?
Microsoft
2
What is the largest Spanish-speaking city in the world?
Mexico City
10
What is the world's fastest bird?
Peregrine Falcon
10
In what country is the Chernobyl nuclear plant located?
Ukraine
10
The Parthenon Marbles are controversially located in what museum?
British Museum
10
...@@ -510,7 +510,7 @@ What prominent American director won an Oscar for helming Forrest Gump? ...@@ -510,7 +510,7 @@ What prominent American director won an Oscar for helming Forrest Gump?
Robert Zemeckis Robert Zemeckis
10 10
Three of Jim Carrey's blockbusters¿The Mask, Dumb and Dumber and Ace Ventura: Pet Detective¿were all released in what year? Three of Jim Carrey's blockbusters The Mask, Dumb and Dumber and Ace Ventura: Pet Detective were all released in what year?
1994 1994
10 10
...@@ -630,7 +630,7 @@ What was the first pandemic era movie to gross over $1 billion at the box office ...@@ -630,7 +630,7 @@ What was the first pandemic era movie to gross over $1 billion at the box office
Spider-Man: No Way Home Spider-Man: No Way Home
10 10
Who is the only actor to appear in Robert Wise¿s 1961 West Side Story movie and the 2021 remake? Who is the only actor to appear in Robert Wise's 1961 West Side Story movie and the 2021 remake?
Rita Moreno Rita Moreno
10 10
...@@ -642,7 +642,7 @@ What internationally esteemed Malaysian actress has starred in a Bond film, Crou ...@@ -642,7 +642,7 @@ What internationally esteemed Malaysian actress has starred in a Bond film, Crou
Michelle Yeoh Michelle Yeoh
10 10
Who won his second Best Actor Oscar in 2021, in the ceremony¿s biggest upset? Who won his second Best Actor Oscar in 2021, in the ceremony's biggest upset?
Anthony Hopkins Anthony Hopkins
10 10
...@@ -650,11 +650,11 @@ What indie horror movie boogeyman became an unexpected LGBTQ+ icon of the 21st c ...@@ -650,11 +650,11 @@ What indie horror movie boogeyman became an unexpected LGBTQ+ icon of the 21st c
The Babadook The Babadook
10 10
What kind of bug is on the back of Ryan Gosling¿s silk jacket in Drive? What kind of bug is on the back of Ryan Gosling's silk jacket in Drive?
Scorpion Scorpion
10 10
What actress was the queen of 1970s ¿Blaxploitation¿ cinema? What actress was the queen of 1970s "Blaxploitation" cinema?
Pam Grier Pam Grier
10 10
...@@ -666,15 +666,15 @@ What is the second (and last) fantasy movie to win Best Picture at the Oscars? ...@@ -666,15 +666,15 @@ What is the second (and last) fantasy movie to win Best Picture at the Oscars?
The Shape of Water The Shape of Water
10 10
What famous heartthrob is unrecognizable under layers of makeup as The Penguin in 2021¿s The Batman? What famous heartthrob is unrecognizable under layers of makeup as The Penguin in 2021s The Batman?
Colin Farrell Colin Farrell
10 10
In Clueless, what character said, ¿You¿re a virgin who can¿t drive?¿ In Clueless, what character said, "You're a virgin who can't drive"?
Tai Tai
10 10
What is the name of the love interest whose ¿hair looks sexy pushed back¿ in Mean Girls? What is the name of the love interest whose hair looks sexy pushed back in Mean Girls?
Aaron Samuels Aaron Samuels
10 10
...@@ -682,7 +682,7 @@ What highly acclaimed Richard Linklater drama was filmed over and produced over ...@@ -682,7 +682,7 @@ What highly acclaimed Richard Linklater drama was filmed over and produced over
Boyhood Boyhood
10 10
What is the name of Humperdinck¿s kingdom in The Princess Bride? What is the name of Humperdinck's kingdom in The Princess Bride?
Florin Florin
10 10
...@@ -704,7 +704,7 @@ What critically maligned 2004 superhero film co-stars Sharon Stone as an evil co ...@@ -704,7 +704,7 @@ What critically maligned 2004 superhero film co-stars Sharon Stone as an evil co
Catwoman Catwoman
10 10
Which actress replaced Rachel Weisz as Evelyn O¿Connor in The Mummy: Tomb of the Dragon Emperor? Which actress replaced Rachel Weisz as Evelyn O'Connor in The Mummy: Tomb of the Dragon Emperor?
Maria Bello Maria Bello
10 10
...@@ -716,11 +716,11 @@ Who plays Duncan Idaho in Dune (2021)? ...@@ -716,11 +716,11 @@ Who plays Duncan Idaho in Dune (2021)?
Jason Momoa Jason Momoa
10 10
Dakota Johnson dropped out of Olivia Wilde¿s sophomore feature Don¿t Worry Darling to appear in what critically acclaimed 2021 drama? Dakota Johnson dropped out of Olivia Wilde's sophomore feature Don't Worry Darling to appear in what critically acclaimed 2021 drama?
The Lost Daughter The Lost Daughter
10 10
What legendary pop star judges a fashion ¿walk-off¿ between Ben Stiller and Owen Wilson in Zoolander? What legendary pop star judges a fashion "walk-off" between Ben Stiller and Owen Wilson in Zoolander?
David Bowie David Bowie
10 10
...@@ -1182,3 +1182,27 @@ Lady Gaga ...@@ -1182,3 +1182,27 @@ Lady Gaga
What type of music has been shown to help plants grow better and faster? What type of music has been shown to help plants grow better and faster?
Classical Classical
10 10
What character has both Robert Downey Jr. and Benedict Cumberbatch played?
Sherlock Holmes
10
What prison film starring Tim Robbins was based on a story by Stephen King?
The Shawshank Redemption
10
What is the highest-rated film on IMDb as of January 1st, 2022?
The Shawshank Redemption
10
Which grammy-nominated New York rapper died in April of 2021?
DMX
10
Who created the alien rock superstar Ziggy Stardust?
David Bowie
10
What character did Michael J. Fox play in 'Back to the Future'?
Marty McFly
10
Good Time Trivia Good Time Trivia
Version 1.01 Version 1.02
Release date: 2022-11-25 Release date: 2022-12-08
by by
...@@ -57,10 +57,6 @@ scores. This will delete the scores.json file. ...@@ -57,10 +57,6 @@ scores. This will delete the scores.json file.
This is currently a single-player game, but multiple users on different nodes This is currently a single-player game, but multiple users on different nodes
can play it simultaneously. can play it simultaneously.
Currently, this trivia game is local to the current BBS only. In the future,
I think it would be good to add a feature for networked/inter-BBS games.
Answer matching: When a user answers a question, the game can allow non-exact Answer matching: When a user answers a question, the game can allow non-exact
answer matching in some circumstances, to account for typos and spelling answer matching in some circumstances, to account for typos and spelling
mistakes. If the answer is a single word up to 12 characters, the game will mistakes. If the answer is a single word up to 12 characters, the game will
...@@ -74,6 +70,48 @@ For more information on Levenshtein distances: ...@@ -74,6 +70,48 @@ For more information on Levenshtein distances:
https://www.cuelogic.com/blog/the-levenshtein-algorithm https://www.cuelogic.com/blog/the-levenshtein-algorithm
Shared game scores on a server BBS
----------------------------------
The game can be configured to share its local game scores, to be stored on a
remote BBS. The scores can be shared either by directly contacting the remote
BBS (via the JSON DB service) and/or by posting the local game scores in one or
more (networked) message sub-boards (both configurable in gttrivia.ini). The
option to post scores in a message sub-board is a backup in case the remote BBS
is down and cannot be contacted directly. You may also opt to just have Good
Time Trivia post scores in a message sub-board and not configure a remote BBS
server.
Digital Distortion (BBS) is set up to host scores for this game, and the
default settings in the REMOTE_SERVER section of gttrivia.ini point to
Digital Distortion, to use the direct-connect JSON DB method to update remote
scores.
Digital Distortion is also set up to look for scores for this game in the
Dove-Net Synchronet Data message area, as well as FSXNet Inter-BBS Data.
If your BBS has the Dove-Net message sub-boards, you could configure Good Time
Trivia to post scores in the Dove-Net Synchronet Data sub-board (using the
internal code configured on your BBS). If there are other BBSes besides Digital
Distortion hosting scores, the host BBS would also need to have Dove-Net and
have an event configured to periodically run Good Time Trivia to poll Dove-Net
Synchronet Data and read the game scores.
By default, the game is set up to post scores to Digital Distortion, so you may
choose to view the scores there or host scores yourself. See section 4
(Configuration file) for more information on the options to send scores to a
remote BBS.
When configured to send user scores to a remote BBS and to write scores to a
message sub-board, that happens whenever a user stops playing a game. The logic
for sending the scores is as follows:
- Try to post the scores to the remote BBS
- If that fails, then post the scores in the configured message sub-board, if
there is one configured.
That logic should ensure that the scores get posted. The remote BBS should then
be configured to have their JSON-DB service running and/or have Good Time Trivia
periodically scan the same (networked) message sub-board to read the scores.
3. Installation & Setup 3. Installation & Setup
======================= =======================
Aside from readme.txt and revision_history.txt, Good Time Trivia is comprised Aside from readme.txt and revision_history.txt, Good Time Trivia is comprised
...@@ -178,6 +216,20 @@ scripts): ...@@ -178,6 +216,20 @@ scripts):
╚══════════════════════════════════════════════════════════╝ ╚══════════════════════════════════════════════════════════╝
That is all you need to do to get Good Time Trivia running.
Optional
--------
As mentioned in the introduction, server scores can be sent to a remote BBS so
that scores from players on multiple BBSes can be viewed. Normally, if Good Time
Trivia is unable to connect to the remote BBS directly, it will fall back to
posting scores in a networked message sub-board (if configured) as a backup
option. That happens automatically after a user finishes playing a game, but
Good Time Trivia can also (optionally) be configured to post game scores in a
sub-board as a timed event by running gttrivia.js with the
-post_scores_to_subboard command-line argument.
4. Configuration File 4. Configuration File
===================== =====================
...@@ -200,6 +252,27 @@ numTriesPerQuestion The maximum number of times a user is ...@@ -200,6 +252,27 @@ numTriesPerQuestion The maximum number of times a user is
maxNumPlayerScoresToDisplay The maximum number of player scores to display maxNumPlayerScoresToDisplay The maximum number of player scores to display
in the list of high scores in the list of high scores
scoresMsgSubBoardsForPosting This can be used to specify a comma-separated
list of internal sub-board codes for the game
to post user scores in, if you want your user
scores to be shared. In case the remote BBS
in the REMOTE_SERVER setting can't be reached
or there is no remote BBS configured, the game
will post scores in the sub-board(s) specified
here. You can specify more than one sub-board
code in case there are multiple BBSes that
host scores for this game.
Note that this setting is empty by default,
because internal sub-board codes are probably
different on each BBS. Digital Distortion is
set up to host scores for this game, and if
your BBS is connected to Dove-Net, it is
recommended to use your BBS's internal code
code for the Dove-Net Synchronet Data
sub-board here. FSXNet also has an InterBBS
Data area that might be used for conveying
game scores to a host BBS.
[COLORS] section [COLORS] section
---------------- ----------------
In this section, the color codes are simply specified by a string of color In this section, the color codes are simply specified by a string of color
...@@ -270,6 +343,19 @@ deleteScoresOlderThanDays The number of days to keep old player scores. ...@@ -270,6 +343,19 @@ deleteScoresOlderThanDays The number of days to keep old player scores.
The background service will remove player The background service will remove player
scores older than this number of days. scores older than this number of days.
scoresMsgSubBoardsForReading This can be used to specify a comma-separated
list of internal sub-board codes for the game
to read user scores from, for client BBSes
that post their game scores there. See section
5 (Optional: Configuring your BBS to host
player scores) for information on adding an
event in SCFG to periodically read scores
from the sub-board(s) if you want to host game
scores on your BBS. By default, the game is
set up to post scores to Digital Distortion,
so you may choose to view the scores there or
host scores yourself.
5. Optional: Configuring your BBS to host player scores 5. Optional: Configuring your BBS to host player scores
======================================================= =======================================================
...@@ -297,3 +383,51 @@ dir=../xtrn/gttrivia/server/ ...@@ -297,3 +383,51 @@ dir=../xtrn/gttrivia/server/
It would then probably be a good idea to stop and re-start your Synchronet BBS It would then probably be a good idea to stop and re-start your Synchronet BBS
in order for it to recognize that you have a new JSON database configured. in order for it to recognize that you have a new JSON database configured.
Periodic reading of scores from a message sub-board
---------------------------------------------------
BBSes with the game installed could configure their game to post scores in a
(networked) message sub-board. If you decide to host game scores on your BBS,
it's also a good idea to configure your BBS to read scores from a networked
message sub-board (which would need to be the same one that BBSes post in). For
instance, you could set it up to read scores from Dove-Net Synchronet Data,
FSXNet InterBBS Data, or perhaps another networked sub-board that is meant to
carry BBS data.
To specify which sub-board(s) to read scores from, you can specify those as a
comma-separated list of internal sub-board codes using the
scoresMsgSubBoardsForReading setting under the [SERVER] section of
gttrivia.ini.
Then, in SCFG, you will need to configure an event to run periodically to run
gttrivia.js to read those message sub-boards for game scores. You can do that
in SCFG > External Programs > Timed Events. Set it up to run gttrivia.js with
the command-line parameter -read_scores_from_subboard
Add an event as follows (internal code can be what you want; GTRIVSIM is short
for Good Time Trivia scores import):
╔═══════════════════════════════════════════════════════════════[< >]╗
║ GTRIVSIM Timed Event ║
╠════════════════════════════════════════════════════════════════════╣
║ │Internal Code GTRIVSIM ║
║ │Start-up Directory ../xtrn/gttrivia ║
║ │Command Line ?gttrivia.js -read_scores_from_subboard
║ │Enabled Yes ║
║ │Execution Node 12 ║
║ │Execution Months Any ║
║ │Execution Days of Month Any ║
║ │Execution Days of Week All ║
║ │Execution Frequency 96 times a day ║
║ │Requires Exclusive Execution No ║
║ │Force Users Off-line For Event No ║
║ │Native Executable/Script Yes ║
║ │Use Shell or New Context No ║
║ │Background Execution No ║
║ │Always Run After Init/Re-init No ║
║ │Error Log Level Error ║
╚════════════════════════════════════════════════════════════════════╝
The number of times per day is up to you, but it would probably be beneficial
for this to happen frequently so that scores are kept up to date. In the above
example, 96 times per day would mean it would run every 15 minutes.
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment