在 Javascript 中将游戏数据压缩或转换为短字符串密码(并再次转换回来)
Compressing or converting game data to a short string password (and back again) in Javascript
(编辑了标题,因为我不知道我在找什么,而且它具有误导性。)
编辑:
我正在寻找的是二进制到字符串,然后再返回。我已经在下面回答了我自己的问题。)
原Post:
我正在尝试为 JavaScript 制作的游戏制作一个 retro-style 密码系统。 (例如,在旧的 NES 游戏中,使用 alpha-numeric 个字符来加载您所在的关卡或与该关卡相关的所有标志。)
到目前为止,我已经生成了一串标志(全部为数字),然后通过使用正则表达式对这些标志进行排序然后将它们放回我的游戏状态 object(使用 object里面有我所有的各种旗帜。
每个标志都是0-9之间的数字
每个 object (或标志组)的长度为 8 个字符。 (通常带有前导零,因此这些组的长度始终为 8 个字符)
典型的字符串可能如下所示:
var gameStr = "000102340000001000019531";
(shown in groups to illustrate them individually)
00010234
00000010
00019531
(例如 3 组,每组 8 个字符)(24 个字符长)(但游戏结束时可能最终会有超过 25 组,每组 8 个)
如您所想,这个数字会变得很长,显然不能作为用户输入的密码使用。
所以我开始在网上寻找压缩这个数字的方法。
我希望将它压缩成用户可以轻松复制并粘贴到推文或聊天消息中的内容,看起来不会太“丑陋”并且不会太长(我不知道,我在这里并不挑剔,它可以是 6-24 个字符之间的任何地方?)而且我不介意它是否很容易未加密 - 安全性对于这个用例并不重要。
如有必要,我愿意更改规则,例如数字的存储方式,每组 4 flags/digits。我只是在寻找一种方法来通过数学或某种压缩算法使这个数字更小。
我遇到了两个看似有希望的解决方案,
第一个是this JavaScript library called lz-string
它类似于 LZW,但更快更具体,它将字符串压缩为十六进制代码,类似于:
Input:
000102340000001000019531000102340000001000019531000102340000001000019531
(72 characters)
(9 groups of 8 characters separated just to visualise the numbers in their groups)
00010234
00000010
00019531
00010234
00000010
00019531
00010234
00000010
00019531
Output:
0803c08c199858808e5f807a059c936258190d2c2d438ec6b43c4c5d0080
(spaces removed)(60 characters)
但是如您所见,十六进制仍然很长。
所以我找到的第二个解决方案是 this quiet answer tucked away on SO:
Jamie Morgan:
What about converting a big number to a formula: So instead of 21312312312 I might use 4^34
(他们向一些数学论坛提供了一个 link,但是 link 已经死了。)
在我看来,这似乎 可以 工作,但我不知道如何开始编写这样一个可以做到这一点的函数..(数学真的不是我的强项..)这个想法似乎在数学上等同于“unfrying an egg”..
所以我的问题是,
关于如何缩短此数字或将其压缩为压缩数字(或字符串)然后再返回的任何想法?
顺便说一句,我想提一下,我已经花了将近一周的时间在谷歌上搜索并查看 SO 中对此类问题的其他答案,到目前为止,我开始认为这可能是不可能的。 .如果你有理由相信它是不可能的,请告诉我这样我就可以停止寻找答案..我可以很容易地将这些数据保存到浏览器的localStorage
并完成它,但我认为密码系统会更真实,并且是一个有趣的挑战,以这种方式合并和学习一些关于压缩和数字的工作。
提前感谢您的理解以及您可能提供的任何帮助。
你要压缩的初始状态从哪里来?我想可能有三种选择。
这是随机的。这很可能意味着一些代码使用一些值来播种伪随机数生成器,例如一天中的时间,然后用它来产生值。在这种情况下,您可以获得种子(很可能是一个相当短的数字)并将其用作计算其他所有内容的标识符。确保使用具有明确定义的确定性行为的便携式随机数生成器,例如一些 Mersenne Twister implementation. The JavaScript built in number generator is implementation-defined 所以它不符合这个法案。
它来自游戏开发者(即您)制作的某个目录。然后将索引混淆到该目录中可能就足够了。
它来自一些用户手动调整的值。在这种情况下,您就不走运了,因为据我了解,问题很可能是可以输入任何可能的组合。您无法在不丢失信息的情况下将大量值压缩为较小的值集。
可能会有中间立场。您可以有一个随机设置,随后进行手动调整,并且作为初始种子加上一些修改的描述将比整套设置更短。或者只有遵循游戏开发者制定的特定规则才允许手动调整,这同样会导致一组有限的可能值和可能更短的编码。按照这些类别思考可能有助于您分析自己的情况并找到合适的解决方案。
您也可以从 information theory point of view. You can't expect to encode a sequence of fully independent and uniformly distributed digits with less information than those digits, perhaps expressed in some other base or whatever. You can compress data if there are patterns to it that make some combinations more likely than others. The more you tell us about these patterns, the better we might be able to advise. In total you can't get below the entropy of the source (i.e. game state distribution) 中查看此内容,因此估计可能会帮助您找到预期结果的下限。
第 1 步是去除前导零。对于这些八位数字组中的每一组,似乎存在远小于八位数字的可能值范围。关于每个组中的数字范围,您知道或可以控制什么?
第 2 步将用引用替换重复。为什么重复相同的值?这是预期的吗?还是您的示例异常?
第 3 步是编码。如果你有一个任意序列的 n 十进制数字,你可以将它编码成 ceiling(n log 10 / log k) 符号,其中 k 是允许的符号数。例如,如果您允许所有数字、小写和大写数字,则 k 为 62。您可以添加大部分标点符号字符,并在 80 年代或 90 年代得到它。您只需使用基本转换即可完成此操作。您所做的只是将数字从基数 10 转换为基数 k。此步骤将使您接近两倍的压缩率。
(回答我自己的问题)
我想我已经找到了解决方案。
起初我认为我想要的可能与基数转换有关(例如二进制 (base2) 到十六进制 (base16) 或 base2 到 base64 等)和 [= 的使用68=] 函数 btoa()
和 atob()
(根据 James K. Polk 总统和 CertainPerformance 的评论以及 MvG 和 Mark Adler 雄辩的书面回答进行了更多谷歌搜索之后)(谢谢你们所有人,我感谢你们帮助),
我最终找到了网站 cryptii.com,它有一个非常干净且易于理解的字符串、数字、位、字节(以及您可能需要的任何其他内容)转换器。
该网站帮助我直观地了解这些转换在十六进制、二进制和基数级别上的工作原理。
事实证明,(根据我的理解),对于我正在尝试做的事情,我不需要转换为不同的基础或使用 LZW 进行压缩,因为它们只是生成比我已经可以用二进制的几个字节来做。
一个字节(一组 8 个布尔标志/二进制位)等于字符串中的一个字符。
所以如果我的游戏状态 object 有 14 组 8 个布尔标志(14 个二进制字节)
我可以将这些字节转换为 14 个字符的字符串。
我的意思是:
例如这组随机的二进制文件:
0110111001100101011100010111010101101011011000110110101101100010011101100110110101100001011011100110011001101101
等同于:
nequkckbvmanfm
我发现 a small Javascript function called ABC
二进制到字符串并返回
let ABC = {
toAscii(bin) {
return bin.replace(/\s*[01]{8}\s*/g, function(bin) {
return String.fromCharCode(parseInt(bin, 2));
});
},
toBinary(str, spaceSeparatedOctets) {
return str.replace(/[\s\S]/g, function(str) {
str = ABC.zeroPad(str.charCodeAt().toString(2));
return !1 == spaceSeparatedOctets ? str : str + ' '
});
},
zeroPad(num) {
return '00000000'.slice(String(num).length) + num;
}
};
我可以这样使用它:
//..convert my object full of boolean flags to a string with a loop.. (not shown here)
//a string named gameState would contain something like:
//0110111001100101011100010111010101101011011000110110101101100010011101100110110101100001011011100110011001101101
//binary to ascii
var testAsciiResult = ABC.toAscii(gameState); // Result --> nequkckbvmanfm
//back into binary
var testBinaryResult = ABC.toBinary(testAsciiResult,0);
// Result --> 0110111001100101011100010111010101101011011000110110101101100010011101100110110101100001011011100110011001101101
//(the ",0" flag outputs binary without the spaces)
//(don't use a flag if you want to output binary with spaces every 8 bits)
除此之外,我还可以将二进制的这些字节(或少于一个字节,具体取决于最大数量)的任意组合转换为 base10(正常数字),而不是二进制中的单个标志字符串,这样我就可以存储金币的数量,或者主要任务的状态,或者某个东西的等级。
应该注意的是,整个字节中的许多输出符号可能不是用户可以键入的字符(或看到,例如 space 或换行符)(如上所述this website I found on google 表示“ascii 特殊字符”)
为了确保密码中只出现 user-readable 个字符,可以将标志以 6 位而不是 8 位为一组的形式存储在 JavaScript 中,这样我的密码生成器只输出介于64 - 127(超出该网站上的图表),然后在为我的游戏创建密码时,在每组 6 位数字的开头添加一个 01
以使 6 个布尔标志集恢复为字节,以便它可以正确分配正确的字符。
我还发现重要的是要注意字符 127 (01111111
) 是一个
“删除”字符(在浏览器中显示为空白 space),我想我必须将其转换为密码中 64-127 范围之外的其他符号(?字符,例如字符 63, 00111111
) 然后在加载密码时再次检查它以将数据标志恢复到应有的状态。
但这超出了本question/answer的范围。
对于任何在大学或其他地方学过这方面的人来说,这听起来一定是微不足道的,但我是第一次学习这些东西,我很奇怪我是如何与 JavaScript 一起工作这么久的直到现在才知道这些东西。很抱歉这个问题和答案变得如此冗长,但很难简明扼要,我希望这些信息能帮助其他人在某天从 google 中跌跌撞撞地尝试做类似的事情。
我想我在解释这个特殊问题时遇到了很多困难,主要是因为我不知道什么叫做什么,而且我什至都在努力思考字符串、十六进制和二进制是如何协同工作的。
我想我还有很多书要读。
如果除了 OP 之外还有人正在寻找这样的 Javascript 东西,一个非常有效的方法就是 BWTC32Key,其中 Base32768 被用作二进制到文本的编码以实现最大效率,并使用 BZip 家族的改进版本进行高效压缩。 (它还有可选的 AES256-CTR 加密,这很好,因为它不需要填充。)
(编辑了标题,因为我不知道我在找什么,而且它具有误导性。)
编辑: 我正在寻找的是二进制到字符串,然后再返回。我已经在下面回答了我自己的问题。)
原Post: 我正在尝试为 JavaScript 制作的游戏制作一个 retro-style 密码系统。 (例如,在旧的 NES 游戏中,使用 alpha-numeric 个字符来加载您所在的关卡或与该关卡相关的所有标志。)
到目前为止,我已经生成了一串标志(全部为数字),然后通过使用正则表达式对这些标志进行排序然后将它们放回我的游戏状态 object(使用 object里面有我所有的各种旗帜。
每个标志都是0-9之间的数字 每个 object (或标志组)的长度为 8 个字符。 (通常带有前导零,因此这些组的长度始终为 8 个字符)
典型的字符串可能如下所示:
var gameStr = "000102340000001000019531";
(shown in groups to illustrate them individually)
00010234
00000010
00019531
(例如 3 组,每组 8 个字符)(24 个字符长)(但游戏结束时可能最终会有超过 25 组,每组 8 个)
如您所想,这个数字会变得很长,显然不能作为用户输入的密码使用。
所以我开始在网上寻找压缩这个数字的方法。
我希望将它压缩成用户可以轻松复制并粘贴到推文或聊天消息中的内容,看起来不会太“丑陋”并且不会太长(我不知道,我在这里并不挑剔,它可以是 6-24 个字符之间的任何地方?)而且我不介意它是否很容易未加密 - 安全性对于这个用例并不重要。 如有必要,我愿意更改规则,例如数字的存储方式,每组 4 flags/digits。我只是在寻找一种方法来通过数学或某种压缩算法使这个数字更小。
我遇到了两个看似有希望的解决方案,
第一个是this JavaScript library called lz-string 它类似于 LZW,但更快更具体,它将字符串压缩为十六进制代码,类似于:
Input:
000102340000001000019531000102340000001000019531000102340000001000019531
(72 characters)
(9 groups of 8 characters separated just to visualise the numbers in their groups)
00010234
00000010
00019531
00010234
00000010
00019531
00010234
00000010
00019531
Output:
0803c08c199858808e5f807a059c936258190d2c2d438ec6b43c4c5d0080
(spaces removed)(60 characters)
但是如您所见,十六进制仍然很长。
所以我找到的第二个解决方案是 this quiet answer tucked away on SO:
Jamie Morgan:
What about converting a big number to a formula: So instead of 21312312312 I might use 4^34
(他们向一些数学论坛提供了一个 link,但是 link 已经死了。)
在我看来,这似乎 可以 工作,但我不知道如何开始编写这样一个可以做到这一点的函数..(数学真的不是我的强项..)这个想法似乎在数学上等同于“unfrying an egg”..
所以我的问题是, 关于如何缩短此数字或将其压缩为压缩数字(或字符串)然后再返回的任何想法?
顺便说一句,我想提一下,我已经花了将近一周的时间在谷歌上搜索并查看 SO 中对此类问题的其他答案,到目前为止,我开始认为这可能是不可能的。 .如果你有理由相信它是不可能的,请告诉我这样我就可以停止寻找答案..我可以很容易地将这些数据保存到浏览器的localStorage
并完成它,但我认为密码系统会更真实,并且是一个有趣的挑战,以这种方式合并和学习一些关于压缩和数字的工作。
提前感谢您的理解以及您可能提供的任何帮助。
你要压缩的初始状态从哪里来?我想可能有三种选择。
这是随机的。这很可能意味着一些代码使用一些值来播种伪随机数生成器,例如一天中的时间,然后用它来产生值。在这种情况下,您可以获得种子(很可能是一个相当短的数字)并将其用作计算其他所有内容的标识符。确保使用具有明确定义的确定性行为的便携式随机数生成器,例如一些 Mersenne Twister implementation. The JavaScript built in number generator is implementation-defined 所以它不符合这个法案。
它来自游戏开发者(即您)制作的某个目录。然后将索引混淆到该目录中可能就足够了。
它来自一些用户手动调整的值。在这种情况下,您就不走运了,因为据我了解,问题很可能是可以输入任何可能的组合。您无法在不丢失信息的情况下将大量值压缩为较小的值集。
可能会有中间立场。您可以有一个随机设置,随后进行手动调整,并且作为初始种子加上一些修改的描述将比整套设置更短。或者只有遵循游戏开发者制定的特定规则才允许手动调整,这同样会导致一组有限的可能值和可能更短的编码。按照这些类别思考可能有助于您分析自己的情况并找到合适的解决方案。
您也可以从 information theory point of view. You can't expect to encode a sequence of fully independent and uniformly distributed digits with less information than those digits, perhaps expressed in some other base or whatever. You can compress data if there are patterns to it that make some combinations more likely than others. The more you tell us about these patterns, the better we might be able to advise. In total you can't get below the entropy of the source (i.e. game state distribution) 中查看此内容,因此估计可能会帮助您找到预期结果的下限。
第 1 步是去除前导零。对于这些八位数字组中的每一组,似乎存在远小于八位数字的可能值范围。关于每个组中的数字范围,您知道或可以控制什么?
第 2 步将用引用替换重复。为什么重复相同的值?这是预期的吗?还是您的示例异常?
第 3 步是编码。如果你有一个任意序列的 n 十进制数字,你可以将它编码成 ceiling(n log 10 / log k) 符号,其中 k 是允许的符号数。例如,如果您允许所有数字、小写和大写数字,则 k 为 62。您可以添加大部分标点符号字符,并在 80 年代或 90 年代得到它。您只需使用基本转换即可完成此操作。您所做的只是将数字从基数 10 转换为基数 k。此步骤将使您接近两倍的压缩率。
(回答我自己的问题)
我想我已经找到了解决方案。
起初我认为我想要的可能与基数转换有关(例如二进制 (base2) 到十六进制 (base16) 或 base2 到 base64 等)和 [= 的使用68=] 函数 btoa()
和 atob()
(根据 James K. Polk 总统和 CertainPerformance 的评论以及 MvG 和 Mark Adler 雄辩的书面回答进行了更多谷歌搜索之后)(谢谢你们所有人,我感谢你们帮助),
我最终找到了网站 cryptii.com,它有一个非常干净且易于理解的字符串、数字、位、字节(以及您可能需要的任何其他内容)转换器。
该网站帮助我直观地了解这些转换在十六进制、二进制和基数级别上的工作原理。
事实证明,(根据我的理解),对于我正在尝试做的事情,我不需要转换为不同的基础或使用 LZW 进行压缩,因为它们只是生成比我已经可以用二进制的几个字节来做。
一个字节(一组 8 个布尔标志/二进制位)等于字符串中的一个字符。
所以如果我的游戏状态 object 有 14 组 8 个布尔标志(14 个二进制字节) 我可以将这些字节转换为 14 个字符的字符串。
我的意思是:
例如这组随机的二进制文件:
0110111001100101011100010111010101101011011000110110101101100010011101100110110101100001011011100110011001101101
等同于:
nequkckbvmanfm
我发现 a small Javascript function called ABC 二进制到字符串并返回
let ABC = {
toAscii(bin) {
return bin.replace(/\s*[01]{8}\s*/g, function(bin) {
return String.fromCharCode(parseInt(bin, 2));
});
},
toBinary(str, spaceSeparatedOctets) {
return str.replace(/[\s\S]/g, function(str) {
str = ABC.zeroPad(str.charCodeAt().toString(2));
return !1 == spaceSeparatedOctets ? str : str + ' '
});
},
zeroPad(num) {
return '00000000'.slice(String(num).length) + num;
}
};
我可以这样使用它:
//..convert my object full of boolean flags to a string with a loop.. (not shown here)
//a string named gameState would contain something like:
//0110111001100101011100010111010101101011011000110110101101100010011101100110110101100001011011100110011001101101
//binary to ascii
var testAsciiResult = ABC.toAscii(gameState); // Result --> nequkckbvmanfm
//back into binary
var testBinaryResult = ABC.toBinary(testAsciiResult,0);
// Result --> 0110111001100101011100010111010101101011011000110110101101100010011101100110110101100001011011100110011001101101
//(the ",0" flag outputs binary without the spaces)
//(don't use a flag if you want to output binary with spaces every 8 bits)
除此之外,我还可以将二进制的这些字节(或少于一个字节,具体取决于最大数量)的任意组合转换为 base10(正常数字),而不是二进制中的单个标志字符串,这样我就可以存储金币的数量,或者主要任务的状态,或者某个东西的等级。
应该注意的是,整个字节中的许多输出符号可能不是用户可以键入的字符(或看到,例如 space 或换行符)(如上所述this website I found on google 表示“ascii 特殊字符”)
为了确保密码中只出现 user-readable 个字符,可以将标志以 6 位而不是 8 位为一组的形式存储在 JavaScript 中,这样我的密码生成器只输出介于64 - 127(超出该网站上的图表),然后在为我的游戏创建密码时,在每组 6 位数字的开头添加一个 01
以使 6 个布尔标志集恢复为字节,以便它可以正确分配正确的字符。
我还发现重要的是要注意字符 127 (01111111
) 是一个
“删除”字符(在浏览器中显示为空白 space),我想我必须将其转换为密码中 64-127 范围之外的其他符号(?字符,例如字符 63, 00111111
) 然后在加载密码时再次检查它以将数据标志恢复到应有的状态。
但这超出了本question/answer的范围。
对于任何在大学或其他地方学过这方面的人来说,这听起来一定是微不足道的,但我是第一次学习这些东西,我很奇怪我是如何与 JavaScript 一起工作这么久的直到现在才知道这些东西。很抱歉这个问题和答案变得如此冗长,但很难简明扼要,我希望这些信息能帮助其他人在某天从 google 中跌跌撞撞地尝试做类似的事情。
我想我在解释这个特殊问题时遇到了很多困难,主要是因为我不知道什么叫做什么,而且我什至都在努力思考字符串、十六进制和二进制是如何协同工作的。
我想我还有很多书要读。
如果除了 OP 之外还有人正在寻找这样的 Javascript 东西,一个非常有效的方法就是 BWTC32Key,其中 Base32768 被用作二进制到文本的编码以实现最大效率,并使用 BZip 家族的改进版本进行高效压缩。 (它还有可选的 AES256-CTR 加密,这很好,因为它不需要填充。)