3

I am trying to learn more about functional-reactive programming by using Rx.Net to implement Tic-Tac-Toe. The problem I am having is that there seems to be a circular dependency in my game-logic.

The commands stream (PlaceToken, ResetGame, etc.) is generated from user-input streams.

The current state of the game (boardStates) is derived by applying commands to the previous state, starting with the initial state:

var initialBoardState = new BoardState();

var boardStates = commands
    .Scan(initialBoardState, (boardState, command) => command.Apply(boardState))
    .DistinctUntilChanged();

However, the commands stream should depend on the boardStates stream. This is because the valid set of commands changes with the current state.

For example, the PlaceToken command should only be issued when the user clicks on an empty tile, but the set of empty tiles is defined by the current state!

So to summarise, I have two streams which seem to depend on each-other. How should I work around this in functional-reactive programming?

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245

2 Answers2

2

While @LeeCampbell's solution does work, it requires making your core model classes mutable. Instead, I found it best to copy the approach taken by cycle.js. You can see their explanation here.

The problem is that we have a cycle. The stream of actions depends on the stream of board states, which in turn depends on the stream of actions:

boardStream = f(actionStream)
actionStream = g(boardStream)

The cycle.js solution is to use a proxy stream to wire everything together:

// Create a proxy
proxyActionStream = new Stream()

// Create our mutually dependent streams using the proxy
boardStream = f(proxyActionStream)
actionStream = g(boardStream)

// Feed actionStream back into proxyActionStream
actionStream.Subscribe(x => proxyActionStream.OnNext(x))

In Rx.Net land, the proxy stream should be a ReplaySubject.

The only place you have to be careful is with run-away feedback loops: if your streams never stabilize then they hit an infinite loop! It is helpful to think of the streams as a mutual recursion.

sdgfsdh
  • 33,689
  • 26
  • 132
  • 245
1

Not everything needs to be an event. Remember Rx/Callbacks are a way of allowing something that you depend on, to call you back (without taking a dependency on you).

As a rule of thumb

  1. Circular dependencies indicate a design flaw
  2. Send Commands, Receive Events

2) can also be translated to

Just call methods on your dependencies to change their state, but subscribe to their events to see their changes

So instead of thinking of Commands as a stream, you should think of user actions as a stream that when listened to may create a command.

So the BoardState might look something like this

public class BoardState
{
    public void PlaceToken(PlaceTokenCommand placeToken)
    {
        //Process, then raise event
    }

    public void Reset()
    {
        //Process, then raise event
    }

    public IObservable<?> StateUpdates()
    {

    }
}

and the ViewModel(?) code might look something like this

public class TicTacToeViewModel
{
    private readonly BoardState _board;
    public TicTacToeViewModel()
    {
        _board = new BoardState();
        MoveTokenCommand = new DelegateCommand(MoveToken, CanMoveToken);
        ResetBoardCommand = new DelegateCommand(_board.Reset);

        board.StateUpdates(state => UpdatePresentation(state));
    }

    public DelegateCommand MoveTokenCommand { get; private set;}
    public DelegateCommand ResetBoardCommand { get; private set;}


    private void MoveToken()
    {
        var token = CurrentToken;
        var location = ActiveLocation;
        var cmd = new PlaceTokenCommand(token, location);
        _board.PlaceToken(cmd);
    }

    private bool CanMoveToken()
    {
        //?
    }
}

But as @Enigmativity, requests in the comments, without a MCVE it is very hard to offer sensible help.

Last note, While Rx is Functional and it is Reactive, the zealots would object to Rx being considered FRP (see Conal, Behaviours etc).

Lee Campbell
  • 10,631
  • 1
  • 34
  • 29