227 lines
6.2 KiB
Ruby

class Play < ActiveRecord::Base
belongs_to :game, foreign_key: 'game_id'
belongs_to :player, foreign_key: 'player_id'
has_many :player_cards, foreign_key: 'play_id'
alias_attribute :hand, :player_cards
validates :game, presence: true
validates :player, presence: true
validate :hand_valid?
validate :player_has_control?
after_save :check_for_winner
after_save :update_play_to_beat
after_save :set_controlling_player
def check_for_winner
if self.game.over?
self.game.winner_player_id = self.player.id
self.game.status = "Player #{self.player.display_name} wins!"
self.game.save
end
end
def update_play_to_beat
if hand_valid?
@pb = PlayToBeat.new
@pb.game = self.game
@pb.play = self
@pb.save
self.game.play_to_beat = @pb
self.game.save!
end
end
def set_controlling_player
self.game.set_controlling_player 'next'
self.game.save
end
# Validation methods
def player_has_control?
if self.player == self.game.controlling_player
return true
else
errors.add(:player, "it's not your turn")
return false
end
end
def hand_valid?
return false if self.hand_type == nil
# Gather the truths about the hand
@truths = []
# Validations
# Is there a hand_to_beat ?
if !game.try(:hand_to_beat).blank?
# current player is not the player to beat
if (game.player_to_beat != player)
# valid bomb?
# if (self.hand_type =~ /bomb|double run/ && game.play_to_beat.try("play").try("hand_type") == 'single' && game.play_to_beat.try("play").try("player_cards")[0].try('rank') == '2')
if (self.hand_type =~ /bomb|double run/)
@truths << true
return @truths
else
# hand_type match?
@truths << (self.hand_type == game.play_to_beat.try("play").try("hand_type")) if game.play_to_beat.try("play").try(:hand_type)
# beats?
@truths << (self.beats? self.game.play_to_beat.try(:play))
end
end
end
if has_player_cards
@truths << !hand_type.nil?
if first_play?
@truths << contains_lowest_card?
end
end
if @truths.include? false
errors.add(:player_cards, "invalid hand")
end
# Did any validations return false?
return (!@truths.include? false)
end
# Check if the value of this hand is higher than the hand_to_beat
def beats?(play_to_beat)
if play_to_beat
self.high_card_value > play_to_beat.high_card_value
else
false
end
end
def high_card
self.hand.last unless self.hand.empty?
end
def high_card_value
return self.hand.last.value unless self.hand.empty?
0
end
# TODO: Recognize face cards
def hand_type
case self.player_cards.length
when 0
@hand_type = 'empty'
when 1
@hand_type = 'single'
when 2
@hand_type = 'double'
if (self.player_cards[0].rank != self.player_cards[1].rank)
errors.add(:player_cards, "Invalid pair. Rank must match.")
end
when 3
@hand_type = 'triple'
@ranks = Array.new << self.player_cards.map {|pc| pc.rank}
@ranks.flatten!
@suits = Array.new << self.player_cards.map {|pc| pc.suit}
@suits.flatten!
# TODO: Does not work for face cards :(
if (@ranks[0].to_i + 1 == @ranks[1].to_i) && (@ranks[1].to_i + 1 == @ranks[2].to_i)
@hand_type += ' run'
# Check if suited
if (@suits.uniq.length == 1)
@hand_type += ' suited'
end
elsif @ranks.uniq.length != 1
errors.add(:player_cards, "Invalid triple. Rank must match.")
end
when 4
@ranks = Array.new << self.player_cards.map {|pc| pc.rank}
@ranks.flatten!
@suits = Array.new << self.player_cards.map {|pc| pc.suit}
@suits.flatten!
if (@ranks.uniq.length == 1)
@hand_type = 'bomb'
end
# TODO: Does not work for face cards :(
if (@ranks[0].to_i + 1 == @ranks[1].to_i) && (@ranks[1].to_i + 1 == @ranks[2].to_i) & (@ranks[2].to_i + 1 == @ranks[3].to_i)
@hand_type = 'run of 4'
end
if (@suits.uniq.length == 1)
@hand_type += ' suited'
end
when 5..13
@hand_type = "run of #{self.player_cards.length}"
@ranks = [self.player_cards.map {|pc| pc.rank}].flatten!.sort_by(&:to_i)
@suits = [self.player_cards.map {|pc| pc.suit}].flatten!
# double? && length?
if (@ranks.uniq.length == (@ranks.length/2)) && ([6,8,10].include? @ranks.length)
@hand_type = "double " + @hand_type
# run?
# iterate over each unique rank; except for the last rank
@ranks.uniq.sort_by(&:to_i)[0...-1].each.with_index do |rank, i|
# each rank one away from each other?
if (rank.to_i + 1) == @ranks.uniq.sort_by(&:to_i)[i+1].to_i
# valid do nothing
else
@hand_type = "Invalid " + @hand_type
errors.add(:player_cards, "Invalid double run of #{@ranks.length}")
end
end
else
# normal run?
# For each card except for the last card
@ranks.sort_by(&:to_i)[0...-1].each_with_index do |rank, i|
# If cards are not a single run
if (rank.to_i + 1) != @ranks[i+1].to_i
# And if cards are not a run
errors.add(:player_cards, "Invalid run of #{@ranks.length}")
end
end
end
# Check if suited
if (@suits.uniq.length == 1)
@hand_type += ' suited'
end
else
errors.add(:player_cards, "Unrecognized hand type.")
end
return @hand_type
end
def first_play?
# First play of the game
if self.game.try(:plays).try(:empty?) || ( self.game.try(:plays).try(:length) == 1 && self.game.try(:plays).try(:first) == self )
true
else
false
end
end
def contains_lowest_card?
# Cards being played must include the lowest card
if (self.player_cards.first.value != self.game.lowest_card.value)
errors.add(:player_cards, "First hand must contain the lowest card: #{game.lowest_card.to_s}")
return false
else
return true
end
end
def has_player_cards
true unless self.player_cards.empty?
end
def to_s
player_cards.join ' '
end
end