From be27a4c16c95135f9ceecbde44556c632bd41472 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 19 Dec 2024 18:16:34 +0900 Subject: [PATCH 1/6] Validate AvatarAddressAndScore except --- ArenaService.Tests/ArenaService.Tests.csproj | 27 ++++++++++++++ .../AvatarAddressAndScoreTest.cs | 36 +++++++++++++++++++ ArenaService/ArenaService.sln | 6 ++++ 3 files changed, 69 insertions(+) create mode 100644 ArenaService.Tests/ArenaService.Tests.csproj create mode 100644 ArenaService.Tests/AvatarAddressAndScoreTest.cs diff --git a/ArenaService.Tests/ArenaService.Tests.csproj b/ArenaService.Tests/ArenaService.Tests.csproj new file mode 100644 index 0000000..befd5a4 --- /dev/null +++ b/ArenaService.Tests/ArenaService.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/ArenaService.Tests/AvatarAddressAndScoreTest.cs b/ArenaService.Tests/AvatarAddressAndScoreTest.cs new file mode 100644 index 0000000..f049474 --- /dev/null +++ b/ArenaService.Tests/AvatarAddressAndScoreTest.cs @@ -0,0 +1,36 @@ +using Libplanet.Crypto; + +namespace ArenaService.Tests; + +public class AvatarAddressAndScoreTest +{ + [Fact] + public void Except_List() + { + var address = new PrivateKey().Address; + var address2 = new PrivateKey().Address; + var addressAndScore = new AvatarAddressAndScore(address, 100); + var addressAndScore2 = new AvatarAddressAndScore(address2, 100); + var updated = new AvatarAddressAndScore(address, 200); + + // not equal because score is different + Assert.NotEqual(addressAndScore, updated); + // not equal because address is different + Assert.NotEqual(addressAndScore, addressAndScore2); + + var prev = new List + { + addressAndScore, + addressAndScore2, + }; + var next = new List + { + updated, + addressAndScore2 + }; + var excepted = Assert.Single(next.Except(prev)); + Assert.Equal(updated.AvatarAddr, excepted.AvatarAddr); + Assert.Equal(updated.Score, excepted.Score); + Assert.Equal(updated, excepted); + } +} diff --git a/ArenaService/ArenaService.sln b/ArenaService/ArenaService.sln index 04b3b1e..ee8a88b 100644 --- a/ArenaService/ArenaService.sln +++ b/ArenaService/ArenaService.sln @@ -4,6 +4,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArenaService", "ArenaServic EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NineChronicles.RPC.Shared", "..\NineChronicles.RPC.Shared\NineChronicles.RPC.Shared\NineChronicles.RPC.Shared.csproj", "{C936C300-10D8-4A70-8815-67F16CEC6A0A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArenaService.Tests", "..\ArenaService.Tests\ArenaService.Tests.csproj", "{B8F66D71-6DAB-4CF3-B5DA-E35FAAB393F9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,5 +20,9 @@ Global {C936C300-10D8-4A70-8815-67F16CEC6A0A}.Debug|Any CPU.Build.0 = Debug|Any CPU {C936C300-10D8-4A70-8815-67F16CEC6A0A}.Release|Any CPU.ActiveCfg = Release|Any CPU {C936C300-10D8-4A70-8815-67F16CEC6A0A}.Release|Any CPU.Build.0 = Release|Any CPU + {B8F66D71-6DAB-4CF3-B5DA-E35FAAB393F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8F66D71-6DAB-4CF3-B5DA-E35FAAB393F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8F66D71-6DAB-4CF3-B5DA-E35FAAB393F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8F66D71-6DAB-4CF3-B5DA-E35FAAB393F9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 86de7f226b34ef78eb21f4664cabcc59784d7f1b Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 19 Dec 2024 20:16:28 +0900 Subject: [PATCH 2/6] Extract AvatarAddrAndScoresWithRank --- ArenaService/ArenaService/ArenaWorker.cs | 77 +++++++++++++++++++++++- ArenaService/ArenaService/RpcClient.cs | 74 ----------------------- 2 files changed, 76 insertions(+), 75 deletions(-) diff --git a/ArenaService/ArenaService/ArenaWorker.cs b/ArenaService/ArenaService/ArenaWorker.cs index e42f2e5..f6f7d31 100644 --- a/ArenaService/ArenaService/ArenaWorker.cs +++ b/ArenaService/ArenaService/ArenaWorker.cs @@ -79,7 +79,7 @@ public async Task PrepareArenaParticipants() // 이전상태의 아바타 주소, 점수를 비교해서 추가되거나 점수가 변경된 대상만 찾음 var updatedAddressAndScores = avatarAddrAndScores.Except(prevAddrAndScores).ToList(); // 전체목록의 랭킹 순서 처리 - var avatarAddrAndScoresWithRank = _rpcClient.AvatarAddrAndScoresWithRank(avatarAddrAndScores); + var avatarAddrAndScoresWithRank = AvatarAddrAndScoresWithRank(avatarAddrAndScores); // 전체목록의 ArenaParticipant 업데이트 var result = await _rpcClient.GetArenaParticipants(tip, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants, cancellationToken); // 캐시 업데이트 @@ -89,4 +89,79 @@ public async Task PrepareArenaParticipants() sw.Stop(); _logger.LogInformation("[ArenaParticipantsWorker]Set Arena Cache[{CacheKey}]: {Elapsed}", cacheKey, sw.Elapsed); } + + + /// + /// Retrieves the avatar addresses and scores with ranks for a given list of avatar addresses, current round data, and world state. + /// + /// Ths list of avatar address and score tuples. + /// The list of avatar addresses, scores, and ranks. + public static List AvatarAddrAndScoresWithRank(List avatarAddrAndScores) + { + List orderedTuples = avatarAddrAndScores + .OrderByDescending(tuple => tuple.Score) + .ThenBy(tuple => tuple.AvatarAddr) + .Select(tuple => new ArenaScoreAndRank(tuple.AvatarAddr, tuple.Score, 0)) + .ToList(); + int? currentScore = null; + var currentRank = 1; + var avatarAddrAndScoresWithRank = new List(); + var trunk = new List(); + for (var i = 0; i < orderedTuples.Count; i++) + { + var tuple = orderedTuples[i]; + if (!currentScore.HasValue) + { + currentScore = tuple.Score; + trunk.Add(tuple); + continue; + } + + if (currentScore.Value == tuple.Score) + { + trunk.Add(tuple); + currentRank++; + if (i < orderedTuples.Count - 1) + { + continue; + } + + foreach (var tupleInTrunk in trunk) + { + avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( + tupleInTrunk.AvatarAddr, + tupleInTrunk.Score, + currentRank)); + } + + trunk.Clear(); + + continue; + } + + foreach (var tupleInTrunk in trunk) + { + avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( + tupleInTrunk.AvatarAddr, + tupleInTrunk.Score, + currentRank)); + } + + trunk.Clear(); + if (i < orderedTuples.Count - 1) + { + trunk.Add(tuple); + currentScore = tuple.Score; + currentRank++; + continue; + } + + avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( + tuple.AvatarAddr, + tuple.Score, + currentRank + 1)); + } + + return avatarAddrAndScoresWithRank; + } } diff --git a/ArenaService/ArenaService/RpcClient.cs b/ArenaService/ArenaService/RpcClient.cs index f144c07..6abf44a 100644 --- a/ArenaService/ArenaService/RpcClient.cs +++ b/ArenaService/ArenaService/RpcClient.cs @@ -250,80 +250,6 @@ public void OnPreloadEnd() return participants; } - /// - /// Retrieves the avatar addresses and scores with ranks for a given list of avatar addresses, current round data, and world state. - /// - /// Ths list of avatar address and score tuples. - /// The list of avatar addresses, scores, and ranks. - public List AvatarAddrAndScoresWithRank(List avatarAddrAndScores) - { - List orderedTuples = avatarAddrAndScores - .OrderByDescending(tuple => tuple.Score) - .ThenBy(tuple => tuple.AvatarAddr) - .Select(tuple => new ArenaScoreAndRank(tuple.AvatarAddr, tuple.Score, 0)) - .ToList(); - int? currentScore = null; - var currentRank = 1; - var avatarAddrAndScoresWithRank = new List(); - var trunk = new List(); - for (var i = 0; i < orderedTuples.Count; i++) - { - var tuple = orderedTuples[i]; - if (!currentScore.HasValue) - { - currentScore = tuple.Score; - trunk.Add(tuple); - continue; - } - - if (currentScore.Value == tuple.Score) - { - trunk.Add(tuple); - currentRank++; - if (i < orderedTuples.Count - 1) - { - continue; - } - - foreach (var tupleInTrunk in trunk) - { - avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( - tupleInTrunk.AvatarAddr, - tupleInTrunk.Score, - currentRank)); - } - - trunk.Clear(); - - continue; - } - - foreach (var tupleInTrunk in trunk) - { - avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( - tupleInTrunk.AvatarAddr, - tupleInTrunk.Score, - currentRank)); - } - - trunk.Clear(); - if (i < orderedTuples.Count - 1) - { - trunk.Add(tuple); - currentScore = tuple.Score; - currentRank++; - continue; - } - - avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( - tuple.AvatarAddr, - tuple.Score, - currentRank + 1)); - } - - return avatarAddrAndScoresWithRank; - } - public async Task> GetAvatarAddrAndScores(Block block, List
avatarAddrList, ArenaSheet.RoundData currentRoundData, CancellationToken cancellationToken) { From e744cc2a3a544499bb0baf98894836e6022c6242 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 19 Dec 2024 22:47:32 +0900 Subject: [PATCH 3/6] Refactor AvatarAddrAndScoresWithRank --- ArenaService.Tests/ArenaWorkerTest.cs | 130 +++++++++++++++++++++++ ArenaService/ArenaService/ArenaWorker.cs | 79 +++++--------- 2 files changed, 159 insertions(+), 50 deletions(-) create mode 100644 ArenaService.Tests/ArenaWorkerTest.cs diff --git a/ArenaService.Tests/ArenaWorkerTest.cs b/ArenaService.Tests/ArenaWorkerTest.cs new file mode 100644 index 0000000..7fd2c0e --- /dev/null +++ b/ArenaService.Tests/ArenaWorkerTest.cs @@ -0,0 +1,130 @@ +using System.Text.Json; +using Libplanet.Crypto; + +namespace ArenaService.Tests; + +public class ArenaWorkerTest +{ + [Fact] + public void AvatarAddrAndScoresWithRank_Single() + { + var avatarAddress = new Address(); + var avatarAddressAndScore = new AvatarAddressAndScore(avatarAddress, 1000); + var result = Assert.Single(ArenaParticipantsWorker.AvatarAddrAndScoresWithRank([avatarAddressAndScore])); + Assert.Equal(avatarAddress, result.AvatarAddr); + Assert.Equal(1000, result.Score); + Assert.Equal(1, result.Rank); + } + + [Fact] + public void AvatarAddrAndScoresWithRank() + { + var address = new PrivateKey().Address; + var address2 = new PrivateKey().Address; + var addressAndScore = new AvatarAddressAndScore(address, 100); + var addressAndScore2 = new AvatarAddressAndScore(address2, 100); + var result = ArenaParticipantsWorker.AvatarAddrAndScoresWithRank([addressAndScore, addressAndScore2]); + Assert.Equal(2, result.Count); + Assert.All(result, rank => Assert.Equal(2, rank.Rank)); + } + + [Fact] + public void AvatarAddrAndScoresWithRank_Result_Not_Chnaged() + { + const string cached = "[{\"AvatarAddr\":\"9010F7098De49Ee5f0d96610f3ac7f4d71A1fF07\",\"Score\":1160},{\"AvatarAddr\":\"aB44635462880666dAa7f2Be5a21c71C1590fF2B\",\"Score\":1000},{\"AvatarAddr\":\"BD151Dd4B09033AA24e6fC2c81617C8C4c5E596a\",\"Score\":1000},{\"AvatarAddr\":\"BDef4AA00d5CA4401de8e57AaBd3e366Af2951Bf\",\"Score\":1117},{\"AvatarAddr\":\"9dD9D5bB536287c41B5De0b4aA18e1368909228e\",\"Score\":1158},{\"AvatarAddr\":\"b3FC2E1cf9b21B2ff92bB5EB63cCf8F3888AA010\",\"Score\":1159},{\"AvatarAddr\":\"d34d67D27A2031c516F66745C30cA94FC72C85d4\",\"Score\":1136},{\"AvatarAddr\":\"78AeAe86d7309b0dC8D2407F5069a74ab14047dD\",\"Score\":1078},{\"AvatarAddr\":\"00b6a82fd0fBd4A7702519872540b81615966ad0\",\"Score\":1000},{\"AvatarAddr\":\"e1eA4D0a74d0B9F1FD7D44Bf7067d2E31F1c65ef\",\"Score\":1000},{\"AvatarAddr\":\"8A472FD3E117090a03dc7F769AB8e93187ea984f\",\"Score\":1000},{\"AvatarAddr\":\"335859aB48dF250Fb325F8bB7f337Be82B237b12\",\"Score\":1000},{\"AvatarAddr\":\"5cBDD7F69777d4597A9984F355316174Bd50A5A8\",\"Score\":1155},{\"AvatarAddr\":\"D5C430FcFD339f43f6e956CE07A8864E198799Dc\",\"Score\":1000},{\"AvatarAddr\":\"54B925472d839dD8A5Dee5353eb9DF5d69FB52C3\",\"Score\":1000},{\"AvatarAddr\":\"F03A081B22B34856390cafd0de68c744E3Af0A73\",\"Score\":1000},{\"AvatarAddr\":\"AfC739561a0c451dcf1458CCb56c03b20dB7fC62\",\"Score\":1000},{\"AvatarAddr\":\"EFaAe7e51859970BA3F3463e5d30FF86524a9fAE\",\"Score\":1000},{\"AvatarAddr\":\"1F44910e253c4fA8948b5A6Be3f77c05387fFd82\",\"Score\":1000},{\"AvatarAddr\":\"fCBc96b15C1AAc0EBd1B45a7a2e7a91C2F974513\",\"Score\":1144},{\"AvatarAddr\":\"EECd13BE6aeb1Fc3565e9e7737486eaaEd988269\",\"Score\":1118},{\"AvatarAddr\":\"F96d67DC290FcE628D5e0362725E1f660FdCfEF4\",\"Score\":1000},{\"AvatarAddr\":\"020e8b48434404ceccbe7F3dA51416d18aeB0504\",\"Score\":1152},{\"AvatarAddr\":\"eF6Db2D629E52bb4571a433B9391a7442Ab0470F\",\"Score\":1000},{\"AvatarAddr\":\"A6a24761415979D014D54C8AF4C2F58bcBE2C062\",\"Score\":1000},{\"AvatarAddr\":\"11ea194A98e21451836Df55e87A8A845b2cB1644\",\"Score\":1000},{\"AvatarAddr\":\"fFF36e5C0E59A469Fd025799B526eF5D7e2fB3A9\",\"Score\":1000},{\"AvatarAddr\":\"f9CD29951B3AddcE2a926955Ef22A846fD8B1D06\",\"Score\":1155},{\"AvatarAddr\":\"49711D01a245a193e9Fc60d77cEd1Aaa6730FDfe\",\"Score\":1000},{\"AvatarAddr\":\"80CF03A383844CB6297b8b10d2187C15CE4e8Ed3\",\"Score\":1000},{\"AvatarAddr\":\"B326d255cE65137eaAA7494258ec020aA5CBBC01\",\"Score\":1154},{\"AvatarAddr\":\"06977724F6D9073Ab6E1b4D78A5996015B0A36B7\",\"Score\":1090},{\"AvatarAddr\":\"CcE64a5D08A5B9842fE0d4F631f18c2b464E8a8b\",\"Score\":1000},{\"AvatarAddr\":\"9322ED30337446673eEeD80d2Fa18949C850Ec9D\",\"Score\":1140},{\"AvatarAddr\":\"3067092b1674605BA3eB43DCbA613E975FfbEc8a\",\"Score\":1000},{\"AvatarAddr\":\"0F3F3d5f3D9bd92d1A53B68e5044924c82F750F3\",\"Score\":1127},{\"AvatarAddr\":\"19f4c13c840480b6f116A2ac680B2E8B3cbC00Ec\",\"Score\":1159},{\"AvatarAddr\":\"eb8c9E96C54A78326B0E0C7de226c744ab73Ad6b\",\"Score\":1000},{\"AvatarAddr\":\"790C3A657FA63abb92b9bEaBCE536cf710f7FfB3\",\"Score\":1000},{\"AvatarAddr\":\"83dbCf03091BDBE08Be241d6E62a7703baB25b6a\",\"Score\":1000},{\"AvatarAddr\":\"1865C6674F59C41d143daFdD50bd62FA2F620aBa\",\"Score\":1000},{\"AvatarAddr\":\"8445B0aA3403F463B5655d1D60e11D5717d33398\",\"Score\":1000},{\"AvatarAddr\":\"03477972E857Ea57d0aEA29964114C1BcFdcdf99\",\"Score\":1000},{\"AvatarAddr\":\"37A72032237350d9625e9cA69D8F109e46FF395F\",\"Score\":1000},{\"AvatarAddr\":\"d24E24A8D5aEe4885049Ce28f75f4376CABc83a9\",\"Score\":1000},{\"AvatarAddr\":\"0e986C8755458Eff322Aef6870fd3F0c2890476D\",\"Score\":1000},{\"AvatarAddr\":\"De3395E353495a7Bd068E646185184f26D61869d\",\"Score\":1000},{\"AvatarAddr\":\"CD9319Ec72dE74287EA0F97078191163d3A45d30\",\"Score\":1000},{\"AvatarAddr\":\"23b14beaf23863494E402D1E7F677F2905bf6bE8\",\"Score\":1000},{\"AvatarAddr\":\"cAD62651E8329309451b156234815E329e5a15C6\",\"Score\":1000},{\"AvatarAddr\":\"71fB666A8c8614a044cCE3Fc2B76dC1735a5B3be\",\"Score\":1000},{\"AvatarAddr\":\"357E44545D423C1E96E7299eB69FCf3246BF4D53\",\"Score\":1000},{\"AvatarAddr\":\"c208630c229183935DF0Fbbf3129C7f6d8DF7162\",\"Score\":1144},{\"AvatarAddr\":\"9e57c049Fe0c7Bf1e63a7e372Aa830E260c2e882\",\"Score\":1000},{\"AvatarAddr\":\"E7E81C65f926562EC104f47837c9419A90C6a422\",\"Score\":1000},{\"AvatarAddr\":\"D48ed5e73017A7942b67996bb7Fe4F1bb6bfB1f1\",\"Score\":1000},{\"AvatarAddr\":\"2DB02d98129DaAcBB9730E5147f3FE10505f8D65\",\"Score\":1000},{\"AvatarAddr\":\"96f3f92A97CB0D7a7B5BEBFb80259A0a9bFD54e6\",\"Score\":1160},{\"AvatarAddr\":\"6AC86123fDc9B50A1113117DA0037492a775227E\",\"Score\":1000},{\"AvatarAddr\":\"151ad3bFffb9c355Fd7ab07A1F11773d39182cD6\",\"Score\":1000},{\"AvatarAddr\":\"87fDD1a2115b8655164Cd3355c37845D0C173461\",\"Score\":1000},{\"AvatarAddr\":\"bc0928b1850D15848c4Af65bB948a7D0Aa310ffA\",\"Score\":1000},{\"AvatarAddr\":\"6d87A81051106e8484f562c046612E25Cc423C2A\",\"Score\":1000},{\"AvatarAddr\":\"a958adAA11c9Fbc8e955D212010b65c205Bd38AC\",\"Score\":1000},{\"AvatarAddr\":\"feC995C35a4CBdeB45844d032bD95608C47F344D\",\"Score\":1000},{\"AvatarAddr\":\"23ded23bf794eD93d7E37C40Ecc4271e9e604D65\",\"Score\":1000},{\"AvatarAddr\":\"5c925B7A9e756a1a26913A277A7CB757286a3726\",\"Score\":1000},{\"AvatarAddr\":\"42bdA026b812ff12B86fbDfEf0d06b0E86e21996\",\"Score\":1000},{\"AvatarAddr\":\"d4BD6031f8BE9878aA0465805E771ae99EBb60B3\",\"Score\":1000},{\"AvatarAddr\":\"1C5cE6F72BB72DE19443E4eED88521C1d19a5100\",\"Score\":1000},{\"AvatarAddr\":\"568b14B14ca95723C0d5E04D7d3F05bbFc5Fbbb1\",\"Score\":1000},{\"AvatarAddr\":\"bC17885A8C70F8d0b2C10275dD570c3fCC3740e3\",\"Score\":1000},{\"AvatarAddr\":\"9F4186F6cf7aC81b2c082d7D4FE5DD4E81F012e0\",\"Score\":1160},{\"AvatarAddr\":\"756FbF713C08f00DBa61a1CD0768bFBE33D13C58\",\"Score\":1110},{\"AvatarAddr\":\"1bc2bBA66A0bBdecf4450408EB0eEB9baB635634\",\"Score\":1152},{\"AvatarAddr\":\"147ec77029d9ba0DfFbDC2Bd6Ba6A8954ed9c6f4\",\"Score\":1000},{\"AvatarAddr\":\"199659Dc0f544DF957eb67eF371D0d5E8D5E3d33\",\"Score\":1000},{\"AvatarAddr\":\"dA6DCDF4f34eF51eA80Da56D9991F910a351CFFD\",\"Score\":1000},{\"AvatarAddr\":\"98ba3e263a6Fe529BCd40560E5d8007a5Ca5929A\",\"Score\":1000},{\"AvatarAddr\":\"97097b50Cd7C20Ff59e4a5F76aB77fc2DD54D09b\",\"Score\":1159},{\"AvatarAddr\":\"67DA6A06c7831887025b755c5200C48FFC90C2Eb\",\"Score\":1078},{\"AvatarAddr\":\"8287A62B8413AEE2cf8A61FA0d9513f9De7167c5\",\"Score\":1000},{\"AvatarAddr\":\"35c9e63e89eA231B644db8f60dF23C79CD4c64EA\",\"Score\":1000},{\"AvatarAddr\":\"0201e8ABd19eF8a1D8C5107A589C09F84Fe61a88\",\"Score\":1000},{\"AvatarAddr\":\"543f0cc3dC25937ac75D07Fe1D7691131bE0aFdA\",\"Score\":1055},{\"AvatarAddr\":\"441e8f292d0535C5F7564b53a4Dac0B7655d3F13\",\"Score\":1000},{\"AvatarAddr\":\"A222Ac97A3dac14E743dd9fEDF3f4a7E28e15011\",\"Score\":1000},{\"AvatarAddr\":\"0D3DFe2260884721197899484B33C8F1F57e9181\",\"Score\":1000},{\"AvatarAddr\":\"b1a280e31baB221bF830E68477fae9622a4911E7\",\"Score\":1143},{\"AvatarAddr\":\"88D6B254963aef19AFD46576B172D3FC35B42014\",\"Score\":1000},{\"AvatarAddr\":\"8EAC6dA320438a03E35450E28D6780F06Cb0e83e\",\"Score\":1000},{\"AvatarAddr\":\"7E310E51b5733a40a0cF25D737c810023A51f42E\",\"Score\":1158},{\"AvatarAddr\":\"7b66CC7Dd353CBaCC51db25D742Dd946b0c13424\",\"Score\":1146},{\"AvatarAddr\":\"73101C4ccFCc06980944DcF4485Ead471Ae6ED39\",\"Score\":1000},{\"AvatarAddr\":\"f62948B6675F5333c45616872EA188dca266E4E4\",\"Score\":1104},{\"AvatarAddr\":\"930D1c136FE5ca82251694B4518F510926399120\",\"Score\":1159},{\"AvatarAddr\":\"652a828c0f62633213cf4a506d2Ae9E32CA2c2a5\",\"Score\":1000},{\"AvatarAddr\":\"5b3fd07eb505747A28Dd5cadD5053e74e28d590A\",\"Score\":1158},{\"AvatarAddr\":\"9Ca89a90B728e6CB0554B5D2F3E1D398a2dc1D52\",\"Score\":1000},{\"AvatarAddr\":\"6C1f2470a498F7B635C56441369a6D105C94d12D\",\"Score\":1000},{\"AvatarAddr\":\"15610801f8acf718DC8cE14Ca07395d9f576aF8c\",\"Score\":1000},{\"AvatarAddr\":\"69e8177E69A792a7513dC19F50c4c95C1E998f51\",\"Score\":1152},{\"AvatarAddr\":\"754e532DCd195FB7C740E1De840a5A3Dd723ffD2\",\"Score\":1000},{\"AvatarAddr\":\"85A2d09AeCAa1120062F1C7c75ad254f020937e9\",\"Score\":1000},{\"AvatarAddr\":\"9416001777346A9cC52e49088568b4368626824b\",\"Score\":1100},{\"AvatarAddr\":\"F7842a517F2bc21a9f9FEE062801B00c0Fbf3F4B\",\"Score\":1000},{\"AvatarAddr\":\"F997623a59B46612836c201A184A9487803208f0\",\"Score\":1036},{\"AvatarAddr\":\"cc0b6D014e3fFb4E430189b4D88271767FD6ec26\",\"Score\":1000},{\"AvatarAddr\":\"85Fb4BdC5f3bF0Fc26e125579552d078c49D9540\",\"Score\":1144},{\"AvatarAddr\":\"b9e300a64da42787862Bd6C0fc0f3e0138336d82\",\"Score\":1160},{\"AvatarAddr\":\"728b79CbEcFfcE539bFd2ea856a4DF666D9Db358\",\"Score\":1000},{\"AvatarAddr\":\"95df761dd1a656020dE0A91f9e3210a2026Be035\",\"Score\":1000},{\"AvatarAddr\":\"29695A6621330325B7817B268b1197eC64bf8C01\",\"Score\":1131},{\"AvatarAddr\":\"Dc08Db296b1b98d8FB862f539A01a353A92b4e17\",\"Score\":1117},{\"AvatarAddr\":\"c484E356C2aCDADe546E9b5288fAF8018FE24147\",\"Score\":1160},{\"AvatarAddr\":\"221fB30Fa02B686a5E2898Ac740725E69e4F633a\",\"Score\":1072},{\"AvatarAddr\":\"b780f1e4ecd3740B973A74fa12533a58cc78e623\",\"Score\":1000},{\"AvatarAddr\":\"22ea4344479679Cdb58Fcdbb3994981212E0CCc6\",\"Score\":1000},{\"AvatarAddr\":\"3E30d01fd741201DEB9e6c7fCCC81C6af2f24C85\",\"Score\":1000},{\"AvatarAddr\":\"C42D3305dCd199Aa755F1dFE859BF9238915181c\",\"Score\":1160},{\"AvatarAddr\":\"60b1E421Be767e0adfEE403AaDA7B19eA2a5bb38\",\"Score\":1000},{\"AvatarAddr\":\"fbe66aa4b68648144afE949de362845e5aD07060\",\"Score\":1143},{\"AvatarAddr\":\"49d462BB68fC155Db8ad8Ab269127e0c89d0dA0f\",\"Score\":1000},{\"AvatarAddr\":\"Cc3daE35aA2F1b053Da05204d05CBb4C20Fdbe74\",\"Score\":1152},{\"AvatarAddr\":\"a5416124ce2d9bB8Ad5395BdD841c3d71CC9f5CB\",\"Score\":1000},{\"AvatarAddr\":\"eE17ef94F85b2f6A0e10Cfb70Cc24177dCEAa9ff\",\"Score\":1159},{\"AvatarAddr\":\"00A7e34B28318192a9dBbDD796a6E8e8f1f6062a\",\"Score\":1157},{\"AvatarAddr\":\"9daB25e19f3a688b42Ac5fD1B1B93c3bb929de57\",\"Score\":1152},{\"AvatarAddr\":\"C11AB8dEEb3Ac92D3DA8f24644b0D17499D754Ed\",\"Score\":1152},{\"AvatarAddr\":\"AD12E4e1E364147E2fc4E7B9Fd15A617e50c0106\",\"Score\":1159},{\"AvatarAddr\":\"68853709B3b0199d90C04fd77d2e594586a2b8d8\",\"Score\":1000},{\"AvatarAddr\":\"88750df6FA45a3F12f8D0021babD355517a7DF6D\",\"Score\":1152},{\"AvatarAddr\":\"d244C365137892DA24f7497500D36E9f4f3fd14a\",\"Score\":1152},{\"AvatarAddr\":\"38D87332664c575A4a79ADcFae8A45E26b2527eC\",\"Score\":1000},{\"AvatarAddr\":\"d32982a0e593D69630a6de9C6aAf256d6aB5bA6a\",\"Score\":1000},{\"AvatarAddr\":\"700f8Bb59c2B35Bd922b5799f3A3Ffc7d2e187A4\",\"Score\":1152},{\"AvatarAddr\":\"61608C29F0b5757F33D3336B0c8e196395896a92\",\"Score\":1158},{\"AvatarAddr\":\"9A5D17D91882aff267f6fFb4A981508dA27BC7FE\",\"Score\":1000},{\"AvatarAddr\":\"AAC397D6dF1aA9fB5f948329346936cf2F55C61B\",\"Score\":1159},{\"AvatarAddr\":\"e0DE4fdF0369EECf512dFa40f12EdeF756bF2974\",\"Score\":1000},{\"AvatarAddr\":\"809FdaBd3C3B1482502d559004cA2D1F5A1EDA40\",\"Score\":1158},{\"AvatarAddr\":\"2ABC180506E8c772687677716ab5e9a35630c9aF\",\"Score\":1000},{\"AvatarAddr\":\"edDd94d7B20B14e31820dcA27C32e0BCbc94247a\",\"Score\":1159},{\"AvatarAddr\":\"56844c5Ba366188ac018dC566f7BE1AdF9e064Ac\",\"Score\":1159},{\"AvatarAddr\":\"F456F7790DFAbAd8Fe498B1bE2C68a958de7A97B\",\"Score\":1159},{\"AvatarAddr\":\"b09A16e51d0b294BfD288DD94aeCB379B1d3023D\",\"Score\":1000},{\"AvatarAddr\":\"DECbb974E113951B61cC77D1f6d70026E24F96Af\",\"Score\":1159},{\"AvatarAddr\":\"451901C8E568C0baae41962d5b41F35D031E7823\",\"Score\":1152},{\"AvatarAddr\":\"9aFb8Ab9d1C285773020bf7a5B3e539BAF34E2ec\",\"Score\":1159},{\"AvatarAddr\":\"7739E21CA53DeE194928562f2B124a5F65c65aA5\",\"Score\":1155},{\"AvatarAddr\":\"058fc0Df20a649716C67F8Af1ef373682De5a175\",\"Score\":1138},{\"AvatarAddr\":\"6D0240F5742812a6c6c5Ed3794198524C1F284EF\",\"Score\":1159},{\"AvatarAddr\":\"76cC2746ac35EADcc16B08183ce7194EED277947\",\"Score\":1159},{\"AvatarAddr\":\"155De1A87832DEf74Cf9b8a39e097e2CF8eEcdDc\",\"Score\":1145},{\"AvatarAddr\":\"C05E2e31f178F58b6bE0852FB7811ea5D7563363\",\"Score\":1160},{\"AvatarAddr\":\"8BEddc9353DbB9773EA6E6311684d0d2511B8aF8\",\"Score\":1000},{\"AvatarAddr\":\"b602Bc7EFadE9Ce17232C5951fff873a3bc7111a\",\"Score\":1000},{\"AvatarAddr\":\"520986BD45fDa819feb4E50263801e13D038C8Ae\",\"Score\":1000},{\"AvatarAddr\":\"A52d21b4673f308024950F55F65F388892782001\",\"Score\":1000},{\"AvatarAddr\":\"333F65b955120C5D533bE8A7cD3Dd8eF6482965C\",\"Score\":1139},{\"AvatarAddr\":\"614024b75760A5e95EC6C3165fF3a823F2518472\",\"Score\":1155},{\"AvatarAddr\":\"C3542294a475930B96dB496A2Fc1Eb27dC6916a5\",\"Score\":1143},{\"AvatarAddr\":\"C43796E8C97a7276dd5BBFB89354A685B6d691f1\",\"Score\":1000},{\"AvatarAddr\":\"40cddBa65cF92126CF754eDfA9f7E3DEd972C4F4\",\"Score\":1000},{\"AvatarAddr\":\"8D1B41723Bbc87b1Bc7ecF367e495BB3b13C43C8\",\"Score\":1140},{\"AvatarAddr\":\"c9156b9e4dd1f10a88388465660FaB82Ab24e72c\",\"Score\":1000},{\"AvatarAddr\":\"496B4c0d0b9580F55aF639739c9ffb2eAAbA3180\",\"Score\":1159},{\"AvatarAddr\":\"d273d8F7EE2c2546f375a6540d5afEC7A7bFb7Ce\",\"Score\":1000},{\"AvatarAddr\":\"677B8d2fb7A5Fad70B7707FC88C10475D5D721B4\",\"Score\":1159},{\"AvatarAddr\":\"94aA704baa878FF842968F035597ADF2D487Fbb2\",\"Score\":1000},{\"AvatarAddr\":\"c90BC4B4F95382c8A68bECcA9c1056C4c75864C4\",\"Score\":1151},{\"AvatarAddr\":\"a07CdC27Be2E867F9e3736cC188E4eDaaad6d097\",\"Score\":1144},{\"AvatarAddr\":\"ffbC9623dA9c6929145c8783819fc7c3e81AF165\",\"Score\":1143},{\"AvatarAddr\":\"64AFd3D5dBBdA9fF198B04DBfA63f481dB09F016\",\"Score\":1144},{\"AvatarAddr\":\"fe1F732ED4B81084319521dA9a36A783027139bd\",\"Score\":1143},{\"AvatarAddr\":\"aDe6eBee292E8ccc0C9B04525B35246fE2Ce2ad6\",\"Score\":1144},{\"AvatarAddr\":\"B7A6fB66Aee9d54c2f74fA5A8Fa76F3E5479d2C1\",\"Score\":1159},{\"AvatarAddr\":\"424A4cB8eD50e2e61875249d48D9D7542E45c3f5\",\"Score\":1159},{\"AvatarAddr\":\"2Bac48b60CA0E441b83575b0f8533d97b59E973D\",\"Score\":1151},{\"AvatarAddr\":\"2a20dEb1a02d2551153E9e4d0bbFF798C67f1Dc5\",\"Score\":1152},{\"AvatarAddr\":\"de9eFf6dAB562Fbed2f95b99395F80eBf3BbFdF2\",\"Score\":1144},{\"AvatarAddr\":\"5f5101Ddf6B5b00A338101d43f1759D6Fc047559\",\"Score\":1136},{\"AvatarAddr\":\"CFAf499Ba7cF15abaC0849e9917bc902427Ff7A8\",\"Score\":1144},{\"AvatarAddr\":\"B1592078be12d4008b7A868EAd955BB273E93035\",\"Score\":1144},{\"AvatarAddr\":\"620fcB60772c6272E63c5420E0393046a3EBb82E\",\"Score\":1000},{\"AvatarAddr\":\"e51682B26261d72963643038ce7D73306C22F460\",\"Score\":1159},{\"AvatarAddr\":\"571b99930822D95c867eaf75B74DAF3301a1Bc06\",\"Score\":1152},{\"AvatarAddr\":\"F56B360B0ebF571daD1A0f9669CE51aDc07027ab\",\"Score\":1151},{\"AvatarAddr\":\"7c332AD8634Bb36730f80903c8977f70461E9FEE\",\"Score\":1158},{\"AvatarAddr\":\"76A1dB1792C4c3f17e32E1C88B3F358B845015A0\",\"Score\":1144},{\"AvatarAddr\":\"6F813Ad797823B4400ba51B7e2Bb4113466F26F9\",\"Score\":1160},{\"AvatarAddr\":\"672B633f6Cad8B4b4752cF709346A1dC90ff119E\",\"Score\":1145},{\"AvatarAddr\":\"B24160aba1350FD4d1864BD7aF73EDF09f0F0572\",\"Score\":1160},{\"AvatarAddr\":\"4FC1bcc455096B22166eBD1e56625A7AEAce9C1D\",\"Score\":1151},{\"AvatarAddr\":\"05224F6401574a75199eB7d38380402280E9047a\",\"Score\":1158},{\"AvatarAddr\":\"Dcd52148aA94e4658180642627B6CB967ddA5751\",\"Score\":1020},{\"AvatarAddr\":\"786f9C1f488C3b3e27A8A4AE4461b0870bC25Da2\",\"Score\":1160},{\"AvatarAddr\":\"e25C04E79062cc4451a959bD55ddACc095e0C8ee\",\"Score\":1000},{\"AvatarAddr\":\"7618657D1cE6Ec9cA9bF90D26BF08153c9822F37\",\"Score\":1153},{\"AvatarAddr\":\"cf1270E34b683FB42c9c4339e2A72C3D474f8dB4\",\"Score\":1000},{\"AvatarAddr\":\"C6b0d1fBFa00f4fAaECAC99D30f3bb962Eb65773\",\"Score\":1152},{\"AvatarAddr\":\"4de28c8adFBC7a0fEb6F7AbF6c939C54084D8e1a\",\"Score\":1144},{\"AvatarAddr\":\"0146392e479199F68b5c4d8673087b99ad5c44Be\",\"Score\":1159},{\"AvatarAddr\":\"b11dB3A96841b1cfBf61Da66068F4C3a05b99A18\",\"Score\":1152},{\"AvatarAddr\":\"89F52380c9Bd6D33A5CB39948851c508ba113043\",\"Score\":1152},{\"AvatarAddr\":\"6a507a97b1fC24CC0439f2D7aaEFc25581C56e5D\",\"Score\":1152},{\"AvatarAddr\":\"03a9206dA825cACcb7974f59e8F8C1c1cF9E286F\",\"Score\":1144},{\"AvatarAddr\":\"4E25E77BE0442b55f8140DDd9c3B9C5159F994A7\",\"Score\":1152},{\"AvatarAddr\":\"fCAc74eB215868D1Bb72540435Bf72eF39F4C46c\",\"Score\":1159},{\"AvatarAddr\":\"EdA88B0dccEed447ce7C1143f0E756a1313e6609\",\"Score\":1159},{\"AvatarAddr\":\"9481CbFE326e323Faf3Dd22FB8Ca043659A43662\",\"Score\":1159},{\"AvatarAddr\":\"4F57B1E0ED4e14208444432Ba05067D98E30eDC1\",\"Score\":1159},{\"AvatarAddr\":\"BeA65Db9DC40f53914Cc23b668b051DC8d548a58\",\"Score\":1160},{\"AvatarAddr\":\"e2f5718253EeEA5fA64614C5EB38Fa61512a10b9\",\"Score\":1160},{\"AvatarAddr\":\"1C7D9581780511E76807638b62357D416131BF11\",\"Score\":1152},{\"AvatarAddr\":\"d2853b9B007B0e44C94016B34189fe582A5AD3CE\",\"Score\":1159},{\"AvatarAddr\":\"891F28cD6B81dA1c52022718f311630665c52D15\",\"Score\":1151},{\"AvatarAddr\":\"2d7F180Ac1A5c7C5492597227eB24ef8F8dD4395\",\"Score\":1144},{\"AvatarAddr\":\"F0fAf163EFdB9ADb291371418be433F4b729A6c1\",\"Score\":1108},{\"AvatarAddr\":\"700CF148D724b6B4a6116642339361c203743737\",\"Score\":1160},{\"AvatarAddr\":\"cf9078feCDB4F4dD2F15b8fEaC4D796d002BdB1d\",\"Score\":1136},{\"AvatarAddr\":\"24BD4b45F74BF3ac5C3cac2Ea296f19d011a2Ae8\",\"Score\":1152},{\"AvatarAddr\":\"1F2b52a51f1D186b58ed467B58168167016a4749\",\"Score\":1158},{\"AvatarAddr\":\"27CA50e1fcA37A18e0dCb9f99b249533Fd02E507\",\"Score\":1152},{\"AvatarAddr\":\"732a63542e63Ad6c35C44959272c4094F49f9871\",\"Score\":1034},{\"AvatarAddr\":\"D6D52C7012D2effF3bBD2b22e40DadBe3Ff03702\",\"Score\":1020},{\"AvatarAddr\":\"14e987F38c2896d799e44aEfFa65C9BdBfDCDe38\",\"Score\":1152},{\"AvatarAddr\":\"0Cc4Cc434818B74edBEddC90d25eb2E3Fb2F7b92\",\"Score\":1159},{\"AvatarAddr\":\"7660789E89428aE741aF36b470f1C54738255C15\",\"Score\":1000},{\"AvatarAddr\":\"AC9D450C69dF27ee290C90EC61c4B76154b0A21c\",\"Score\":1000},{\"AvatarAddr\":\"d5A41708Fe3b372afD86d219C7FC7EE91a8DeDC4\",\"Score\":1158},{\"AvatarAddr\":\"c28FBED6E66947BD48c822DC3F426f8FB5Ec0f18\",\"Score\":1144},{\"AvatarAddr\":\"FEcfe132205AcF98564144212278d2Ee0665b6Ca\",\"Score\":1152},{\"AvatarAddr\":\"c0dc28100680E02c78F22b66dc503025a961aAF9\",\"Score\":1160},{\"AvatarAddr\":\"776860c97c3FB8B178c6132b2BFD41E6e447B19a\",\"Score\":1159},{\"AvatarAddr\":\"135f34A89607F6c7727B46FcB7B8406895732E77\",\"Score\":1144},{\"AvatarAddr\":\"7C72e587A339eC1925F07711809B97cb3E0130cA\",\"Score\":1158},{\"AvatarAddr\":\"CF6d9ae9873210496935D153309b214bCC5eE683\",\"Score\":1160},{\"AvatarAddr\":\"65EC8E8b0a5AC53321817aF07d7e4303b15145Aa\",\"Score\":1000},{\"AvatarAddr\":\"ac7255bf042C6eBe6Ae13469580B86F2679A4805\",\"Score\":1160},{\"AvatarAddr\":\"03B80A00F8F041D9DCb87bCDB6864d34A684713f\",\"Score\":1158},{\"AvatarAddr\":\"286436870433C49452728e54062f25ec5d0Bd626\",\"Score\":1155},{\"AvatarAddr\":\"B81D5458dF779d62bc097FBe9bF7Ac77D4e6b24a\",\"Score\":1159},{\"AvatarAddr\":\"950826223854AaE37974B1819913C8d41CB7dEBE\",\"Score\":1159},{\"AvatarAddr\":\"5471FDF9E7bDF65bAAc27Bede8aB4A4e77C32CBf\",\"Score\":1026},{\"AvatarAddr\":\"19326abE59e7115290cF0c69d295C542E2CF973A\",\"Score\":1159},{\"AvatarAddr\":\"6b99335dE1C1D2f6b71BE4578F824D934412948e\",\"Score\":1159},{\"AvatarAddr\":\"fC267f569f2D8967070C0A9602D671935ffd459a\",\"Score\":1020},{\"AvatarAddr\":\"f0965370b1705b288Dafe6852CD6B3F839404E47\",\"Score\":1160},{\"AvatarAddr\":\"718fc06692f420eB4C4f80e1161001E38076A3d0\",\"Score\":1136},{\"AvatarAddr\":\"a5C39B381Faa36C046ed4683cd2f3f1081eD100E\",\"Score\":1000},{\"AvatarAddr\":\"E84f7f9e126F38C81aF1EF46Ef355092a806b2bC\",\"Score\":1158},{\"AvatarAddr\":\"74aD5d4E6552A1DB43385192a2d092c495395D2b\",\"Score\":1012},{\"AvatarAddr\":\"D7235a701B7176fD3d466965e213DDE78c600b12\",\"Score\":1000},{\"AvatarAddr\":\"ef9c662EE51f4d4546ffc2eF6ae5a0fa3A619F23\",\"Score\":1152},{\"AvatarAddr\":\"FFcB3406dE9666aD96ef908dcEd84Af58830673c\",\"Score\":1000},{\"AvatarAddr\":\"9b490e04Fb1FEcce2adD12Cf4ce285e45780eac9\",\"Score\":1152},{\"AvatarAddr\":\"354Bae17545aDb26bd3018858F5F5def0D74144B\",\"Score\":1000},{\"AvatarAddr\":\"Bf36946f1f856E62b6214433343aC46A19511b60\",\"Score\":1058},{\"AvatarAddr\":\"4d6Bd04f0c253920b3A3bf1e453e7b5Ff74F1b3a\",\"Score\":1000},{\"AvatarAddr\":\"d63E1a9B734Dbc4625dB4036c3e5Fd1b654141fa\",\"Score\":1146},{\"AvatarAddr\":\"0E6764B421e647cf399d1670895F0915E97629cB\",\"Score\":1000},{\"AvatarAddr\":\"321C4b33A2c36Da7F569f180D8EcA820A799Aa76\",\"Score\":1000},{\"AvatarAddr\":\"570B505c6816cE71e76775f97836A9eFE394174C\",\"Score\":1158},{\"AvatarAddr\":\"7c7F0a51F386664CD53a51C11e1Ec32687ee7193\",\"Score\":1000},{\"AvatarAddr\":\"B58d2de10a884f8237dBA358B80ff265b745ad5C\",\"Score\":1160},{\"AvatarAddr\":\"D7f2b12D8C61cD7fb5C9939EC5105fd700E678c7\",\"Score\":1160},{\"AvatarAddr\":\"8D089A87109d2400d0F405D21F4Dc1BD449B78E4\",\"Score\":1080},{\"AvatarAddr\":\"62a0f2af5E90709357fb0664191486Bc459681a1\",\"Score\":1152},{\"AvatarAddr\":\"5c23114d927b1d55DA1BB592e1cdC90dCE82125A\",\"Score\":1159},{\"AvatarAddr\":\"f623B48e0aA1ACC8BbB7ba5d8b6611414475B0BD\",\"Score\":1160},{\"AvatarAddr\":\"668de3E6d50bd8cAf5bF5DD7964897658E69E83f\",\"Score\":1000},{\"AvatarAddr\":\"b30A336D5157A3717836d327fBcD9C80231F06EC\",\"Score\":1000},{\"AvatarAddr\":\"EC918579584472fF601fDEE86c07bA95DF9B2708\",\"Score\":1160},{\"AvatarAddr\":\"487E8631a34652f8B838E171a7dCdeD8e5214fea\",\"Score\":1159},{\"AvatarAddr\":\"5CDDB26D36D5B7b27Db33dA66BD2A785c24e6eB2\",\"Score\":1151},{\"AvatarAddr\":\"13CFE4FF836DBF0CA36ABDDfCB3da96e78392739\",\"Score\":1098},{\"AvatarAddr\":\"7F324b4fD177A2dbEe552A28bff260525fCcc7a6\",\"Score\":1160},{\"AvatarAddr\":\"15df5A8cc0b08e6d889dc5212E4260679fDf46a1\",\"Score\":1159},{\"AvatarAddr\":\"05f3Dcf58eBE004d0eEA0Be2BDf752C7e50186cF\",\"Score\":1159},{\"AvatarAddr\":\"C559E3224d1d0aA7D48D1d4472209d3fB3C776FB\",\"Score\":1160},{\"AvatarAddr\":\"E9705dDaeed092AB4741Ce7a4604615749c3d22B\",\"Score\":1160},{\"AvatarAddr\":\"42d21c8B9a240e3e74Ab146792a18E413C044f4e\",\"Score\":1000},{\"AvatarAddr\":\"24ed584003970eF661c06cC4890338712b9452B4\",\"Score\":1144},{\"AvatarAddr\":\"11989B4a3B62bD4F8D31a5E1778Faf234AAfD6db\",\"Score\":1152},{\"AvatarAddr\":\"97E367c5Bd499b6949b0601Bcaec1f6f77E778e7\",\"Score\":1150},{\"AvatarAddr\":\"A61b04011DDF72aa68d70F64dBc96Fc1f5c7d903\",\"Score\":1147},{\"AvatarAddr\":\"D87825c96b55D1E593DCCD5D5471669759eAA40B\",\"Score\":1000},{\"AvatarAddr\":\"C69FDb1ba9042D199C052Dc1480010640366515c\",\"Score\":1092},{\"AvatarAddr\":\"a0461a8B8A09018b13d4F41Df931803A34212001\",\"Score\":1158},{\"AvatarAddr\":\"0003f98545f5c017a7A598E88fED80Ad5e8e19b0\",\"Score\":1139},{\"AvatarAddr\":\"ee8aFe5B21b91D2dA42e97fc53D73dbF559aA188\",\"Score\":1160},{\"AvatarAddr\":\"E1A7c0EaBe10bEAE39874AdD46D2D545D3C76DA8\",\"Score\":1160},{\"AvatarAddr\":\"2D7e7d5741368A987C89FA10B7bcb2b2471e6D95\",\"Score\":1159},{\"AvatarAddr\":\"8824Fe1c4398E3aD316E0875C8031FfDAb7313ED\",\"Score\":1000},{\"AvatarAddr\":\"FB9688A5659384Fda385AB58cB910EfF17626E92\",\"Score\":1160},{\"AvatarAddr\":\"7a59c1197D441aE3Fd51F3A55231119bfF2CC1D5\",\"Score\":1159},{\"AvatarAddr\":\"b5B58F969Fe6eBFaB7694bd4e7388E20162E05A5\",\"Score\":1159},{\"AvatarAddr\":\"becB71066e4066cA29B994F28C44ed707EF69aA5\",\"Score\":1160},{\"AvatarAddr\":\"2692D73A42627f137B42CC819726f25580a23137\",\"Score\":1060},{\"AvatarAddr\":\"5BEC4cDC56b51925807D17619fB7B3EE49dDa28b\",\"Score\":1160},{\"AvatarAddr\":\"e6A2988165bE52CCaCAcD9c037268807f49d2785\",\"Score\":1160},{\"AvatarAddr\":\"71241D6b54b04B190E720706EaA0147c078Fd902\",\"Score\":1000},{\"AvatarAddr\":\"3060eA6C2c88C89ffcD955385c6650499de77518\",\"Score\":1159},{\"AvatarAddr\":\"D1740330Cc9f28de4Ba4C363dFa75559F375c030\",\"Score\":1000},{\"AvatarAddr\":\"e31Fc42EC9F9D163c04c9dbd90d2976e1f688919\",\"Score\":1000},{\"AvatarAddr\":\"6A681659610A174495D11D4B1cB36185b75CF1Ec\",\"Score\":1160},{\"AvatarAddr\":\"55C5C15d69d9FbdBbd053E59d23E1AbCbb8FDD1F\",\"Score\":1152},{\"AvatarAddr\":\"97D16A5503e11490c4E3f0150dF5B6B39142373e\",\"Score\":1160},{\"AvatarAddr\":\"a499356353b7198fad3742B890Aa6eF8c135D987\",\"Score\":1144},{\"AvatarAddr\":\"005E571b6C850E38874E5B8Ac0A24FE46F315Dea\",\"Score\":1152},{\"AvatarAddr\":\"99BE60C838aB97017Def66d763Dfb06B4EdCb2E6\",\"Score\":1000},{\"AvatarAddr\":\"46D4992d3Eb5046E2f85c313D855c9B1e9aA76c0\",\"Score\":1144},{\"AvatarAddr\":\"8F8A0cc5C8651dC1F08Ef4A4921F22eb9Bda72c3\",\"Score\":1144},{\"AvatarAddr\":\"0A63A5F318F3b51BA6970fE62fd277414b495Fa0\",\"Score\":1149},{\"AvatarAddr\":\"A97b7449b7B020a3d25d495855aB70E86e11855D\",\"Score\":1160},{\"AvatarAddr\":\"536Eb10336Fa87AD1d8D13772beB02d0ca2601d1\",\"Score\":1037},{\"AvatarAddr\":\"3a24f80Fb9C081729840C79d34f239224F61c405\",\"Score\":1194},{\"AvatarAddr\":\"33936D2608cc6401CEF88456617fBAA71253b280\",\"Score\":1000},{\"AvatarAddr\":\"Be49F5Dc8937D04201b47030357E3F4c84f882a1\",\"Score\":1160},{\"AvatarAddr\":\"69f403d05084595f29F4373fA2fAcDADe01c1ab9\",\"Score\":1160},{\"AvatarAddr\":\"d0643E862efA67012036D641D78Ca81978A9E487\",\"Score\":1160},{\"AvatarAddr\":\"0aceFC05288289AF43a5DC367670D1f0D35d0bB1\",\"Score\":1160},{\"AvatarAddr\":\"7fd180f8F50f7DC837c61d09108c524008a475fA\",\"Score\":1160},{\"AvatarAddr\":\"07f7696a385B5DfD97Ac6381772CFfe438023496\",\"Score\":1144},{\"AvatarAddr\":\"5DBEaC128ff36580a3Cb75beB471C7D47EC554d7\",\"Score\":1020},{\"AvatarAddr\":\"aF4085922EFE68C2AEAbf9784282ac9A4Fa28257\",\"Score\":1160},{\"AvatarAddr\":\"2A7Bb5219EC98Bf252AbB5e71606FA3AE2B686BA\",\"Score\":1000},{\"AvatarAddr\":\"4741fA18cB21e7253C00C2c776F7b2576Ce36390\",\"Score\":1160},{\"AvatarAddr\":\"6f999eCDb8Ba3ACA62e2e53a36c91BE9eFFBDc25\",\"Score\":1160}]"; + var list = JsonSerializer.Deserialize>(cached); + var expected = AvatarAddrAndScoresWithRank_Legacy(list); + var result = ArenaParticipantsWorker.AvatarAddrAndScoresWithRank(list); + Assert.Equal(expected, result); + Assert.True(list.Count > 0); + } + + + /// Copy from legacy RpcClient.AvatarAddrAndScoresWithRank for calculate rank result not changed. + /// + /// Retrieves the avatar addresses and scores with ranks for a given list of avatar addresses, current round data, and world state. + /// + /// Ths list of avatar address and score tuples. + /// The list of avatar addresses, scores, and ranks. + private static List AvatarAddrAndScoresWithRank_Legacy(List avatarAddrAndScores) + { + if (avatarAddrAndScores.Count == 0) + { + return new List(); + } + + if (avatarAddrAndScores.Count == 1) + { + var score = avatarAddrAndScores.Single(); + return [new ArenaScoreAndRank(score.AvatarAddr, score.Score, 1)]; + } + + List orderedTuples = avatarAddrAndScores + .OrderByDescending(tuple => tuple.Score) + .ThenBy(tuple => tuple.AvatarAddr) + .Select(tuple => new ArenaScoreAndRank(tuple.AvatarAddr, tuple.Score, 0)) + .ToList(); + int? currentScore = null; + var currentRank = 1; + var avatarAddrAndScoresWithRank = new List(); + var trunk = new List(); + for (var i = 0; i < orderedTuples.Count; i++) + { + var tuple = orderedTuples[i]; + // i = 0일때, currentScore를 설정하고 넘어간다. + if (!currentScore.HasValue) + { + currentScore = tuple.Score; + trunk.Add(tuple); + continue; + } + + // 동점자가 존재하면 Rank값을 증가시켜야한다. + if (currentScore.Value == tuple.Score) + { + trunk.Add(tuple); + currentRank++; + if (i < orderedTuples.Count - 1) + { + continue; + } + + foreach (var tupleInTrunk in trunk) + { + avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( + tupleInTrunk.AvatarAddr, + tupleInTrunk.Score, + currentRank)); + } + + trunk.Clear(); + + continue; + } + + foreach (var tupleInTrunk in trunk) + { + avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( + tupleInTrunk.AvatarAddr, + tupleInTrunk.Score, + currentRank)); + } + + trunk.Clear(); + if (i < orderedTuples.Count - 1) + { + trunk.Add(tuple); + currentScore = tuple.Score; + currentRank++; + continue; + } + + avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( + tuple.AvatarAddr, + tuple.Score, + currentRank + 1)); + } + + return avatarAddrAndScoresWithRank; + } +} diff --git a/ArenaService/ArenaService/ArenaWorker.cs b/ArenaService/ArenaService/ArenaWorker.cs index f6f7d31..b8e7d02 100644 --- a/ArenaService/ArenaService/ArenaWorker.cs +++ b/ArenaService/ArenaService/ArenaWorker.cs @@ -98,68 +98,47 @@ public async Task PrepareArenaParticipants() /// The list of avatar addresses, scores, and ranks. public static List AvatarAddrAndScoresWithRank(List avatarAddrAndScores) { - List orderedTuples = avatarAddrAndScores + if (avatarAddrAndScores.Count == 0) + { + return new List(); + } + + if (avatarAddrAndScores.Count == 1) + { + var score = avatarAddrAndScores.Single(); + return [new ArenaScoreAndRank(score.AvatarAddr, score.Score, 1)]; + } + + var orderedTuples = avatarAddrAndScores .OrderByDescending(tuple => tuple.Score) .ThenBy(tuple => tuple.AvatarAddr) - .Select(tuple => new ArenaScoreAndRank(tuple.AvatarAddr, tuple.Score, 0)) .ToList(); - int? currentScore = null; - var currentRank = 1; + var avatarAddrAndScoresWithRank = new List(); - var trunk = new List(); - for (var i = 0; i < orderedTuples.Count; i++) + while (orderedTuples.Count > 0) { - var tuple = orderedTuples[i]; - if (!currentScore.HasValue) - { - currentScore = tuple.Score; - trunk.Add(tuple); - continue; - } - - if (currentScore.Value == tuple.Score) + // 동점자를 찾기위해 기준 점수 설정 + var currentScore = orderedTuples.First().Score; + var groupSize = 0; + var targets = new List(); + foreach (var tuple in orderedTuples) { - trunk.Add(tuple); - currentRank++; - if (i < orderedTuples.Count - 1) + if (currentScore == tuple.Score) { - continue; + groupSize++; + targets.Add(tuple); } - - foreach (var tupleInTrunk in trunk) + else { - avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( - tupleInTrunk.AvatarAddr, - tupleInTrunk.Score, - currentRank)); + break; } - - trunk.Clear(); - - continue; - } - - foreach (var tupleInTrunk in trunk) - { - avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( - tupleInTrunk.AvatarAddr, - tupleInTrunk.Score, - currentRank)); - } - - trunk.Clear(); - if (i < orderedTuples.Count - 1) - { - trunk.Add(tuple); - currentScore = tuple.Score; - currentRank++; - continue; } - avatarAddrAndScoresWithRank.Add(new ArenaScoreAndRank( - tuple.AvatarAddr, - tuple.Score, - currentRank + 1)); + // 순위는 기존 상위권 순위 + 동점자의 숫자 + var rank = avatarAddrAndScoresWithRank.Count + groupSize; + avatarAddrAndScoresWithRank.AddRange(targets.Select(tuple => new ArenaScoreAndRank(tuple.AvatarAddr, tuple.Score, rank))); + // 다음 순위 설정을 위해 이번 그룹 숫자만큼 삭제 + orderedTuples.RemoveRange(0, groupSize); } return avatarAddrAndScoresWithRank; From dd13f18251f62623eefdbae75385bebacf5727ec Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 19 Dec 2024 22:51:12 +0900 Subject: [PATCH 4/6] Add synced block index --- ArenaService/ArenaService/ArenaWorker.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ArenaService/ArenaService/ArenaWorker.cs b/ArenaService/ArenaService/ArenaWorker.cs index b8e7d02..014b051 100644 --- a/ArenaService/ArenaService/ArenaWorker.cs +++ b/ArenaService/ArenaService/ArenaWorker.cs @@ -59,6 +59,7 @@ public async Task PrepareArenaParticipants() } var tip = _rpcClient.Tip!; + var blockIndex = tip.Index; var currentRoundData = await _rpcClient.GetRoundData(tip, cancellationToken); var participants = await _rpcClient.GetArenaParticipantsState(tip, currentRoundData, cancellationToken); var cacheKey = $"{currentRoundData.ChampionshipId}_{currentRoundData.Round}"; @@ -69,7 +70,7 @@ public async Task PrepareArenaParticipants() if (participants is null) { await _service.SetArenaParticipantsAsync(cacheKey, new List(), expiry); - _logger.LogInformation("[ArenaParticipantsWorker] participants({CacheKey}) is null. set empty list", cacheKey); + _logger.LogInformation("[ArenaParticipantsWorker] participants({CacheKey}) is null. set empty list on {BlockIndex}", cacheKey, blockIndex); return; } @@ -87,7 +88,7 @@ public async Task PrepareArenaParticipants() await _service.SetSeasonAsync(cacheKey, expiry); await _service.SetAvatarAddrAndScores(scoreCacheKey, avatarAddrAndScores, expiry); sw.Stop(); - _logger.LogInformation("[ArenaParticipantsWorker]Set Arena Cache[{CacheKey}]: {Elapsed}", cacheKey, sw.Elapsed); + _logger.LogInformation("[ArenaParticipantsWorker]Set Arena Cache[{CacheKey}] on {BlockIndex}: {Elapsed}", cacheKey, blockIndex, sw.Elapsed); } From ac08860210e3e927a3f04b6d836839dbd3d2eb6e Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 19 Dec 2024 23:37:48 +0900 Subject: [PATCH 5/6] Refactor GetArenaParticipnats for reduce call get state --- ...rticipant.cs => ArenaParticipantStruct.cs} | 7 +- .../ArenaService/ArenaParticipantType.cs | 20 +- ArenaService/ArenaService/ArenaWorker.cs | 6 +- .../IRedisArenaParticipantsService.cs | 4 +- .../RedisArenaParticipantsService.cs | 8 +- ArenaService/ArenaService/RpcClient.cs | 273 ++---------------- ArenaService/ArenaService/StateQuery.cs | 6 +- 7 files changed, 51 insertions(+), 273 deletions(-) rename ArenaService/ArenaService/{ArenaParticipant.cs => ArenaParticipantStruct.cs} (89%) diff --git a/ArenaService/ArenaService/ArenaParticipant.cs b/ArenaService/ArenaService/ArenaParticipantStruct.cs similarity index 89% rename from ArenaService/ArenaService/ArenaParticipant.cs rename to ArenaService/ArenaService/ArenaParticipantStruct.cs index 6d11921..94a0830 100644 --- a/ArenaService/ArenaService/ArenaParticipant.cs +++ b/ArenaService/ArenaService/ArenaParticipantStruct.cs @@ -1,9 +1,8 @@ using Libplanet.Crypto; -using Nekoyume.Model.State; namespace ArenaService; -public struct ArenaParticipant +public struct ArenaParticipantStruct { public Address AvatarAddr { get; set; } public int Score { get; set; } @@ -15,11 +14,11 @@ public struct ArenaParticipant public string NameWithHash { get; set; } = ""; public int Level { get; set; } - public ArenaParticipant() + public ArenaParticipantStruct() { } - public ArenaParticipant( + public ArenaParticipantStruct( Address avatarAddr, int score, int rank, diff --git a/ArenaService/ArenaService/ArenaParticipantType.cs b/ArenaService/ArenaService/ArenaParticipantType.cs index 2420f18..cd10c56 100644 --- a/ArenaService/ArenaService/ArenaParticipantType.cs +++ b/ArenaService/ArenaService/ArenaParticipantType.cs @@ -2,44 +2,44 @@ namespace ArenaService; -public class ArenaParticipantType : ObjectGraphType +public class ArenaParticipantType : ObjectGraphType { public ArenaParticipantType() { Field>( - nameof(ArenaParticipant.AvatarAddr), + nameof(ArenaParticipantStruct.AvatarAddr), description: "Address of avatar.", resolve: context => context.Source.AvatarAddr); Field>( - nameof(ArenaParticipant.Score), + nameof(ArenaParticipantStruct.Score), description: "Arena score of avatar.", resolve: context => context.Source.Score); Field>( - nameof(ArenaParticipant.Rank), + nameof(ArenaParticipantStruct.Rank), description: "Arena rank of avatar.", resolve: context => context.Source.Rank); Field>( - nameof(ArenaParticipant.WinScore), + nameof(ArenaParticipantStruct.WinScore), description: "Score for victory.", resolve: context => context.Source.WinScore); Field>( - nameof(ArenaParticipant.LoseScore), + nameof(ArenaParticipantStruct.LoseScore), description: "Score for defeat.", resolve: context => context.Source.LoseScore); Field>( - nameof(ArenaParticipant.Cp), + nameof(ArenaParticipantStruct.Cp), description: "Cp of avatar.", resolve: context => context.Source.Cp); Field>( - nameof(ArenaParticipant.PortraitId), + nameof(ArenaParticipantStruct.PortraitId), description: "Portrait icon id.", resolve: context => context.Source.PortraitId); Field>( - nameof(ArenaParticipant.Level), + nameof(ArenaParticipantStruct.Level), description: "Level of avatar.", resolve: context => context.Source.Level); Field>( - nameof(ArenaParticipant.NameWithHash), + nameof(ArenaParticipantStruct.NameWithHash), description: "Name of avatar.", resolve: context => context.Source.NameWithHash); } diff --git a/ArenaService/ArenaService/ArenaWorker.cs b/ArenaService/ArenaService/ArenaWorker.cs index 014b051..bc70fd3 100644 --- a/ArenaService/ArenaService/ArenaWorker.cs +++ b/ArenaService/ArenaService/ArenaWorker.cs @@ -62,6 +62,8 @@ public async Task PrepareArenaParticipants() var blockIndex = tip.Index; var currentRoundData = await _rpcClient.GetRoundData(tip, cancellationToken); var participants = await _rpcClient.GetArenaParticipantsState(tip, currentRoundData, cancellationToken); + var championshipId = currentRoundData.ChampionshipId; + var round = currentRoundData.Round; var cacheKey = $"{currentRoundData.ChampionshipId}_{currentRoundData.Round}"; var scoreCacheKey = $"{cacheKey}_scores"; var prevAddrAndScores = await _service.GetAvatarAddrAndScores(scoreCacheKey); @@ -69,7 +71,7 @@ public async Task PrepareArenaParticipants() var expiry = TimeSpan.FromMinutes(5); if (participants is null) { - await _service.SetArenaParticipantsAsync(cacheKey, new List(), expiry); + await _service.SetArenaParticipantsAsync(cacheKey, new List(), expiry); _logger.LogInformation("[ArenaParticipantsWorker] participants({CacheKey}) is null. set empty list on {BlockIndex}", cacheKey, blockIndex); return; } @@ -82,7 +84,7 @@ public async Task PrepareArenaParticipants() // 전체목록의 랭킹 순서 처리 var avatarAddrAndScoresWithRank = AvatarAddrAndScoresWithRank(avatarAddrAndScores); // 전체목록의 ArenaParticipant 업데이트 - var result = await _rpcClient.GetArenaParticipants(tip, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants, cancellationToken); + var result = await _rpcClient.GetArenaParticipants(tip, championshipId, round, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants, cancellationToken); // 캐시 업데이트 await _service.SetArenaParticipantsAsync(cacheKey, result, expiry); await _service.SetSeasonAsync(cacheKey, expiry); diff --git a/ArenaService/ArenaService/IRedisArenaParticipantsService.cs b/ArenaService/ArenaService/IRedisArenaParticipantsService.cs index 543d0cd..dd6f379 100644 --- a/ArenaService/ArenaService/IRedisArenaParticipantsService.cs +++ b/ArenaService/ArenaService/IRedisArenaParticipantsService.cs @@ -4,8 +4,8 @@ namespace ArenaService; public interface IRedisArenaParticipantsService { - Task> GetArenaParticipantsAsync(string key); - Task SetArenaParticipantsAsync(string key, List value, TimeSpan? expiry = null); + Task> GetArenaParticipantsAsync(string key); + Task SetArenaParticipantsAsync(string key, List value, TimeSpan? expiry = null); Task GetSeasonKeyAsync(); Task SetSeasonAsync(string value, TimeSpan? expiry = null); Task> GetAvatarAddrAndScores(string key); diff --git a/ArenaService/ArenaService/RedisArenaParticipantsService.cs b/ArenaService/ArenaService/RedisArenaParticipantsService.cs index 44721b4..45a86d2 100644 --- a/ArenaService/ArenaService/RedisArenaParticipantsService.cs +++ b/ArenaService/ArenaService/RedisArenaParticipantsService.cs @@ -11,18 +11,18 @@ public class RedisArenaParticipantsService(IConnectionMultiplexer redis) private readonly IDatabase _db = redis.GetDatabase(); - public async Task> GetArenaParticipantsAsync(string key) + public async Task> GetArenaParticipantsAsync(string key) { RedisValue result = await _db.StringGetAsync(key); if (result.IsNull) { - return new List(); + return new List(); } - return JsonSerializer.Deserialize>(result.ToString())!; + return JsonSerializer.Deserialize>(result.ToString())!; } - public async Task SetArenaParticipantsAsync(string key, List value, TimeSpan? expiry = null) + public async Task SetArenaParticipantsAsync(string key, List value, TimeSpan? expiry = null) { var serialized = JsonSerializer.Serialize(value); await _db.StringSetAsync(key, serialized, expiry); diff --git a/ArenaService/ArenaService/RpcClient.cs b/ArenaService/ArenaService/RpcClient.cs index 6abf44a..616a727 100644 --- a/ArenaService/ArenaService/RpcClient.cs +++ b/ArenaService/ArenaService/RpcClient.cs @@ -8,17 +8,12 @@ using Libplanet.Types.Blocks; using MagicOnion.Client; using Nekoyume; -using Nekoyume.Battle; -using Nekoyume.Helper; using Nekoyume.Model.Arena; -using Nekoyume.Model.EnumType; using Nekoyume.Model.Item; -using Nekoyume.Model.Stat; using Nekoyume.Model.State; using Nekoyume.Shared.Hubs; using Nekoyume.Shared.Services; using Nekoyume.TableData; -using Nekoyume.TableData.Rune; namespace ArenaService; @@ -285,41 +280,23 @@ public async Task> GetAvatarAddrAndScores(Block bloc /// Retrieve a list of arena participants based on the provided world state, avatar address list, and avatar addresses with scores and ranks. /// /// from which to retrieve the arena participants. + /// target arena season id + /// target arena season round /// The list of avatar addresses to filter the matching participants. /// The list of avatar addresses with their scores and ranks. - /// The list of previous synced arena participants. if the score has not changed, is reused. + /// The list of previous synced arena participants. if the score has not changed, is reused. /// /// A list of arena participants. - public async Task> GetArenaParticipants(Block block, List
avatarAddrList, - List avatarAddrAndScoresWithRank, List prevArenaParticipants, + public async Task> GetArenaParticipants(Block block, int championshipId, int round, List
avatarAddrList, + List avatarAddrAndScoresWithRank, List prevArenaParticipants, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } - var runeListSheet = await GetSheet(block, cancellationToken); - var costumeSheet = await GetSheet(block, cancellationToken); - var characterSheet = await GetSheet(block, cancellationToken); - var runeOptionSheet = await GetSheet(block, cancellationToken); - var runeLevelBonusSheet = await GetSheet(block, cancellationToken); - var row = characterSheet[GameConfig.DefaultAvatarCharacterId]; - CollectionSheet collectionSheet = new CollectionSheet(); - var collectionStates = await GetCollectionStates(block, avatarAddrList, cancellationToken); - bool collectionSheetExist = true; - try - { - collectionSheet = await GetSheet(block, cancellationToken); - } - catch (Exception) - { - collectionSheetExist = false; - } - var itemSlotStates = await GetItemSlotStates(block, avatarAddrList, cancellationToken); - var runeSlotStates = await GetRuneSlotStates(block, avatarAddrList, cancellationToken); - var avatarStates = await GetAvatarStates(block, avatarAddrList, cancellationToken); - var allRuneStates = await GetAllRuneStates(block, avatarAddrList, cancellationToken); + var arenaParticipantStates = await GetArenaParticipantStates(block, championshipId, round, avatarAddrList, cancellationToken); var tasks = avatarAddrAndScoresWithRank.Select(async tuple => { if (cancellationToken.IsCancellationRequested) @@ -330,68 +307,23 @@ public async Task> GetArenaParticipants(Block block, List // 점수가 변경된 경우, BattleArena를 실행한 아바타기때문에 전체 정보를 업데이트한다. if (avatarAddrList.Contains(avatarAddr)) { - if (!allRuneStates.TryGetValue(avatarAddr, out var runeStates)) - { - runeStates = await GetRuneState(block, avatarAddr, runeListSheet, cancellationToken); - } - var avatar = avatarStates[avatarAddr]; - var itemSlotState = itemSlotStates[avatarAddr]; - var runeSlotState = runeSlotStates[avatarAddr]; - - var equippedRuneStates = new List(); - foreach (var runeId in runeSlotState.GetRuneSlot().Select(slot => slot.RuneId)) - { - if (!runeId.HasValue) - { - continue; - } - - if (runeStates.TryGetRuneState(runeId.Value, out var runeState)) - { - equippedRuneStates.Add(runeState); - } - } - - var equipments = itemSlotState.Equipments - .Select(guid => - avatar.inventory.Equipments.FirstOrDefault(x => x.ItemId == guid)) - .Where(item => item != null).ToList(); - var costumes = itemSlotState.Costumes - .Select(guid => - avatar.inventory.Costumes.FirstOrDefault(x => x.ItemId == guid)) - .Where(item => item != null).ToList(); - var runeOptions = GetRuneOptions(equippedRuneStates, runeOptionSheet); - var collectionExist = collectionStates.ContainsKey(avatarAddr); - var collectionModifiers = new List(); - if (collectionSheetExist && collectionExist) - { - var collectionState = collectionStates[avatarAddr]; - foreach (var collectionId in collectionState.Ids) - { - collectionModifiers.AddRange(collectionSheet[collectionId].StatModifiers); - } - } - - var cp = CPHelper.TotalCP(equipments, costumes, runeOptions, avatar.level, row, costumeSheet, collectionModifiers, - RuneHelper.CalculateRuneLevelBonus(runeStates, runeListSheet, runeLevelBonusSheet) - ); - var portraitId = GetPortraitId(equipments, costumes); - return new ArenaParticipant( + var arenaParticipantState = arenaParticipantStates[avatarAddr]; + return new ArenaParticipantStruct( avatarAddr, tuple.Score, tuple.Rank, - avatar.NameWithHash, - avatar.level, - portraitId, + $"{arenaParticipantState.Name} #{avatarAddr.ToHex().Substring(0, 4)}", + arenaParticipantState.Level, + arenaParticipantState.PortraitId, 0, 0, - cp + arenaParticipantState.Cp ); } // 점수가 그대로인 경우, 순위만 변경한다. var prev = prevArenaParticipants.First(r => r.AvatarAddr == avatarAddr); - return new ArenaParticipant( + return new ArenaParticipantStruct( avatarAddr, tuple.Score, tuple.Rank, @@ -407,35 +339,6 @@ public async Task> GetArenaParticipants(Block block, List return result.ToList(); } - /// - /// Retrieves the collection states for the given addresses from the world state. - /// - /// The world state used to retrieve the collection states. - /// The list of addresses to retrieve the collection states for. - /// - /// A dictionary of Address and CollectionState pairs representing the collection states - /// for the given addresses, - /// or an empty dictionary for addresses that do not have a collection state. - public async Task> GetCollectionStates(Block block, IReadOnlyList
addresses, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - var result = new Dictionary(); - var values = await GetStates(block, Addresses.Collection, addresses, cancellationToken); - foreach (var address in addresses) - { - var serialized = values[address]; - if (serialized is List bencoded) - { - result.TryAdd(address, new CollectionState(bencoded)); - } - } - - return result; - } - /// /// Split and get separately by given chunkSize. /// @@ -466,34 +369,6 @@ public async Task> GetStates(Block block, Address ac return result.ToDictionary(kv => kv.Key, kv => kv.Value); } - public async Task GetRuneState(Block block, Address avatarAddress, RuneListSheet runeListSheet, - CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - var serialized = await GetState(block, Addresses.RuneState, avatarAddress, cancellationToken); - AllRuneState allRuneState; - if (serialized is null) - { - // Get legacy rune states - allRuneState = new AllRuneState(); - var runeAddresses = runeListSheet.Values.Select(r => RuneState.DeriveAddress(avatarAddress, r.Id)).ToList(); - var runeStates = await GetRuneStates(block, runeAddresses, cancellationToken); - foreach (var runeState in runeStates) - { - allRuneState.AddRuneState(runeState); - } - } - else - { - allRuneState = new AllRuneState((List)serialized); - } - - return allRuneState; - } - /// /// Get from account by block index. /// @@ -539,128 +414,30 @@ public async Task GetRuneState(Block block, Address avatarAddress, } /// - /// Retrieves the item slot states for the given avatar addresses from the world state. + /// Get from given arena season. /// - /// The world state used to retrieve the collection states. - /// The list of addresses to retrieve the item slot states for. - /// - /// A dictionary of Address and pairs representing the item slot states - /// for the given addresses. - public async Task> GetItemSlotStates(Block block, - IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - var result = new Dictionary(); - var slotAddresses = avatarAddresses.Select(a => ItemSlotState.DeriveAddress(a, BattleType.Arena)).ToList(); - var values = await GetStates(block, ReservedAddresses.LegacyAccount, slotAddresses, cancellationToken); - foreach (var address in avatarAddresses) - { - var slotAddress = ItemSlotState.DeriveAddress(address, BattleType.Arena); - var serialized = values[slotAddress]; - var itemSlotState = serialized is List bencoded - ? new ItemSlotState(bencoded) - : new ItemSlotState(BattleType.Arena); - result.TryAdd(address, itemSlotState); - } - return result; - } - - /// - /// Retrieves the rune slot states for the given avatar addresses from the world state. - /// - /// The world state used to retrieve the collection states. - /// The list of avatar addresses to retrieve the rune slot states for. + /// from which to retrieve the arena participants. + /// target arena season id + /// target arena season round + /// The list of avatar addresses to filter the matching participants. /// - /// A dictionary of Address and pairs representing the rune slot states - /// for the given addresses. - public async Task> GetRuneSlotStates(Block block, - IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - var result = new Dictionary(); - var slotAddresses = avatarAddresses.Select(a => RuneSlotState.DeriveAddress(a, BattleType.Arena)).ToList(); - var values = await GetStates(block, ReservedAddresses.LegacyAccount, slotAddresses, cancellationToken); - foreach (var address in avatarAddresses) - { - var slotAddress = RuneSlotState.DeriveAddress(address, BattleType.Arena); - var serialized = values[slotAddress]; - var runeSlotState = serialized is List bencoded - ? new RuneSlotState(bencoded) - : new RuneSlotState(BattleType.Arena); - result.TryAdd(address, runeSlotState); - } - return result; - } - - public async Task> GetAvatarStates(Block block, - IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - var avatarResults = await GetStates(block, Addresses.Avatar, avatarAddresses, cancellationToken); - var inventoryResults = await GetStates(block, Addresses.Inventory, avatarAddresses, cancellationToken); - var result = new Dictionary(); - foreach (var kv in avatarResults) - { - var address = kv.Key; - var avatarResult = kv.Value; - if (avatarResult is List l) - { - var avatarState = new AvatarState(l); - var inventory = inventoryResults[address] is List l2 - ? new Inventory(l2) - : new Inventory(); - avatarState.inventory = inventory; - result.TryAdd(address, avatarState); - } - } - return result; - } - - public async Task> GetRuneStates(Block block, IReadOnlyList
runeAddresses, - CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - var result = new List(); - var runeResults = await GetStates(block, ReservedAddresses.LegacyAccount, runeAddresses, cancellationToken); - foreach (var pair in runeResults) - { - if (pair.Value is List rawState) - { - var runeState = new RuneState(rawState); - result.Add(runeState); - } - } - return result; - } - - public async Task> GetAllRuneStates(Block block, - IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) + /// A dictionary of Address and pairs for the given addresses. + public async Task> GetArenaParticipantStates(Block block, int championshipId, int round, IReadOnlyList
avatarAddresses, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } - var serializedResults = await GetStates(block, Addresses.RuneState, avatarAddresses, cancellationToken); - var result = new Dictionary(); + var accountAddress = Addresses.GetArenaParticipantAccountAddress(championshipId, round); + var serializedResults = await GetStates(block, accountAddress, avatarAddresses, cancellationToken); + var result = new Dictionary(); foreach (var address in avatarAddresses) { var serialized = serializedResults[address]; if (serialized is List l) { - var allRuneState = new AllRuneState(l); - result.TryAdd(address, allRuneState); + var arenaParticipant = new ArenaParticipant(l); + result.TryAdd(address, arenaParticipant); } } return result; diff --git a/ArenaService/ArenaService/StateQuery.cs b/ArenaService/ArenaService/StateQuery.cs index 16c4436..52917ea 100644 --- a/ArenaService/ArenaService/StateQuery.cs +++ b/ArenaService/ArenaService/StateQuery.cs @@ -33,7 +33,7 @@ public StateQuery(IRedisArenaParticipantsService redisArenaParticipantsService) var currentAvatarAddr = context.GetArgument
("avatarAddress"); var filterBounds = context.GetArgument("filterBounds"); int playerScore = ArenaScore.ArenaScoreDefault; - List result = new(); + List result = new(); string cacheKey; try { @@ -68,8 +68,8 @@ public StateQuery(IRedisArenaParticipantsService redisArenaParticipantsService) ); } - public static List GetBoundsWithPlayerScore( - List arenaInformation, + public static List GetBoundsWithPlayerScore( + List arenaInformation, ArenaType arenaType, int playerScore) { From d59091ab5d347ed138a4ada239dd0cd7e128f7f9 Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Thu, 19 Dec 2024 23:46:38 +0900 Subject: [PATCH 6/6] Add LatestBattleBlockIndex on log --- ArenaService/ArenaService/ArenaWorker.cs | 6 +++--- ArenaService/ArenaService/RpcClient.cs | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ArenaService/ArenaService/ArenaWorker.cs b/ArenaService/ArenaService/ArenaWorker.cs index bc70fd3..a4a2eeb 100644 --- a/ArenaService/ArenaService/ArenaWorker.cs +++ b/ArenaService/ArenaService/ArenaWorker.cs @@ -84,13 +84,13 @@ public async Task PrepareArenaParticipants() // 전체목록의 랭킹 순서 처리 var avatarAddrAndScoresWithRank = AvatarAddrAndScoresWithRank(avatarAddrAndScores); // 전체목록의 ArenaParticipant 업데이트 - var result = await _rpcClient.GetArenaParticipants(tip, championshipId, round, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants, cancellationToken); + var tuple = await _rpcClient.GetArenaParticipants(tip, championshipId, round, updatedAddressAndScores.Select(i => i.AvatarAddr).ToList(), avatarAddrAndScoresWithRank, prevArenaParticipants, cancellationToken); // 캐시 업데이트 - await _service.SetArenaParticipantsAsync(cacheKey, result, expiry); + await _service.SetArenaParticipantsAsync(cacheKey, tuple.Item1, expiry); await _service.SetSeasonAsync(cacheKey, expiry); await _service.SetAvatarAddrAndScores(scoreCacheKey, avatarAddrAndScores, expiry); sw.Stop(); - _logger.LogInformation("[ArenaParticipantsWorker]Set Arena Cache[{CacheKey}] on {BlockIndex}: {Elapsed}", cacheKey, blockIndex, sw.Elapsed); + _logger.LogInformation("[ArenaParticipantsWorker]Set Arena Cache[{CacheKey}] on {BlockIndex}/{LatestBattleBlockIndex}: {Elapsed}", cacheKey, blockIndex, tuple.Item2, sw.Elapsed); } diff --git a/ArenaService/ArenaService/RpcClient.cs b/ArenaService/ArenaService/RpcClient.cs index 616a727..0e06ca5 100644 --- a/ArenaService/ArenaService/RpcClient.cs +++ b/ArenaService/ArenaService/RpcClient.cs @@ -286,8 +286,8 @@ public async Task> GetAvatarAddrAndScores(Block bloc /// The list of avatar addresses with their scores and ranks. /// The list of previous synced arena participants. if the score has not changed, is reused. /// - /// A list of arena participants. - public async Task> GetArenaParticipants(Block block, int championshipId, int round, List
avatarAddrList, + /// A list of arena participants and latest RankingBattle executed block index. + public async Task<(List, long)> GetArenaParticipants(Block block, int championshipId, int round, List
avatarAddrList, List avatarAddrAndScoresWithRank, List prevArenaParticipants, CancellationToken cancellationToken) { @@ -297,6 +297,9 @@ public async Task> GetArenaParticipants(Block block } var arenaParticipantStates = await GetArenaParticipantStates(block, championshipId, round, avatarAddrList, cancellationToken); + var latestBlockIndex = arenaParticipantStates.Count > 0 + ? arenaParticipantStates.Values.Max(i => i.LastBattleBlockIndex) + : 0L; var tasks = avatarAddrAndScoresWithRank.Select(async tuple => { if (cancellationToken.IsCancellationRequested) @@ -336,7 +339,7 @@ public async Task> GetArenaParticipants(Block block ); }).ToList(); var result = await Task.WhenAll(tasks); - return result.ToList(); + return (result.ToList(), latestBlockIndex); } ///