Communication between objects

Let's see. The main question here is a design one, I think, so let's go for it from this angle. I want to note beforehand that I will describe just a single example of the approach: there are a lot of ways to do it, and I am writing out the simplest I can imagine that works. Also, for the sake of simplicity, code that deals with synchronization, graceful termination and so on is omitted.

Firstly, you have a player to be a separate thing, but in your code it reacts only when it is called from the outside. When it looks like a natural approach when implementing turn-based games, we still want to have some kind of communication. What if a player leaves? What if there is some error condition?

And as you noted the server wants to notify the player about outcome of the game. It seems like we want to have a bi-directional messaging between our Server and Player. Of course, if there is a One Server -> Many Players relation, it is another deal, but we will keep it simple.

Let's prepare some boilerplate code:

# We will get to this `Start` later
enum EventType <Start Hit Miss>;

# A handy class to hold a position, and likely some other data in the future
class Position {
    has Int $.x;
    has Int $.y;
}

# A board
class Board {
    has @.cell = [ +Bool.pick xx ^10 ] xx ^10;
}

Now here is a server:

class Server {
    has Board $!board = Board.new;
    has Supply $.shots;
    has Channel $.player;

    method serve {
        react {
            # Whenever we get a shot coordinates, sent a Hit or Miss to the player
            whenever $!shots -> Position $pos {
                $!player.send($!board.cell[$pos.y][$pos.x] ?? Hit !! Miss);
                # Don't forget to say "I am ready for new events" for the client
                $!player.send(Start);
            }
            # Somebody should start first, and it will be a Server...
            $!player.send(Start);
        }
    }
}

It has a board, and two other attributes - a Supply $.shots and a Channel $.player. If we want to tell something to our player, we are sending a message to the channel. At the same time, we want to know what player wants us to know, so we are listening on everything that comes from our $!shots async stream of values. The serve method just does our logic - reacts to player's events.

Now to our Player:

class Player {
    has Channel $.server;
    has Supply $.events;

    method play {
        react {
            whenever $!events {
                when Start {
                    # Here can be user's input
                    # Simulate answer picking
                    sleep 1;
                    $!server.send: Position.new(x => (^10).pick, y => (^10).pick);
                    # Can be something like:
                    # my ($x, $y) = get.Int, get.Int;
                    # $!server.send: Position.new(:$x, :$y);

                }
                when Hit {
                    say "I hit that! +1 gold coin!";
                }
                when Miss {
                    say "No, that's a miss... -1 bullet!"
                }
            }
        }
    }
}

Player has a Channel and a Supply too, as we want a bi-directional relationship. $!server is used to send actions to the server and $!events provides us a stream of events back.

The play method is implemented this way: if the server says that it is ok with our action, we can made our move, if not - we are basically waiting, and when a Hit or Miss event appears, we react to it.

Now we want to tie those two together:

class Game {
    has Server $!server;
    has Player $!player;

    method start {
        my $server-to-player = Channel.new;
        my $player-to-server = Channel.new;

        $!server = Server.new(player => $server-to-player,
                              shots => $player-to-server.Supply);
        $!player = Player.new(server => $player-to-server,
                              events => $server-to-player.Supply);

        start $!server.serve;
        sleep 1;
        $!player.play;
    }
}.new.start;

Firstly, we are creating two channels with self-contained names. Then we create both Server and Player with those channels reversed: player can send to the first one and listen to the second one, server can send to the second one and listen to the first one.

As react is a blocking construct, we cannot run both methods in the same thread, so we start a server in another thread. Then we sleep 1 second to make sure it serves us(that's a hack to avoid negotiation code in this already pretty long answer), and start the player (be it emulation or a real input, you can try both).

Modifying the handlers and the data types sent between Player and Server you can build more complex examples yourself.


One way to do it is to add a Board to the player. If you make it $.board then you get at least a public read accessor which you'll be able to use in the Game class and if you make it is rw you'll get a write accessor so you can just write it.

So, add the Board to Player:

class Player {
  has Board  $.board is rw = Board.new;

  method fire ( ) {
    (^10).pick, (^10).pick
}

(And for that to compile you'll need to move the Board class declaration above Player otherwise you'll get a Type 'Board' is not declared error.)

And now you can add a line like this somewhere in the Board class:

  $!player.board.cell[$y][$x] = Hit; # or Miss

Also, you need to record one of three states in the cells of the player's board, not two -- Hit, Miss, or unknown. Maybe add Unknown to the enum and initialize the player's board with Unknowns.

Tags:

Raku