coding-horror/basic-computer-games · error · CardError

invalid suit

Error message

invalid suit

What it means

CardError is a custom Ruby exception (subclass of StandardError) defined inside War::Card. It is raised in Card#initialize when the suit argument is not one of :spades, :hearts, :clubs, or :diamonds (checked against the frozen SUITS constant). In the current codebase this guard is effectively dead code: the only call site is Card.shuffle (war.rb:40), which destructures pairs from CARDS = SUITS.product(PIPS), guaranteeing every suit is a valid SUITS member. CardError is never rescued anywhere, so if a bad suit were ever passed the program would crash with an unhandled exception traceback.

Source

Thrown at 94_War/ruby/war.rb:17

#!/usr/bin/env ruby
# reinterpreted from BASIC by stephan.com
class War
  class Card
    class CardError < StandardError; end

    SUITS = %i[spades hearts clubs diamonds].freeze
    PIPS = %i[ace deuce trey four five six seven eight nine ten jack king queen].freeze
    CARDS = SUITS.product(PIPS).freeze
    VALUES = PIPS.zip(1..13).to_h.freeze

    attr_reader :value

    def initialize(suit, pip)
      @suit = suit
      @pip = pip
      raise CardError, 'invalid suit' unless SUITS.include? @suit
      raise CardError, 'invalid pip' unless PIPS.include? @pip

      @value = VALUES[pip]
    end

    def <=>(other)
      @value <=> other.value
    end

    def >(other)
      @value > other.value
    end

    def <(other)
      @value < other.value
    end

    def to_s

View on GitHub (pinned to 5301155192)

Solutions

  1. Validate the suit against SUITS before calling Card.new: raise CardError early with a clear message at the data boundary.
  2. Convert string input to symbol with .to_sym and check membership: SUITS.include?(suit.to_sym) before construction.
  3. Rescue War::Card::CardError at the call site to degrade gracefully instead of crashing.
  4. Always use Card.shuffle for standard decks, which provably never triggers the guard.
  5. Add a factory method Card.from_h or Card.from_json that validates and converts input before calling new.

Example fix

# before
card = Card.new('spades', :ace)  # string, not symbol -> raises CardError

# after — validate and convert at the boundary
suit = suit.to_sym if suit.is_a?(String)
raise CardError, "invalid suit: #{suit}" unless SUITS.include?(suit)
card = Card.new(suit, pip)

# or rescue at call site
begin
  card = Card.new(suit_input, pip_input)
rescue War::Card::CardError => e
  warn "Skipping invalid card: #{e.message}"
  next
end
Defensive patterns

Strategy: validation

Validate before calling

# Validate suit symbol before constructing a Card
suit = suit.to_sym if suit.is_a?(String)
unless War::Card::SUITS.include?(suit)
  warn "Ignoring invalid suit: #{suit.inspect}"
  next  # or raise, or use a default
end
card = War::Card.new(suit, pip)

Type guard

# Type guard: narrow any input to a valid suit symbol or reject
module War
  class Card
    def self.valid_suit?(s)
      SUITS.include?(s.to_sym)
    end

    def self.from_input(suit_str, pip_str)
      suit = suit_str.to_sym
      pip = pip_str.to_sym
      return nil unless SUITS.include?(suit) && PIPS.include?(pip)
      new(suit, pip)
    end
  end
end

Try / catch

# Rescue CardError at the call site for graceful degradation
begin
  card = War::Card.new(parsed_suit, parsed_pip)
rescue War::Card::CardError => e
  warn "Invalid card data (#{e.message}), skipping."
  next
end

Prevention

When it happens

Trigger: A future refactor or external caller passing Card.new(:joker, :ace), Card.new('spades', :ace) (string instead of symbol), or any suit not in the SUITS array. Deserializing saved game state from JSON/YAML where suits are strings. Parsing card names from user input or the original BASIC data format without converting to valid symbols.

Common situations: Adding custom deck support (jokers, extra suits). Serializing/deserializing card state across a network boundary. Internationalizing suit names. Writing test fixtures with typos in suit symbols. Refactoring PIPS/SUITS constants without updating all references.

Related errors


AI-assisted analysis of coding-horror/basic-computer-games@5301155192 (2026-08-13). Data as JSON: /api/errors/9ce1feedf90ed60b. Report an issue: GitHub.