# The game class class Game < ActiveRecord::Base belongs_to :table, optional: true has_many :seats has_many :players, dependent: :destroy has_many :users, through: :players has_many :player_cards, through: :players has_many :plays, through: :players belongs_to :play_to_beat, optional: true belongs_to :controlling_player, class_name: "Player", foreign_key: "controlling_player_id", optional: true accepts_nested_attributes_for :players #validates :title, presence: true after_create :add_table # Returns the newest joinable game or creates a new game if none are found scope :play_now, lambda { |user| where(status:nil).joins("LEFT OUTER JOIN players ON players.game_id = games.id").group("games.id").having("count(players) < 4").last || Game.create(title: "Game " + user.soft_token[0..4]) } def start validate_startable return false unless startable? # Start the game self.status = 'Game Started' # Create a deck d = Deck.new # Shuffle the deck d.cards.shuffle! # Deal the cards players.each do |player| 13.times do card = d.cards.shift player.player_cards.new(card.instance_values) end player.save end # Set status to first turn # Player with the lowest card self.status = 'First Play at: ' + self.lowest_card.player.display_name self.controlling_player_id = self.lowest_card.player.id save end def winner self.players.find_by_id(self.winner_player_id) end def validate_startable @player_count = players.count @user_count = players.where.not(soft_token: nil).count errors.add(:players, 'Must be at least 2 players.') if @player_count < 2 errors.add(:players, 'Must be less than 5 players.') if @player_count > 4 errors.add(:status, 'Game already started...') if self.status.nil? == false errors.add(:player_cards, 'Cards already dealt...') unless player_cards.empty? fail StandardError, errors if errors.count > 0 return true end def current_hand return nil end def startable? begin self.validate_startable rescue return false end end def lowest_card self.player_cards.order(:value).first end def add_player_from_user(user) return false unless self.joinable?(user) new_player = self.players.create(user: user, soft_token: user.soft_token) end def add_player_bot # todo return if game is full # return false unless self.joinable?(user) new_player = self.players.create(is_bot: true) end def remove_player_from_user(user) return false unless self.has_user? user if self.players.exists?(user_id: user.id) @player = self.players.find_by(user_id: user.id) elsif self.players.exists?(soft_token: user.soft_token) @player = self.players.find_by(soft_token: user.soft_token) end if @player @player.user_id = nil @player.soft_token = nil end end def can_accomodate(user) !(already_has?(user) || full?) end #TODO: Which has_user? method is better? Probably has_user?. def already_has?(user) self.players.pluck(:user_id, :soft_token).flatten.compact.to_set.intersect? ([user.id, user.soft_token].to_set) end def has_user? user if user.id return true if self.players.exists?(user_id: user.id) end if user.soft_token return true if self.players.exists?(soft_token: user.soft_token) end return false end def full? # players.count >= 4 end def set_controlling_player(arg) case arg when 'next' self.controlling_player_id = self.next_player_id end # Clear play to beat if play returns to owner # if self.play_to_beat.try(:player).try(:id) == self.controlling_player_id # self.play_to_beat_id = nil # end end def joinable? user # !self.has_user?(user) && !self.full? !self.full? end def player_ids_by_seat_order player_ids = self.seats.order(:position).select {|s| ( s.player.present? && s.player.inventory.count > 0) ? s.player_id : nil}.compact.map {|s| s.player_id} end def next_player_id @controlling_player_index = self.player_ids_by_seat_order.index(controlling_player_id) # TODO Fix bug @controlling_player_index ||= 0 player_ids_by_seat_order[(@controlling_player_index + 1) % self.player_ids_by_seat_order.length] end # The user whose turn it is def controlling_user self.players.find(self.controlling_player_id).user end def hand_to_beat self.play_to_beat.try(:play).try(:hand) end def player_to_beat self.play_to_beat.try(:play).try(:player) end def started? !self.status.nil? end def over? return false unless self.started? @inventory_counts = self.players.map {|p| p.player_cards.count > 0 ? p.inventory.count : nil} @inventory_counts.include? 0 end # private def add_table self.table ||= Table.create current_game_id: self.id self.seats = self.table.seats self.seats.each do |seat| # if (seat.user_id || seat.user_soft_token) if (seat.occupied?) new_player = self.players.create(user_id: seat.user_id, soft_token: seat.user_soft_token, is_bot: seat.player.is_bot) seat.player = new_player seat.save end end self.save end end