A fun and interactive Rock Paper Scissors game built with SvelteKit, TypeScript, and Bun! Perfect for learning Svelte basics.
Make sure you have Bun installed on your computer.
# Install dependencies
bun install
# Start the development server
bun run dev
# Open in browser (usually http://localhost:5173)
This guide explains how the game works, perfect for junior high students learning Svelte!
Svelte is a modern way to build websites. Unlike other frameworks, Svelte turns your code into tiny, fast JavaScript when you build your app.
SvelteKit is a tool that helps you build complete web applications with Svelte. It handles:
/ to /pvp)src/
āāā routes/ # Your pages go here!
ā āāā +page.svelte # Home page (/)
ā āāā pvp/
ā ā āāā +page.svelte # PvP mode page (/pvp)
ā āāā cpu/
ā āāā +page.svelte # vs CPU page (/cpu)
āāā app.html # Main HTML template
āāā lib/ # Reusable code (empty for now)
Important: In SvelteKit, files named +page.svelte automatically become pages!
routes/+page.svelte ā becomes the / pageroutes/pvp/+page.svelte ā becomes the /pvp pageA Svelte file (.svelte) has 3 sections:
<script>
// JavaScript code goes here
// Variables, functions, game logic
</script>
<!-- HTML goes here -->
<!-- What the user sees -->
<style>
/* CSS styling goes here */
/* How things look */
</style>
Let's break down each section!
File: src/routes/+page.svelte
<script lang="ts">
import { goto } from '$app/navigation';
function startPvP() {
goto('/pvp');
}
function startVsCPU() {
goto('/cpu');
}
</script>
What's happening?
lang="ts" means we're using TypeScript (JavaScript with extra type checking)import { goto } brings in a function to navigate between pagesstartPvP() and startVsCPU() are functions that take us to different pages<div class="container">
<h1>šŖØšāļø</h1>
<h2>Rock Paper Scissors</h2>
<div class="button-group">
<button class="mode-button" on:click={startPvP}>
š„ Player vs Player
</button>
<button class="mode-button" on:click={startVsCPU}>
š¤ Player vs CPU
</button>
</div>
</div>
New Svelte concept: on:click={startPvP}
startPvP functiononclick but more powerful!<style>
.container {
text-align: center;
background: white;
padding: 3rem 2rem;
border-radius: 20px;
/* More styles... */
}
</style>
Special: Styles in Svelte are scoped by default!
File: src/routes/pvp/+page.svelte
let player1Choice: Choice = null;
let player2Choice: Choice = null;
let scores = { player1: 0, player2: 0 };
In Svelte, regular let variables are reactive:
type Choice = 'rock' | 'paper' | 'scissors' | null;
This TypeScript code says: "A Choice can ONLY be one of these values"
{#if !player1Choice}
<div>Player 1's Turn</div>
{:else if showPlayer2 && !player2Choice}
<div>Player 2's Turn</div>
{:else if result}
<div>Show Results</div>
{/if}
Svelte's special syntax:
{#if condition} - Show this if true{:else if condition} - Otherwise, check this{:else} - Otherwise, show this{/if} - End of if block{#each choices as choice}
<button on:click={() => selectPlayer1(choice)}>
<span>{emojis[choice]}</span>
<span>{choice}</span>
</button>
{/each}
What's happening?
{#each array as item} loops through an arrayfunction determineWinner() {
if (player1Choice === player2Choice) {
result = "It's a Tie! š¤";
} else if (
(player1Choice === 'rock' && player2Choice === 'scissors') ||
(player1Choice === 'scissors' && player2Choice === 'paper') ||
(player1Choice === 'paper' && player2Choice === 'rock')
) {
result = 'Player 1 Wins! š';
scores.player1++; // Automatically updates the display!
} else {
result = 'Player 2 Wins! š';
scores.player2++;
}
}
How Rock Paper Scissors works:
When we update scores.player1++, Svelte automatically updates the scoreboard on the page!
File: src/routes/cpu/+page.svelte
function selectChoice(choice: Choice) {
playerChoice = choice;
isThinking = true;
setTimeout(() => {
const randomIndex = Math.floor(Math.random() * choices.length);
cpuChoice = choices[randomIndex];
isThinking = false;
determineWinner();
}, 1000);
}
What's happening?
isThinking = true (shows loading spinner)setTimeout)isThinking = falseRandom Number Explained:
Math.random() // 0.0 to 0.999...
Math.random() * 3 // 0.0 to 2.999...
Math.floor(Math.random() * 3) // 0, 1, or 2
choices[randomIndex] // rock, paper, or scissors
let count = 0; // When this changes...
<p>{count}</p> <!-- This updates automatically! -->
In other frameworks, you'd need to tell the page to update. Svelte does it automatically!
<button on:click={playAgain}>Play Again</button>
<button on:click={() => goto('/')}>Go Home</button>
on:click={function} - Call a functionon:click={() => doSomething()} - Call with arrow function (useful for parameters)"State" means "the current condition of your app":
In Svelte, state is just regular variables with let!
import { goto } from '$app/navigation';
goto('/pvp'); // Go to the /pvp page
SvelteKit makes page navigation easy with the goto function.
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
h1 {
animation: bounce 2s ease-in-out infinite;
}
This makes the emoji bounce up and down!
Try these modifications to learn more:
# Development
bun run dev # Start development server
bun run build # Build for production
bun run preview # Preview production build
# Type checking
bun run check # Check for TypeScript errors
Why is Svelte different? It compiles your code into tiny JavaScript instead of including a big framework.
What makes Svelte reactive? Variables with let automatically update the page when changed.
How does routing work? Files named +page.svelte in the routes folder automatically become pages.
Why use TypeScript? It catches errors before you run your code and makes it easier to understand.
You now understand:
Next steps: Try modifying the game and see what happens. The best way to learn is by doing!
Built with ā¤ļø using SvelteKit + TypeScript + Bun