class Solitaire
Constants
- LEADERBOARD_TITLE
- STATS_TEMPLATE
- TITLE
- WIN_REWARD
Public Class Methods
Source
# File plugins/solitaire.rb, line 148 def self.active_session(user, games: default_games) games[normalize_user(user)] end
Source
# File plugins/solitaire.rb, line 51 def self.catalog_entries AisleriotCatalog.catalog(coded_ids: SolitairePlay::Registry.coded_ids) end
Source
# File plugins/solitaire.rb, line 29 def self.command_pattern /solitaire(?: (.+))?$/i end
Source
# File plugins/solitaire.rb, line 43 def self.default_games @games ||= {} end
Source
# File plugins/solitaire.rb, line 47 def self.default_stats @stats ||= PersistentStore.new(plugin: "solitaire", key: "stats", symbolize_loaded_hashes: true) end
Source
# File plugins/solitaire.rb, line 273 def self.dispatch_command(args_text, user:, games: default_games, store: default_store, stats: default_stats, deck: nil, rand: ->(max) { Random.rand(0...max) }) parsed = parse_command(args_text.to_s) return { error: parsed[:error] } if parsed[:error] return { message: usage_message } if parsed[:action] == :help case parsed[:action] when :list entries = catalog_entries message = AisleriotCatalog.format_list( entries, page: parsed[:page], filter: parsed[:filter] ) { message: message } when :info entry = find_catalog_entry(parsed[:game]) return { error: "Unknown game — try !solitaire list." } unless entry { message: AisleriotCatalog.format_info(entry) } when :balance { message: format_balance(user, balance_for(user, store: store), title: TITLE) } when :leaderboard { message: format_leaderboard(store, title: LEADERBOARD_TITLE, empty_message: "No balances yet.") } when :beg result = beg(user, store: store, title: TITLE, rand: rand) return { error: result[:error] } if result[:error] { message: result[:message] } when :new start_game(user, parsed[:game], games: games, deck: deck, rand: rand) when :board show_board(user, game_id: parsed[:game], games: games) when :move move_cards( user, game_id: parsed[:game], from: parsed[:from], to: parsed[:to], count: parsed[:count], games: games, store: store, stats: stats ) when :quit quit_game(user, game_id: parsed[:game], games: games, stats: stats) else { message: usage_message } end end
Source
# File plugins/solitaire.rb, line 55 def self.find_catalog_entry(id) AisleriotCatalog.find(id, coded_ids: SolitairePlay::Registry.coded_ids) end
Source
# File plugins/solitaire.rb, line 234 def self.move_cards(user, game_id:, from:, to:, count:, games: default_games, store: default_store, stats: default_stats) session = active_session(user, games: games) return { error: "#{user}, no active game — !solitaire <game> to start." } unless session if session_game_mismatch?(session, game_id) return { error: "#{user}, you're in #{session[:title]} — use !solitaire #{session[:game_id]} move ..." } end result = session[:engine].move(from, to, count: count) return { error: result[:error] } if result[:error] lines = [session[:engine].board_text(user: user)] if result[:won] credit_winnings(user, WIN_REWARD, store: store) user_stats = record_win(user, reward: WIN_REWARD, stats: stats) balance = balance_for(user, store: store) games.delete(normalize_user(user)) lines << format_win_footer(user, balance, stats: user_stats) lines << "#{STYLE_HINT}Play again: !solitaire #{session[:game_id]}#{IRC_RESET}" elsif result[:message] lines << "#{STYLE_WIN}#{result[:message]}#{IRC_RESET}" end { message: lines.join("\n") } end
Source
# File plugins/solitaire.rb, line 59 def self.normalize_user(user) Normalize.user_key(user) end
Source
# File plugins/solitaire.rb, line 70 def self.parse_command(text) parts = text.to_s.strip.split(/\s+/) return { action: :help } if parts.empty? meta = parse_meta_command(parts) return meta if meta parse_game_command(parts) end
Source
# File plugins/solitaire.rb, line 99 def self.parse_game_command(parts) game_token = parts[0] entry = find_catalog_entry(game_token) return { error: "Unknown game — try !solitaire list." } unless entry game_id = entry.id if parts.length == 1 return { action: :new, game: game_id, entry: entry } end case parts[1].downcase when "board", "show" { action: :board, game: game_id, entry: entry } when "move" return { error: "Usage: !solitaire #{game_id} move <from> <to> [count]" } if parts.length < 4 count = parts.length >= 5 ? parts[4].to_i : 1 return { error: "Move count must be at least 1." } if count < 1 { action: :move, game: game_id, entry: entry, from: parts[2], to: parts[3], count: count } when "quit", "stop" { action: :quit, game: game_id, entry: entry } when "info" { action: :info, game: game_id, entry: entry } else { error: usage_message } end end
Source
# File plugins/solitaire.rb, line 128 def self.parse_list_command(args) args ||= [] filter = :summary page = 1 args.each do |token| case token.downcase when "all" filter = :all when "coded" filter = :coded when /\A\d+\z/ page = token.to_i filter = :all if filter == :summary end end { action: :list, filter: filter, page: page } end
Source
# File plugins/solitaire.rb, line 80 def self.parse_meta_command(parts) case parts[0].downcase when "help" { action: :help } when "list" parse_list_command(parts[1..]) when "info" return { error: "Usage: !solitaire info <game>" } if parts.length < 2 { action: :info, game: parts[1..].join(" ") } when "balance", "score", "credits" { action: :balance } when "leaderboard", "top" { action: :leaderboard } when "beg" { action: :beg } end end
Source
# File plugins/solitaire.rb, line 167 def self.persist_store_entry!(store, key, value) store[key] = value end
Source
# File plugins/solitaire.rb, line 259 def self.quit_game(user, game_id:, games: default_games, stats: default_stats) key = normalize_user(user) session = games[key] return { error: "#{user}, no active game to quit." } unless session if session_game_mismatch?(session, game_id) return { error: "#{user}, you're in #{session[:title]} — use !solitaire #{session[:game_id]} quit." } end title = session[:title] games.delete(key) record_quit(user, stats: stats) { message: "#{TITLE} Abandoned #{title}. Use !solitaire #{session[:game_id]} to deal again." } end
Source
# File plugins/solitaire.rb, line 180 def self.record_quit(user, stats: default_stats) key = normalize_user(user) entry = store_entry(stats, key, STATS_TEMPLATE) entry[:quits] += 1 persist_store_entry!(stats, key, entry) entry end
Source
# File plugins/solitaire.rb, line 171 def self.record_win(user, reward:, stats: default_stats) key = normalize_user(user) entry = store_entry(stats, key, STATS_TEMPLATE) entry[:wins] += 1 entry[:total_won] += reward persist_store_entry!(stats, key, entry) entry end
Source
# File plugins/solitaire.rb, line 152 def self.session_game_mismatch?(session, game_id) return false unless session AisleriotCatalog.loose_id(session[:game_id]) != AisleriotCatalog.loose_id(game_id) end
Source
# File plugins/solitaire.rb, line 224 def self.show_board(user, game_id:, games: default_games) session = active_session(user, games: games) return { error: "#{user}, no active game — !solitaire <game> to start." } unless session if session_game_mismatch?(session, game_id) return { error: "#{user}, you're in #{session[:title]} — use !solitaire #{session[:game_id]} board." } end { message: session[:engine].board_text(user: user) } end
Source
# File plugins/solitaire.rb, line 199 def self.start_game(user, game_id, games: default_games, deck: nil, rand: ->(max) { Random.rand(0...max) }) key = normalize_user(user) entry = find_catalog_entry(game_id) return { error: "Unknown game — try !solitaire list." } unless entry if games[key] return { error: "#{user}, you already have an active game — !solitaire #{games[key][:game_id]} board or quit first." } end engine_class = SolitairePlay::Registry.fetch(entry.id) unless engine_class return { error: "#{entry.title} is catalogued but not playable in IRC yet — try !solitaire info #{entry.id}." } end engine = engine_class.new_game(deck: deck, rand: rand) games[key] = { game_id: entry.id, title: entry.title, engine: engine } { message: engine.board_text(user: user) } end
Source
# File plugins/solitaire.rb, line 158 def self.stats_for(user, stats: default_stats) store_entry(stats, normalize_user(user), STATS_TEMPLATE) end
Source
# File plugins/solitaire.rb, line 162 def self.store_entry(store, key, template) existing = store[key] existing.nil? ? template.dup : existing.dup end
Source
# File plugins/solitaire.rb, line 63 def self.usage_message "Usage: !solitaire list [all|coded] [page] — catalog | !solitaire info <game> — rules | " \ "!solitaire balance | !solitaire leaderboard | !solitaire beg | " \ "!solitaire <game> — start | !solitaire <game> board | " \ "!solitaire <game> move <from> <to> [count] | !solitaire <game> quit" end
Public Instance Methods
Source
# File plugins/solitaire.rb, line 324 def execute(m, *args) args_text = args.flatten.compact.map(&:to_s).reject(&:empty?).join(" ").strip result = self.class.dispatch_command( args_text, user: m.user.nick, games: self.class.default_games, store: self.class.default_store, stats: self.class.default_stats ) themed_flood_safe_reply(m, result[:error] || result[:message]) end