@@ -53,6 +53,7 @@ type GameData struct {
5353// 텔레메트리 구조체
5454// ========================
5555
56+ // === v1 통계 ===
5657type TelemetryStats struct {
5758 TotalCycles int `json:"total_cycles"`
5859 SuccessfulCycles int `json:"successful_cycles"`
@@ -77,6 +78,43 @@ type TelemetryStats struct {
7778 FarmingAttempts int `json:"farming_attempts"`
7879 HiddenFound int `json:"hidden_found"`
7980 TrashFound int `json:"trash_found"`
81+
82+ // === v2 새로 추가 ===
83+ SwordBattleStats map [string ]* SwordBattleStat `json:"sword_battle_stats,omitempty"`
84+ HiddenFoundByName map [string ]int `json:"hidden_found_by_name,omitempty"`
85+ UpsetStatsByDiff map [int ]* UpsetStat `json:"upset_stats_by_diff,omitempty"`
86+ SwordSaleStats map [string ]* SwordSaleStat `json:"sword_sale_stats,omitempty"`
87+ ItemFarmingStats map [string ]* ItemFarmingStat `json:"item_farming_stats,omitempty"`
88+ }
89+
90+ // === v2 구조체들 ===
91+
92+ // SwordBattleStat 검 종류별 배틀 통계
93+ type SwordBattleStat struct {
94+ BattleCount int `json:"battle_count"`
95+ BattleWins int `json:"battle_wins"`
96+ UpsetAttempts int `json:"upset_attempts"`
97+ UpsetWins int `json:"upset_wins"`
98+ }
99+
100+ // UpsetStat 레벨차별 역배 통계
101+ type UpsetStat struct {
102+ Attempts int `json:"attempts"`
103+ Wins int `json:"wins"`
104+ GoldEarned int `json:"gold_earned"`
105+ }
106+
107+ // SwordSaleStat 검 종류별 판매 통계
108+ type SwordSaleStat struct {
109+ TotalPrice int `json:"total_price"`
110+ Count int `json:"count"`
111+ }
112+
113+ // ItemFarmingStat 아이템별 파밍 통계
114+ type ItemFarmingStat struct {
115+ TotalCount int `json:"total_count"`
116+ HiddenCount int `json:"hidden_count"`
117+ NormalCount int `json:"normal_count"`
80118}
81119
82120type TelemetryPayload struct {
@@ -108,10 +146,22 @@ type StatsStore struct {
108146 hiddenFound int
109147 salesCount int
110148 salesTotalGold int
149+
150+ // === v2 통계 ===
151+ swordBattleStats map [string ]* SwordBattleStat
152+ hiddenFoundByName map [string ]int
153+ upsetStatsByDiff map [int ]* UpsetStat
154+ swordSaleStats map [string ]* SwordSaleStat
155+ itemFarmingStats map [string ]* ItemFarmingStat
111156}
112157
113158var stats = & StatsStore {
114- enhanceByLevel : make (map [int ]int ),
159+ enhanceByLevel : make (map [int ]int ),
160+ swordBattleStats : make (map [string ]* SwordBattleStat ),
161+ hiddenFoundByName : make (map [string ]int ),
162+ upsetStatsByDiff : make (map [int ]* UpsetStat ),
163+ swordSaleStats : make (map [string ]* SwordSaleStat ),
164+ itemFarmingStats : make (map [string ]* ItemFarmingStat ),
115165}
116166
117167// ========================
@@ -213,6 +263,7 @@ func handleTelemetry(w http.ResponseWriter, r *http.Request) {
213263
214264 // 통계 업데이트
215265 stats .mu .Lock ()
266+ // v1 통계
216267 stats .enhanceAttempts += payload .Stats .EnhanceAttempts
217268 stats .enhanceSuccess += payload .Stats .EnhanceSuccess
218269 stats .enhanceFail += payload .Stats .EnhanceFail
@@ -229,6 +280,54 @@ func handleTelemetry(w http.ResponseWriter, r *http.Request) {
229280 stats .hiddenFound += payload .Stats .HiddenFound
230281 stats .salesCount += payload .Stats .SalesCount
231282 stats .salesTotalGold += payload .Stats .SalesTotalGold
283+
284+ // v2 통계 (schema_version >= 2)
285+ if payload .SchemaVersion >= 2 {
286+ // 검 종류별 배틀 통계
287+ for name , stat := range payload .Stats .SwordBattleStats {
288+ if stats .swordBattleStats [name ] == nil {
289+ stats .swordBattleStats [name ] = & SwordBattleStat {}
290+ }
291+ stats .swordBattleStats [name ].BattleCount += stat .BattleCount
292+ stats .swordBattleStats [name ].BattleWins += stat .BattleWins
293+ stats .swordBattleStats [name ].UpsetAttempts += stat .UpsetAttempts
294+ stats .swordBattleStats [name ].UpsetWins += stat .UpsetWins
295+ }
296+
297+ // 히든 이름별 통계
298+ for name , cnt := range payload .Stats .HiddenFoundByName {
299+ stats .hiddenFoundByName [name ] += cnt
300+ }
301+
302+ // 레벨차별 역배 통계
303+ for diff , stat := range payload .Stats .UpsetStatsByDiff {
304+ if stats .upsetStatsByDiff [diff ] == nil {
305+ stats .upsetStatsByDiff [diff ] = & UpsetStat {}
306+ }
307+ stats .upsetStatsByDiff [diff ].Attempts += stat .Attempts
308+ stats .upsetStatsByDiff [diff ].Wins += stat .Wins
309+ stats .upsetStatsByDiff [diff ].GoldEarned += stat .GoldEarned
310+ }
311+
312+ // 검 판매 통계
313+ for key , stat := range payload .Stats .SwordSaleStats {
314+ if stats .swordSaleStats [key ] == nil {
315+ stats .swordSaleStats [key ] = & SwordSaleStat {}
316+ }
317+ stats .swordSaleStats [key ].TotalPrice += stat .TotalPrice
318+ stats .swordSaleStats [key ].Count += stat .Count
319+ }
320+
321+ // 아이템 파밍 통계
322+ for name , stat := range payload .Stats .ItemFarmingStats {
323+ if stats .itemFarmingStats [name ] == nil {
324+ stats .itemFarmingStats [name ] = & ItemFarmingStat {}
325+ }
326+ stats .itemFarmingStats [name ].TotalCount += stat .TotalCount
327+ stats .itemFarmingStats [name ].HiddenCount += stat .HiddenCount
328+ stats .itemFarmingStats [name ].NormalCount += stat .NormalCount
329+ }
330+ }
232331 stats .mu .Unlock ()
233332
234333 log .Printf ("[텔레메트리] 세션=%s 버전=%s OS=%s" , payload .SessionID [:8 ], payload .AppVersion , payload .OSType )
@@ -317,6 +416,168 @@ func handleHealth(w http.ResponseWriter, r *http.Request) {
317416 })
318417}
319418
419+ // === v2 API 엔드포인트 ===
420+
421+ // 검 종류별 승률 랭킹
422+ func handleSwordStats (w http.ResponseWriter , r * http.Request ) {
423+ w .Header ().Set ("Content-Type" , "application/json" )
424+ w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
425+
426+ stats .mu .RLock ()
427+ defer stats .mu .RUnlock ()
428+
429+ type SwordEntry struct {
430+ Name string `json:"name"`
431+ BattleCount int `json:"battle_count"`
432+ WinRate float64 `json:"win_rate"`
433+ UpsetWinRate float64 `json:"upset_win_rate"`
434+ }
435+
436+ var swords []SwordEntry
437+ for name , stat := range stats .swordBattleStats {
438+ winRate := 0.0
439+ upsetWinRate := 0.0
440+ if stat .BattleCount > 0 {
441+ winRate = float64 (stat .BattleWins ) / float64 (stat .BattleCount ) * 100
442+ }
443+ if stat .UpsetAttempts > 0 {
444+ upsetWinRate = float64 (stat .UpsetWins ) / float64 (stat .UpsetAttempts ) * 100
445+ }
446+ swords = append (swords , SwordEntry {
447+ Name : name ,
448+ BattleCount : stat .BattleCount ,
449+ WinRate : winRate ,
450+ UpsetWinRate : upsetWinRate ,
451+ })
452+ }
453+
454+ json .NewEncoder (w ).Encode (map [string ]interface {}{
455+ "swords" : swords ,
456+ })
457+ }
458+
459+ // 히든 검 출현 확률
460+ func handleHiddenStats (w http.ResponseWriter , r * http.Request ) {
461+ w .Header ().Set ("Content-Type" , "application/json" )
462+ w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
463+
464+ stats .mu .RLock ()
465+ defer stats .mu .RUnlock ()
466+
467+ type HiddenEntry struct {
468+ Name string `json:"name"`
469+ Count int `json:"count"`
470+ Rate float64 `json:"rate"`
471+ }
472+
473+ var hidden []HiddenEntry
474+ for name , cnt := range stats .hiddenFoundByName {
475+ rate := 0.0
476+ if stats .farmingAttempts > 0 {
477+ rate = float64 (cnt ) / float64 (stats .farmingAttempts ) * 100
478+ }
479+ hidden = append (hidden , HiddenEntry {
480+ Name : name ,
481+ Count : cnt ,
482+ Rate : rate ,
483+ })
484+ }
485+
486+ json .NewEncoder (w ).Encode (map [string ]interface {}{
487+ "total_farming" : stats .farmingAttempts ,
488+ "hidden" : hidden ,
489+ })
490+ }
491+
492+ // 역배 실측 승률
493+ func handleUpsetStats (w http.ResponseWriter , r * http.Request ) {
494+ w .Header ().Set ("Content-Type" , "application/json" )
495+ w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
496+
497+ stats .mu .RLock ()
498+ defer stats .mu .RUnlock ()
499+
500+ // 이론 승률
501+ theoryRates := map [int ]float64 {
502+ 1 : 35.0 ,
503+ 2 : 20.0 ,
504+ 3 : 10.0 ,
505+ }
506+
507+ type DiffStat struct {
508+ Attempts int `json:"attempts"`
509+ Wins int `json:"wins"`
510+ WinRate float64 `json:"win_rate"`
511+ Theory float64 `json:"theory"`
512+ GoldEarned int `json:"gold_earned"`
513+ }
514+
515+ byDiff := make (map [string ]DiffStat )
516+ for diff := 1 ; diff <= 3 ; diff ++ {
517+ stat := stats .upsetStatsByDiff [diff ]
518+ winRate := 0.0
519+ attempts := 0
520+ wins := 0
521+ gold := 0
522+ if stat != nil {
523+ attempts = stat .Attempts
524+ wins = stat .Wins
525+ gold = stat .GoldEarned
526+ if attempts > 0 {
527+ winRate = float64 (wins ) / float64 (attempts ) * 100
528+ }
529+ }
530+ byDiff [fmt .Sprintf ("%d" , diff )] = DiffStat {
531+ Attempts : attempts ,
532+ Wins : wins ,
533+ WinRate : winRate ,
534+ Theory : theoryRates [diff ],
535+ GoldEarned : gold ,
536+ }
537+ }
538+
539+ json .NewEncoder (w ).Encode (map [string ]interface {}{
540+ "by_level_diff" : byDiff ,
541+ })
542+ }
543+
544+ // 아이템 파밍 통계
545+ func handleItemStats (w http.ResponseWriter , r * http.Request ) {
546+ w .Header ().Set ("Content-Type" , "application/json" )
547+ w .Header ().Set ("Access-Control-Allow-Origin" , "*" )
548+
549+ stats .mu .RLock ()
550+ defer stats .mu .RUnlock ()
551+
552+ type ItemEntry struct {
553+ Name string `json:"name"`
554+ TotalCount int `json:"total_count"`
555+ HiddenCount int `json:"hidden_count"`
556+ NormalCount int `json:"normal_count"`
557+ HiddenRate float64 `json:"hidden_rate"`
558+ }
559+
560+ var items []ItemEntry
561+ for name , stat := range stats .itemFarmingStats {
562+ hiddenRate := 0.0
563+ if stat .TotalCount > 0 {
564+ hiddenRate = float64 (stat .HiddenCount ) / float64 (stat .TotalCount ) * 100
565+ }
566+ items = append (items , ItemEntry {
567+ Name : name ,
568+ TotalCount : stat .TotalCount ,
569+ HiddenCount : stat .HiddenCount ,
570+ NormalCount : stat .NormalCount ,
571+ HiddenRate : hiddenRate ,
572+ })
573+ }
574+
575+ json .NewEncoder (w ).Encode (map [string ]interface {}{
576+ "total_farming" : stats .farmingAttempts ,
577+ "items" : items ,
578+ })
579+ }
580+
320581func generateSignature (sessionID , period string ) string {
321582 h := sha256 .Sum256 ([]byte (sessionID + period + appSecret ))
322583 return hex .EncodeToString (h [:])[:16 ]
@@ -334,11 +595,20 @@ func main() {
334595 http .HandleFunc ("/api/game-data" , handleGameData )
335596 http .HandleFunc ("/api/telemetry" , handleTelemetry )
336597 http .HandleFunc ("/api/stats/detailed" , handleStatsDetailed )
598+ // v2 엔드포인트
599+ http .HandleFunc ("/api/stats/swords" , handleSwordStats )
600+ http .HandleFunc ("/api/stats/hidden" , handleHiddenStats )
601+ http .HandleFunc ("/api/stats/upset" , handleUpsetStats )
602+ http .HandleFunc ("/api/stats/items" , handleItemStats )
337603
338604 log .Printf ("🚀 Sword API 서버 시작 (포트: %s)" , port )
339605 log .Printf (" /api/game-data - 게임 데이터 조회" )
340606 log .Printf (" /api/telemetry - 텔레메트리 수신" )
341607 log .Printf (" /api/stats/detailed - 커뮤니티 통계" )
608+ log .Printf (" /api/stats/swords - 검 종류별 승률 (v2)" )
609+ log .Printf (" /api/stats/hidden - 히든 검 출현 확률 (v2)" )
610+ log .Printf (" /api/stats/upset - 역배 실측 승률 (v2)" )
611+ log .Printf (" /api/stats/items - 아이템 파밍 통계 (v2)" )
342612
343613 if err := http .ListenAndServe (":" + port , nil ); err != nil {
344614 log .Fatal (err )
0 commit comments