made this a hell of a lot better
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
instance
|
instance
|
||||||
.venv
|
.venv
|
||||||
|
venv
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
|
from flask import Flask, render_template, request, redirect, url_for, send_from_directory
|
||||||
import markdown as md
|
import markdown as md
|
||||||
|
import ast
|
||||||
from models import db, Problem, Solution
|
from QPP.models import db, Problem, Solution
|
||||||
from utils import run_code_against_tests
|
from QPP.utils import run_code_against_tests
|
||||||
from leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard
|
from QPP.leaderboard import create_leaderboard_table, log_leaderboard, get_leaderboard
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
## from problem_loader import load_problems_from_json, schedule_problem_reload
|
## from problem_loader import load_problems_from_json, schedule_problem_reload
|
||||||
from problem_scanner import start_problem_scanner
|
from QPP.problem_scanner import start_problem_scanner
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3'
|
|
||||||
|
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)
|
db.init_app(app)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def setup():
|
def setup():
|
||||||
db.create_all()
|
db.create_all()
|
||||||
@@ -38,7 +41,7 @@ def favicon():
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
db_path = Path(__file__).parent / 'problems.sqlite3'
|
db_path = Path(__file__).parent / 'database/problems.sqlite3'
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
#<!-- The query was fucked up so it fetched the fucking testcode -->
|
#<!-- The query was fucked up so it fetched the fucking testcode -->
|
||||||
@@ -65,7 +68,7 @@ def new_problem():
|
|||||||
|
|
||||||
@app.route('/problem/<folder>', methods=['GET', 'POST'])
|
@app.route('/problem/<folder>', methods=['GET', 'POST'])
|
||||||
def view_problem(folder):
|
def view_problem(folder):
|
||||||
db_path = Path(__file__).parent / 'problems.sqlite3'
|
db_path = Path(__file__).parent / 'database/problems.sqlite3'
|
||||||
conn = sqlite3.connect(db_path)
|
conn = sqlite3.connect(db_path)
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute('SELECT folder, description,test_code , difficulty FROM problems WHERE folder = ?', (folder,))
|
c.execute('SELECT folder, description,test_code , difficulty FROM problems WHERE folder = ?', (folder,))
|
||||||
@@ -78,11 +81,11 @@ def view_problem(folder):
|
|||||||
problem = {
|
problem = {
|
||||||
'folder': row[0],
|
'folder': row[0],
|
||||||
'description': row[1],
|
'description': row[1],
|
||||||
'difficulty': row[2],
|
'difficulty': row[3], # now correct
|
||||||
'test_code': row[3], # This is used internally, not displayed
|
'test_code': row[2], # now correct
|
||||||
'title': row[0].replace('_', ' ').title()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
result = None
|
result = None
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
user_code = request.form['user_code']
|
user_code = request.form['user_code']
|
||||||
@@ -95,7 +98,6 @@ def view_problem(folder):
|
|||||||
memory_used = peak // 1024 # in KB
|
memory_used = peak // 1024 # in KB
|
||||||
# Try to get the last line number executed (even for successful runs)
|
# Try to get the last line number executed (even for successful runs)
|
||||||
line_number = None
|
line_number = None
|
||||||
import ast
|
|
||||||
try:
|
try:
|
||||||
tree = ast.parse(user_code)
|
tree = ast.parse(user_code)
|
||||||
# Find the highest line number in the AST (for multi-function/user code)
|
# Find the highest line number in the AST (for multi-function/user code)
|
||||||
@@ -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()
|
||||||
@@ -19,7 +19,7 @@ except ImportError:
|
|||||||
WATCHDOG_AVAILABLE = False
|
WATCHDOG_AVAILABLE = False
|
||||||
|
|
||||||
PROBLEMS_DIR = Path(__file__).parent / 'problems'
|
PROBLEMS_DIR = Path(__file__).parent / 'problems'
|
||||||
DB_PATH = Path(__file__).parent / 'problems.sqlite3'
|
DB_PATH = Path(__file__).parent / 'database/problems.sqlite3'
|
||||||
|
|
||||||
class ProblemScannerThread(threading.Thread):
|
class ProblemScannerThread(threading.Thread):
|
||||||
def __init__(self, scan_interval=2):
|
def __init__(self, scan_interval=2):
|
||||||
26
QPP/problems/reversedstring/test.py
Normal file
26
QPP/problems/reversedstring/test.py
Normal 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)
|
||||||
7
QPP/problems/sortlist/manifets.json
Normal file
7
QPP/problems/sortlist/manifets.json
Normal 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"
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
102
QPP/templates/index.html
Normal file
102
QPP/templates/index.html
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Quick Problem Platform</title>
|
||||||
|
<!--<link rel="favicon" href="/favicon/favicon.ico">-->
|
||||||
|
<script src="script.js" async defer></script>
|
||||||
|
<link rel="stylesheet" href="/static/index.css">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
<header>
|
||||||
|
<h1>Quick Problem Platform</h1>
|
||||||
|
</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 }}">
|
||||||
|
<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>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Leaderboard -->
|
||||||
|
<section class="card" id="leaderboardSection">
|
||||||
|
<div class="leaderboard-head">
|
||||||
|
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
|
||||||
|
<button class="btn" id="toggleLeaderboard">Hide</button>
|
||||||
|
</div>
|
||||||
|
<div class="leaderboard-controls">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="search-input"
|
||||||
|
id="userSearch"
|
||||||
|
placeholder="Filter by user..."
|
||||||
|
/>
|
||||||
|
<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>{{ 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 %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
296
QPP/templates/problem.html
Normal file
296
QPP/templates/problem.html
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<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 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">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
color: #333;
|
||||||
|
min-height: 100vh; /* allow content to grow */
|
||||||
|
overflow-y: auto; /* allow vertical scroll */
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
*, *::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: white;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
border-right: 1px solid #eaeaea;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container {
|
||||||
|
flex: 1 1 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: white;
|
||||||
|
max-height: 100vh;
|
||||||
|
overflow: hidden; /* internal scroll handling */
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
border-bottom: 1px solid #eaeaea;
|
||||||
|
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;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #666;
|
||||||
|
margin-right: 15px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-desc {
|
||||||
|
line-height: 1.6;
|
||||||
|
font-size: 15px;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-desc pre {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.problem-desc code {
|
||||||
|
background: #f6f8fa;
|
||||||
|
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: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions button:hover {
|
||||||
|
background-color: #0069d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 300px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: 60vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-panel {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
min-height: 120px;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 30vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-panel h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-panel pre {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 #eaeaea;
|
||||||
|
}
|
||||||
|
#editor {
|
||||||
|
min-height: 400px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
.result-panel {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
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>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Flask>=3.0
|
||||||
|
Flask-SQLAlchemy>=3.1
|
||||||
|
Markdown>=3.6
|
||||||
|
MarkupSafe>=2.1
|
||||||
|
watchdog>=4.0
|
||||||
14
run.bash
Normal file
14
run.bash
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e # exit if any command fails
|
||||||
|
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
|
|
||||||
|
pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
export FLASK_APP=QPP.app
|
||||||
|
export FLASK_ENV=production
|
||||||
|
|
||||||
|
flask run --host=0.0.0.0 --port=5000
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
||||||
<title>Quick Problem Platform</title>
|
|
||||||
<!--<link rel="favicon" href="/favicon/favicon.ico">-->
|
|
||||||
<script src="script.js" async defer></script>
|
|
||||||
<link rel="stylesheet" href="/static/index.css">
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="wrap">
|
|
||||||
<header>
|
|
||||||
<h1>Quick Problem Platform</h1>
|
|
||||||
</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 }}">
|
|
||||||
<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>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Leaderboard -->
|
|
||||||
<section class="card" id="leaderboardSection">
|
|
||||||
<div class="leaderboard-head">
|
|
||||||
<h2 style="font-size:1.1rem;margin:0">Leaderboard</h2>
|
|
||||||
<button class="btn" id="toggleLeaderboard">Hide</button>
|
|
||||||
</div>
|
|
||||||
<div class="leaderboard-controls">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="search-input"
|
|
||||||
id="userSearch"
|
|
||||||
placeholder="Filter by user..."
|
|
||||||
/>
|
|
||||||
<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>{{ 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 %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -1,296 +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 }} - Coding Problem</title>
|
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
|
||||||
<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">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: #f9f9f9;
|
|
||||||
color: #333;
|
|
||||||
min-height: 100vh; /* allow content to grow */
|
|
||||||
overflow-y: auto; /* allow vertical scroll */
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
*, *::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: white;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
border-right: 1px solid #eaeaea;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-container {
|
|
||||||
flex: 1 1 400px;
|
|
||||||
min-width: 300px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
background: white;
|
|
||||||
max-height: 100vh;
|
|
||||||
overflow: hidden; /* internal scroll handling */
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-header {
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-bottom: 1px solid #eaeaea;
|
|
||||||
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;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #666;
|
|
||||||
margin-right: 15px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-btn:hover {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
color: #1a1a1a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-desc {
|
|
||||||
line-height: 1.6;
|
|
||||||
font-size: 15px;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-desc pre {
|
|
||||||
background: #f6f8fa;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.problem-desc code {
|
|
||||||
background: #f6f8fa;
|
|
||||||
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: #007bff;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editor-actions button:hover {
|
|
||||||
background-color: #0069d9;
|
|
||||||
}
|
|
||||||
|
|
||||||
#editor {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-height: 300px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 60vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-panel {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
min-height: 120px;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 30vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-panel h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-panel pre {
|
|
||||||
background: #f6f8fa;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-family: 'JetBrains Mono', monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
color: #999;
|
|
||||||
font-style: italic;
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="text"] {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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 #eaeaea;
|
|
||||||
}
|
|
||||||
#editor {
|
|
||||||
min-height: 400px;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
.result-panel {
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
</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>
|
|
||||||
</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>
|
|
||||||
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>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user