@@ -16,7 +16,7 @@ import type { Handler } from '../service/server';
16
16
import { Optional } from '../typeutils' ;
17
17
import { PERM , STATUS , STATUS_SHORT_TEXTS } from './builtin' ;
18
18
import * as document from './document' ;
19
- import problem from './problem' ;
19
+ import problem , { ProblemDoc } from './problem' ;
20
20
import user , { User } from './user' ;
21
21
22
22
interface AcmJournal {
@@ -569,6 +569,98 @@ const ledo = buildContestRule({
569
569
} ,
570
570
} , oi ) ;
571
571
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
+
572
664
const homework = buildContestRule ( {
573
665
TEXT : 'Assignment' ,
574
666
hidden : true ,
@@ -719,7 +811,7 @@ const homework = buildContestRule({
719
811
} ) ;
720
812
721
813
export const RULES : ContestRules = {
722
- acm, oi, homework, ioi, ledo, strictioi,
814
+ acm, oi, homework, ioi, ledo, strictioi, cf ,
723
815
} ;
724
816
725
817
const collBalloon = db . collection ( 'contest.balloon' ) ;
@@ -741,7 +833,7 @@ export async function add(
741
833
RULES [ rule ] . check ( data ) ;
742
834
await bus . parallel ( 'contest/before-add' , data ) ;
743
835
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 ] : [ ] } ) , { } ) ,
745
837
} ) ;
746
838
await bus . parallel ( 'contest/add' , data , res ) ;
747
839
return res ;
@@ -906,8 +998,8 @@ export function canViewHiddenScoreboard(this: { user: User }, tdoc: Tdoc) {
906
998
return this . user . hasPerm ( PERM . PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD ) ;
907
999
}
908
1000
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 ;
911
1003
if ( allowPermOverride && canViewHiddenScoreboard . call ( this , tdoc ) ) return true ;
912
1004
return false ;
913
1005
}
@@ -984,6 +1076,18 @@ export const statusText = (tdoc: Tdoc, tsdoc?: any) => (
984
1076
? 'Live...'
985
1077
: 'Done' ) ;
986
1078
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
+
987
1091
global . Hydro . model . contest = {
988
1092
RULES ,
989
1093
buildContestRule,
@@ -1026,4 +1130,6 @@ global.Hydro.model.contest = {
1026
1130
isExtended,
1027
1131
applyProjection,
1028
1132
statusText,
1133
+ getLockedList,
1134
+ updateLockedList,
1029
1135
} ;
0 commit comments