Added: Example .env
This commit is contained in:
27
.env_example
Normal file
27
.env_example
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# THIS IS AN EXAMPLE
|
||||||
|
# CHANGE IT IN PRODUCTION
|
||||||
|
|
||||||
|
# Postgres Setup
|
||||||
|
POSTGRES_DB=jupyter
|
||||||
|
POSTGRES_USER=jupyter
|
||||||
|
POSTGRES_PASSWORD=1234
|
||||||
|
|
||||||
|
# Jupyter Setup (Run openssl rand -hex 32)
|
||||||
|
JUPYTERHUB_CRYPT_KEY=5bb3affb5036422b0ac201e9cadde6ab783c7742861006c04e65e46d2bf3564b
|
||||||
|
JUPYTERHUB_AUTH_TOKEN=c546bb0e1dbcb73b41092b2e7e46fa69f8c031c14ea0cd94cc6696349ffacceb
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
JUPYTERHUB_BASE_URL=/jupyter/
|
||||||
|
NATIVE_AUTH_MIN_PASSWORD_LENGTH=8
|
||||||
|
JUPYTERHUB_ADMIN_USER=admin
|
||||||
|
ENABLE_LAB=1
|
||||||
|
SPAWNER_DEFAULT_URL=/lab
|
||||||
|
NOTEBOOK_MEMORY_LIMIT=500M
|
||||||
|
NOTEBOOK_CPU_LIMIT=1.0
|
||||||
|
HUB_IP=jupyterhub # Name of the Docker container
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
DOCKER_NETWORK_NAME=jupyter_network
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
JUPYTERHUB_PORT=8000
|
||||||
|
NGINX_HTTP_PORT=8080
|
||||||
|
NGINX_HTTPS_PORT=8443
|
@@ -3,7 +3,9 @@ import sys
|
|||||||
from dockerspawner import DockerSpawner
|
from dockerspawner import DockerSpawner
|
||||||
from jupyterhub.utils import random_port
|
from jupyterhub.utils import random_port
|
||||||
from nativeauthenticator import NativeAuthenticator
|
from nativeauthenticator import NativeAuthenticator
|
||||||
#from nbgrader.auth import JupyterHubAuthPlugin
|
import subprocess
|
||||||
|
|
||||||
|
# from nbgrader.auth import JupyterHubAuthPlugin
|
||||||
from traitlets import Unicode, Bool, Int, Float
|
from traitlets import Unicode, Bool, Int, Float
|
||||||
|
|
||||||
import secrets
|
import secrets
|
||||||
@@ -11,86 +13,93 @@ import string
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
def generate_api_token(
|
def generate_api_token(
|
||||||
length: int = 32,
|
length: int = 32,
|
||||||
prefix: Optional[str] = None,
|
prefix: Optional[str] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Generate a secure random API token with optional prefix and expiration.
|
Generate a secure random API token with optional prefix and expiration.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
length: Length of the token (default: 32)
|
length: Length of the token (default: 32)
|
||||||
prefix: Optional prefix for the token (e.g., 'hub_')
|
prefix: Optional prefix for the token (e.g., 'hub_')
|
||||||
expiration_days: Optional expiration in days from now
|
expiration_days: Optional expiration in days from now
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
- token: The generated token
|
- token: The generated token
|
||||||
"""
|
"""
|
||||||
# Generate cryptographically secure random string
|
# Generate cryptographically secure random string
|
||||||
alphabet = string.ascii_letters + string.digits
|
alphabet = string.ascii_letters + string.digits
|
||||||
token = ''.join(secrets.choice(alphabet) for _ in range(length))
|
token = "".join(secrets.choice(alphabet) for _ in range(length))
|
||||||
|
|
||||||
# Add prefix if specified
|
# Add prefix if specified
|
||||||
if prefix:
|
if prefix:
|
||||||
token = f"{prefix}{token}"
|
token = f"{prefix}{token}"
|
||||||
|
|
||||||
return token
|
|
||||||
|
|
||||||
import subprocess
|
return token
|
||||||
|
|
||||||
|
|
||||||
def is_service_available_cmd(host, port):
|
def is_service_available_cmd(host, port):
|
||||||
"""Check service using system commands"""
|
"""Check service using system commands"""
|
||||||
try:
|
try:
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['nc', '-z', host, str(port)],
|
["nc", "-z", host, str(port)],
|
||||||
check=True,
|
check=True,
|
||||||
timeout=3,
|
timeout=3,
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.DEVNULL
|
stderr=subprocess.DEVNULL,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# Configuration file for JupyterHub
|
# Configuration file for JupyterHub
|
||||||
c = get_config()
|
c = get_config()
|
||||||
|
|
||||||
# Base URL configuration (The new way, dont use ip/port/bind_url attributes it throws a warning and is deprecated)
|
# Base URL configuration
|
||||||
port = int(os.environ.get('JUPYTERHUB_PORT', '8000'))
|
# The new way, dont use ip/port/bind_url
|
||||||
base_url = os.environ.get('JUPYTERHUB_BASE_URL', '/jupyter/')
|
# attributes it throws a warning and is deprecated
|
||||||
|
port = int(os.environ.get("JUPYTERHUB_PORT", "8000"))
|
||||||
|
base_url = os.environ.get("JUPYTERHUB_BASE_URL", "/jupyter/")
|
||||||
c.JupyterHub.bind_url = f"http://0.0.0.0:{port}{base_url}"
|
c.JupyterHub.bind_url = f"http://0.0.0.0:{port}{base_url}"
|
||||||
|
|
||||||
# Database configuration
|
# Database configuration
|
||||||
c.JupyterHub.db_url = f"postgresql://{os.environ['POSTGRES_USER']}:{os.environ['POSTGRES_PASSWORD']}@stack-{os.environ['POSTGRES_HOST']}:5432/{os.environ['POSTGRES_DB']}"
|
c.JupyterHub.db_url = f"postgresql://{os.environ['POSTGRES_USER']}:{os.environ['POSTGRES_PASSWORD']}@stack-{os.environ['POSTGRES_HOST']}:5432/{os.environ['POSTGRES_DB']}"
|
||||||
#c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite'
|
# c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite'
|
||||||
|
|
||||||
|
|
||||||
# Authenticator configuration - NativeAuthenticator
|
# Authenticator configuration - NativeAuthenticator
|
||||||
c.JupyterHub.authenticator_class = NativeAuthenticator
|
c.JupyterHub.authenticator_class = NativeAuthenticator
|
||||||
|
|
||||||
# Native Authenticator settings
|
# Native Authenticator settings
|
||||||
#c.NativeAuthenticator.create_system_users = True
|
# c.NativeAuthenticator.create_system_users = True
|
||||||
c.NativeAuthenticator.minimum_password_length = int(os.environ.get('NATIVE_AUTH_MIN_PASSWORD_LENGTH', '8'))
|
c.NativeAuthenticator.minimum_password_length = int(
|
||||||
|
os.environ.get("NATIVE_AUTH_MIN_PASSWORD_LENGTH", "8")
|
||||||
|
)
|
||||||
c.NativeAuthenticator.check_common_password = True
|
c.NativeAuthenticator.check_common_password = True
|
||||||
c.NativeAuthenticator.enable_signup = True
|
c.NativeAuthenticator.enable_signup = True
|
||||||
c.NativeAuthenticator.ask_email_on_signup = False
|
c.NativeAuthenticator.ask_email_on_signup = False
|
||||||
c.NativeAuthenticator.allow_2fa = False
|
c.NativeAuthenticator.allow_2fa = False
|
||||||
|
|
||||||
# Admin configuration
|
# Admin configuration
|
||||||
admin_user = os.environ.get('JUPYTERHUB_ADMIN_USER', 'admin')
|
admin_user = os.environ.get("JUPYTERHUB_ADMIN_USER", "admin")
|
||||||
c.Authenticator.admin_users = {admin_user, 'DerGrumpf'}
|
c.Authenticator.admin_users = {admin_user}
|
||||||
c.Authenticator.any_allow_config = True
|
c.Authenticator.any_allow_config = True
|
||||||
c.Authenticator.allow_all = True
|
c.Authenticator.allow_all = True
|
||||||
|
|
||||||
# Custom logo and templates
|
# Custom logo and templates
|
||||||
c.JupyterHub.logo_file = '/srv/jupyterhub/templates/logo.png'
|
c.JupyterHub.logo_file = "/srv/jupyterhub/templates/logo.png"
|
||||||
c.JupyterHub.template_paths = ['/etc/jupyterhub/templates']
|
c.JupyterHub.template_paths = ["/etc/jupyterhub/templates"]
|
||||||
|
|
||||||
|
|
||||||
# Hub environment for containers
|
# Hub environment for containers
|
||||||
c.JupyterHub.spawner_class = DockerSpawner
|
c.JupyterHub.spawner_class = DockerSpawner
|
||||||
c.Spawner.ip = '0.0.0.0'
|
c.Spawner.ip = "0.0.0.0"
|
||||||
c.DockerSpawner.image = os.environ.get('NOTEBOOK_IMAGE', 'jupyter/scipy-notebook:latest')
|
c.DockerSpawner.image = os.environ.get(
|
||||||
|
"NOTEBOOK_IMAGE", "jupyter/scipy-notebook:latest"
|
||||||
|
)
|
||||||
'''network_name = os.environ.get('DOCKER_NETWORK_NAME', 'stack_default')
|
'''network_name = os.environ.get('DOCKER_NETWORK_NAME', 'stack_default')
|
||||||
c.DockerSpawner.network_name = network_name
|
c.DockerSpawner.network_name = network_name
|
||||||
c.DockerSpawner.extra_host_config = {
|
c.DockerSpawner.extra_host_config = {
|
||||||
@@ -142,7 +151,7 @@ def pre_spawn_hook(spawner):
|
|||||||
c.DockerSpawner.pre_spawn_hook = pre_spawn_hook
|
c.DockerSpawner.pre_spawn_hook = pre_spawn_hook
|
||||||
'''
|
'''
|
||||||
|
|
||||||
'''
|
"""
|
||||||
# Services configuration for NBGrader
|
# Services configuration for NBGrader
|
||||||
c.JupyterHub.services = [
|
c.JupyterHub.services = [
|
||||||
{
|
{
|
||||||
@@ -162,15 +171,15 @@ c.JupyterHub.services = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
'''
|
"""
|
||||||
'''
|
"""
|
||||||
# NBGrader configuration
|
# NBGrader configuration
|
||||||
c.JupyterHub.load_groups = {
|
c.JupyterHub.load_groups = {
|
||||||
'nbgrader-instructors': ['instructor'],
|
'nbgrader-instructors': ['instructor'],
|
||||||
'nbgrader-students': []
|
'nbgrader-students': []
|
||||||
}
|
}
|
||||||
'''
|
"""
|
||||||
'''
|
"""
|
||||||
# NBGrader Config
|
# NBGrader Config
|
||||||
c.JupyterHub.services = [
|
c.JupyterHub.services = [
|
||||||
{
|
{
|
||||||
@@ -194,14 +203,14 @@ c.JupyterHub.services = [
|
|||||||
'user': 'root'
|
'user': 'root'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
'''
|
"""
|
||||||
|
|
||||||
# Security settings
|
# Security settings
|
||||||
c.JupyterHub.cookie_secret_file = '/srv/jupyterhub/cookie_secret'
|
c.JupyterHub.cookie_secret_file = "/srv/jupyterhub/cookie_secret"
|
||||||
c.ConfigurableHTTPProxy.auth_token = os.environ.get('JUPYTERHUB_AUTH_TOKEN', '')
|
c.ConfigurableHTTPProxy.auth_token = os.environ.get("JUPYTERHUB_AUTH_TOKEN", "")
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
c.JupyterHub.log_level = os.environ.get('LOG_LEVEL', 'INFO')
|
c.JupyterHub.log_level = os.environ.get("LOG_LEVEL", "INFO")
|
||||||
|
|
||||||
# Shutdown settings
|
# Shutdown settings
|
||||||
c.JupyterHub.shutdown_on_logout = False
|
c.JupyterHub.shutdown_on_logout = False
|
||||||
|
@@ -1,360 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Discord Mod Simulator 2025</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background-color: #36393f;
|
|
||||||
color: #dcddde;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
#game-container {
|
|
||||||
background-color: #2f3136;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 20px;
|
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
#status-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #202225;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.stat {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
#event-display {
|
|
||||||
min-height: 150px;
|
|
||||||
padding: 15px;
|
|
||||||
background-color: #40444b;
|
|
||||||
border-radius: 5px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
#actions {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
button {
|
|
||||||
background-color: #5865f2;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
button:hover {
|
|
||||||
background-color: #4752c4;
|
|
||||||
}
|
|
||||||
button:disabled {
|
|
||||||
background-color: #4f545c;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
#log {
|
|
||||||
margin-top: 20px;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #40444b;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
.log-entry {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
border-bottom: 1px solid #4f545c;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
.positive {
|
|
||||||
color: #3ba55c;
|
|
||||||
}
|
|
||||||
.negative {
|
|
||||||
color: #ed4245;
|
|
||||||
}
|
|
||||||
.warning {
|
|
||||||
color: #faa61a;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="game-container">
|
|
||||||
<h1>Discord Mod Simulator 2025</h1>
|
|
||||||
<div id="status-bar">
|
|
||||||
<div class="stat">
|
|
||||||
<span>Members</span>
|
|
||||||
<span id="members">1000</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat">
|
|
||||||
<span>Activity</span>
|
|
||||||
<span id="activity">50%</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat">
|
|
||||||
<span>Reputation</span>
|
|
||||||
<span id="reputation">50%</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat">
|
|
||||||
<span>Day</span>
|
|
||||||
<span id="day">1</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="event-display">
|
|
||||||
Welcome to Discord Mod Simulator 2025! As a moderator, you must balance keeping the server active while maintaining order. Make your decisions carefully!
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="actions">
|
|
||||||
<!-- Buttons will be generated by JavaScript -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="log">
|
|
||||||
<div class="log-entry">Game started. Good luck!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Game state
|
|
||||||
const gameState = {
|
|
||||||
members: 1000,
|
|
||||||
activity: 50,
|
|
||||||
reputation: 50,
|
|
||||||
day: 1,
|
|
||||||
powerUps: {
|
|
||||||
adminBackup: 3,
|
|
||||||
muteAll: 2,
|
|
||||||
customRole: 3
|
|
||||||
},
|
|
||||||
gameOver: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Event database
|
|
||||||
const events = [
|
|
||||||
{
|
|
||||||
id: 'spam',
|
|
||||||
text: "User xX_SpamLord_Xx is posting rapid-fire memes in #general. What do you do?",
|
|
||||||
options: [
|
|
||||||
{ text: "Delete messages and warn", members: 0, activity: -5, reputation: 5 },
|
|
||||||
{ text: "Timeout for 1 hour", members: -10, activity: -10, reputation: 10 },
|
|
||||||
{ text: "Ban the user", members: -30, activity: -15, reputation: 15 },
|
|
||||||
{ text: "Ignore it", members: 5, activity: 10, reputation: -10 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'argument',
|
|
||||||
text: "Two users are having a heated argument about which year had the best memes. It's getting personal.",
|
|
||||||
options: [
|
|
||||||
{ text: "Warn both users", members: 0, activity: -5, reputation: 5 },
|
|
||||||
{ text: "Timeout both for 30 minutes", members: -15, activity: -15, reputation: 10 },
|
|
||||||
{ text: "Create #debate channel and move them", members: 10, activity: 5, reputation: 15 },
|
|
||||||
{ text: "Let them fight", members: -20, activity: 20, reputation: -20 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'raid',
|
|
||||||
text: "OH NO! The server is being raided by another community! Spam and offensive content is flooding in!",
|
|
||||||
options: [
|
|
||||||
{ text: "Use Mute All power-up (stops raid but lowers activity)", powerUp: 'muteAll', members: -50, activity: -30, reputation: 20 },
|
|
||||||
{ text: "Call Admin Backup", powerUp: 'adminBackup', members: -20, activity: -10, reputation: 10 },
|
|
||||||
{ text: "Ban raiders manually", members: -100, activity: -40, reputation: 5 },
|
|
||||||
{ text: "Do nothing (risky!)", members: -150, activity: -50, reputation: -30 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'feature',
|
|
||||||
text: "A user suggests a great feature for the server that would require some moderator work to implement.",
|
|
||||||
options: [
|
|
||||||
{ text: "Implement the feature", members: 20, activity: 15, reputation: 10 },
|
|
||||||
{ text: "Say you'll consider it", members: 5, activity: 5, reputation: 5 },
|
|
||||||
{ text: "Reject the idea", members: -10, activity: -5, reputation: -5 },
|
|
||||||
{ text: "Create a poll for the community", members: 10, activity: 20, reputation: 15 }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'bot',
|
|
||||||
text: "The moderation bot is malfunctioning! It's randomly timing out users and missing actual rule violations.",
|
|
||||||
options: [
|
|
||||||
{ text: "Fix the bot (takes time)", members: -10, activity: -10, reputation: 20 },
|
|
||||||
{ text: "Disable the bot temporarily", members: 0, activity: 10, reputation: -10 },
|
|
||||||
{ text: "Blame the bot developer", members: -20, activity: -20, reputation: -20 },
|
|
||||||
{ text: "Use Custom Roles to help moderate", powerUp: 'customRole', members: 0, activity: 0, reputation: 15 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// DOM elements
|
|
||||||
const membersEl = document.getElementById('members');
|
|
||||||
const activityEl = document.getElementById('activity');
|
|
||||||
const reputationEl = document.getElementById('reputation');
|
|
||||||
const dayEl = document.getElementById('day');
|
|
||||||
const eventDisplayEl = document.getElementById('event-display');
|
|
||||||
const actionsEl = document.getElementById('actions');
|
|
||||||
const logEl = document.getElementById('log');
|
|
||||||
|
|
||||||
// Update the UI with current game state
|
|
||||||
function updateUI() {
|
|
||||||
membersEl.textContent = gameState.members;
|
|
||||||
activityEl.textContent = `${gameState.activity}%`;
|
|
||||||
reputationEl.textContent = `${gameState.reputation}%`;
|
|
||||||
dayEl.textContent = gameState.day;
|
|
||||||
|
|
||||||
// Update colors based on values
|
|
||||||
activityEl.className = gameState.activity < 30 ? 'negative' : gameState.activity > 70 ? 'positive' : '';
|
|
||||||
reputationEl.className = gameState.reputation < 30 ? 'negative' : gameState.reputation > 70 ? 'positive' : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a message to the log
|
|
||||||
function addLog(message, type = '') {
|
|
||||||
const entry = document.createElement('div');
|
|
||||||
entry.className = `log-entry ${type}`;
|
|
||||||
entry.textContent = `Day ${gameState.day}: ${message}`;
|
|
||||||
logEl.appendChild(entry);
|
|
||||||
logEl.scrollTop = logEl.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present a random event to the player
|
|
||||||
function presentEvent() {
|
|
||||||
if (gameState.gameOver) return;
|
|
||||||
|
|
||||||
const event = events[Math.floor(Math.random() * events.length)];
|
|
||||||
eventDisplayEl.textContent = event.text;
|
|
||||||
|
|
||||||
// Clear previous buttons
|
|
||||||
actionsEl.innerHTML = '';
|
|
||||||
|
|
||||||
// Create new buttons for each option
|
|
||||||
event.options.forEach((option, index) => {
|
|
||||||
const button = document.createElement('button');
|
|
||||||
button.textContent = option.text;
|
|
||||||
|
|
||||||
// Check if this option requires a power-up we don't have
|
|
||||||
if (option.powerUp && gameState.powerUps[option.powerUp] <= 0) {
|
|
||||||
button.disabled = true;
|
|
||||||
button.title = "No more uses left!";
|
|
||||||
}
|
|
||||||
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
handleChoice(option, event);
|
|
||||||
});
|
|
||||||
|
|
||||||
actionsEl.appendChild(button);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle player's choice
|
|
||||||
function handleChoice(option, event) {
|
|
||||||
// Use power-up if needed
|
|
||||||
if (option.powerUp) {
|
|
||||||
gameState.powerUps[option.powerUp]--;
|
|
||||||
addLog(`Used ${option.powerUp} power-up.`, 'warning');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update game state
|
|
||||||
gameState.members += option.members || 0;
|
|
||||||
gameState.activity += option.activity || 0;
|
|
||||||
gameState.reputation += option.reputation || 0;
|
|
||||||
|
|
||||||
// Ensure values stay within bounds
|
|
||||||
gameState.members = Math.max(0, gameState.members);
|
|
||||||
gameState.activity = Math.max(0, Math.min(100, gameState.activity));
|
|
||||||
gameState.reputation = Math.max(0, Math.min(100, gameState.reputation));
|
|
||||||
|
|
||||||
// Log the result
|
|
||||||
let resultMessage = `You chose: ${option.text}. `;
|
|
||||||
if (option.members !== 0) {
|
|
||||||
resultMessage += `Members ${option.members > 0 ? '+' : ''}${option.members}. `;
|
|
||||||
}
|
|
||||||
if (option.activity !== 0) {
|
|
||||||
resultMessage += `Activity ${option.activity > 0 ? '+' : ''}${option.activity}%. `;
|
|
||||||
}
|
|
||||||
if (option.reputation !== 0) {
|
|
||||||
resultMessage += `Reputation ${option.reputation > 0 ? '+' : ''}${option.reputation}%. `;
|
|
||||||
}
|
|
||||||
|
|
||||||
addLog(resultMessage.trim());
|
|
||||||
|
|
||||||
// Advance to next day
|
|
||||||
gameState.day++;
|
|
||||||
|
|
||||||
// Check win/lose conditions
|
|
||||||
checkGameState();
|
|
||||||
|
|
||||||
// Update UI and present new event
|
|
||||||
updateUI();
|
|
||||||
|
|
||||||
if (!gameState.gameOver) {
|
|
||||||
setTimeout(presentEvent, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check win/lose conditions
|
|
||||||
function checkGameState() {
|
|
||||||
if (gameState.members >= 10000 && gameState.reputation >= 75) {
|
|
||||||
eventDisplayEl.textContent = "CONGRATULATIONS! You've grown the server to 10,000 members with great reputation! You win!";
|
|
||||||
gameState.gameOver = true;
|
|
||||||
actionsEl.innerHTML = '<button onclick="location.reload()">Play Again</button>';
|
|
||||||
addLog("YOU WIN! Server is thriving!", 'positive');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameState.members <= 100) {
|
|
||||||
eventDisplayEl.textContent = "GAME OVER! The server has died from lack of members. Maybe you were too strict?";
|
|
||||||
gameState.gameOver = true;
|
|
||||||
actionsEl.innerHTML = '<button onclick="location.reload()">Play Again</button>';
|
|
||||||
addLog("Game over - server died.", 'negative');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameState.reputation <= 20) {
|
|
||||||
eventDisplayEl.textContent = "GAME OVER! The community revolted against your moderation and you were demoted.";
|
|
||||||
gameState.gameOver = true;
|
|
||||||
actionsEl.innerHTML = '<button onclick="location.reload()">Play Again</button>';
|
|
||||||
addLog("Game over - you were demoted.", 'negative');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gameState.reputation >= 90 && Math.random() < 0.3) {
|
|
||||||
addLog("The server owner promoted you to Admin for your great work!", 'positive');
|
|
||||||
gameState.powerUps.adminBackup += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random member growth based on reputation and activity
|
|
||||||
const growthFactor = (gameState.reputation / 100) * (gameState.activity / 100);
|
|
||||||
const randomGrowth = Math.floor(Math.random() * 20 * growthFactor);
|
|
||||||
if (randomGrowth > 0) {
|
|
||||||
gameState.members += randomGrowth;
|
|
||||||
addLog(`Server grew by ${randomGrowth} members organically!`, 'positive');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Random events
|
|
||||||
if (Math.random() < 0.2) {
|
|
||||||
const randomEvent = Math.random();
|
|
||||||
if (randomEvent < 0.3) {
|
|
||||||
// Good event
|
|
||||||
gameState.reputation += 5;
|
|
||||||
addLog("A user complimented your moderation style in #feedback!", 'positive');
|
|
||||||
} else if (randomEvent < 0.6) {
|
|
||||||
// Bad event
|
|
||||||
gameState.activity -= 10;
|
|
||||||
addLog("A popular meme channel went quiet for a day.", 'negative');
|
|
||||||
} else {
|
|
||||||
// Neutral event
|
|
||||||
addLog("Nothing particularly interesting happened today.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the game
|
|
||||||
updateUI();
|
|
||||||
presentEvent();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Reference in New Issue
Block a user