improving game
This commit is contained in:
@@ -8,7 +8,7 @@ defmodule Backend.Application do
|
||||
BackendWeb.Telemetry,
|
||||
{Phoenix.PubSub, name: Backend.PubSub},
|
||||
Backend.Cluster,
|
||||
{Backend.GlobalSingleton, Backend.GameState},
|
||||
{Backend.GlobalSingleton, Backend.GameRunner},
|
||||
BackendWeb.Endpoint
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
defmodule Backend.GameState do
|
||||
defmodule Backend.GameRunner do
|
||||
@moduledoc """
|
||||
GenServer to track all players and their positions in the game.
|
||||
Uses :global registry for distributed singleton pattern - only one instance
|
||||
@@ -27,7 +27,7 @@ defmodule Backend.GameState do
|
||||
|
||||
def add_player(player_id) do
|
||||
Logger.info("Player #{player_id} connected")
|
||||
GenServer.call(@name, {:add_player, player_id})
|
||||
GenServer.cast(@name, {:add_player, player_id})
|
||||
end
|
||||
|
||||
def remove_player(player_id) do
|
||||
@@ -56,24 +56,27 @@ defmodule Backend.GameState do
|
||||
sleep_delay = round(1000 / @fps)
|
||||
:timer.send_interval(sleep_delay, :tick)
|
||||
|
||||
{:ok, %{}}
|
||||
{:ok, %{players: %{}, tick_number: 0}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:add_player, player_id}, _from, state) do
|
||||
def handle_cast({:add_player, player_id}, _from, state) do
|
||||
# Start players at random position
|
||||
x = :rand.uniform(800)
|
||||
y = :rand.uniform(600)
|
||||
|
||||
new_state = Map.put(state, player_id, %{x: x, y: y, keys_pressed: []})
|
||||
broadcast_state(new_state)
|
||||
{:reply, new_state, new_state}
|
||||
new_state = %{
|
||||
state
|
||||
| players: state.players |> Map.put(player_id, %{x: x, y: y, keys_pressed: []})
|
||||
}
|
||||
|
||||
{:noreply, broadcast_state(new_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_cast({:update_player_keys, player_id, keys_pressed}, state) do
|
||||
player =
|
||||
case Map.get(state, player_id) do
|
||||
case Map.get(state.players, player_id) do
|
||||
nil ->
|
||||
Logger.warning(
|
||||
"Key update for non-existent player: #{player_id}, creating at random position"
|
||||
@@ -85,18 +88,23 @@ defmodule Backend.GameState do
|
||||
Map.put(existing_player, :keys_pressed, keys_pressed)
|
||||
end
|
||||
|
||||
new_state = Map.put(state, player_id, player)
|
||||
broadcast_state(new_state)
|
||||
{:noreply, new_state}
|
||||
new_state = %{state | players: Map.put(state.players, player_id, player)}
|
||||
|
||||
{:noreply, broadcast_state(new_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_info(:tick, state) do
|
||||
# On each tick, move players based on their keys_pressed
|
||||
new_state =
|
||||
Enum.reduce(state, state, fn {player_id, player}, acc ->
|
||||
if rem(state.tick_number, 100) == 0 do
|
||||
Logger.info("Game tick #{state.tick_number} on #{node()}, updating player positions")
|
||||
end
|
||||
|
||||
step = 10
|
||||
|
||||
new_players =
|
||||
state.players
|
||||
|> Enum.map(fn {player_id, player} ->
|
||||
directions = player.keys_pressed || []
|
||||
step = 10
|
||||
|
||||
new_player =
|
||||
Enum.reduce(directions, player, fn direction, acc ->
|
||||
@@ -109,11 +117,13 @@ defmodule Backend.GameState do
|
||||
end
|
||||
end)
|
||||
|
||||
Map.put(acc, player_id, new_player)
|
||||
{player_id, new_player}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
broadcast_state(new_state)
|
||||
{:noreply, new_state}
|
||||
new_state = %{state | players: new_players, tick_number: state.tick_number + 1}
|
||||
|
||||
{:noreply, broadcast_state(new_state)}
|
||||
end
|
||||
|
||||
@impl true
|
||||
@@ -123,14 +133,14 @@ defmodule Backend.GameState do
|
||||
|
||||
@impl true
|
||||
def handle_cast({:remove_player, player_id}, state) do
|
||||
new_state = Map.delete(state, player_id)
|
||||
broadcast_state(new_state)
|
||||
{:noreply, new_state}
|
||||
end
|
||||
players = Map.delete(state.players, player_id)
|
||||
new_state = %{state | players: players}
|
||||
|
||||
# Private Functions
|
||||
{:noreply, broadcast_state(new_state)}
|
||||
end
|
||||
|
||||
defp broadcast_state(state) do
|
||||
Phoenix.PubSub.broadcast(@pubsub, @topic, {:game_state_updated, state})
|
||||
state
|
||||
end
|
||||
end
|
||||
@@ -26,24 +26,35 @@ defmodule Backend.GlobalSingleton do
|
||||
defp monitor_loop(module) do
|
||||
case :global.whereis_name(module) do
|
||||
:undefined ->
|
||||
Logger.info("#{module} not running, attempting to start on #{node()}")
|
||||
# Double-check before attempting to start
|
||||
Process.sleep(50)
|
||||
|
||||
case module.start_link([]) do
|
||||
{:ok, _pid} ->
|
||||
Logger.info("#{module} started on #{node()}")
|
||||
case :global.whereis_name(module) do
|
||||
:undefined ->
|
||||
Logger.info("#{module} not running, attempting to start on #{node()}")
|
||||
|
||||
{:error, {:already_started, _pid}} ->
|
||||
Logger.debug("#{module} already started by another node")
|
||||
case module.start_link([]) do
|
||||
{:ok, _pid} ->
|
||||
Logger.info("#{module} started on #{node()}")
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
{:error, {:already_started, _pid}} ->
|
||||
Logger.debug("#{module} already started by another node")
|
||||
|
||||
_ ->
|
||||
:ok
|
||||
end
|
||||
|
||||
Process.sleep(100)
|
||||
monitor_loop(module)
|
||||
|
||||
_pid ->
|
||||
# Another node won the race
|
||||
monitor_loop(module)
|
||||
end
|
||||
|
||||
Process.sleep(100)
|
||||
monitor_loop(module)
|
||||
|
||||
pid when is_pid(pid) ->
|
||||
ref = Process.monitor(pid)
|
||||
|
||||
receive do
|
||||
{:DOWN, ^ref, :process, ^pid, _reason} ->
|
||||
Logger.warning("#{module} went down, attempting takeover")
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
defmodule Backend.Mailer do
|
||||
use Swoosh.Mailer, otp_app: :backend
|
||||
end
|
||||
18
backend/lib/backend_web/channels/cluster_status_channel.ex
Normal file
18
backend/lib/backend_web/channels/cluster_status_channel.ex
Normal file
@@ -0,0 +1,18 @@
|
||||
defmodule BackendWeb.ClusterStatusChannel do
|
||||
@moduledoc """
|
||||
Channel for cluster status information
|
||||
"""
|
||||
use BackendWeb, :channel
|
||||
require Logger
|
||||
|
||||
@impl true
|
||||
def join("clusterstatus", _params, socket) do
|
||||
Logger.info("Client joined clusterstatus channel")
|
||||
{:ok, %{status: "connected"}, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_in(_event, _payload, socket) do
|
||||
{:noreply, socket}
|
||||
end
|
||||
end
|
||||
@@ -20,7 +20,7 @@ defmodule BackendWeb.ConnectedUserChannel do
|
||||
:new_browser_connection
|
||||
)
|
||||
|
||||
current_state = Backend.GameState.get_state()
|
||||
current_state = Backend.GameRunner.get_state()
|
||||
{:ok, %{game_state: current_state}, socket}
|
||||
end
|
||||
|
||||
@@ -55,7 +55,7 @@ defmodule BackendWeb.ConnectedUserChannel do
|
||||
|> assign(:player_name, name)
|
||||
|> assign(:keys_pressed, MapSet.new())
|
||||
|
||||
Backend.GameState.add_player(name)
|
||||
Backend.GameRunner.add_player(name)
|
||||
|
||||
{:reply, :ok, socket}
|
||||
end
|
||||
@@ -75,7 +75,7 @@ defmodule BackendWeb.ConnectedUserChannel do
|
||||
"Player '#{player_name}' key down: #{key}, keys: #{inspect(MapSet.to_list(keys_pressed))}"
|
||||
)
|
||||
|
||||
Backend.GameState.update_player_keys(player_name, MapSet.to_list(keys_pressed))
|
||||
Backend.GameRunner.update_player_keys(player_name, MapSet.to_list(keys_pressed))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
@@ -96,7 +96,7 @@ defmodule BackendWeb.ConnectedUserChannel do
|
||||
"Player '#{player_name}' key up: #{key}, keys: #{inspect(MapSet.to_list(keys_pressed))}"
|
||||
)
|
||||
|
||||
Backend.GameState.update_player_keys(player_name, MapSet.to_list(keys_pressed))
|
||||
Backend.GameRunner.update_player_keys(player_name, MapSet.to_list(keys_pressed))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
@@ -110,7 +110,7 @@ defmodule BackendWeb.ConnectedUserChannel do
|
||||
|
||||
player_name ->
|
||||
Logger.info("Player '#{player_name}' disconnected from #{node()}")
|
||||
Backend.GameState.remove_player(player_name)
|
||||
Backend.GameRunner.remove_player(player_name)
|
||||
end
|
||||
|
||||
:ok
|
||||
|
||||
@@ -5,6 +5,7 @@ defmodule BackendWeb.UserSocket do
|
||||
|
||||
## Channels
|
||||
channel("user:*", BackendWeb.ConnectedUserChannel)
|
||||
channel("clusterstatus", BackendWeb.ClusterStatusChannel)
|
||||
|
||||
@impl true
|
||||
def connect(%{"user_name" => user_name}, socket, _connect_info) do
|
||||
|
||||
@@ -24,7 +24,6 @@ defmodule BackendWeb.Router do
|
||||
pipe_through([:fetch_session, :protect_from_forgery])
|
||||
|
||||
live_dashboard("/dashboard", metrics: BackendWeb.Telemetry)
|
||||
forward("/mailbox", Plug.Swoosh.MailboxPreview)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user