Getting Started with Card-Factory
We will take you on a quick walkthrough on how to use our library
Project Setup
Feel free to use any build tool you would like, we will be using a lightweight Vite setup for our demo. Lets create a new project folder, and install vite.
npm install -D vite
next we will install our package, card-factory
npm install card-factory
next lets create our index.html, our styles.css and our card.ts files and link them accordingly.
index.html
<!doctype html>
<html lang="en">
<head>
<script type="module" src="./card.ts"></script>
</head>
<body></body>
</html>card.ts
import "./styles.css";To run our app, we will use the command npx vite. That should run our project on localhost:5173.
That should be it for a quick project setup!
Theme Configuration
The fastest way to apply a theme is using the setTheme function with a predefined theme on your playing surface. On this example we will make the body our playing surface:
card.ts
import { setTheme, greenFelt } from "card-factory";
import "./styles.css"; // ensure this stays at end of imports to override default styles
const body = document.querySelector("body");
if (body) {
setTheme(greenFelt, body);
}Initiating a Deck of Cards
To create a deck of standard playing cards (52 cards, Ace to King, 4 suits) we have provided a quick function.
card.ts
import StandardDeckOfCards from "card-factory";
const deck = StandardDeckOfCards(); // StandardDeckOfCards(true) will also provide 2 jokersThe deck contains all of the card objects, but is not used to display anything on browsers. It is purely a functional component. Where the cards will get displayed is in the pile Elements
Creating Pile Elements
Pile Elements are visually where cards will appear on the screen. Think of any possible stack or hand or "pile" of cards as a distinct pile Element. The best method of creating piles is by using the deck initiated in the previous step.
Below we will initiate 3 piles, an empty discard pile, an empty hand, and a draw pile with all 52 cards in it.
We can initiate cards in a pile element by passing an array of cards as the second argument to deck.createPileElement()
card.ts
const discardPile = deck.createPileElement("discardPile");
const drawPile = deck.createPileElement("drawPile", deck.cards); // initiate all cards here
const playerHand = deck.createPileElement("Hand"); // will begin with no cardsPiles also have more advanced options, which is the third optional argument to createPileElement. Please see more on Piles Options
Appending Piles to Page
I will now append these pileElements to the page. PileElements are objects that contain many methods, and properties. The HTML Element of a pileElement is found under the property container.
For all pileElements properties and methods see PileElements
index.html
<!doctype html>
<html lang="en">
<head>
<script type="module" src="./card.ts"></script>
</head>
<body>
<div id="discardPile"></div>
<div id="drawPile"></div>
<div id="hand"></div>
</body>
</html>card.ts
const discardDiv = document.getElementById("discardPile");
discardDiv.appendChild(discardPile.container);
const drawDiv = document.getElementById("drawPile");
drawDiv.appendChild(drawPile.container);
const handDiv = document.getElementById("hand");
handDiv.appendChild(playerHand.container);We have created our piles, taken our bare bones html file and appended our piles to them. One more step will have us able to interact with the cards.
We will have to wait for the DOMContent to be loaded, and then run cascade on any piles that have cards initiated in them.
Cascade is essentially, re-stack, and is an async function which should be awaited. Lets also just shuffle up the cards before we re-stack them.
Note any time you shuffle cards, you should re-stack cascade() them.
card.ts
drawPile.shuffle();
window.addEventListener("DOMContentLoaded", async () => {
await drawPile.cascade();
});Bingo! We now have playing cards in a stack in the middle, an empty pile above, and below. The cards are able to be dragged and dropped by default, however any other interactions are up to us to code.
Currently I am sure that it doesn't look great, part of the card is likely cut off and the piles are overlapping a bit, but we haven't written any css yet!
Card Sizing
To override css on the default card sizing, which is quite large, we will need to configure a css file.
lets create one called styles.css and target the card size. I will also add some minimal styling on the body to clean up the look of our table.
styles.css
:root {
--card-size: 50px;
}
body {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}And we will import this into our typescript file after all other imports.
card.ts
import { setTheme, greenFelt, StandardDeckOfCards } from "card-factory";
import "./styles.css";Interactivity With Cards
Now that we have some cards on the screen, we can start adding functionality to them, the best way is to add event listeners to the piles themselves. Lets start with flipping the top card on the draw pile when it is clicked.
card.ts
drawPile.container.addEventListener("click", () => {
drawPile.topCardElement.flip();
});Awesome! But, personally I don't think the hand looks much like a hand. It's still a pile, lets change an option to the hand so that cards that get put in there are spread out horizontally.
One of the options to a pileElement is layout, lets change hand from the default layout (stack) to a layout of cascade.
card.ts
playerHand.applyCascadeLayout("cascade");Ok, you may have noticed that we can move a whole bunch of cards from draw pile to the hand. We have included some functions with pile to help figure out which card was clicked on. Lets add an event listener to hand so that we can flip over any card we touch, not just the top card as long as it is face down.
I'm going to write this type safe, to continue using our useful typescript auto-complete
card.ts
playerHand.container.addEventListener("click", (e) => {
if (!(e.target instanceof HTMLElement)) return;
const cardElement = playerHand.findCardContainer(e.target);
if (cardElement === null) return;
if (cardElement.faceUp) return;
cardElement.flip();
});Now you may be saying, how do I just grab a middle card from my hand and not the whole stack? It's just another option in pile. Lets fix that so we can play one card at a time.
card.ts
playerHand.options.groupDrag = false;Wrapping Up
Ok, That's it for the basics! The final code for the basic tutorial is below, in case you got lost along the way.
index.html
<!doctype html>
<html lang="en">
<head>
<script type="module" src="./card.ts"></script>
</head>
<body>
<div id="discardPile"></div>
<div id="drawPile"></div>
<div id="hand"></div>
</body>
</html>card.ts
import { setTheme, greenFelt, StandardDeckOfCards } from "card-factory";
import "./styles.css";
const body = document.querySelector("body");
if (body) {
setTheme(greenFelt, body);
}
const deck = StandardDeckOfCards(); // StandardDeckOfCards(true) will provide 2 jokers
const discardPile = deck.createPileElement("discardPile");
const drawPile = deck.createPileElement("drawPile", deck.cards); // initiate all cards here
const playerHand = deck.createPileElement("Hand"); // will begin with no cards
const discardDiv = document.getElementById("discardPile");
discardDiv.appendChild(discardPile.container);
const drawDiv = document.getElementById("drawPile");
drawDiv.appendChild(drawPile.container);
const handDiv = document.getElementById("hand");
handDiv.appendChild(playerHand.container);
drawPile.shuffle();
window.addEventListener("DOMContentLoaded", async () => {
drawPile.cascade();
drawPile.container.addEventListener("click", () => {
drawPile.topCardElement.flip();
});
playerHand.applyCascadeLayout("cascade");
drawPile.container.addEventListener("click", () => {
drawPile.topCardElement.flip();
});
playerHand.container.addEventListener("click", (e) => {
if (!(e.target instanceof HTMLElement)) return;
const cardElement = playerHand.findCardContainer(e.target);
if (cardElement === null) return;
if (cardElement.faceUp) return;
cardElement.flip();
});
playerHand.options.groupDrag = false;
});styles.css
:root {
--card-size: 50px;
}
body {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
}