1

I'm creating a card game in Phoenix with 2 players using Phoenix channels and GenServer. This is what my Game Struct looks like:

  schema "games" do
    field :winner, :integer
    field :player_1, :id, default: nil
    field :player_2, :id, default: nil
    field :status, :string

    ## VIRTUAL FIELDS ##
    field :player_1_hand, :map, virtual: :true
    field :player_2_hand, :map, virtual: :true

    timestamps()
  end

So, as you can see I'll handle the players hands with virtual fields and then persist the players and the winner to the database.

I have a Lobby channel set up now where players can chat. I have a game channel with no functionality. How do I allow players to invite each other to play games, accept or decline, and then put both players into a game together?

This is what I have so far in my lobby channel (regarding games):

  def handle_in("game_invite", %{"username" => username}, socket) do
    data = %{"username" => username, "sender" => socket.assigns.current_player.username }
    broadcast! socket, "game_invite", data
    {:noreply, socket}
  end

  intercept ["game_invite"]
def handle_out("game_invite", %{"username" => username, "sender" => sender}, socket) do
  if socket.assigns.current_player.username == username do
    push socket, "game_invite", %{ username: sender}
  end
  {:noreply, socket}
end

I don't know if I'm even asking the right questions. I'm trying to generate a new game ID (from postgres) put two player ID's in the game, and then let the GameServer use GenServer to manage the player hands.

Frederick John
  • 517
  • 2
  • 6
  • 14

1 Answers1

0

This is going to be a pseudo answer, because you also made a pseudo question; one way of doing it:

assuming you have a user channel through which you can send socket events to each individual player (or any other way of reaching a player individually)

Player_1 picks a Player_2 from a list, this sends a socket event representing that, like { action: 'create_invitation', invitee: 'player_2' }, then you have a handle_in(%{'action' => 'create_invitation', 'invitee': invitee})

On this handle_in you check if the player making the invitation doesn't have any open game yet, and isn't playing a game either, if so, first you assign to its socket the info that he now has a game with an ID of, e.g., "#{socket.current_player}#{invitee.id}", and you initiate a new gen_server with the global name being that id, passing to the start function the player inviting id and the invitee id.

On the gen_server init, you broadcast a message to player_2, asking if he wants to join the game with the global id you set before, and set its initial state to, e.g.: %{ player_1: player_1_id, player_2: invitee_id, game_id: "#{player_1_id}#{player_2_id}", started: false } And you set a sensible timeout for this genserver to receive a reply, say 25 seconds, by returning from the init function: {:ok, state, 25_000}

Now on player_2's front-end you need to take this received broadcast stating he was invited and turn it into an actionable thing, that will send a message to the lobby channel, like %{action: "accept", game_id: "player_1_idplayer_2_id"}. You make all checks to see if it's valid, this player has no game yet, and if so set in his socket that he now has a game, then call the previously initiated gen_server with, e.g. GenServer.call(game_id,{:accept_game, player_2_id})

and then on the gen_server you have a handle_call

def handle_call({:accept_game, player_2_id}, _from, %{started: false, player_2: p2_id} = state) when player_2_id == p2_id do
  #create game hands, broadcasts, etc
  {:reply, :ok, Map.put(state, :started, true)}
end

You need to create one handle_info at least to handle the invitation expiring - which should cleanly terminate the gen_server, and trigger a cleanup of player_1's socket, etc...

m3characters
  • 2,240
  • 2
  • 14
  • 18