对象之间的通信

时间:2019-04-18 18:19:30

标签: perl6 raku

如果我拥有同时具有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;


2 个答案:

答案 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

此外,您需要在播放器面板的单元格中记录三个状态之一,而不是两个状态-HitMiss或未知状态。也许将Unknown添加到枚举,并用Unknown s初始化玩家的棋盘。