生成最短的字母数字保存代码
Generate the shortest alphanumeric save code
出于游戏的目的,我需要生成一个保存代码,用户可以在某处记下该代码,并在以后用于重新加载他的游戏状态(不可能有持久数据)。
保存代码需要像 6DZF1D3
一样短(一个 base 36 或 base 62 字符串)。
游戏关卡分数可以简化为 string
,例如 1232312321321321321,其中每个字符都是 "stars" 中的一个关卡分数(1、2 或 3 星)。
将有大约 30 个游戏关卡。
我想为用户生成尽可能短的代码,所以我的第一个想法是在数组中生成所有可能性。然后生成用户所在key的base 62编码。但是有 3^30 种可能性,这将生成一个具有 2e+14 key/values 的数组,这对内存和 CPU.
不利
第二个想法是使用 base 4 到 62 转换器,但我发现的大多数代码都使用 int
或 long
,它们的大小有限且少于 30 个字符。
您知道如何生成由字母数字字符组成的最短保存代码吗?
将二进制数据转换为文本表示的最常见方法是 Base64。每个字符代表 6 位信息。你只有不到 48 位的信息,这很好地让你得到 8 个 Base64 数字。
所以策略是:
1. 使用 this algorithm.
将 3 进制(星形)数组转换为 2 进制
2.将位转换为字节数组using Convert.ToByte();
3. 使用Convert.ToBase64String()创建一个Base64字符串。
编辑: 我知道你想把它放在 Base36 中,there are some code examples that can do it. 此代码需要一个字符串作为输入,但将其转换为 char[]
,因此您可以只提供 ByteArray。
编辑2:
证明是在吃,刚刚为任何基地创建了一个来回转换器,直到 base36(但可以扩展)。对于您的星星,您只需提供一个字符串,其中星星值作为数字(1 到 3)。
private static string ConvertToOtherBase(string toConvert, int fromBase, int toBase)
{
const string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
long value = 0;
string result = "";
foreach (char digit in toConvert.ToCharArray())
value = (value * fromBase) + characters.IndexOf(digit);
while (value > 0)
{
result = characters[(int)(value % toBase)] + result;
value /= toBase;
}
return result;
}
你可以这样称呼它(来回):
var stars = "112131121311213112131121311213";
string base36Result = ConvertToOtherBase(stars, 4, 36);
// 32NSB7MBR9T3
string base4Result = ConvertToOtherBase(base36Result, 36, 4);
// 112131121311213112131121311213
当然,这个问题是基于意见的,但这里有一种简单的保存方法
创建对象
public class Memento
{
public int Id {get; set;}
public int Level {get; set;}
public int Score {get; set;}
}
然后只需使用 Newtonsoft.Json
库对其进行序列化。最重要的是,你可以加密序列化 JSON 这样用户就看不到保存数据的内部,并将其写入磁盘。但是,当然,有很多方法可以保留分数。顺便说一下,我的名字 class 应该会为您指出一个专门解决这个问题的编程模式
更新
正在阅读您的评论 - 这是您要找的吗?
int x = 5, y = 10;
byte[]xb = BitConverter.GetBytes(x);
var enumer = xb.Concat(BitConverter.GetBytes(y));
string outStr = Convert.ToBase64String(enumer.ToArray());
Console.WriteLine(outStr);
// your code: BQAAAAoAAAA=
顺便说一句,如果你使用 int16,你的代码会更短:BQAKAA==
byte[] back = Convert.FromBase64String(outStr);
short a = BitConverter.ToInt16(back, 0);
short b = BitConverter.ToInt16(back, 2);
Console.WriteLine(a + "_" + b);
如果用户应该能够将其写下来,我更喜欢 Base58 编码。因此,对于每个级别 1-3 个可能的星星,我们需要 2 位来对每个级别进行编码。
00 => 0 star (would mean last unplayed level reached)
01 => 1 star
10 => 2 stars
11 => 3 stars
我们需要 60 位来表示 30 个级别,所有具有 3 颗星的级别都是十进制的 1152921504606846975。这个,base58 编码,将是 3gDmDv6tjHG,不会太长吧?!
更新:
@DrNootNoot 很高兴您找到了解决问题的方法!但是我很高兴为我提到的 base58 版本破解一小段代码。我改编了您使用的 Pavel Vladov 的两个功能。
也许有一天其他人也会遇到类似的问题:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] scoreArray = new string[30] { "1", "2", "3", "3", "1", "2", "2", "2", "3", "1", "1", "1", "2", "3", "2", "1", "2", "3", "1", "1", "1", "2", "2", "2", "1", "1", "2", "1", "2","3" };
ulong numScore = ScoreToDecimal(scoreArray);
string saveScore = UDecimalToBase58String(numScore);
Console.WriteLine("Score array: " + String.Join("-",scoreArray));
Console.WriteLine("Numeric score: " + Convert.ToString(numScore));
Console.WriteLine("Base58 score: " + saveScore);
ulong numScoreRestored = Base58StringToUDecimal(saveScore);
string[] scoreArrayRestored = DecimalToScore(numScoreRestored);
Console.WriteLine("From Base58 converted numeric score: " + Convert.ToString(numScoreRestored));
Console.WriteLine("From Base58 converted score array: " + String.Join("-", scoreArray));
Console.Read();
}
/// <summary>
/// Converts the stars-per-level array to a decimal value for the saved game.
/// </summary>
/// <param name="score">score array to convert. Max. 32 entries/levels.</param>
/// <returns></returns>
public static ulong ScoreToDecimal(string[] score)
{
int arrLength = score.Length;
if (arrLength > 32)
throw new ArgumentException("The score array must not be larger than 32 entries");
ulong result = 0;
for (int i = arrLength - 1; i >= 0; i--)
{
ulong singleScore = Convert.ToUInt64(score[i]);
if (singleScore > 3)
throw new ArgumentException(String.Format("Invalid score value. Max. allowed value is 3, but {0} was given at index {1}", singleScore, i), "score");
result += (singleScore << ((arrLength - 1 - i) * 2));
}
return result;
}
/// <summary>
/// Converts the decimal value of the saved game back to a stars-per-level array.
/// </summary>
/// <param name="decimalScore">Maximal 64-bit unsigned saved game number to convert.</param>
/// <returns></returns>
public static string[] DecimalToScore(ulong decimalScore)
{
List<string> result = new List<string>();
while(decimalScore > 0)
{
result.Add(Convert.ToString(decimalScore % 4));
decimalScore /= 4;
}
result.Reverse();
return result.ToArray();
}
/// <summary>
/// Adapted Unsigned-Base58-Version of Pavel Vladovs DecimalToArbitrarySystem function.
/// See: https://www.pvladov.com/2012/05/decimal-to-arbitrary-numeral-system.html
/// </summary>
/// <param name="decimalNumber"></param>
/// <returns></returns>
public static string UDecimalToBase58String(ulong decimalNumber)
{
const int BitsInLong = 64;
const int FixedRadix = 58;
const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (decimalNumber == 0)
return "0";
int index = BitsInLong - 1;
ulong currentNumber = decimalNumber;
char[] charArray = new char[BitsInLong];
while (currentNumber != 0)
{
int remainder = (int)(currentNumber % FixedRadix);
charArray[index--] = Digits[remainder];
currentNumber = currentNumber / FixedRadix;
}
string result = new String(charArray, index + 1, BitsInLong - index - 1);
return result;
}
/// <summary>
/// Adapted Unsigned-Base58-Version of Pavel Vladovs ArbitraryToDecimalSystem function.
/// See: https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
/// </summary>
/// <param name="base58String"></param>
/// <returns></returns>
public static ulong Base58StringToUDecimal(string base58String)
{
const int FixedRadix = 58;
const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (String.IsNullOrEmpty(base58String))
return 0;
ulong result = 0;
ulong multiplier = 1;
for (int i = base58String.Length - 1; i >= 0; i--)
{
char c = base58String[i];
int digit = Digits.IndexOf(c);
if (digit == -1)
throw new ArgumentException(
"Invalid character in the arbitrary numeral system number",
"number");
result += (uint)digit * multiplier;
multiplier *= FixedRadix;
}
return result;
}
}
}
此致
所以这是我根据@Yosh 的想法编写的代码,它的功能是:https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
string code = "";
string[] scoreArray = new string[100];
foreach (KeyValuePair<string, LevelScore> l in scores)
{
scoreArray[l.Value.levelNum - 1] = Convert.ToString(l.Value.stars, 2).PadLeft(2, '0');
}
for (int s = 0; s < scoreArray.Length; s++)
{
code = scoreArray[s] + code;
}
string b2 = code ;// like "111111111111111111111111111111111111111111111111111111111111";
print("b2 " + b2);
long b10 = ScoreUtils.ArbitraryToDecimalSystem(b2, 2);
print("b10 " + b10);
string b36 = ScoreUtils.DecimalToArbitrarySystem(b10, 36);
print("b36 " + b36);
出于游戏的目的,我需要生成一个保存代码,用户可以在某处记下该代码,并在以后用于重新加载他的游戏状态(不可能有持久数据)。
保存代码需要像 6DZF1D3
一样短(一个 base 36 或 base 62 字符串)。
游戏关卡分数可以简化为 string
,例如 1232312321321321321,其中每个字符都是 "stars" 中的一个关卡分数(1、2 或 3 星)。
将有大约 30 个游戏关卡。
我想为用户生成尽可能短的代码,所以我的第一个想法是在数组中生成所有可能性。然后生成用户所在key的base 62编码。但是有 3^30 种可能性,这将生成一个具有 2e+14 key/values 的数组,这对内存和 CPU.
不利第二个想法是使用 base 4 到 62 转换器,但我发现的大多数代码都使用 int
或 long
,它们的大小有限且少于 30 个字符。
您知道如何生成由字母数字字符组成的最短保存代码吗?
将二进制数据转换为文本表示的最常见方法是 Base64。每个字符代表 6 位信息。你只有不到 48 位的信息,这很好地让你得到 8 个 Base64 数字。
所以策略是:
1. 使用 this algorithm.
将 3 进制(星形)数组转换为 2 进制
2.将位转换为字节数组using Convert.ToByte();
3. 使用Convert.ToBase64String()创建一个Base64字符串。
编辑: 我知道你想把它放在 Base36 中,there are some code examples that can do it. 此代码需要一个字符串作为输入,但将其转换为 char[]
,因此您可以只提供 ByteArray。
编辑2: 证明是在吃,刚刚为任何基地创建了一个来回转换器,直到 base36(但可以扩展)。对于您的星星,您只需提供一个字符串,其中星星值作为数字(1 到 3)。
private static string ConvertToOtherBase(string toConvert, int fromBase, int toBase)
{
const string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
long value = 0;
string result = "";
foreach (char digit in toConvert.ToCharArray())
value = (value * fromBase) + characters.IndexOf(digit);
while (value > 0)
{
result = characters[(int)(value % toBase)] + result;
value /= toBase;
}
return result;
}
你可以这样称呼它(来回):
var stars = "112131121311213112131121311213";
string base36Result = ConvertToOtherBase(stars, 4, 36);
// 32NSB7MBR9T3
string base4Result = ConvertToOtherBase(base36Result, 36, 4);
// 112131121311213112131121311213
当然,这个问题是基于意见的,但这里有一种简单的保存方法
创建对象
public class Memento
{
public int Id {get; set;}
public int Level {get; set;}
public int Score {get; set;}
}
然后只需使用 Newtonsoft.Json
库对其进行序列化。最重要的是,你可以加密序列化 JSON 这样用户就看不到保存数据的内部,并将其写入磁盘。但是,当然,有很多方法可以保留分数。顺便说一下,我的名字 class 应该会为您指出一个专门解决这个问题的编程模式
更新
正在阅读您的评论 - 这是您要找的吗?
int x = 5, y = 10;
byte[]xb = BitConverter.GetBytes(x);
var enumer = xb.Concat(BitConverter.GetBytes(y));
string outStr = Convert.ToBase64String(enumer.ToArray());
Console.WriteLine(outStr);
// your code: BQAAAAoAAAA=
顺便说一句,如果你使用 int16,你的代码会更短:BQAKAA==
byte[] back = Convert.FromBase64String(outStr);
short a = BitConverter.ToInt16(back, 0);
short b = BitConverter.ToInt16(back, 2);
Console.WriteLine(a + "_" + b);
如果用户应该能够将其写下来,我更喜欢 Base58 编码。因此,对于每个级别 1-3 个可能的星星,我们需要 2 位来对每个级别进行编码。
00 => 0 star (would mean last unplayed level reached)
01 => 1 star
10 => 2 stars
11 => 3 stars
我们需要 60 位来表示 30 个级别,所有具有 3 颗星的级别都是十进制的 1152921504606846975。这个,base58 编码,将是 3gDmDv6tjHG,不会太长吧?!
更新:
@DrNootNoot 很高兴您找到了解决问题的方法!但是我很高兴为我提到的 base58 版本破解一小段代码。我改编了您使用的 Pavel Vladov 的两个功能。
也许有一天其他人也会遇到类似的问题:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string[] scoreArray = new string[30] { "1", "2", "3", "3", "1", "2", "2", "2", "3", "1", "1", "1", "2", "3", "2", "1", "2", "3", "1", "1", "1", "2", "2", "2", "1", "1", "2", "1", "2","3" };
ulong numScore = ScoreToDecimal(scoreArray);
string saveScore = UDecimalToBase58String(numScore);
Console.WriteLine("Score array: " + String.Join("-",scoreArray));
Console.WriteLine("Numeric score: " + Convert.ToString(numScore));
Console.WriteLine("Base58 score: " + saveScore);
ulong numScoreRestored = Base58StringToUDecimal(saveScore);
string[] scoreArrayRestored = DecimalToScore(numScoreRestored);
Console.WriteLine("From Base58 converted numeric score: " + Convert.ToString(numScoreRestored));
Console.WriteLine("From Base58 converted score array: " + String.Join("-", scoreArray));
Console.Read();
}
/// <summary>
/// Converts the stars-per-level array to a decimal value for the saved game.
/// </summary>
/// <param name="score">score array to convert. Max. 32 entries/levels.</param>
/// <returns></returns>
public static ulong ScoreToDecimal(string[] score)
{
int arrLength = score.Length;
if (arrLength > 32)
throw new ArgumentException("The score array must not be larger than 32 entries");
ulong result = 0;
for (int i = arrLength - 1; i >= 0; i--)
{
ulong singleScore = Convert.ToUInt64(score[i]);
if (singleScore > 3)
throw new ArgumentException(String.Format("Invalid score value. Max. allowed value is 3, but {0} was given at index {1}", singleScore, i), "score");
result += (singleScore << ((arrLength - 1 - i) * 2));
}
return result;
}
/// <summary>
/// Converts the decimal value of the saved game back to a stars-per-level array.
/// </summary>
/// <param name="decimalScore">Maximal 64-bit unsigned saved game number to convert.</param>
/// <returns></returns>
public static string[] DecimalToScore(ulong decimalScore)
{
List<string> result = new List<string>();
while(decimalScore > 0)
{
result.Add(Convert.ToString(decimalScore % 4));
decimalScore /= 4;
}
result.Reverse();
return result.ToArray();
}
/// <summary>
/// Adapted Unsigned-Base58-Version of Pavel Vladovs DecimalToArbitrarySystem function.
/// See: https://www.pvladov.com/2012/05/decimal-to-arbitrary-numeral-system.html
/// </summary>
/// <param name="decimalNumber"></param>
/// <returns></returns>
public static string UDecimalToBase58String(ulong decimalNumber)
{
const int BitsInLong = 64;
const int FixedRadix = 58;
const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (decimalNumber == 0)
return "0";
int index = BitsInLong - 1;
ulong currentNumber = decimalNumber;
char[] charArray = new char[BitsInLong];
while (currentNumber != 0)
{
int remainder = (int)(currentNumber % FixedRadix);
charArray[index--] = Digits[remainder];
currentNumber = currentNumber / FixedRadix;
}
string result = new String(charArray, index + 1, BitsInLong - index - 1);
return result;
}
/// <summary>
/// Adapted Unsigned-Base58-Version of Pavel Vladovs ArbitraryToDecimalSystem function.
/// See: https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
/// </summary>
/// <param name="base58String"></param>
/// <returns></returns>
public static ulong Base58StringToUDecimal(string base58String)
{
const int FixedRadix = 58;
const string Digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
if (String.IsNullOrEmpty(base58String))
return 0;
ulong result = 0;
ulong multiplier = 1;
for (int i = base58String.Length - 1; i >= 0; i--)
{
char c = base58String[i];
int digit = Digits.IndexOf(c);
if (digit == -1)
throw new ArgumentException(
"Invalid character in the arbitrary numeral system number",
"number");
result += (uint)digit * multiplier;
multiplier *= FixedRadix;
}
return result;
}
}
}
此致
所以这是我根据@Yosh 的想法编写的代码,它的功能是:https://www.pvladov.com/2012/07/arbitrary-to-decimal-numeral-system.html
string code = "";
string[] scoreArray = new string[100];
foreach (KeyValuePair<string, LevelScore> l in scores)
{
scoreArray[l.Value.levelNum - 1] = Convert.ToString(l.Value.stars, 2).PadLeft(2, '0');
}
for (int s = 0; s < scoreArray.Length; s++)
{
code = scoreArray[s] + code;
}
string b2 = code ;// like "111111111111111111111111111111111111111111111111111111111111";
print("b2 " + b2);
long b10 = ScoreUtils.ArbitraryToDecimalSystem(b2, 2);
print("b10 " + b10);
string b36 = ScoreUtils.DecimalToArbitrarySystem(b10, 36);
print("b36 " + b36);