ink-playing-cards
is a React-based framework that enables developers to create engaging card games that run in the terminal.
By combining the power of React‘s component model with Ink‘s terminal rendering capabilities, this library provides a flexible and extensible foundation for building everything from simple card games to complex trading card game systems.
Creating card games for the terminal presents unique challenges. While graphical card games can rely on rich visual feedback and complex UI interactions, terminal-based games need to work within the constraints of character-based display and limited input methods. Additionally, card games often require complex state management, event handling, and rule systems that need to be both flexible and maintainable.
To address these challenges, I developed ink-playing-cards
with several key design principles in mind:
The library provides a set of carefully designed components optimized for terminal display:
import { Card, MiniCard, CardStack } from 'ink-playing-cards'
const GameBoard = () => (
<Box flexDirection="column">
<CardStack cards={deckCards} name="Deck" />
<Box>
{handCards.map((card) => (
<MiniCard
key={card.id}
{...card}
// variant="mini" // or "micro" for tighter spaces
faceUp
/>
))}
</Box>
</Box>
)
TypeScriptThe zone system provides a structured approach to managing game state:
const { deck, hand, discardPile, playArea } = useDeck()
// Move a card from hand to play area
const playCard = (card) => {
hand.removeCard(card)
playArea.addCard(card)
}
TypeScriptThe event system enables complex game interactions and rule implementation:
useEffect(() => {
eventManager.addEventListener('CARD_DRAWN', handleCardDrawn)
eventManager.addEventListener('CARD_PLAYED', handleCardPlayed)
return () => {
eventManager.removeEventListener('CARD_DRAWN', handleCardDrawn)
eventManager.removeEventListener('CARD_PLAYED', handleCardPlayed)
}
}, [])
TypeScriptThe effect system allows for the implementation of complex card abilities and game mechanics:
const healEffect = {
apply: (gameState, amount) => {
gameState.playerHealth += amount
},
}
// Apply effect when card is played
effectManager.applyCardEffects(card, gameState, { amount: card.amount })
TypeScriptThe library is built on several key technologies:
Terminal-First Design
Flexible State Management
Developer Experience
The library includes several example implementations showcasing its capabilities:
npx tblackjack
” 🚀 – https://www.npmjs.com/package/tblackjacknpm install ink-playing-cards
yarn add ink-playing-cards
pnpm install ink-playing-cards
import React from 'react'
import { Box, Text } from 'ink'
import { DeckProvider, useDeck, Card } from 'ink-playing-cards'
const Game = () => {
const { deck, hand, draw, shuffle } = useDeck()
React.useEffect(() => {
shuffle()
draw(5, 'player1')
}, [])
return (
<Box flexDirection="column">
<CardStack cards={deck.cards} name="Deck" />
<Text>Your hand:</Text>
<CardStack cards={hand.cards} name="Hand" faceUp maxDisplay={5} />
</Box>
)
}
const App = () => (
<DeckProvider>
<Game />
</DeckProvider>
)
TypeScriptThe limited space in terminal environments required careful consideration of component sizing and layout. The solution was to create multiple card components, each with a variant enum prop
simple
, ascii
, and minimal
variantsmini
and micro
variantsManaging complex game states across multiple zones required a robust yet flexible system. The zone-based approach provides:
Implementing card effects in a terminal environment required creative solutions for visual feedback and state management:
The library continues to evolve with planned features including:
Multiplayer Support
Enhanced Visual Effects
Advanced Game Features
ink-playing-cards
demonstrates how modern web technologies can be adapted to create engaging terminal-based applications. Hopefully, it enables developers to focus on game logic and creativity rather than low-level implementation details if/when they are curious about building games or card-based interfaces for Ink terminal applications.
The project showcases the power of component-based architecture and event-driven design in creating flexible, maintainable systems, even within the constraints of terminal-based interfaces.