Skip to content

everscending/hadoken-high-roller

Repository files navigation

Hadoken High Roller

A Street Fighter themed slot machine game built as an Electron desktop application using React and TypeScript.

Features

Game Mechanics

  • 4-Reel Slot Machine: Spin 4 reels featuring 16 iconic Street Fighter characters (Ryu, Ken, Chun-Li, Blanka, Guile, Zangief, Dhalsim, E. Honda, Balrog, Vega, Sagat, M. Bison, Cammy, Fei Long, T. Hawk, and Dee Jay)
  • Coin System: Start with 100 coins, bet 10 coins per spin
  • Reward System:
    • Pair (2 matching symbols): 20 coins
    • Three-of-a-Kind (3 matching symbols): 50 coins
    • Perfect Match (all 4 symbols match): 100 coins
    • Rewards accumulate (e.g., 2 pairs = 40 coins, pair + three-of-a-kind = 70 coins)
  • Auto-Spin: Enable continuous automatic spinning until you run out of coins or manually stop
  • Game Over: When coins drop below the bet amount, the game ends with a special game over screen

Player Management

  • Add Players: Create new players with custom names
  • Leaderboard: View all players with their highest balance and total spins
  • Player Persistence: All player data is stored locally in a SQLite database

Game Features

  • Sound Effects: Authentic Street Fighter sound effects including:
    • Game start music and round 1 theme
    • Symbol selection sounds
    • Win/loss sound effects
    • Perfect win celebration
    • Game over sequence
  • Visual Feedback:
    • Animated slot reels with sequential stopping
    • Highlighted winning symbols
    • Reward modal showing winnings
    • Retro-styled UI with Street Fighter aesthetic
  • Game Session Tracking: Each game session is tracked with:
    • Start and end times
    • Starting and ending coin balances
    • Individual spin records with symbols, bet amounts, and winnings

Technical Features

  • Local Database: SQLite database stores all game data locally
  • Cross-Platform: Build for Windows, macOS, and Linux
  • Modern Stack: Built with Electron, React, TypeScript, and Vite

Prerequisites

  • Node.js: Version 18 or higher
  • Yarn: Package manager (install via npm install -g yarn)

Running the Game Locally

1. Install Dependencies

yarn

2. Start Development Server

yarn run dev

This will:

  • Start the Electron application in development mode
  • Enable hot-reload for React components
  • Open the game window automatically

3. Play the Game

  1. Add a Player: Enter your name in the input field and click "Add Player"
  2. Start Playing: Click "Play" next to your name in the players list
  3. Spin: Click the "Spin" button to play (costs 10 coins)
  4. Auto-Spin: Enable "Auto-Spin" for continuous gameplay
  5. Restart: Click "Restart" to start a new game with 100 coins
  6. Quit: Click "Quit" to return to the home screen

Building for Production

Build for Windows

yarn build:win

Build for macOS

yarn build:mac

Build for Linux

yarn build:linux

Built applications will be available in the dist/ directory.

Development Scripts

  • yarn dev - Start development server
  • yarn build - Build the application (runs type checking)
  • yarn start - Preview the built application
  • yarn lint - Run ESLint
  • yarn format - Format code with Prettier
  • yarn typecheck - Run TypeScript type checking

Recommended IDE Setup

Project Structure

src/
├── main/          # Electron main process (database, IPC handlers)
├── preload/       # Preload scripts for secure IPC communication
└── renderer/      # React application (UI components)
    └── src/
        ├── components/  # React components
        ├── assets/      # Images, sounds, styles
        └── lib/         # Utility functions

Database

The game uses SQLite to store:

  • Players: Name, creation date, highest balance, total spins
  • Games: Game sessions with start/end times and balances
  • Spins: Individual spin records with symbols, bets, and winnings

The database file is stored in the Electron app's user data directory.

Design Decisions & Trade-offs

This section explains key architectural and implementation decisions made during development, along with their trade-offs.

Architecture

Electron Multi-Process Architecture

Decision: Separated main process (Node.js) and renderer process (React) with IPC communication via preload scripts.

Rationale:

  • Security: Context isolation prevents renderer from accessing Node.js APIs directly
  • Performance: Main process handles database operations without blocking UI
  • Separation of concerns: Business logic (database) separate from presentation (React)

Trade-offs:

  • ✅ Better security and performance isolation
  • ✅ Follows Electron security best practices
  • ❌ More complex than a single-process app
  • ❌ Requires IPC boilerplate for all main-renderer communication

Context Isolation Enabled

Decision: Enabled contextIsolation: true and used contextBridge for secure IPC.

Rationale:

  • Prevents renderer from accessing Node.js globals directly
  • Required for modern Electron security best practices
  • Allows controlled API exposure

Trade-offs:

  • ✅ Enhanced security against XSS attacks
  • ✅ Future-proof against Electron security changes
  • ❌ Requires explicit API definition in preload scripts
  • ❌ Slightly more setup than disabling context isolation

State Management

React Hooks Only (No State Management Library)

Decision: Used React's built-in useState and useEffect hooks instead of Redux, Zustand, or other state management libraries.

Rationale:

  • Simplicity: No additional dependencies or boilerplate
  • Sufficient for current app complexity
  • Easier for developers unfamiliar with state management libraries

Trade-offs:

  • ✅ Simpler codebase, easier to understand
  • ✅ No external dependencies
  • ✅ Faster initial development
  • ❌ Could become unwieldy with more complex state interactions
  • ❌ No built-in devtools for state debugging
  • ❌ Prop drilling in deeply nested components

Future Consideration: If the app grows significantly, consider Zustand or Jotai for lightweight state management.

Database

SQLite with better-sqlite3 (Synchronous)

Decision: Used better-sqlite3 (synchronous SQLite) instead of async alternatives like sql.js or node-sqlite3.

Rationale:

  • Performance: Synchronous operations are faster for local desktop apps
  • Simplicity: No need for async/await in database code
  • Reliability: better-sqlite3 is well-maintained and performant
  • Native bindings: Better performance than JavaScript implementations

Trade-offs:

  • ✅ Excellent performance for local desktop app
  • ✅ Simpler code (no async/await needed)
  • ✅ Native C++ bindings for speed
  • ❌ Blocks main thread (acceptable for desktop app with IPC)
  • ❌ Requires native compilation (handled by electron-builder)

WAL Mode Enabled

Decision: Enabled Write-Ahead Logging (WAL) mode for SQLite.

Rationale:

  • Better concurrent read performance
  • Faster writes (sequential log writes)
  • Allows readers to not block writers

Trade-offs:

  • ✅ Better performance for concurrent operations
  • ✅ Faster writes
  • ❌ Slightly more complex database file structure (WAL file)
  • ❌ Requires proper cleanup (handled by SQLite)

Normalized Database Schema

Decision: Used normalized schema with separate tables for players, games, and spins with foreign key constraints.

Rationale:

  • Data integrity: Foreign keys prevent orphaned records
  • Query flexibility: Easy to aggregate statistics
  • Scalability: Can handle complex queries efficiently

Trade-offs:

  • ✅ Data integrity and consistency
  • ✅ Flexible querying and reporting
  • ✅ Easy to extend with new features
  • ❌ More complex than a single-table approach
  • ❌ Requires JOINs for some queries (acceptable trade-off)

Database Location: User Data Directory

Decision: Store database in Electron's user data directory instead of app directory.

Rationale:

  • Persistence: Survives app updates
  • User-specific: Each user has their own data
  • OS-appropriate: Follows platform conventions

Trade-offs:

  • ✅ Data persists across app updates
  • ✅ Follows OS conventions
  • ✅ User-specific data isolation
  • ❌ Requires app permissions (handled automatically)

Game Logic

Client-Side Reward Calculation

Decision: Calculate rewards in the renderer process (Play.tsx) instead of main process.

Rationale:

  • Immediate feedback: No IPC delay
  • Simpler code: All game logic in one place
  • Acceptable for single-player game (no cheating concerns)

Trade-offs:

  • ✅ Instant UI updates
  • ✅ Simpler code organization
  • ✅ Better user experience
  • ❌ Not suitable for multiplayer (could be manipulated)
  • ❌ Logic duplication if server-side validation needed later

Note: For a multiplayer or online game, reward calculation should be server-side.

Sequential Reel Stopping

Decision: Reels stop sequentially (500ms apart) instead of simultaneously.

Rationale:

  • Better UX: Creates anticipation and excitement
  • More realistic: Mimics physical slot machines
  • Visual feedback: Users can see each symbol revealed

Trade-offs:

  • ✅ Enhanced user experience
  • ✅ More engaging gameplay
  • ✅ Better visual feedback
  • ❌ Longer spin duration (2 seconds total)
  • ❌ More complex state management

Build & Development

electron-vite Instead of Webpack

Decision: Used electron-vite instead of webpack or other bundlers.

Rationale:

  • Faster builds: Vite's esbuild-based bundling
  • Better DX: Faster HMR and dev server
  • Simpler config: Less boilerplate than webpack
  • Modern: Built for modern tooling

Trade-offs:

  • ✅ Much faster development builds
  • ✅ Excellent HMR experience
  • ✅ Simpler configuration
  • ❌ Less mature ecosystem than webpack
  • ❌ Fewer plugins available

Fixed Window Size

Decision: Fixed window size (900x720) instead of resizable.

Rationale:

  • Consistent UI: Prevents layout issues
  • Game design: Slot machine UI works best at fixed size
  • Simpler: No responsive design needed

Trade-offs:

  • ✅ Consistent user experience
  • ✅ Simpler CSS and layout
  • ✅ No responsive design complexity
  • ❌ Less flexible for users
  • ❌ Doesn't adapt to different screen sizes

Audio

Direct Audio API (No Sound Manager)

Decision: Used browser Audio API directly instead of a sound management library.

Rationale:

  • Simplicity: No additional dependencies
  • Sufficient for current needs
  • Lightweight: No library overhead

Trade-offs:

  • ✅ No external dependencies
  • ✅ Simple implementation
  • ✅ Full control over audio playback
  • ❌ No built-in volume management
  • ❌ Manual error handling required
  • ❌ Could benefit from audio pooling for performance

Future Consideration: If sound management becomes complex, consider howler.js or similar library.

Routing

MemoryRouter Instead of BrowserRouter

Decision: Used React Router's MemoryRouter instead of BrowserRouter.

Rationale:

  • Desktop app: No URL bar, no need for browser history
  • Simpler: No hash or path routing complexity
  • Appropriate: Electron apps typically use memory routing

Trade-offs:

  • ✅ Appropriate for desktop app
  • ✅ Simpler routing logic
  • ✅ No URL manipulation concerns
  • ❌ No deep linking support (not needed for this app)
  • ❌ No browser back/forward (not applicable)

Type Safety

TypeScript Throughout

Decision: Used TypeScript for all code (main, preload, renderer).

Rationale:

  • Type safety: Catch errors at compile time
  • Better DX: IDE autocomplete and refactoring
  • Maintainability: Self-documenting code

Trade-offs:

  • ✅ Compile-time error detection
  • ✅ Better IDE support
  • ✅ Easier refactoring
  • ❌ Slightly slower development (type annotations)
  • ❌ Build step required (handled by tooling)

Component Structure

Functional Components with Hooks

Decision: Used functional components exclusively with React hooks.

Rationale:

  • Modern React: Recommended approach
  • Simpler: Less boilerplate than class components
  • Hooks: Better code reuse and organization

Trade-offs:

  • ✅ Modern React best practices
  • ✅ Less boilerplate
  • ✅ Better performance (with React optimizations)
  • ❌ Learning curve for developers unfamiliar with hooks
  • ❌ Requires careful dependency arrays in useEffect

Performance Optimizations

Prepared Statements in Database

Decision: Used SQLite prepared statements for all queries.

Rationale:

  • Performance: Compiled queries are faster
  • Security: Prevents SQL injection
  • Best practice: Standard database practice

Trade-offs:

  • ✅ Better performance
  • ✅ SQL injection protection
  • ✅ Industry best practice
  • ❌ Slightly more verbose code
  • ❌ Requires statement management

Symbol Images as URLs

Decision: Imported symbol images as URLs using Vite's asset handling.

Rationale:

  • Build optimization: Vite handles asset optimization
  • Caching: Browser caching benefits
  • Simplicity: Standard web asset handling

Trade-offs:

  • ✅ Build-time optimization
  • ✅ Browser caching
  • ✅ Standard approach
  • ❌ Multiple HTTP requests (mitigated by bundling)
  • ❌ Could use sprite sheets for fewer requests (future optimization)

About

A Street Fighter themed slot machine game built as an Electron desktop application using React and TypeScript.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors