Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e52baf2555 | |||
| 45d6b1d9de | |||
| bb3ad834b2 | |||
| addd9d90f0 | |||
| 69b92b34d8 | |||
| 9935d70e31 | |||
| 3f7ff13ef7 | |||
| 0706f99427 | |||
| 9bd2b21d44 | |||
| b15752e051 | |||
| 84d15b70c6 | |||
| 271fc52e82 |
17
app.py
17
app.py
@@ -789,7 +789,13 @@ def route_api_leaderboard_submit():
|
||||
else:
|
||||
score_obj = score_data
|
||||
|
||||
score_value = int(score_obj.get('score', 0))
|
||||
# Check for 'score' first, then 'points'
|
||||
# Frontend usually sends 'points'
|
||||
score_val = score_obj.get('score')
|
||||
if score_val is None:
|
||||
score_val = score_obj.get('points')
|
||||
|
||||
score_value = int(score_val or 0)
|
||||
except:
|
||||
return api_error('invalid_score_format')
|
||||
|
||||
@@ -858,7 +864,6 @@ def route_api_leaderboard_submit():
|
||||
|
||||
return jsonify({'status': 'ok', 'message': 'score_submitted'})
|
||||
|
||||
|
||||
@app.route(basedir + 'api/leaderboard/get')
|
||||
def route_api_leaderboard_get():
|
||||
song_id = request.args.get('song_id', None)
|
||||
@@ -867,10 +872,13 @@ def route_api_leaderboard_get():
|
||||
if not song_id or not difficulty:
|
||||
return abort(400)
|
||||
|
||||
# Accept both numeric IDs and hash strings
|
||||
# Try to convert to int if possible, otherwise use as string
|
||||
try:
|
||||
song_id = int(song_id)
|
||||
except:
|
||||
return abort(400)
|
||||
except (ValueError, TypeError):
|
||||
# Keep as string (hash ID)
|
||||
pass
|
||||
|
||||
# Validate difficulty
|
||||
valid_difficulties = ['easy', 'normal', 'hard', 'oni', 'ura']
|
||||
@@ -901,6 +909,7 @@ def route_api_leaderboard_get():
|
||||
return jsonify({'status': 'ok', 'leaderboard': leaderboard, 'month': current_month})
|
||||
|
||||
|
||||
|
||||
@app.route(basedir + 'privacy')
|
||||
def route_api_privacy():
|
||||
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
||||
|
||||
201
local_debug.py
Normal file
201
local_debug.py
Normal file
@@ -0,0 +1,201 @@
|
||||
|
||||
import time
|
||||
import json
|
||||
import base64
|
||||
|
||||
# Mock DB and objects
|
||||
class MockCollection:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.data = []
|
||||
|
||||
def find_one(self, query):
|
||||
for item in self.data:
|
||||
match = True
|
||||
for k, v in query.items():
|
||||
if item.get(k) != v:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
return item
|
||||
return None
|
||||
|
||||
def find(self, query, projection=None):
|
||||
results = []
|
||||
for item in self.data:
|
||||
match = True
|
||||
for k, v in query.items():
|
||||
if item.get(k) != v:
|
||||
match = False
|
||||
break
|
||||
if match:
|
||||
if projection:
|
||||
filtered = {k: v for k, v in item.items() if projection.get(k, False)}
|
||||
if projection.get('_id') is None or projection.get('_id') is True:
|
||||
filtered['_id'] = item.get('_id')
|
||||
results.append(filtered)
|
||||
else:
|
||||
results.append(item)
|
||||
return MockCursor(results)
|
||||
|
||||
def insert_one(self, doc):
|
||||
doc['_id'] = len(self.data) + 1
|
||||
print(f"[{self.name}] Inserted: {doc}")
|
||||
self.data.append(doc)
|
||||
|
||||
def count_documents(self, query):
|
||||
return len(self.data)
|
||||
|
||||
class MockCursor:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
def sort(self, key, direction):
|
||||
self.data.sort(key=lambda x: x.get(key, 0), reverse=(direction < 0))
|
||||
return self
|
||||
def limit(self, n):
|
||||
self.data = self.data[:n]
|
||||
return self.data
|
||||
|
||||
class MockDB:
|
||||
def __init__(self):
|
||||
self.users = MockCollection('users')
|
||||
self.leaderboards = MockCollection('leaderboards')
|
||||
|
||||
db = MockDB()
|
||||
db.users.insert_one({'username': 'testuser', 'display_name': 'Test User'})
|
||||
|
||||
# --- Logic from app.py rewritten for standalone test ---
|
||||
|
||||
def logic_submit(data, username):
|
||||
print(f"\n--- Submitting {data.get('song_id')} ---")
|
||||
user = db.users.find_one({'username': username})
|
||||
if not user:
|
||||
return {'error': 'user_not_found'}
|
||||
|
||||
song_id = data.get('song_id')
|
||||
difficulty = data.get('difficulty')
|
||||
score_data = data.get('score')
|
||||
|
||||
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||
|
||||
existing = db.leaderboards.find_one({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'username': username,
|
||||
'month': current_month
|
||||
})
|
||||
|
||||
# Patched Logic from app.py
|
||||
try:
|
||||
if isinstance(score_data, str):
|
||||
score_obj = json.loads(score_data)
|
||||
else:
|
||||
score_obj = score_data
|
||||
|
||||
# Check for 'score' first, then 'points'
|
||||
score_val = score_obj.get('score')
|
||||
if score_val is None:
|
||||
score_val = score_obj.get('points')
|
||||
|
||||
score_value = int(score_val or 0)
|
||||
print(f" Parsed Score Value: {score_value}")
|
||||
except Exception as e:
|
||||
print(f"Error parsing score: {e}")
|
||||
return {'error': 'invalid_score_format'}
|
||||
|
||||
if existing:
|
||||
print(" Existing record found (simulated update).")
|
||||
else:
|
||||
print(" Inserting new record.")
|
||||
db.leaderboards.insert_one({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'username': username,
|
||||
'display_name': user['display_name'],
|
||||
'score': score_data,
|
||||
'score_value': score_value,
|
||||
'submitted_at': time.time(),
|
||||
'month': current_month
|
||||
})
|
||||
return {'status': 'ok'}
|
||||
|
||||
def logic_get(args):
|
||||
print(f"\n--- Getting {args.get('song_id')} ---")
|
||||
song_id = args.get('song_id', None)
|
||||
difficulty = args.get('difficulty', None)
|
||||
|
||||
if not song_id or not difficulty:
|
||||
return {'error': 'missing args'}
|
||||
|
||||
try:
|
||||
song_id = int(song_id)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
current_month = time.strftime('%Y-%m', time.gmtime())
|
||||
|
||||
leaderboard = list(db.leaderboards.find({
|
||||
'song_id': song_id,
|
||||
'difficulty': difficulty,
|
||||
'month': current_month
|
||||
}, {
|
||||
'_id': False,
|
||||
'username': True,
|
||||
'score_value': True
|
||||
}).sort('score_value', -1).limit(50))
|
||||
|
||||
return {'status': 'ok', 'leaderboard': leaderboard}
|
||||
|
||||
# --- Test Cases ---
|
||||
|
||||
# Case 1: Hash ID
|
||||
print("=== TEST CASE 1: Hash ID ===")
|
||||
logic_submit({
|
||||
'song_id': "hash123",
|
||||
'difficulty': 'oni',
|
||||
'score': {'score': 1000000}
|
||||
}, 'testuser')
|
||||
res1 = logic_get({'song_id': "hash123", 'difficulty': 'oni'})
|
||||
if len(res1['leaderboard']) > 0: print("✅ Success")
|
||||
else: print("❌ Failed")
|
||||
|
||||
# Case 2: Numeric ID
|
||||
print("\n=== TEST CASE 2: Numeric ID ===")
|
||||
logic_submit({
|
||||
'song_id': 123,
|
||||
'difficulty': 'oni',
|
||||
'score': {'score': 2000000}
|
||||
}, 'testuser')
|
||||
res2 = logic_get({'song_id': "123", 'difficulty': 'oni'})
|
||||
if len(res2['leaderboard']) > 0: print("✅ Success")
|
||||
else: print("❌ Failed")
|
||||
|
||||
# Case 3: Points Only (Frontend Payload Simulation)
|
||||
print("\n=== TEST CASE 3: Points Only (Frontend Payload) ===")
|
||||
# This simulates what the JS 'this.resultsObj' looks like (it has 'points', not 'score')
|
||||
logic_submit({
|
||||
'song_id': "points_song",
|
||||
'difficulty': 'hard',
|
||||
'score': {'points': 345678, 'bad': 0, 'good': 100} # No 'score' key
|
||||
}, 'testuser')
|
||||
|
||||
res3 = logic_get({'song_id': "points_song", 'difficulty': 'hard'})
|
||||
passed = False
|
||||
if len(res3.get('leaderboard', [])) > 0:
|
||||
val = res3['leaderboard'][0].get('score_value')
|
||||
if val == 345678:
|
||||
print(f"✅ SUCCESS: Correctly parsed 'points' -> {val}")
|
||||
passed = True
|
||||
else:
|
||||
print(f"❌ FAILED: Score value mismatch. Expected 345678, got {val}")
|
||||
else:
|
||||
print("❌ FAILED: No record found")
|
||||
|
||||
if passed:
|
||||
print("\n---------------------------------------------------")
|
||||
print("🎉 ALL LOCAL TESTS PASSED. BACKEND LOGIC VERIFIED.")
|
||||
print("---------------------------------------------------")
|
||||
else:
|
||||
print("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||
print("🛑 TESTS FAILED. CHECK LOGS.")
|
||||
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||
@@ -7,6 +7,7 @@ var assets = {
|
||||
"titlescreen.js",
|
||||
"scoresheet.js",
|
||||
"songselect.js",
|
||||
"leaderboard.js",
|
||||
"keyboard.js",
|
||||
"gameinput.js",
|
||||
"game.js",
|
||||
@@ -140,6 +141,7 @@ var assets = {
|
||||
},
|
||||
"views": [
|
||||
"game.html",
|
||||
"leaderboard.html",
|
||||
"loadsong.html",
|
||||
"songselect.html",
|
||||
"titlescreen.html",
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
class Leaderboard {
|
||||
constructor() {
|
||||
this.init()
|
||||
this.canvas = null
|
||||
this.ctx = null
|
||||
this.songId = null
|
||||
this.difficulty = null
|
||||
this.leaderboardData = []
|
||||
this.currentMonth = ""
|
||||
this.visible = false
|
||||
this.draw = null
|
||||
this.keyboard = null
|
||||
}
|
||||
|
||||
init() {
|
||||
this.canvas = document.getElementById("leaderboard-canvas")
|
||||
if (!this.canvas) {
|
||||
return
|
||||
console.error("Leaderboard canvas not found!")
|
||||
return false
|
||||
}
|
||||
this.ctx = this.canvas.getContext("2d")
|
||||
|
||||
@@ -16,12 +25,6 @@ class Leaderboard {
|
||||
this.ctx.imageSmoothingEnabled = false
|
||||
}
|
||||
|
||||
this.songId = null
|
||||
this.difficulty = null
|
||||
this.leaderboardData = []
|
||||
this.currentMonth = ""
|
||||
this.visible = false
|
||||
|
||||
this.draw = new CanvasDraw(noSmoothing)
|
||||
|
||||
// Keyboard controls
|
||||
@@ -33,6 +36,7 @@ class Leaderboard {
|
||||
}, this.keyPress.bind(this))
|
||||
|
||||
pageEvents.add(this.canvas, ["mousedown", "touchstart"], this.onClose.bind(this))
|
||||
return true
|
||||
}
|
||||
|
||||
keyPress(pressed, name) {
|
||||
@@ -55,6 +59,12 @@ class Leaderboard {
|
||||
|
||||
loader.changePage("leaderboard", false)
|
||||
|
||||
// Initialize after page is loaded
|
||||
if (!this.init()) {
|
||||
console.error("Failed to initialize leaderboard")
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch leaderboard data
|
||||
await this.fetchLeaderboard()
|
||||
|
||||
@@ -67,10 +77,18 @@ class Leaderboard {
|
||||
}
|
||||
|
||||
async fetchLeaderboard() {
|
||||
// Validate songId exists
|
||||
if (!this.songId) {
|
||||
console.error("Missing song ID for leaderboard")
|
||||
this.leaderboardData = []
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
var response = await loader.ajax(
|
||||
`${gameConfig.basedir || "/"}api/leaderboard/get?song_id=${this.songId}&difficulty=${this.difficulty}`
|
||||
`${gameConfig.basedir || "/"}api/leaderboard/get?song_id=${encodeURIComponent(this.songId)}&difficulty=${this.difficulty}`
|
||||
)
|
||||
|
||||
var data = JSON.parse(response)
|
||||
if (data.status === "ok") {
|
||||
this.leaderboardData = data.leaderboard || []
|
||||
@@ -84,6 +102,7 @@ class Leaderboard {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
changeDifficulty(direction) {
|
||||
var difficulties = ["easy", "normal", "hard", "oni", "ura"]
|
||||
var currentIndex = difficulties.indexOf(this.difficulty)
|
||||
@@ -108,11 +127,11 @@ class Leaderboard {
|
||||
var x = (event.offsetX || event.touches[0].pageX - rect.left)
|
||||
var y = (event.offsetY || event.touches[0].pageY - rect.top)
|
||||
|
||||
// Check if clicked outside modal (approximately)
|
||||
// Check if clicked outside modal - updated dimensions
|
||||
var centerX = this.canvas.width / 2
|
||||
var centerY = this.canvas.height / 2
|
||||
var modalWidth = 800
|
||||
var modalHeight = 600
|
||||
var modalWidth = 880 // Updated from 800
|
||||
var modalHeight = 640 // Updated from 600
|
||||
|
||||
if (x < centerX - modalWidth / 2 || x > centerX + modalWidth / 2 ||
|
||||
y < centerY - modalHeight / 2 || y > centerY + modalHeight / 2) {
|
||||
@@ -158,119 +177,223 @@ class Leaderboard {
|
||||
|
||||
this.ctx.scale(ratio, ratio)
|
||||
|
||||
// Draw modal background
|
||||
var modalX = 240
|
||||
var modalY = 60
|
||||
var modalW = 800
|
||||
var modalH = 600
|
||||
// Draw modal background with gradient
|
||||
var modalX = 200
|
||||
var modalY = 40
|
||||
var modalW = 880
|
||||
var modalH = 640
|
||||
|
||||
this.ctx.fillStyle = "#ffffff"
|
||||
// Gradient background
|
||||
var bgGradient = this.ctx.createLinearGradient(modalX, modalY, modalX, modalY + modalH)
|
||||
bgGradient.addColorStop(0, "#ffffff")
|
||||
bgGradient.addColorStop(1, "#f0f4f8")
|
||||
this.ctx.fillStyle = bgGradient
|
||||
this.ctx.shadowColor = "rgba(0, 0, 0, 0.3)"
|
||||
this.ctx.shadowBlur = 30
|
||||
this.ctx.shadowOffsetX = 0
|
||||
this.ctx.shadowOffsetY = 10
|
||||
this.ctx.fillRect(modalX, modalY, modalW, modalH)
|
||||
this.ctx.shadowBlur = 0
|
||||
|
||||
this.ctx.strokeStyle = "#333333"
|
||||
this.ctx.lineWidth = 4
|
||||
// Modal border with gradient
|
||||
var borderGradient = this.ctx.createLinearGradient(modalX, modalY, modalX + modalW, modalY + modalH)
|
||||
borderGradient.addColorStop(0, "#4a90e2")
|
||||
borderGradient.addColorStop(0.5, "#7b68ee")
|
||||
borderGradient.addColorStop(1, "#ff6b9d")
|
||||
this.ctx.strokeStyle = borderGradient
|
||||
this.ctx.lineWidth = 6
|
||||
this.ctx.strokeRect(modalX, modalY, modalW, modalH)
|
||||
|
||||
// Draw title
|
||||
this.ctx.fillStyle = "#000000"
|
||||
this.ctx.font = "bold 40px " + (strings.font || "sans-serif")
|
||||
// Draw title with gradient
|
||||
var titleGradient = this.ctx.createLinearGradient(0, 90, 0, 130)
|
||||
titleGradient.addColorStop(0, "#4a90e2")
|
||||
titleGradient.addColorStop(1, "#7b68ee")
|
||||
this.ctx.fillStyle = titleGradient
|
||||
this.ctx.font = "bold 48px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("🏆 排行榜 Leaderboard", 640, 110)
|
||||
this.ctx.shadowColor = "rgba(0, 0, 0, 0.2)"
|
||||
this.ctx.shadowBlur = 5
|
||||
this.ctx.shadowOffsetX = 2
|
||||
this.ctx.shadowOffsetY = 2
|
||||
this.ctx.fillText("🏆 排行榜 Leaderboard", 640, 100)
|
||||
this.ctx.shadowBlur = 0
|
||||
|
||||
// Draw difficulty selector
|
||||
// Draw difficulty selector with improved styling
|
||||
var diffX = 640
|
||||
var diffY = 160
|
||||
var diffY = 155
|
||||
var difficulties = [
|
||||
{ id: "easy", name: "簡単", color: "#00a0e9" },
|
||||
{ id: "normal", name: "普通", color: "#00a040" },
|
||||
{ id: "hard", name: "難しい", color: "#ff8c00" },
|
||||
{ id: "oni", name: "鬼", color: "#dc143c" },
|
||||
{ id: "ura", name: "裏", color: "#9400d3" }
|
||||
{ id: "easy", name: "簡単", color: "#00a0e9", glow: "#00d4ff" },
|
||||
{ id: "normal", name: "普通", color: "#00a040", glow: "#00ff66" },
|
||||
{ id: "hard", name: "難しい", color: "#ff8c00", glow: "#ffb347" },
|
||||
{ id: "oni", name: "鬼", color: "#dc143c", glow: "#ff6b9d" },
|
||||
{ id: "ura", name: "裏", color: "#9400d3", glow: "#da70d6" }
|
||||
]
|
||||
|
||||
this.ctx.font = "24px " + (strings.font || "sans-serif")
|
||||
this.ctx.font = "bold 22px " + (strings.font || "sans-serif")
|
||||
for (var i = 0; i < difficulties.length; i++) {
|
||||
var diff = difficulties[i]
|
||||
var x = diffX - 200 + i * 100
|
||||
var x = diffX - 220 + i * 110
|
||||
|
||||
if (diff.id === this.difficulty) {
|
||||
this.ctx.fillStyle = diff.color
|
||||
this.ctx.fillRect(x - 45, diffY - 25, 90, 40)
|
||||
// Selected difficulty with glow effect
|
||||
this.ctx.shadowColor = diff.glow
|
||||
this.ctx.shadowBlur = 15
|
||||
var gradient = this.ctx.createLinearGradient(x - 50, diffY - 30, x + 50, diffY + 20)
|
||||
gradient.addColorStop(0, diff.color)
|
||||
gradient.addColorStop(1, diff.glow)
|
||||
this.ctx.fillStyle = gradient
|
||||
this.ctx.fillRect(x - 50, diffY - 30, 100, 50)
|
||||
this.ctx.shadowBlur = 0
|
||||
this.ctx.fillStyle = "#ffffff"
|
||||
this.ctx.shadowColor = "rgba(0, 0, 0, 0.5)"
|
||||
this.ctx.shadowBlur = 3
|
||||
} else {
|
||||
// Unselected difficulty
|
||||
this.ctx.strokeStyle = diff.color
|
||||
this.ctx.lineWidth = 2
|
||||
this.ctx.strokeRect(x - 45, diffY - 25, 90, 40)
|
||||
this.ctx.lineWidth = 3
|
||||
this.ctx.strokeRect(x - 50, diffY - 30, 100, 50)
|
||||
this.ctx.fillStyle = diff.color
|
||||
}
|
||||
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText(diff.name, x, diffY + 5)
|
||||
this.ctx.fillText(diff.name, x, diffY)
|
||||
this.ctx.shadowBlur = 0
|
||||
}
|
||||
|
||||
// Draw month info
|
||||
// Draw month info with style
|
||||
this.ctx.fillStyle = "#666666"
|
||||
this.ctx.font = "18px " + (strings.font || "sans-serif")
|
||||
this.ctx.font = "20px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("当月排行 " + this.currentMonth, 640, 210)
|
||||
this.ctx.fillText("📅 当月排行 " + this.currentMonth, 640, 215)
|
||||
|
||||
// Header bar
|
||||
var headerY = 250
|
||||
var headerGradient = this.ctx.createLinearGradient(modalX, headerY, modalX, headerY + 35)
|
||||
headerGradient.addColorStop(0, "#e8eef5")
|
||||
headerGradient.addColorStop(1, "#d0dae8")
|
||||
this.ctx.fillStyle = headerGradient
|
||||
this.ctx.fillRect(modalX + 20, headerY, modalW - 40, 35)
|
||||
|
||||
this.ctx.fillStyle = "#333333"
|
||||
this.ctx.font = "bold 18px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("排名", modalX + 80, headerY + 23)
|
||||
this.ctx.textAlign = "left"
|
||||
this.ctx.fillText("玩家", modalX + 140, headerY + 23)
|
||||
this.ctx.textAlign = "right"
|
||||
this.ctx.fillText("分数", modalX + modalW - 60, headerY + 23)
|
||||
|
||||
// Draw leaderboard entries
|
||||
var startY = 240
|
||||
var rowHeight = 35
|
||||
var startY = 295
|
||||
var rowHeight = 38
|
||||
|
||||
this.ctx.font = "20px " + (strings.font || "sans-serif")
|
||||
this.ctx.font = "22px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "left"
|
||||
|
||||
if (this.leaderboardData.length === 0) {
|
||||
this.ctx.fillStyle = "#999999"
|
||||
this.ctx.font = "24px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("暂无排行数据", 640, startY + 100)
|
||||
this.ctx.fillText("暂无排行数据", 640, startY + 120)
|
||||
} else {
|
||||
for (var i = 0; i < Math.min(this.leaderboardData.length, 15); i++) {
|
||||
var entry = this.leaderboardData[i]
|
||||
var y = startY + i * rowHeight
|
||||
var rank = entry.rank
|
||||
|
||||
// Rank background color
|
||||
// Rank background color with gradient
|
||||
var gradient
|
||||
if (rank === 1) {
|
||||
this.ctx.fillStyle = "#ffd700" // Gold
|
||||
// Gold gradient for 1st place
|
||||
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||
gradient.addColorStop(0, "#ffd700")
|
||||
gradient.addColorStop(1, "#ffed4e")
|
||||
this.ctx.fillStyle = gradient
|
||||
this.ctx.shadowColor = "#ffd700"
|
||||
this.ctx.shadowBlur = 20
|
||||
} else if (rank === 2) {
|
||||
this.ctx.fillStyle = "#c0c0c0" // Silver
|
||||
// Silver gradient for 2nd place
|
||||
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||
gradient.addColorStop(0, "#c0c0c0")
|
||||
gradient.addColorStop(1, "#e8e8e8")
|
||||
this.ctx.fillStyle = gradient
|
||||
this.ctx.shadowColor = "#c0c0c0"
|
||||
this.ctx.shadowBlur = 15
|
||||
} else if (rank === 3) {
|
||||
this.ctx.fillStyle = "#cd7f32" // Bronze
|
||||
// Bronze gradient for 3rd place
|
||||
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||
gradient.addColorStop(0, "#cd7f32")
|
||||
gradient.addColorStop(1, "#e9a86a")
|
||||
this.ctx.fillStyle = gradient
|
||||
this.ctx.shadowColor = "#cd7f32"
|
||||
this.ctx.shadowBlur = 12
|
||||
} else {
|
||||
this.ctx.fillStyle = "#f5f5f5"
|
||||
// Regular entries with subtle gradient
|
||||
gradient = this.ctx.createLinearGradient(modalX + 20, y - 25, modalX + 20, y + 10)
|
||||
gradient.addColorStop(0, "#ffffff")
|
||||
gradient.addColorStop(1, "#f8f9fa")
|
||||
this.ctx.fillStyle = gradient
|
||||
}
|
||||
this.ctx.fillRect(modalX + 20, y - 20, modalW - 40, 30)
|
||||
|
||||
// Rank
|
||||
this.ctx.fillStyle = rank <= 3 ? "#ffffff" : "#333333"
|
||||
this.ctx.font = "bold 20px " + (strings.font || "sans-serif")
|
||||
// Rounded rectangle effect
|
||||
this.ctx.fillRect(modalX + 20, y - 25, modalW - 40, 35)
|
||||
this.ctx.shadowBlur = 0
|
||||
|
||||
// Border for entries
|
||||
if (rank <= 3) {
|
||||
this.ctx.strokeStyle = rank === 1 ? "#ffd700" : rank === 2 ? "#c0c0c0" : "#cd7f32"
|
||||
this.ctx.lineWidth = 2
|
||||
this.ctx.strokeRect(modalX + 20, y - 25, modalW - 40, 35)
|
||||
}
|
||||
|
||||
// Rank with medal emoji for top 3
|
||||
if (rank === 1) {
|
||||
this.ctx.fillStyle = "#8b6914"
|
||||
this.ctx.font = "bold 24px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText(rank, modalX + 60, y)
|
||||
this.ctx.fillText("🥇", modalX + 80, y + 2)
|
||||
} else if (rank === 2) {
|
||||
this.ctx.fillStyle = "#5c5c5c"
|
||||
this.ctx.font = "bold 24px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("🥈", modalX + 80, y + 2)
|
||||
} else if (rank === 3) {
|
||||
this.ctx.fillStyle = "#6d4423"
|
||||
this.ctx.font = "bold 24px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("🥉", modalX + 80, y + 2)
|
||||
} else {
|
||||
this.ctx.fillStyle = rank <= 3 ? "#ffffff" : "#555555"
|
||||
this.ctx.font = "bold 22px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("#" + rank, modalX + 80, y + 2)
|
||||
}
|
||||
|
||||
// Display name
|
||||
this.ctx.fillStyle = "#000000"
|
||||
this.ctx.font = "20px " + (strings.font || "sans-serif")
|
||||
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#333333"
|
||||
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "left"
|
||||
var displayName = entry.display_name || entry.username
|
||||
if (displayName.length > 15) {
|
||||
displayName = displayName.substring(0, 15) + "..."
|
||||
if (displayName.length > 18) {
|
||||
displayName = displayName.substring(0, 18) + "..."
|
||||
}
|
||||
this.ctx.fillText(displayName, modalX + 100, y)
|
||||
this.ctx.fillText(displayName, modalX + 140, y + 2)
|
||||
|
||||
// Score
|
||||
var score = entry.score && entry.score.score ? entry.score.score : 0
|
||||
// Score with formatting
|
||||
// Score with formatting
|
||||
var score = entry.score_value !== undefined ? entry.score_value : (entry.score && entry.score.score ? entry.score.score : 0)
|
||||
this.ctx.fillStyle = rank <= 3 ? "#1a1a1a" : "#555555"
|
||||
this.ctx.font = (rank <= 3 ? "bold " : "") + "20px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "right"
|
||||
this.ctx.fillText(score.toLocaleString(), modalX + modalW - 40, y)
|
||||
this.ctx.fillText(score.toLocaleString(), modalX + modalW - 60, y + 2)
|
||||
}
|
||||
}
|
||||
|
||||
// Draw close hint
|
||||
this.ctx.fillStyle = "#666666"
|
||||
// Draw close hint with icon
|
||||
this.ctx.fillStyle = "#888888"
|
||||
this.ctx.font = "18px " + (strings.font || "sans-serif")
|
||||
this.ctx.textAlign = "center"
|
||||
this.ctx.fillText("按ESC或点击外部关闭 Press ESC or click outside to close", 640, 680)
|
||||
this.ctx.fillText("⌨️ 按ESC或点击外部关闭 Press ESC or click outside to close", 640, 665)
|
||||
|
||||
this.ctx.restore()
|
||||
}
|
||||
|
||||
@@ -178,7 +178,93 @@ class Scoresheet {
|
||||
}
|
||||
this.game.appendChild(this.tetsuoHana)
|
||||
}
|
||||
|
||||
// Add leaderboard submit button if user is logged in
|
||||
if (account.loggedIn && !this.multiplayer) {
|
||||
this.leaderboardBtn = document.createElement("div")
|
||||
this.leaderboardBtn.id = "leaderboard-submit-btn"
|
||||
this.leaderboardBtn.innerHTML = "🏆 提交排行榜<br><small>Submit to Leaderboard</small>"
|
||||
this.leaderboardBtn.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 25px;
|
||||
background: linear-gradient(135deg, #ff6b9d, #c44db3);
|
||||
color: white;
|
||||
border-radius: 15px;
|
||||
font-size: 18px;
|
||||
font-family: ${strings.font || "sans-serif"};
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||
border: 3px solid #fff;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
`
|
||||
this.leaderboardBtn.onmouseover = () => {
|
||||
if (!this.leaderboardSubmitted) {
|
||||
this.leaderboardBtn.style.transform = "scale(1.05)"
|
||||
this.leaderboardBtn.style.boxShadow = "0 6px 20px rgba(0,0,0,0.4)"
|
||||
}
|
||||
}
|
||||
this.leaderboardBtn.onmouseout = () => {
|
||||
this.leaderboardBtn.style.transform = "scale(1)"
|
||||
this.leaderboardBtn.style.boxShadow = "0 4px 15px rgba(0,0,0,0.3)"
|
||||
}
|
||||
this.leaderboardBtn.onclick = () => this.onLeaderboardBtnClick()
|
||||
this.game.appendChild(this.leaderboardBtn)
|
||||
}
|
||||
}
|
||||
|
||||
onLeaderboardBtnClick() {
|
||||
if (this.leaderboardSubmitted) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback: This is CRITICAL for cases where saveScore() failed or was skipped
|
||||
if (!this.leaderboardData) {
|
||||
console.warn("Leaderboard data missing, attempting to reconstruct...")
|
||||
var song = this.controller.selectedSong
|
||||
var results = this.resultsObj
|
||||
|
||||
if (song && results) {
|
||||
var songId = song.id || song.hash
|
||||
|
||||
if (songId) {
|
||||
this.leaderboardData = {
|
||||
songId: songId,
|
||||
difficulty: results.difficulty,
|
||||
scoreObj: Object.assign({ score: results.points }, results)
|
||||
}
|
||||
// Clean up scoreObj
|
||||
if (this.leaderboardData.scoreObj) {
|
||||
delete this.leaderboardData.scoreObj.title
|
||||
delete this.leaderboardData.scoreObj.difficulty
|
||||
delete this.leaderboardData.scoreObj.gauge
|
||||
}
|
||||
console.log("Leaderboard data reconstructed:", this.leaderboardData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.leaderboardData || !this.leaderboardData.songId) {
|
||||
this.showLeaderboardNotification("no_song_id")
|
||||
return
|
||||
}
|
||||
|
||||
// Disable button and show submitting state
|
||||
this.leaderboardBtn.innerHTML = "⏳ 提交中..."
|
||||
this.leaderboardBtn.style.background = "linear-gradient(135deg, #888, #666)"
|
||||
this.leaderboardBtn.style.cursor = "default"
|
||||
|
||||
this.submitToLeaderboard(
|
||||
this.leaderboardData.songId,
|
||||
this.leaderboardData.difficulty,
|
||||
this.leaderboardData.scoreObj
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
redraw() {
|
||||
if (!this.redrawRunning) {
|
||||
@@ -926,13 +1012,24 @@ class Scoresheet {
|
||||
var title = this.controller.selectedSong.originalTitle
|
||||
var hash = this.controller.selectedSong.hash
|
||||
var difficulty = this.resultsObj.difficulty
|
||||
var songId = this.controller.selectedSong.id
|
||||
// Use id if available, otherwise use hash
|
||||
var songId = this.controller.selectedSong.id || hash
|
||||
var oldScore = scoreStorage.get(hash, difficulty, true)
|
||||
var clearReached = this.controller.game.rules.clearReached(this.resultsObj.gauge)
|
||||
var crown = ""
|
||||
if (clearReached) {
|
||||
crown = this.resultsObj.bad === 0 ? "gold" : "silver"
|
||||
}
|
||||
|
||||
// Store data for manual leaderboard submission
|
||||
this.leaderboardData = {
|
||||
songId: songId,
|
||||
difficulty: difficulty,
|
||||
scoreObj: Object.assign({ score: this.resultsObj.points }, this.resultsObj)
|
||||
}
|
||||
this.leaderboardSubmitted = false
|
||||
|
||||
|
||||
if (!oldScore || oldScore.points <= this.resultsObj.points) {
|
||||
if (oldScore && (oldScore.crown === "gold" || oldScore.crown === "silver" && !crown)) {
|
||||
crown = oldScore.crown
|
||||
@@ -941,10 +1038,7 @@ class Scoresheet {
|
||||
delete this.resultsObj.title
|
||||
delete this.resultsObj.difficulty
|
||||
delete this.resultsObj.gauge
|
||||
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).then(() => {
|
||||
// Auto-submit to leaderboard if logged in and has song ID
|
||||
this.submitToLeaderboard(songId, difficulty, this.resultsObj)
|
||||
}).catch(() => {
|
||||
scoreStorage.add(hash, difficulty, this.resultsObj, true, title).catch(() => {
|
||||
this.showWarning = { name: "scoreSaveFailed" }
|
||||
})
|
||||
} else if (oldScore && (crown === "gold" && oldScore.crown !== "gold" || crown && !oldScore.crown)) {
|
||||
@@ -957,28 +1051,130 @@ class Scoresheet {
|
||||
this.scoreSaved = true
|
||||
}
|
||||
|
||||
|
||||
submitToLeaderboard(songId, difficulty, scoreObj) {
|
||||
// Only submit if user is logged in and song has valid ID
|
||||
// Only submit if user is logged in and song has an ID
|
||||
if (!account.loggedIn || !songId) {
|
||||
return
|
||||
}
|
||||
|
||||
var self = this
|
||||
loader.getCsrfToken().then(token => {
|
||||
var request = new XMLHttpRequest()
|
||||
request.open("POST", "api/leaderboard/submit")
|
||||
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8")
|
||||
request.setRequestHeader("X-CSRFToken", token)
|
||||
|
||||
request.onload = function () {
|
||||
if (request.status === 200) {
|
||||
try {
|
||||
var response = JSON.parse(request.responseText)
|
||||
if (response.status === "ok") {
|
||||
self.leaderboardSubmitted = true
|
||||
self.showLeaderboardNotification(response.message)
|
||||
// Update button to show success
|
||||
if (self.leaderboardBtn) {
|
||||
self.leaderboardBtn.innerHTML = "✅ 已提交<br><small>Submitted!</small>"
|
||||
self.leaderboardBtn.style.background = "linear-gradient(135deg, #4CAF50, #45a049)"
|
||||
}
|
||||
} else {
|
||||
// Show error
|
||||
self.showLeaderboardNotification("error")
|
||||
if (self.leaderboardBtn) {
|
||||
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to parse leaderboard response:", e)
|
||||
self.showLeaderboardNotification("error")
|
||||
}
|
||||
} else {
|
||||
self.showLeaderboardNotification("error")
|
||||
if (self.leaderboardBtn) {
|
||||
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = function () {
|
||||
self.showLeaderboardNotification("error")
|
||||
if (self.leaderboardBtn) {
|
||||
self.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||
self.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||
}
|
||||
}
|
||||
|
||||
request.send(JSON.stringify({
|
||||
song_id: songId,
|
||||
difficulty: difficulty,
|
||||
score: scoreObj
|
||||
}))
|
||||
}).catch(() => {
|
||||
// Silently fail - leaderboard submission is optional
|
||||
console.log("Leaderboard submission failed")
|
||||
this.showLeaderboardNotification("error")
|
||||
if (this.leaderboardBtn) {
|
||||
this.leaderboardBtn.innerHTML = "❌ 失败<br><small>Failed</small>"
|
||||
this.leaderboardBtn.style.background = "linear-gradient(135deg, #f44336, #d32f2f)"
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
showLeaderboardNotification(message) {
|
||||
var notification = document.createElement("div")
|
||||
notification.className = "leaderboard-notification"
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
padding: 15px 25px;
|
||||
background: linear-gradient(135deg, #4a90e2, #7b68ee);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
font-size: 16px;
|
||||
font-family: ${strings.font || "sans-serif"};
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 15px rgba(0,0,0,0.3);
|
||||
opacity: 0;
|
||||
transform: translateX(100px);
|
||||
transition: all 0.3s ease-out;
|
||||
`
|
||||
|
||||
var text = ""
|
||||
switch (message) {
|
||||
case "score_submitted": text = "🏆 成绩已提交到排行榜!"; break
|
||||
case "score_updated": text = "🎉 排行榜成绩已更新!"; break
|
||||
case "score_not_higher": text = "📊 已有更高成绩"; break
|
||||
case "score_too_low": text = "未进入排行榜前50"; break
|
||||
case "error": text = "❌ 提交失败,请重试"; break
|
||||
case "no_song_id": text = "❌ 无法提交:歌曲ID缺失"; break
|
||||
default: text = "排行榜已更新"
|
||||
}
|
||||
notification.innerText = text
|
||||
|
||||
document.body.appendChild(notification)
|
||||
|
||||
// Trigger animation
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = "1"
|
||||
notification.style.transform = "translateX(0)"
|
||||
}, 10)
|
||||
|
||||
// Remove after 3 seconds
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = "0"
|
||||
notification.style.transform = "translateX(100px)"
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification)
|
||||
}
|
||||
}, 300)
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
|
||||
clean() {
|
||||
this.keyboard.clean()
|
||||
this.gamepad.clean()
|
||||
@@ -997,10 +1193,17 @@ class Scoresheet {
|
||||
if (!this.multiplayer) {
|
||||
delete this.tetsuoHana
|
||||
}
|
||||
// Clean up leaderboard button
|
||||
if (this.leaderboardBtn && this.leaderboardBtn.parentNode) {
|
||||
this.leaderboardBtn.parentNode.removeChild(this.leaderboardBtn)
|
||||
}
|
||||
delete this.leaderboardBtn
|
||||
delete this.leaderboardData
|
||||
delete this.ctx
|
||||
delete this.canvas
|
||||
delete this.fadeScreen
|
||||
delete this.results
|
||||
delete this.rules
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -359,6 +359,7 @@ class Search{
|
||||
}
|
||||
|
||||
highlightResult(text, result) {
|
||||
if (text === null || text === undefined) return document.createDocumentFragment();
|
||||
var fragment = document.createDocumentFragment()
|
||||
var ranges = (result ? result.ranges : null) || []
|
||||
var lastIdx = 0
|
||||
|
||||
@@ -3335,9 +3335,14 @@ class SongSelect {
|
||||
});
|
||||
}
|
||||
toLeaderboard() {
|
||||
if (!this.songs[this.selectedSong].id) {
|
||||
var song = this.songs[this.selectedSong]
|
||||
var songId = song.id || song.hash
|
||||
|
||||
// Allow leaderboard for any song with an ID (numeric or hash)
|
||||
if (!songId) {
|
||||
return
|
||||
}
|
||||
|
||||
// Default to first available difficulty if not in a valid difficulty selection
|
||||
var selectedDiff = this.selectedDiff - this.diffOptions.length
|
||||
if (selectedDiff < 0) {
|
||||
@@ -3347,6 +3352,7 @@ class SongSelect {
|
||||
|
||||
this.clean()
|
||||
this.playSound("se_don")
|
||||
new Leaderboard().display(this.songs[this.selectedSong].id, diffId)
|
||||
new Leaderboard().display(songId, diffId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
133
reproduce_issue.py
Normal file
133
reproduce_issue.py
Normal file
@@ -0,0 +1,133 @@
|
||||
|
||||
import requests
|
||||
import json
|
||||
import base64
|
||||
|
||||
BASE_URL = "http://localhost:34801"
|
||||
|
||||
def register_user(username, password):
|
||||
url = f"{BASE_URL}/api/register"
|
||||
data = {"username": username, "password": password}
|
||||
try:
|
||||
response = requests.post(url, json=data)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
print(f"Register failed: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"Register error: {e}")
|
||||
return None
|
||||
|
||||
def login_user(username, password):
|
||||
url = f"{BASE_URL}/api/login"
|
||||
data = {"username": username, "password": password}
|
||||
session = requests.Session()
|
||||
try:
|
||||
response = session.post(url, json=data)
|
||||
if response.status_code == 200:
|
||||
return session
|
||||
print(f"Login failed: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"Login error: {e}")
|
||||
return None
|
||||
|
||||
def get_csrf_token(session):
|
||||
url = f"{BASE_URL}/api/csrftoken"
|
||||
response = session.get(url)
|
||||
if response.status_code == 200:
|
||||
return response.json()['token']
|
||||
return None
|
||||
|
||||
def submit_score(session, song_id, difficulty, score_obj, csrf_token):
|
||||
url = f"{BASE_URL}/api/leaderboard/submit"
|
||||
headers = {"X-CSRFToken": csrf_token}
|
||||
data = {
|
||||
"song_id": song_id,
|
||||
"difficulty": difficulty,
|
||||
"score": score_obj
|
||||
}
|
||||
response = session.post(url, json=data, headers=headers)
|
||||
return response.json()
|
||||
|
||||
def get_leaderboard(session, song_id, difficulty):
|
||||
url = f"{BASE_URL}/api/leaderboard/get"
|
||||
params = {"song_id": song_id, "difficulty": difficulty}
|
||||
response = session.get(url, params=params)
|
||||
return response.json()
|
||||
|
||||
def main():
|
||||
username = "debug_user_1"
|
||||
password = "password123"
|
||||
|
||||
print("1. Registering/Logging in...")
|
||||
# Try login first
|
||||
session = login_user(username, password)
|
||||
if not session:
|
||||
# Register
|
||||
reg = register_user(username, password)
|
||||
if reg:
|
||||
session = login_user(username, password)
|
||||
else:
|
||||
print("Could not register or login")
|
||||
return
|
||||
|
||||
print("2. Getting CSRF token...")
|
||||
csrf_token = get_csrf_token(session)
|
||||
if not csrf_token:
|
||||
print("Could not get CSRF token")
|
||||
return
|
||||
|
||||
song_id = 1 # Assuming song 1 exists
|
||||
difficulty = "oni"
|
||||
|
||||
# Simulate scoresheet.js submission (points only)
|
||||
score_obj = {
|
||||
"points": 123456,
|
||||
"good": 100,
|
||||
"ok": 10,
|
||||
"bad": 0,
|
||||
"maxCombo": 110,
|
||||
"drumroll": 5,
|
||||
"crown": "gold"
|
||||
}
|
||||
|
||||
print(f"3. Submitting score: {score_obj['points']}")
|
||||
submit_res = submit_score(session, song_id, difficulty, score_obj, csrf_token)
|
||||
print(f"Submit response: {submit_res}")
|
||||
|
||||
print("4. Fetching leaderboard...")
|
||||
leaderboard_res = get_leaderboard(session, song_id, difficulty)
|
||||
|
||||
if leaderboard_res['status'] == 'ok':
|
||||
leaderboard = leaderboard_res['leaderboard']
|
||||
found = False
|
||||
for entry in leaderboard:
|
||||
if entry['username'] == username:
|
||||
found = True
|
||||
print(f"Found entry: {entry}")
|
||||
print(f"entry.score_value: {entry.get('score_value')}")
|
||||
print(f"entry.score: {entry.get('score')}")
|
||||
|
||||
score_value = entry.get('score_value')
|
||||
score_data = entry.get('score')
|
||||
|
||||
# Check what leaderboard.js would see
|
||||
if score_data and 'score' in score_data:
|
||||
print(f"JS would assume score: {score_data['score']}")
|
||||
else:
|
||||
print("JS would assume score: 0 (undefined)")
|
||||
|
||||
if score_value == 123456:
|
||||
print("SUCCESS: score_value is correct.")
|
||||
else:
|
||||
print(f"FAILURE: score_value is incorrect (expected 123456, got {score_value})")
|
||||
|
||||
if score_data and score_data.get('points') == 123456:
|
||||
print("SUCCESS: points preserved in score object.")
|
||||
|
||||
if not found:
|
||||
print("Entry not found in leaderboard (maybe not top 50?)")
|
||||
else:
|
||||
print(f"Failed to get leaderboard: {leaderboard_res}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -85,9 +85,10 @@ leaderboard_submit = {
|
||||
'$schema': 'http://json-schema.org/schema#',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'song_id': {'type': 'number'},
|
||||
'song_id': {'type': ['number', 'string']},
|
||||
'difficulty': {'type': 'string'},
|
||||
'score': {'type': 'object'}
|
||||
},
|
||||
'required': ['song_id', 'difficulty', 'score']
|
||||
}
|
||||
|
||||
|
||||
2
setup.sh
2
setup.sh
@@ -8,7 +8,7 @@ CODENAME=${VERSION_CODENAME:-}
|
||||
VERSION=${VERSION_ID:-}
|
||||
|
||||
echo "更新系统软件源..."
|
||||
apt-get update -y
|
||||
apt-get update -y || true
|
||||
echo "安装基础依赖..."
|
||||
apt-get install -y python3 python3-venv python3-pip git ffmpeg rsync curl gnupg libcap2-bin
|
||||
|
||||
|
||||
32
test_frontend_logic.js
Normal file
32
test_frontend_logic.js
Normal file
@@ -0,0 +1,32 @@
|
||||
|
||||
// Simulation of song objects
|
||||
const songs = [
|
||||
{ name: "Server Song", id: 123, hash: "hash123" },
|
||||
{ name: "Custom Song (No ID)", id: undefined, hash: "hashCustom" },
|
||||
{ name: "Custom Song (Null ID)", id: null, hash: "hashNull" },
|
||||
{ name: "Edge Case (ID 0)", id: 0, hash: "hashZero" } // Checking if behavior is acceptable
|
||||
];
|
||||
|
||||
console.log("=== Frontend Logic Test: Song ID Fallback ===");
|
||||
|
||||
songs.forEach(song => {
|
||||
// Logic from scoresheet.js / songselect.js
|
||||
const songId = song.id || song.hash;
|
||||
|
||||
console.log(`Song: ${song.name}`);
|
||||
console.log(` Raw ID: ${song.id}, Hash: ${song.hash}`);
|
||||
console.log(` Resolved songId: ${songId}`);
|
||||
|
||||
if (song.id && songId === song.id) {
|
||||
console.log(" ✅ Correctly used ID");
|
||||
} else if (!song.id && songId === song.hash) {
|
||||
console.log(" ✅ Correctly fell back to Hash");
|
||||
} else if (song.id === 0 && songId === song.hash) {
|
||||
console.log(" ⚠️ ID was 0, used Hash. (Acceptable if ID > 0 always)");
|
||||
} else {
|
||||
console.log(" ❌ FAILED logic mismatch");
|
||||
}
|
||||
console.log("---");
|
||||
});
|
||||
|
||||
console.log("Tests Completed.");
|
||||
Reference in New Issue
Block a user