Посмотрим. Я думаю, что главный вопрос - это вопрос дизайна, так что давайте подойдем к этому с этой точки зрения. Заранее хочу отметить, что я опишу лишь один пример этого подхода: есть много способов сделать это, и я пишу самый простой, который я могу себе представить, который работает. Кроме того, для простоты код, который имеет дело с синхронизацией, постепенным завершением и т. Д., Опущен.
Во-первых, у вас есть игрок, который должен быть отдельным предметом, но в вашем коде он реагирует только тогда, когда он вызывается извне. Когда это выглядит как естественный подход при реализации пошаговых игр, мы все равно хотим иметь какое-то общение. Что делать, если игрок уходит? Что делать, если есть какое-то условие ошибки?
И, как вы отметили, сервер хочет уведомить игрока о результатах игры. Кажется, что мы хотим иметь двунаправленный обмен сообщениями между нашим Сервером и Игроком. Конечно, если существует отношение «Один сервер -> Много игроков», это еще одна сделка, но мы будем простыми.
Давайте подготовим примерный код:
# 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 секунду, чтобы убедиться, что он нам подходит (это хак, чтобы избежать кода согласования в этом уже довольно длинном ответе), и запускаем проигрыватель (будь то эмуляция или реальный ввод, вы можете попробовать оба).
Изменяя обработчики и типы данных, передаваемые между проигрывателем и сервером, вы можете создавать более сложные примеры самостоятельно.