Генерация кратчайшего буквенно-цифрового кода сохранения

120
11

Для игры мне нужно сгенерировать код сохранения, который пользователь может где-то записать и использовать для перезагрузки своего игрового состояния позже (постоянные данные невозможны). Код сохранения должен быть коротким, как 6DZF1D3, (строка 36 или 62).

Множество игровых уровней можно упростить в виде string например 1232312321321321321, последовательность, в которой каждый символ представляет собой показатель уровня в "звездах" (1, 2 или 3 звезды). Там будет около 30 игровых уровней.

Я хотел бы создать максимально короткий код для пользователя, поэтому моей первой идеей было создать все возможности внутри массива. Затем сгенерируйте базовый код 62 ключа, где находится пользователь. Но с 3 ^ 30 возможностями это генерирует массив с 2e + 14 ключами/значениями, что не хорошо для памяти и процессора.

Вторая мысль состояла в том, чтобы использовать базовый конвертер 4 в 62, но большинство кодов, которые я обнаружил, используют int или long которые имеют ограниченный размер и менее 30 символов.

Есть ли у вас какие-либо идеи о том, как создать кратчайший код сохранения, состоящий из буквенно-цифровых символов?

спросил(а) 2021-01-19T20:20:55+03:00 9 месяцев назад
1
Решение
65

Конечно, этот вопрос основан на мнении, но вот один простой способ сэкономить

Создать объект

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);

ответил(а) 2021-01-19T20:20:55+03:00 9 месяцев назад
46

Так что это код, который я написал с идеей @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);

ответил(а) 2021-01-19T20:20:55+03:00 9 месяцев назад
46

Наиболее распространенный способ получить двоичные данные в текстовом представлении - 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

ответил(а) 2021-01-19T20:20:55+03:00 9 месяцев назад
47

Если пользователь сможет записать его, я бы предпочел кодировку 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;
}
}
}

С уважением

ответил(а) 2021-01-19T20:20:55+03:00 9 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема