Skip to content

Commit

Permalink
feat: latest wheel wins (#440)
Browse files Browse the repository at this point in the history
Co-authored-by: Nuno Miguel <[email protected]>
  • Loading branch information
ruioliveira02 and nunom27 authored Feb 11, 2025
1 parent 12356a2 commit 082adf4
Show file tree
Hide file tree
Showing 11 changed files with 288 additions and 31 deletions.
93 changes: 84 additions & 9 deletions lib/safira/minigames.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ defmodule Safira.Minigames do
SlotsPayline,
SlotsPaytable,
SlotsReelIcon,
WheelDrop
WheelDrop,
WheelSpin
}

@pubsub Safira.PubSub
Expand Down Expand Up @@ -161,7 +162,10 @@ defmodule Safira.Minigames do
"""
def list_wheel_drops do
Repo.all(WheelDrop)
WheelDrop
|> order_by([wd], asc: wd.probability)
|> Repo.all()
|> Repo.preload([:badge, :prize])
end

@doc """
Expand Down Expand Up @@ -193,9 +197,13 @@ defmodule Safira.Minigames do
"""
def create_wheel_drop(attrs \\ %{}) do
%WheelDrop{}
|> WheelDrop.changeset(attrs)
|> Repo.insert()
result =
%WheelDrop{}
|> WheelDrop.changeset(attrs)
|> Repo.insert()

broadcast_wheel_config_update("drops", list_wheel_drops())
result
end

@doc """
Expand All @@ -211,9 +219,13 @@ defmodule Safira.Minigames do
"""
def update_wheel_drop(%WheelDrop{} = wheel_drop, attrs) do
wheel_drop
|> WheelDrop.changeset(attrs)
|> Repo.update()
result =
wheel_drop
|> WheelDrop.changeset(attrs)
|> Repo.update()

broadcast_wheel_config_update("drops", list_wheel_drops())
result
end

@doc """
Expand All @@ -229,7 +241,9 @@ defmodule Safira.Minigames do
"""
def delete_wheel_drop(%WheelDrop{} = wheel_drop) do
Repo.delete(wheel_drop)
result = Repo.delete(wheel_drop)
broadcast_wheel_config_update("drops", list_wheel_drops())
result
end

@doc """
Expand Down Expand Up @@ -309,6 +323,14 @@ defmodule Safira.Minigames do
end
end

def wheel_latest_wins(count) do
WheelSpin
|> order_by([ws], desc: ws.inserted_at)
|> limit(^count)
|> Repo.all()
|> Repo.preload(attendee: [:user], drop: [:prize, :badge])
end

defp spin_wheel_transaction(attendee) do
Multi.new()
# Fetch the wheel spin price
Expand All @@ -325,10 +347,40 @@ defmodule Safira.Minigames do
|> Multi.merge(fn %{drop: drop, attendee: attendee} ->
drop_reward_action(drop, attendee)
end)
# Add record of the spin transaction to the database
|> Multi.merge(fn %{drop: drop, attendee: attendee} ->
add_spin_action(drop, attendee)
end)
|> Multi.run(:notify, fn _repo, params -> broadcast_spin_changes(params) end)
# Execute the transaction
|> Repo.transaction()
end

defp broadcast_spin_changes(params) do
case broadcast_wheel_win(Map.get(params, :spin)) do
:ok ->
case broadcast_wheel_config_update("drops", list_wheel_drops()) do
:ok -> {:ok, :ok}
e -> e
end

e ->
e
end
end

defp add_spin_action(drop, attendee) do
if is_nil(drop) or (is_nil(drop.badge_id) and is_nil(drop.prize_id)) do
# If there was no prize, or the prize was just tokens, don't insert it
Multi.new()
else
Multi.new()
|> Multi.insert(:spin, fn _ ->
WheelSpin.changeset(%WheelSpin{}, %{drop_id: drop.id, attendee_id: attendee.id})
end)
end
end

defp generate_valid_wheel_drop(attendee) do
drop = generate_wheel_drop()

Expand Down Expand Up @@ -525,6 +577,29 @@ defmodule Safira.Minigames do
Phoenix.PubSub.broadcast(@pubsub, wheel_config_topic(config), {config, value})
end

@doc """
Subscribes the caller to the wheel's wins.
## Examples
iex> subscribe_to_wheel_wins()
:ok
"""
def subscribe_to_wheel_wins do
Phoenix.PubSub.subscribe(@pubsub, "wheel_win")
end

defp broadcast_wheel_win(value) do
value = value |> Repo.preload(attendee: [:user], drop: [:prize, :badge])

if not is_nil(value) and not is_nil(value.drop) and
(not is_nil(value.drop.badge) or not is_nil(value.drop.prize)) do
Phoenix.PubSub.broadcast(@pubsub, "wheel_win", {"win", value})
else
:ok
end
end

# Generates a random number using the Erlang crypto module
defp strong_randomizer do
<<i1::unsigned-integer-32, i2::unsigned-integer-32, i3::unsigned-integer-32>> =
Expand Down
23 changes: 23 additions & 0 deletions lib/safira/minigames/wheel_spin.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
defmodule Safira.Minigames.WheelSpin do
@moduledoc """
Lucky wheel minigame spin result
"""

use Safira.Schema

@required_fields ~w(attendee_id drop_id)a

schema "wheel_spins" do
belongs_to :attendee, Safira.Accounts.Attendee
belongs_to :drop, Safira.Minigames.WheelDrop

timestamps(type: :utc_datetime)
end

@doc false
def changeset(wheel_spin, attrs) do
wheel_spin
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
end
end
59 changes: 59 additions & 0 deletions lib/safira_web/live/app/wheel_live/components/awards.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule SafiraWeb.App.WheelLive.Components.Awards do
@moduledoc """
Lucky wheel awards component.
"""
use SafiraWeb, :component

attr :entries, :list, default: []

def awards(assigns) do
~H"""
<table class="w-full">
<tr class="border-b-2 text-md sm:text-lg">
<th class="pr-2 text-left">Name</th>
<th class="px-4 sm:block hidden text-center">Stock</th>
<th class="px-4 text-center">Max. / Attendee</th>
<th class="pl-2 text-right">Probability</th>
</tr>
<%= for entry <- @entries do %>
<tr class="text-sm sm:text-md">
<td class="pr-2 py-2 font-bold text-left"><%= entry_name(entry) %></td>
<td class="px-4 sm:block hidden py-2 font-bold text-center"><%= entry_stock(entry) %></td>
<td class="px-4 py-2 text-center"><%= entry.max_per_attendee %></td>
<td class="pl-2 py-2 text-accent font-bold text-right">
<%= format_probability(entry.probability) %>
</td>
</tr>
<% end %>
</table>
"""
end

defp entry_stock(drop) do
if is_nil(drop.prize) do
"∞"
else
drop.prize.stock
end
end

defp format_probability(probability) do
"#{probability * 100} %"
end

defp entry_name(drop) do
cond do
not is_nil(drop.prize) ->
drop.prize.name

not is_nil(drop.badge) ->
drop.badge.name

drop.entries > 0 ->
"#{drop.entries} Entries"

drop.tokens > 0 ->
"#{drop.tokens} Tokens"
end
end
end
37 changes: 37 additions & 0 deletions lib/safira_web/live/app/wheel_live/components/latest_wins.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
defmodule SafiraWeb.App.WheelLive.Components.LatestWins do
@moduledoc """
Lucky wheel latest wins component.
"""
use SafiraWeb, :component

attr :entries, :list, default: []

def latest_wins(assigns) do
~H"""
<table class="w-full">
<tr class="border-b-2 text-md sm:text-lg">
<th class="pr-2 text-left"><%= gettext("Attendee") %></th>
<th class="px-4 text-center"><%= gettext("Prize") %></th>
<th class="pl-2 text-right"><%= gettext("When") %></th>
</tr>
<%= for entry <- @entries do %>
<tr class="text-sm sm:text-md">
<td class="pr-2 py-2 font-bold text-left"><%= entry.attendee.user.name %></td>
<td class="px-4 py-2 text-center"><%= entry_name(entry) %></td>
<td class="pl-2 py-2 text-accent font-bold text-right">
<%= Timex.from_now(entry.inserted_at) %>
</td>
</tr>
<% end %>
</table>
"""
end

defp entry_name(entry) do
if is_nil(entry.drop.badge) do
entry.drop.prize.name
else
entry.drop.badge.name
end
end
end
2 changes: 1 addition & 1 deletion lib/safira_web/live/app/wheel_live/components/wheel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defmodule SafiraWeb.App.WheelLive.Components.Wheel do
<div
id="wheel"
class="h-full w-full rounded-full drop-shadow-[0_0px_10px_rgba(0,0,0,0.7)]"
style="background: conic-gradient(#ffdb0d,#bfa408,#ffdb0d,#bfa408,#ffdb0d);"
style="background: conic-gradient(#ffdb0d,#ffe866,#ffdb0d,#ffe866,#ffdb0d);"
>
<%= for i <- 0..@slices do %>
<div
Expand Down
27 changes: 26 additions & 1 deletion lib/safira_web/live/app/wheel_live/index.ex
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
defmodule SafiraWeb.App.WheelLive.Index do
use SafiraWeb, :app_view

import SafiraWeb.App.WheelLive.Components.LatestWins
import SafiraWeb.App.WheelLive.Components.Awards
import SafiraWeb.App.WheelLive.Components.ResultModal
import SafiraWeb.App.WheelLive.Components.Wheel

alias Safira.{Contest, Minigames}

@max_wins 6

@impl true
def mount(_params, _session, socket) do
if socket.assigns.current_user.attendee.ineligible do
Expand All @@ -17,6 +21,8 @@ defmodule SafiraWeb.App.WheelLive.Index do
if connected?(socket) do
Minigames.subscribe_to_wheel_config_update("price")
Minigames.subscribe_to_wheel_config_update("is_active")
Minigames.subscribe_to_wheel_config_update("drops")
Minigames.subscribe_to_wheel_wins()
end

{:ok,
Expand All @@ -26,7 +32,9 @@ defmodule SafiraWeb.App.WheelLive.Index do
|> assign(:attendee_tokens, socket.assigns.current_user.attendee.tokens)
|> assign(:wheel_price, Minigames.get_wheel_price())
|> assign(:result, nil)
|> assign(:wheel_active?, Minigames.wheel_active?())}
|> assign(:wheel_active?, Minigames.wheel_active?())
|> assign(:latest_wins, Minigames.wheel_latest_wins(@max_wins))
|> assign(:drops, Minigames.list_wheel_drops())}
end
end

Expand Down Expand Up @@ -94,6 +102,23 @@ defmodule SafiraWeb.App.WheelLive.Index do
{:noreply, socket |> assign(:wheel_active?, value)}
end

@impl true
def handle_info({"drops", value}, socket) do
{:noreply, socket |> assign(:drops, value)}
end

@impl true
def handle_info({"win", value}, socket) do
{:noreply,
socket
|> assign(:latest_wins, merge_wins(socket.assigns.latest_wins, value))}
end

defp merge_wins(latest_wins, new_win) do
([new_win] ++ latest_wins)
|> Enum.take(@max_wins)
end

defp can_spin?(wheel_active?, tokens, price, in_spin?) do
!in_spin? && wheel_active? && tokens >= price
end
Expand Down
38 changes: 29 additions & 9 deletions lib/safira_web/live/app/wheel_live/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,35 @@
💰 <%= @attendee_tokens %>
</span>
</:actions>
<.wheel />
<div class="flex flex-row justify-center w-full pt-16">
<.action_button
title={gettext("Spin")}
subtitle={"💰 #{@wheel_price}"}
class="!w-96"
disabled={!can_spin?(@wheel_active?, @attendee_tokens, @wheel_price, @in_spin?)}
phx-click="spin-wheel"
/>

<div class="grid grid-cols-1 xl:grid-cols-2 xl:gap-10">
<div class="mt-12">
<div>
<h2 class="text-2xl font-terminal uppercase font-bold mb-8">
<%= gettext("Spin To Win!") %>
</h2>
</div>
<.wheel />
<div class="flex flex-row justify-center w-full pt-16">
<.action_button
title={gettext("Spin")}
subtitle={"💰 #{@wheel_price}"}
class="w-64"
disabled={!can_spin?(@wheel_active?, @attendee_tokens, @wheel_price, @in_spin?)}
phx-click="spin-wheel"
/>
</div>
</div>
<div class="mt-12">
<h2 class="text-2xl font-terminal uppercase font-bold mb-8">
<%= gettext("Latest Wins") %>
</h2>
<.latest_wins entries={@latest_wins} />
<h2 class="text-2xl font-terminal uppercase font-bold mt-24 mb-8">
<%= gettext("Awards") %>
</h2>
<.awards entries={@drops} />
</div>
</div>
</.page>

Expand Down
Loading

0 comments on commit 082adf4

Please sign in to comment.