Skip to main content Jump to list of all articles

New Svelte 5 Guessing Game 2024


In this tutorial, you’ll build a fun and interactive guessing game using Svelte 5, the latest version of the lightweight JavaScript framework designed for creating fast and reactive web applications. This game will challenge users to guess a randomly generated number within a certain range, offering real-time feedback on whether their guesses are too high or too low. By leveraging Svelte’s powerful reactivity and efficient state management, you’ll create a smooth user experience with minimal code, making it an ideal project to explore Svelte 5’s new features and capabilities.

Svelte 5 has introduced runes, the equivalent of signals in other frameworks including Solid, Vue and Angular. Signals notify functions when updated, making it a real-time experience by making the system more dynamic. This can reduce the complexity of your app, allow the focus to be more on the components and avoid such opinionated strict syntax.

Svelte 5 is in the release candidate phase and will officially be released once the bugs are ironed out. It is not recommended for production. I have ported my vanilla JavaScript Guessing game to use Svelte 5 for a learning experience.

Prerequisites

An active node installation. If you run MacOS I recommend using NVM which can be installed using homebrew. The guessing game uses TypeScript, Daisy UI and Tailwind. All installation steps are detailed below.

Download

The latest Svelte 5 Guessing Game has been released on 4th October 2024.

Also available on GitHub.

Installation & Svelte 5 Code

Step 1 – Install Svelte 5

Install the latest version of Svelte and run through the installation setup.

npm create svelte@latest
Svelte 5 setup

For this tutorial, I haven’t enabled Vitest or Playwright.

Step 2 – Install Tailwind

Install Tailwind and create a tailwind config file.

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Add the following to the tailwind.config.js file.

/** @type {import('tailwindcss').Config} */
export default {
  content: ['./src/**/*.{html,js,svelte,ts}'],
  theme: {
    extend: {},
  },
  plugins: [],
}

Step 3 – Setup CSS File

Create a CSS file called src/app.css and add the following to it.

@tailwind base;
@tailwind components;
@tailwind utilities;

Now we have a CSS file we need to load it in a layout file. Create a +layout.svelte file inside the routes folder and add the following.

<script>
  import "../app.css";
</script>

<slot />

Step 4 – Install Daisy UI

Now let’s add Daisy UI to our Tailwind installation.

pnpm add -D daisyui@latest

Include Daisy in the tailwind config file. Your config file should look like the following.

import daisyui from 'daisyui';

/** @type {import('tailwindcss').Config} */
export default {
	content: ['./src/**/*.{html,js,svelte,ts}'],
	theme: {
		extend: {}
	},
	plugins: [daisyui]
};

I have included 2 themes in my build. Visit Daisy UI themes if you want to choose your own.

Your tailwind config file now looks like this.

import daisyui from 'daisyui';
export default {
	content: ['./src/**/*.{svelte,js,ts}'],
	plugins: [daisyui],
	daisyui: {
		themes: ['light', 'sunset']
	}
};

In src/app.html add your default theme to your main file.

<!doctype html>

<!-- The following line needs changing -->
<html data-theme="sunset" lang="en">
	
        <head>
		<meta charset="utf-8" />
		<link rel="icon" href="%sveltekit.assets%/favicon.png" />
		<meta name="viewport" content="width=device-width, initial-scale=1" />
		%sveltekit.head%
	</head>
	<body data-sveltekit-preload-data="hover">
		<div style="display: contents">%sveltekit.body%</div>
	</body>
</html>

Step 5 – Layout Page

Add the following code to the src/routes/+layout.svelte file to create the initial layout.

<script lang="ts">
	import '../app.css';
</script>

<div class="hero bg-base-200 min-h-[95vh]">
	<div class="hero-content">
		<div class="w-screen">
			<div class="mb-6 text-5xl font-extrabold font-mono">
				<h1 class="bg-gradient-to-r from-cyan-500 to-blue-500 bg-clip-text
                 text-transparent">
					Svelte 5 Guessing Game
				</h1>
			</div>
			<slot />
		</div>
	</div>
</div>
<footer class="w-screen bg-cover bg-base-200 min-h-[5vh]">
	<p class="text-center">Made by Tracy Ridge</p>
</footer>

Step 6 – Edit Main Page

Open src/routes/+page.svelte and remove any code. From here we will go through the main code.

I used the JoyOfCode code to connect and sync to the browser’s local storage to store the user-selected level.

Inside the src/lib folder create a new file called localStore.svelte.ts and add the following code.

/*With thanks to JoyOfCode*/
import { browser } from '$app/environment';

export class LocalStore<T> {
	value = $state<T>() as T;
	key = '';

	constructor(key: string, value: T) {
		this.key = key;
		this.value = value;

		if (browser) {
			const item = localStorage.getItem(key);
			if (item) this.value = this.deserialize(item);
		}

		$effect(() => {
			localStorage.setItem(this.key, this.serialize(this.value));
		});
	}

	serialize(value: T): string {
		return JSON.stringify(value);
	}

	deserialize(item: string): T {
		return JSON.parse(item);
	}
}

export function localStore<T>(key: string, value: T) {
	return new LocalStore(key, value);
}

Step 7 – Import and Initialise Variables

Import localStore.ts.svelte into your +page.svelte and initialise the variables.

import { localStore } from '$lib/localStore.svelte';

Now we will initialise all of our variables using the new Svelte 5 $state variable.

	// Syncs the current level with browser local storage
	let currentLevel = localStore('currentLevel', 5);

	// Adds a modal when the game ends
	let modal: boolean = $state(false);

	// To focus on the input
	let ref: HTMLElement;

	// The user's guess
	let guess: number | string = $state('');

	// Generates the random number
	let randomNumber: number = $state.raw(Math.floor(Math.random() * (1 - 100) + 100));

	// The initial message to display
	let message: string = $state('Pick a number');

	// Keeps track of the user guesses
	let guesses: number = $state(0);

	// Array to hold previous guesses
	let badges: number[] = $state([]);

Step 8 – Input & Message HTML

Whilst in +page.svelte add the code for the input and the message

<!-- Message -->
<div class="my-6">
	<h2 class="text-4xl font-bold">{message}</h2>
</div>

<!-- User input -->
<div class="my-6">
	<div class="join w-full">
		<input
			type="number"
			min="1"
			max="100"
			maxlength="3"
			bind:this={ref}
			bind:value={guess}
			placeholder="Enter number"
			class="input input-bordered join-item w-5/6"
			onkeydown={handleKeydown}
		/>
		<button onclick={checkNumber} class="btn btn-primary join-item rounded-r-full">Submit</button>
	</div>
</div>
<!-- Informational message -->
<p class="my-2">Hit <kbd class="kbd kbd-md">Enter</kbd> to submit.</p>

Step 9 – Input Functions

Let’s focus on the functions handleKeyDown() and checkNumber()

	//Fires when the user presses enter
	const handleKeydown = (e: KeyboardEvent) => {
		if (e.key === 'Enter') {
			checkNumber();
		}
	};

Most of the work is done during the checkNumber() function. This is fired after pressing the submit button or hitting the enter key. See comments inside the code for details.

const checkNumber = () => {
		guess = Number(guess);
		ref.focus();
		
		//Checks to see if the number is within the boundaries
		if (guess < 1 || guess > 100) {
			message = 'Please enter a number between 1 and 100';
			return;
		}
		
		//Checks to see if the guess has already been checked
		if (badges.includes(guess)) {
			message = 'You have already guessed that number';
			return;
		}
		
		//Increment the guesses
		guesses++;

		/*If you go over your guesses limit your game will come to an end.*/
		if (guesses >= currentLevel.value) {
			message = 
            'You have reached the maximum number of guesses. The number was ' + randomNumber;
			modal = true;
			return;
		}
		//If you are within your limit display a badge
		badges.push(guess);
		if (guess !== randomNumber) {
			if (guess > randomNumber) {
				message = 'You need to go lower 👇';
			} else {
				message = 'You need to go higher 👆';
			}
		} else {
			message = `Super, you guessed right. You guessed it in ${guesses} tries.`;
			modal = true;
		}
		//reset guess
		guess = '';
	};

Step 10 – Game Over Code

Once the game is over the modal will display so you can restart the game.

	const restartGame = () => {
		randomNumber = Math.floor(Math.random() * (1 - 100) + 100);
		guesses = 0;
		badges = [];
		message = 'Pick a number';
		guess = '';
		modal = false;
		ref.focus();
	};

Step 11 – Displaying Badges

Displaying the badges goes underneath the informational message (Step 8).

<!--Displays the badges -->
<div class="my-6">
	{#if badges.length > 0}
		<h2 class="text-xl">Guesses</h2>
		{#each badges as badge}
			<div class="badge badge-secondary mx-2">{badge}</div>
		{/each}
	{/if}
</div>

Step 12 – Game Over Modal HTML

Creating the model when the game is finished

{#if modal}
	<dialog class="modal {modal ? 'modal-open' : ''}">
		<div class="modal-box">
			<h3 class="text-lg font-bold">Game Over</h3>
			<p class="py-4">{message}</p>
			<div class="modal-action">
				<form method="dialog">
					<button onclick={restartGame} class="btn btn-info">Play Again</button>
				</form>
			</div>
		</div>
	</dialog>
{/if}

Step 13 – Focus Input

To focus on the input when it loads we use the new $effect rune which has replaced onMount. This is placed at the bottom of the closing script tag.

	$effect(() => {
		ref.focus();
	});

Step 14 – Level Select

Now we focus on the level select feature. Create a ButtonGroup.svelte file inside of the lib/components.

<script lang="ts">
	type Props = {
		levelSwitch: (val: number) => void;
		currentLevel: number;
	};
	//Sets the values and name for the buttons
	const buttons = [
		{ level: 'Easy', val: 10 },
		{ level: 'Medium', val: 5 },
		{ level: 'Hard', val: 2 }
	];
	
    //These are passed from the root
	let { currentLevel, levelSwitch }: Props = $props();
</script>

<!-- Displays the buttons -->
{#each buttons as b}
	<button
		onclick={() => levelSwitch(b.val)}
		class="btn mr-4 btn-sm {currentLevel === b.val ? 'btn-secondary' : 'btn-accent'}"
	>
		{b.level}
	</button>
{/each}

Step 15 – Once the Button is Fired

There are three buttons and once a button is pressed the value of that button is passed to the levelSwitch() function in +page.svelte. The button colour changes to an active state as a result.

const levelSwitch = (value: number) => {
		if (guesses >= value) {
			message = 'You will lose, please switch to a different level';
			return;
		} else {
			currentLevel.value = value;
		}
};

If the number of guesses is greater than or equal to the value of the button a message will be displayed otherwise the status of the currentLevel in local storage changes.

Step 16 – Import Button Group

Finally, add the ButtonGroup component inside the HTML. I have placed it below the closing script tag.

<ButtonGroup currentLevel={currentLevel.value} {levelSwitch} />

Conclusion

If you compare the code to the vanilla JavaScript guessing game you can see there isn’t as much code. This is because front-end frameworks do a lot of the heavy lifting. I hope you have liked this tutorial. What would you do differently if you were to create your version?


Discover more from WorldOWeb

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.