Skip to content

Instantly share code, notes, and snippets.

@angusgrant
Created March 9, 2023 09:24
Show Gist options
  • Save angusgrant/dfc61c9a1bdff546413d6c90c24b2ff0 to your computer and use it in GitHub Desktop.
Save angusgrant/dfc61c9a1bdff546413d6c90c24b2ff0 to your computer and use it in GitHub Desktop.

Revisions

  1. angusgrant created this gist Mar 9, 2023.
    30 changes: 30 additions & 0 deletions dice.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Dice Games | Seven Seas</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>

    <nav>
    <a href="index.html">🏴‍☠️ Seven Seas</a>
    <p class="description">The travel app for pirates</p>
    </nav>

    <h1>Dice Games</h1>

    <p>Roll some dice, have some fun!</p>

    <div class="dice-game">
    <roll-dice></roll-dice>
    <roll-dice></roll-dice>
    </div>


    <script src="scripts.js"></script>

    </body>
    </html>
    30 changes: 30 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Seven Seas</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>

    <nav>
    <a href="index.html">🏴‍☠️ Seven Seas</a>
    <p class="description">The travel app for pirates</p>
    </nav>

    <h1>Seven Seas</h1>

    <p id="loot"></p>

    <p>
    <a class="btn btn-secondary" href="dice.html">Dice Games</a>
    <a class="btn" href="treasure.html">Treasure Chest</a>
    </p>


    <script src="scripts.js"></script>

    </body>
    </html>
    387 changes: 387 additions & 0 deletions scripts.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,387 @@
    (function () {
    'use strict';

    class TreasureChest {

    #bronze;
    #silver;
    #gold;
    #loot;

    /**
    * Create the constructor object
    * @param {Object} options User settings
    */
    constructor (options = {}) {

    // Merge options into defaults
    let {bronze, silver, gold, loot} = Object.assign({
    bronze: 0,
    silver: 0,
    gold: 0,
    loot: `You have {{gold}} gold, {{silver}} silver, and {{bronze}} bronze.`
    }, options);

    // Set instance properties
    this.#bronze = bronze;
    this.#silver = silver;
    this.#gold = gold;
    this.#loot = loot;

    }

    /**
    * Randomly shuffle an array
    * https://stackoverflow.com/a/2450976/1293256
    * @param {Array} array The array to shuffle
    * @return {Array} The shuffled array
    */
    static #shuffle (array) {

    let currentIndex = array.length;
    let temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {
    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
    }

    return array;

    }

    /**
    * Emit a custom event
    * @param {String} type The event type
    * @param {*} detail Any details to pass along with the event
    */
    #emit (type, detail) {

    // Create a new event
    let event = new CustomEvent(type, {
    bubbles: true,
    cancelable: true,
    detail: detail
    });

    // Dispatch the event
    return document.dispatchEvent(event);

    }

    /**
    * Add bronze to the treasure chest
    * @param {Number} n The amount to add
    */
    addBronze (n) {
    this.#bronze += n;
    this.#emit('treasure:bronze', this);
    return this;
    }

    /**
    * Add silver to the treasure chest
    * @param {Number} n The amount to add
    */
    addSilver (n) {
    this.#silver += n;
    this.#emit('treasure:silver', this);
    return this;
    }

    /**
    * Add gold to the treasure chest
    * @param {Number} n The amount to add
    */
    addGold (n) {
    this.#gold += n;
    this.#emit('treasure:gold', this);
    return this;
    }

    /**
    * Get the total amount of loot as a string
    * @return {String} The total amount of loot
    */
    getLoot () {
    return this.#loot.replace('{{gold}}', this.#gold).replace('{{silver}}', this.#silver).replace('{{bronze}}', this.#bronze);
    }

    /**
    * Get the amount of bronze
    * @return {Number} The amount
    */
    getBronze () {
    return this.#bronze;
    }

    /**
    * Get the amount of silver
    * @return {Number} The amount
    */
    getSilver () {
    return this.#silver;
    }

    /**
    * Get the amount of gold
    * @return {Number} The amount
    */
    getGold () {
    return this.#gold;
    }

    /**
    * Generate random treasure
    * @return {Object} The amount and type of loot found
    */
    static random () {

    // Amount and type
    let amount = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50];
    let type = ['bronze', 'silver', 'gold'];

    // Randomize the amounts
    this.#shuffle(amount);
    this.#shuffle(type);

    // Return the random loot
    return {
    amount: amount[0],
    type: type[0]
    };

    }

    }

    // Hold the treasure instance
    let treasure;

    /**
    * Create new treasure instance
    * @return {Constructor} A new TreasureChest instance
    */
    function createTreasure () {

    // Get any saved loot from localStorage
    let savedLoot = JSON.parse(localStorage.getItem('ss-treasure'));

    // Create new Treasure Chest instance
    treasure = new TreasureChest(savedLoot);

    }

    /**
    * Display the amount of loot in the UI
    */
    function showLoot () {
    let loot = document.querySelector('#loot');
    if (!loot) return;
    loot.textContent = treasure.getLoot();
    }

    /**
    * Save loot to localStorage and update the UI
    * @param {Event} event The event object
    */
    function saveLoot (event) {

    // Create the treasure object
    let treasure = {
    gold: event.detail.getGold(),
    silver: event.detail.getSilver(),
    bronze: event.detail.getBronze()
    };

    // Save it to localStorage
    localStorage.setItem('ss-treasure', JSON.stringify(treasure));

    // Update the UI
    showLoot(event.detail);

    }

    /**
    * Handle treasure submit events
    * @param {Event} event The event object
    */
    function submitHandler (event) {

    // Get the coin type
    // Only run on [data-treasure] forms
    let coin = event.target.getAttribute('data-treasure');
    if (!coin) return;

    // Stop the form from reloading the page
    event.preventDefault();

    // Get coin value
    let val = parseFloat(event.target.querySelector('[type="number"]').value);
    if (!val) return;

    // Add the correct loot
    if (coin === 'gold') {
    treasure.addGold(val);
    } else if (coin === 'silver') {
    treasure.addSilver(val);
    } else if (coin === 'bronze') {
    treasure.addBronze(val);
    }

    }

    /**
    * Listen for loot events
    * @param {Constructor} treasure The TreasureChest object
    */
    function lootListeners () {
    document.addEventListener('submit', submitHandler);
    document.addEventListener('treasure:gold', saveLoot);
    document.addEventListener('treasure:silver', saveLoot);
    document.addEventListener('treasure:bronze', saveLoot);
    }

    class RollDice extends HTMLElement {

    #dice;

    /**
    * The constructor object
    */
    constructor () {

    // Run this first
    super();

    // Creates a shadow root
    this.root = this.attachShadow({mode: 'closed'});

    // Define properties
    this.#dice = [1, 2, 3, 4, 5, 6];

    // Render HTML
    this.root.innerHTML =
    `<style>
    button {
    background-color: var(--bg-color, #0088cc);
    border: 1px solid var(--bg-color, #0088cc);
    border-radius: var(--radius, 0.25em);
    color: var(--color, #ffffff);
    font-size: var(--size, 1.5em);
    padding: 0.5em 1em;
    }
    [aria-live] {
    font-size: var(--msg-size, 1.3125em);
    font-weight: var(--msg-weight, normal);
    font-style: var(--msg-style, normal);
    color: var(--msg-color, inherit);
    }
    </style>
    <p>
    <button><slot>Roll Dice</slot></button>
    </p>
    <div aria-live="polite"></div>`;

    }

    /**
    * Randomly shuffle an array
    * https://stackoverflow.com/a/2450976/1293256
    * @param {Array} array The array to shuffle
    * @return {Array} The shuffled array
    */
    #shuffle (array) {

    let currentIndex = array.length;
    let temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {
    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
    }

    return array;

    }

    /**
    * Shuffle dice array and return first number
    * @return {Number} The result
    */
    #roll () {
    this.#shuffle(this.#dice);
    return this.#dice[0];
    }

    /**
    * Handle click events
    * @param {Event} event The event object
    */
    #clickHandler (event) {

    // Get the host component
    let host = event.target.getRootNode().host;

    // Get the message element
    let target = host.root.querySelector('[aria-live="polite"]');
    if (!target) return;

    // Roll the dice
    let roll = host.#roll();

    // Inject the message into the UI
    target.textContent = `You rolled a ${roll}`;

    }

    /**
    * Runs each time the element is appended to or moved in the DOM
    */
    connectedCallback () {

    // Attach a click event listener to the button
    let btn = this.root.querySelector('button');
    if (!btn) return;
    btn.addEventListener('click', this.#clickHandler);

    }

    /**
    * Runs when the element is removed from the DOM
    */
    disconnectedCallback () {

    // Remove the click event listener from the button
    let btn = this.root.querySelector('button');
    if (!btn) return;
    btn.removeEventListener('click', this.#clickHandler);

    }

    }

    if ('customElements' in window) {
    customElements.define('roll-dice', RollDice);
    }

    createTreasure();
    showLoot();
    lootListeners();

    })();
    188 changes: 188 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,188 @@
    /**
    * Main Styles
    */

    body {
    color: #272727;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
    margin: 1em auto;
    max-width: 44em;
    width: 88%;
    }

    /**
    * Add box sizing to everything
    * @link http://www.paulirish.com/2012/box-sizing-border-box-ftw/
    */
    *,
    *:before,
    *:after {
    box-sizing: border-box;
    }

    p {
    margin: 0 0 1.5em;
    }

    img {
    height: auto;
    max-width: 100%;
    }

    a {
    color: #0088cc;
    }

    a:active,
    a:hover {
    color: #272727;
    }


    /**
    * Nav Menu
    */

    @media (min-width: 40em) {
    nav {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: 1fr;
    grid-column-gap: 2em;
    grid-row-gap: 0;
    }

    nav .description {
    text-align: right;
    }
    }


    nav a {
    color: #272727;
    font-size: 1.2em;
    font-weight: bold;
    text-decoration: none;
    }

    nav a:active,
    nav a:hover {
    color: #0088cc;
    }

    nav .description {
    color: #808080;
    font-style: italic;
    }


    /**
    * Buttons
    */

    button,
    .btn {
    background-color: #0088cc;
    border: 1px solid #0088cc;
    border-radius: 0.25em;
    font-family: inherit;
    color: #ffffff;
    font-size: 1em;
    margin-right: 0.5em;
    padding: 0.5em 1em;
    text-decoration: none;
    }

    button:hover,
    .btn:hover {
    background-color: #005c8a;
    border-color: #005c8c;
    color: #ffffff;
    }

    .btn-secondary {
    background-color: #f7272f;
    border-color: #f7272f;
    }

    .btn-secondary:hover {
    background-color: #cb070e;
    border-color: #cb070e;
    }

    .btn-tertiary {
    background-color: #272727;
    border-color: #272727;
    }

    .btn-tertiary:hover {
    background-color: #808080;
    border-color: #808080;
    }



    /**
    * Typography
    */

    .text-large {
    font-size: 1.2em;
    }



    /**
    * Forms
    */

    label,
    input {
    display: block;
    width: 100%;
    }

    input {
    margin-bottom: 0.5em;
    }



    /**
    * Dice Game
    */

    @media (min-width: 40em) {
    .dice-game {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: 1fr;
    grid-column-gap: 2em;
    grid-row-gap: 0;
    }
    }



    /**
    * Treasure Chest
    */

    #loot {
    font-size: 1.2em;
    font-weight: bold;
    }

    @media (min-width: 40em) {
    .treasure-chest {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: 1fr;
    grid-column-gap: 2em;
    grid-row-gap: 0;
    }
    }

    [data-treasure] {
    margin-bottom: 1.5em;
    }
    51 changes: 51 additions & 0 deletions treasure.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,51 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Treasure Chest | Seven Seas</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <link rel="stylesheet" type="text/css" href="style.css">
    </head>
    <body>

    <nav>
    <a href="index.html">🏴‍☠️ Seven Seas</a>
    <p class="description">The travel app for pirates</p>
    </nav>

    <h1>Treasure Chest</h1>

    <p id="loot" aria-live="polite"></p>

    <div class="treasure-chest">
    <div>
    <form data-treasure="gold">
    <label for="gold">Amount of Gold</label>
    <input type="number" step="1" id="gold" value="1">
    <button class="btn btn-secondary">Add Gold</button>
    </form>
    </div>

    <div>
    <form data-treasure="silver">
    <label for="silver">Amount of Silver</label>
    <input type="number" step="1" id="silver" value="1">
    <button class="btn btn-tertiary">Add Silver</button>
    </form>
    </div>

    <div>
    <form data-treasure="bronze">
    <label for="bronze">Amount of Bronze</label>
    <input type="number" step="1" id="bronze" value="1">
    <button>Add Bronze</button>
    </form>
    </div>
    </div>


    <script src="scripts.js"></script>

    </body>
    </html>