fix ranking order + display winning gifs
This commit is contained in:
parent
92dddca964
commit
4ba6a48246
@ -8,10 +8,8 @@ from django.shortcuts import get_object_or_404
|
||||
from accounts.models import CustomUser
|
||||
from animations.schemas import (
|
||||
RankingSchema,
|
||||
TournamentWinnersOut,
|
||||
TournamentSubmissionsOut,
|
||||
TournamentPuzzleResultsOut,
|
||||
PuzzleWinnerOut,
|
||||
PuzzleSubmissionsOut,
|
||||
PuzzleResultsOut,
|
||||
PuzzleSubmissionWithRankOut,
|
||||
@ -62,72 +60,6 @@ def results(request: HttpRequest) -> dict:
|
||||
return data
|
||||
|
||||
|
||||
@router.get("winners", response=TournamentWinnersOut)
|
||||
def winners(request: HttpRequest) -> TournamentWinnersOut:
|
||||
"""Get tournament winners with submission file URLs. Only available when tournament is closed."""
|
||||
collection = get_object_or_404(SteamCollection, is_active=True)
|
||||
|
||||
# Only allow access when tournament is closed
|
||||
if collection.accepting_submissions:
|
||||
raise HttpError(403, "Tournament is still accepting submissions")
|
||||
|
||||
# Get all puzzles
|
||||
puzzles = SteamCollectionItem.objects.filter(collection=collection).order_by("order_index")
|
||||
|
||||
# Get best response for each puzzle
|
||||
winners_by_puzzle = {}
|
||||
for puzzle in puzzles:
|
||||
# Get the best response for this puzzle (ranked by points)
|
||||
best_response = (
|
||||
PuzzleResponse.objects
|
||||
.filter(puzzle=puzzle, needs_manual_validation=False)
|
||||
.filter_user_best_response()
|
||||
.annotate_rank_points()
|
||||
.order_by("rank_points")
|
||||
.first()
|
||||
)
|
||||
|
||||
if best_response:
|
||||
winners_by_puzzle[puzzle.id] = best_response
|
||||
|
||||
# Build response
|
||||
winners_list = []
|
||||
for puzzle in puzzles:
|
||||
best_response = winners_by_puzzle.get(puzzle.id)
|
||||
|
||||
winner_data = None
|
||||
if best_response:
|
||||
# Get submission files
|
||||
files = best_response.files.all()
|
||||
winner_files = [
|
||||
WinnerFileOut(
|
||||
file_url=file.file_url or "",
|
||||
original_filename=file.original_filename
|
||||
)
|
||||
for file in files
|
||||
]
|
||||
|
||||
winner_data = WinnerResponseOut(
|
||||
user_id=best_response.submission.user.id if best_response.submission.user else 0,
|
||||
username=best_response.submission.user.username if best_response.submission.user else "Anonymous",
|
||||
final_cost=best_response.final_cost,
|
||||
final_cycles=best_response.final_cycles,
|
||||
final_area=best_response.final_area,
|
||||
rank_points=best_response.rank_points,
|
||||
files=winner_files,
|
||||
)
|
||||
|
||||
winners_list.append(
|
||||
PuzzleWinnerOut(
|
||||
puzzle_id=puzzle.id,
|
||||
puzzle_title=puzzle.title,
|
||||
winner=winner_data,
|
||||
)
|
||||
)
|
||||
|
||||
return TournamentWinnersOut(winners=winners_list)
|
||||
|
||||
|
||||
@router.get("top-submissions", response=TournamentSubmissionsOut)
|
||||
def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissionsOut:
|
||||
"""Get tournament top submissions for each puzzle. Only available when tournament is closed."""
|
||||
@ -143,13 +75,13 @@ def top_submissions(request: HttpRequest, limit: int = 5) -> TournamentSubmissio
|
||||
# Build response
|
||||
submissions_list = []
|
||||
for puzzle in puzzles:
|
||||
# Get the top N responses for this puzzle (ranked by points)
|
||||
# Get the top N responses for this puzzle (ranked by points, highest first)
|
||||
top_responses = (
|
||||
PuzzleResponse.objects
|
||||
.filter(puzzle=puzzle, needs_manual_validation=False)
|
||||
.filter_user_best_response()
|
||||
.annotate_rank_points()
|
||||
.order_by("rank_points")[:limit]
|
||||
.order_by("-rank_points")[:limit]
|
||||
)
|
||||
|
||||
# Build submission list for this puzzle
|
||||
@ -218,7 +150,7 @@ def puzzle_results(request: HttpRequest, limit: int = 5) -> TournamentPuzzleResu
|
||||
.filter(puzzle=puzzle, needs_manual_validation=False)
|
||||
.filter_user_best_response()
|
||||
.annotate_rank_points()
|
||||
.order_by("rank_points")[:limit]
|
||||
.order_by("-rank_points")[:limit]
|
||||
)
|
||||
|
||||
# Build submission list for this puzzle with rank
|
||||
|
||||
@ -64,14 +64,6 @@ class WinnerResponseOut(Schema):
|
||||
files: List[WinnerFileOut]
|
||||
|
||||
|
||||
class PuzzleWinnerOut(Schema):
|
||||
"""Schema for puzzle with winner info"""
|
||||
|
||||
puzzle_id: int
|
||||
puzzle_title: str
|
||||
winner: Optional[WinnerResponseOut]
|
||||
|
||||
|
||||
class PuzzleSubmissionsOut(Schema):
|
||||
"""Schema for puzzle with all top submissions"""
|
||||
|
||||
@ -80,12 +72,6 @@ class PuzzleSubmissionsOut(Schema):
|
||||
submissions: List[WinnerResponseOut]
|
||||
|
||||
|
||||
class TournamentWinnersOut(Schema):
|
||||
"""Schema for tournament winners results"""
|
||||
|
||||
winners: List[PuzzleWinnerOut]
|
||||
|
||||
|
||||
class TournamentSubmissionsOut(Schema):
|
||||
"""Schema for tournament top submissions results"""
|
||||
|
||||
|
||||
@ -52,6 +52,12 @@ const getRankBadge = (rank: number) => {
|
||||
return badges[rank - 1] || `#${rank}`;
|
||||
};
|
||||
|
||||
const getWinnersForPuzzle = (puzzle: any) => {
|
||||
if (!puzzle.submissions || puzzle.submissions.length === 0) return [];
|
||||
const topPoints = puzzle.submissions[0].rank_points;
|
||||
return puzzle.submissions.filter((submission: any) => submission.rank_points === topPoints);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchResults();
|
||||
});
|
||||
@ -99,6 +105,26 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Winners GIF Display (all with same top points) -->
|
||||
<div v-if="puzzle.submissions.length > 0 && getWinnersForPuzzle(puzzle).length > 0"
|
||||
class="bg-base-200 p-4 rounded-lg">
|
||||
<p class="text-xs text-base-content/70 font-semibold mb-3 text-center">🏆 Winning Solutions</p>
|
||||
<div class="flex flex-wrap justify-center gap-6">
|
||||
<template v-for="submission in getWinnersForPuzzle(puzzle)"
|
||||
:key="`${puzzle.puzzle_id}-${submission.user_id}`">
|
||||
<div v-if="submission && submission.files && submission.files.length > 0" class="text-center w-96">
|
||||
<div class="text-sm text-base-content/70 mb-3 font-semibold truncate">{{ submission.username }}
|
||||
</div>
|
||||
<button @click="openImageModal(submission.files[0].file_url, submission.files[0].original_filename)"
|
||||
class="hover:opacity-80 transition-opacity cursor-pointer w-full flex items-center justify-center">
|
||||
<img :src="submission.files[0].file_url" :alt="`${puzzle.puzzle_title} - ${submission.username}`"
|
||||
class="max-h-full max-w-full object-contain" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Table -->
|
||||
|
||||
@ -5,7 +5,6 @@ import type {
|
||||
PuzzleResponse,
|
||||
SubmissionFile,
|
||||
UserInfo,
|
||||
TournamentWinners,
|
||||
TournamentSubmissions,
|
||||
TournamentPuzzleResults
|
||||
} from '../types'
|
||||
@ -109,10 +108,6 @@ export class ApiService {
|
||||
return this.request<SteamCollection>('/submissions/collection')
|
||||
}
|
||||
|
||||
async getWinners(): Promise<ApiResponse<TournamentWinners>> {
|
||||
return this.request<TournamentWinners>('/results/winners')
|
||||
}
|
||||
|
||||
async getTopSubmissions(limit = 5): Promise<ApiResponse<TournamentSubmissions>> {
|
||||
return this.request<TournamentSubmissions>(`/results/top-submissions?limit=${limit}`)
|
||||
}
|
||||
|
||||
@ -125,16 +125,6 @@ export interface WinnerResponse {
|
||||
files: WinnerFile[]
|
||||
}
|
||||
|
||||
export interface PuzzleWinner {
|
||||
puzzle_id: number
|
||||
puzzle_title: string
|
||||
winner?: WinnerResponse
|
||||
}
|
||||
|
||||
export interface TournamentWinners {
|
||||
winners: PuzzleWinner[]
|
||||
}
|
||||
|
||||
export interface PuzzleSubmissions {
|
||||
puzzle_id: number
|
||||
puzzle_title: string
|
||||
|
||||
@ -1 +1 @@
|
||||
import{k as t,l as a,p as n,v as s}from"./style-C433w8gz.js";const c={key:0,class:"flex justify-center"},k={key:0,class:"badge badge-warning badge-lg"},d={key:1,class:"badge badge-lg"},l={key:2,class:"badge badge-lg"},o={key:3,class:"badge badge-lg"},g={key:1,class:"text-2xl text-base-content/50"},y=t({__name:"RankBadge",props:{rank:{}},setup(e){return(i,r)=>e.rank!==null?(n(),a("div",c,[e.rank===1?(n(),a("span",k," 🏆 #"+s(e.rank),1)):e.rank===2?(n(),a("span",d," 🥈 #"+s(e.rank),1)):e.rank===3?(n(),a("span",l," 🥉 #"+s(e.rank),1)):(n(),a("span",o," #"+s(e.rank),1))])):(n(),a("div",g," No rank yet "))}});export{y as _};
|
||||
import{k as t,l as a,p as n,v as s}from"./style-BKSucaDP.js";const c={key:0,class:"flex justify-center"},k={key:0,class:"badge badge-warning badge-lg"},d={key:1,class:"badge badge-lg"},l={key:2,class:"badge badge-lg"},o={key:3,class:"badge badge-lg"},g={key:1,class:"text-2xl text-base-content/50"},y=t({__name:"RankBadge",props:{rank:{}},setup(e){return(i,r)=>e.rank!==null?(n(),a("div",c,[e.rank===1?(n(),a("span",k," 🏆 #"+s(e.rank),1)):e.rank===2?(n(),a("span",d," 🥈 #"+s(e.rank),1)):e.rank===3?(n(),a("span",l," 🥉 #"+s(e.rank),1)):(n(),a("span",o," #"+s(e.rank),1))])):(n(),a("div",g," No rank yet "))}});export{y as _};
|
||||
@ -1 +1 @@
|
||||
import{k as b,c as v,r as g,l as a,p as n,s as t,F as h,y as x,v as i,x as f,O as _}from"./style-C433w8gz.js";const y={class:"min-h-screen bg-base-300 flex items-center justify-center px-4"},w={class:"w-full max-w-6xl"},k={class:"grid grid-cols-1 md:grid-cols-2 gap-8"},S=["onClick"],I={class:"relative h-60 bg-base-300 overflow-hidden"},j=["src","alt","onError"],E={key:1,class:"w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"},N={class:"card-body"},C={class:"card-title text-2xl"},B={class:"text-base-content/70"},O=b({__name:"Home",setup(A){const c=v(()=>[{id:"opus-magnum",title:"Opus Magnum",description:"Submit your best Opus Magnum puzzle solutions",appId:558990,path:"/opus-magnum"},{id:"noita",title:"Noita",description:"Submit your greatest Noita achievements",appId:881100,path:"/noita"}]),r=g(new Set),d=o=>`https://cdn.akamai.steamstatic.com/steam/apps/${o}/header.jpg`,u=o=>{r.value.add(o)},p=o=>{window.location.href=o};return(o,e)=>(n(),a("div",y,[t("div",w,[e[3]||(e[3]=t("div",{class:"text-center mb-12"},[t("h1",{class:"text-5xl font-bold mb-4"},"PolyLAN Submitter"),t("p",{class:"text-xl text-base-content/70"}," Choose a game and submit your best solutions ")],-1)),t("div",k,[(n(!0),a(h,null,x(c.value,s=>(n(),a("div",{key:s.id,onClick:m=>p(s.path),class:"card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden"},[t("figure",I,[r.value.has(s.appId)?(n(),a("div",E,[...e[0]||(e[0]=[t("i",{class:"mdi mdi-gamepad-variant text-5xl"},null,-1)])])):(n(),a("img",{key:0,src:d(s.appId),alt:s.title,onError:m=>u(s.appId),class:"w-full h-full object-cover"},null,40,j)),e[1]||(e[1]=t("div",{class:"absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"},null,-1))]),t("div",N,[t("h2",C,i(s.title),1),t("p",B,i(s.description),1),e[2]||(e[2]=t("div",{class:"card-actions justify-end mt-4"},[t("button",{class:"btn btn-primary"},[t("i",{class:"mdi mdi-arrow-right mr-2"}),f(" Submit results ")])],-1))])],8,S))),128))]),e[4]||(e[4]=t("div",{class:"text-center mt-12 text-base-content/50"},[t("p",null,"Select a game above to begin submitting")],-1))])]))}}),l="#app",$=document.querySelector(l),z=_(O,{...$?.dataset});z.mount(l);
|
||||
import{k as b,c as v,r as g,l as a,p as n,s as t,F as h,y as x,v as i,x as f,O as _}from"./style-BKSucaDP.js";const y={class:"min-h-screen bg-base-300 flex items-center justify-center px-4"},w={class:"w-full max-w-6xl"},k={class:"grid grid-cols-1 md:grid-cols-2 gap-8"},S=["onClick"],I={class:"relative h-60 bg-base-300 overflow-hidden"},j=["src","alt","onError"],E={key:1,class:"w-full h-full bg-gradient-to-br from-blue-600 to-blue-400 flex items-center justify-center text-white"},N={class:"card-body"},C={class:"card-title text-2xl"},B={class:"text-base-content/70"},O=b({__name:"Home",setup(A){const c=v(()=>[{id:"opus-magnum",title:"Opus Magnum",description:"Submit your best Opus Magnum puzzle solutions",appId:558990,path:"/opus-magnum"},{id:"noita",title:"Noita",description:"Submit your greatest Noita achievements",appId:881100,path:"/noita"}]),r=g(new Set),d=o=>`https://cdn.akamai.steamstatic.com/steam/apps/${o}/header.jpg`,u=o=>{r.value.add(o)},p=o=>{window.location.href=o};return(o,e)=>(n(),a("div",y,[t("div",w,[e[3]||(e[3]=t("div",{class:"text-center mb-12"},[t("h1",{class:"text-5xl font-bold mb-4"},"PolyLAN Submitter"),t("p",{class:"text-xl text-base-content/70"}," Choose a game and submit your best solutions ")],-1)),t("div",k,[(n(!0),a(h,null,x(c.value,s=>(n(),a("div",{key:s.id,onClick:m=>p(s.path),class:"card card-xl bg-base-200 shadow-xl hover:shadow-2xl transition-all cursor-pointer transform hover:-translate-y-2 hover:scale-[1.05] hover:bg-base-100 overflow-hidden"},[t("figure",I,[r.value.has(s.appId)?(n(),a("div",E,[...e[0]||(e[0]=[t("i",{class:"mdi mdi-gamepad-variant text-5xl"},null,-1)])])):(n(),a("img",{key:0,src:d(s.appId),alt:s.title,onError:m=>u(s.appId),class:"w-full h-full object-cover"},null,40,j)),e[1]||(e[1]=t("div",{class:"absolute inset-0 bg-black/30 group-hover:bg-black/20 transition-colors"},null,-1))]),t("div",N,[t("h2",C,i(s.title),1),t("p",B,i(s.description),1),e[2]||(e[2]=t("div",{class:"card-actions justify-end mt-4"},[t("button",{class:"btn btn-primary"},[t("i",{class:"mdi mdi-arrow-right mr-2"}),f(" Submit results ")])],-1))])],8,S))),128))]),e[4]||(e[4]=t("div",{class:"text-center mt-12 text-base-content/50"},[t("p",null,"Select a game above to begin submitting")],-1))])]))}}),l="#app",$=document.querySelector(l),z=_(O,{...$?.dataset});z.mount(l);
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,16 +1,20 @@
|
||||
{
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-IllWPCyW.js": {
|
||||
"file": "assets/RankBadge.vue_vue_type_script_setup_true_lang-IllWPCyW.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js": {
|
||||
"file": "assets/RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js",
|
||||
"name": "RankBadge.vue_vue_type_script_setup_true_lang",
|
||||
"imports": [
|
||||
"_style-C433w8gz.js"
|
||||
"_style-BKSucaDP.js"
|
||||
]
|
||||
},
|
||||
"_style-C433w8gz.js": {
|
||||
"file": "assets/style-C433w8gz.js",
|
||||
"_style-8zNjBvNQ.css": {
|
||||
"file": "assets/style-8zNjBvNQ.css",
|
||||
"src": "_style-8zNjBvNQ.css"
|
||||
},
|
||||
"_style-BKSucaDP.js": {
|
||||
"file": "assets/style-BKSucaDP.js",
|
||||
"name": "style",
|
||||
"css": [
|
||||
"assets/style-CMHHCLN4.css"
|
||||
"assets/style-8zNjBvNQ.css"
|
||||
],
|
||||
"assets": [
|
||||
"assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
||||
@ -19,10 +23,6 @@
|
||||
"assets/materialdesignicons-webfont-B7mPwVP_.ttf"
|
||||
]
|
||||
},
|
||||
"_style-CMHHCLN4.css": {
|
||||
"file": "assets/style-CMHHCLN4.css",
|
||||
"src": "_style-CMHHCLN4.css"
|
||||
},
|
||||
"node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot": {
|
||||
"file": "assets/materialdesignicons-webfont-CSr8KVlo.eot",
|
||||
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.eot"
|
||||
@ -40,32 +40,32 @@
|
||||
"src": "node_modules/.pnpm/@mdi+font@7.4.47/node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff2"
|
||||
},
|
||||
"src/home.ts": {
|
||||
"file": "assets/home-f2CGvY1q.js",
|
||||
"file": "assets/home-YbwRQP1Y.js",
|
||||
"name": "home",
|
||||
"src": "src/home.ts",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-C433w8gz.js"
|
||||
"_style-BKSucaDP.js"
|
||||
]
|
||||
},
|
||||
"src/noita.ts": {
|
||||
"file": "assets/noita-BcY9X9eS.js",
|
||||
"file": "assets/noita-Su4dRkwf.js",
|
||||
"name": "noita",
|
||||
"src": "src/noita.ts",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-C433w8gz.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-IllWPCyW.js"
|
||||
"_style-BKSucaDP.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js"
|
||||
]
|
||||
},
|
||||
"src/opus-magnum.ts": {
|
||||
"file": "assets/opus_magnum-CCZTjOvR.js",
|
||||
"file": "assets/opus_magnum-BftkLCBu.js",
|
||||
"name": "opus_magnum",
|
||||
"src": "src/opus-magnum.ts",
|
||||
"isEntry": true,
|
||||
"imports": [
|
||||
"_style-C433w8gz.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-IllWPCyW.js"
|
||||
"_style-BKSucaDP.js",
|
||||
"_RankBadge.vue_vue_type_script_setup_true_lang-c__WwHxT.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user