├── .gitignore ├── src ├── suits │ ├── index.ts │ ├── suits.ts │ ├── _suit.ts │ └── unicode.ts ├── cards │ ├── index.ts │ ├── _card.ts │ └── joker.ts ├── ranks │ ├── index.ts │ ├── non-standard.ts │ ├── _rank.ts │ ├── standard.ts │ ├── trump.ts │ └── major-arcana.ts ├── util.ts ├── hand.ts ├── index.ts ├── decks │ ├── index.ts │ ├── euchre.ts │ ├── piquet.ts │ ├── pinochel.ts │ ├── major-arcana.ts │ ├── standard.ts │ ├── baraja.ts │ ├── tarot.ts │ └── _deck.ts └── shuffle.ts ├── docs ├── assets │ ├── icons.png │ ├── widgets.png │ ├── icons@2x.png │ ├── widgets@2x.png │ └── highlight.css ├── .nojekyll ├── interfaces │ ├── decks.FindCardCallback.html │ ├── decks.EuchreDeckOpts.html │ ├── decks.PiquetDeckOpts.html │ ├── decks.PinochelDeckOpts.html │ ├── decks.MajorArcanaDeckOpts.html │ ├── RandomGenerator.html │ ├── decks.DeckOpts.html │ ├── decks.TarotDeckOpts.html │ ├── decks.StandardDeckOpts.html │ ├── decks.CardLocation.html │ └── decks.BarajaDeckOpts.html ├── modules │ ├── ranks.html │ ├── ranks.nonStandard.html │ └── decks.html ├── enums │ ├── decks.TarotTrumpSuit.html │ ├── jokerColor.html │ └── decks.Pile.html ├── classes │ ├── MathRandomGenerator.html │ └── ranks.Rank.html └── modules.html ├── .npmignore ├── .prettierignore ├── tests ├── deck.test.ts ├── rank.test.ts ├── suit.test.ts ├── deck-euchre.test.ts ├── deck-piquet.test.ts ├── deck-pinochel.test.ts ├── deck-major-arcana.test.ts ├── deck-tarot.test.ts ├── deck-baraja.test.ts ├── .mocharc.js ├── .nycrc.js ├── Card.test.ts └── shuffle.test.ts ├── .editorconfig ├── tsconfig.json ├── .github ├── workflows │ └── ci.yaml └── FUNDING.yml ├── .prettierrc.js ├── license ├── package.json ├── contributing.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | old 4 | /report 5 | -------------------------------------------------------------------------------- /src/suits/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './_suit'; 3 | export * from './suits'; 4 | -------------------------------------------------------------------------------- /src/cards/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './_card'; 3 | 4 | export * from './joker'; 5 | -------------------------------------------------------------------------------- /docs/assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbjr/node-cards/HEAD/docs/assets/icons.png -------------------------------------------------------------------------------- /docs/assets/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbjr/node-cards/HEAD/docs/assets/widgets.png -------------------------------------------------------------------------------- /docs/assets/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbjr/node-cards/HEAD/docs/assets/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kbjr/node-cards/HEAD/docs/assets/widgets@2x.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | docs 2 | tests 3 | src 4 | .github 5 | /report 6 | .prettier* 7 | .editorconfig 8 | tsconfig.json 9 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /src/ranks/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './_rank'; 3 | 4 | export * from './standard'; 5 | export * from './non-standard'; 6 | export * from './trump'; 7 | export * from './major-arcana'; 8 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | 2 | export function remove(array: T[], item: T) { 3 | for (let i = 0; i < array.length; i++) { 4 | if (array[i] === item) { 5 | array.splice(i--, 1); 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Yes. Format these files when they are present. 2 | !.prettier*/ 3 | !.mochar*/ 4 | !.huskyr*/ 5 | 6 | # No. Do not format these files. 7 | .github 8 | /docs 9 | node_modules/ 10 | /build 11 | -------------------------------------------------------------------------------- /src/hand.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Card } from './cards'; 3 | 4 | export class Hand extends Array { 5 | public sortBySuit() { 6 | // this.sort((a, b) => { 7 | // // 8 | // }); 9 | } 10 | 11 | public sortByRank() { 12 | // 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './shuffle'; 3 | export * from './cards'; 4 | export * as decks from './decks'; 5 | export * as ranks from './ranks'; 6 | export * as suits from './suits'; 7 | 8 | import { initUnicode } from './unicode'; 9 | 10 | initUnicode(); 11 | -------------------------------------------------------------------------------- /src/decks/index.ts: -------------------------------------------------------------------------------- 1 | 2 | export * from './_deck'; 3 | 4 | export * from './baraja'; 5 | export * from './euchre'; 6 | export * from './major-arcana'; 7 | export * from './pinochel'; 8 | export * from './piquet'; 9 | export * from './standard'; 10 | export * from './tarot'; 11 | -------------------------------------------------------------------------------- /tests/deck.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('Deck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.Deck).to.be.a('function'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/rank.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ranks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('Rank', function() { 7 | it('should exist, and be a function', function() { 8 | expect(ranks.Rank).to.be.a('function'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/suit.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { suits } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('Suit', function() { 7 | it('should exist, and be a function', function() { 8 | expect(suits.Suit).to.be.a('function'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /src/ranks/non-standard.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Rank } from './_rank'; 3 | 4 | export namespace nonStandard { 5 | export const one = new Rank('1', 'One'); 6 | export const cavalier = new Rank('C', 'Cavalier'); 7 | export const knight = new Rank('KN', 'Knight'); 8 | export const page = new Rank('P', 'Page'); 9 | } 10 | -------------------------------------------------------------------------------- /src/ranks/_rank.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * The main base class for representing a rank of card (eg. "ace" or "king") 4 | */ 5 | export class Rank { 6 | constructor( 7 | public readonly abbrn: string, 8 | public readonly name: string, 9 | ) { } 10 | 11 | /** 12 | * Returns a human-readable string representation of the rank object 13 | * 14 | * eg. `""` 15 | */ 16 | public toString() { 17 | return ``; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | # 4 | # Formatting configuration for those that may choose to not use prettier. 5 | # At least serves as a minimal styleguide for contributors. 6 | # 7 | 8 | root = true 9 | 10 | [*] 11 | end_of_line = lf 12 | charset = utf-8 13 | insert_final_newline = true 14 | trim_trailing_whitespace = true 15 | indent_style = tab 16 | indent_size = 2 17 | max_line_length = 80 18 | 19 | [*.py] 20 | indent_style = space 21 | indent_size = 4 22 | 23 | [*.bat] 24 | end_of_line = crlf 25 | 26 | [Makefile] 27 | indent_style = tab 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "outDir": "build", 6 | "rootDir": "src", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "declarationMap": false, 10 | "incremental": true, 11 | "moduleResolution": "node", 12 | "typeRoots": [ 13 | "node_modules/@types/" 14 | ], 15 | "tsBuildInfoFile": "build/.tsbuildinfo", 16 | "lib": [ 17 | "es2020" 18 | ] 19 | }, 20 | "exclude": [ 21 | "node_modules", 22 | "build" 23 | ], 24 | "include": [ 25 | "src/**/*.ts" 26 | ] 27 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [12.x, 14.x, 16.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: 'npm' 26 | - run: npm ci 27 | - run: npm run build 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /tests/deck-euchre.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('EuchreDeck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.EuchreDeck).to.be.a('function'); 9 | }); 10 | 11 | it('should be creatable', function() { 12 | expect(create).to.not.throw(); 13 | 14 | function create() { 15 | return new decks.EuchreDeck(); 16 | } 17 | }); 18 | 19 | it('should extend Deck', function() { 20 | expect(new decks.EuchreDeck()).to.be.instanceof(decks.Deck); 21 | }); 22 | 23 | // TODO: Test rng option 24 | }); 25 | -------------------------------------------------------------------------------- /tests/deck-piquet.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('PiquetDeck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.PiquetDeck).to.be.a('function'); 9 | }); 10 | 11 | it('should be creatable', function() { 12 | expect(create).to.not.throw(); 13 | 14 | function create() { 15 | return new decks.PiquetDeck(); 16 | } 17 | }); 18 | 19 | it('should extend Deck', function() { 20 | expect(new decks.PiquetDeck()).to.be.instanceof(decks.Deck); 21 | }); 22 | 23 | // TODO: Test rng option 24 | }); 25 | -------------------------------------------------------------------------------- /tests/deck-pinochel.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('PinochelDeck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.PinochelDeck).to.be.a('function'); 9 | }); 10 | 11 | it('should be creatable', function() { 12 | expect(create).to.not.throw(); 13 | 14 | function create() { 15 | return new decks.PinochelDeck(); 16 | } 17 | }); 18 | 19 | it('should extend Deck', function() { 20 | expect(new decks.PinochelDeck()).to.be.instanceof(decks.Deck); 21 | }); 22 | 23 | // TODO: Test rng option 24 | }); 25 | -------------------------------------------------------------------------------- /tests/deck-major-arcana.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('MajorArcanaDeck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.MajorArcanaDeck).to.be.a('function'); 9 | }); 10 | 11 | it('should be creatable', function() { 12 | expect(create).to.not.throw(); 13 | 14 | function create() { 15 | return new decks.MajorArcanaDeck(); 16 | } 17 | }); 18 | 19 | it('should extend Deck', function() { 20 | expect(new decks.MajorArcanaDeck()).to.be.instanceof(decks.Deck); 21 | }); 22 | 23 | // TODO: Test rng option 24 | }); 25 | -------------------------------------------------------------------------------- /tests/deck-tarot.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('TarotDeck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.TarotDeck).to.be.a('function'); 9 | }); 10 | 11 | it('should be creatable', function() { 12 | expect(create).to.not.throw(); 13 | 14 | function create() { 15 | return new decks.TarotDeck(); 16 | } 17 | }); 18 | 19 | it('should extend Deck', function() { 20 | expect(new decks.TarotDeck()).to.be.instanceof(decks.Deck); 21 | }); 22 | 23 | // TODO: Test rng option 24 | // TODO: Test trumpSuit option 25 | }); 26 | -------------------------------------------------------------------------------- /tests/deck-baraja.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { decks } from '../src'; 3 | import { describe, it } from 'mocha'; 4 | import { expect } from 'chai'; 5 | 6 | describe('BarajaDeck', function() { 7 | it('should exist, and be a function', function() { 8 | expect(decks.BarajaDeck).to.be.a('function'); 9 | }); 10 | 11 | it('should be creatable', function() { 12 | expect(create).to.not.throw(); 13 | 14 | function create() { 15 | return new decks.BarajaDeck(); 16 | } 17 | }); 18 | 19 | it('should extend Deck', function() { 20 | expect(new decks.BarajaDeck()).to.be.instanceof(decks.Deck); 21 | }); 22 | 23 | // TODO: Test stripped flag 24 | // TODO: Test rng option 25 | // TODO: Test jokers option 26 | }); 27 | -------------------------------------------------------------------------------- /src/suits/suits.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Suit } from './_suit'; 3 | 4 | 5 | // "Standard" Cards 6 | 7 | export const spades = new Suit('spades'); 8 | export const hearts = new Suit('hearts'); 9 | export const diamonds = new Suit('diamonds'); 10 | export const clubs = new Suit('clubs'); 11 | export const trump = new Suit('trump'); 12 | 13 | 14 | // Minor / Major Arcana (Tarot) 15 | 16 | export const swords = new Suit('swords'); 17 | export const cups = new Suit('cups'); 18 | export const coins = new Suit('coins'); 19 | export const wands = new Suit('wands'); 20 | export const majorArcana = new Suit('major arcana'); 21 | 22 | 23 | // None 24 | 25 | export const none = new Suit('none'); 26 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: jbrumond 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /src/suits/_suit.ts: -------------------------------------------------------------------------------- 1 | 2 | import { unicodeCards, unicodeSuits } from './unicode'; 3 | 4 | /** 5 | * The main class for representing a suit of cards 6 | */ 7 | export class Suit { 8 | constructor( 9 | public readonly name: string 10 | ) { } 11 | 12 | /** 13 | * Returns a human-readable string representation of the suit object 14 | * 15 | * eg. `""` 16 | */ 17 | public toString() { 18 | return ``; 19 | } 20 | 21 | /** 22 | * The unicode character that represents this particular suit 23 | */ 24 | public get unicode() { 25 | return unicodeSuits.get(this); 26 | } 27 | 28 | /** 29 | * A map of ranks to the appropriate unicode character for that card 30 | */ 31 | public get unicodeCards() { 32 | return unicodeCards.get(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/.mocharc.js: -------------------------------------------------------------------------------- 1 | /** Test results report title. */ 2 | const reportTitle = 'Unit Test Results'; 3 | 4 | /** Test results report path. */ 5 | const reportPath = 'report/test/unit/results'; 6 | 7 | /** Unit test configuration. */ 8 | const config = { 9 | exit: true, 10 | recursive: true, 11 | require: 'ts-node/register', 12 | extension: ['ts'], 13 | spec: ['tests/**/*.test.*'], 14 | reporter: 'mochawesome', 15 | reporterOptions: `reportDir=${reportPath}, reportFilename=index.html, html=true, json=true, reportTitle=${reportTitle}, reportPageTitle=${reportTitle}, showSkipped=true`, 16 | }; 17 | 18 | if (typeof require !== 'undefined' && require.main === module) { 19 | // When a script. 20 | log.debug(JSON.stringify(config, null, 2)); 21 | } else { 22 | // When a module. 23 | module.exports = config; 24 | } 25 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | /** Simple logger. */ 2 | /* istanbul ignore next: Unnecessary. */ 3 | if (!global.log) global.log = console; 4 | 5 | /** prettier formatter configuration. */ 6 | const config = { 7 | arrowParens: 'always', 8 | bracketSpacing: true, 9 | endOfLine: 'lf', 10 | printWidth: 80, 11 | proseWrap: 'never', 12 | quoteProps: 'as-needed', 13 | semi: true, 14 | singleQuote: true, 15 | tabWidth: 2, 16 | trailingComma: 'all', 17 | useTabs: true, 18 | overrides: [ 19 | { 20 | files: ['**/*.json', '**/*.yaml', '**/*.yml'], 21 | options: { 22 | useTabs: false, 23 | }, 24 | }, 25 | ], 26 | }; 27 | 28 | if (typeof require !== 'undefined' && require.main === module) { 29 | // When a script. 30 | log.debug(JSON.stringify(config, null, 2)); 31 | } else { 32 | // When a module. 33 | module.exports = config; 34 | } 35 | -------------------------------------------------------------------------------- /src/ranks/standard.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Rank } from './_rank'; 3 | 4 | export namespace standard { 5 | export const ace = new Rank('A', 'Ace'); 6 | export const two = new Rank('2', 'Two'); 7 | export const three = new Rank('3', 'Three'); 8 | export const four = new Rank('4', 'Four'); 9 | export const five = new Rank('5', 'Five'); 10 | export const six = new Rank('6', 'Six'); 11 | export const seven = new Rank('7', 'Seven'); 12 | export const eight = new Rank('8', 'Eight'); 13 | export const nine = new Rank('9', 'Nine'); 14 | export const ten = new Rank('10', 'Ten'); 15 | export const jack = new Rank('J', 'Jack'); 16 | export const queen = new Rank('Q', 'Queen'); 17 | export const king = new Rank('K', 'King'); 18 | export const joker = new Rank('Joker', 'Joker'); 19 | } 20 | -------------------------------------------------------------------------------- /src/decks/euchre.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { spades, hearts, diamonds, clubs } from '../suits'; 6 | import { standard } from '../ranks'; 7 | 8 | export interface EuchreDeckOpts { 9 | rng?: RandomGenerator; 10 | } 11 | 12 | const suits = [ spades, hearts, diamonds, clubs ]; 13 | const ranks = [ 14 | standard.ace, 15 | standard.nine, 16 | standard.ten, 17 | standard.jack, 18 | standard.queen, 19 | standard.king 20 | ]; 21 | 22 | export class EuchreDeck extends Deck { 23 | constructor(opts: EuchreDeckOpts = { }) { 24 | const cards = EuchreDeck.generateCards(); 25 | 26 | super({ 27 | cards, 28 | rng: opts.rng 29 | }); 30 | } 31 | 32 | private static generateCards() { 33 | const cards: Card[] = [ ]; 34 | 35 | suits.forEach((suit) => { 36 | ranks.forEach((rank) => { 37 | cards.push(new Card(suit, rank)); 38 | }); 39 | }); 40 | 41 | return cards; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/decks/piquet.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { spades, hearts, diamonds, clubs } from '../suits'; 6 | import { standard } from '../ranks'; 7 | 8 | export interface PiquetDeckOpts { 9 | rng?: RandomGenerator; 10 | } 11 | 12 | const suits = [ spades, hearts, diamonds, clubs ]; 13 | const ranks = [ 14 | standard.ace, 15 | standard.seven, 16 | standard.eight, 17 | standard.nine, 18 | standard.ten, 19 | standard.jack, 20 | standard.queen, 21 | standard.king 22 | ]; 23 | 24 | export class PiquetDeck extends Deck { 25 | constructor(opts: PiquetDeckOpts = { }) { 26 | const cards = PiquetDeck.generateCards(); 27 | 28 | super({ 29 | cards, 30 | rng: opts.rng 31 | }); 32 | } 33 | 34 | private static generateCards() { 35 | const cards: Card[] = [ ]; 36 | 37 | suits.forEach((suit) => { 38 | ranks.forEach((rank) => { 39 | cards.push(new Card(suit, rank)); 40 | }); 41 | }); 42 | 43 | return cards; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/decks/pinochel.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { spades, hearts, diamonds, clubs } from '../suits'; 6 | import { standard } from '../ranks'; 7 | 8 | export interface PinochelDeckOpts { 9 | rng?: RandomGenerator; 10 | } 11 | 12 | const suits = [ spades, hearts, diamonds, clubs ]; 13 | const ranks = [ 14 | standard.ace, 15 | standard.nine, 16 | standard.ten, 17 | standard.jack, 18 | standard.queen, 19 | standard.king 20 | ]; 21 | 22 | export class PinochelDeck extends Deck { 23 | constructor(opts: PinochelDeckOpts = { }) { 24 | const cards = PinochelDeck.generateCards(); 25 | 26 | super({ 27 | cards, 28 | rng: opts.rng 29 | }); 30 | } 31 | 32 | private static generateCards() { 33 | const cards: Card[] = [ ]; 34 | 35 | suits.forEach((suit) => { 36 | ranks.forEach((rank) => { 37 | cards.push(new Card(suit, rank)); 38 | cards.push(new Card(suit, rank)); 39 | }); 40 | }); 41 | 42 | return cards; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/decks/major-arcana.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { majorArcana } from '../suits'; 6 | import { majorArcana as ma } from '../ranks'; 7 | 8 | export interface MajorArcanaDeckOpts { 9 | rng?: RandomGenerator; 10 | } 11 | 12 | const arcanaRanks = [ 13 | ma.arcana0, ma.arcana1, ma.arcana2, ma.arcana3, ma.arcana4, ma.arcana5, ma.arcana6, ma.arcana7, 14 | ma.arcana8, ma.arcana9, ma.arcana10, ma.arcana11, ma.arcana12, ma.arcana13, ma.arcana14, 15 | ma.arcana15, ma.arcana16, ma.arcana17, ma.arcana18, ma.arcana19, ma.arcana20, ma.arcana21 16 | ]; 17 | 18 | export class MajorArcanaDeck extends Deck { 19 | constructor(opts: MajorArcanaDeckOpts = { }) { 20 | const cards = MajorArcanaDeck.generateCards(); 21 | 22 | super({ 23 | cards, 24 | rng: opts.rng 25 | }); 26 | } 27 | 28 | private static generateCards() { 29 | const cards: Card[] = [ ]; 30 | 31 | arcanaRanks.forEach((rank) => { 32 | cards.push(new Card(majorArcana, rank)); 33 | }); 34 | 35 | return cards; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018,2022 James Brumond 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/.nycrc.js: -------------------------------------------------------------------------------- 1 | /** Simple logger. */ 2 | /* istanbul ignore next: Unnecessary. */ 3 | if (!global.log) global.log = console; 4 | 5 | /** Unit test coverage configuration. */ 6 | const config = { 7 | exclude: ['node_modules/', 'tmp/', 'docs/', '.github/', 'report/', 'tests/'], 8 | reporter: ['html', 'json-summary', 'text', 'cobertura'], 9 | 'report-dir': 'report/test/unit/coverage', 10 | 'temp-directory': 'report/test/unit/coverage/.nyc_output', 11 | 12 | // Report coverage (or lack of) for all, not just the ones with tests. 13 | all: true, 14 | 15 | // Check whether within thresholds provided. 16 | 'check-coverage': true, 17 | 18 | cache: true, 19 | 'cache-dir': 'report/test/unit/coverage/.cache', 20 | clean: true, 21 | 22 | // What percentages of coverage are required. 23 | branches: 0, 24 | lines: 0, 25 | functions: 0, 26 | statements: 0, 27 | 28 | // Provides the green, yellow, red you see in text and bar charts. 29 | watermarks: { 30 | branches: [0, 0], 31 | lines: [75, 95], 32 | functions: [75, 95], 33 | statements: [75, 95], 34 | }, 35 | }; 36 | 37 | if (typeof require !== 'undefined' && require.main === module) { 38 | // When a script. 39 | log.debug(JSON.stringify(config, null, 2)); 40 | } else { 41 | // When a module. 42 | module.exports = config; 43 | } 44 | -------------------------------------------------------------------------------- /src/suits/unicode.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Suit } from './_suit'; 3 | import { Rank } from '../ranks'; 4 | import { jokerColor } from '../cards'; 5 | 6 | /** 7 | * Generic symbols for the various playing card suits. Specifically, the "wands" suit doesn't 8 | * really have a good match for a character, so the "clubs" character is reused. Also, the "trump", 9 | * "major arcana", and "none" suits do not have characters defined. 10 | */ 11 | export const unicodeSuits = new Map(); 12 | 13 | /** 14 | * A nested map structure containing the unicode characters for specific playing cards. The only cards 15 | * that have "well defined" characters in unicode right now are the standard 52 card deck, jokers, and 16 | * the modernized trump suit. The major arcana suit currently uses the same characters as the modern trump 17 | * suit. The other suits do not have unicode characters defined. 18 | * 19 | * _Note: Joker unicode characters are listed separately in `unicodeJokers`_ 20 | * 21 | * @see {@link https://www.compart.com/en/unicode/block/U+1F0A0} 22 | * @see {@link http://unicode.org/cldr/utility/list-unicodeset.jsp?a=[:Block=Playing_Cards:]} 23 | */ 24 | export const unicodeCards = new Map>(); 25 | 26 | /** 27 | * 28 | */ 29 | export const unicodeJokers = new Map(); 30 | -------------------------------------------------------------------------------- /src/cards/_card.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from '../decks'; 3 | import { Rank } from '../ranks'; 4 | import { Suit } from '../suits'; 5 | 6 | export class Card { 7 | private _deck: Deck; 8 | 9 | constructor( 10 | public readonly suit: Suit, 11 | public readonly rank: Rank 12 | ) { } 13 | 14 | /** 15 | * Returns a human-readable string representation of the card object 16 | * 17 | * eg. `""` 18 | * 19 | * @return {string} 20 | */ 21 | public toString() { 22 | return ``; 23 | } 24 | 25 | /** 26 | * The deck instance that this card belongs to 27 | */ 28 | public get deck() { 29 | return this._deck; 30 | } 31 | 32 | set deck(deck) { 33 | if (deck instanceof Deck) { 34 | if (this._deck) { 35 | this._deck.remove(this); 36 | this._deck = null; 37 | } 38 | 39 | this._deck = deck; 40 | } 41 | 42 | else if (deck == null) { 43 | this._deck = null; 44 | } 45 | 46 | else { 47 | throw new Error('Attempted to set `deck` to an invalid value; Must be an instance of Deck, or null'); 48 | } 49 | } 50 | 51 | /** 52 | * The unicode character that represents this particular card if one exists 53 | */ 54 | public get unicode() { 55 | return this.suit.unicodeCards.get(this.rank); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ranks/trump.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Rank } from './_rank'; 3 | 4 | export namespace trump { 5 | export const trump0 = new Rank('0', 'The Fool'); 6 | export const trump1 = new Rank('I', 'The Individual'); 7 | export const trump2 = new Rank('II', 'Childhood'); 8 | export const trump3 = new Rank('III', 'Youth'); 9 | export const trump4 = new Rank('IV', 'Maturity'); 10 | export const trump5 = new Rank('V', 'Old Age'); 11 | export const trump6 = new Rank('VI', 'Morning'); 12 | export const trump7 = new Rank('VII', 'Afternoon'); 13 | export const trump8 = new Rank('VIII', 'Evening'); 14 | export const trump9 = new Rank('IX', 'Night'); 15 | export const trump10 = new Rank('X', 'Earth & Air'); 16 | export const trump11 = new Rank('XI', 'Water & Fire'); 17 | export const trump12 = new Rank('XII', 'Dance'); 18 | export const trump13 = new Rank('XIII', 'Shopping'); 19 | export const trump14 = new Rank('XIV', 'The Outdoors'); 20 | export const trump15 = new Rank('XV', 'Visual Arts'); 21 | export const trump16 = new Rank('XVI', 'Spring'); 22 | export const trump17 = new Rank('XVII', 'Summer'); 23 | export const trump18 = new Rank('XVIII', 'Autumn'); 24 | export const trump19 = new Rank('XIX', 'Winter'); 25 | export const trump20 = new Rank('XX', 'The Game'); 26 | export const trump21 = new Rank('XXI', 'The Collective'); 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cards", 3 | "description": "Basic playing cards module", 4 | "version": "2.0.3", 5 | "author": "James Brumond (http://jbrumond.me)", 6 | "scripts": { 7 | "test": "npm run test:unit", 8 | "test:unit": "mocha --config tests/.mocharc.js", 9 | "test:unit:cover": "nyc --nycrc-path tests/.nycrc.js mocha --config tests/.mocharc.js", 10 | "clean": "rimraf ./build ./docs", 11 | "docs": "typedoc --out docs src/index.ts", 12 | "build": "tsc --build tsconfig.json", 13 | "format": "prettier --write .", 14 | "format:check": "prettier --check ." 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/kbjr/node-cards.git" 19 | }, 20 | "homepage": "https://kbjr.github.io/node-cards/", 21 | "keywords": [ 22 | "cards", 23 | "games", 24 | "random", 25 | "shuffle" 26 | ], 27 | "main": "build/index.js", 28 | "license": "MIT", 29 | "devDependencies": { 30 | "@types/chai": "^4.3.0", 31 | "@types/node": "^17.0.21", 32 | "chai": "^4.3.6", 33 | "mocha": "^9.2.2", 34 | "mochawesome": "^7.1.3", 35 | "nyc": "^15.1.0", 36 | "prettier": "^2.7.1", 37 | "rimraf": "^3.0.2", 38 | "source-map-support": "^0.5.19", 39 | "ts-node": "^10.7.0", 40 | "typedoc": "^0.22.13", 41 | "typescript": "^4.3.4" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cards/joker.ts: -------------------------------------------------------------------------------- 1 | 2 | import { none } from '../suits'; 3 | import { Card } from './_card'; 4 | import { standard } from '../ranks'; 5 | import { unicodeJokers } from '../suits/unicode'; 6 | 7 | export enum jokerColor { 8 | black = 'black', 9 | white = 'white', 10 | red = 'red', 11 | } 12 | 13 | let preferedColor: jokerColor = jokerColor.black; 14 | 15 | /** 16 | * Sets the prefered joker color to use when creating joker cards. Effects the unicode character 17 | * for the card. 18 | * 19 | * @param color 20 | */ 21 | export function preferedJokerColor(color: jokerColor) { 22 | preferedColor = color; 23 | } 24 | 25 | /** 26 | * Special sub-class for representing Joker cards 27 | * 28 | * @param color Controls the specific unicode character used to represent the card; Defaults 29 | * to whatever is set as the prefered color (see `preferedJokerColor()`) 30 | */ 31 | export class JokerCard extends Card { 32 | constructor( 33 | public readonly color: jokerColor = preferedColor 34 | ) { 35 | super(none, standard.joker); 36 | } 37 | 38 | /** 39 | * Returns a human-readable string representation of the card object 40 | * 41 | * eg. `""` 42 | */ 43 | public toString() { 44 | return ``; 45 | } 46 | 47 | public get unicode() { 48 | return unicodeJokers.get(this.color); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ranks/major-arcana.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Rank } from './_rank'; 3 | 4 | export namespace majorArcana { 5 | export const arcana0 = new Rank('0', 'The Fool'); 6 | export const arcana1 = new Rank('I', 'The Magician'); 7 | export const arcana2 = new Rank('II', 'The High Priestess'); 8 | export const arcana3 = new Rank('III', 'The Empress'); 9 | export const arcana4 = new Rank('IV', 'The Emperor'); 10 | export const arcana5 = new Rank('V', 'The Hierophant'); 11 | export const arcana6 = new Rank('VI', 'The Lovers'); 12 | export const arcana7 = new Rank('VII', 'The Chariot'); 13 | export const arcana8 = new Rank('VIII', 'Strength'); 14 | export const arcana9 = new Rank('IX', 'The Hermit'); 15 | export const arcana10 = new Rank('X', 'Wheel of Fortune'); 16 | export const arcana11 = new Rank('XI', 'Justice'); 17 | export const arcana12 = new Rank('XII', 'The Hanged Man'); 18 | export const arcana13 = new Rank('XIII', 'Death'); 19 | export const arcana14 = new Rank('XIV', 'Temperance'); 20 | export const arcana15 = new Rank('XV', 'The Devil'); 21 | export const arcana16 = new Rank('XVI', 'The Tower'); 22 | export const arcana17 = new Rank('XVII', 'The Star'); 23 | export const arcana18 = new Rank('XVIII', 'The Moon'); 24 | export const arcana19 = new Rank('XIX', 'The Sun'); 25 | export const arcana20 = new Rank('XX', 'Judgement'); 26 | export const arcana21 = new Rank('XXI', 'The World'); 27 | } 28 | -------------------------------------------------------------------------------- /src/decks/standard.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card, JokerCard } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { spades, hearts, diamonds, clubs } from '../suits'; 6 | import { standard } from '../ranks'; 7 | 8 | export interface StandardDeckOpts { 9 | /** Either a number of jokers to generate, or an array of specific joker cards to add to the deck */ 10 | jokers?: number | JokerCard[]; 11 | rng?: RandomGenerator; 12 | } 13 | 14 | const suits = [ spades, hearts, diamonds, clubs ]; 15 | const ranks = [ 16 | standard.ace, 17 | standard.two, 18 | standard.three, 19 | standard.four, 20 | standard.five, 21 | standard.six, 22 | standard.seven, 23 | standard.eight, 24 | standard.nine, 25 | standard.ten, 26 | standard.jack, 27 | standard.queen, 28 | standard.king 29 | ]; 30 | 31 | export class StandardDeck extends Deck { 32 | constructor(opts: StandardDeckOpts = { }) { 33 | const cards = StandardDeck.generateCards(opts); 34 | 35 | super({ 36 | cards, 37 | rng: opts.rng 38 | }); 39 | } 40 | 41 | private static generateCards({ jokers }: StandardDeckOpts) { 42 | const cards: Card[] = [ ]; 43 | 44 | suits.forEach((suit) => { 45 | ranks.forEach((rank) => { 46 | cards.push(new Card(suit, rank)); 47 | }); 48 | }); 49 | 50 | if (Array.isArray(jokers)) { 51 | if (! jokers.every((card) => card instanceof JokerCard)) { 52 | throw new Error('Only joker cards may be provided in the jokers parameter'); 53 | } 54 | 55 | cards.push(...jokers); 56 | } 57 | 58 | else { 59 | for (let i = 0; i < jokers; i++) { 60 | cards.push(new JokerCard()); 61 | } 62 | } 63 | 64 | return cards; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | 2 | ## Contributing 3 | 4 | ### A Quick Note 5 | 6 | This project is maintained by one person, and I have a full-time job and other projects. While I generally want to 7 | help and continue to provide updates, I am not always going to be quick to respond or react to everything. 8 | 9 | Please manage your expectations accordingly. 10 | 11 | ### Where should certain things go? 12 | 13 | - If you have a question, please use the [Discussions](https://github.com/kbjr/node-cards/discussions) section 14 | - If you have a bug or feature request, please use the [Issues](https://github.com/kbjr/node-cards/issues) section 15 | 16 | ### Pull Requests 17 | 18 | Pull Requests are welcome, given the following: 19 | 20 | - Attempt to generally follow the same coding style that already exists in the library. I don't have a styleguide or linter setup at the moment, and I don't really want to have to police such things if I don't have to; Please don't make me. 21 | - See `.editorconfig` and `.prettierrc.js` for minimal style guidance. 22 | - There is CI running in GitHub Actions; Please make sure that all builds succeed and pass all existing tests. 23 | - On the point of tests: there aren't very many at the moment, so I won't say its a requirement to add new tests if you add new code, but I also won't stop you... 24 | - Please don't make unneccesary breaking changes 25 | 26 | ### Important Commands and Such 27 | 28 | #### Build Source Code 29 | 30 | ```bash 31 | $ npm run build 32 | ``` 33 | 34 | #### Rebuild Documentation 35 | 36 | ```bash 37 | $ npm run docs 38 | ``` 39 | 40 | #### Run Tests 41 | 42 | ```bash 43 | $ npm test 44 | ``` 45 | 46 | #### Delete All Build Files 47 | 48 | ```bash 49 | $ npm run clean 50 | ``` 51 | -------------------------------------------------------------------------------- /tests/Card.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, beforeEach, it } from 'mocha'; 2 | import { expect } from 'chai'; 3 | import { Suit } from '../src/suits'; 4 | import { Rank } from '../src/ranks'; 5 | import { Deck } from '../src/decks'; 6 | import { Card } from '../src'; 7 | 8 | describe('Card', function () { 9 | describe('Given a suit', function () { 10 | beforeEach(function () { 11 | this.suit001 = new Suit('suit001'); 12 | }); 13 | 14 | describe('and a rank', function () { 15 | beforeEach(function () { 16 | this.rank001 = new Rank('1', 'rank001'); 17 | }); 18 | 19 | describe('then toString', function () { 20 | beforeEach(function () { 21 | this.card = new Card(this.suit001, this.rank001); 22 | }); 23 | 24 | it('should return a suit and rank string.', function () { 25 | const expected = ``; 26 | expect(this.card.toString()).to.equal(expected); 27 | }); 28 | }); 29 | 30 | describe('when a deck is set', function () { 31 | beforeEach(function () { 32 | this.deck001 = new Deck(); 33 | this.card = new Card(this.suit001, this.rank001); 34 | this.card.deck = this.deck001; 35 | }); 36 | 37 | it('then deck should return instance of Deck.', function () { 38 | expect(this.card.deck).to.be.instanceof(Deck); 39 | }); 40 | }); 41 | 42 | describe('when an invalid desk is set', function () { 43 | beforeEach(function () { 44 | this.deckInvalid = new Suit('notADeck'); 45 | this.card = new Card(this.suit001, this.rank001); 46 | }); 47 | 48 | it('then it should throw an invalid value error.', function () { 49 | expect(() => { 50 | this.card.deck = this.deckInvalid; 51 | }).to.throw('Must be an instance of Deck'); 52 | }); 53 | }); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /src/decks/baraja.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card, JokerCard } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { swords, cups, coins, wands } from '../suits'; 6 | import { standard, nonStandard } from '../ranks'; 7 | 8 | export interface BarajaDeckOpts { 9 | /** Either a number of jokers to generate, or an array of specific joker cards to add to the deck */ 10 | jokers?: number | JokerCard[]; 11 | /** Create a stripped deck (no 8 or 9 rank cards) */ 12 | stripped?: boolean; 13 | rng?: RandomGenerator; 14 | } 15 | 16 | const suits = [ swords, cups, coins, wands ]; 17 | const ranks = [ 18 | nonStandard.one, 19 | standard.two, 20 | standard.three, 21 | standard.four, 22 | standard.five, 23 | standard.six, 24 | standard.seven, 25 | standard.eight, 26 | standard.nine, 27 | standard.ten, 28 | standard.jack, 29 | nonStandard.knight, 30 | standard.king 31 | ]; 32 | 33 | export class BarajaDeck extends Deck { 34 | constructor(opts: BarajaDeckOpts = { }) { 35 | const cards = BarajaDeck.generateCards(opts); 36 | 37 | super({ 38 | cards, 39 | rng: opts.rng 40 | }); 41 | } 42 | 43 | private static generateCards({ jokers, stripped }: BarajaDeckOpts) { 44 | const cards: Card[] = [ ]; 45 | 46 | suits.forEach((suit) => { 47 | ranks.forEach((rank) => { 48 | if (stripped && (rank.abbrn === '8' || rank.abbrn === '9')) { 49 | return; 50 | } 51 | 52 | cards.push(new Card(suit, rank)); 53 | }); 54 | }); 55 | 56 | if (Array.isArray(jokers)) { 57 | if (! jokers.every((card) => card instanceof JokerCard)) { 58 | throw new Error('Only joker cards may be provided in the jokers parameter'); 59 | } 60 | 61 | cards.push(...jokers); 62 | } 63 | 64 | else { 65 | for (let i = 0; i < jokers; i++) { 66 | cards.push(new JokerCard()); 67 | } 68 | } 69 | 70 | return cards; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/shuffle.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Shuffles the given array using the given (psuedo-)random number generator 4 | * 5 | * @param array 6 | * @param rng 7 | */ 8 | export function shuffle(array: T[], rng: RandomGenerator) { 9 | let j: number; 10 | 11 | for (let i = array.length - 1; i > 0; i--) { 12 | j = rng.int(i); 13 | let temp = array[i]; 14 | array[i] = array[j]; 15 | array[j] = temp; 16 | } 17 | } 18 | 19 | /** 20 | * A (Psuedo-)Random Number Generator used to provide randomization for shuffling. 21 | * 22 | * @param max The generator's maximum value (int, exclusive) 23 | * @returns An integer between 0 (inclusive) and `max` (exclusive) 24 | */ 25 | export interface RandomGenerator { 26 | int(max: number) : number; 27 | } 28 | 29 | /** 30 | * A simple PRNG implementation using the built-in `Math.random()` 31 | */ 32 | export class MathRandomGenerator implements RandomGenerator { 33 | int(max: number) : number { 34 | return (Math.random() * max) | 0; 35 | } 36 | } 37 | 38 | /** 39 | * A PRNG implementation using the nodejs `crypto` module's `psuedoRandomBytes()` 40 | */ 41 | export class NodeCryptoRandomGenerator implements RandomGenerator { 42 | // NOTE: Should be a multiple of 4; We don't check this assumption for efficiency sake, 43 | // so violating it will break things 44 | private static poolSize = 1024; 45 | 46 | private pool: Buffer; 47 | private poolIndex: number; 48 | 49 | int(max: number) : number { 50 | if (! this.pool || this.poolIndex >= NodeCryptoRandomGenerator.poolSize) { 51 | this.refreshPool(); 52 | } 53 | 54 | const int = this.pool.readUInt32LE(this.poolIndex); 55 | this.poolIndex += 4; 56 | 57 | return ((int / 0xffffffff) * max) | 0; 58 | } 59 | 60 | private refreshPool() { 61 | const { pseudoRandomBytes } = require('crypto') as typeof import('crypto'); 62 | 63 | this.pool = pseudoRandomBytes(NodeCryptoRandomGenerator.poolSize); 64 | this.poolIndex = 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/decks/tarot.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Deck } from './_deck'; 3 | import { Card } from '../cards'; 4 | import { RandomGenerator } from '../shuffle'; 5 | import { swords, cups, coins, wands, trump, majorArcana } from '../suits'; 6 | import { standard, trump as t, majorArcana as ma } from '../ranks'; 7 | 8 | export interface TarotDeckOpts { 9 | rng?: RandomGenerator; 10 | /** Which style of trump suit should be used */ 11 | trumpSuit?: TarotTrumpSuit; 12 | } 13 | 14 | export enum TarotTrumpSuit { 15 | modern = 'modern', 16 | traditional = 'traditional', 17 | } 18 | 19 | const suits = [ swords, cups, coins, wands ]; 20 | const ranks = [ 21 | standard.ace, 22 | standard.nine, 23 | standard.ten, 24 | standard.jack, 25 | standard.queen, 26 | standard.king 27 | ]; 28 | 29 | const trumpRanks = [ 30 | t.trump0, t.trump1, t.trump2, t.trump3, t.trump4, t.trump5, t.trump6, t.trump7, 31 | t.trump8, t.trump9, t.trump10, t.trump11, t.trump12, t.trump13, t.trump14, 32 | t.trump15, t.trump16, t.trump17, t.trump18, t.trump19, t.trump20, t.trump21 33 | ]; 34 | 35 | const arcanaRanks = [ 36 | ma.arcana0, ma.arcana1, ma.arcana2, ma.arcana3, ma.arcana4, ma.arcana5, ma.arcana6, ma.arcana7, 37 | ma.arcana8, ma.arcana9, ma.arcana10, ma.arcana11, ma.arcana12, ma.arcana13, ma.arcana14, 38 | ma.arcana15, ma.arcana16, ma.arcana17, ma.arcana18, ma.arcana19, ma.arcana20, ma.arcana21 39 | ]; 40 | 41 | export class TarotDeck extends Deck { 42 | constructor(opts: TarotDeckOpts = { }) { 43 | const cards = TarotDeck.generateCards(opts.trumpSuit || TarotTrumpSuit.modern); 44 | 45 | super({ 46 | cards, 47 | rng: opts.rng 48 | }); 49 | } 50 | 51 | private static generateCards(trumpSuit: TarotTrumpSuit) { 52 | const cards: Card[] = [ ]; 53 | 54 | // The minor arcana 55 | suits.forEach((suit) => { 56 | ranks.forEach((rank) => { 57 | cards.push(new Card(suit, rank)); 58 | }); 59 | }); 60 | 61 | // The major arcana 62 | if (trumpSuit === 'modern') { 63 | trumpRanks.forEach((rank) => { 64 | cards.push(new Card(trump, rank)); 65 | }); 66 | } 67 | 68 | else if (trumpSuit === 'traditional') { 69 | arcanaRanks.forEach((rank) => { 70 | cards.push(new Card(majorArcana, rank)); 71 | }); 72 | } 73 | 74 | return cards; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/shuffle.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { describe, it } from 'mocha'; 3 | import { expect } from 'chai'; 4 | import { shuffle, RandomGenerator, MathRandomGenerator, NodeCryptoRandomGenerator } from '../src'; 5 | 6 | describe('shuffle', function() { 7 | it('should exist, and be a function', function() { 8 | expect(shuffle).to.be.a('function'); 9 | }); 10 | 11 | it('should shuffle the given list in a non-predictable way', function() { 12 | // TODO: Should use some kind of seeded RandomGenerator implementation 13 | // for tests so test runs can be made reproducable 14 | const rng = new MathRandomGenerator(); 15 | 16 | const list = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 ]; 17 | const listA = list.slice(); 18 | const listB = list.slice(); 19 | const listC = list.slice(); 20 | 21 | shuffle(listA, rng); 22 | shuffle(listB, rng); 23 | shuffle(listC, rng); 24 | 25 | // The contents of the lists should not have changed 26 | expect(listA).to.have.same.members(list); 27 | expect(listB).to.have.same.members(list); 28 | expect(listC).to.have.same.members(list); 29 | 30 | // But the orders should have... 31 | expect(listA).not.to.deep.eq(list); 32 | expect(listB).not.to.deep.eq(list); 33 | expect(listC).not.to.deep.eq(list); 34 | 35 | // And they should not match each other, either 36 | expect(listA).not.to.deep.eq(listB); 37 | expect(listB).not.to.deep.eq(listC); 38 | expect(listC).not.to.deep.eq(listA); 39 | }); 40 | }); 41 | 42 | describe('RandomGenerator implementations', function() { 43 | describeRandomGenerator('MathRandomGenerator', MathRandomGenerator); 44 | describeRandomGenerator('NodeCryptoRandomGenerator', NodeCryptoRandomGenerator); 45 | }); 46 | 47 | interface RandomGeneratorConstructor { 48 | new (): RandomGenerator; 49 | } 50 | 51 | function describeRandomGenerator(desc: string, Generator: RandomGeneratorConstructor) { 52 | describe(desc, function() { 53 | it('should exist, and be a function', function() { 54 | expect(Generator).to.be.a('function'); 55 | }); 56 | 57 | describe('#int(max)', function() { 58 | it('should generate numbers between 0 and `max`', function() { 59 | const rng = new Generator(); 60 | 61 | [ 10, 30, 50, 70, 90 ].forEach((max) => { 62 | for (let i = 0; i < 50; i++) { 63 | const rand = rng.int(max); 64 | expect(rand).to.be.gte(0); 65 | expect(rand).to.be.lt(max, 'expected to be less than `max`'); 66 | expect(rand | 0).to.eq(rand, 'expected an int'); 67 | } 68 | }); 69 | }); 70 | }); 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #000000; 3 | --dark-hl-0: #D4D4D4; 4 | --light-hl-1: #AF00DB; 5 | --dark-hl-1: #C586C0; 6 | --light-hl-2: #001080; 7 | --dark-hl-2: #9CDCFE; 8 | --light-hl-3: #A31515; 9 | --dark-hl-3: #CE9178; 10 | --light-hl-4: #008000; 11 | --dark-hl-4: #6A9955; 12 | --light-hl-5: #0000FF; 13 | --dark-hl-5: #569CD6; 14 | --light-hl-6: #0070C1; 15 | --dark-hl-6: #4FC1FF; 16 | --light-hl-7: #795E26; 17 | --dark-hl-7: #DCDCAA; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #267F99; 21 | --dark-hl-9: #4EC9B0; 22 | --light-code-background: #F5F5F5; 23 | --dark-code-background: #1E1E1E; 24 | } 25 | 26 | @media (prefers-color-scheme: light) { :root { 27 | --hl-0: var(--light-hl-0); 28 | --hl-1: var(--light-hl-1); 29 | --hl-2: var(--light-hl-2); 30 | --hl-3: var(--light-hl-3); 31 | --hl-4: var(--light-hl-4); 32 | --hl-5: var(--light-hl-5); 33 | --hl-6: var(--light-hl-6); 34 | --hl-7: var(--light-hl-7); 35 | --hl-8: var(--light-hl-8); 36 | --hl-9: var(--light-hl-9); 37 | --code-background: var(--light-code-background); 38 | } } 39 | 40 | @media (prefers-color-scheme: dark) { :root { 41 | --hl-0: var(--dark-hl-0); 42 | --hl-1: var(--dark-hl-1); 43 | --hl-2: var(--dark-hl-2); 44 | --hl-3: var(--dark-hl-3); 45 | --hl-4: var(--dark-hl-4); 46 | --hl-5: var(--dark-hl-5); 47 | --hl-6: var(--dark-hl-6); 48 | --hl-7: var(--dark-hl-7); 49 | --hl-8: var(--dark-hl-8); 50 | --hl-9: var(--dark-hl-9); 51 | --code-background: var(--dark-code-background); 52 | } } 53 | 54 | body.light { 55 | --hl-0: var(--light-hl-0); 56 | --hl-1: var(--light-hl-1); 57 | --hl-2: var(--light-hl-2); 58 | --hl-3: var(--light-hl-3); 59 | --hl-4: var(--light-hl-4); 60 | --hl-5: var(--light-hl-5); 61 | --hl-6: var(--light-hl-6); 62 | --hl-7: var(--light-hl-7); 63 | --hl-8: var(--light-hl-8); 64 | --hl-9: var(--light-hl-9); 65 | --code-background: var(--light-code-background); 66 | } 67 | 68 | body.dark { 69 | --hl-0: var(--dark-hl-0); 70 | --hl-1: var(--dark-hl-1); 71 | --hl-2: var(--dark-hl-2); 72 | --hl-3: var(--dark-hl-3); 73 | --hl-4: var(--dark-hl-4); 74 | --hl-5: var(--dark-hl-5); 75 | --hl-6: var(--dark-hl-6); 76 | --hl-7: var(--dark-hl-7); 77 | --hl-8: var(--dark-hl-8); 78 | --hl-9: var(--dark-hl-9); 79 | --code-background: var(--dark-code-background); 80 | } 81 | 82 | .hl-0 { color: var(--hl-0); } 83 | .hl-1 { color: var(--hl-1); } 84 | .hl-2 { color: var(--hl-2); } 85 | .hl-3 { color: var(--hl-3); } 86 | .hl-4 { color: var(--hl-4); } 87 | .hl-5 { color: var(--hl-5); } 88 | .hl-6 { color: var(--hl-6); } 89 | .hl-7 { color: var(--hl-7); } 90 | .hl-8 { color: var(--hl-8); } 91 | .hl-9 { color: var(--hl-9); } 92 | pre, code { background: var(--code-background); } 93 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | [![CI](https://github.com/kbjr/node-cards/actions/workflows/ci.yaml/badge.svg)](https://github.com/kbjr/node-cards/actions/workflows/ci.yaml) 3 | 4 | Node.js library for dealing with playing cards of all types 5 | 6 | [https://github.com/kbjr/node-cards](https://github.com/kbjr/node-cards) 7 | 8 | Fully extensible, you can create custom versions of any component to make different types of deck, including support for custom decks, suits, ranks, and cards. 9 | 10 | ### Version 2 11 | 12 | Version 2 has now been released. Short list of some of the notable changes: 13 | 14 | - Rewrite in TypeScript 15 | - New customizable randomization source 16 | - The previous options related to RNG have been removed, and a new `RandomGenerator` interface exists to enable [providing a custom RNG implementation](#custom-randomization-source). 17 | - Built-in implementations are provided for `Math.random()` and the node.js `crypto` module's `pseudoRandomBytes()` as randomization sources. 18 | - Some thing have moved around to new locations 19 | - With TypeScript came a switch to using TypeDoc for documentation generation, so docs look different now 20 | - Now has **no runtime dependencies** (check the [package.json](./package.json)) 21 | 22 | ### Install 23 | 24 | ```bash 25 | $ npm install cards 26 | ``` 27 | 28 | ### Features 29 | 30 | - Ability to create decks of cards of various configurations 31 | - Shuffle the deck 32 | - Draw cards and discard cards 33 | - Card types 34 | - Standard suits and values 35 | - Suits: spades, hearts, diamonds, clubs 36 | - Values: 2 - 10, Jack, Queen, King, Ace 37 | - Trump suit / Fool card 38 | - [Minor Arcana][1] 39 | - Suits: coins, wands, cups, swords 40 | - Values: 2 - 10, [Page](https://en.wikipedia.org/wiki/Page_of_Wands), [Knight / Cavalier][2], Queen, King, Ace 41 | - Major Arcana (Tarot cards) 42 | - Jokers 43 | - Deck types 44 | - Standard 52 card deck 45 | - 78 card tarot deck 46 | - 24 card euchre deck 47 | - 48 card pinochel deck 48 | - 32 card piquet deck 49 | - 40 card baraja deck 50 | - 22 card major arcana deck 51 | - Support for creating custom decks 52 | - Supports unicode playing card characters where available 53 | - TypeScript definitions provided 54 | 55 | ### Examples 56 | 57 | ```typescript 58 | import { decks } from 'cards'; 59 | 60 | // Create a standard 52 card deck + 2 jokers 61 | const deck = new decks.StandardDeck({ jokers: 2 }); 62 | 63 | // Shuffle the deck 64 | deck.shuffleAll(); 65 | 66 | // Draw a hand of five cards from the deck 67 | const hand = deck.draw(5); 68 | 69 | // Pull 2 cards out of the hand to exchange 70 | const toExchange = hand.splice(2, 2); 71 | 72 | // Discard those 2 cards 73 | deck.discard(toExchange); 74 | 75 | // Draw 2 new ones from the deck 76 | hand.push(...deck.draw(2)); 77 | ``` 78 | 79 | #### Custom Randomization Source 80 | 81 | ```typescript 82 | import { RandomGenerator, decks } from 'cards'; 83 | 84 | // Create a new RandomGenerator implementation 85 | class MyRNG implements RandomGenerator { 86 | // The `int` method should return a random integer between 0 and `max` 87 | int(max: number) : number { 88 | return (Math.random() * max) | 0; 89 | } 90 | } 91 | 92 | // You can pass an instance of RNG implemenation to any `Deck` class, and 93 | // it will be used for any randomization requiring tasks (i.e. shuffling) 94 | const deck = new decks.StandardDeck({ rng: new MyRNG() }); 95 | ``` 96 | 97 | ### TODO 98 | 99 | - Finish building out poker hand evaluation logic 100 | 101 | [1]: https://en.wikipedia.org/wiki/Minor_Arcana 102 | [2]: https://en.wikipedia.org/wiki/Knight_(playing_card) 103 | 104 | --- 105 | 106 | Like my work? 107 | 108 | [![ko-fi](https://www.ko-fi.com/img/donate_sm.png)](https://ko-fi.com/U7U8MIC8) 109 | -------------------------------------------------------------------------------- /docs/interfaces/decks.FindCardCallback.html: -------------------------------------------------------------------------------- 1 | FindCardCallback | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FindCardCallback

Hierarchy

  • FindCardCallback

Callable

  • FindCardCallback(card: Card): boolean

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules/ranks.html: -------------------------------------------------------------------------------- 1 | ranks | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Namespace ranks

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.EuchreDeckOpts.html: -------------------------------------------------------------------------------- 1 | EuchreDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface EuchreDeckOpts

Hierarchy

  • EuchreDeckOpts

Index

Properties

Properties

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.PiquetDeckOpts.html: -------------------------------------------------------------------------------- 1 | PiquetDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface PiquetDeckOpts

Hierarchy

  • PiquetDeckOpts

Index

Properties

Properties

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.PinochelDeckOpts.html: -------------------------------------------------------------------------------- 1 | PinochelDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface PinochelDeckOpts

Hierarchy

  • PinochelDeckOpts

Index

Properties

Properties

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.MajorArcanaDeckOpts.html: -------------------------------------------------------------------------------- 1 | MajorArcanaDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface MajorArcanaDeckOpts

Hierarchy

  • MajorArcanaDeckOpts

Index

Properties

Properties

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/enums/decks.TarotTrumpSuit.html: -------------------------------------------------------------------------------- 1 | TarotTrumpSuit | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Enumeration TarotTrumpSuit

Index

Enumeration members

Enumeration members

modern = "modern"
traditional = "traditional"

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/RandomGenerator.html: -------------------------------------------------------------------------------- 1 | RandomGenerator | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface RandomGenerator

2 |

A (Psuedo-)Random Number Generator used to provide randomization for shuffling.

3 |
param max

The generator's maximum value (int, exclusive)

4 |
returns

An integer between 0 (inclusive) and max (exclusive)

5 |

Hierarchy

  • RandomGenerator

Implemented by

Index

Methods

Methods

  • int(max: number): number

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.DeckOpts.html: -------------------------------------------------------------------------------- 1 | DeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface DeckOpts

Hierarchy

  • DeckOpts

Index

Properties

Properties

cards?: Card[]

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.TarotDeckOpts.html: -------------------------------------------------------------------------------- 1 | TarotDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface TarotDeckOpts

Hierarchy

  • TarotDeckOpts

Index

Properties

Properties

trumpSuit?: TarotTrumpSuit
2 |

Which style of trump suit should be used

3 |

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.StandardDeckOpts.html: -------------------------------------------------------------------------------- 1 | StandardDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface StandardDeckOpts

Hierarchy

  • StandardDeckOpts

Index

Properties

Properties

jokers?: number | JokerCard[]
2 |

Either a number of jokers to generate, or an array of specific joker cards to add to the deck

3 |

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/enums/jokerColor.html: -------------------------------------------------------------------------------- 1 | jokerColor | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Enumeration jokerColor

Index

Enumeration members

Enumeration members

black = "black"
red = "red"
white = "white"

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/enums/decks.Pile.html: -------------------------------------------------------------------------------- 1 | Pile | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Enumeration Pile

Index

Enumeration members

Enumeration members

deck = "deck"
discard = "discard"
held = "held"

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/decks.CardLocation.html: -------------------------------------------------------------------------------- 1 | CardLocation | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface CardLocation

Hierarchy

  • CardLocation

Index

Properties

Properties

card: Card
index: number
pile: Pile

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/classes/MathRandomGenerator.html: -------------------------------------------------------------------------------- 1 | MathRandomGenerator | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class MathRandomGenerator

2 |

A simple PRNG implementation using the built-in Math.random()

3 |

Hierarchy

  • MathRandomGenerator

Implements

Index

Constructors

Methods

Constructors

Methods

  • int(max: number): number

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /src/decks/_deck.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Card } from '../cards'; 3 | import { remove } from '../util'; 4 | import { MathRandomGenerator, RandomGenerator, shuffle } from '../shuffle'; 5 | 6 | export interface DeckOpts { 7 | cards?: Card[]; 8 | rng?: RandomGenerator; 9 | } 10 | 11 | export interface CardLocation { 12 | pile: Pile; 13 | index: number; 14 | card: Card; 15 | } 16 | 17 | export interface FindCardCallback { 18 | (card: Card): boolean; 19 | } 20 | 21 | export enum Pile { 22 | deck = 'deck', 23 | held = 'held', 24 | discard = 'discard', 25 | } 26 | 27 | export class Deck { 28 | private rng: RandomGenerator; 29 | private cards: Set; 30 | private deckPile: Card[]; 31 | private heldPile: Card[] = [ ]; 32 | private discardPile: Card[] = [ ]; 33 | 34 | constructor(opts: DeckOpts = { }) { 35 | const cards = opts.cards || [ ]; 36 | 37 | this.cards = new Set(cards); 38 | this.deckPile = cards.slice(); 39 | this.rng = opts.rng || new MathRandomGenerator(); 40 | 41 | // Assign each card to the deck 42 | cards.forEach((card) => { 43 | card.deck = this; 44 | }); 45 | } 46 | 47 | /** 48 | * The total number of all cards belonging to this deck, regardless of what pile 49 | * they are currently in 50 | */ 51 | public get totalLength() { 52 | return this.cards.size; 53 | } 54 | 55 | /** 56 | * The current number of cards remaining in the deck pile 57 | */ 58 | public get remainingLength() { 59 | return this.deckPile.length; 60 | } 61 | 62 | /** 63 | * Copy of the array of cards in deck pile 64 | */ 65 | public get remainingCards() { 66 | return this.deckPile.slice(); 67 | } 68 | 69 | /** 70 | * Copy of the array of cards in held pile 71 | */ 72 | public get heldCards() { 73 | return this.heldPile.slice(); 74 | } 75 | 76 | /** 77 | * Copy of the array of cards in discard pile 78 | */ 79 | public get discardedCards() { 80 | return this.discardPile.slice(); 81 | } 82 | 83 | /** 84 | * Add a new Card to the deck, placing at the end of the given pile (defaults to `"deck"`). 85 | * 86 | * _Note: Does not confirm that the Card does not already belong to a deck before adding._ 87 | * 88 | * @param card 89 | * @param pile 90 | */ 91 | public add(card: Card, pile: Pile = Pile.deck) { 92 | card.deck = this; 93 | this.cards.add(card); 94 | this[`${pile}Pile`].push(card); 95 | } 96 | 97 | /** 98 | * Removes a card from the deck entirely. 99 | * 100 | * _Note: Does not confirm that the card belongs to this deck before removing._ 101 | * 102 | * @param card 103 | */ 104 | public remove(card: Card) { 105 | this.cards.delete(card); 106 | remove(this.deckPile, card); 107 | remove(this.discardPile, card); 108 | remove(this.heldPile, card); 109 | card.deck = null; 110 | } 111 | 112 | /** 113 | * Merge the given deck into this one, moving all cards belonging to the given deck into this deck. 114 | * 115 | * @param deck 116 | * @param pile 117 | */ 118 | public merge(deck: Deck, pile: Pile = Pile.deck) { 119 | deck.cards.forEach((card) => { 120 | deck.remove(card); 121 | this.add(card, pile); 122 | }); 123 | } 124 | 125 | /** 126 | * Draw the given number of cards, place them in the held pile, and return the drawn cards 127 | * 128 | * @param count 129 | */ 130 | public draw(count: number = 1) { 131 | if (count <= 0) { 132 | return [ ]; 133 | } 134 | 135 | if (count > this.deckPile.length) { 136 | throw new Error('Cannot draw from deck; not enough cards remaining'); 137 | } 138 | 139 | const cards = this.deckPile.splice(0, count); 140 | this.heldPile.push(...cards); 141 | return cards; 142 | } 143 | 144 | /** 145 | * Draw the given number of cards from the bottom of the deck pile, place them in the held pile, 146 | * and return the drawn cards 147 | * 148 | * @param count 149 | */ 150 | public drawFromBottom(count: number = 1) { 151 | if (count <= 0) { 152 | return [ ]; 153 | } 154 | 155 | if (count > this.deckPile.length) { 156 | throw new Error('Cannot draw from deck; not enough cards remaining'); 157 | } 158 | 159 | const start = Math.max(this.deckPile.length - count, 0); 160 | const cards = this.deckPile.splice(start, count).reverse(); 161 | this.heldPile.push(...cards); 162 | return cards; 163 | } 164 | 165 | /** 166 | * Draw the given number of cards, place them in the discard pile, and return the drawn cards 167 | * 168 | * @param count 169 | */ 170 | public drawToDiscard(count: number = 1) { 171 | if (count <= 0) { 172 | return [ ]; 173 | } 174 | 175 | if (count > this.deckPile.length) { 176 | throw new Error('Cannot draw from deck; not enough cards remaining'); 177 | } 178 | 179 | const cards = this.deckPile.splice(0, count); 180 | this.discardPile.push(...cards); 181 | return cards; 182 | } 183 | 184 | /** 185 | * Draw the given number of cards from the bottom of the deck, place them in the discard pile, 186 | * and return the drawn cards 187 | * 188 | * @param count 189 | */ 190 | public drawToDiscardFromBottom(count: number = 1) { 191 | if (count <= 0) { 192 | return [ ]; 193 | } 194 | 195 | if (count > this.deckPile.length) { 196 | throw new Error('Cannot draw from deck; not enough cards remaining'); 197 | } 198 | 199 | const start = Math.max(this.deckPile.length - count, 0); 200 | const cards = this.deckPile.splice(start, count).reverse(); 201 | this.discardPile.push(...cards); 202 | return cards; 203 | } 204 | 205 | /** 206 | * Move the given card into the discard pile 207 | * 208 | * @param card 209 | */ 210 | public discard(card: Card) { 211 | if (Array.isArray(card)) { 212 | return card.forEach((card) => this.discard(card)); 213 | } 214 | 215 | if (! this.cards.has(card)) { 216 | throw new Error('Provided card does not belong to this deck'); 217 | } 218 | 219 | const deckIndex = this.deckPile.indexOf(card); 220 | 221 | if (deckIndex >= 0) { 222 | this.deckPile.splice(deckIndex, 1); 223 | this.discardPile.push(card); 224 | } 225 | 226 | else { 227 | const heldIndex = this.heldPile.indexOf(card); 228 | 229 | if (heldIndex >= 0) { 230 | this.heldPile.splice(heldIndex, 1); 231 | this.discardPile.push(card); 232 | } 233 | } 234 | } 235 | 236 | /** 237 | * Finds the given card and returns an object representing its current location (pile, and index in that pile) 238 | * 239 | * @param card 240 | */ 241 | public locateCard(card: Card) : CardLocation { 242 | if (! this.cards.has(card)) { 243 | throw new Error('Provided card does not belong to this deck'); 244 | } 245 | 246 | const deckIndex = this.deckPile.indexOf(card); 247 | 248 | if (deckIndex >= 0) { 249 | return { 250 | pile: Pile.deck, 251 | index: deckIndex, 252 | card 253 | }; 254 | } 255 | 256 | const heldIndex = this.heldPile.indexOf(card); 257 | 258 | if (heldIndex >= 0) { 259 | return { 260 | pile: Pile.held, 261 | index: heldIndex, 262 | card 263 | }; 264 | } 265 | 266 | const discardIndex = this.discardPile.indexOf(card); 267 | 268 | if (discardIndex >= 0) { 269 | return { 270 | pile: Pile.discard, 271 | index: discardIndex, 272 | card 273 | }; 274 | } 275 | 276 | // This should never happen 277 | throw new Error('Failed to find the given card'); 278 | } 279 | 280 | /** 281 | * Moves all cards back to the deck pile and shuffles the deck 282 | */ 283 | public shuffleAll() { 284 | this.deckPile.push(...this.heldPile); 285 | this.deckPile.push(...this.discardPile); 286 | 287 | this.heldPile.length = 0; 288 | this.discardPile.length = 0; 289 | 290 | shuffle(this.deckPile, this.rng); 291 | } 292 | 293 | /** 294 | * Shuffles the cards remaining in the deck 295 | */ 296 | public shuffleRemaining() { 297 | shuffle(this.deckPile, this.rng); 298 | } 299 | 300 | /** 301 | * Shuffles the cards in the discard pile and then places them at the end of the deck 302 | */ 303 | public shuffleDiscard() { 304 | shuffle(this.discardPile, this.rng); 305 | this.deckPile.push(...this.discardPile); 306 | this.discardPile.length = 0; 307 | } 308 | 309 | /** 310 | * Moves all cards in the discard back to the deck and shuffles the deck 311 | */ 312 | public shuffleDeckAndDiscard() { 313 | this.deckPile.push(...this.discardPile); 314 | this.discardPile.length = 0; 315 | shuffle(this.deckPile, this.rng); 316 | } 317 | 318 | /** 319 | * Moves all currently held cards to the discard pile 320 | */ 321 | public discardAllHeld() { 322 | this.discardPile.push(...this.heldPile); 323 | this.heldPile.length = 0; 324 | } 325 | 326 | /** 327 | * Finds all cards in the deck matching the given filtering function 328 | * 329 | * ```javascript 330 | * const aces = deck.findCards((card) => card.rank === ace); 331 | * ``` 332 | * 333 | * @param filter 334 | */ 335 | public findCards(filter: FindCardCallback) { 336 | const matching: Card[] = [ ]; 337 | 338 | this.cards.forEach((card) => { 339 | if (filter(card)) { 340 | matching.push(card); 341 | } 342 | }); 343 | 344 | return matching; 345 | } 346 | } 347 | -------------------------------------------------------------------------------- /docs/interfaces/decks.BarajaDeckOpts.html: -------------------------------------------------------------------------------- 1 | BarajaDeckOpts | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface BarajaDeckOpts

Hierarchy

  • BarajaDeckOpts

Index

Properties

jokers?: number | JokerCard[]
2 |

Either a number of jokers to generate, or an array of specific joker cards to add to the deck

3 |
stripped?: boolean
4 |

Create a stripped deck (no 8 or 9 rank cards)

5 |

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Property
  • Method
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules/ranks.nonStandard.html: -------------------------------------------------------------------------------- 1 | nonStandard | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Namespace nonStandard

Index

Variables

cavalier: Rank = ...
knight: Rank = ...
one: Rank = ...
page: Rank = ...

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules/decks.html: -------------------------------------------------------------------------------- 1 | decks | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Namespace decks

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/modules.html: -------------------------------------------------------------------------------- 1 | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

cards

Index

Functions

  • 2 |

    Sets the prefered joker color to use when creating joker cards. Effects the unicode character 3 | for the card.

    4 |

    Parameters

    Returns void

  • 6 |

    Shuffles the given array using the given (psuedo-)random number generator

    7 |

    Type parameters

    • T

    Parameters

    Returns void

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/classes/ranks.Rank.html: -------------------------------------------------------------------------------- 1 | Rank | cards
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class Rank

2 |

The main base class for representing a rank of card (eg. "ace" or "king")

3 |

Hierarchy

  • Rank

Index

Constructors

Properties

Methods

Constructors

  • new Rank(abbrn: string, name: string): Rank

Properties

abbrn: string
name: string

Methods

  • toString(): string
  • 4 |

    Returns a human-readable string representation of the rank object

    5 |

    eg. "<Rank name=Ace abbrn=A>"

    6 |

    Returns string

Legend

  • Constructor
  • Property
  • Method
  • Accessor
  • Inherited property
  • Inherited accessor
  • Private property
  • Private method
  • Method

Settings

Theme

Generated using TypeDoc

--------------------------------------------------------------------------------