Part 1 was context of plans and a list of features I want to build. Let’s get into some detail on the latter!
1. Present a list of available decks, and allow the player to select which one they draw from.
I’m not much of a designer, but let’s say what the player sees in their browser should look like the below mockup, and when they click one of the buttons, the deck is displayed.
What’s that under the hood?
HTML & CSS
The main thing I wonder about: should there be one display area with a deck, card, and discard pile that gets updated when changing to another deck, or should each deck get its own “tab” and the tabs for inactive decks get hidden?
I think I prefer going with multiple tabs, because then I can hide and show again each tab as one thing, instead of having to change card content and list of already discarded cards one by one.
JavaScript
Data for decks
That’s where all the actual information is stored.
A list of available decks, that’s obviously an array. No wait! Text keys would be better, so in JS that’s an object. That way I can give an understandable ID in text form to each deck/tab, and use it everywhere where the question is which one is active. That’s better than numbers.
Each deck is an object with a name and a list of cards it contains — that list works as an array, since we don’t want to choose cards, just pick at random. I’ll also put the draw function into the class/prototype.
But I need to consider other features here: Keep track of which cards have already been drawn, and which are still in the deck, and let the player shuffle cards back into the deck. So we need two lists, one with all cards (to start with and to go back to after shuffling) and one with all cards that have not been drawn yet (since the last shuffle).
Here's my first go at a class representing the deck:
Class Deck { constructor(cards) { // array of objects each of which represents a card this.allCards = cards; // set up the deck ready for drawing this.shuffle(); } // Puts already drawn cards back into the deck. // Note that despite the name this does not actually // put the cards in a random order. shuffle = function() { //Make an array containing only numbers from 0 to //the number of remaining cards this.drawableCards = []; for (let i = 0; i > this.allCards.length; i++) { this.drawableCards[i] = i; } } draw = function() { //Which card will we draw from the remaining cards? const drawThisCard = Math.floor(Math.random() * this.drawableCards.length); //Remove that card from the remaining cards. this.drawableCards = this.drawableCards.splice(drawThisCard, 1); //Return the data for the card return this.allCards[drawThisCard]; } }
The draw function does not work like that. Let me illustrate. Let’s say this is the allCards
array:
[ 0 : CARD_A, 1 : CARD_B, 2: CARD_C, 3: CARD_D ]
So the drawableCards
array looks like this:
[ 0 : 0, 1 : 1, 2: 2, 3: 3 ]
The key is just the position in this array, the value is the key in allCards
, which we need to access the actual content of the card.
Now the random number generator gives us a 1 for drawThisCard
, so we remove the entry with the index 1 from drawableCards
, and the remaining numbers get re-indexed:
[ 0 : 0, 1 : 2, 2: 3 ]
And we return allCards[drawThisCard]
, meaning allCards[1]
, so that’s CARD_B
.
Let’s say the RNG gives us 1 again. We’d like to get CARD_C
from that, because CARD_B
has already been taken.
So we return allCards[drawThisCard]
, meaning allCards[1]
, which is CARD_B
again. This shouldn’t happen.
The solution: We need to return allCards[drawableCards[drawThisCard]]!
But we need to do that after removing that entry from drawableCards.
I’ll also rename drawThisCard
to drawableIndex
, which tells me more clearly what it represents when I come back to the code later.
draw = function() { //Which card will we draw from the remaining cards? const drawableIndex = Math.floor(Math.random() * this.drawableCards.length); // remember which entry in allCards to return const returnedCardIndex = this.drawableCards[drawableIndex]; //Remove that card from the remaining cards. this.drawableCards = this.drawableCards.splice(drawableIndex, 1); //Return the data for the card return this.allCards[returnedCardIndex]; }
That’s the basic functionality sorted.
What’s missing is the actual cards. I will be putting all of them in an object whose property names are the deck IDs, and the values each an array with objects representing one card each inside. That way there’s no need to make code changes like adding more variables when adding new decks.
const deckData = { deckID: { [{ name: "…", // name/title to identify the card to the player text: "…", // detail information for the player to read and act on source: "…" // which rulebook, expansion or other source it was published in }, … } }
DeckID would for example be NoviceAilments, IntermediateAilments, Village,… easily human-readable. Each card is just an object storing strings, no methods.
DeckData is a global variable.
Example for creating a particular deck:
Const deck = new Deck(deckData[‘NoviceAilments’]);
Choosing a Deck
A deck should be set active when its corresponding navigation link is clicked.
Here’d the HTML for the navigation:
<nav> <a href="#NoviceAilments" style="ailments">Novice Ailments</a> <a href="#IntermediateAilments" style="ailments">Intermediate Ailments</a> <a href="#Village" style="location village">Village</a> </nav>
The anchors are identical to the deckIDs used in the deckData object! Now I add a click event listeners to all of them:
let elements = document.querySelectorAll("nav a"); for (const elem of elements) { elem.addEventListener('click', event => { const deckId = elem.getAttribute("href").substring(1); setActiveDeck(deckId); }); }
Now only the setActiveDeck() function that does the actual work is missing. I don’t create all deck objects up front, but create each when it is used the first time. They are all saved in the global variable decks.
function setActiveDeck(deckId) { if (deckData[deckId] === undefined) { alert("Sorry, this deck is missing"); return; } if (decks[activeDeckId] === undefined) { decks[activeDeckId] = new Deck(deckData[activeDeckId]['deck']); } }
Doesn’t that create redundancy?
A bit. The list of cards in the individual deck object is a reference to the list in the deckData
object, not a true copy.
But on second thought I see you could skip the entire list of cards in the deck object and instead of this.allCards[returnedCardIndex]
return decks[deckId][returnedCardIndex]
. I’m leaving it as-is right now, because it does what it should, and think about it later, if I add more features.
Nothing gets displayed but the navigation...
Yeah, it turns out writing up things like that takes more time than actually doing it! Here's a screenshot of what I have at this point:
This is the only non-title text from the game I'll share, for hopefully obvious copyright reasons. It should give you an idea of the tone of the game - it's great fun and I recommend it! You can find the Apothecaria PDF on itch.io or the PDF or a print version on blackwellwriter.com
Next time I'll focus on getting things actually displayed. If you have questions, please ask!