Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Arena GQL Simulator - Win % calculator #2084

Open
wants to merge 8 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions NineChronicles.Headless/GraphTypes/Abstractions/LocalRandom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Libplanet.Action;
using System;

namespace NineChronicles.Headless.GraphTypes
{
internal class LocalRandom : System.Random, IRandom
{
public int Seed
{
get
{
throw new NotImplementedException();
}
}

public LocalRandom(int seed)
: base(seed)
{
}
}
}
188 changes: 188 additions & 0 deletions NineChronicles.Headless/GraphTypes/SimulationQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Bencodex.Types;
using GraphQL;
using GraphQL.Types;
using Libplanet;
using Libplanet.Explorer.GraphTypes;
using Nekoyume.Action;
using Nekoyume.Arena;
using Nekoyume.Extensions;
using Nekoyume.Model;
using Nekoyume.Model.EnumType;
using Nekoyume.Model.State;
using Nekoyume.TableData;
using NineChronicles.Headless.GraphTypes.States;


namespace NineChronicles.Headless.GraphTypes
{
public class SimultionQuery : ObjectGraphType<StateContext>
{
public SimultionQuery()
{
Name = "SimultionQuery";

Field<NonNullGraphType<ArenaSimulationStateType>>(
name: "arenaPercentageCalculator",
description: "State for championShip arena.",
arguments: new QueryArguments(
new QueryArgument<NonNullGraphType<AddressType>>
{
Name = "avatarAddress",
Description = "Avatar address."
},
new QueryArgument<NonNullGraphType<AddressType>>
{
Name = "enemyAvatarAddress",
Description = "Enemy Avatar address."
},
new QueryArgument<NonNullGraphType<IntGraphType>>
{
Name = "simulationCount",
Description = "Amount of simulations, between 1 and 1000"
}
),
resolve: context =>
{
Address myAvatarAddress = context.GetArgument<Address>("avatarAddress");
Address enemyAvatarAddress = context.GetArgument<Address>("enemyAvatarAddress");
int simulationCount = context.GetArgument<int>("simulationCount");

var sheets = context.Source.GetSheets(sheetTypes: new[]
{
typeof(ArenaSheet),
typeof(CostumeStatSheet),
typeof(ItemRequirementSheet),
typeof(EquipmentItemRecipeSheet),
typeof(EquipmentItemSubRecipeSheetV2),
typeof(EquipmentItemOptionSheet),
typeof(RuneListSheet),
typeof(MaterialItemSheet),
typeof(SkillSheet),
typeof(SkillBuffSheet),
typeof(StatBuffSheet),
typeof(SkillActionBuffSheet),
typeof(ActionBuffSheet),
typeof(CharacterSheet),
typeof(CharacterLevelSheet),
typeof(EquipmentItemSetEffectSheet),
typeof(WeeklyArenaRewardSheet),
typeof(RuneOptionSheet),
});

if(simulationCount < 1 || simulationCount > 1000)
{
throw new Exception("arenaPercentageCalculator - Invalid simulationCount");
}

var myAvatar = context.Source.GetAvatarStateV2(myAvatarAddress);
var enemyAvatar = context.Source.GetAvatarStateV2(enemyAvatarAddress);

//sheets
var arenaSheets = sheets.GetArenaSimulatorSheets();
var characterSheet = sheets.GetSheet<CharacterSheet>();

if (!characterSheet.TryGetValue(myAvatar.characterId, out var characterRow) || !characterSheet.TryGetValue(enemyAvatar.characterId, out var characterRow2))
{
throw new SheetRowNotFoundException("CharacterSheet", myAvatar.characterId);
}

//MyAvatar
var myArenaAvatarStateAdr = ArenaAvatarState.DeriveAddress(myAvatarAddress);
var myArenaAvatarState = context.Source.GetArenaAvatarState(myArenaAvatarStateAdr, myAvatar);
var myAvatarEquipments = myAvatar.inventory.Equipments;
var myAvatarCostumes = myAvatar.inventory.Costumes;
List<Guid> myArenaEquipementList = myAvatarEquipments.Where(f=>myArenaAvatarState.Equipments.Contains(f.ItemId)).Select(n => n.ItemId).ToList();
List<Guid> myArenaCostumeList = myAvatarCostumes.Where(f=>myArenaAvatarState.Costumes.Contains(f.ItemId)).Select(n => n.ItemId).ToList();

var myRuneSlotStateAddress = RuneSlotState.DeriveAddress(myAvatarAddress, BattleType.Arena);
var myRuneSlotState = context.Source.TryGetState(myRuneSlotStateAddress, out List myRawRuneSlotState)
? new RuneSlotState(myRawRuneSlotState)
: new RuneSlotState(BattleType.Arena);

var myRuneStates = new List<RuneState>();
var myRuneSlotInfos = myRuneSlotState.GetEquippedRuneSlotInfos();
foreach (var address in myRuneSlotInfos.Select(info => RuneState.DeriveAddress(myAvatarAddress, info.RuneId)))
{
if (context.Source.TryGetState(address, out List rawRuneState))
{
myRuneStates.Add(new RuneState(rawRuneState));
}
}

//Enemy
var enemyArenaAvatarStateAdr = ArenaAvatarState.DeriveAddress(enemyAvatarAddress);
var enemyArenaAvatarState = context.Source.GetArenaAvatarState(enemyArenaAvatarStateAdr, enemyAvatar);
var enemyAvatarEquipments = enemyAvatar.inventory.Equipments;
var enemyAvatarCostumes = enemyAvatar.inventory.Costumes;
List<Guid> enemyArenaEquipementList = enemyAvatarEquipments.Where(f=>enemyArenaAvatarState.Equipments.Contains(f.ItemId)).Select(n => n.ItemId).ToList();
List<Guid> enemyArenaCostumeList = enemyAvatarCostumes.Where(f=>enemyArenaAvatarState.Costumes.Contains(f.ItemId)).Select(n => n.ItemId).ToList();

var enemyRuneSlotStateAddress = RuneSlotState.DeriveAddress(enemyAvatarAddress, BattleType.Arena);
var enemyRuneSlotState = context.Source.TryGetState(enemyRuneSlotStateAddress, out List enemyRawRuneSlotState)
? new RuneSlotState(enemyRawRuneSlotState)
: new RuneSlotState(BattleType.Arena);

var enemyRuneStates = new List<RuneState>();
var enemyRuneSlotInfos = enemyRuneSlotState.GetEquippedRuneSlotInfos();
foreach (var address in enemyRuneSlotInfos.Select(info => RuneState.DeriveAddress(enemyAvatarAddress, info.RuneId)))
{
if (context.Source.TryGetState(address, out List rawRuneState))
{
enemyRuneStates.Add(new RuneState(rawRuneState));
}
}

var myArenaPlayerDigest = new ArenaPlayerDigest(
myAvatar,
myArenaEquipementList,
myArenaCostumeList,
myRuneStates);

var enemyArenaPlayerDigest = new ArenaPlayerDigest(
enemyAvatar,
enemyArenaEquipementList,
enemyArenaCostumeList,
enemyRuneStates);

Random rnd =new Random();

int win = 0;
int loss = 0;

List<ArenaSimulationResult> arenaResultsList = new List<ArenaSimulationResult>();
ArenaSimulationState arenaSimulationState = new ArenaSimulationState();
arenaSimulationState.blockIndex = context.Source.BlockIndex;

for (var i = 0; i < simulationCount; i++)
{
ArenaSimulationResult arenaResult = new ArenaSimulationResult();
arenaResult.seed = rnd.Next();
LocalRandom iRandom = new LocalRandom(arenaResult.seed);
var simulator = new ArenaSimulator(iRandom);
var log = simulator.Simulate(
myArenaPlayerDigest,
enemyArenaPlayerDigest,
arenaSheets);
if(log.Result.ToString() == "Win")
{
arenaResult.win = true;
win++;
}
else
{
loss++;
arenaResult.win = false;
}
arenaResultsList.Add(arenaResult);
}
arenaSimulationState.winPercentage = Math.Round(((decimal)win / simulationCount) * 100m, 2);
arenaSimulationState.result = arenaResultsList;
return arenaSimulationState;
});
}
}
}
29 changes: 29 additions & 0 deletions NineChronicles.Headless/GraphTypes/StandaloneQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,36 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi
);
}
);
Field<NonNullGraphType<SimultionQuery>>(name: "simulationQuery", arguments: new QueryArguments(
new QueryArgument<ByteStringType>
{
Name = "hash",
Description = "Offset block hash for query.",
}),
resolve: context =>
{
BlockHash? blockHash = context.GetArgument<byte[]>("hash") switch
{
byte[] bytes => new BlockHash(bytes),
null => standaloneContext.BlockChain?.Tip?.Hash,
};

if (!(standaloneContext.BlockChain is { } chain))
{
return null;
}

return new StateContext(
chain.ToAccountStateGetter(blockHash),
chain.ToAccountBalanceGetter(blockHash),
blockHash switch
{
BlockHash bh => chain[bh].Index,
null => chain.Tip.Index,
}
);
}
);
Field<ByteStringType>(
name: "state",
arguments: new QueryArguments(
Expand Down
6 changes: 4 additions & 2 deletions NineChronicles.Headless/GraphTypes/StateQuery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
using Libplanet.Explorer.GraphTypes;
using Nekoyume;
using Nekoyume.Action;
using Nekoyume.Arena;
using Nekoyume.Extensions;
using Nekoyume.Model;
using Nekoyume.Model.EnumType;
using Nekoyume.Model.Item;
using Nekoyume.Model.State;
using Nekoyume.TableData;
Expand Down Expand Up @@ -346,7 +349,6 @@ public StateQuery()
return null;
}
);

Field<ListGraphType<IntGraphType>>(
"unlockedWorldIds",
description: "List of unlocked world sheet row ids.",
Expand Down Expand Up @@ -514,7 +516,7 @@ public StateQuery()
}

return null;
});
});
}
}
}
13 changes: 13 additions & 0 deletions NineChronicles.Headless/GraphTypes/States/ArenaSimulationResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Libplanet;
using System;
using System.Collections.Generic;

namespace NineChronicles.Headless.GraphTypes.States
{
public class ArenaSimulationResult
{
public int seed { get; set; }
public bool win { get; set; }

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using GraphQL.Types;
using Libplanet.Explorer.GraphTypes;
using Nekoyume.Model.State;

namespace NineChronicles.Headless.GraphTypes.States
{
internal class ArenaSimulationResultType : ObjectGraphType<ArenaSimulationResult>
{
public ArenaSimulationResultType()
{
Field<NonNullGraphType<IntGraphType>>(
nameof(ArenaSimulationResult.seed),
resolve: context => context.Source.seed);
Field<NonNullGraphType<BooleanGraphType>>(
nameof(ArenaSimulationResult.win),
resolve: context => context.Source.win);
}
}
}
13 changes: 13 additions & 0 deletions NineChronicles.Headless/GraphTypes/States/ArenaSimulationState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Libplanet;
using System;
using System.Collections.Generic;

namespace NineChronicles.Headless.GraphTypes.States
{
public class ArenaSimulationState
{
public long? blockIndex { get; set; }
public List<ArenaSimulationResult>? result { get; set; }
public decimal winPercentage { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using GraphQL.Types;
using Libplanet.Explorer.GraphTypes;
using Nekoyume.Model.State;

namespace NineChronicles.Headless.GraphTypes.States
{
public class ArenaSimulationStateType : ObjectGraphType<ArenaSimulationState>
{
public ArenaSimulationStateType()
{
Field<NonNullGraphType<IntGraphType>>(
nameof(ArenaSimulationState.blockIndex),
description: "Block Index",
resolve: context => context.Source.blockIndex);
Field<NonNullGraphType<ListGraphType<ArenaSimulationResultType>>>(
nameof(ArenaSimulationState.result),
description: "Simulation Result",
resolve: context => context.Source.result);
Field<NonNullGraphType<DecimalGraphType>>(
nameof(ArenaSimulationState.winPercentage),
description: "Win percentage",
resolve: context => context.Source.winPercentage);
}
}
}
Loading