【问题标题】:Communication between objects对象之间的通信
【发布时间】:2019-04-18 18:19:29
【问题描述】:

如果我有包含 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;


【问题讨论】:

  • Jonathan Stowe 有一个存储库perl6-patterns,其中包含一些在 Perl 6 中实现的设计模式示例。
  • 会经历的,这绝对有帮助。谢谢

标签: raku


【解决方案1】:

让我们看看。我认为这里的主要问题是设计问题,所以让我们从这个角度出发。我想事先说明,我将仅描述该方法的一个示例:有很多方法可以做到这一点,我正在写出我能想象到的最简单的方法。另外,为了简单起见,处理同步、优雅终止等的代码也被省略了。

首先,你有一个玩家是一个独立的东西,但在你的代码中,它只有在从外部调用它时才会做出反应。当它在实现回合制游戏时看起来像是一种自然的方法时,我们仍然希望进行某种交流。如果球员离开怎么办?如果出现错误情况怎么办?

正如您所说,服务器想要通知玩家游戏结果。似乎我们希望在我们的服务器和播放器之间进行双向消息传递。当然,如果存在 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!"
                }
            }
        }
    }
}

Player 也有一个 Channel 和一个 Supply,因为我们想要一个双向关系。 $!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 秒以确保它为我们服务(这是在这个已经很长的答案中避免协商代码的一种技巧),然后启动播放器(无论是模拟还是真实输入,您都可以尝试两者)。

修改播放器和服务器之间发送的处理程序和数据类型,您可以自己构建更复杂的示例。

【讨论】:

  • 谢谢。这正是我想要的。我知道必须有一种方法可以让对象相互通信,但不知道要搜索什么。
  • 不能 +1 这个足够了。对于希望做任意数量的不同项目以用作基础的任何人来说,这都是一个绝妙的答案
  • 请记住,这种方法(双向链接)的规模非常大(对于只有 4 个具有 N x M 链接的类,将需要 12 个通道)并且其他策略将更合适(例如,本地调度员的单一渠道/供应)。
【解决方案2】:

一种方法是向播放器添加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 添加到枚举中并使用Unknowns 初始化玩家的棋盘。

【讨论】:

  • 那是我的第一次尝试,基本上是添加 Board 作为 Player 的属性,但我不想这样做,因为我认为这会使代码变得复杂,尤其是当我添加更多需要的对象时互相交流。感谢您的帮助。
  • 没有球队计划,实际上只有两名球员互相比赛。我想做的是让Game 对象管理其他不同对象(PlayersBoardShip)之间的所有交互,而不是让对象直接交互。如果使用新类扩展游戏,我的意思是我需要将它作为属性添加到Player
  • 糟糕。我决定删除我的最后一条评论。总之,我认为 something 需要记住 Player 的命中和未命中,并且将其作为玩家对象本身似乎是合理的。但如果你不想要那样,那就够公平了。幸运的是@Takao 知道你的意思。 :)
猜你喜欢
  • 2019-03-13
  • 1970-01-01
  • 1970-01-01
  • 2013-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多