@@ -17,8 +17,10 @@ package v1
1717import (
1818 "fmt"
1919 "log/slog"
20+ "math"
2021 "time"
2122
23+ "github.com/Knetic/govaluate"
2224 "github.com/labstack/echo/v4"
2325 "rina.icu/hoshino/internal/util"
2426 "rina.icu/hoshino/server/context"
@@ -51,33 +53,32 @@ func anticheatCheck(_ *echo.Context, s *store.Store,
5153 flag , err := s .GetFlagByChallenge (flag , challenge )
5254 if err != nil {
5355 slog .Error (fmt .Sprintf ("Failed to get flag: %s " , err .Error ()))
54- return true , CheatReasonNone
55- }
56+ } else {
57+ if flag .Team .ID != team .ID {
58+ event := store.GameEvent {
59+ Content : fmt .Sprintf ("Team `%s` shared the flag `%s` with team `%s`" , team .Name , flag .Flag , flag .Team .Name ),
60+ Game : challenge .Game ,
61+ Challenge : challenge ,
62+ RelatedTeams : []* store.Team {team , flag .Team },
63+ Visibility : true ,
64+ Type : store .GameEventTypeCheatDetected ,
65+ }
5666
57- if flag .Team .ID != team .ID {
58- event := store.GameEvent {
59- Content : fmt .Sprintf ("Team `%s` shared the flag `%s` with team `%s`" , team .Name , flag .Flag , flag .Team .Name ),
60- Game : challenge .Game ,
61- Challenge : challenge ,
62- RelatedTeams : []* store.Team {team , flag .Team },
63- Visibility : true ,
64- Type : store .GameEventTypeCheatDetected ,
65- }
67+ flag .State = store .FlagCheated
68+ s .UpdateFlag (flag )
6669
67- flag .State = store .FlagCheated
68- s .UpdateFlag (flag )
70+ if challenge .Game .AutoBan {
71+ // ban the team instantly
72+ team .Banned = true
73+ s .UpdateTeam (team )
74+ } else {
75+ // log the cheat silently
76+ event .Visibility = false
77+ }
6978
70- if challenge .Game .AutoBan {
71- // ban the team instantly
72- team .Banned = true
73- s .UpdateTeam (team )
74- } else {
75- // log the cheat silently
76- event .Visibility = false
79+ s .CreateGameEvent (& event )
80+ return false , CheatReasonSharingFlag
7781 }
78-
79- s .CreateGameEvent (& event )
80- return false , CheatReasonSharingFlag
8182 }
8283 } else {
8384 attachments , err := s .GetAttachmentsByChallenge (challenge )
@@ -158,6 +159,71 @@ func anticheatCheck(_ *echo.Context, s *store.Store,
158159 return true , CheatReasonNone
159160}
160161
162+ func updateScore (s * store.Store , challenge * store.Challenge ) {
163+ // update the score of the challenge
164+ solvedFlags , err := s .GetSolvedFlagsByChallenge (challenge )
165+ if err != nil {
166+ slog .Error (fmt .Sprintf ("Failed to get solved flags: %s " , err .Error ()))
167+ return
168+ }
169+
170+ solvedRate := float64 (len (solvedFlags )) / float64 (len (challenge .Game .GetTeams (s )))
171+ lossRate := float64 (len (solvedFlags )- 1 ) / float64 (len (challenge .Game .GetTeams (s )))
172+ exponentialScore := float64 (challenge .Score ) * math .Exp ((float64 (challenge .Difficulty )- 2 )* lossRate )
173+ parameters := make (map [string ]interface {})
174+ parameters ["original_score" ] = challenge .Score
175+ parameters ["solved_count" ] = len (solvedFlags )
176+ parameters ["team_count" ] = len (challenge .Game .GetTeams (s ))
177+ parameters ["difficulty" ] = challenge .Difficulty
178+ parameters ["loss_rate" ] = lossRate
179+ parameters ["solved_rate" ] = solvedRate
180+ parameters ["unsolved_rate" ] = 1 - solvedRate
181+ parameters ["linear_score" ] = float64 (challenge .Score ) * (1 - lossRate )
182+ parameters ["exponential_score" ] = exponentialScore
183+
184+ slog .Info (fmt .Sprintf ("Original score: %d, Solved count: %d, Team count: %d, Difficulty: %f, Loss rate: %f, Solved rate: %f, Unsolved rate: %f, Linear score: %f, Exponential score: %f" , challenge .Score , len (solvedFlags ), len (challenge .Game .GetTeams (s )), challenge .Difficulty , lossRate , solvedRate , 1 - solvedRate , float64 (challenge .Score )* (1 - lossRate ), exponentialScore ))
185+
186+ functions := map [string ]govaluate.ExpressionFunction {
187+ "max" : func (args ... interface {}) (interface {}, error ) {
188+ return math .Max (args [0 ].(float64 ), args [1 ].(float64 )), nil
189+ },
190+ "min" : func (args ... interface {}) (interface {}, error ) {
191+ return math .Min (args [0 ].(float64 ), args [1 ].(float64 )), nil
192+ },
193+ "exponential_score_with_top3_bonus" : func (args ... interface {}) (interface {}, error ) {
194+ order := args [0 ].(uint32 )
195+ rate1 := args [1 ].(float64 )
196+ rate2 := args [2 ].(float64 )
197+ rate3 := args [3 ].(float64 )
198+ if order == 1 {
199+ return exponentialScore * rate1 , nil
200+ } else if order == 2 {
201+ return exponentialScore * rate2 , nil
202+ } else if order == 3 {
203+ return exponentialScore * rate3 , nil
204+ }
205+ return exponentialScore , nil
206+ },
207+ }
208+
209+ expression , err := govaluate .NewEvaluableExpressionWithFunctions (challenge .ScoreFormula , functions )
210+
211+ actualOrder := 1
212+ for _ , flag := range solvedFlags {
213+ if flag .State == 2 && challenge .Game .AutoBan {
214+ continue
215+ }
216+
217+ parameters ["order" ] = actualOrder
218+
219+ result , _ := expression .Evaluate (parameters )
220+ score := math .Round (result .(float64 ))
221+ flag .Score = int (score )
222+ s .UpdateFlag (flag )
223+ actualOrder ++
224+ }
225+ }
226+
161227func SubmitFlag (c echo.Context ) error {
162228 ctx := c .(* context.CustomContext )
163229
@@ -224,6 +290,7 @@ func SubmitFlag(c echo.Context) error {
224290 ok , _ := anticheatCheck (& c , ctx .Store , payload .Flag , team , challenge )
225291 if ! ok {
226292 storedFlag .State = store .FlagCheated
293+ storedFlag .SolvedAt = time .Now ().UnixMilli ()
227294 ctx .Store .UpdateFlag (storedFlag )
228295
229296 if challenge .Game .AutoBan {
@@ -247,5 +314,8 @@ func SubmitFlag(c echo.Context) error {
247314 ctx .Store .UpdateFlag (storedFlag )
248315 return Failed (& c , "Flag is incorrect" )
249316 }
317+
318+ go updateScore (ctx .Store , challenge )
319+
250320 return OK (& c )
251321}
0 commit comments