如果我有Game Class,它有Player对象和Board对象,Game询问Player是什么坐标,玩家响应,然后游戏检查Board的坐标和结果是Hit还是Miss。
游戏如何将结果转发给玩家?以便Player使用结果来设置新坐标。
我在下面创建了代码示例,以解释更多我想要做的事情
还有这个项目的链接: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;
让我们来看看。我认为这里的主要问题是设计问题,所以让我们从这个角度来看待它。我想事先注意一下,我将仅描述这种方法的一个例子:有很多方法可以做到这一点,我写的是我能想象的最简单的方法。而且,为了简单起见,省略了处理同步,正常终止等的代码。
首先,你有一个玩家是一个单独的东西,但在你的代码中它只有在从外部调用它时才会做出反应。当它在实现回合制游戏时看起来像是一种自然的方式,我们仍然希望进行某种沟通。如果玩家离开怎么办?如果出现错误情况该怎么办?
正如您所指出的那样,服务器希望通知玩家游戏的结果。看起来我们希望在服务器和播放器之间进行双向消息传递。当然,如果有一个One Server - > Many Players关系,这是另一个交易,但我们会保持简单。
让我们准备一些样板代码:
# 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;
}
现在这是一个服务器:
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);
}
}
}
它有一个板和另外两个属性 - 供应$.shots
和渠道$.player
。如果我们想告诉玩家什么,我们会向频道发送消息。同时,我们想知道玩家想要我们知道什么,所以我们正在倾听来自我们的$!shots
异步价值流的一切。 serve
方法只是做我们的逻辑 - 对玩家的事件作出反应。
现在给我们的玩家:
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!"
}
}
}
}
}
玩家也有渠道和供应,因为我们想要双向关系。 $!server用于向服务器发送动作,$!events为我们提供了一系列事件。
play
方法以这种方式实现:如果服务器说我们的行动没问题,我们可以做出我们的行动,如果没有 - 我们基本上等待,当出现Hit或Miss事件时,我们会对此做出反应。
现在我们要将这两者结合在一起:
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;
首先,我们创建了两个具有自包含名称的频道。然后我们创建服务器和播放器,反转这些频道:播放器可以发送到第一个并听第二个,服务器可以发送到第二个,然后收听第一个。
由于react
是一个阻塞构造,我们不能在同一个线程中运行这两个方法,因此我们在另一个线程中使用start
服务器。然后我们休息1秒以确保它为我们服务(这是一个黑客,以避免谈判代码在这已经很长的答案),并启动播放器(无论是模拟还是真正的输入,你可以尝试两者)。
修改在Player和Server之间发送的处理程序和数据类型,您可以自己构建更复杂的示例。
一种方法是向玩家添加Board
。如果你把它变成$.board
然后你至少得到一个你可以在Game
类中使用的公共读取访问器,如果你使它成为is rw
你将获得一个写访问器,所以你可以写它。
所以,将Board
添加到Player
:
class Player {
has Board $.board is rw = Board.new;
method fire ( ) {
(^10).pick, (^10).pick
}
(为了编译你需要在Board
上面移动Player
类声明,否则你会得到一个Type 'Board' is not declared
错误。)
现在你可以在Board
类的某处添加这样的一行:
$!player.board.cell[$y][$x] = Hit; # or Miss
此外,您需要在玩家棋盘的单元格中记录三种状态中的一种,而不是两种 - Hit
,Miss
或unknown。也许将Unknown
添加到枚举中并使用Unknown
s初始化玩家的棋盘。