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.current_player.plays.new
@play.game_id = @game.id @play.game_id = @game.id
@play.save @play.save
@play.player_cards << @cards_to_play
@cards_to_play.each do |card| #@cards_to_play.each do |card|
card.play_id = @play.id #@play.player_cards << card
card.save #end
end
if @play.save
# TODO: Ensure active player only changes when valid hand is played # TODO: Ensure active player only changes when valid hand is played
@game.set_active_player 'next' @game.set_active_player 'next'
@game.save @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 # TODO: add current_hand logic
# @hand > @game.hand_to_beat # @hand > @game.hand_to_beat
redirect_to @game #redirect_to @game
end end
def create def create

View File

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

View File

@ -2,4 +2,131 @@ class Play < ActiveRecord::Base
belongs_to :game, foreign_key: 'game_id' belongs_to :game, foreign_key: 'game_id'
belongs_to :player, foreign_key: 'player_id' belongs_to :player, foreign_key: 'player_id'
has_many :player_cards, foreign_key: 'play_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 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 -# TODO better logic, not in view
- unless @game.status == nil - unless @game.status == nil
.table .table
- if @game.current_hand - if @game.play_to_beat
%h1 Hand 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 - else
%h1 Open table %h1 Open table
%h2 %h2
Played Last Played
- if @game.plays.count > 0 - if @game.plays.count > 0
- @game.plays.last.player_cards.each do |card| - @game.plays.last.player_cards.each do |card|
= card.to_s = card.to_s
= @game.plays.last.player.user.email
.controls .controls
- if @game.control_user == current_user - 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. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -22,6 +22,16 @@ ActiveRecord::Schema.define(version: 20150503133556) do
t.datetime "updated_at" t.datetime "updated_at"
t.string "status" t.string "status"
t.integer "control_player_id" 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 end
create_table "player_cards", force: true do |t| 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 @card_to_play = game.current_player.player_cards.order(:value).last
check @card_to_play.to_s check @card_to_play.to_s
click_button 'Play Hand' click_button 'Play Hand'
expect(page).to have_content expect(page).to have_content "First hand must contain the lowest card: #{game.lowest_card.to_s}"
"First hand must contain the lowest card: #{game.lowest_card.to_s}" expect(game.play_to_beat).to eq(nil)
end end
# Scenario: Played cards leave the players inventory # 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) expect(@player.inventory.order(:value).join(' ')).to_not have_content(@card_to_play.to_s)
end 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 # Scenario: Current player status updates after successful play
# Given I am the current player # Given I am the current player
# When I play a hand # When I play a hand

View File

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

View File

@ -17,6 +17,8 @@ RSpec.describe Play, type: :model do
subject { @play } subject { @play }
it { should respond_to(:game_id) } 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 it '#game_id returns an integer' do
expect(@play.game_id).to match @game.id expect(@play.game_id).to match @game.id
@ -25,13 +27,168 @@ RSpec.describe Play, type: :model do
describe 'validations' do describe 'validations' do
describe 'first_play' do describe 'first_play' do
context 'when there have been no plays yet' do context 'when there have been no plays yet' do
it 'is valid if it contains the lowest card' do it 'valid if it contains the lowest card' do
expect(@game.plays.count).to eq(1) #expect(@game.plays.count).to eq(1)
# Active player is the lowest card holder
expect(@game.current_player).to eq(@game.lowest_card.player) expect(@game.current_player).to eq(@game.lowest_card.player)
@card_to_play = @game.current_player.player_cards.order(:value).first # Play the lowest card
@card_to_play.play_id = @play.id @cards_to_play = @game.current_player.player_cards.order(:value).first
@card_to_play.save @play.player_cards << @cards_to_play
@play.save
expect(@play.player_cards.include? @game.lowest_card).to eq true 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 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| RSpec.configure do |config|
config.include Features::SessionHelpers, type: :feature config.include Features::SessionHelpers, type: :feature
config.include Features::GameHelpers, type: :feature config.include Features::GameHelpers, type: :feature
config.include Models::PlayHelpers, type: :model
end 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