それが問題です!すべてを1つのプロセスに保持するか、管理する必要のある状態ごとに個別のプロセスを作成する方がよいでしょうか。この記事では、プロセスを使用するかどうかについて少し説明します。また、複雑なステートフルロジックを時間的動作やプロセス間通信などの問題から切り離す方法についても説明します。
しかし、始める前に、記事が長くなるので、要点を概説したいと思います。
関数とモジュールを使用して、思考エンティティを分離します。
プロセスを使用して、ランタイムエンティティを分離します。
思考の実体を分離するためにプロセス(エージェントでさえ)を使用しないでください。
ここでの「思考エンティティ」の構成は、「順序」、「順序の位置」、「製品」など、私たちの頭の中にあるアイデアを指します。これらの概念が複雑すぎる場合は、次の場所に実装する価値があります。モジュールと関数を分離して、異なるエンティティを分離し、コードのすべての部分に焦点を合わせて一貫性を保ちます。
このためにプロセス(エージェントなど)を使用することは、人々がよく犯す間違いです。このアプローチは、Elixirの機能を大幅に見逃し、代わりにプロセスによってオブジェクトを模倣しようとします。実装は、単純な機能的アプローチ(または同等のオブジェクト指向プログラミング言語)よりも悪い可能性があります。したがって、プロセスから具体的なメリットがある場合にのみ、プロセスに目を向ける価値があります。コード編成はそれらの利点の1つではないため、プロセスを使用する理由にはなりません。
- , . , , , . - , . . , , - .
- , , , . . , : () (). . 21, . ( ).
- , (2-10) , , 10. 1 11, , ( ) .
, . , . , , .
, , , , (), , , .
, , : , . - . , «» , . , , , . , . : , , , . .
, , . , . . , . : , , ( ). , , , , , .
, . . , , . , . ( ) , , ( ) ( ). , , :-) (. : , ?)
, , . , , .
, , , - . , , , , . , , , :-)
, ? , . , , , , .
, , , .
, - . 52 . , .
, , . , , . , .
. , . :
@cards (
for suit <- [:spades, :hearts, :diamonds, :clubs],
rank <- [2, 3, 4, 5, 6, 7, 8, 9, 10, :jack, :queen, :king, :ace],
do: %{suit: suit, rank: rank}
)
shuffle/0
:
def shuffled(), do: Enum.shuffle(@cards)
, take/1
, :
def take([card | rest]), do: {:ok, card, rest} def take([]), do: {:error, :empty}
take/1
{:ok, card_taken, rest_of_the_deck}
, {:error, :empty}
. ( ) , .
:
deck = Blackjack.Deck.shuffled()
case Blackjack.Deck.take(deck) do
{:ok, card, transformed_deck} ->
# do something with the card and the transform deck
{:error, :empty} ->
# deck is empty -> do something else
end
, « », :
,
,
,
, - . - Deck
, Deck
. ( ), , ( , , , - . .)
, . . shuffled_deck/0
take_card/1
. , , , . , - . (. : , )
, . , .
. . (:ok
:busted
). Blackjack.Hand.
. new/0
, deal/2
, . :
# create a deck deck = Blackjack.Deck.shuffled() # create a hand hand = Blackjack.Hand.new() # draw one card from the deck {:ok, card, deck} = Blackjack.Deck.take(deck) # give the card to the hand result = Blackjack.Hand.deal(hand, card)
deal/2
{hand_status, transformed_hand}
, hand_status
:ok
:busted
.
, Blackjack.Round, . :
,
( / )
,
, . , . . , , , , , . , , .
, , /, GenServer
:gen_statem
. (, ) .
, , . , , , , . , (netsplits), , . , , , event sourcing - .
, , . .
, . .
. , start/1
:
{instructions, round} = Blackjack.Round.start([:player_1, :player_2])
, , - . , :
. - . :
[ {:notify_player, :player_1, {:deal_card, %{rank: 4, suit: :hearts}}}, {:notify_player, :player_1, {:deal_card, %{rank: 8, suit: :diamonds}}}, {:notify_player, :player_1, :move} ]
- , , . , , . , :
1,
1,
1,
. , ,
GenServer
, . , , . () ,Round
.
, round
, . , . , round
. , , instruction
.
, 1:
{instructions, round} = Blackjack.Round.move(round, :player_1, :hit)
, , . , , .
, :
[
{:notify_player, :player_1, {:deal_card, %{rank: 10, suit: :spades}}},
{:notify_player, :player_1, :busted},
{:notify_player, :player_2, {:deal_card, %{rank: :ace, suit: :spades}}},
{:notify_player, :player_2, {:deal_card, %{rank: :jack, suit: :spades}}},
{:notify_player, :player_2, :move}
]
, 1 . 4 8 , , . 2 , , .
2:
{instructions, round} = Blackjack.Round.move(round, :player_2, :stand) # instructions: [ {:notify_player, :player_1, {:winners, [:player_2]}} {:notify_player, :player_2, {:winners, [:player_2]}} ]
2 , . .
, Round
Deck
Hand
. Round
:
defp deal(round) do
{:ok, card, deck} =
with {:error, :empty} <- Blackjack.Deck.take(round.deck), do:
Blackjack.Deck.take(Blackjack.Deck.shuffled())
{hand_status, hand} = Hand.deal(round.current_hand, card)
round =
%Round{round | deck: deck, current_hand: hand}
|> notify_player(round.current_player_id, {:deal_card, card})
{hand_status, round}
end
, , . , , (:ok
:busted
) . :-)
notify_player
- , . (, GenServer Phoenix). - , . , , .
, , Round
. notify_player
. , , take_instructions
Round
, , .
. , . , , . , , .
Blackjack.RoundServer, GenServer
. Agent
, , GenServer
. , , :-)
, start_playing/2
. start_link
, start_link
. , start_playing
- , .
: . - , . .
, :
@type player :: %{id: Round.player_id, callback_mod: module, callback_arg: any}
, . . , , callback_mod.some_function (some_arguments)
, some_arguments
, , callback_arg
, .
callback_mod
, :
, HTTP
, TCP
iex
()
. , .
@callback deal_card(RoundServer.callback_arg, Round.player_id, Blackjack.Deck.card) :: any @callback move(RoundServer.callback_arg, Round.player_id) :: any @callback busted(RoundServer.callback_arg, Round.player_id) :: any @callback winners(RoundServer.callback_arg, Round.player_id, [Round.player_id]) :: any @callback unauthorized_move(RoundServer.callback_arg, Round.player_id) :: any
, . , . . , , , , .
- , . . asserting/refuting , RoundServer.move/3
, .
Round
, , .
. , . - , . , , . , , . , .
Blackjack.PlayerNotifier, GenServer
, - . start_playing/2
, .
, . , //(M/F/A) .
, , (, , ). , . :
[
{:notify_player, :player_1, {:deal_card, %{rank: 10, suit: :spades}}},
{:notify_player, :player_1, :busted},
{:notify_player, :player_2, {:deal_card, %{rank: :ace, suit: :spades}}},
{:notify_player, :player_2, {:deal_card, %{rank: :jack, suit: :spades}}},
{:notify_player, :player_2, :move}
]
, player_2
, player_1
, . , . , , , , .
, : Round
, . .
OTP :blackjack
( Blackjack). , : Registry
( ) :simple_one_for_one
, .
, . . Phoenix, Cowboy, Ranch ( TCP), elli , . , .
Demo, , , GenServer, , :
$ iex -S mix
iex(1)> Demo.run
player_1: 4 of spades
player_1: 3 of hearts
player_1: thinking ...
player_1: hit
player_1: 8 of spades
player_1: thinking ...
player_1: stand
player_2: 10 of diamonds
player_2: 3 of spades
player_2: thinking ...
player_2: hit
player_2: 3 of diamonds
player_2: thinking ...
player_2: hit
player_2: king of spades
player_2: busted
...
, , :
, ? , ! , Deck
and Hand
, .
, . , . , . . , .
/ , . , , ( ), - . , . - , , . (netsplits), , - .
最後に、最終的な目標を覚えておく価値があります。私は(まだ)そこに行きませんでしたが、私は常にこのコードが何らかのWebサーバーでホストされることを計画していました。したがって、このシナリオをサポートするためにいくつかの決定が行われます。特に、RoundServer
プレーヤーごとにコールバックモジュールを受け入れる実装により、さまざまなテクノロジーを使用してさまざまなタイプのクライアントに接続できます。これにより、ブラックジャックサービスは特定のライブラリやフレームワークから独立し(もちろん、標準ライブラリとOTPを除く)、完全に柔軟になります。