【问题标题】:C Replacements for srand() and rand()srand() 和 rand() 的 C 替换
【发布时间】:2020-12-12 11:51:05
【问题描述】:

我正在尝试使 C 中的游戏符合 3KB 的限制,为此我想删除所有标准库引用。我想在我的程序中拥有的唯一标题是windows.h,到目前为止我做得很好。

我拥有的唯一外部引用是 rand()srand()time()getch() 中的每个调用。对于 3KB 以下的游戏来说,这似乎很多,但 time、rand 和 srand 函数只是为了在游戏开始时可以有一个随机种子。

rand 获取数字,srand 设置种子,time 获取随机种子。因此,如果我能找到一种方法来摆脱过多的随机生成过程,我就可以摆脱 3/4 标准库函数。我不太确定如何处理 getch,但稍后会重点关注。

有人推荐了xorshifts,但是好像需要uint32_t这个类型,属于stdint.h。我也尝试使用整数,虽然结果数字是随机的,但它是随机的,因为返回的数字是随机的,但你可以依赖它每次都给你那个随机数。我可以给它时间作为一种种子,但是 id 仍然必须使用 time 函数。有什么我忽略了,还是应该使用不同的方法?

编辑:根据要求,我的编译标志是 -ffunction-sections-fdata-sections-s-Os。我唯一的链接器标志是-Wl,--gc-sections,我不确定动态链接库和静态链接库是什么,但我有一个不错的想法。我的代码是 12KB,如果我使用 UPX,我可以将它降低到 7KB。我的代码在这里:

#include <windows.h>

#define WIDTH 100
#define HEIGHT 100
#define BOMBS 800

struct xorshift_state {
  int a;
};

int xorshift(struct xorshift_state *state)
{
    int x = state->a;
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    return state->a = x;
}

void ExpandGrid(int fullGrid[WIDTH][HEIGHT], int knownGrid[WIDTH][HEIGHT], int blankPos[2])
{
    int neighbors[8][2] = {{0,1}, {1,0}, {1,1},
                          {0,-1},        {-1,0},
                          {-1,-1},{-1,1},{1,-1}};
    int curTile[2];

    knownGrid[blankPos[0]][blankPos[1]] = 1;
    if(fullGrid[blankPos[0]][blankPos[1]] != 0) return;

    for(int blck = 0; blck < 8; ++blck)
    {
        curTile[0] = blankPos[0]+neighbors[blck][0];
        curTile[1] = blankPos[1]+neighbors[blck][1];
        if(curTile[0] > WIDTH-1 || curTile[1] > HEIGHT-1 || curTile[0] < 0 || curTile[1] < 0) continue;

        if(fullGrid[curTile[0]][curTile[1]] == 0 && knownGrid[curTile[0]][curTile[1]] == 0)
        {
            knownGrid[curTile[0]][curTile[1]] = 1;
            ExpandGrid(fullGrid, knownGrid, curTile);
        }
        else if(fullGrid[curTile[0]][curTile[1]] > 0) knownGrid[curTile[0]][curTile[1]] = 1;
    }
}

int main(int argc, char *argv[])
{

    COORD characterBufferSize = { WIDTH, HEIGHT };
    COORD characterPosition = { 0, 0 };
    SMALL_RECT consoleWriteArea = { 0, 0, WIDTH - 1, HEIGHT - 1 };
    CHAR_INFO consoleBuffer[WIDTH][HEIGHT];
    HANDLE wHnd = GetStdHandle(-11);

    int startGrid[WIDTH][HEIGHT] = { 0 };
    int knownGrid[WIDTH][HEIGHT] = { 0 };
    int arrowPos[2] = {0, 0};
    int bomb[2] = {0};
    struct xorshift_state seed = {argc == 2 ? (int) argv[1] : 1};

    for (int i = 0; i < BOMBS; i++)
    {
        while (startGrid[bomb[0]][bomb[1]] < -1 || bomb[0] <= 0 || bomb[1] <= 0 || bomb[0] >= WIDTH-1 || bomb[1] >= HEIGHT-1)
        {
            bomb[0] = (xorshift(&seed) % WIDTH-1) + 1;
            bomb[1] = (xorshift(&seed) % HEIGHT-1) + 1;
        }

        startGrid[bomb[0]][bomb[1]] = -9;

        startGrid[bomb[0] + 1][bomb[1] + 1]++;
        startGrid[bomb[0] + 1][bomb[1]]++;
        startGrid[bomb[0]][bomb[1] + 1]++;
        startGrid[bomb[0] - 1][bomb[1] + 1]++;
        startGrid[bomb[0]][bomb[1] - 1]++;
        startGrid[bomb[0] + 1][bomb[1] - 1]++;
        startGrid[bomb[0] - 1][bomb[1] - 1]++;
        startGrid[bomb[0] - 1][bomb[1]]++;
    }


    while(1)
    {
        if (arrowPos[0] > WIDTH-1) arrowPos[0] = WIDTH-1;
        if (arrowPos[0] < 0) arrowPos[0] = 0;
        if (arrowPos[1] > HEIGHT-1) arrowPos[1] = HEIGHT-1;
        if (arrowPos[1] < 0) arrowPos[1] = 0;

        for (int x = 0; x < WIDTH; ++x)
        {
            for (int y = 0; y < HEIGHT; ++y)
            {

                if (knownGrid[x][y] == 1)
                {
                    if (startGrid[x][y] > 0)
                    {
                        consoleBuffer[x][y].Char.AsciiChar = '0' + startGrid[x][y];
                        consoleBuffer[x][y].Attributes = 10;
                    }
                    else
                    {
                        consoleBuffer[x][y].Char.AsciiChar = 'o';
                        consoleBuffer[x][y].Attributes = startGrid[x][y] < 0 ? 4 : 17;
                    }
                }
                else
                {
                    consoleBuffer[x][y].Char.AsciiChar = 00;
                    consoleBuffer[x][y].Attributes = 0;
                }

                if(arrowPos[0] == x && arrowPos[1] == y)
                {
                    consoleBuffer[x][y].Attributes = 112;
                }
            }
        }

        WriteConsoleOutputA(wHnd, *consoleBuffer, characterBufferSize, characterPosition, &consoleWriteArea);

        switch(getch())
        {
            case 72:
                arrowPos[0]--;
                break;
            case 80:
                arrowPos[0]++;
                break;
            case 75:
                arrowPos[1]--;
                break;
            case 77:
                arrowPos[1]++;
                break;
            case '\r':
                ExpandGrid(startGrid, knownGrid, arrowPos);
                break;
        }
    }
}

【问题讨论】:

  • 可以用用户做种子吗?您可以要求用户输入几个字符,然后测量输入时间。
  • 我可以,但我认为获取输入也需要使用标准库函数。
  • 我有点困惑——你的游戏下有操作系统吗?如果是这样,这个 3k 限制是如何到达 eist 的?只是好奇,好像游戏需要整个操作系统才能运行,说可执行文件本身低于 3k 似乎有点误导,不是吗?
  • 没有任何用户输入的游戏如何?
  • 仅仅通过包含头文件,例如stdint.h,你并没有增加编译二进制文件的大小(除非你调用任何函数,当然,在头文件中声明)

标签: c function random shared-libraries


【解决方案1】:

这在很大程度上取决于您对随机生成器的要求。如果您只是想要它,以便每次玩游戏时游戏都会有所不同并且不是完全确定的,那么几乎任何随机生成器都可以。只要上网看看,你就会发现原始随机生成器的例子。

其中最简单(但仍然相当不错)是:

int seed = 123456789;

int rand()
{
  seed = (a * seed + c) % m;
  return seed;
}    

请注意,acm 需要良好的值。在我找到它的地方进一步解释:https://stackoverflow.com/a/3062783/6699433

说到种子,有一些选择。如果可能,您可以将种子作为参数发送给程序。您可以使用用户输入并测量时间。

另外,请记住,可执行文件的大小不会(或至少一般不会)变大,因为如果您不使用它们,您将包含标题。如果您已激活优化,则尤其如此。

【讨论】:

  • 所以,我尝试了您的版本,但由于某种原因,这些值非常大且不受控制,很可能是因为我设置了错误的数字。但是,你通过提到我可以如何从命令行接受参数给了我一个想法,所以我尝试了类似的维基百科版本,除了使用普通整数而不是 uint32_t,它有效!!!!那么这在技术上是我应该接受的答案吗?
  • @TryingMyBest 好吧,答案有帮助,最后没有其他答案可供选择:)
【解决方案2】:

您可以使用可执行文件的参数,并将 argv[1] 哈希作为种子。这样,人们就可以一遍又一遍地玩同一个问题,互相对抗。例如“仙女座”或“伦敦”。 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-26
    • 1970-01-01
    • 2014-05-01
    • 1970-01-01
    • 2021-01-23
    • 1970-01-01
    • 1970-01-01
    • 2016-06-07
    相关资源
    最近更新 更多