对象之间的通信

问题描述 投票:8回答:2

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


perl6 raku
2个回答
12
投票

让我们来看看。我认为这里的主要问题是设计问题,所以让我们从这个角度来看待它。我想事先注意一下,我将仅描述这种方法的一个例子:有很多方法可以做到这一点,我写的是我能想象的最简单的方法。而且,为了简单起见,省略了处理同步,正常终止等的代码。

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

正如您所指出的那样,服务器希望通知玩家游戏的结果。看起来我们希望在服务器和播放器之间进行双向消息传递。当然,如果有一个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之间发送的处理程序和数据类型,您可以自己构建更复杂的示例。


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添加到枚举中并使用Unknowns初始化玩家的棋盘。

© www.soinside.com 2019 - 2024. All rights reserved.