
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.
The Challenge
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.
The Solution
To address these challenges, I developed ink-playing-cards with several key design principles in mind:
- Component-Based Architecture: Leveraging React’s component model for reusable and composable game elements
- Flexible State Management: Using a zone-based system for tracking card locations and game state
- Event-Driven Design: Implementing a robust event system for handling game actions and effects
- Terminal-Optimized UI: Provide comprehensive out-of-box kit for Ink terminal apps
Core Features
Component Library
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>
)TypeScriptZone System
The 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)
}TypeScriptEvent System
The 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)
}
}, [])TypeScriptEffect System
The 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 })TypeScriptTechnical Implementation
Architecture Overview
The library is built on several key technologies:
- React for component management and state handling
- Ink for terminal rendering
- TypeScript for type safety and developer experience
- Event Emitter for the event system
Key Design Decisions
Terminal-First Design
- Optimized component rendering for character-based display
- Multiple card display options (simple, ASCII, minimal, mini, micro) for space efficiency
- Careful consideration of color and Unicode character support
Flexible State Management
- Zone-based system for intuitive card management
- Event-driven architecture for decoupled components
- Effect system for extensible game mechanics
Developer Experience
- Comprehensive TypeScript definitions
- Intuitive APIs for common card game operations
- Extensive documentation and examples
Example Games
The library includes several example implementations showcasing its capabilities:
- War: A simple two-player card game demonstrating basic mechanics
- Blackjack: Classic casino game showing betting and scoring systems
- Check out a fully functional game here! – https://github.com/gfargo/tBlackjack
- or play now via “
npx tblackjack” ๐ – https://www.npmjs.com/package/tblackjack
- Solitaire: Complex card movement and zone management
- Magic Lite: Advanced effect and event system usage
Getting Started
npm install ink-playing-cardsyarn add ink-playing-cardspnpm install ink-playing-cardsBasic usage example:
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>
)TypeScriptTechnical Challenges and Solutions
Challenge 1: Terminal Space Constraints
The 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
- Card Component: Full-size card display with
simple,ascii, andminimalvariants - MiniCard Component: Supporting both
miniandmicrovariants
Challenge 2: State Management
Managing complex game states across multiple zones required a robust yet flexible system. The zone-based approach provides:
- Clear ownership of cards
- Intuitive movement operations
- Event-driven updates
- Type-safe interactions
Challenge 3: Effect Implementation
Implementing card effects in a terminal environment required creative solutions for visual feedback and state management:
- Event-based effect triggering
- Async effect resolution
- Visual state indicators
- Effect queueing system
Future Development
The library continues to evolve with planned features including:
Multiplayer Support
- Network play capabilities
- Synchronized game state
- Player interaction systems
Enhanced Visual Effects
- More animation options
- Additional card styles
- Custom effect visualizations
Advanced Game Features
- AI player support
- Save/load game state
- Replay system
Conclusion
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.
Resources and Documentation
Like what you saw?
There's more where that came from.
