@@ -16,7 +16,7 @@ import type { Handler } from '../service/server';
1616import { Optional } from '../typeutils' ;
1717import { PERM , STATUS , STATUS_SHORT_TEXTS } from './builtin' ;
1818import * as document from './document' ;
19- import problem from './problem' ;
19+ import problem , { ProblemDoc } from './problem' ;
2020import user , { User } from './user' ;
2121
2222interface AcmJournal {
@@ -569,6 +569,98 @@ const ledo = buildContestRule({
569569 } ,
570570} , oi ) ;
571571
572+ const cf = buildContestRule ( {
573+ TEXT : 'Codeforces' ,
574+ check : ( ) => { } ,
575+ submitAfterAccept : false ,
576+ showScoreboard : ( tdoc , now ) => now > tdoc . beginAt ,
577+ showSelfRecord : ( ) => true ,
578+ showRecord : ( tdoc , now , user , pdoc ) => {
579+ if ( now > tdoc . endAt ) return true ;
580+ if ( pdoc && tdoc . lockedList [ pdoc . docId ] . includes ( user . _id ) ) return true ;
581+ return false ;
582+ } ,
583+ stat ( tdoc , journal ) {
584+ const ntry = Counter < number > ( ) ;
585+ const hackSucc = Counter < number > ( ) ;
586+ const hackFail = Counter < number > ( ) ;
587+ const detail = { } ;
588+ for ( const j of journal ) {
589+ if ( [ STATUS . STATUS_COMPILE_ERROR , STATUS . STATUS_FORMAT_ERROR ] . includes ( j . status ) ) continue ;
590+ // if (this.submitAfterAccept) continue;
591+ if ( STATUS . STATUS_ACCEPTED !== j . status ) ntry [ j . pid ] ++ ;
592+ if ( [ STATUS . STATUS_HACK_SUCCESSFUL , STATUS . STATUS_HACK_UNSUCCESSFUL ] . includes ( j . status ) ) {
593+ if ( j . status === STATUS . STATUS_HACK_SUCCESSFUL ) detail [ j . pid ] . hackSucc ++ ;
594+ else detail [ j . pid ] . hackFail ++ ;
595+ continue ;
596+ }
597+ const timePenaltyScore = Math . round ( Math . max ( j . score * 100
598+ - ( ( j . rid . getTimestamp ( ) . getTime ( ) - tdoc . beginAt . getTime ( ) ) * ( j . score * 100 ) ) / ( 250 * 60000 ) ,
599+ j . score * 100 * 0.3 ) ) ;
600+ const penaltyScore = Math . max ( timePenaltyScore - 50 * ( ntry [ j . pid ] ) , 0 ) ;
601+ if ( ! detail [ j . pid ] || detail [ j . pid ] . penaltyScore < penaltyScore ) {
602+ detail [ j . pid ] = {
603+ ...j ,
604+ penaltyScore,
605+ timePenaltyScore,
606+ ntry : ntry [ j . pid ] ,
607+ hackFail : hackFail [ j . pid ] ,
608+ hackSucc : hackSucc [ j . pid ] ,
609+ } ;
610+ }
611+ }
612+ let score = 0 ;
613+ let originalScore = 0 ;
614+ for ( const pid of tdoc . pids ) {
615+ if ( ! detail [ pid ] ) continue ;
616+ detail [ pid ] . penaltyScore -= 50 * detail [ pid ] . hackFail ;
617+ detail [ pid ] . penaltyScore += 100 * detail [ pid ] . hackSucc ;
618+ score += detail [ pid ] . penaltyScore ;
619+ originalScore += detail [ pid ] . score ;
620+ }
621+ return {
622+ score, originalScore, detail,
623+ } ;
624+ } ,
625+ async scoreboardRow ( config , _ , tdoc , pdict , udoc , rank , tsdoc , meta ) {
626+ const tsddict = tsdoc . detail || { } ;
627+ const row : ScoreboardRow = [
628+ { type : 'rank' , value : rank . toString ( ) } ,
629+ { type : 'user' , value : udoc . uname , raw : tsdoc . uid } ,
630+ ] ;
631+ if ( config . isExport ) {
632+ row . push ( { type : 'email' , value : udoc . mail } ) ;
633+ row . push ( { type : 'string' , value : udoc . school || '' } ) ;
634+ row . push ( { type : 'string' , value : udoc . displayName || '' } ) ;
635+ row . push ( { type : 'string' , value : udoc . studentId || '' } ) ;
636+ }
637+ row . push ( {
638+ type : 'total_score' ,
639+ value : tsdoc . score || 0 ,
640+ hover : tsdoc . score !== tsdoc . originalScore ? _ ( 'Original score: {0}' ) . format ( tsdoc . originalScore ) : '' ,
641+ } ) ;
642+ for ( const s of tsdoc . journal || [ ] ) {
643+ if ( ! pdict [ s . pid ] ) continue ;
644+ pdict [ s . pid ] . nSubmit ++ ;
645+ if ( s . status === STATUS . STATUS_ACCEPTED ) pdict [ s . pid ] . nAccept ++ ;
646+ }
647+ for ( const pid of tdoc . pids ) {
648+ row . push ( {
649+ type : 'record' ,
650+ value : tsddict [ pid ] ?. penaltyScore || '' ,
651+ hover : tsddict [ pid ] ?. penaltyScore
652+ ? `${ tsddict [ pid ] . timePenaltyScore } , -${ tsddict [ pid ] . ntry } , +${ tsddict [ pid ] . hackSucc } , -${ tsddict [ pid ] . hackFail } `
653+ : '' ,
654+ raw : tsddict [ pid ] ?. rid ,
655+ style : tsddict [ pid ] ?. status === STATUS . STATUS_ACCEPTED && tsddict [ pid ] ?. rid . getTimestamp ( ) . getTime ( ) === meta ?. first ?. [ pid ]
656+ ? 'background-color: rgb(217, 240, 199);'
657+ : undefined ,
658+ } ) ;
659+ }
660+ return row ;
661+ } ,
662+ } , oi ) ;
663+
572664const homework = buildContestRule ( {
573665 TEXT : 'Assignment' ,
574666 hidden : true ,
@@ -719,7 +811,7 @@ const homework = buildContestRule({
719811} ) ;
720812
721813export const RULES : ContestRules = {
722- acm, oi, homework, ioi, ledo, strictioi,
814+ acm, oi, homework, ioi, ledo, strictioi, cf ,
723815} ;
724816
725817const collBalloon = db . collection ( 'contest.balloon' ) ;
@@ -741,7 +833,7 @@ export async function add(
741833 RULES [ rule ] . check ( data ) ;
742834 await bus . parallel ( 'contest/before-add' , data ) ;
743835 const res = await document . add ( domainId , content , owner , document . TYPE_CONTEST , null , null , null , {
744- ...data , title, rule, beginAt, endAt, pids, attend : 0 , rated,
836+ ...data , title, rule, beginAt, endAt, pids, attend : 0 , rated, lockedList : pids . reduce ( ( acc , curr ) => ( { ... acc , [ curr ] : [ ] } ) , { } ) ,
745837 } ) ;
746838 await bus . parallel ( 'contest/add' , data , res ) ;
747839 return res ;
@@ -906,8 +998,8 @@ export function canViewHiddenScoreboard(this: { user: User }, tdoc: Tdoc) {
906998 return this . user . hasPerm ( PERM . PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD ) ;
907999}
9081000
909- export function canShowRecord ( this : { user : User } , tdoc : Tdoc , allowPermOverride = true ) {
910- if ( RULES [ tdoc . rule ] . showRecord ( tdoc , new Date ( ) ) ) return true ;
1001+ export function canShowRecord ( this : { user : User } , tdoc : Tdoc , allowPermOverride = true , pdoc ?: ProblemDoc ) {
1002+ if ( RULES [ tdoc . rule ] . showRecord ( tdoc , new Date ( ) , this . user , pdoc ) ) return true ;
9111003 if ( allowPermOverride && canViewHiddenScoreboard . call ( this , tdoc ) ) return true ;
9121004 return false ;
9131005}
@@ -984,6 +1076,18 @@ export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
9841076 ? 'Live...'
9851077 : 'Done' ) ;
9861078
1079+ export async function getLockedList ( domainId : string , tid : ObjectId ) {
1080+ const tdoc = await document . get ( domainId , document . TYPE_CONTEST , tid ) ;
1081+ if ( tdoc . rule !== 'cf' ) return null ;
1082+ return tdoc . lockedList ;
1083+ }
1084+
1085+ export async function updateLockedList ( domainId : string , tid : ObjectId , $lockList : any ) {
1086+ const tdoc = await document . get ( domainId , document . TYPE_CONTEST , tid ) ;
1087+ tdoc . lockedList = $lockList ;
1088+ edit ( domainId , tid , tdoc ) ;
1089+ }
1090+
9871091global . Hydro . model . contest = {
9881092 RULES ,
9891093 buildContestRule,
@@ -1026,4 +1130,6 @@ global.Hydro.model.contest = {
10261130 isExtended,
10271131 applyProjection,
10281132 statusText,
1133+ getLockedList,
1134+ updateLockedList,
10291135} ;
0 commit comments