Skip to main content

Built and signed on GitHub Actions

A modular JavaScript library for implementing Hanafuda card games, with a focus on Koi-Koi. Features core mechanics, scoring rules, and state management.

This package works with Cloudflare Workers, Node.js, Deno, Bun, Browsers
This package works with Cloudflare Workers
This package works with Node.js
This package works with Deno
This package works with Bun
This package works with Browsers
JSR Score
94%
Published
3 weeks ago (0.3.0)

Hanafuda Koi-Koi

A modular JavaScript library for implementing Hanafuda card games, with a focus on Koi-Koi. This library provides core functionality for card management, game state handling, and scoring according to traditional Koi-Koi rules.

Features

  • Complete Hanafuda card game engine
  • Scoring system with traditional yaku (combinations)
  • State management with serialization support
  • Collection-based card management
  • Phase-based game flow

Installation

NPM

npm install @fudapop/hanafuda-js

Deno

import { KoiKoi } from "jsr:@fudapop/hanafuda-js"

CDN

You can include Hanafuda-JS directly in your HTML:

<script src="https://unpkg.com/@fudapop/hanafuda-js"></script>

Then use it globally:

<script>
  const game = new hanafudaJS.KoiKoi()

  // Start a new round
  const { state, teyaku } = game.startRound()

  // Access other exports
  const deck = hanafudaJS.createStandardDeck()
  const collection = hanafudaJS.createCollection()
</script>

Usage

ESM/TypeScript

import { KoiKoi, createStandardDeck, createCollection } from "@fudapop/hanafuda-js"

// Create a new game instance
const game = new KoiKoi()

// Start a new round
const { state, teyaku } = game.startRound()

CommonJS

const { KoiKoi, createStandardDeck, createCollection } = require("@fudapop/hanafuda-js")

// Create a new game instance
const game = new KoiKoi()

// Start a new round
const { state, teyaku } = game.startRound()

Basic Game Setup

import { KoiKoi } from "./src/koikoi/koikoi.js"

// Create a new game instance
const game = new KoiKoi()

// Start a new round
const { state, teyaku } = game.startRound()

// Check for any initial teyaku (hand yaku)
if (Object.keys(teyaku).length > 0) {
  for (const [playerId, playerTeyaku] of Object.entries(teyaku)) {
    console.log(`Player ${playerId} has teyaku:`, playerTeyaku)
  }
}

Playing a Turn

// Select a card from hand
const result = game.selectCard(cardIndex, "hand")
if (result.type === "NO_MATCHES") {
  // No matches available - card will be discarded
  game.placeSelectedCard()
} else if (result.type === "SELECTION_UPDATED") {
  // Card selected - now select matching field cards
  const fieldResult = game.selectCard(fieldCardIndex, "field")
  if (fieldResult.type === "SELECTION_UPDATED") {
    // Play the selected cards
    const playResult = game.playCards()
    // Handle the result (DECK_DRAW, SCORE_UPDATE, etc.)
  }
}

Handling Drawn Cards

// After playing cards, a card is automatically drawn
// The result will indicate if there are matches
if (result.type === "DECK_DRAW") {
  if (result.data.hasMatches) {
    // Must select matching cards from the field
    const fieldResult = game.selectCard(fieldCardIndex, "field")
    if (fieldResult.type === "SELECTION_UPDATED") {
      game.playCards()
    }
  } else {
    // Card was automatically placed on the field
    // Game moves to next player
  }
}

Scoring and Koi-Koi Decisions

// When a scoring combination is completed
if (result.type === "SCORE_UPDATE") {
  const { completedYaku } = result.data
  console.log("Completed yaku:", completedYaku)

  // Player decides whether to continue (koi-koi) or end the round
  const chooseKoiKoi = confirm("Would you like to declare Koi-Koi and continue?")
  const decision = game.makeKoiKoiDecision(chooseKoiKoi)

  if (decision.type === "ROUND_END") {
    console.log("Round ended! Winner:", decision.data.winner)
    console.log("Final yaku:", decision.data.yaku)
  }
}

State Management

// Get current game state
const state = game.getState()

// Save game state
const savedState = JSON.stringify(state)
localStorage.setItem("hanafudaGameState", savedState)

// Load game state
const loadedState = localStorage.getItem("hanafudaGameState")
if (loadedState) {
  const game = new KoiKoi()
  // TODO: Implement state loading
}

Example Application

An example web application demonstrating the library's features is available in the example/ directory. It provides a basic interface for playing Hanafuda Koi-Koi and showcases:

  • Game state management
  • Card selection and matching
  • Basic game flow implementation
  • State persistence using localStorage

See the example README for setup and running instructions.

Game Phases

The game follows these phases:

  • WAITING_FOR_HAND_CARD: Player needs to select a card from their hand
  • WAITING_FOR_FIELD_CARDS: Player needs to select matching cards from the field
  • NO_MATCHES_DISCARD: No matches available, card will be discarded
  • WAITING_FOR_DECK_MATCH: Player must capture matching cards for drawn card
  • WAITING_FOR_KOI_DECISION: Player must decide whether to continue (koi-koi)
  • ROUND_END: Round is complete

Development

# Clone the repository
git clone https://github.com/fudapop/hanafuda-js.git

# Navigate to the project directory
cd hanafuda-js

# Install dependencies (if any)
deno install

Project Structure

hanafuda-js/
├── src/
│   ├── core/           # Core game mechanics (cards, matching, collections)
│   ├── koikoi/           # Game state management and main game loop
│   └── scoring/        # Yaku scoring system and rules
├── tests/              # Test suites
└── README.md

License

MIT

Built and signed on
GitHub Actions
View transparency log

Add Package

deno add jsr:@fudapop/hanafuda-js

Import symbol

import * as hanafuda_js from "@fudapop/hanafuda-js";

---- OR ----

Import directly with a jsr specifier

import * as hanafuda_js from "jsr:@fudapop/hanafuda-js";

Add Package

npx jsr add @fudapop/hanafuda-js

Import symbol

import * as hanafuda_js from "@fudapop/hanafuda-js";

Add Package

yarn dlx jsr add @fudapop/hanafuda-js

Import symbol

import * as hanafuda_js from "@fudapop/hanafuda-js";

Add Package

pnpm dlx jsr add @fudapop/hanafuda-js

Import symbol

import * as hanafuda_js from "@fudapop/hanafuda-js";

Add Package

bunx jsr add @fudapop/hanafuda-js

Import symbol

import * as hanafuda_js from "@fudapop/hanafuda-js";