Skip to content

Instantly share code, notes, and snippets.

@nileshtrivedi
Created July 9, 2025 07:52
Show Gist options
  • Save nileshtrivedi/29f0b5b7b660e9cfd6ea0bbe87792475 to your computer and use it in GitHub Desktop.
Save nileshtrivedi/29f0b5b7b660e9cfd6ea0bbe87792475 to your computer and use it in GitHub Desktop.
distributed reactivity example
defmodule ExampleWeb.LiveSigil do
use ExampleWeb, :live_view
def render(assigns) do
~V"""
<script>
import { onMount } from 'svelte';
// props sent from the server
let { shown_word, leaderboard, live } = $props();
// Client-Side State using Runes
let name = $state('');
let user_guess = $state(''); // e.g., "ACB"
let status_message = $state(''); // Feedback from the server
let error_message = $state(''); // Client-side validation errors
onMount(() => {
live.handleEvent("feedback", (payload) => {
// This will be called when the server pushes feedback
status_message = payload.message;
});
});
function handleSubmit(){
// --- Client-Side Validation ---
// Assignments to $state variables automatically trigger updates.
error_message = '';
status_message = '';
if (name.trim() === '') {
error_message = 'Please enter your name.';
return;
}
if (user_guess.length != shown_word.length) {
error_message = 'Word length is not correct.';
return;
}
if (user_guess.split('').sort().join('') != shown_word.split('').sort().join('')) {
error_message = 'Letters are not correct.';
return;
}
// `pushEvent` sends the data to the Phoenix LiveView.
live.pushEvent("submit_answer", {name: name, attempt: user_guess});
}
</script>
<h1 class="text-2xl font-bold mb-4">Sorting Guessing Game</h1>
<p class="prose">The server has put these letters <b>{shown_word}</b> in some random order which you need to guess. Correct guess earns you 10 points.</p>
<!-- Guess Word -->
<div class="space-y-4 mb-6">
<div class="flex items-center space-x-4 bg-gray-50 p-3 rounded-lg">
<input
id="name"
type="text"
class="w-48 p-2 border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Your name"
bind:value={name}
/>
<input
type="text"
min="1"
max="{shown_word.length}"
class="w-48 p-2 text-center border border-gray-300 rounded-lg shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
placeholder="Your guess"
bind:value={user_guess}
/>
<button
class="w-full sm:w-auto px-6 py-3 bg-indigo-600 text-white font-bold rounded-lg shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors duration-200"
onclick={handleSubmit}
>
Submit Answer
</button>
</div>
</div>
<!-- Status/Error Messages -->
{#if status_message}
<div class="mt-4 p-3 rounded-lg text-center" class:bg-green-100={status_message.includes('Correct')} class:text-green-800={status_message.includes('Correct')} class:bg-red-100={!status_message.includes('Correct')} class:text-red-800={!status_message.includes('Correct')}>
{status_message}
</div>
{/if}
{#if error_message}
<div class="mt-4 p-3 rounded-lg bg-yellow-100 text-yellow-800 text-center">
{error_message}
</div>
{/if}
<!-- Leaderboard Section -->
<div class="bg-white p-6 sm:p-8 rounded-2xl shadow-lg">
<h2 class="text-2xl font-bold text-gray-800 mb-4">Leaderboard</h2>
<div class="space-y-3">
{#if Object.keys(leaderboard).length > 0}
<!-- Sort leaderboard by score descending -->
{#each Object.entries(leaderboard).sort((a, b) => b[1] - a[1]) as [name, score]}
<div class="flex justify-between items-center bg-gray-50 p-3 rounded-lg">
<span class="font-medium text-gray-700">{name}</span>
<span class="font-bold text-indigo-600">{score} pts</span>
</div>
{/each}
{:else}
<p class="text-gray-500">No scores yet. Be the first!</p>
{/if}
</div>
</div>
"""
end
@doc """
Mounts the LiveView, initializing the server-side state.
"""
def mount(_params, _session, socket) do
# On mount, we shuffle the items and set the initial state.
new_secret_word = Enum.shuffle(String.split("ABC","", trim: true)) |> Enum.join
socket =
assign(socket,
secret_word: new_secret_word,
shown_word: "ABC",
leaderboard: %{}
)
{:ok, socket}
end
@doc """
Handles the `submit_answer` event pushed from the Svelte client.
"""
def handle_event("submit_answer", %{"name" => name, "attempt" => attempt}, socket) do
# All game logic happens securely on the server.
if socket.assigns.secret_word == attempt do
# Correct Answer
new_leaderboard = Map.update(socket.assigns.leaderboard, name, 10, &(&1 + 10))
# Push a feedback event to the client.
socket = push_event(socket, "feedback", %{message: "Correct! You earned 10 points."})
# Reset the game for a new round
new_secret_word = Enum.shuffle(String.split("ABC","", trim: true)) |> Enum.join
new_socket =
assign(socket,
secret_word: new_secret_word,
shown_word: "ABC",
leaderboard: new_leaderboard
)
{:noreply, new_socket}
else
# Incorrect Answer
socket = push_event(socket, "feedback", %{message: "Not quite, try again!"})
{:noreply, socket}
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment