Master High Score Board in Gleam: Complete Learning Path
Master High Score Board in Gleam: Complete Learning Path
A high score board in Gleam involves creating and managing an immutable, sorted collection of player scores, typically using core data structures like lists and custom types. This guide teaches you how to implement a fully functional high score board from scratch, covering data modeling, sorting, and state management best practices.
You’ve poured hours into crafting the perfect game mechanic. The controls are smooth, the graphics are crisp, and the core loop is addictive. But as players finish their first session, they hit a void. There's no record of their achievement, no leaderboard to climb, no friendly rival to beat. That missing piece is what separates a fleeting experience from a lasting, competitive community.
This is a common roadblock for developers, but it doesn't have to be yours. In this comprehensive guide, we'll dismantle the problem and build a robust, efficient high score board using Gleam. You'll learn how to leverage Gleam's type safety and functional paradigm to create a system that is not only correct by design but also elegant and easy to maintain. Prepare to add that crucial layer of engagement to your applications.
What is a High Score Board, Really?
At its heart, a high score board is more than just a list of numbers. It's a narrative of achievement, a driver of competition, and a tool for user retention. Conceptually, it provides a ranked list of the best performances within a system, whether that's a video game, a sales team's quarterly results, or a student's test scores.
Technically, implementing one involves three core operations:
- Data Collection: Capturing a player's identity (like a name or user ID) and their corresponding score (an integer or float).
- Data Storage & Management: Holding this collection of scores in a structured way, typically sorted from highest to lowest.
- Data Retrieval: Providing a clean, ordered list of the top scores to be displayed to the user.
In Gleam, we approach this with a functional mindset. Instead of modifying a list in place, we create new, updated versions of the high score board with each change. This immutable approach eliminates a whole class of bugs related to state management and makes our code predictable and easier to test.
Why Gleam is a Perfect Fit for Building Leaderboards
You might think any language can build a leaderboard, and you'd be right. However, Gleam brings a unique set of advantages to the table that make it an exceptionally strong choice for this kind of data manipulation task, ensuring your implementation is both robust and maintainable.
The Power of Static Typing and Immutability
Gleam's static type system is your first line of defense against bugs. It guarantees that a score is always a number and a player's name is always a string. You can't accidentally add "Five Hundred" as a score, because the compiler will stop you dead in your tracks. This eliminates runtime errors that could corrupt your leaderboard.
Immutability, a core tenet of Gleam, means that data, once created, cannot be changed. When you add a new score to your board, you aren't modifying the old board; you're creating a brand new board that includes the new score. This sounds inefficient, but it's incredibly fast due to how Gleam handles data structures under the hood. The primary benefit is predictability: you never have to worry about a function unexpectedly changing your data elsewhere in the program.
// In Gleam, a Score is always what you define it to be.
// No surprises at runtime.
pub type Score {
Score(name: String, value: Int)
}
Expressive and Readable Syntax
Gleam's syntax is clean, minimal, and inspired by languages like Elm and ReScript. This focus on readability makes your code easier to understand, debug, and refactor. When dealing with data transformations—like adding a score, sorting a list, and then taking the top ten—Gleam's pipe operator (|>) allows you to express this flow of data in a natural, left-to-right sequence that reads like a sentence.
This clarity is invaluable, especially as the logic for your leaderboard grows more complex, perhaps to handle ties or multiple boards for different game levels.
Interoperability with the Erlang Ecosystem
Gleam compiles to Erlang or JavaScript. When compiling to the Erlang BEAM virtual machine, you gain access to a world-renowned platform for building concurrent, fault-tolerant systems. This means your Gleam-powered leaderboard can be a component in a much larger, highly scalable application. You could easily build a web server in Elixir that uses your Gleam module to manage leaderboards for thousands of concurrent players, benefiting from the BEAM's legendary stability.
How to Build a High Score Board in Gleam from Scratch
Let's roll up our sleeves and build this thing. We'll approach it step-by-step, starting with the data structures and then building the functions to manipulate them. This follows the 5W1H (What, Why, Who, When, Where, How) framework, with a heavy focus on the "How".
What: Defining the Blueprint with Custom Types
First, we need to decide what our data looks like. A single score entry needs a player's name (a String) and their score value (an Int). A high score board itself is simply a list of these entries. In Gleam, we can model this elegantly using a custom type for the score and a type alias for the board.
Create a file named src/high_score_board.gleam and add the following code:
import gleam/list
import gleam/int
import gleam/io
// We define a custom type `Score` to hold our data.
// This makes our code more readable and type-safe.
pub type Score {
Score(name: String, value: Int)
}
// A `HighScoreBoard` is simply a list of `Score` records.
// Using a type alias makes function signatures clearer.
pub type HighScoreBoard =
List(Score)
// A function to create a new, empty board.
pub fn new() -> HighScoreBoard {
[]
}
Here, pub type Score creates a new record type. pub type HighScoreBoard = List(Score) creates an alias, which doesn't create a new type but gives a more descriptive name to List(Score). The new() function is our starting point, providing a clean, empty board.
How: The Core Logic for Adding and Ranking Scores
Now for the main functionality. We need a function to add a new score and a function to retrieve the list of scores. The "add" function is where the magic happens: it will take the existing board and the new score, and it will return a *new* board that is correctly sorted.
Add these functions to your high_score_board.gleam file:
// This function adds a new score to the board.
// It returns a new board, leaving the original unchanged.
pub fn add_score(
board: HighScoreBoard,
name: String,
value: Int,
) -> HighScoreBoard {
let new_score = Score(name: name, value: value)
let updated_board = list.append(board, [new_score])
// Sort the board in descending order based on the score value.
// `int.compare` returns an `Order` enum (Lt, Eq, Gt).
// We want higher scores first, so we swap the arguments `b` and `a`.
list.sort(updated_board, by: fn(a, b) {
int.compare(b.value, a.value)
})
}
// This function simply returns the board. Since the board is always
// kept in a sorted state after each addition, this gives us the ranked list.
pub fn get_scores(board: HighScoreBoard) -> List(Score) {
board
}
The key part is list.sort. It takes a list and a comparison function. This function receives two elements (a and b) and must return an Order value (Lt for less than, Eq for equal, or Gt for greater than). By comparing b.value to a.value, we are telling Gleam to sort in descending order—the player with the greater score comes first.
Here is a visual representation of the score insertion logic:
● Start with a new score (e.g., "ada", 100)
│
▼
┌──────────────────┐
│ Create Score Record │
│ Score("ada", 100) │
└────────┬─────────┘
│
▼
┌─────────────────────────┐
│ Append to Existing Board │
│ [Score("bob", 80), Score("ada", 100)] │
└────────────┬────────────┘
│
▼
┌───────────────────────────┐
│ Sort the entire new list │
│ using `int.compare(b, a)` │
└────────────┬──────────────┘
│
▼
┌─────────────────────────┐
│ Return the Sorted Board │
│ [Score("ada", 100), Score("bob", 80)] │
└────────────┬────────────┘
│
▼
● End
When & Where: Putting It All Together in a Main Function
Let's see our module in action. In your src/your_project_name.gleam file (or wherever your `main` function is), you can import and use the `high_score_board` module.
import high_score_board
import gleam/io
import gleam/list
pub fn main() {
io.println("--- Initializing High Score Board ---")
let board = high_score_board.new()
io.debug(board) // Will print: []
io.println("\n--- Adding Scores ---")
let board =
board
|> high_score_board.add_score("luigi", 850)
|> high_score_board.add_score("mario", 1200)
|> high_score_board.add_score("peach", 950)
|> high_score_board.add_score("bowser", 1200) // A tie!
io.println("\n--- Final High Scores ---")
let ranked_scores = high_score_board.get_scores(board)
// Iterate over the scores and print them
list.each(ranked_scores, fn(score) {
let rank_text =
"Player: " <> score.name <> ", Score: " <> int.to_string(score.value)
io.println(rank_text)
})
}
To run this, navigate to your project's root directory in the terminal and execute:
gleam run
The expected output will be:
--- Initializing High Score Board ---
[]
--- Adding Scores ---
--- Final High Scores ---
Player: mario, Score: 1200
Player: bowser, Score: 1200
Player: peach, Score: 950
Player: luigi, Score: 850
Notice how the pipe operator |> makes the process of adding multiple scores clean and readable. Each call to add_score takes the result of the previous one and passes it as the first argument, creating a beautiful data pipeline.
This ASCII diagram illustrates the data structure hierarchy we've built:
HighScoreBoard (Type Alias for List)
│
├─ Score (Record)
│ ├─ name: String
│ └─ value: Int
│
├─ Score (Record)
│ ├─ name: String
│ └─ value: Int
│
└─ ... and so on
Real-World Applications & Advanced Considerations
While our implementation is solid, real-world systems often have more requirements. Let's explore some common challenges and how to think about them in Gleam.
Beyond Gaming: Leaderboards in Business and Education
The logic we've built is universally applicable.
- Sales Dashboards: Track top-performing salespeople by revenue or deals closed. The
namewould be the salesperson and thevaluewould be their sales figure. - E-Learning Platforms: Rank students based on quiz scores or course completion times to foster a competitive learning environment.
- Fitness Apps: Create leaderboards for weekly steps, running distance, or workout consistency among friends.
Common Pitfalls and How to Avoid Them
- Handling Ties: Our current sorting function handles ties, but the order between tied scores isn't guaranteed (it depends on the sort algorithm's stability). If you need a secondary sorting criterion (e.g., by name alphabetically, or by who achieved the score first), you can chain comparisons inside the sorting function.
- Performance on Large Datasets: For a list with millions of entries, sorting the entire list on every insertion can become a bottleneck. A more advanced approach would be to keep the list sorted and perform an efficient insertion into the correct position. However, for most applications (up to tens of thousands of scores), Gleam's `list.sort` is incredibly fast and the simplicity is worth the trade-off.
- State Persistence: Our high score board is in-memory, meaning it resets every time the program restarts. To make it persistent, you'd need to serialize the data (e.g., to JSON) and write it to a file, or store it in a database. Libraries like `gleam/json` can help with the former.
Pros and Cons: List vs. Map for Score Storage
We used a List, which is great for a ranked leaderboard. But what if a player can only have *one* entry on the board (their best score)? A gleam/map.Map might be better. Here’s a comparison:
| Aspect | List(Score) (Our Approach) |
Map(String, Int) (Alternative) |
|---|---|---|
| Multiple Scores per Player | Pro: Easily allows a player to appear multiple times on the board. | Con: Inherently prevents multiple scores; a player's name is a unique key. |
| Updating a Score | Con: Requires finding the old score, removing it, and adding the new one. Can be complex. | Pro: Trivial. map.insert(board, player_name, new_score) automatically overwrites the old score. |
| Ranking | Pro: The data structure is inherently an ordered list, perfect for display. | Con: Maps are unordered. To get a ranked list, you must convert the map to a list and then sort it. |
| Best Use Case | Classic arcade-style leaderboards where all top scores are shown. | Systems where each user has a single, up-to-date score or rating. |
Your Turn: The Kodikra High Score Board Challenge
Theory is one thing, but mastery comes from practice. The concepts we've covered—custom types, list manipulation, and sorting with higher-order functions—are fundamental building blocks in Gleam. The exclusive curriculum at kodikra.com provides the perfect environment to solidify this knowledge.
The High Score Board module in our Gleam learning path will challenge you to implement these functions yourself, guided by a suite of tests that will check your logic for correctness and edge cases. This hands-on experience is the fastest way to build confidence and true understanding.
Ready to prove your skills? Dive into the challenge now.
Master the High Score Board module step by step
Frequently Asked Questions (FAQ)
How do I handle duplicate scores in Gleam?
The current implementation handles them naturally. If two players have the same score, `int.compare` will return `Eq`, and the sort algorithm will place them next to each other. If you need a tie-breaker (e.g., sort by name if scores are equal), you can build more complex logic in the `by` function for `list.sort`.
What's the best way to sort scores in descending order?
The method shown in this guide is the most idiomatic in Gleam. The `list.sort` function sorts in ascending order by default based on the `Order` enum. To reverse this, you simply swap the arguments in your comparison function: `int.compare(b.value, a.value)` instead of `int.compare(a.value, b.value)`. This effectively inverts the sorting logic to be descending.
Can I store the high score board in a file?
Yes. While this guide focuses on the in-memory logic, you can persist the data. The general process would be: 1. On change, convert your `HighScoreBoard` list into a serializable format like JSON using the `gleam/json` library. 2. Write the resulting JSON string to a file. 3. On program start, read the file, parse the JSON back into your Gleam `HighScoreBoard` type, and use that as your initial state.
Is Gleam's immutable approach good for a high score board?
It's excellent. Immutability prevents a wide range of bugs where data is changed unexpectedly. It makes your program's state flow predictable and much easier to reason about, especially in concurrent environments. While it may seem like you're creating a lot of data, Gleam's underlying implementation is highly optimized to share unchanged parts of data structures, making it very performant.
How do I limit the number of scores on the board?
You can easily add this logic. After sorting the board in the `add_score` function, you can use the `gleam/list.take(list, count)` function to keep only the top N scores. For example, `list.take(sorted_board, 10)` would return a new list containing only the top 10 scores.
What if two players have the same score? How is the tie handled?
By default, the relative order of elements that compare as equal is not guaranteed; it depends on the stability of the sorting algorithm. If you need a deterministic tie-breaker, you must add a secondary comparison. For example, if scores are equal, then compare player names alphabetically. This requires a slightly more complex comparison function.
How can I update an existing player's score if they get a better one?
If you need to update scores (i.e., a player can only have one entry—their best), a `List` is not the ideal structure. You would be better off using a `gleam/map.Map` with the player's name as the key. To add/update a score, you would check if the new score is higher than the existing one in the map before inserting. To display the leaderboard, you would convert the map's values to a list and then sort it.
Conclusion: Your Next Steps in Gleam
You have successfully journeyed from a conceptual idea to a fully functional, type-safe high score board in Gleam. We've covered the essentials: defining clear data structures with custom types, leveraging immutability for predictable state changes, and using higher-order functions like list.sort to perform powerful data manipulation.
This module is more than just an exercise; it's a perfect example of Gleam's strengths in action. The clarity, safety, and functional elegance you've seen here are principles that apply to any problem you choose to solve with the language. Now, the best way forward is to apply this knowledge. Tackle the kodikra.com challenge, experiment with the code, and try extending it. Perhaps add a limit to the board size, or implement tie-breaking logic. Your journey as a Gleam developer is just getting started.
To continue exploring the language and its powerful features, return to our main guide.
Back to the Complete Gleam Guide
Disclaimer: All code snippets and concepts are based on the latest stable versions of Gleam and its standard library at the time of writing. As the language evolves, some function names or module paths may change. Always refer to the official Gleam documentation for the most current information.
Published by Kodikra — Your trusted Gleam learning resource.
Post a Comment