如果我拥有同时具有Player对象和Board对象的Game Class,Game会问Player是什么坐标,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;
答案 0 :(得分:12)
让我们看看。我认为这里的主要问题是设计问题,所以让我们从这个角度出发。我想事先指出,我将仅描述该方法的一个示例:有很多方法可以实现,而且我正在写出我能想到的最简单的方法。另外,为简单起见,省略了处理同步,正常终止等的代码。
首先,您需要将播放器与众不同,但是在您的代码中,仅当从外部调用它时,它才会做出反应。当实施回合制游戏看起来很自然时,我们仍然希望进行某种形式的交流。如果玩家离开该怎么办?如果出现某些错误情况该怎么办?
正如您所指出的,服务器希望将游戏的结果通知玩家。似乎我们希望在服务器和播放器之间建立双向消息传递。当然,如果存在“一台服务器->多人游戏”关系,那是另一回事,但我们将使其保持简单。
让我们准备一些样板代码:
# 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
在另一线程中是一台服务器。然后我们睡一秒钟,以确保它能为我们服务(这是一种避免在已经很长的答案中避免使用协商代码的技巧),然后启动播放器(无论是模拟还是真实的输入,您都可以尝试两者)。
修改Player和Server之间发送的处理程序和数据类型,您可以自己构建更复杂的示例。
答案 1 :(得分:4)
一种实现方法是向播放器添加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
s初始化玩家的棋盘。