当游戏状态包含功能时存储游戏状态

时间:2019-03-28 20:23:22

标签: javascript functional-programming local-storage

我正在开发一个简单的网络游戏,需要启用保存游戏状态的功能,以便用户可以在关闭浏览器或刷新页面后从上次退出的地方继续学习。该游戏完全在浏览器中运行,并使用基本的HTML / CSS / Javascript。

我当前正在使用本地存储,因为它为在跨会话中保存游戏状态的问题提供了一种简单的解决方案:

function saveGame() {
  window.localStorage.setItem('gameData', JSON.stringify(game));
}

function loadGame() {
  game = JSON.parse(window.localStorage.getItem('gameData'));
}

但是,我的游戏状态包括一个函数数组和一个包含函数的对象数组。当我加载以前的游戏状态时,这些函数和对象未正确加载,因为不能将这些函数和对象转换为JSON(至少-不容易。我尝试对函数进行String()并对其进行eval(),以及尽管它们似乎可以正确存储,但我无法检索到它们。无论哪种方式,这都感觉是一个糟糕的解决方案)。

是否有更好的方法来保存和恢复包含功能的游戏状态?这是数据模型问题吗?另外,我有一些HTML是以编程方式修改的情况。 HTML是否也可以存储并在以后重新加载,以便修改后的HTML在重新加载后仍然存在?

编辑:添加游戏对象供您参考:

function Game() {
  this.player = new Player();
  this.triggerFnSet = new Set();
  this.tasks = [];
  this.activeTask = undefined;
  this.resources = { fame: {},
                     money: {},
                     beats: { instrument: "laptop",
                              clicksPer: 30,
                              xpPer: 5 },
                     samples: { instrument: "laptop",
                               resourcesPer: 25,
                               requiredResource: "beats",
                               xpPer: 50 },
                     notes: { instrument: "keyboard",
                              clicksPer: 50,
                              xpPer: 5 },
                     measures: { instrument: "keyboard",
                                 resourcesPer: 25,
                                 requiredResource: "notes",
                                 xpPer: 50 }
                    };
  this.specialResources = { songs: { instruments: ["laptop", "keyboard"],
                                     resourcesPer: 50,
                                     validResources: ["samples", "measures"],
                                     xpPer: 500 }};
  this.instruments = { laptop: { level: 1,
                                 currentTempo: "slow",
                                 tempoSpeeds: { slowest: 25,
                                                slow: 15,
                                                fast: 10,
                                                fastest: 5 },
                                 dropActive: false },
                       keyboard: { currentNote: undefined,
                                   currentSong: undefined }
                     };
};

有问题的字段是任务和triggerFnSet,其中任务看起来像:

function Task(name, tooltip, checkFn, failFn, startFn, tickFn, finishFn, timeToComplete) {
    this.name = name;
    this.tooltip = tooltip;
    this.checkFn = checkFn;
    this.failFn = failFn;
    this.startFn = startFn;
    this.tickFn = tickFn;
    this.finishFn = finishFn;
    this.timeToComplete = timeToComplete;
}

triggerFn如下所示,并在每次游戏中执行时执行,并在返回true后从列表中删除:

function firstBeatTrigger() {
  if (game.player.stats.beats.lifetime >= 1) {
    document.getElementById('beats').style.display = "block";
    appendToOutputContainer("You've created your first beat. A building block to something greater.");
    game.triggerFnSet.add(tenthBeatTrigger);
    return true;
  }
}

4 个答案:

答案 0 :(得分:2)

在这种情况下,您可能必须将反序列化的对象映射到具有所有功能的完整模型。可能是您首选的库中的JSON合并。另外,我认为将HTML作为值存储在JSON对象中没有问题。 但是,实际上,将您的功能与模型分开。所有功能应为单独的类。称它为gameEngine之类的东西。

答案 1 :(得分:1)

尽管如前所述,您的游戏状态不应该依赖于功能,但值得注意的是,可以通过引用键而不是功能并确保始终拥有功能来解决功能数组拥有可用功能的对象 所以:     a={}, a.m=function () {}

,然后像这样a[array[1]]()而不是array[1]()来调用数组

如果没有大量的重写代码,这种方法将无法轻松地用于对象(除非它们仅托管函数),而这些代码可能更适合用于将逻辑与数据分离的更干净的解决方案。

答案 2 :(得分:0)

首先,这里的大多数评论者都是正确的,因为这主要是一个数据模型问题。我不想重构模型,因此我正在考虑以下解决方法的正确答案:

序列化功能作为字符串引用:

function saveGame() {
  var triggers = [];

  game.triggerFnSet.forEach(function (trigger){
    triggers.push(trigger.name);
  });

  // Serialize regular game data
  window.localStorage.setItem('gameData', JSON.stringify(game));

  // Serialize triggerFns
  window.localStorage.setItem('triggers', JSON.stringify(triggers));
}

然后通过窗口对象对函数进行反序列化,然后将它们转换回函数:

function loadGame() {
  var triggers = JSON.parse(window.localStorage.getItem('triggers'));
  var gameData = JSON.parse(window.localStorage.getItem('gameData'));

  if (gameData !== null) {
    game = gameData;
    game.triggerFnSet = new Set();

    triggers.forEach(function (trigger){
      game.triggerFnSet.add(window[trigger]);
    });
  }
}

之所以起作用,是因为我的函数不是匿名函数,而是存在于可以通过窗口轻松访问的全局上下文中!

答案 3 :(得分:-1)

function t(l){
   return l+1;
}

eval("("+t.toString()+")")(1)

这似乎可行,但我不推荐

最重要的是eval("function t(l){ return l+1;}") //没有括号

将函数注册到window对象中,然后您可以调用它

t(2);

(即使它不会返回)