Fighting with FRP

Fighting with FRP

本文关键字:FRP with Fighting      更新时间:2023-09-26

我读过关于FRP的文章,非常兴奋。它看起来很棒,所以你可以写更多的高级代码,一切都更容易组合,等等。

然后我试着用几百个sloc重写我自己的小游戏,从普通的js到培根。

我发现,我并没有只写高级逻辑代码,而是用Bacon.js及其对原则的坚持来击败它。

我遇到了一些头痛,主要是干扰干净的代码

  1. .take(1)

我应该创造丑陋的建筑,而不是获得价值。

  1. 循环依赖项

有时它们应该符合逻辑。但在FRP中实施它是可怕的

  1. 活动状态

即使是bacon.js的创建者也有麻烦。


这里的例子是代码的和平来证明这个问题:

任务是不允许两个玩家呆在同一个地方

使用bacon.js 实现

http://jsbin.com/zopiyarugu/2/edit?js,控制台

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

我想展示的是:

  1. me.positions有其自身的依赖性
  2. 理解这段代码并不容易。这是当务之急。它看起来更容易理解。我花了更多的时间在培根的实现上。结果,我不确定它是否会像预期的那样工作

我的问题:

也许我错过了一些基本的东西。也许我的实施方式不是FRP风格的?

也许这个代码看起来不错,只是不习惯新的编码风格?

还是这个众所周知的问题,我应该选择万恶之中的至善?所以FRP的问题就像所描述的,或者OOP的问题。

我在尝试用Bacon和RxJs编写游戏时也有过类似的经历。有自我依赖性的事情(比如球员的位置)很难用"纯FRP"的方式处理。

例如,在我早期的Worzone游戏中,我包含了一个可变目标对象,可以查询玩家和怪物的位置。

另一种方法是像Elm一样:将整个游戏状态建模为单个属性(或Elm中称之为Signal),并根据该完整状态计算下一个状态。

到目前为止,我的结论是FRP不太适合游戏编程,至少在"纯粹"的方式上是这样。毕竟,对于某些事情来说,可变状态可能是更容易组合的方法。在一些游戏项目中,比如Hello World Open赛车赛,我使用了可变状态,比如用于存储状态的DOM和用于传递事件的EventStreams。

所以,Bacon.js不是一颗银弹。我建议你自己找出,在哪里应用FRP,在哪里不应用!

我有时也会有类似的填充。对我来说,使用FRP编程的经验主要是解决难题。其中有些很容易,有些则不然。而那些我觉得容易的事情可能对我的同事来说更难,反之亦然。我不喜欢玻璃钢。

别误会我的意思,我喜欢解谜,这很有趣!但我认为付费工作的编程应该更。。。没趣的更可预测。代码应该尽可能简单明了,甚至是原始代码。

当然,全局可变状态也不是我们应该走的路。我认为我们应该想办法让玻璃钢更无聊:)


还有一点关于你的代码的评论,我认为这将是更FRP’ish(一个未经测试的草案):

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