Compare commits
7 Commits
80a9ec5807
...
08851d70fc
Author | SHA1 | Date |
---|---|---|
j | 08851d70fc | |
j | 5e898fe8ce | |
j | f5698d37e1 | |
j | dac5f3b361 | |
j | d1aa19e4c3 | |
j | 0087c3523d | |
j | 01a2825fc0 |
|
@ -490,7 +490,7 @@
|
|||
4690 GOTO 4710
|
||||
|
||||
4700 REM ***MOUNTAINS***
|
||||
4710 IF H <= 950 THEN 1230
|
||||
4710 IF totalMileageWholeTrip <= 950 THEN 1230
|
||||
4720 IF RND(-1)*10>9-((totalMileageWholeTrip/100-15)^2+72)/((totalMileageWholeTrip/100-15)^2+12) THEN 4860
|
||||
4730 PRINT "RUGGED MOUNTAINS"
|
||||
4740 IF RND(-1)>.1 THEN 4780
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE IF NOT EXISTS gameState (
|
||||
authorId INTEGER PRIMARY KEY,
|
||||
data TEXT NOT NULL
|
||||
);
|
|
@ -1,28 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS user_mentions (
|
||||
-- Unique identifier for each record. Auto-incremented to ensure uniqueness.
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- The unique identifier of the comment from the r/Drama platform. Not unique in this table
|
||||
-- because a single comment can mention multiple users.
|
||||
rdrama_comment_id TEXT NOT NULL,
|
||||
|
||||
-- The mentioned Reddit username in a standardized format (e.g., u/username). Lowercased
|
||||
-- to ensure consistency and prevent duplicate entries due to case differences.
|
||||
username TEXT NOT NULL,
|
||||
|
||||
-- The content of the message sent to the mentioned user, if any. Allows tracking
|
||||
-- of what communication has been made, useful for audit purposes or resending messages.
|
||||
message TEXT,
|
||||
|
||||
-- Timestamp when the mention was processed and, if applicable, when a message was sent.
|
||||
-- Defaults to the current timestamp at the time of record creation.
|
||||
sent_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- Enforces uniqueness for each comment-username pair to prevent processing and notifying
|
||||
-- the same user mention in the same comment more than once.
|
||||
CONSTRAINT unique_comment_user UNIQUE (rdrama_comment_id, username)
|
||||
);
|
||||
|
||||
-- Consider adding indexes based on query patterns for improved performance, such as:
|
||||
-- CREATE INDEX idx_username ON user_mentions(username);
|
||||
-- CREATE INDEX idx_rdrama_comment_id ON user_mentions(rdrama_comment_id);
|
|
@ -1,9 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
||||
id INTEGER PRIMARY KEY,
|
||||
token_identifier TEXT NOT NULL UNIQUE, -- Static identifier for the OAuth token
|
||||
access_token TEXT NOT NULL,
|
||||
token_type TEXT NOT NULL,
|
||||
expires_in INTEGER NOT NULL,
|
||||
expiry_timestamp INTEGER NOT NULL,
|
||||
scope TEXT NOT NULL
|
||||
);
|
|
@ -1,6 +1,6 @@
|
|||
import { Database } from 'sqlite';
|
||||
import { Comment } from '../../rdrama/models/Comment';
|
||||
import { DatabaseInitializer } from '../initializeDatabase';
|
||||
import GameState from '../../game/gameState';
|
||||
|
||||
/**
|
||||
* Service for interacting with the SQLite database for operations related to comments and user mentions.
|
||||
|
@ -27,136 +27,56 @@ export class DatabaseService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Inserts a new user mention into the database.
|
||||
* This static method adds a record of a user being mentioned in a comment.
|
||||
*
|
||||
* Loads an existing game state for a given author from the database.
|
||||
* If an existing game state is found, it returns a new GameState instance initialized with the stored values.
|
||||
* If no game state exists for the given author, it returns null, indicating that a new game state needs to be initialized.
|
||||
*
|
||||
* @example
|
||||
* await DatabaseService.insertUserMention({
|
||||
* rdrama_comment_id: 456,
|
||||
* username: 'mentionedUser',
|
||||
* message: 'You were mentioned in a comment.'
|
||||
* });
|
||||
*
|
||||
* @param {Object} mention - The user mention object to insert.
|
||||
* @param {number} mention.rdrama_comment_id - The ID of the comment from the r/Drama platform.
|
||||
* @param {string} mention.username - The mentioned Reddit username.
|
||||
* @param {string} [mention.message] - The content of the message sent to the mentioned user.
|
||||
* @throws {Error} Will throw an error if the insert operation fails.
|
||||
* const gameState = await DatabaseService.loadGameState(authorId);
|
||||
* if (gameState) {
|
||||
* console.log('Game state loaded successfully.');
|
||||
* } else {
|
||||
* console.log('No existing game state found. Initializing a new game.');
|
||||
* }
|
||||
*
|
||||
* @param {number} authorId - The unique identifier for the author of the game.
|
||||
* @returns {Promise<GameState | null>} A promise that resolves to a GameState instance if found, or null if no existing game state is present.
|
||||
* @throws {Error} Will throw an error if the database operation fails.
|
||||
*/
|
||||
public static async insertUserMention(mention: { rdrama_comment_id: number; username: string; message?: string }): Promise<void> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const sql = `INSERT INTO user_mentions (rdrama_comment_id, username, message) VALUES (?, ?, ?)`;
|
||||
await db.run(sql, [mention.rdrama_comment_id, mention.username, mention.message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the database to check if a username has been mentioned.
|
||||
*
|
||||
* @example
|
||||
* const mentioned = await DatabaseService.userMentionExists('exampleUser');
|
||||
* console.log(mentioned ? 'User has been mentioned.' : 'User has not been mentioned.');
|
||||
*
|
||||
* @param {string} username - The username to search for.
|
||||
* @returns {Promise<boolean>} A boolean indicating whether the username has been mentioned.
|
||||
* @throws {Error} Will throw an error if the query operation fails.
|
||||
*/
|
||||
public static async userMentionExists(username: string): Promise<boolean> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const sql = `SELECT 1 FROM user_mentions WHERE username = ?`;
|
||||
const result = await db.get(sql, [username]);
|
||||
return !!result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates the OAuth token in the database for a specific service.
|
||||
*
|
||||
* @example
|
||||
* await DatabaseService.upsertOAuthToken('https://oauth.reddit.com', {
|
||||
* access_token: 'abc123',
|
||||
* token_type: 'bearer',
|
||||
* expires_in: 3600,
|
||||
* scope: 'read'
|
||||
* });
|
||||
*
|
||||
* @param {string} token_identifier - A unique identifier for the token, typically the service's base URL.
|
||||
* @param {Object} tokenData - The OAuth token data including access_token, token_type, expires_in, and scope.
|
||||
* @throws {Error} Will throw an error if the upsert operation fails.
|
||||
*/
|
||||
public static async upsertOAuthToken(token_identifier: string, tokenData: any) {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const { access_token, token_type, expires_in, scope } = tokenData;
|
||||
const expiryTimestamp = Math.floor(Date.now() / 1000) + expires_in;
|
||||
console.log('token_identifier', token_identifier)
|
||||
console.log('access_token', `${access_token.substring(0, 5)}XXXXX`)
|
||||
console.log('token_type', token_type)
|
||||
console.log('expires_in', expires_in)
|
||||
console.log('scope', scope)
|
||||
|
||||
await db.run(`
|
||||
INSERT INTO oauth_tokens (token_identifier, access_token, token_type, expires_in, expiry_timestamp, scope)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(token_identifier) DO UPDATE SET
|
||||
access_token = excluded.access_token,
|
||||
token_type = excluded.token_type,
|
||||
expires_in = excluded.expires_in,
|
||||
expiry_timestamp = excluded.expiry_timestamp,
|
||||
scope = excluded.scope
|
||||
`, [token_identifier, access_token, token_type, expires_in, expiryTimestamp, scope]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current, unexpired OAuth token for a specific service.
|
||||
*
|
||||
* @example
|
||||
* const token = await DatabaseService.getCurrentOAuthToken('https://oauth.reddit.com');
|
||||
* console.log(token ? `Current token: ${token.access_token}` : 'No valid token found.');
|
||||
*
|
||||
* @param {string} token_identifier - The unique identifier for the token, typically the service's base URL.
|
||||
* @returns {Promise<Object|null>} The current OAuth token data or null if expired or not found.
|
||||
* @throws {Error} Will throw an error if the query operation fails.
|
||||
*/
|
||||
public static async getCurrentOAuthToken(token_identifier: string) {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const tokenRow = await db.get(`
|
||||
SELECT access_token, token_type, scope, expiry_timestamp FROM oauth_tokens
|
||||
WHERE token_identifier = ?
|
||||
`, token_identifier);
|
||||
|
||||
return tokenRow || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cooldown period has passed since the last notification was sent, allowing for a new notification to be sent.
|
||||
*
|
||||
* @example
|
||||
* const canSend = await DatabaseService.canSendNotification();
|
||||
* console.log(canSend ? 'Can send a new notification.' : 'Still in cooldown period.');
|
||||
*
|
||||
* @returns {Promise<boolean>} True if the cooldown period has passed, allowing new notifications to be sent.
|
||||
* @throws {Error} Will throw an error if the check operation fails.
|
||||
*/
|
||||
public static async canSendNotification(): Promise<boolean> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const cooldownHours = process.env.NOTIFICATION_COOLDOWN_HOURS || 4;
|
||||
const sql = `
|
||||
SELECT MAX(sent_time) as last_notification_time
|
||||
FROM user_mentions
|
||||
`;
|
||||
const result = await db.get(sql);
|
||||
|
||||
if (!result || !result.last_notification_time) {
|
||||
// No notifications have been sent yet, or unable to retrieve the last sent time.
|
||||
return true;
|
||||
public static async loadGameState(authorId: number): Promise<GameState | null> {
|
||||
const db = await DatabaseService.getDatabase();
|
||||
const sql = `SELECT data FROM game_state WHERE authorId = ?`;
|
||||
const row = await db.get(sql, [authorId]);
|
||||
if (row) {
|
||||
return JSON.parse(row.data) as GameState; // Assuming GameState constructor can take authorId and a partial state object
|
||||
} else {
|
||||
return null; // Or return a new GameState with defaults
|
||||
}
|
||||
}
|
||||
|
||||
const lastNotificationTime = new Date(result.last_notification_time).getTime();
|
||||
const currentTime = new Date(new Date().toISOString().slice(0, 19).replace('T', ' ')).getTime();
|
||||
const timeElapsed = currentTime - lastNotificationTime;
|
||||
//console.log('timeElapsed', timeElapsed)
|
||||
const cooldownPeriod = +cooldownHours * 60 * 60 * 1000; // Convert hours to milliseconds
|
||||
//console.log('cooldownPeriod', cooldownPeriod)
|
||||
|
||||
return timeElapsed >= cooldownPeriod;
|
||||
/**
|
||||
* Saves the current game state for a given author to the database.
|
||||
* If an existing game state for the author exists, it updates the stored values. If no game state exists, it inserts a new record.
|
||||
* This method ensures the game state is persisted between sessions, allowing players to resume their game at any time.
|
||||
*
|
||||
* @example
|
||||
* await DatabaseService.saveGameState(authorId, gameState);
|
||||
* console.log('Game state saved successfully.');
|
||||
*
|
||||
* @param {number} authorId - The unique identifier for the author of the game.
|
||||
* @param {GameState} gameState - The current game state to be saved.
|
||||
* @returns {Promise<void>} A promise that resolves when the game state is successfully saved.
|
||||
* @throws {Error} Will throw an error if the save operation fails.
|
||||
*/
|
||||
public static async saveGameState(authorId: number, gameState: GameState): Promise<void> {
|
||||
const db = await DatabaseService.getDatabase();
|
||||
const data = JSON.stringify(gameState);
|
||||
const sql = `
|
||||
INSERT INTO game_state (authorId, data) VALUES (?, ?)
|
||||
ON CONFLICT(authorId) DO UPDATE SET
|
||||
data = excluded.data;
|
||||
`;
|
||||
await db.run(sql, [authorId, data]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import GameState from "./gameState";
|
||||
|
||||
class GameEvents {
|
||||
private events: OregonEvent[];
|
||||
private randomEvents: OregonEvent[];
|
||||
private totalThreshold: number;
|
||||
constructor() {
|
||||
// Initialize the events and their thresholds.
|
||||
this.events = [
|
||||
this.randomEvents = [
|
||||
{
|
||||
name: "Wagon Breakdown",
|
||||
threshold: 10, // Probability weight
|
||||
|
@ -99,14 +99,14 @@ class GameEvents {
|
|||
// Add more events here...
|
||||
];
|
||||
|
||||
this.totalThreshold = this.events.reduce((acc, event) => acc + event.threshold, 0);
|
||||
this.totalThreshold = this.randomEvents.reduce((acc, event) => acc + event.threshold, 0);
|
||||
}
|
||||
|
||||
generateEvent(gameState: GameState): string {
|
||||
generateRandomEvent(gameState: GameState): string {
|
||||
const randomEventNumber = Math.random() * this.totalThreshold;
|
||||
let cumulativeThreshold = 0;
|
||||
|
||||
for (let event of this.events) {
|
||||
for (let event of this.randomEvents) {
|
||||
cumulativeThreshold += event.threshold;
|
||||
if (randomEventNumber < cumulativeThreshold) {
|
||||
console.log(`Event triggered: ${event.name}`);
|
||||
|
@ -115,6 +115,65 @@ class GameEvents {
|
|||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
mountains(gameState: GameState): string {
|
||||
if (gameState.totalMileageWholeTrip <= 950) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let message = "**🏔️ Mountain Passage 🏔️**\n";
|
||||
let encounter = false;
|
||||
|
||||
let randomFactor = Math.random() * 10;
|
||||
let mountainDifficulty = 9 - ((gameState.totalMileageWholeTrip / 100 - 15) ** 2 + 72) / ((gameState.totalMileageWholeTrip / 100 - 15) ** 2 + 12);
|
||||
|
||||
if (randomFactor > mountainDifficulty) {
|
||||
encounter = true;
|
||||
message += "Rugged mountains make your journey difficult.\n";
|
||||
|
||||
if (Math.random() > 0.1) {
|
||||
gameState.totalMileageWholeTrip -= 60;
|
||||
message += "🔍 You got lost—losing valuable time trying to find the trail!\n";
|
||||
} else if (Math.random() > 0.11) {
|
||||
gameState.amountSpentOnMiscellaneousSupplies -= 5;
|
||||
gameState.amountSpentOnAmmunition -= 200;
|
||||
gameState.totalMileageWholeTrip -= 20 + Math.random() * 30;
|
||||
message += "🛠️ Wagon damaged!—Lose time and supplies.\n";
|
||||
} else {
|
||||
gameState.totalMileageWholeTrip -= 45 + Math.random() / 0.02;
|
||||
message += "🐢 The going gets slow.\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!gameState.clearSouthPassFlag) {
|
||||
gameState.clearSouthPassFlag = true;
|
||||
if (Math.random() < 0.8) {
|
||||
encounter = true;
|
||||
message += "✅ You made it safely through South Pass—no snow.\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (gameState.totalMileageWholeTrip >= 1700 && !gameState.clearBlueMountainsFlag) {
|
||||
gameState.clearBlueMountainsFlag = true;
|
||||
if (Math.random() < 0.7) {
|
||||
encounter = true;
|
||||
gameState.blizzardFlag = true;
|
||||
gameState.amountSpentOnFood -= 25;
|
||||
gameState.amountSpentOnMiscellaneousSupplies -= 10;
|
||||
gameState.amountSpentOnAmmunition -= 300;
|
||||
gameState.totalMileageWholeTrip -= 30 + Math.random() * 40;
|
||||
message += "❄️ Blizzard in mountain pass—time and supplies lost.\n";
|
||||
|
||||
if (gameState.amountSpentOnClothing < 18 + Math.random() * 2) {
|
||||
message += "❄️🧣 Not enough clothing for the cold. This could be dire.\n";
|
||||
// Add logic to affect player health or trigger a death sequence.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return encounter ? message : "The mountains are distant... for now.\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GameEvents;
|
||||
|
|
|
@ -14,17 +14,30 @@ class GameSession {
|
|||
this.gameManager = new GameFlow(this.gameState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes player actions and updates the game state accordingly.
|
||||
* @param action The player's action.
|
||||
* @param params Optional parameters for the action.
|
||||
*/
|
||||
public async processAction(action: string, params?: any): Promise<void> {
|
||||
// Update the game state based on the action.
|
||||
// This might include changing locations, spending money, hunting for food, etc.
|
||||
await this.gameManager.handleAction(action, params);
|
||||
// Save the current game state to allow resuming later.
|
||||
this.saveGameState();
|
||||
async handleUserInput(userId: string, input: string): Promise<void> {
|
||||
const gameState = await this.loadGameState(userId);
|
||||
|
||||
switch (input.split(' ')[0].toLowerCase()) {
|
||||
case "!!oregon":
|
||||
await this.processCommand(gameState, input);
|
||||
break;
|
||||
// Additional commands as necessary
|
||||
}
|
||||
|
||||
await this.saveGameState(gameState);
|
||||
}
|
||||
|
||||
private async processCommand(gameState: GameState, command: string): Promise<void> {
|
||||
const args = command.split(' ');
|
||||
// Process different game setup commands (e.g., "start over", "buy supplies")
|
||||
if (args[1].toLowerCase() === "startover") {
|
||||
gameState.reset(); // Resets game state to initial values
|
||||
} else if (args[1].toLowerCase() === "buysupplies") {
|
||||
// Transition to handling purchases
|
||||
// This would involve setting the next expected action in the game state
|
||||
// And possibly sending a prompt for the first purchase
|
||||
}
|
||||
// Handle other commands and shopping logic
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import { DatabaseService } from "../db/services/Database";
|
||||
|
||||
/**
|
||||
* Represents the state of a game session for an Oregon Trail-style game.
|
||||
* This class stores all relevant game state information and provides methods
|
||||
* to load from and save to a database.
|
||||
*/
|
||||
class GameState {
|
||||
authorId: number = 0;
|
||||
amountSpentOnAnimals: number = 0;
|
||||
amountSpentOnAmmunition: number = 0;
|
||||
actualResponseTimeForBang: number = 0;
|
||||
|
@ -32,12 +40,48 @@ class GameState {
|
|||
actionChoiceForEachTurn: number = 0;
|
||||
fortOptionFlag: boolean = false;
|
||||
death: boolean = false;
|
||||
phase: PHASE_ENUM = PHASE_ENUM.SETUP;
|
||||
subPhase: number = 0;
|
||||
|
||||
constructor(authorId: string) {
|
||||
// Load existing session for authorId or create default values
|
||||
private constructor(authorId: number, state?: Partial<GameState>) {
|
||||
this.authorId = authorId;
|
||||
Object.assign(this, state); // Initialize with loaded state or undefined
|
||||
}
|
||||
|
||||
// Methods to update game state variables and check game conditions
|
||||
/**
|
||||
* Saves the current game state to the database.
|
||||
*/
|
||||
public async save() {
|
||||
await DatabaseService.saveGameState(this.authorId, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an existing game state from the database or creates a new one if it doesn't exist.
|
||||
* @param {number} authorId - The ID of the author/player.
|
||||
* @returns {Promise<GameState>} - The loaded or newly created game state.
|
||||
*/
|
||||
public static async load(authorId: number): Promise<GameState> {
|
||||
const loadedState = await DatabaseService.loadGameState(authorId);
|
||||
if (loadedState) {
|
||||
return new GameState(authorId, loadedState);
|
||||
} else {
|
||||
// Create a new GameState with default values
|
||||
const newState = new GameState(authorId);
|
||||
await newState.save(); // Optionally save the new state to the database
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GameState
|
||||
export default GameState
|
||||
|
||||
// Enumeration for different phases of the game.
|
||||
enum PHASE_ENUM {
|
||||
SETUP = 'SETUP',
|
||||
TRAVEL = 'TRAVEL',
|
||||
FORT_HUNT = 'FORT_HUNT',
|
||||
ENCOUNTER = 'ENCOUNTER',
|
||||
EVENT = 'EVENT',
|
||||
MOUNTAIN = 'MOUNTAIN',
|
||||
DEATH_CHECK = 'DEATH_CHECK',
|
||||
}
|
Loading…
Reference in New Issue