生成最短的字母数字保存代码

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 转换器,但我发现的大多数代码都使用 intlong,它们的大小有限且少于 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);