Compare commits

17 Commits

Author SHA1 Message Date
5dc45b9a9b this works now, ironed out shit and made stuff work 2025-08-17 11:47:56 +02:00
57a7b0e68f semi working darkmode 2025-08-16 21:44:29 +02:00
68b7b81741 new problem and deleted the obsolete problem loader 2025-08-16 20:44:47 +02:00
e97dde65fb animation ; fix the displaying and calculating tmrw 2025-08-14 22:08:47 +02:00
6079813e2c stashcom 2025-08-14 22:05:20 +02:00
04dc638cf0 if the user submitted solution is shit wrong then fucking dont add to the shit database.
also removed unnccessary fucking function
2025-08-13 22:56:06 +02:00
38c3256f19 zajebis2 2025-08-12 20:42:52 +02:00
1374cb9cb1 zajebis 2025-08-12 20:42:26 +02:00
c1ef310f6a shit didnt woirk. frucky ou 2025-08-12 20:37:59 +02:00
525297f19b delete templates which was empty 2025-08-12 20:17:14 +02:00
89ea87951e made this a hell of a lot better 2025-08-12 20:16:46 +02:00
c7c1b8ecd6 some more info and some difficulty added! 2025-08-12 18:56:52 +02:00
0bffdf612c refactored the prb page and added fib seqq 2025-08-12 16:37:35 +02:00
a03f9ddb14 Update readme.md 2025-08-12 11:52:15 +00:00
3f1f709f30 Merge pull request 'shitdontwork' (#1) from shitdontwork into main
Reviewed-on: #1
2025-08-12 11:47:10 +00:00
1ac0a13fc3 fixed the tests and done some frontend shit 2025-08-12 13:46:48 +02:00
5fe140c4f9 fuck you all 2025-08-12 12:47:35 +02:00
44 changed files with 2966 additions and 659 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
__pycache__ __pycache__
instance instance
.venv .venv
venv
*.sqlite3 *.sqlite3

89
app.py
View File

@@ -1,89 +0,0 @@
from flask import Flask, render_template, request, redirect, url_for
from models import db, Problem, Solution
from utils import run_code_against_tests
from leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard
import os
from problem_loader import load_problems_from_json, schedule_problem_reload
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3'
db.init_app(app)
@app.before_request
def setup():
db.create_all()
create_leaderboard_table() # Ensure leaderboard table exists
# Load problems from JSON at startup
json_path = os.path.join(os.path.dirname(__file__), 'problems.json')
load_problems_from_json(json_path)
# Schedule reload every 10 hours
schedule_problem_reload(app, json_path, interval_hours=10)
@app.route('/')
def index():
problems = Problem.query.all()
leaderboard = get_leaderboard()
# Map problem_id to title for leaderboard display
problem_titles = {p.id: p.title for p in problems}
return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles)
@app.route('/problem/new', methods=['GET', 'POST'])
def new_problem():
if request.method == 'POST':
title = request.form['title']
description = request.form['description']
test_code = request.form['test_code']
problem = Problem(title=title, description=description, test_code=test_code)
db.session.add(problem)
db.session.commit()
return redirect(url_for('index'))
return render_template('new_problem.html')
@app.route('/problem/<int:problem_id>', methods=['GET', 'POST'])
def view_problem(problem_id):
problem = Problem.query.get_or_404(problem_id)
result = None
if request.method == 'POST':
user_code = request.form['user_code']
username = request.form.get('username', '').strip() or 'Anonymous'
import tracemalloc
tracemalloc.start()
run_result = run_code_against_tests(user_code, problem.test_code)
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
memory_used = peak // 1024 # in KB
# Try to get the last line number executed (even for successful runs)
line_number = None
import ast
try:
tree = ast.parse(user_code)
# Get the last line number in the user's code
if hasattr(tree, 'body') and tree.body:
last_node = tree.body[-1]
line_number = getattr(last_node, 'lineno', None)
except Exception:
pass
# If there was an error, try to get the error line number from the traceback
if run_result['error']:
tb = run_result['error']
import traceback
try:
tb_lines = traceback.extract_tb(traceback.TracebackException.from_string(tb).stack)
if tb_lines:
line_number = tb_lines[-1].lineno
except Exception:
pass
log_leaderboard(username, problem.id, run_result['runtime'], memory_used, line_number)
solution = Solution(problem_id=problem.id, user_code=user_code, passed=run_result['passed'], output=run_result['output'])
db.session.add(solution)
db.session.commit()
result = run_result
return render_template('problem.html', problem=problem, result=result)
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -1,37 +0,0 @@
import json
import os
import threading
import time
from models import db, Problem
from flask import current_app
def load_problems_from_json(json_path):
if not os.path.exists(json_path):
print(f"Problem JSON file not found: {json_path}")
return
with open(json_path, 'r') as f:
problems = json.load(f)
for p in problems:
# Check if problem already exists by title
existing = Problem.query.filter_by(title=p['title']).first()
# Load test code from solution file if provided
test_code = ''
if 'solution' in p and os.path.exists(p['solution']):
with open(p['solution'], 'r') as sf:
test_code = sf.read()
if existing:
existing.description = p['description']
existing.test_code = test_code
else:
new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code)
db.session.add(new_problem)
db.session.commit()
def schedule_problem_reload(app, json_path, interval_hours=10):
def reload_loop():
while True:
with app.app_context():
load_problems_from_json(json_path)
time.sleep(interval_hours * 3600)
t = threading.Thread(target=reload_loop, daemon=True)
t.start()

View File

@@ -1,12 +0,0 @@
[
{
"title": "Reversed String",
"description": "Reverse a String with a Function (revstring); the function is supposed to take the string as an argument and is supposed to return the reversed string and print it.",
"solution": "problems/solution_reversed_string.py"
},
{
"title": "Sort List",
"description": "Sort a List with a Function (sortlist); the function is supposed to take the list as an argument and is supposed to return the sorted list and print it.",
"solution": "problems/sortlist.py"
}
]

View File

@@ -1,14 +0,0 @@
import unittest
#<!-- The Function the User needs to write -->
def revstring(x):
return x[::-1]
#<!-- This Test, test if the function works -->
class TestSolution(unittest.TestCase):
def test_simple(self):
x="";
self.assertEqual(revstring(x), x[::-1])
if __name__ == "__main__":
unittest.main()

View File

@@ -1,11 +0,0 @@
import unittest
class TestSolution(unittest.TestCase):
def test_sort(self):
# define x as a empty array.
# this will be used for the functiun ; a empty var does not work.
self.x = []
self.assertEqual(sortlist(self.x), sorted(self.x)) ## sort
if __name__ == "__main__":
unittest.main()

View File

@@ -4,22 +4,59 @@
but more lightweight but more lightweight
run the bash script to start the server.
if you want to contribute write tests like this: if you want to contribute write tests like this:
### FileStructure:
In /problems/ create a folder named after the problem.
In this folder create ```manifest.json, test.py, description.md```
**Manifest.JSON needs to exsist and _needs_ to look like this:**
```json
{
"title": "Title of the Problem",
"description": "Write a very short description here",
"description_md": "problems/problempath/description.md",
"difficulty": "easy || medium || hard",
"test_code": "problems/problempath/test.py"
}
```
I do know it might be a bit tedious but this is required and its the easiest way.
#### After you've decided on how you would name / write your Test write it like this:
- It is important to note that you _CAN_ write the Code the User is expected to write firstly. **BUT** after writing the UnitTest and it passing, comment out the written code.
It is supposed to look something like this (/sortlist/):
```python ```python
"""
@TESTSAMPLE.PY / NAME THIS "test.py" in your actual project
"""
import unittest import unittest
#<!-- The Function the User needs to write --> " )) First Point from the List "
def revstring(x): # def sortlist(lst = [4,3,2,1]) -> list:
return x[::-1] # return sorted(lst)
#<!-- This Test, test if the function works --> ")) This is a 'easy' Test, if you want you can write more defined ones."
class TestSolution(unittest.TestCase): class TestSolution(unittest.TestCase):
def test_simple(self): def test_sort(self):
# !! This needs to be dynamic ; if the user enters some shit then it is supposed to work too self.x = []
x=""; self.assertEqual(sortlist(self.x), sorted(self.x)) # pyright: ignore[reportUndefinedVariable] <- This is only here so that pyright doesnt complain ; NOT NECCESARY!
self.assertEqual(revstring(x), x[::-1])
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
``` ```
#### Writing the description:
**Please** by _God_ write simple and easy to understand terms. If you write like Einstein noone is going to understand you.
- Syntax:
- Normal Markdown.
- Start with "##" instead of "#" ; "##" looks better
- Use CrossLinks ( something like [W3](https://www.w3schools.com/), or the [PyDocs](https://docs.python.org/3/))
- Good Formatting is always appreciated

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
Flask>=3.0
Flask-SQLAlchemy>=3.1
Markdown>=3.6
MarkupSafe>=2.1
watchdog>=4.0

17
run.bash Normal file
View File

@@ -0,0 +1,17 @@
u!/bin/bash
set -e # exit if any command fails
# Ensure QPP/database directory exists
mkdir -p src/database
python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
export FLASK_APP=src.app
export FLASK_ENV=production
flask run --host=0.0.0.0 --port=5000

1
run.bat Normal file
View File

@@ -0,0 +1 @@
python -m flask --app .\src\app.py run --host=0.0.0.0 --port=5000

136
src/app.py Normal file
View File

@@ -0,0 +1,136 @@
from markupsafe import Markup
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
import markdown as md
import ast
from src.models import db, Problem, Solution
from src.utils import run_code_against_tests
from src.leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard
import os
## from problem_loader import load_problems_from_json, schedule_problem_reload
from src.problem_scanner import start_problem_scanner
import sqlite3
from pathlib import Path
app = Flask(__name__)
BASE_DIR = Path(__file__).parent
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{BASE_DIR / 'database' / 'db.sqlite3'}"
print(f">>>>>>>>>>>>>>>>>>>>< Using database URI: {app.config['SQLALCHEMY_DATABASE_URI']}")
db.init_app(app)
@app.before_request
def setup():
db.create_all()
create_leaderboard_table() # Ensure leaderboard table exists
# Problems are now loaded from manifests by the background scanner. No need to load problems.json.
# Start the background thread to scan problems
start_problem_scanner()
@app.route("/script.js")
def script():
return send_from_directory("templates", "script.js")
@app.route('/favicon.ico')
def favicon():
return send_from_directory("templates", "favicon.ico")
@app.route('/')
def index():
db_path = Path(__file__).parent / 'database/problems.sqlite3'
conn = sqlite3.connect(db_path)
c = conn.cursor()
#<!-- The query was fucked up so it fetched the fucking testcode -->
c.execute('SELECT folder, description, test_code, difficulty FROM problems')
problems = c.fetchall()
conn.close()
# Get leaderboard entries
leaderboard = get_leaderboard()
# Map folder to title for display
problem_titles = {folder: folder.replace('_', ' ').title() for folder, _, _, _ in problems}
return render_template('index.html', problems=problems, leaderboard=leaderboard, problem_titles=problem_titles)
@app.route('/problem/new', methods=['GET', 'POST'])
def new_problem():
if request.method == 'POST':
title = request.form['title']
description = request.form['description']
test_code = request.form['test_code']
problem = Problem(title=title, description=description, test_code=test_code)
db.session.add(problem)
db.session.commit()
return redirect(url_for('index'))
return render_template('new_problem.html')
@app.route('/problem/<folder>', methods=['GET', 'POST'])
def view_problem(folder):
db_path = Path(__file__).parent / 'database/problems.sqlite3'
conn = sqlite3.connect(db_path)
c = conn.cursor()
c.execute('SELECT folder, description,test_code , difficulty FROM problems WHERE folder = ?', (folder,))
row = c.fetchone()
conn.close()
if not row:
return 'Problem not found', 404
problem = {
'folder': row[0],
'description': row[1],
'difficulty': row[3], # now correct
'test_code': row[2], # now correct
}
result = None
if request.method == 'POST':
user_code = request.form['user_code']
username = request.form.get('username', '').strip() or 'Anonymous'
import tracemalloc
tracemalloc.start()
run_result = run_code_against_tests(user_code, problem['test_code'])
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
memory_used = peak // 1024 # in KB
# Try to get the last line number executed (even for successful runs)
line_number = None
try:
tree = ast.parse(user_code)
# Find the highest line number in the AST (for multi-function/user code)
def get_max_lineno(node):
max_lineno = getattr(node, 'lineno', 0)
for child in ast.iter_child_nodes(node):
max_lineno = max(max_lineno, get_max_lineno(child))
return max_lineno
line_number = get_max_lineno(tree)
except Exception:
pass
# If there was an error, try to get the error line number from the traceback
if run_result['error']:
tb = run_result['error']
import traceback
try:
tb_lines = traceback.extract_tb(traceback.TracebackException.from_string(tb).stack)
if tb_lines:
line_number = tb_lines[-1].lineno
except Exception:
pass
# ONLY log to leaderboard if the solution passed all tests
if run_result['passed']:
log_leaderboard(username, problem['folder'], run_result['runtime'], memory_used, line_number)
result = run_result
return render_template('problem.html', problem=problem, result=result)
@app.template_filter('markdown')
def markdown_filter(text):
return Markup(md.markdown(text or '', extensions=['extra', 'sane_lists']))
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -2,12 +2,14 @@ from flask_sqlalchemy import SQLAlchemy
from flask import g from flask import g
import os import os
import sqlite3 import sqlite3
from pathlib import Path
def get_db(): def get_db():
db = getattr(g, '_database', None) if 'db' not in g:
if db is None: db_path = Path(__file__).parent / 'database' / 'db.sqlite3'
db = g._database = sqlite3.connect(os.path.join(os.path.dirname(__file__), 'db.sqlite3')) db_path.parent.mkdir(exist_ok=True) # Ensure /database folder exists
return db g.db = sqlite3.connect(db_path)
return g.db
def create_leaderboard_table(): def create_leaderboard_table():
db = get_db() db = get_db()

384
src/problem_scanner.py Normal file
View File

@@ -0,0 +1,384 @@
import os
import time
import json
import sqlite3
import threading
import random
import tempfile
import subprocess
import sys
import traceback
import io
from pathlib import Path
try:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
WATCHDOG_AVAILABLE = True
except ImportError:
WATCHDOG_AVAILABLE = False
PROBLEMS_DIR = Path(__file__).parent / 'problems'
DB_PATH = Path(__file__).parent / 'database/problems.sqlite3'
class ProblemScannerThread(threading.Thread):
def __init__(self, scan_interval=2):
super().__init__(daemon=True)
self.scan_interval = scan_interval
self.last_state = {}
self.observer = None
def create_table(self, conn):
c = conn.cursor()
c.execute('PRAGMA journal_mode=WAL;')
c.execute('''CREATE TABLE IF NOT EXISTS problems (
id INTEGER PRIMARY KEY AUTOINCREMENT,
folder TEXT,
description TEXT,
difficulty TEXT,
test_code TEXT
)''')
conn.commit()
def scan(self):
problems = []
if not PROBLEMS_DIR.exists():
print(f"Problems directory does not exist: {PROBLEMS_DIR}")
return problems
for folder in PROBLEMS_DIR.iterdir():
if folder.is_dir():
# Dynamically find manifest file (manifest.json or manifets.json)
manifest_path = None
for candidate in ["manifest.json", "manifets.json"]:
candidate_path = folder / candidate
if candidate_path.exists():
manifest_path = candidate_path
break
desc_path = folder / 'description.md'
test_path = folder / 'test.py'
# Check if required files exist
if manifest_path and desc_path.exists() and test_path.exists():
try:
with open(desc_path, 'r', encoding='utf-8') as f:
description = f.read()
with open(test_path, 'r', encoding='utf-8') as f:
test_code = f.read()
with open(manifest_path, 'r', encoding='utf-8') as f:
manifest = json.load(f)
difficulty = manifest.get('difficulty', 'unknown')
problems.append({
'folder': folder.name,
'description': description,
'test_code': test_code,
'difficulty': difficulty
})
print(f"Found problem: {folder.name} ; Difficulty: {difficulty}")
except Exception as e:
print(f"Error reading problem files for {folder.name}: {e}")
else:
missing_files = []
if not manifest_path:
missing_files.append("manifest.json/manifets.json")
if not desc_path.exists():
missing_files.append("description.md")
if not test_path.exists():
missing_files.append("test.py")
print(f"Skipping {folder.name}: missing {', '.join(missing_files)}")
print(f"Total problems found: {len(problems)}")
return problems
def update_db(self, problems, retries=5):
for attempt in range(retries):
try:
conn = sqlite3.connect(DB_PATH, timeout=5)
c = conn.cursor()
c.execute('PRAGMA journal_mode=WAL;')
# Clear existing problems
c.execute('DELETE FROM problems')
# Insert new problems
for p in problems:
c.execute('''INSERT INTO problems
(folder, description, difficulty, test_code)
VALUES (?, ?, ?, ?)''',
(p['folder'], p['description'], p['difficulty'], p['test_code']))
conn.commit()
print(f"Updated database with {len(problems)} problems")
conn.close()
return
except sqlite3.OperationalError as e:
if 'locked' in str(e).lower():
wait_time = 0.2 + random.random() * 0.3
print(f"Database locked, retrying in {wait_time:.2f}s (attempt {attempt + 1})")
time.sleep(wait_time)
else:
print(f"Database error: {e}")
raise
except Exception as e:
print(f"Unexpected error updating database: {e}")
raise
print('Failed to update problems DB after several retries due to lock.')
def rescan_and_update(self):
print("Scanning for problems...")
problems = self.scan()
self.update_db(problems)
def run(self):
print("Starting problem scanner...")
# Initial scan and table creation
try:
conn = sqlite3.connect(DB_PATH)
self.create_table(conn)
conn.close()
print("Database initialized")
except Exception as e:
print(f"Failed to initialize database: {e}")
return
# Initial scan
self.rescan_and_update()
if WATCHDOG_AVAILABLE:
print("Using watchdog for file monitoring")
class Handler(FileSystemEventHandler):
def __init__(self, scanner):
self.scanner = scanner
self.last_event_time = 0
def on_any_event(self, event):
# Debounce events to avoid too many rescans
now = time.time()
if now - self.last_event_time > 1: # Wait at least 1 second between rescans
self.last_event_time = now
print(f"File system event: {event.event_type} - {event.src_path}")
self.scanner.rescan_and_update()
event_handler = Handler(self)
self.observer = Observer()
self.observer.schedule(event_handler, str(PROBLEMS_DIR), recursive=True)
self.observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Stopping problem scanner...")
finally:
self.observer.stop()
self.observer.join()
else:
print(f"Watchdog not available, using polling every {self.scan_interval}s")
# Fallback: poll every scan_interval seconds
try:
while True:
time.sleep(self.scan_interval)
self.rescan_and_update()
except KeyboardInterrupt:
print("Stopping problem scanner...")
def start_problem_scanner():
scanner = ProblemScannerThread()
scanner.start()
return scanner
# Flask model loading functions
def load_problems_from_json(json_path):
"""Load problems from JSON file into Flask database"""
if not os.path.exists(json_path):
print(f"Problem JSON file not found: {json_path}")
return
try:
with open(json_path, 'r', encoding='utf-8') as f:
problems = json.load(f)
except Exception as e:
print(f"Error reading JSON file: {e}")
return
# This assumes you have imported the necessary Flask/SQLAlchemy components
try:
from models import db, Problem
for p in problems:
# Check if problem already exists by title
existing = Problem.query.filter_by(title=p['title']).first()
# Load test code from solution file if provided
test_code = ''
if 'solution' in p and os.path.exists(p['solution']):
try:
with open(p['solution'], 'r', encoding='utf-8') as sf:
test_code = sf.read()
except Exception as e:
print(f"Error reading solution file for {p['title']}: {e}")
if existing:
existing.description = p['description']
existing.test_code = test_code
print(f"Updated problem: {p['title']}")
else:
new_problem = Problem(title=p['title'], description=p['description'], test_code=test_code)
db.session.add(new_problem)
print(f"Added new problem: {p['title']}")
db.session.commit()
print("Successfully updated problems from JSON")
except ImportError:
print("Flask models not available - skipping JSON load")
except Exception as e:
print(f"Error loading problems from JSON: {e}")
def schedule_problem_reload(app, json_path, interval_hours=10):
"""Schedule periodic reloading of problems from JSON"""
def reload_loop():
while True:
try:
with app.app_context():
load_problems_from_json(json_path)
time.sleep(interval_hours * 3600)
except Exception as e:
print(f"Error in problem reload loop: {e}")
time.sleep(60) # Wait 1 minute before retrying
t = threading.Thread(target=reload_loop, daemon=True)
t.start()
def run_code_against_tests(user_code, test_code, timeout=10):
"""
Execute user code against test code with proper error handling.
Args:
user_code: The user's solution code
test_code: The test code to validate the solution
timeout: Maximum execution time in seconds
Returns:
dict: Result with passed, output, runtime, and error fields
"""
if not user_code or not user_code.strip():
return {
'passed': False,
'output': '',
'runtime': 0,
'error': 'No code provided'
}
if not test_code or not test_code.strip():
return {
'passed': False,
'output': '',
'runtime': 0,
'error': 'No test code available'
}
start_time = time.perf_counter()
output = ''
error = None
passed = False
temp_file = None
try:
# Check if unittest is used in test_code
if 'unittest' in test_code:
# Create temporary file with user code + test code
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False, encoding='utf-8') as f:
# Combine user code and test code
combined_code = f"{user_code}\n\n{test_code}"
f.write(combined_code)
f.flush()
temp_file = f.name
try:
# Run the file as a subprocess with timeout
proc = subprocess.run(
[sys.executable, temp_file],
capture_output=True,
text=True,
timeout=timeout,
encoding='utf-8'
)
output = proc.stdout
if proc.stderr:
output += f"\nSTDERR:\n{proc.stderr}"
passed = proc.returncode == 0
if not passed:
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
except subprocess.TimeoutExpired:
passed = False
error = f"Code execution timed out after {timeout} seconds"
output = "Execution timed out"
else:
# Direct execution approach for simple assert-based tests
local_ns = {}
# Capture stdout
old_stdout = sys.stdout
captured_output = io.StringIO()
sys.stdout = captured_output
try:
# Execute user code first
exec(user_code, {}, local_ns)
# Execute test code in the same namespace
exec(test_code, local_ns, local_ns)
# If we get here without exceptions, tests passed
passed = True
except AssertionError as e:
passed = False
error = f"Assertion failed: {str(e)}"
except Exception as e:
passed = False
error = f"Runtime error: {traceback.format_exc()}"
finally:
output = captured_output.getvalue()
sys.stdout = old_stdout
except Exception as e:
passed = False
error = f"Execution error: {traceback.format_exc()}"
finally:
# Clean up temporary file
if temp_file and os.path.exists(temp_file):
try:
os.unlink(temp_file)
except Exception as e:
print(f"Warning: Could not delete temp file {temp_file}: {e}")
runtime = time.perf_counter() - start_time
result = {
'passed': passed,
'output': output.strip() if output else '',
'runtime': runtime,
'error': error if not passed else None
}
print(f"Test execution result: passed={passed}, runtime={runtime:.3f}s")
if error:
print(f"Error: {error}")
return result

View File

@@ -0,0 +1,60 @@
## Problem: Check if a String is a Palindrome
Given a string `s`, determine whether it reads the same forward and backward.
Return `True` if it is a palindrome, otherwise return `False`.
A **palindrome** is a sequence of characters that is identical when reversed.
Comparison is **case-sensitive** and should consider all characters, including spaces and punctuation.
---
### Example 1
**Input:**
```
s = "racecar"
```
**Output:**
```
True
```
**Explanation:**
Reversing `"racecar"` results in `"racecar"`, which is the same as the original string.
---
### Example 2
**Input:**
```
s = "hello"
```
**Output:**
```
False
```
**Explanation:**
Reversing `"hello"` results in `"olleh"`, which is different from the original string.
---
### Constraints
* `0 <= len(s) <= 10^5`
* `s` may contain letters, digits, symbols, and spaces.
---
### Function Signature (Python)
```python
def palindrome(s: str) -> bool:
```

View File

@@ -0,0 +1,7 @@
{
"title": "Palindrome",
"description": "Find out wether or not a String is a Palindrome",
"description_md": "problems/Palindrome/description.md",
"test_code": "problems/Palindrome/test.py",
"difficulty": "medium"
}

View File

@@ -0,0 +1,32 @@
import unittest
#<!-- User expected Function -->
## def palindrome(s:str) -> bool:
## return s == s[::-1]
class TestSolution(unittest.TestCase):
def test_palindrome(self):
test_cases = [
("racecar", True), # Simple palindrome
("hello", False), # Not a palindrome
("", True), # Empty string
("a", True), # Single character
("madam", True), # Palindrome word
("Madam", False), # Case-sensitive check
("12321", True), # Numeric string palindrome
("123456", False), # Numeric string non-palindrome
]
print("\nFUNCTION OUTPUT TEST RESULTS")
for input_val, expected in test_cases:
try:
actual = palindrome(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{input_val}' -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,44 @@
## Reverse a List
Write a function called `reverse_list` that takes a list as input and returns the list in reverse order.
You are **not allowed** to just use Pythons built-in `.reverse()` method or slicing (`[::-1]`) — try to reverse it manually for practice.
### Function Signature:
```python
def reverse_list(lst):
# your code here
```
### Requirements
* The function should return a new list with the elements in reversed order.
* The input list can contain:
* Numbers
* Strings
* Booleans
* A mix of different types
* Your function will be tested with:
* A small list (e.g., `[1, 2, 3]``[3, 2, 1]`)
* A longer list (e.g., `[1, 2, 3, 4]``[4, 3, 2, 1]`)
* An empty list (should return an empty list)
* A single-element list (should return the same list)
* A mixed-type list (e.g., `[1, 'a', True]``[True, 'a', 1]`)
### Example
```python
reverse_list([1, 2, 3])
# Output: [3, 2, 1]
reverse_list([])
# Output: []
reverse_list([5])
# Output: [5]
reverse_list([1, 'a', True])
# Output: [True, 'a', 1]
```

View File

@@ -0,0 +1,7 @@
{
"title": "Reversed List",
"description": "Given a list, return a new list with the elements in reverse order.",
"description_md": "problems/reversedlist/description.md",
"difficulty": "easy",
"test_code": "problems/reversedlist/test.py"
}

View File

@@ -0,0 +1,27 @@
import unittest
#def reverse_list(lst : list) -> list:
#return lst[::-1]
class TestSolution(unittest.TestCase):
def test_simple(self):
test_cases = [
([1, 2, 3], [3, 2, 1]), # Simple case
([1, 2, 3, 4], [4, 3, 2, 1]), # Longer list
([], []), # Empty list
([5], [5]), # Single element list
([1, 'a', True], [True, 'a', 1]) # Mixed types
]
print("\n FUNCTION OUTPUT TEST RESULTS")
for input_val , expected in test_cases:
try:
actual = reverse_list(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: {input_val} -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: {input_val} -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,40 @@
## Fibonacci Number
Write a function called `fibonacci` that takes a non-negative integer `n` as input and returns the **n-th Fibonacci number**.
The Fibonacci sequence is defined as:
* `F(0) = 0`
* `F(1) = 1`
* `F(n) = F(n-1) + F(n-2)` for `n > 1`
### Function Signature:
```python
def fibonacci(n):
# return your solution
```
#### Requirements
* The function should return the `n`-th number in the Fibonacci sequence.
* If `n` is less than `0`, print `"Incorrect input"`.
* Your function will be tested with:
* Base cases (`n = 0` and `n = 1`)
* Small values of `n`
* Larger values of `n` (e.g., 9)
* Multiple test cases in sequence
#### Example:
```python
fibonacci(0) # returns 0
fibonacci(1) # returns 1
fibonacci(2) # returns 1
fibonacci(3) # returns 2
fibonacci(5) # returns 5
fibonacci(9) # returns 34
```
You can copy this into your problems solution description.

View File

@@ -0,0 +1,7 @@
{
"title": "Fibonacci Sequence",
"description": "Calculate the n-th Fibonacci number using a function. The Fibonacci sequence is defined as follows: F(0) = 0, F(1) = 1, and F(n) = F(n-1) + F(n-2) for n > 1.",
"description_md": "problems/fibonacisequence/description.md",
"difficulty": "medium",
"test_code": "problems/fibonacisequence/test.py"
}

View File

@@ -0,0 +1,52 @@
import unittest
class TestSolution(unittest.TestCase):
def test_simple(self):
test_cases = [
(0, 0), # Base case: n = 0
(1, 1), # Base case: n = 1
(2, 1), # Fibonacci(2) = 1
(3, 2), # Fibonacci(3) = 2
(5, 5), # Fibonacci(5) = 5
(9, 34), # Fibonacci(9) = 34
]
print("\n=== Function Output Test Results ===")
for input_val, expected in test_cases:
try:
actual = fibonacci(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: {input_val} -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: {input_val} -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)
"""
def fibonacci(n):
a = 0
b = 1
# Check if n is less than 0
if n < 0:
print("Incorrect input")
# Check if n is equal to 0
elif n == 0:
return 0
# Check if n is equal to 1
elif n == 1:
return b
else:
for i in range(1, n):
c = a + b
a = b
b = c
return b
print(fibonacci(9))
"""

View File

@@ -0,0 +1,58 @@
# Phone Number Regular Expression Validation
You are asked to write a function that checks if a given string is a valid phone number.
A valid phone number must follow this format:
```python
123-456-7890
```
* It contains **3 digits**, followed by a **dash (-)**
* Then another **3 digits**, followed by a **dash (-)**
* Then exactly **4 digits**
If the string matches this exact format, return **True**. Otherwise, return **False**.
---
### Example 1
```python
Input: "123-456-7890"
Output: True
```
### Example 2
```python
Input: "1234567890"
Output: False
```
### Example 3
```python
Input: "abc-def-ghij"
Output: False
```
---
### Function Signature
```python
import re
def is_valid_phone_number(phone_number: str) -> bool:
return bool("Your Solution Here!")
```
---
### Hint 🔑
* Use the **`re`** (regular expression) library.
* `\d` means “a digit” in regex.
* You will need exactly **3 digits**, then a dash, then **3 digits**, another dash, then **4 digits**.
* Anchors `^` (start of string) and `$` (end of string) can help ensure the whole string matches.

View File

@@ -0,0 +1,7 @@
{
"title": "Regex Phonenumber",
"description": "A regex problem to match phone numbers in various formats.",
"description_md": "problems/regex-phone/description.md",
"difficulty": "hard",
"test_code": "problems/regex-phone/test.py"
}

View File

@@ -0,0 +1,33 @@
import re
import unittest
## def is_valid_phone_number(phone_number : str):
## return bool(re.search(r"^(\d{3}-){2}\d{4}$", phone_number))
import unittest
class TestPhoneNumberRegex(unittest.TestCase):
def test_if_valid(self):
test_cases = [
("123-456-7890", True), # Valid format
("111-222-3333", True), # Another valid format
("abc-def-ghij", False), # Letters instead of digits
("1234567890", False), # Missing dashes
("123-45-67890", False), # Wrong grouping
("12-3456-7890", False), # Wrong grouping again
("", False), # Empty string
]
print("\nPHONE NUMBER VALIDATION TEST RESULTS")
for phone, expected in test_cases:
try:
actual = is_valid_phone_number(phone) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{phone}' -> Got: {actual} | Expected: {expected}")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{phone}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1,29 @@
## Reversed String
Write a function called ```revstring``` that takes a string as input and returns the reversed string.
### Function Signature:
```python
def revstring(x).
# return your solution
```
#### Requirements
- The function should return the input string reversed
- Your function will be tested with various cases, including:
- An empty string
- A single character
- A palindrome ("racecar")
- A string of numbers ("12345")
- Special characters
- A normal string ( "Hello World" )
#### Example:
```python
revstring("Hello World") # returns "dlroW olleH"
revstring("") # returns ""
revstring("racecar") # returns "racecar"
revstring("12345") # returns "54321"
revstring("!@# $%") # returns "%$ #@!"
```
You can copy this into your problems solution

View File

@@ -0,0 +1,7 @@
{
"title":"Reversed String",
"description":"Reverse a String using a Function ; Try to write as little code as possible",
"description_md":"problems/reversedstring/description.md",
"difficulty":"easy",
"test_code":"problems/reversedstring/test.py"
}

View File

@@ -0,0 +1,26 @@
import unittest
class TestSolution(unittest.TestCase):
def test_simple(self):
test_cases = [
("Hello World", "dlroW olleH"),
("", ""),
("a", "a"),
("racecar", "racecar"),
("12345", "54321"),
("!@# $%", "%$ #@!")
]
print("\n=== Function Output Test Results ===")
for input_val, expected in test_cases:
try:
actual = revstring(input_val) # pyright: ignore[reportUndefinedVariable]
status = "✓ PASS" if actual == expected else "✗ FAIL"
print(f"{status} | Input: '{input_val}' -> Got: '{actual}' | Expected: '{expected}'")
self.assertEqual(actual, expected)
except Exception as e:
print(f"✗ ERROR | Input: '{input_val}' -> Exception: {e}")
raise
if __name__ == "__main__":
unittest.main(verbosity=2)

View File

@@ -0,0 +1 @@
this is a easy sorting problem **it is solvable in less than 2 seconds**

View File

@@ -0,0 +1,7 @@
{
"title": "Sort List",
"description": "Sort a List with a Function (sortlist); the function is supposed to take the list as an argument and is supposed to return the sorted list and print it.",
"description_md": "problems/sortlist/description.md",
"difficulty": "easy",
"test_code": "problems/sortlist/test.py"
}

View File

@@ -0,0 +1,17 @@
import unittest
# This is the function the user is expected to write.
# Its a really simple one, the user can choose not to type tho.
#def sortlist(lst = [4,3,2,1]) -> list:
#return sorted(lst)
class TestSolution(unittest.TestCase):
def test_sort(self):
# define x as a empty array.
# this will be used for the functiun ; a empty var does not work.
self.x = []
self.assertEqual(sortlist(self.x), sorted(self.x)) # pyright: ignore[reportUndefinedVariable]
if __name__ == "__main__":
unittest.main()

BIN
src/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

326
src/static/index.css Normal file
View File

@@ -0,0 +1,326 @@
:root {
--bg: #f6f8fb;
--card: #fff;
--text: #0f172a;
--muted: #6b7280;
--accent: #2563eb;
--border: #e5e7eb;
--hover: #f3f4f6;
--shadow: 0 4px 12px rgba(16, 24, 40, 0.06);
--radius: 8px;
--mono: "JetBrains Mono", monospace;
}
/* Dark mode variables */
html.dark {
--bg: #0f172a;
--card: #1e293b;
--text: #f1f5f9;
--muted: #94a3b8;
--accent: #3b82f6;
--border: #334155;
--hover: #334155;
--shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
body {
font-family: Inter, sans-serif;
background: var(--bg);
color: var(--text);
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
transition:
background-color 0.3s ease,
color 0.3s ease;
}
.wrap {
width: 100%;
max-width: 1100px;
}
header {
margin-bottom: 14px;
}
.header-content {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
header h1 {
text-align: center;
font-size: 1.6rem;
color: var(--text);
}
header p {
color: var(--muted);
font-size: 0.9rem;
}
.dark-mode-toggle {
position: absolute;
right: 0;
background: none;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 6px 10px;
cursor: pointer;
color: var(--text);
font-size: 1.2rem;
transition: all 0.3s ease;
}
.dark-mode-toggle:hover {
background: var(--hover);
transform: scale(1.05);
}
html.dark .dark-mode-icon::before {
content: "☀︎️";
}
html:not(.dark) .dark-mode-icon::before {
content: "⏾";
}
.dark-mode-icon {
display: inline-block;
}
.dark-mode-icon::before {
font-size: 1em;
}
.content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.content.single-column {
grid-template-columns: 1fr;
}
.card {
background: var(--card);
border-radius: var(--radius);
box-shadow: var(--shadow);
padding: 12px;
}
/* Search/filter controls */
.search-controls {
margin-bottom: 12px;
display: flex;
gap: 8px;
}
.search-input {
flex: 1;
padding: 6px 10px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.9rem;
background: var(--card);
color: var(--text);
transition: border-color 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: var(--accent);
}
.filter-select {
padding: 6px 8px;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.9rem;
background: var(--card);
color: var(--text);
transition: border-color 0.3s ease;
}
.filter-select:focus {
outline: none;
border-color: var(--accent);
}
/* Problems list */
.problems-list .problem-item {
padding: 8px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease;
}
.problem-item:hover {
background: var(--hover);
}
.problem-item:last-child {
border-bottom: none;
}
.problem-item a {
text-decoration: none;
color: var(--accent);
font-weight: 600;
transition: color 0.3s ease;
}
.problem-item a:hover {
text-decoration: underline;
}
/* Difficulty badge */
.difficulty {
display: inline-flex;
align-items: center;
padding: 0.25em 0.6em;
border-radius: 10px;
font-size: 0.85em;
font-weight: bold;
text-transform: uppercase;
color: white;
white-space: nowrap;
}
.difficulty[data-difficulty="easy"] {
background-color: #4caf50; /* Green */
}
.difficulty[data-difficulty="medium"] {
background-color: #ffc107; /* Amber */
color: #333;
}
.difficulty[data-difficulty="hard"] {
background-color: #f44336; /* Red */
}
/* Leaderboard */
.leaderboard-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.leaderboard-controls {
display: flex;
gap: 8px;
margin-bottom: 12px;
}
.leaderboard-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.leaderboard-table th,
.leaderboard-table td {
padding: 6px 8px;
border-bottom: 1px solid var(--border);
text-align: left;
}
.leaderboard-table th {
background: var(--hover);
font-weight: 600;
color: var(--muted);
}
.leaderboard-table tr:hover {
background: var(--hover);
}
/* Sort indicators */
.sortable {
cursor: pointer;
position: relative;
padding-right: 16px;
}
.sortable::after {
content: "↕";
position: absolute;
right: 4px;
top: 50%;
transform: translateY(-50%);
font-size: 0.8em;
opacity: 0.5;
}
.sort-asc::after {
content: "↑";
opacity: 1;
}
.sort-desc::after {
content: "↓";
opacity: 1;
}
/* Toggle button */
.btn {
border: none;
background: transparent;
cursor: pointer;
color: var(--accent);
font-size: 0.85rem;
padding: 4px 6px;
border-radius: 4px;
}
.btn:hover {
background: rgba(37, 99, 235, 0.08);
}
.btn.active {
background: rgba(37, 99, 235, 0.15);
}
@media (max-width: 800px) {
.content {
grid-template-columns: 1fr;
}
.leaderboard-controls {
flex-direction: column;
}
}
/* Leaderboard horizontal collapse */
#leaderboardSection {
transition:
max-width 0.35s ease,
opacity 0.25s ease;
overflow: hidden;
max-width: 100%;
}
#leaderboardSection.hidden {
max-width: 0;
opacity: 0;
pointer-events: none;
}
#leaderboardSection.visible {
max-width: 100%; /* take full available space in grid column */
opacity: 1;
}
#rankingExplanation {
transition: all 0.35s ease;
}
/* Pagination Controls */
.pagination-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid var(--border);
}
.pagination-btn {
background: var(--card);
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
color: var(--text);
font-size: 0.9rem;
transition: all 0.3s ease;
}
.pagination-btn:hover:not(:disabled) {
background: var(--hover);
border-color: var(--accent);
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-info {
color: var(--muted);
font-size: 0.9rem;
}
/* Hide pagination when not needed */
.pagination-controls.hidden {
display: none;
}

296
src/static/problem.css Normal file
View File

@@ -0,0 +1,296 @@
:root {
--bg: #f9f9f9;
--card: #fff;
--text: #333;
--muted: #666;
--accent: #007bff;
--accent-hover: #0069d9;
--border: #eaeaea;
--hover: #f8f9fa;
--code-bg: #f6f8fa;
--editor-border: #ddd;
}
html.dark {
--bg: #0f172a;
--card: #1e293b;
--text: #f1f5f9;
--muted: #94a3b8;
--accent: #3b82f6;
--accent-hover: #2563eb;
--border: #334155;
--hover: #334155;
--code-bg: #1e293b;
--editor-border: #475569;
}
body {
font-family: "Inter", sans-serif;
margin: 0;
padding: 0;
background-color: var(--bg);
color: var(--text);
min-height: 100vh; /* allow content to grow */
overflow-y: auto; /* allow vertical scroll */
box-sizing: border-box;
transition:
background-color 0.3s ease,
color 0.3s ease;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
.main-container {
display: flex;
flex-wrap: wrap; /* wrap on small screens */
min-height: 100vh;
width: 100vw;
}
.problem-panel {
flex: 1 1 400px; /* grow/shrink with base 400px */
min-width: 300px;
background: var(--card);
overflow-y: auto;
padding: 20px;
border-right: 1px solid var(--border);
max-height: 100vh;
transition: background-color 0.3s ease;
}
.editor-container {
flex: 1 1 400px;
min-width: 300px;
display: flex;
flex-direction: column;
background: var(--card);
max-height: 100vh;
overflow: hidden; /* internal scroll handling */
transition: background-color 0.3s ease;
}
.editor-header {
padding: 15px 20px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.editor-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: column;
min-height: 0;
padding: 0 20px;
overflow-y: auto;
}
.problem-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.back-btn {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
cursor: pointer;
font-size: 16px;
color: var(--muted);
margin-right: 15px;
padding: 6px 10px;
transition: all 0.3s ease;
}
.back-btn:hover {
color: var(--text);
background: var(--hover);
}
.dark-mode-toggle {
background: none;
border: 1px solid var(--border);
border-radius: 4px;
padding: 6px 10px;
cursor: pointer;
color: var(--text);
font-size: 1.2rem;
transition: all 0.3s ease;
}
.dark-mode-toggle:hover {
background: var(--hover);
transform: scale(1.05);
}
html.dark .dark-mode-icon::before {
content: "☀";
}
html:not(.dark) .dark-mode-icon::before {
content: "⏾";
}
.dark-mode-icon {
display: inline-block;
}
.dark-mode-icon::before {
font-size: 1em;
}
h1 {
font-size: 22px;
font-weight: 600;
margin: 0;
color: var(--text);
flex: 1;
}
.problem-desc {
line-height: 1.6;
font-size: 15px;
overflow-wrap: break-word;
}
.problem-desc pre {
background: var(--code-bg);
padding: 12px;
border-radius: 4px;
overflow-x: auto;
font-family: "JetBrains Mono", monospace;
font-size: 14px;
border: 1px solid var(--border);
}
.problem-desc code {
background: var(--code-bg);
padding: 2px 4px;
border-radius: 3px;
font-family: "JetBrains Mono", monospace;
font-size: 14px;
}
.editor-actions {
padding: 15px 0;
display: flex;
justify-content: flex-end;
flex-shrink: 0;
}
.editor-actions button {
background-color: var(--accent);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
font-size: 14px;
transition: background-color 0.3s ease;
}
.editor-actions button:hover {
background-color: var(--accent-hover);
}
#editor {
flex: 1 1 auto;
min-height: 300px;
border: 1px solid var(--editor-border);
border-radius: 4px;
overflow: auto;
max-height: 60vh;
}
.result-panel {
margin-top: 20px;
padding: 15px;
background: var(--hover);
border-radius: 4px;
margin-bottom: 20px;
min-height: 120px;
overflow-y: auto;
max-height: 30vh;
border: 1px solid var(--border);
transition: background-color 0.3s ease;
}
.result-panel h3 {
margin-top: 0;
font-size: 16px;
margin-bottom: 10px;
}
.result-panel pre {
background: var(--code-bg);
padding: 12px;
border-radius: 4px;
overflow-x: auto;
white-space: pre-wrap;
font-family: "JetBrains Mono", monospace;
font-size: 14px;
margin: 5px 0;
border: 1px solid var(--border);
}
.placeholder {
color: var(--muted);
font-style: italic;
text-align: center;
padding: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-size: 14px;
color: var(--muted);
}
input[type="text"] {
width: 100%;
padding: 8px;
border: 1px solid var(--border);
border-radius: 4px;
margin-bottom: 15px;
font-family: "Inter", sans-serif;
background: var(--card);
color: var(--text);
transition: border-color 0.3s ease;
}
input[type="text"]:focus {
outline: none;
border-color: var(--accent);
}
/* Responsive adjustments */
@media (max-width: 768px) {
.main-container {
flex-direction: column;
height: auto;
overflow-y: visible;
}
.problem-panel,
.editor-container {
flex: none;
width: 100%;
min-width: auto;
max-height: none;
border-right: none;
border-bottom: 1px solid var(--border);
}
#editor {
min-height: 400px;
max-height: none;
}
.result-panel {
max-height: none;
}
}

403
src/static/script.js Normal file
View File

@@ -0,0 +1,403 @@
document.addEventListener("DOMContentLoaded", () => {
// Dark mode functionality
const darkModeToggle = document.getElementById("darkModeToggle");
const html = document.documentElement;
// Load saved dark mode preference
const savedDarkMode = localStorage.getItem("darkMode");
if (
savedDarkMode === "true" ||
(savedDarkMode === null &&
// detect if the user already has a dark mode enabled in the system settings ( works for all systems )
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
html.classList.add("dark");
}
darkModeToggle?.addEventListener("click", () => {
html.classList.toggle("dark");
localStorage.setItem("darkMode", html.classList.contains("dark"));
});
// Problem search and pagination
const problemSearch = document.getElementById("problemSearch");
const problemsContainer = document.getElementById("problemsContainer");
const problemsPagination = document.getElementById("problemsPagination");
const problemsPrevBtn = document.getElementById("problemsPrevBtn");
const problemsNextBtn = document.getElementById("problemsNextBtn");
const problemsPaginationInfo = document.getElementById(
"problemsPaginationInfo",
);
let allProblemItems = [];
let filteredProblemItems = [];
let currentPage = 1;
const itemsPerPage = 5;
// Initialize problem items
function initializeProblemItems() {
allProblemItems = Array.from(
problemsContainer?.querySelectorAll(".problem-item") || [],
);
filteredProblemItems = [...allProblemItems];
updatePagination();
}
function updatePagination() {
const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
// Hide all items first
allProblemItems.forEach((item) => {
item.style.display = "none";
});
// Show current page items
filteredProblemItems.slice(startIndex, endIndex).forEach((item) => {
item.style.display = "";
});
// Update pagination controls
if (problemsPrevBtn) problemsPrevBtn.disabled = currentPage <= 1;
if (problemsNextBtn) problemsNextBtn.disabled = currentPage >= totalPages;
if (problemsPaginationInfo) {
problemsPaginationInfo.textContent =
totalPages > 0
? `Page ${currentPage} of ${totalPages}`
: "No problems found";
}
// Hide pagination if not needed
if (problemsPagination) {
problemsPagination.classList.toggle("hidden", totalPages <= 1);
}
}
function filterProblems() {
const term = problemSearch?.value.toLowerCase().trim() || "";
filteredProblemItems = allProblemItems.filter((item) => {
const name = item.dataset.name?.toLowerCase() || "";
const desc = item.dataset.desc?.toLowerCase() || "";
return !term || name.includes(term) || desc.includes(term);
});
currentPage = 1;
updatePagination();
}
// Event listeners for pagination
problemsPrevBtn?.addEventListener("click", () => {
if (currentPage > 1) {
currentPage--;
updatePagination();
}
});
problemsNextBtn?.addEventListener("click", () => {
const totalPages = Math.ceil(filteredProblemItems.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
updatePagination();
}
});
problemSearch?.addEventListener("input", filterProblems);
// Initialize problems pagination
if (problemsContainer) {
initializeProblemItems();
}
// Leaderboard functionality
const problemFilter = document.getElementById("problemFilter");
const runtimeFilter = document.getElementById("runtimeFilter");
const leaderboardBody = document.getElementById("leaderboardBody");
const sortableHeaders = document.querySelectorAll(".sortable");
let currentSort = { column: "rank", direction: "asc" };
let allRows = [];
// Initialize rows array
function initializeRows() {
allRows = Array.from(leaderboardBody.querySelectorAll("tr")).map((row) => {
return {
element: row,
user: row.dataset.user || "",
problem: row.dataset.problem || "",
runtime: parseFloat(row.dataset.runtime) || 0,
memory: parseFloat(row.dataset.memory) || 0,
timestamp: new Date(row.dataset.timestamp || Date.now()).getTime(),
language: row.dataset.language || "",
originalIndex: Array.from(leaderboardBody.children).indexOf(row),
};
});
}
function updateRankClasses() {
const visibleRows = allRows.filter(
(row) => row.element.style.display !== "none",
);
visibleRows.forEach((rowData, index) => {
const rank = index + 1;
const row = rowData.element;
// Update rank cell
const rankCell = row.cells[0];
if (rankCell) rankCell.textContent = rank;
// Update rank classes
row.className = row.className.replace(/\brank-\d+\b/g, "");
if (rank === 1) row.classList.add("rank-1");
else if (rank <= 3) row.classList.add("rank-top3");
});
}
function calculateOverallRanking() {
const visibleRows = allRows.filter(
(row) => row.element.style.display !== "none",
);
if (visibleRows.length === 0) return;
// Group submissions by problem to find the best performance for each
const problemBests = {};
visibleRows.forEach((rowData) => {
const problem = rowData.problem;
if (!problemBests[problem]) {
problemBests[problem] = {
bestRuntime: Infinity,
bestMemory: Infinity,
};
}
problemBests[problem].bestRuntime = Math.min(
problemBests[problem].bestRuntime,
rowData.runtime,
);
problemBests[problem].bestMemory = Math.min(
problemBests[problem].bestMemory,
rowData.memory,
);
});
// Calculate normalized scores for each submission
visibleRows.forEach((rowData) => {
const problemBest = problemBests[rowData.problem];
// Prevent division by zero
const runtimeScore =
problemBest.bestRuntime > 0
? rowData.runtime / problemBest.bestRuntime
: 1;
const memoryScore =
problemBest.bestMemory > 0
? rowData.memory / problemBest.bestMemory
: 1;
// Weighted overall score (70% runtime, 30% memory)
rowData.overallScore = runtimeScore * 0.7 + memoryScore * 0.3;
});
// Sort by overall score (lower is better), then by timestamp (earlier is better for ties)
visibleRows.sort((a, b) => {
const scoreDiff = a.overallScore - b.overallScore;
if (Math.abs(scoreDiff) > 0.000001) return scoreDiff; // Use small epsilon for float comparison
// If scores are essentially equal, prefer earlier submission
return a.timestamp - b.timestamp;
});
// Reorder DOM elements and update ranks
visibleRows.forEach((rowData, index) => {
leaderboardBody.appendChild(rowData.element);
});
updateRankClasses();
}
function filterLeaderboard() {
const problemTerm = (problemFilter?.value || "").toLowerCase().trim();
const runtimeType = runtimeFilter?.value || "all";
// Reset all rows to visible first
allRows.forEach((rowData) => {
rowData.element.style.display = "";
});
// Apply problem filter
if (problemTerm) {
allRows.forEach((rowData) => {
const problemMatch = rowData.problem
.toLowerCase()
.includes(problemTerm);
if (!problemMatch) {
rowData.element.style.display = "none";
}
});
}
// Apply runtime filter (best/worst per user per problem)
if (runtimeType === "best" || runtimeType === "worst") {
const userProblemGroups = {};
// Group by user + problem combination
allRows.forEach((rowData) => {
if (rowData.element.style.display === "none") return;
const key = `${rowData.user}::${rowData.problem}`;
if (!userProblemGroups[key]) {
userProblemGroups[key] = [];
}
userProblemGroups[key].push(rowData);
});
// Hide all except best/worst for each user-problem combination
Object.values(userProblemGroups).forEach((group) => {
if (group.length <= 1) return;
// Sort by runtime
group.sort((a, b) => a.runtime - b.runtime);
const keepIndex = runtimeType === "best" ? 0 : group.length - 1;
group.forEach((rowData, index) => {
if (index !== keepIndex) {
rowData.element.style.display = "none";
}
});
});
}
calculateOverallRanking();
}
function getCellValue(rowData, column) {
switch (column) {
case "rank":
return parseInt(rowData.element.cells[0]?.textContent) || 0;
case "user":
return rowData.user.toLowerCase();
case "problem":
return rowData.problem.toLowerCase();
case "runtime":
return rowData.runtime;
case "memory":
return rowData.memory;
case "timestamp":
return rowData.timestamp;
case "language":
return rowData.language.toLowerCase();
default:
return "";
}
}
function sortLeaderboard(column, direction) {
if (column === "rank") {
calculateOverallRanking();
return;
}
const visibleRows = allRows.filter(
(row) => row.element.style.display !== "none",
);
visibleRows.sort((a, b) => {
const valueA = getCellValue(a, column);
const valueB = getCellValue(b, column);
let comparison = 0;
if (typeof valueA === "number" && typeof valueB === "number") {
comparison = valueA - valueB;
} else {
comparison = valueA < valueB ? -1 : valueA > valueB ? 1 : 0;
}
return direction === "asc" ? comparison : -comparison;
});
// Reorder DOM elements
visibleRows.forEach((rowData) => {
leaderboardBody.appendChild(rowData.element);
});
updateRankClasses();
}
// Event listeners for sorting
sortableHeaders.forEach((header) => {
header.addEventListener("click", () => {
const column = header.dataset.sort;
if (!column) return;
// Remove sorting classes from all headers
sortableHeaders.forEach((h) =>
h.classList.remove("sort-asc", "sort-desc"),
);
// Toggle sort direction
if (currentSort.column === column) {
currentSort.direction =
currentSort.direction === "asc" ? "desc" : "asc";
} else {
currentSort.column = column;
currentSort.direction = "asc";
}
// Add sorting class to current header
header.classList.add(`sort-${currentSort.direction}`);
sortLeaderboard(column, currentSort.direction);
});
});
// Filter event listeners
problemFilter?.addEventListener("input", filterLeaderboard);
runtimeFilter?.addEventListener("change", filterLeaderboard);
// Rank info popout
const rankInfoBtn = document.getElementById("rankInfoBtn");
const rankingExplanation = document.getElementById("rankingExplanation");
rankInfoBtn?.addEventListener("click", (e) => {
e.preventDefault();
rankingExplanation?.classList.toggle("active");
rankInfoBtn?.classList.toggle("active");
});
// Close ranking explanation when clicking outside
document.addEventListener("click", (e) => {
if (
rankingExplanation?.classList.contains("active") &&
!rankingExplanation.contains(e.target) &&
!rankInfoBtn?.contains(e.target)
) {
rankingExplanation.classList.remove("active");
rankInfoBtn?.classList.remove("active");
}
});
// Initialize everything
if (leaderboardBody && leaderboardBody.children.length > 0) {
initializeRows();
calculateOverallRanking();
// Set initial sort indicator
const defaultHeader = document.querySelector('[data-sort="rank"]');
if (defaultHeader) {
defaultHeader.classList.add("sort-asc");
}
}
// Apply dark mode to dynamically created elements
function applyDarkModeToElements() {
const isDark = html.classList.contains("dark");
// Any additional dark mode styling for dynamically created elements can go here
}
// Watch for dark mode changes
new MutationObserver(applyDarkModeToElements).observe(html, {
attributes: true,
attributeFilter: ["class"],
});
});

View File

@@ -1,3 +1,49 @@
:root {
--bg: #f8f9fa;
--card: #fff;
--text: #333;
--heading: #2c3e50;
--heading-secondary: #34495e;
--accent: #3498db;
--accent-hover: #2980b9;
--success: #27ae60;
--success-hover: #229954;
--error: #e74c3c;
--muted: #6c757d;
--muted-hover: #5a6268;
--border: #ddd;
--code-bg: #f4f4f4;
--success-bg: #d4edda;
--success-text: #155724;
--error-bg: #f8d7da;
--error-text: #721c24;
--hover-bg: #e3f2fd;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
html.dark {
--bg: #0f172a;
--card: #1e293b;
--text: #f1f5f9;
--heading: #3b82f6;
--heading-secondary: #94a3b8;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #22c55e;
--success-hover: #16a34a;
--error: #ef4444;
--muted: #64748b;
--muted-hover: #475569;
--border: #334155;
--code-bg: #1e293b;
--success-bg: #065f46;
--success-text: #d1fae5;
--error-bg: #7f1d1d;
--error-text: #fecaca;
--hover-bg: #1e40af;
--shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
/* Reset and base styles */ /* Reset and base styles */
* { * {
margin: 0; margin: 0;
@@ -6,39 +52,43 @@
} }
body { body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
line-height: 1.6; line-height: 1.6;
color: #333; color: var(--text);
background-color: #f8f9fa; background-color: var(--bg);
padding: 20px; padding: 20px;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
transition:
background-color 0.3s ease,
color 0.3s ease;
} }
/* Main heading */ /* Main heading */
h1 { h1 {
color: #2c3e50; color: var(--heading);
margin-bottom: -10px; margin-bottom: -10px;
padding-bottom: 3px; padding-bottom: 3px;
border-bottom: 3px solid #3498db; border-bottom: 3px solid var(--accent);
font-size: 2.2em; font-size: 2.2em;
} }
h2 { h2 {
color: #34495e; color: var(--heading-secondary);
margin: 30px 0 20px 0; margin: 30px 0 20px 0;
font-size: 1.5em; font-size: 1.5em;
} }
h3 { h3 {
color: #34495e; color: var(--heading-secondary);
margin: 25px 0 15px 0; margin: 25px 0 15px 0;
font-size: 1.3em; font-size: 1.3em;
} }
/* Links and buttons */ /* Links and buttons */
a { a {
color: #3498db; color: var(--accent);
text-decoration: none; text-decoration: none;
padding: 8px 16px; padding: 8px 16px;
border-radius: 5px; border-radius: 5px;
@@ -46,13 +96,13 @@ a {
} }
a:hover { a:hover {
background-color: #e3f2fd; background-color: var(--hover-bg);
text-decoration: none; text-decoration: none;
} }
/* Primary action link (Submit New Problem) */ /* Primary action link (Submit New Problem) */
a[href="/problem/new"] { a[href="/problem/new"] {
background-color: #3498db; background-color: var(--accent);
color: white; color: white;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
@@ -62,22 +112,23 @@ a[href="/problem/new"] {
} }
a[href="/problem/new"]:hover { a[href="/problem/new"]:hover {
background-color: #2980b9; background-color: var(--accent-hover);
} }
/* Problem list */ /* Problem list */
ul { ul {
list-style: none; list-style: none;
background: white; background: var(--card);
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: var(--shadow);
padding: 25px; padding: 25px;
margin: 20px 0; margin: 20px 0;
transition: background-color 0.3s ease;
} }
li { li {
padding: 15px 0; padding: 15px 0;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--border);
} }
li:last-child { li:last-child {
@@ -93,7 +144,7 @@ li a {
} }
li a:hover { li a:hover {
background-color: #f8f9fa; background-color: var(--hover-bg);
transform: translateX(5px); transform: translateX(5px);
transition: all 0.2s ease; transition: all 0.2s ease;
} }
@@ -107,7 +158,7 @@ li a:hover {
} }
.back-btn { .back-btn {
background-color: #95a5a6; background-color: var(--muted);
color: white; color: white;
border: none; border: none;
padding: 10px 20px; padding: 10px 20px;
@@ -119,30 +170,32 @@ li a:hover {
} }
.back-btn:hover { .back-btn:hover {
background-color: #7f8c8d; background-color: var(--muted-hover);
} }
.problem-desc { .problem-desc {
background: white; background: var(--card);
padding: 30px; padding: 30px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: var(--shadow);
margin-bottom: 30px; margin-bottom: 30px;
font-size: 1.1em; font-size: 1.1em;
line-height: 1.7; line-height: 1.7;
transition: background-color 0.3s ease;
} }
/* Editor section */ /* Editor section */
.editor-section { .editor-section {
background: white; background: var(--card);
padding: 30px; padding: 30px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: var(--shadow);
margin-bottom: 30px; margin-bottom: 30px;
transition: background-color 0.3s ease;
} }
#editor { #editor {
border: 2px solid #ddd; border: 2px solid var(--border);
border-radius: 8px; border-radius: 8px;
margin: 20px 0; margin: 20px 0;
height: 400px; height: 400px;
@@ -155,7 +208,7 @@ li a:hover {
} }
form button[type="submit"] { form button[type="submit"] {
background-color: #27ae60; background-color: var(--success);
color: white; color: white;
border: none; border: none;
padding: 12px 30px; padding: 12px 30px;
@@ -167,50 +220,52 @@ form button[type="submit"] {
} }
form button[type="submit"]:hover { form button[type="submit"]:hover {
background-color: #229954; background-color: var(--success-hover);
} }
/* Results section */ /* Results section */
b { b {
color: #2c3e50; color: var(--heading);
display: inline-block; display: inline-block;
margin: 10px 0 5px 0; margin: 10px 0 5px 0;
} }
pre { pre {
background-color: #f4f4f4; background-color: var(--code-bg);
padding: 20px; padding: 20px;
border-radius: 6px; border-radius: 6px;
border-left: 4px solid #3498db; border-left: 4px solid var(--accent);
margin: 10px 0 20px 0; margin: 10px 0 20px 0;
overflow-x: auto; overflow-x: auto;
font-family: 'JetBrains Mono', 'Courier New', monospace; font-family: "JetBrains Mono", "Courier New", monospace;
font-size: 14px; font-size: 14px;
line-height: 1.4; line-height: 1.4;
border: 1px solid var(--border);
transition: background-color 0.3s ease;
} }
pre[style*="color:red"] { pre[style*="color:red"] {
border-left-color: #e74c3c; border-left-color: var(--error);
background-color: #fdf2f2; background-color: var(--error-bg);
} }
/* Status messages */ /* Status messages */
p[style*="color:green"] { p[style*="color:green"] {
background-color: #d4edda; background-color: var(--success-bg);
color: #155724; color: var(--success-text);
padding: 15px 20px; padding: 15px 20px;
border-radius: 6px; border-radius: 6px;
border-left: 4px solid #27ae60; border-left: 4px solid var(--success);
margin: 20px 0; margin: 20px 0;
font-weight: 600; font-weight: 600;
} }
p[style*="color:red"] { p[style*="color:red"] {
background-color: #f8d7da; background-color: var(--error-bg);
color: #721c24; color: var(--error-text);
padding: 15px 20px; padding: 15px 20px;
border-radius: 6px; border-radius: 6px;
border-left: 4px solid #e74c3c; border-left: 4px solid var(--error);
margin: 20px 0; margin: 20px 0;
font-weight: 600; font-weight: 600;
} }
@@ -219,7 +274,7 @@ p[style*="color:red"] {
a[href="/"] { a[href="/"] {
display: inline-block; display: inline-block;
margin-top: 30px; margin-top: 30px;
background-color: #6c757d; background-color: var(--muted);
color: white; color: white;
padding: 10px 20px; padding: 10px 20px;
border-radius: 6px; border-radius: 6px;
@@ -227,7 +282,7 @@ a[href="/"] {
} }
a[href="/"]:hover { a[href="/"]:hover {
background-color: #5a6268; background-color: var(--muted-hover);
} }
/* Responsive design */ /* Responsive design */
@@ -246,7 +301,9 @@ a[href="/"]:hover {
font-size: 1.8em; font-size: 1.8em;
} }
.problem-desc, .editor-section, ul { .problem-desc,
.editor-section,
ul {
padding: 20px; padding: 20px;
} }

146
src/templates/index.html Normal file
View File

@@ -0,0 +1,146 @@
<!doctype html>
<html lang="en" class="">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Quick Problem Platform</title>
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
</head>
<style>
/* Popout explanation */
#rankingExplanation {
grid-column: 1 / span 2;
max-height: 0;
opacity: 0;
overflow: hidden;
transition: max-height 0.5s ease, opacity 0.4s ease, padding 0.4s ease;
padding: 0 12px;
}
#rankingExplanation.active {
max-height: 800px;
opacity: 1;
padding: 12px;
}
#rankInfoBtn.active { color: #2563eb; cursor:pointer; transition: transform 0.3s ease; }
#rankInfoBtn.active { transform: rotate(90deg); }
/* Highlight top rank */
.rank-1 td:first-child { font-weight: bold; }
.sort-asc::after { content: " ↑"; }
.sort-desc::after { content: " ↓"; }
</style>
</head>
<body>
<div class="wrap">
<header>
<div class="header-content">
<h1>Quick Problem Platform</h1>
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle dark mode">
<span class="dark-mode-icon"></span>
</button>
</div>
</header>
<div class="content" id="contentContainer">
<!-- Problems -->
<section class="card problems-list">
<div class="search-controls">
<input type="text" class="search-input" id="problemSearch" placeholder="Search problems..." />
</div>
<h2 style="margin-bottom:6px;font-size:1.1rem">Problems</h2>
<div id="problemsContainer">
{% for folder, description, test_code, difficulty in problems %}
<div class="problem-item" data-name="{{ folder.replace('_',' ').title() }}" data-desc="{{ description }}" data-difficulty="{{ difficulty|lower }}">
<a href="/problem/{{ folder }}">{{ folder.replace('_',' ').title() }}</a>
<span class="difficulty" data-difficulty="{{ difficulty|lower }}">{{ difficulty }}</span>
</div>
{% else %}
<div class="problem-item">No problems yet.</div>
{% endfor %}
</div>
<div class="pagination-controls" id="problemsPagination">
<button class="pagination-btn" id="problemsPrevBtn" disabled>← Previous</button>
<span class="pagination-info" id="problemsPaginationInfo">Page 1 of 1</span>
<button class="pagination-btn" id="problemsNextBtn" disabled>Next →</button>
</div>
</section>
<!-- Leaderboard -->
<section class="card" id="leaderboardSection">
<div class="leaderboard-head">
<h2 style="font-size:1.1rem;margin:0">Leaderboard
<span id="rankInfoBtn" title="How ranking works"></span>
</h2>
</div>
<div class="leaderboard-controls">
<input type="text" class="search-input" id="problemFilter" placeholder="Filter by problem..." />
<select class="filter-select" id="runtimeFilter">
<option value="">All runtimes</option>
<option value="best">Best runtime</option>
<option value="worst">Worst runtime</option>
</select>
</div>
<div id="leaderboardContainer">
<table class="leaderboard-table">
<thead>
<tr>
<th class="sortable" data-sort="rank">Rank</th>
<th class="sortable" data-sort="user">User</th>
<th class="sortable" data-sort="problem">Problem</th>
<th class="sortable" data-sort="runtime">Runtime (s)</th>
<th class="sortable" data-sort="memory">Memory (KB)</th>
<th>Line</th>
<th class="sortable" data-sort="timestamp">Timestamp</th>
</tr>
</thead>
<tbody id="leaderboardBody">
{% for entry in leaderboard %}
<tr data-user="{{ entry[0] }}" data-problem="{{ problem_titles.get(entry[1], 'Unknown') }}"
data-runtime="{{ '%.4f'|format(entry[2]) }}" data-memory="{{ entry[3] }}"
data-timestamp="{{ entry[5] }}">
<td>{{ loop.index }}</td>
<td>{{ entry[0] }}</td>
<td>
<a href="/problem/{{ problem_titles.get(entry[1], 'Unknown') }}"
style="color:#2563eb; text-decoration: none;"
onmouseover="this.style.textDecoration='underline';"
onmouseout="this.style.textDecoration='none';">
{{ problem_titles.get(entry[1], 'Unknown') }}
</a>
</td>
<td>{{ '%.4f'|format(entry[2]) }}</td>
<td>{{ entry[3] }}</td>
<td>{{ entry[4] if entry[4] else '-' }}</td>
<td>{{ entry[5] }}</td>
</tr>
{% else %}
<tr><td colspan="7">No leaderboard entries yet.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</section>
<!-- Ranking explanation -->
<section class="card" id="rankingExplanation">
<h2 style="font-size:1.1rem;margin-bottom:6px">How Ranking Works</h2>
<p>The leaderboard uses a <strong>weighted scoring system</strong> to determine overall rank:</p>
<ul style="margin-left: 15px;">
<li><strong>Runtime:</strong> How fast the solution runs (lower is better).</li>
<li><strong>Memory Usage:</strong> How much memory the solution uses (lower is better).</li>
</ul>
<p>Overall score is calculated as:</p>
<div style="background:#f9f9f9; border-left:4px solid #007acc; padding:1rem; margin:1rem 0;">
<code>
runtimeScore = yourRuntime / bestRuntime<br>
memoryScore = yourMemory / bestMemory<br>
overallScore = runtimeScore × 0.7 + memoryScore × 0.3
</code>
</div>
<p>Lower overall scores are better. If scores are equal, earlier submission ranks higher.</p>
</section>
</div>
</div>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>

158
src/templates/problem.html Normal file
View File

@@ -0,0 +1,158 @@
<!doctype html>
<html lang="en" class="">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ problem.title }} - Coding Problem</title>
<link rel="stylesheet" href="/static/style.css" />
<link rel="stylesheet" href="/static/problem.css" />
<!-- this is stoopid fucking html link for favicon. just cause of flask-->
<link
rel="icon"
type="image/x-icon"
href="{{ url_for('static', filename='favicon.ico') }}"
/>
<link
href="https://fonts.cdnfonts.com/css/jetbrains-mono"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
</head>
<body>
<div class="main-container">
<div class="problem-panel">
<div class="problem-header">
<button class="back-btn" onclick="window.location.href='/'">
← Back
</button>
<h1>{{ problem.title }}</h1>
<button
id="darkModeToggle"
class="dark-mode-toggle"
title="Toggle dark mode"
>
<span class="dark-mode-icon"></span>
</button>
</div>
<div class="problem-desc">
{{ problem.description | safe | markdown }}
</div>
</div>
<div class="editor-container">
<div class="editor-header">
<h2 style="margin: 0; font-size: 18px">
Submit Your Solution (Python)
</h2>
</div>
<div class="editor-wrapper">
<form method="post">
<label for="username">Username (optional):</label>
<input
type="text"
name="username"
id="username"
placeholder="Anonymous"
/>
<div id="editor"></div>
<textarea
name="user_code"
id="user_code"
style="display: none"
></textarea>
<div class="editor-actions">
<button type="submit">Run & Submit</button>
</div>
</form>
<div class="result-panel">
<h3>Result</h3>
{% if result %}
<p>
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }}
seconds
</p>
<p><b>Output:</b></p>
<pre>{{ result.output }}</pre>
{% if result.error %}
<p><b>Error:</b></p>
<pre>{{ result.error }}</pre>
{% endif %} {% else %}
<div class="placeholder">
Your code execution results will appear here
</div>
{% endif %}
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
// Dark mode functionality
const darkModeToggle = document.getElementById("darkModeToggle");
const html = document.documentElement;
// Load saved dark mode preference
const savedDarkMode = localStorage.getItem("darkMode");
if (
savedDarkMode === "true" ||
(savedDarkMode === null &&
window.matchMedia("(prefers-color-scheme: dark)").matches)
) {
html.classList.add("dark");
}
darkModeToggle.addEventListener("click", () => {
html.classList.toggle("dark");
localStorage.setItem(
"darkMode",
html.classList.contains("dark"),
);
// Update Monaco editor theme
if (window.monacoEditor) {
const isDark = html.classList.contains("dark");
window.monacoEditor.updateOptions({
theme: isDark ? "vs-dark" : "vs-light",
});
}
});
require.config({
paths: {
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs",
},
});
require(["vs/editor/editor.main"], function () {
const isDark = html.classList.contains("dark");
window.monacoEditor = monaco.editor.create(
document.getElementById("editor"),
{
value: "",
language: "python",
theme: isDark ? "vs-dark" : "vs-light",
fontFamily: "JetBrains Mono, monospace",
fontLigatures: true,
automaticLayout: true,
fontSize: 16,
minimap: { enabled: false },
},
);
document
.querySelector("form")
.addEventListener("submit", function (e) {
var code = window.monacoEditor.getValue();
if (!code.trim()) {
alert("Please enter your code before submitting.");
e.preventDefault();
return false;
}
document.getElementById("user_code").value = code;
});
});
</script>
</body>
</html>

262
src/utils.py Normal file
View File

@@ -0,0 +1,262 @@
import sys
import traceback
import time
import io
import tempfile
import subprocess
import os
import re
import ast
# Security configuration
ALLOWED_IMPORTS = {
'math', 'random', 'datetime', 'json', 'collections', 'itertools',
'functools', 'operator', 'copy', 'unittest', 're', 'string'
}
DANGEROUS_PATTERNS = [
r'import\s+os(?:\s|$|\.)',
r'from\s+os\s+import',
r'import\s+subprocess(?:\s|$|\.)',
r'from\s+subprocess\s+import',
r'import\s+sys(?:\s|$|\.)',
r'from\s+sys\s+import',
r'import\s+shutil(?:\s|$|\.)',
r'from\s+shutil\s+import',
r'import\s+pathlib(?:\s|$|\.)',
r'from\s+pathlib\s+import',
r'__import__\s*\(',
r'exec\s*\(',
r'eval\s*\(',
r'compile\s*\(',
r'open\s*\(',
r'file\s*\(',
r'input\s*\(',
r'raw_input\s*\(',
r'\.unlink\s*\(',
r'\.remove\s*\(',
r'\.rmdir\s*\(',
r'\.rmtree\s*\(',
r'\.delete\s*\(',
r'\.kill\s*\(',
r'\.terminate\s*\(',
]
def validate_code_security(code):
"""
Validates code for security issues.
Returns (is_safe, error_message)
"""
# Check for dangerous patterns
for pattern in DANGEROUS_PATTERNS:
if re.search(pattern, code, re.IGNORECASE):
return False, f"Dangerous operation detected: {pattern}"
# Parse AST to check imports
try:
tree = ast.parse(code)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
module_name = alias.name.split('.')[0]
if module_name not in ALLOWED_IMPORTS:
return False, f"Import not allowed: {module_name}"
elif isinstance(node, ast.ImportFrom):
if node.module:
module_name = node.module.split('.')[0]
if module_name not in ALLOWED_IMPORTS:
return False, f"Import not allowed: {module_name}"
except SyntaxError as e:
return False, f"Syntax error in code: {str(e)}"
return True, None
def create_restricted_globals():
"""Create a restricted global namespace for code execution."""
safe_builtins = {
'abs', 'all', 'any', 'bin', 'bool', 'chr', 'dict', 'dir',
'divmod', 'enumerate', 'filter', 'float', 'format', 'frozenset',
'hex', 'id', 'int', 'isinstance', 'issubclass', 'iter', 'len',
'list', 'map', 'max', 'min', 'next', 'oct', 'ord', 'pow',
'print', 'range', 'repr', 'reversed', 'round', 'set', 'slice',
'sorted', 'str', 'sum', 'tuple', 'type', 'zip'
}
restricted_globals = {
'__builtins__': {name: __builtins__[name] for name in safe_builtins if name in __builtins__}
}
# Add allowed modules
for module in ALLOWED_IMPORTS:
try:
restricted_globals[module] = __import__(module)
except ImportError:
pass # Module not available
return restricted_globals
def run_code_against_tests(user_code, test_code, max_execution_time=5):
"""
Securely run user code against test code with safety restrictions.
Args:
user_code: The user's solution code
test_code: The test code to validate the solution
max_execution_time: Maximum execution time in seconds (default: 5)
Returns:
dict: Result containing passed, output, runtime, and error information
"""
# Validate security for both user code and test code
user_safe, user_error = validate_code_security(user_code)
if not user_safe:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"Security violation in user code: {user_error}"
}
test_safe, test_error = validate_code_security(test_code)
if not test_safe:
return {
'passed': False,
'output': '',
'runtime': 0,
'error': f"Security violation in test code: {test_error}"
}
local_ns = {}
output = ''
start = time.perf_counter()
error = None
passed = False
temp_file = None
try:
# Check if unittest is used in test_code
if 'unittest' in test_code:
# Create temp file in a secure temporary directory
temp_dir = tempfile.mkdtemp(prefix='secure_code_')
try:
temp_file = os.path.join(temp_dir, 'test_code.py')
combined_code = f"{user_code}\n\n{test_code}"
# Write to temp file with restricted permissions
with open(temp_file, 'w', encoding='utf-8') as f:
f.write(combined_code)
os.chmod(temp_file, 0o600) # Read/write for owner only
# Run the file as a subprocess with additional security
try:
proc = subprocess.run(
[sys.executable, temp_file],
capture_output=True,
text=True,
timeout=max_execution_time,
encoding='utf-8',
cwd=temp_dir, # Run in the temporary directory
env={'PYTHONPATH': ''} # Restrict Python path
)
# Combine both stdout and stderr to capture all output
combined_output = ""
if proc.stdout:
combined_output += proc.stdout
if proc.stderr:
if combined_output:
combined_output += "\n" + proc.stderr
else:
combined_output = proc.stderr
output = combined_output
passed = proc.returncode == 0
if not passed:
error = f"Tests failed. Return code: {proc.returncode}\n{output}"
except subprocess.TimeoutExpired:
passed = False
error = f"Code execution timed out after {max_execution_time} seconds"
output = "Execution timed out"
finally:
# Secure cleanup of temporary directory and files
try:
if temp_file and os.path.exists(temp_file):
os.chmod(temp_file, 0o600) # Ensure we can delete
os.unlink(temp_file)
if os.path.exists(temp_dir):
os.rmdir(temp_dir)
except Exception as cleanup_error:
print(f"Warning: Could not clean up temp files: {cleanup_error}")
else:
# Direct execution with restricted globals
old_stdout = sys.stdout
captured_output = io.StringIO()
sys.stdout = captured_output
try:
# Create restricted execution environment
restricted_globals = create_restricted_globals()
# Execute user code in restricted environment
exec(user_code, restricted_globals, local_ns)
# Execute test code (should raise AssertionError if fail)
exec(test_code, {**restricted_globals, **local_ns}, local_ns)
passed = True
except AssertionError as e:
passed = False
error = f"Assertion failed: {str(e)}"
except Exception as e:
passed = False
error = f"Runtime error: {traceback.format_exc()}"
finally:
output = captured_output.getvalue()
sys.stdout = old_stdout
except Exception as e:
passed = False
error = f"Execution error: {traceback.format_exc()}"
runtime = time.perf_counter() - start
# Limit output size to prevent memory issues
max_output_size = 10000 # 10KB limit
if len(output) > max_output_size:
output = output[:max_output_size] + "\n... (output truncated)"
result = {
'passed': passed,
'output': output.strip() if output else '',
'runtime': runtime,
'error': error if not passed else None
}
return result
# Example usage with additional safety wrapper
def safe_code_runner(user_code, test_code):
"""
Additional wrapper for extra safety checks.
"""
# Additional length checks
if len(user_code) > 50000: # 50KB limit
return {
'passed': False,
'output': '',
'runtime': 0,
'error': "User code too large (maximum 50KB allowed)"
}
if len(test_code) > 10000: # 10KB limit for test code
return {
'passed': False,
'output': '',
'runtime': 0,
'error': "Test code too large (maximum 10KB allowed)"
}
return run_code_against_tests(user_code, test_code)

View File

@@ -1,99 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Quick Problem Platform</title>
<link rel="stylesheet" href="/static/style.css">
<style>
.leaderboard-table {
width: auto;
min-width: 400px;
max-width: 600px;
border-collapse: collapse;
margin: 2em 0;
background: #fff;
box-shadow: 0 2px 8px #0001;
border-radius: 8px;
overflow: hidden;
}
.leaderboard-table th, .leaderboard-table td {
padding: 0.7em 1em;
text-align: center;
}
.leaderboard-table th {
background: #f5f5f5;
font-weight: 600;
color: #333;
}
.leaderboard-table tr:nth-child(even) {
background: #fafbfc;
}
.leaderboard-table tr:nth-child(odd) {
background: #f0f2f5;
}
.leaderboard-table td {
font-family: 'JetBrains Mono', monospace;
font-size: 1em;
}
.leaderboard-table tr:hover {
background: #e0e7ff;
}
.problems-list {
max-width: 600px;
min-width: 400px;
margin: 0;
padding: 0;
}
.problems-list ul {
list-style: none;
padding: 0;
margin: 0;
}
.problems-list li {
padding: 0.5em 0;
}
</style>
</head>
<body>
<h1>Quick Problem Platform</h1>
<!--<a href="/problem/new">Submit New Problem</a>-->
<section class="problems-list">
<h2>Problems</h2>
<ul>
{% for problem in problems %}
<li><a href="/problem/{{ problem.id }}">{{ problem.title }}</a></li>
{% else %}
<li>No problems yet.</li>
{% endfor %}
</ul>
</section>
<section>
<h2>Leaderboard</h2>
<table class="leaderboard-table">
<tr>
<th>Rank</th>
<th>User</th>
<th>Problem</th>
<th>Runtime (s)</th>
<th>Memory (KB)</th>
<th>Line Number</th>
<th>Timestamp</th>
</tr>
{% for entry in leaderboard %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ entry[0] }}</td>
<td>{{ problem_titles.get(entry[1], 'Unknown') }}</td>
<td>{{ '%.4f'|format(entry[2]) }}</td>
<td>{{ entry[3] }}</td>
<td>{{ entry[4] if entry[4] else '-' }}</td>
<td>{{ entry[5] }}</td>
</tr>
{% else %}
<tr><td colspan="7">No leaderboard entries yet.</td></tr>
{% endfor %}
</table>
</section>
</body>
</html>

View File

@@ -1,72 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ problem.title }}</title>
<link rel="stylesheet" href="/static/style.css">
<link href="https://fonts.cdnfonts.com/css/jetbrains-mono" rel="stylesheet">
</head>
<body>
<div class="problem-header">
<button class="back-btn" onclick="window.location.href='/'">← Back</button>
<h1 style="margin-bottom:0;">{{ problem.title }}</h1>
</div>
<div class="problem-desc">{{ problem.description }}</div>
<div class="editor-section" style="max-width:1160;margin:0">
<h2 style="margin-top:0;">Submit Your Solution (Python)</h2>
<form method="post">
<label for="username">Username (optional):</label>
<input type="text" name="username" id="username" placeholder="Anonymous" style="margin-bottom:10px;">
<div id="editor"></div>
<textarea name="user_code" id="user_code" style="display:none;"></textarea>
<div class="editor-actions">
<button type="submit">Run & Submit</button>
</div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs/loader.js"></script>
<script>
require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs' } });
require(['vs/editor/editor.main'], function() {
var editor = monaco.editor.create(document.getElementById('editor'), {
value: '',
language: 'python',
theme: 'vs-light',
fontFamily: 'JetBrains Mono, monospace',
fontLigatures: true,
automaticLayout: true,
fontSize: 16,
minimap: { enabled: false }
});
document.querySelector('form').addEventListener('submit', function(e) {
var code = editor.getValue();
if (!code.trim()) {
alert('Please enter your code before submitting.');
e.preventDefault();
return false;
}
document.getElementById('user_code').value = code;
});
});
</script>
{% if result %}
<div class="editor-section" style="max-width:1160;margin:0;margin-top: 5px;">
<h3>Result:</h3>
<b>Runtime:</b> {{ '%.4f'|format(result.runtime) }} seconds<br>
<b>Output:</b>
<pre>{{ result.output }}</pre>
{% if result.error %}
<b>Error:</b>
<pre style="color:red;">{{ result.error }}</pre>
{% endif %}
{% if result.passed %}
<p style="color:green;">Passed!</p>
{% else %}
<p style="color:red;">Failed!</p>
{% endif %}
</div>
{% endif %}
<!--<a href="/">Back to Problems</a>-->
</body>
</html>

View File

@@ -1,51 +0,0 @@
import sys
import traceback
import time
import io
def run_code_against_tests(user_code, test_code):
import tempfile
import subprocess
local_ns = {}
output = ''
start = time.perf_counter()
error = None
passed = False
try:
# Check if unittest is used in test_code
if 'unittest' in test_code:
# Write user code + test code to a temp file
with tempfile.NamedTemporaryFile('w+', suffix='.py', delete=False) as f:
f.write(user_code + '\n' + test_code)
f.flush()
f_name = f.name
# Run the file as a subprocess
proc = subprocess.run([sys.executable, f_name], capture_output=True, text=True, timeout=10)
output = proc.stdout + proc.stderr
passed = proc.returncode == 0
error = None if passed else output
else:
# Capture stdout
old_stdout = sys.stdout
sys.stdout = mystdout = io.StringIO()
# Execute user code
exec(user_code, {}, local_ns)
# Execute test code (should raise AssertionError if fail)
exec(test_code, local_ns, local_ns)
passed = True
except Exception as e:
passed = False
error = traceback.format_exc()
finally:
if 'mystdout' in locals():
output = mystdout.getvalue() or output
sys.stdout = old_stdout if 'old_stdout' in locals() else sys.stdout
runtime = time.perf_counter() - start
result = {
'passed': passed,
'output': output,
'runtime': runtime,
'error': error if not passed else None
}
return result