Генерация кратчайшего буквенно-цифрового кода сохранения
Для игры мне нужно сгенерировать код сохранения, который пользователь может где-то записать и использовать для перезагрузки своего игрового состояния позже (постоянные данные невозможны). Код сохранения должен быть коротким, как 6DZF1D3
(Строка базы 36 или базы 62).
Множество игровых уровней можно упростить как string
как 1232312321321321321, последовательность, где каждый символ представляет собой показатель уровня в "звездах" (1, 2 или 3 звезды). Там будет около 30 игровых уровней.
Я хотел бы создать максимально короткий код для пользователя, поэтому моей первой идеей было создать все возможности внутри массива. Затем сгенерируйте базовый код 62 ключа, где находится пользователь. Но с 3^30 возможностями это генерирует массив с 2e+14 ключами / значениями, что не хорошо для памяти и процессора.
Вторая мысль заключалась в том, чтобы использовать конвертер с 4 по 62, но большинство кодов, которые я нашел, используют int
или же long
которые ограничены в размере и ниже, чем 30 символов.
Есть ли у вас какие-либо идеи о том, как создать кратчайший код сохранения, состоящий из буквенно-цифровых символов?
4 ответа
Если пользователь сможет записать его, я бы предпочел кодировку 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. Я адаптировал две функции Павла Владова, которые вы использовали.
Возможно, когда-нибудь у кого-то возникнет похожая проблема:
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;
}
}
}
С уважением
Наиболее распространенный способ получить двоичные данные в текстовом представлении - Base64. Каждый символ представляет 6 бит информации. У вас есть чуть менее 48 бит информации, которая позволяет получить 8 цифр Base64.
Таким образом, стратегия будет:
1. Преобразуйте массив base 3 (star) в base 2, используя этот алгоритм.
2. Преобразуйте биты в байтовый массив, используя Convert.ToByte ();
3. Используйте Convert.ToBase64String() для создания строки Base64.
Изменить: Я понимаю, что вы хотите иметь его в Base36, есть несколько примеров кода, которые могут это сделать. Этот код нуждается в строке в качестве входных данных, но преобразует ее в char[]
, так что вы можете просто предоставить ByteArray.
Edit2: доказательство в еде, только что созданный конвертер туда и обратно для любой базы вплоть до базы36 (но может быть расширен). Для ваших звезд необходимо указать строку со значениями звездочек в виде чисел (от 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, чтобы пользователь не мог видеть внутренности сохраненных данных, и записать их на диск. Но, конечно, есть много способов сохранить счет. Кстати, имя моего класса должно указывать на шаблон программирования, который специально решает эту проблему
Обновить
Читая ваш комментарий - это то, что вы ищете?
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);
Так что это код, который я написал с идеей @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);