Below is the code that exactly run during the arena round. The outcome is determined by a consequtive Hashchain, that cannot be altered. You can verify that by solving the following equation:
sha512(ArenaRoundHash[n]) = ArenaRoundHash[n-1]
The sha512 of this ArenaRoundHash equals to the ArenaRoundHash of the previous arena round.
Once you have verified this, the ArenaRoundHash will be salted by all concatinated ParticipantsIDs, ordered ascending.
The SaltedArenaRoundHash could not be foreseen either by anyone: Neither the viewers, nor the developer, nor the streamer.
The SaltedArenaRoundHash will now be used to determine the whole outcome of the arena round. Below you can find the code that runs on every arena round.
For easier use, we have put everything in one file, so that it can be easily verified with any C# online compiler. Just hit "Copy full source for this round" and paste & run it in any C# compiler of your choice.
Hint: Make sure to use .NET Version 8 in order to compile correctly.
+
-
ProvenFairDebugger
using System.Security.Cryptography;
using System.Text;
class ProvenFairDebugger
{
static void Main(string[] args)
{
var arenaHash = "EDBA3D604E3B7820ECDE46DCEADB791BB0926D379DBF575E7A08683F6C3885326F04B28EE40712C1BD6C7F8B10EDB6276EBB290492A506EAC7E18956D6A8AC66".ToHex();
var previousHash = "981DD247354931B81D55F4312E329A692B13F23F993C7AD1B3904F6B38C98C8440FFFE9FC5786AE4FB9FE970A2E3725CEE135C248F1FB0CD4C97512FCCEE15F0";
var expectedPreviousHash = SHA512.Create().ComputeHash(arenaHash).ToHexString();
Console.WriteLine("----- Verifying hash chain. -----");
Console.WriteLine(" Based on this hash, the previous arena hash should be: ");
Console.WriteLine($" {expectedPreviousHash}");
Console.WriteLine("");
Console.WriteLine($" Previous hash was: ");
Console.WriteLine($" {previousHash}");
Console.WriteLine("");
var isValid = expectedPreviousHash == previousHash;
Console.WriteLine($" Verification result: {(isValid ? "valid" : "INVALID")} .");
Console.WriteLine("");
Console.WriteLine("");
var participants = new List<Participant>{
new Participant{Name="luebzi17", ID="183154657"},
new Participant{Name="doug_ha_punkt", ID="251136681"},
new Participant{Name="nuevovegaz", ID="425353781"},
new Participant{Name="the_bavarian_viking", ID="426443156"},
new Participant{Name="rimerio1", ID="479098948"},
new Participant{Name="renze1309", ID="480099244"},
new Participant{Name="hamster135555", ID="49843172"},
new Participant{Name="anubiskani", ID="534230949"},
new Participant{Name="zockgames21", ID="556401756"},
new Participant{Name="morgenfreeman69", ID="669276905"},
new Participant{Name="lepi___", ID="721697282"},
new Participant{Name="cosacala", ID="732489708"},
new Participant{Name="para_united", ID="740944056"},
new Participant{Name="mrs_michi_887", ID="807892718"},
new Participant{Name="sodarus1010", ID="873529569"},
new Participant{Name="phil24071997", ID="924309731"},
new Participant{Name="suchtitv2000", ID="943604363"},
new Participant{Name="kathleen1403hro", ID="984488306"},
};
var provenFair = new ProvenFair();
provenFair.ChainSeed = arenaHash;
provenFair.SetParticipants(participants);
var winner = provenFair.DrawWinner();
Console.WriteLine("----- Verifying winner. -----");
Console.WriteLine($" Winner is {winner.Name} [{winner.ID}]");
Console.WriteLine("");
Console.WriteLine("");
var outcome = ProvenFairGameWheel.GetGameWheelWinner(GameWheelDescriptor.GetGameWheelDescriptor(), provenFair.SaltedArenaSeed).Outcome;
Console.WriteLine("----- Verifying game result: -----");
Console.WriteLine($" Gamewheel outcome is: {outcome}.");
Console.WriteLine("");
switch (outcome)
{
case ArenaRoundGameOutcome.SpaceTumble:
Console.WriteLine($" Space tumble results in {new ProvenFairSpaceTumble().GetSpaceTumbleResult(provenFair.SaltedArenaSeed).WinAmount} points.");
break;
case ArenaRoundGameOutcome.ToTheMoon:
Console.WriteLine($" Rocket explodes at {ProvenFairToTheMoon.GetRocketExplodePoint(provenFair.SaltedArenaSeed)}");
break;
case ArenaRoundGameOutcome.Cases:
var assignments = ProvenFairSpaceCases.GetCaseValues(provenFair.SaltedArenaSeed);
foreach (var caseWinAssignment in assignments)
Console.WriteLine($" Case {caseWinAssignment.CaseNumber} has {caseWinAssignment.Winning} points");
var openingOrder = string.Join(", ", ProvenFairSpaceCases.GetOpeningOrder(provenFair.SaltedArenaSeed));
Console.WriteLine($" Cases will open in this order. {openingOrder}");
break;
}
}
}
// attack mode is triggered always on instant prices.
var attackMode = true;
if (outcome == ArenaRoundGameOutcome.Cases || outcome == ArenaRoundGameOutcome.SpaceTumble || outcome == ArenaRoundGameOutcome.ToTheMoon)
attackMode = ProvenFairAttackMode.IsAttackModeRound(provenFair.SaltedArenaSeed);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("----- Verifying attack mode: -----");
if (attackMode)
{
Console.WriteLine($" Attack mode is triggered.");
Console.WriteLine($" Chance needed to hit: {ProvenFairAttackMode.ChanceNeededToHit(provenFair.SaltedArenaSeed) * 100.0f:0.0#} %");
}
else
Console.WriteLine($" Attack mode is not triggered.");
Console.WriteLine("");
Console.WriteLine("");
}
}
+
-
ExtensionMethods
using System.Security.Cryptography;
public static class ProvenFairHelper
{
/// <summary>
/// Converts a hex string to byte array
/// </summary>
/// <param name="s">the string containing the hex representation of the byte array</param>
/// <returns></returns>
public static byte[] ToHex(this string s)
{
return Enumerable.Range(0, s.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(s.Substring(x, 2), 16))
.ToArray();
}
public static string ToHexString(this byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", "");
}
/// <summary>
/// Draws an element with fair equality from a list
/// </summary>
/// <typeparam name="T">Element type of the List</typeparam>
/// <param name="list">the list to draw from</param>
/// <param name="hash">initial hash to use for seeding.</param>
/// <returns></returns>
public static T Draw<T>(this List<T> list, byte[] hash)
{
var sha512 = SHA512.Create();
var rand = BitConverter.ToUInt64(hash);
var max = (ulong.MaxValue / (ulong)list.Count) * (ulong)list.Count;
// rehash for true equality.
while (rand >= max)
{
hash = sha512.ComputeHash(hash);
rand = BitConverter.ToUInt64(hash);
}
return list[(int)(rand % (ulong)list.Count)];
}
}
+
-
ProvenFair
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
[Serializable]
public class Participant
{
public string Name;
public string ID;
}
public class ProvenFair
{
public List<Participant> Participants => _participants.ToList();
public byte[] ChainSeed; // Next chain seed (unsalted)
public byte[] SaltedArenaSeed; // Chain seed salted with all participants
private List<Participant> _participants;
public void SetParticipants(List<Participant> participants)
{
_participants = participants.OrderBy(x => x.ID).ToList();
CalculateSaltedSeed();
}
private void CalculateSaltedSeed()
{
var salt = string.Join("", _participants.Select(x => x.ID).ToList());
SaltedArenaSeed = SHA512.Create().ComputeHash(ChainSeed.Concat(Encoding.UTF8.GetBytes(salt)).ToArray());
}
public Participant DrawWinner()
{
return _participants.Any() ? _participants.Draw(SaltedArenaSeed) : null;
}
}
+
-
GameWheel
using System.Security.Cryptography;
using System.Text;
public enum ArenaRoundGameOutcome
{
SpaceTumble,
ToTheMoon,
Cases,
InstantPriceSmall,
InstantPriceMedium,
InstantPriceLarge
}
public class WheelSegment
{
public int Share;
public ArenaRoundGameOutcome Outcome;
}
public class WheelDescriptor
{
public string ID;
public List<WheelSegment> WeightedSegments = new();
public List<WheelSegment> Segments = new();
public WheelDescriptor(string id)
{
ID = id;
}
public void Add(WheelSegment segment)
{
for (int i = 0; i < segment.Share; i++)
WeightedSegments.Add(segment);
Segments.Add(segment);
}
}
public static class GameWheelDescriptor
{
public static WheelDescriptor GetGameWheelDescriptor()
{
var result = new WheelDescriptor("GameWheel");
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.SpaceTumble
});
result.Add(new WheelSegment
{
Share = 26,
Outcome = ArenaRoundGameOutcome.InstantPriceSmall
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.ToTheMoon
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.Cases
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.SpaceTumble
});
result.Add(new WheelSegment
{
Share = 12,
Outcome = ArenaRoundGameOutcome.InstantPriceMedium
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.ToTheMoon
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.SpaceTumble
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.ToTheMoon
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.Cases
});
result.Add(new WheelSegment
{
Share = 2,
Outcome = ArenaRoundGameOutcome.InstantPriceLarge
});
result.Add(new WheelSegment
{
Share = 24,
Outcome = ArenaRoundGameOutcome.Cases
});
return result;
}
}
public static class ProvenFairGameWheel
{
public static WheelSegment GetGameWheelWinner(WheelDescriptor wheelDescriptor, byte[] saltedArenaHash)
{
var wheelSeed = SHA512.Create().ComputeHash(saltedArenaHash.Concat(Encoding.UTF8.GetBytes(wheelDescriptor.ID)).ToArray());
return wheelDescriptor.WeightedSegments.Draw(wheelSeed);
}
}
+
-
SpaceTumble
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class ProvenFairSpaceTumble
{
private byte[] _hash;
private SHA512 _sha;
public SpaceTumbleResult GetSpaceTumbleResult(byte[] saltedArenaSeed)
{
var accumulatedWin = 0;
_sha = SHA512.Create();
var result = new SpaceTumbleResult();
_hash = _sha.ComputeHash(saltedArenaSeed.Concat(Encoding.UTF8.GetBytes("spacetumble")).ToArray());
for (int r = 0; r < SpaceTumbleDescriptor.Rounds; r++)
{
var round = CalculateNextRound(accumulatedWin);
result.Rounds.Add(round);
accumulatedWin += round.WinAmount;
if (accumulatedWin >= SpaceTumbleDescriptor.MaxWin)
return result;
}
return result;
}
private SpaceTumbleRound CalculateNextRound(int winAmountBefore)
{
Rehash();
var gameState = new SpaceTumbleGameState(_hash);
var round = new SpaceTumbleRound();
round.InitialState = gameState;
while (true)
{
Rehash();
var iteration = new SpaceTumbleIteration(gameState, _hash);
gameState = iteration.GameStateAfterIteration;
round.Iterations.Add(iteration);
if (iteration.IsDeadEnd)
break;
if (round.WinAmount + winAmountBefore >= SpaceTumbleDescriptor.MaxWin)
break;
}
return round;
}
private void Rehash()
{
_hash = _sha.ComputeHash(_hash);
}
}
public class SpaceTumbleGameState
{
public SpaceTumbleItemID[,] Items = new SpaceTumbleItemID[6,6];
public int Multiplyer = 1;
public SpaceTumbleGameState(byte[] hash)
{
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
var amount = 3 + BitConverter.ToUInt32(hash, 40 + columnID) % 4;
var toSkip = (int)((SpaceTumbleDescriptor.Rows - amount) / 2.0);
for (int rowID = 0; rowID < SpaceTumbleDescriptor.Rows; rowID++)
{
toSkip--;
if (toSkip >= 0 || amount <= 0)
Items[rowID, columnID] = SpaceTumbleItemID.Blocker;
else
{
Items[rowID, columnID] = SpaceTumbleDescriptor.GetItemIDByRandomByte(hash[rowID * SpaceTumbleDescriptor.Rows + columnID]);
amount--;
}
}
}
}
private SpaceTumbleGameState(SpaceTumbleGameState source)
{
Items = source.Items.Clone() as SpaceTumbleItemID[,];
Multiplyer = source.Multiplyer;
}
public bool IsCleared
{
get
{
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
for (int rowID = 0; rowID < SpaceTumbleDescriptor.Rows; rowID++)
{
if (Items[rowID, columnID] == SpaceTumbleItemID.Blocker)
return false;
}
}
return true;
}
}
public static SpaceTumbleGameState Clone(SpaceTumbleGameState initialState)
{
return new SpaceTumbleGameState(initialState);
}
public List<SpaceTumbleItemID> GetColumn(int column)
{
var result = new List<SpaceTumbleItemID>();
for (int row = 0; row < SpaceTumbleDescriptor.Rows; row++)
result.Add(Items[row, column]);
return result;
}
public List<SpaceTumbleItemID> GetRow(int row)
{
var result = new List<SpaceTumbleItemID>();
for (int column = 0; column < SpaceTumbleDescriptor.Columns; column++)
result.Add(Items[row, column]);
return result;
}
}
public class SpaceTumbleRound
{
public SpaceTumbleGameState InitialState;
public List<SpaceTumbleIteration> Iterations = new();
public int WinAmount => Iterations.Sum(x => x.Rewards.Sum(r => r.PointsWon));
}
[Serializable]
public class SpaceTumbleIteration
{
/// <summary>
/// The game state before this iteration
/// </summary>
public SpaceTumbleGameState InitialState;
/// <summary>
/// Tells the game which element should get dissolved.
/// </summary>
public bool[,] DissolveMatrix;
/// <summary>
/// Tells the game, which element should drop how many rows.
/// </summary>
public ushort[,] GravityMatrix;
/// <summary>
/// Tells the game which elements should drop.
/// </summary>
public SpaceTumbleItemID[,] RedropMatrix;
/// <summary>
/// The game state after this iteration.
/// </summary>
public SpaceTumbleGameState GameStateAfterIteration;
/// <summary>
/// Describes the Rewards in Points for this iteration.
/// </summary>
public List<SpaceTumbleDropReward> Rewards { get; private set; } = new List<SpaceTumbleDropReward>();
private Dictionary<SpaceTumbleItemID, int> _multipliers = new()
{
{ SpaceTumbleItemID.Slot1, 2 },
{ SpaceTumbleItemID.Slot2, 4 },
{ SpaceTumbleItemID.Slot3, 7 },
{ SpaceTumbleItemID.Slot4, 11 },
{ SpaceTumbleItemID.Slot5, 20 },
{ SpaceTumbleItemID.Slot6, 40 },
{ SpaceTumbleItemID.Slot7, 70 },
{ SpaceTumbleItemID.Slot8, 100 },
};
public bool IsDeadEnd
{
get
{
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
for (int rowId = 0; rowId < SpaceTumbleDescriptor.Rows; rowId++)
if (DissolveMatrix[rowId, columnID])
return false;
return true;
}
}
public SpaceTumbleIteration(SpaceTumbleGameState gameState, byte[] bytes)
{
InitialState = gameState;
CalculateMatches(gameState);
CalculateGravityMatrix(gameState);
CalculateDropResult(bytes);
CalculateGameStateAfterIteration();
}
private void CalculateMatches(SpaceTumbleGameState gameState)
{
DissolveMatrix = new bool[6, 6];
var activeItems = gameState.GetColumn(0).Where(x => x != SpaceTumbleItemID.Blocker).Distinct().ToList();
foreach (var activeItem in activeItems)
{
var currentDissolveMatrix = DissolveMatrix.Clone() as bool[,];
var columnCount = 0;
var ways = 1;
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
var amount = 0;
for (int rowID = 0; rowID < SpaceTumbleDescriptor.Rows; rowID++)
{
if (gameState.Items[rowID, columnID] != activeItem)
continue;
currentDissolveMatrix[rowID, columnID] = true;
try { currentDissolveMatrix[rowID, columnID - 1] = currentDissolveMatrix[rowID, columnID - 1] || gameState.Items[rowID, columnID - 1] == SpaceTumbleItemID.Blocker; } catch { }
try { currentDissolveMatrix[rowID, columnID + 1] = currentDissolveMatrix[rowID, columnID + 1] || gameState.Items[rowID, columnID + 1] == SpaceTumbleItemID.Blocker; } catch { }
try { currentDissolveMatrix[rowID - 1, columnID] = currentDissolveMatrix[rowID - 1, columnID] || gameState.Items[rowID - 1, columnID] == SpaceTumbleItemID.Blocker; } catch { }
try { currentDissolveMatrix[rowID + 1, columnID] = currentDissolveMatrix[rowID + 1, columnID] || gameState.Items[rowID + 1, columnID] == SpaceTumbleItemID.Blocker; } catch { }
amount++;
}
if (amount == 0)
break;
columnCount += 1;
ways *= amount;
}
if (columnCount >= 3)
{
Rewards.Add(new SpaceTumbleDropReward
{
ItemIDToClear = activeItem,
PointsWon = ColumCountToMultiplier(columnCount) * ways * _multipliers[activeItem] * gameState.Multiplyer
});
DissolveMatrix = currentDissolveMatrix;
}
}
}
private int ColumCountToMultiplier(int columnCount)
{
if (columnCount <= 3)
return 6;
if (columnCount <= 4)
return 7;
if (columnCount <= 5)
return 8;
return 12;
}
private void CalculateGravityMatrix(SpaceTumbleGameState gameState)
{
GravityMatrix = new ushort[6, 6];
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
for (int rowID = 0; rowID < SpaceTumbleDescriptor.Rows; rowID++)
{
if (DissolveMatrix[rowID, columnID])
continue;
for (int rowBelow = rowID + 1; rowBelow < SpaceTumbleDescriptor.Rows; rowBelow++)
if (DissolveMatrix[rowBelow, columnID])
GravityMatrix[rowID, columnID]++;
}
}
}
public void CalculateDropResult(byte[] hash)
{
RedropMatrix = new SpaceTumbleItemID[6, 6];
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
var amountToDrop = 0;
for (int rowId = 0; rowId < SpaceTumbleDescriptor.Rows; rowId++)
{
RedropMatrix[rowId, columnID] = SpaceTumbleItemID.Blocker;
if (DissolveMatrix[rowId, columnID])
amountToDrop++;
}
for (int rowId = 0; rowId < amountToDrop; rowId++)
RedropMatrix[rowId, columnID] = SpaceTumbleDescriptor.GetItemIDByRandomByte(hash[rowId * SpaceTumbleDescriptor.Rows + columnID]);
}
}
private void CalculateGameStateAfterIteration()
{
GameStateAfterIteration = SpaceTumbleGameState.Clone(InitialState);
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
for (int rowId = 0; rowId < SpaceTumbleDescriptor.Rows; rowId++)
{
var rowsToDrop = GravityMatrix[rowId, columnID];
if (rowsToDrop > 0)
GameStateAfterIteration.Items[rowId + rowsToDrop, columnID] = InitialState.Items[rowId, columnID];
}
}
for (int columnID = 0; columnID < SpaceTumbleDescriptor.Columns; columnID++)
{
for (int rowId = 0; rowId < SpaceTumbleDescriptor.Rows; rowId++)
{
var id = RedropMatrix[rowId, columnID];
if (id != SpaceTumbleItemID.Blocker)
GameStateAfterIteration.Items[rowId, columnID] = id;
}
}
if (GameStateAfterIteration.IsCleared && GameStateAfterIteration.Multiplyer < 1024)
GameStateAfterIteration.Multiplyer *= 2;
}
}
public class SpaceTumbleReward
{
public int PointsWon;
}
public class SpaceTumbleResult
{
public List<SpaceTumbleRound> Rounds = new();
private int _actualWin => Rounds.Sum(x => x.WinAmount);
public int WinAmount => Math.Clamp(_actualWin, SpaceTumbleDescriptor.MinWin, SpaceTumbleDescriptor.MaxWin);
public bool IsMinWin => _actualWin < SpaceTumbleDescriptor.MinWin;
public bool IsMaxWin => _actualWin >= SpaceTumbleDescriptor.MaxWin;
}
public static class SpaceTumbleDescriptor
{
public const int Rounds = 10;
public const int Columns = 6;
public const int Rows = 6;
public static int MaxWin = 250000;
public static int MinWin = 10000;
public static SpaceTumbleItemID GetItemIDByRandomByte(byte b)
{
if (b < 32)
return SpaceTumbleItemID.Slot1;
if (b < 64)
return SpaceTumbleItemID.Slot2;
if (b < 96)
return SpaceTumbleItemID.Slot3;
if (b < 128)
return SpaceTumbleItemID.Slot4;
if (b < 160)
return SpaceTumbleItemID.Slot5;
if (b < 192)
return SpaceTumbleItemID.Slot6;
if (b < 224)
return SpaceTumbleItemID.Slot7;
return SpaceTumbleItemID.Slot8;
}
}
public struct SpaceTumbleItem
{
public int Column;
public int Row;
public SpaceTumbleItemID ID;
}
public enum SpaceTumbleItemID
{
Blocker = -1,
Slot1,
Slot2,
Slot3,
Slot4,
Slot5,
Slot6,
Slot7,
Slot8
}
public class SpaceTumbleDropReward
{
public SpaceTumbleItemID ItemIDToClear;
public int PointsWon;
}
+
-
SpaceCases
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class CaseWinAssignment
{
public int CaseNumber { get; set; }
public int Winning { get; set; }
}
public class ProvenFairSpaceCases
{
public static readonly List<int> CaseWinnings = new()
{
1000,
1500,
2000,
3000,
5000,
7500,
10000,
15000,
20000,
30000,
40000,
50000,
75000,
100000
};
public List<CaseWinAssignment> GetCaseValues(byte[] saltedArenaSeed)
{
var winnings = CaseWinnings.ToList();
var hash = SHA512.Create().ComputeHash(saltedArenaSeed.Concat(Encoding.UTF8.GetBytes("cases")).ToArray());
var result = new List<CaseWinAssignment>();
for (int caseNumber = 1; caseNumber <= CaseWinnings.Count; caseNumber++)
{
var winning = winnings[(int)(BitConverter.ToUInt32(hash, caseNumber * 4) % winnings.Count)];
result.Add(new CaseWinAssignment
{
CaseNumber = caseNumber,
Winning = winning
});
winnings.Remove(winning);
}
return result;
}
public List<int> GetOpeningOrder(byte[] saltedArenaSeed)
{
var caseNumbers = Enumerable.Range(1, 14).ToList();
var hash = SHA512.Create().ComputeHash(saltedArenaSeed.Concat(Encoding.UTF8.GetBytes("cases")).ToArray());
var result = new List<int>();
var amount = caseNumbers.Count();
for (int caseNumber = 1; caseNumber <= amount; caseNumber++)
{
var numberToOpen = caseNumbers[(int)(BitConverter.ToUInt32(hash, caseNumber * 4) % caseNumbers.Count)];
result.Add(numberToOpen);
caseNumbers.Remove(numberToOpen);
}
return result;
}
}
+
-
ToTheMoon
using System.Security.Cryptography;
using System.Text;
public class ProvenFairToTheMoon
{
public int MaxRocketWin = 1000000;
public int MinRocketWin = 10000;
public int GetRocketExplodePoint(byte[] saltedArenaSeed)
{
var hash = SHA512.Create().ComputeHash(saltedArenaSeed.Concat(Encoding.UTF8.GetBytes("rocket")).ToArray());
var numerator = BitConverter.ToUInt32(hash, 0);
var denominator = (float)uint.MaxValue;
if (numerator == 0)
return MaxRocketWin;
var rand = numerator / denominator;
return Math.Clamp((int)(1 / rand * 10000.0), MinRocketWin, MaxRocketWin);
}
public int GetPointsByPercentage(float percentage)
{
return Math.Clamp((int)(1.0f / (1.0f - percentage) * 10000.0), MinRocketWin, MaxRocketWin);
}
}
+
-
AttackMode
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
public class ProvenFairGameWheel
{
public WheelSegment GetGameWheelWinner(WheelDescriptor wheelDescriptor, byte[] saltedArenaHash)
{
var wheelSeed = SHA512.Create().ComputeHash(saltedArenaHash.Concat(Encoding.UTF8.GetBytes(wheelDescriptor.ID)).ToArray());
var winner = (int)(BitConverter.ToUInt32(wheelSeed, 0) % wheelDescriptor.WeightedSegments.Count);
return wheelDescriptor.WeightedSegments[winner];
}
}
+
-
Giveaway
using System;
using System.Collections.Generic;
using System.Linq;
[Serializable]
public class GiveawaySettings
{
public int AmountWinners;
public List<WeightedParticipant> Participants;
}
[Serializable]
public class WeightedParticipant
{
public string Name;
public int Tickets;
}
[Serializable]
public class Winner
{
public Participant Participant;
public int Place;
}
public static class ProvenFairGiveaway
{
/// <summary>
/// Returns a Dictionary with the key being the place and the value the name of the winner.
/// </summary>
/// <param name="saltedArenaSeed">The already salted arena seed</param>
/// <param name="giveawaySettings">The settings for the giveaway to apply.</param>
/// <returns></returns>
public static List<Winner> DrawWinners(byte[] saltedArenaSeed, GiveawaySettings giveawaySettings)
{
var weighted = new List<string>();
var order = giveawaySettings.Participants.OrderBy(x => x.Name.ToLower());
foreach (var weightedParticipant in order)
for (int i = 0; i < weightedParticipant.Tickets; i++)
weighted.Add(weightedParticipant.Name);
var result = new List<Winner>();
for (int place = 1; place <= giveawaySettings.AmountWinners; place++)
{
var winnerName = weighted.Draw(saltedArenaSeed);
result.Add(new Winner
{
Participant = new Participant { ID = "unknown", Name = winnerName},
Place = place
});
weighted.RemoveAll(x => x == winnerName);
}
return result;
}
}