stash for play-hand-validations_wip_stash

This commit is contained in:
Jesse C. Fisher 2015-08-11 07:04:52 -07:00
parent 23308abc97
commit 63ff0afb67
15 changed files with 420 additions and 18 deletions

View File

@ -29,19 +29,32 @@ class GamesController < ApplicationController
@play = @game.current_player.plays.new
@play.game_id = @game.id
@play.save
@play.player_cards << @cards_to_play
@cards_to_play.each do |card|
card.play_id = @play.id
card.save
end
#@cards_to_play.each do |card|
#@play.player_cards << card
#end
if @play.save
# TODO: Ensure active player only changes when valid hand is played
@game.set_active_player 'next'
@game.save
else
# If play is invalid, remove the cards from the play
@play.player_cards.map {|card| card.play_id = nil; card.save}
# Delete play
@play.destroy
end
@play.save
@play.errors.messages.each do |key, msg|
flash[key] = msg.join
end
redirect_to(@game)
# TODO: add current_hand logic
# @hand > @game.hand_to_beat
redirect_to @game
#redirect_to @game
end
def create

View File

@ -4,6 +4,7 @@ class Game < ActiveRecord::Base
has_many :users, through: :players
has_many :player_cards, through: :players
has_many :plays, through: :players
has_one :play_to_beat
accepts_nested_attributes_for :players
validates :title, presence: true

View File

@ -2,4 +2,131 @@ 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'
validates :game, presence: true
validates :player, presence: true
validate :hand_valid?
after_save :update_play_to_beat
def update_play_to_beat
if hand_valid?
@pb = PlayToBeat.new
@pb.game = self.game
@pb.play = self
@pb.save
end
end
# Validation methods
def hand_valid?
if has_player_cards
first_play?
# Open table / first play / no play to beat OR hand type matches OR play is back to the original active player
self.game.play_to_beat == nil || self.hand_type == self.game.play_to_beat.play.hand_type || self.player == self.game.play_to_beat.player
end
end
# TODO: Recognize all valid hand types
def hand_type
case self.player_cards.length
when 1
@hand_type = 'single'
when 2
@hand_type = 'pair'
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 = Array.new << self.player_cards.map {|pc| pc.rank}
@ranks.flatten!
@suits = Array.new << self.player_cards.map {|pc| pc.suit}
@suits.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[0...-1].each.with_index do |rank, i|
# each rank one away from each other?
if (rank.to_i + 1) == @ranks.uniq.sort[i+1].to_i
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[0...-1].each_with_index do |r, i|
# If cards are not a single run
if (r.to_i + 1) != @ranks[i+1].to_i
# And if cards are not a double 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.plays.empty? || ( self.game.plays.length == 1 && self.game.plays.first == self )
# Cards being played must include the lowest card
if (self.player_cards.include? self.game.lowest_card) == false
errors.add(:player_cards, "First hand must contain the lowest card: #{game.lowest_card.to_s}")
end
end
end
def has_player_cards
true unless self.player_cards.empty?
end
end

View File

@ -0,0 +1,4 @@
class PlayToBeat < ActiveRecord::Base
belongs_to :game
belongs_to :play
end

View File

@ -45,15 +45,21 @@
-# TODO better logic, not in view
- unless @game.status == nil
.table
- if @game.current_hand
%h1 Hand to beat
- if @game.play_to_beat
%h1
Hand to beat
- @game.play_to_beat.play.player_cards.each do |card|
= card.to_s
= @game.play_to_beat.play.player.user.email
- else
%h1 Open table
%h2
Played
Last Played
- if @game.plays.count > 0
- @game.plays.last.player_cards.each do |card|
= card.to_s
= @game.plays.last.player.user.email
.controls
- if @game.control_user == current_user

View File

@ -0,0 +1,5 @@
class AddPlayToBeatToGame < ActiveRecord::Migration
def change
add_reference :games, :play_to_beat, index: true
end
end

View File

@ -0,0 +1,10 @@
class PlayToBeat < ActiveRecord::Migration
def change
create_table :play_to_beats do |t|
t.integer :game_id
t.integer :play_id
t.timestamps null: false
end
end
end

View File

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150503133556) do
ActiveRecord::Schema.define(version: 20150810115450) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -22,6 +22,16 @@ ActiveRecord::Schema.define(version: 20150503133556) do
t.datetime "updated_at"
t.string "status"
t.integer "control_player_id"
t.integer "play_to_beat_id"
end
add_index "games", ["play_to_beat_id"], name: "index_games_on_play_to_beat_id", using: :btree
create_table "play_to_beats", force: true do |t|
t.integer "game_id"
t.integer "play_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "player_cards", force: true do |t|

View File

@ -0,0 +1,6 @@
FactoryGirl.define do
factory :play_to_beat do
end
end

View File

@ -37,8 +37,8 @@ feature 'Play a hand', type: :feature do
@card_to_play = game.current_player.player_cards.order(:value).last
check @card_to_play.to_s
click_button 'Play Hand'
expect(page).to have_content
"First hand must contain the lowest card: #{game.lowest_card.to_s}"
expect(page).to have_content "First hand must contain the lowest card: #{game.lowest_card.to_s}"
expect(game.play_to_beat).to eq(nil)
end
# Scenario: Played cards leave the players inventory
@ -64,6 +64,31 @@ feature 'Play a hand', type: :feature do
expect(@player.inventory.order(:value).join(' ')).to_not have_content(@card_to_play.to_s)
end
# Scenario: Invalid cards do not leave the players inventory
# Given I am the current player
# When I play an invalid hand
# Then the cards played should not leave my inventory
scenario 'invalid hand played cards are not removed from the players inventory' do
game = setup_game
# Store initial state of players inventory
@player = game.current_player
@player_inventory = @player.player_cards.order(:value).join(' ')
@player_inventory = @player.inventory.order(:value).join(' ')
login_as(game.current_player.user, scope: :user)
visit game_path game
# Play invalid hand
@card_to_play = game.current_player.player_cards.order(:value).last
check @card_to_play.to_s
click_button 'Play Hand'
# Played hand is rendered
expect(page).to have_content("First hand must contain the lowest card: #{game.lowest_card.to_s}")
# Player inventory is rendered with played cards
expect(page).to have_content(@player_inventory)
# Player inventory method does return played cards
expect(@player.inventory.order(:value).join(' ')).to have_content(@card_to_play.to_s)
end
# Scenario: Current player status updates after successful play
# Given I am the current player
# When I play a hand

View File

@ -10,6 +10,7 @@ RSpec.describe Game, type: :model do
subject { @game }
it { should respond_to(:title) }
it { should respond_to(:play_to_beat) }
it '#title returns a string' do
expect(@game.title).to match 'Test Game'

View File

@ -17,6 +17,8 @@ RSpec.describe Play, type: :model do
subject { @play }
it { should respond_to(:game_id) }
it { should respond_to(:player_cards) }
it { should respond_to(:hand_type) }
it '#game_id returns an integer' do
expect(@play.game_id).to match @game.id
@ -25,13 +27,168 @@ RSpec.describe Play, type: :model do
describe 'validations' do
describe 'first_play' do
context 'when there have been no plays yet' do
it 'is valid if it contains the lowest card' do
expect(@game.plays.count).to eq(1)
it 'valid if it contains the lowest card' do
#expect(@game.plays.count).to eq(1)
# Active player is the lowest card holder
expect(@game.current_player).to eq(@game.lowest_card.player)
@card_to_play = @game.current_player.player_cards.order(:value).first
@card_to_play.play_id = @play.id
@card_to_play.save
# Play the lowest card
@cards_to_play = @game.current_player.player_cards.order(:value).first
@play.player_cards << @cards_to_play
@play.save
expect(@play.player_cards.include? @game.lowest_card).to eq true
expect(@game.play_to_beat.play).to eq subject
end
end
end
describe 'hand_type' do
context 'when a single card is played' do
it 'single' do
@cards_to_play = @game.current_player.player_cards.order(:value).first
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('single')
end
end
context 'when two cards are played' do
it 'valid pair' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club3 = PlayerCard.new(rank: "3", suit: "Club")
@cards_to_play = [@spade3, @club3]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('pair')
expect(@play.errors[:player_cards]).to eq([])
end
it 'invalid pair' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Club")
@cards_to_play = [@spade3, @club4]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('pair')
expect(@play.errors[:player_cards]).to eq(['Invalid pair. Rank must match.'])
end
end
context 'when three cards are played' do
it 'valid triple' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club3 = PlayerCard.new(rank: "3", suit: "Club")
@diamond3 = PlayerCard.new(rank: "3", suit: "Diamond")
@cards_to_play = [@spade3, @club3, @diamond3]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('triple')
end
it 'invalid triple' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club3 = PlayerCard.new(rank: "3", suit: "Club")
@diamond4 = PlayerCard.new(rank: "4", suit: "Diamond")
@cards_to_play = [@spade3, @club3, @diamond4]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('triple')
expect(@play.errors[:player_cards]).to eq(['Invalid triple. Rank must match.'])
end
it 'valid run of 3' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Club")
@diamond5 = PlayerCard.new(rank: "5", suit: "Diamond")
@cards_to_play = [@spade3, @club4, @diamond5]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('triple run')
end
it 'valid run of 3 suited' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Spade")
@diamond5 = PlayerCard.new(rank: "5", suit: "Spade")
@cards_to_play = [@spade3, @club4, @diamond5]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('triple run suited')
end
end
context 'when four cards are played' do
it 'valid bomb' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club3 = PlayerCard.new(rank: "3", suit: "Club")
@diamond3 = PlayerCard.new(rank: "3", suit: "Diamond")
@heart3 = PlayerCard.new(rank: "3", suit: "Heart")
@cards_to_play = [@spade3, @club3, @diamond3, @heart3]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('bomb')
end
it 'valid run of 4' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Club")
@diamond5 = PlayerCard.new(rank: "5", suit: "Diamond")
@diamond6 = PlayerCard.new(rank: "6", suit: "Diamond")
@cards_to_play = [@spade3, @club4, @diamond5, @diamond6]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('run of 4')
end
it 'valid run of 4 suited' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Spade")
@diamond5 = PlayerCard.new(rank: "5", suit: "Spade")
@diamond6 = PlayerCard.new(rank: "6", suit: "Spade")
@cards_to_play = [@spade3, @club4, @diamond5, @diamond6]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('run of 4 suited')
end
end
context 'when five cards are played' do
it 'valid run of 5' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Club")
@diamond5 = PlayerCard.new(rank: "5", suit: "Diamond")
@diamond6 = PlayerCard.new(rank: "6", suit: "Diamond")
@diamond7 = PlayerCard.new(rank: "7", suit: "Diamond")
@cards_to_play = [@spade3, @club4, @diamond5, @diamond6, @diamond7]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('run of 5')
end
it 'invalid run of 5' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Club")
@diamond5 = PlayerCard.new(rank: "5", suit: "Diamond")
@diamond6 = PlayerCard.new(rank: "6", suit: "Diamond")
@diamond8 = PlayerCard.new(rank: "8", suit: "Diamond")
@cards_to_play = [@spade3, @club4, @diamond5, @diamond6, @diamond8]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('run of 5')
expect(@play.errors[:player_cards]).to eq(['Invalid run of 5'])
end
it 'valid run of 5 suited' do
@spade3 = PlayerCard.new(rank: "3", suit: "Spade")
@club4 = PlayerCard.new(rank: "4", suit: "Spade")
@diamond5 = PlayerCard.new(rank: "5", suit: "Spade")
@diamond6 = PlayerCard.new(rank: "6", suit: "Spade")
@diamond7 = PlayerCard.new(rank: "7", suit: "Spade")
@cards_to_play = [@spade3, @club4, @diamond5, @diamond6, @diamond7]
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq('run of 5 suited')
end
end
(5..13).each do |i|
context "when #{i} cards are played" do
it "run of #{i}" do
@cards_to_play = create_hand(i, 'run')
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq("run of #{i}")
end
it "run of #{i} suited" do
@cards_to_play = create_hand(i, 'run suited')
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq("run of #{i} suited")
end
if [6, 8, 10].include? i
it "double run of #{i}" do
@cards_to_play = create_hand(i, 'double run')
@play.player_cards << @cards_to_play
expect(@play.hand_type).to eq("double run of #{i}")
expect(@play.errors[:player_cards]).to eq([])
end
end
end
end
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe PlayToBeat, :type => :model do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -2,4 +2,5 @@ require 'support/helpers/session_helpers'
RSpec.configure do |config|
config.include Features::SessionHelpers, type: :feature
config.include Features::GameHelpers, type: :feature
config.include Models::PlayHelpers, type: :model
end

View File

@ -0,0 +1,31 @@
module Models
module PlayHelpers
def create_hand(count, type)
cards = []
count.times do
cards << PlayerCard.new
end
case type
when /run/
cards.each.with_index(3) do |c, i|
# double
if type.include? 'double'
c.rank = Card::RANKS[i % (cards.count/2)]
else
c.rank = Card::RANKS[i % 13]
end
# suited
if type.include? 'suited'
c.suit = Card::SUITS[0]
else
c.suit = Card::SUITS[i % 4]
end
end
end
end
end
end