If I have Game Class, which has Player object and Board object, Game asks Player what are the coordinates, Player responds, then game checks Board for the coordinates and the result either Hit or Miss.
How can Game forward the result back to Player? so that Player uses the result to set the new coordinates.
I have created code sample below to explain more what i want to do
and also a link to the project here: https://github.com/hythm7/Battleship/tree/master
#!/usr/bin/env perl6
enum Result < Miss Hit >;
class Player {
method fire ( ) {
(^10).pick, (^10).pick
}
}
class Board {
has @.cell = [ +Bool.pick xx ^10 ] xx ^10;
}
class Game {
has Board $.board = Board.new;
has Player $!player = Player.new;
method run ( ) {
loop {
my ($x, $y) = $!player.fire;
if $!board.cell[$y][$x] {
say Hit;
}
else {
say Miss;
}
# How to forward the above result (Hit or Miss) back to the Player object? so
# it can set $y, $x accordingly for the next call to $player.fire
sleep 1;
}
}
}
my $game = Game.new;
$game.run;
Communication Objects define data structures that are communicated through services between components. The definition of communication objects requires primitive data types such as Int, Double, String, etc. and complex data types (i.e. composed data types).
OOC is an attempt to balance simplicity and expressivity, and is sufficiently flexible to express data acquisition, control requests, alarm messages and error messages in a straightforward generic way. OOC supports dynamic creation of objects.
Objects communicate with each other by passing messages. A message consists of the receiver object name and a method name with parameters. The methods of an object are invoked by means of messages.
We communicate with objects using methods. Methods are executable code within each object, for which an interface has been established. Sometimes the interface is only for the object itself. Other times it is an interface accessible by other 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 Unknown
s.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With