Javascript 与玻璃钢作战
我读过关于玻璃钢的书,非常激动。 它看起来很棒,因此您可以编写更多的高级代码,并且所有内容都更易于组合,等等 然后我试着用几百个sloc从普通js到培根重写我自己的小游戏 我发现,我并没有编写高级逻辑代码,而是用Bacon.js及其对原则的坚持击败了它 我遇到了一些主要干扰干净代码的头痛问题Javascript 与玻璃钢作战,javascript,reactive-programming,frp,bacon.js,Javascript,Reactive Programming,Frp,Bacon.js,我读过关于玻璃钢的书,非常激动。 它看起来很棒,因此您可以编写更多的高级代码,并且所有内容都更易于组合,等等 然后我试着用几百个sloc从普通js到培根重写我自己的小游戏 我发现,我并没有编写高级逻辑代码,而是用Bacon.js及其对原则的坚持击败了它 我遇到了一些主要干扰干净代码的头痛问题 .take(1) 我应该创造丑陋的建筑,而不是获得价值 循环依赖 有时,它们应该符合逻辑。但是在FRP中实现它是可怕的 活动状态 即使是bacon.js的创建者也有这样的想法 这里的示例是代码的和平,以演
.take(1)
这里的示例是代码的和平,以演示问题: 任务是不允许两个玩家呆在同一个地方 用bacon.js实现 我想展示的是:
me.positions
有其自身的依赖性我的问题: 也许我错过了一些基本的东西。也许我的实现不是FRP风格的 也许这段代码看起来不错,只是不习惯新的编码风格
还是这个众所周知的问题,我应该选择万恶中之最?因此,FRP的问题就像前面描述的,或者OOP的问题。我在尝试用Bacon和RxJs编写游戏时也有过类似的经历。有自我依赖性的事情(比如球员的位置)很难用“纯FRP”的方式处理 例如,在我早期的Worzone游戏中,我加入了一个可变对象,可以查询玩家和怪物的位置 另一种方法是像Elm的人一样:将整个游戏状态建模为单个属性(或在Elm中称为信号),并基于该完整状态计算下一个状态 到目前为止,我的结论是FRP不太适合游戏编程,至少在“纯粹”的方式上是这样。毕竟,对于某些事情来说,可变状态可能是更可组合的方法。在一些游戏项目中,比如Hello World开放式汽车比赛,我使用了可变状态,比如DOM存储状态,使用事件流传递事件
因此,Bacon.js不是一颗灵丹妙药。我建议你找出自己,在哪里使用玻璃钢,在哪里不使用 我有时也有类似的填充物。对我来说,使用FRP编程的经验主要是解决难题。有些是容易的,有些不是。而那些我觉得容易的可能比我的同事们更难,反之亦然。我不喜欢玻璃钢 别误会,我喜欢解谜题,这很有趣!但我认为付费工作的编程应该更。。。没趣的更可预测。代码应该尽可能简单,甚至是原始的 当然,全球可变状态也不是我们应该走的路。我认为我们应该找到一种让FRP更无聊的方法:)
另外,请注意您的代码,我认为这将更像FRP'ish(未经测试的草稿):
我认为在Elm中,你不必将玩家的动作建立在“完全状态”的基础上,你也可以创建小型的独立“模块”。当然,正如在任何纯语言中一样,您必须将它们组合到一个全局的完整程序中。当然,在Elm中,您不能有循环的DEP,这会将您推到这个“作为信号的世界”模型中。我并不是说这是不好的,只是指出它可能是以全功能风格编写游戏时唯一有效的模型。比如说,他们支持这一点,因为只有一个单一的世界信号依赖于自身(pong示例中的
gameState
)。但是,是的,你可以有循环依赖,只要在任何地方使用Signal.foldp
。好吧,有点循环,因为你的信号可以依赖于它自己的过去值。但是信号A和B不能相互依赖。这在游戏中经常发生。啊,你指的就是这个。虽然真正的循环依赖关系根本无法解决,但您的意思是a
确实依赖于前面的a
和前面的B
就像B
依赖于前面的两个值一样。对于这些,你需要一个共同的“世界信号”,它包括a
和B
事实上。我认为简单是一回事,“原始”或“无聊”是另一回事。。。FP声称自己能带来更容易理解的代码,因此应该是更直观的代码,但正如我们所看到的,尤其是在FRP中,这并不总是正确的。那就没有银弹了。
function add(a) {return function(b){return a + b}}
function nEq(a) {return function(b){return a !== b}}
function eq(a) {return function(b){return a === b}}
function always(val) {return function(){return val}}
function id(a){return a}
var Player = function(players, movement, initPos){
var me = {};
me.position = movement
.flatMap(function(val){
return me.position
.take(1)
.map(add(val))
})
.flatMap(function(posFuture){
var otherPlayerPositions = players
.filter(nEq(me))
.map(function(player){return player.position.take(1)})
return Bacon
.combineAsArray(otherPlayerPositions)
.map(function(positions){
return !positions.some(eq(posFuture));
})
.filter(id)
.map(always(posFuture))
})
.log('player:' + initPos)
.toProperty(initPos);
return me;
}
var moveA = new Bacon.Bus();
var moveB = new Bacon.Bus();
var players = [];
players.push(new Player(players, moveA, 0));
players.push(new Player(players, moveB, 10));
moveA.push(4);
moveB.push(-4);
moveA.push(1);
moveB.push(-1);
moveB.push(-1);
moveB.push(-1);
moveA.push(1);
moveA.push(-1);
moveB.push(-1);
var otherPlayerPositions = players
.filter(nEq(me))
.map(function(player){return player.position});
otherPlayerPositions = Bacon.combineAsArray(otherPlayerPositions);
me.position = otherPlayerPositions
.sampledBy(movement, function(otherPositions, move) {
return {otherPositions: otherPositions, move: move};
})
.scan(initPos, function(myCurPosition, restArgs) {
var myNextPosition = myCurPosition + restArgs.move;
if (!restArgs.otherPositions.some(eq(myNextPosition))) {
return myNextPosition;
} else {
return myCurPosition;
}
});