diff --git a/.gitignore b/.gitignore index cd75cc7..746a98b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ # ctags tags +gems.tags # bundler state /.bundle diff --git a/Gemfile b/Gemfile index aeffd86..bcc1c73 100644 --- a/Gemfile +++ b/Gemfile @@ -40,6 +40,7 @@ group :development do gem 'rb-fsevent', require: false gem 'rb-inotify', require: false gem 'spring-commands-rspec' + gem 'guard-ctags-bundler' end group :development, :test do gem 'factory_girl_rails' diff --git a/Gemfile.lock b/Gemfile.lock index 72f0fd4..b34f378 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -114,6 +114,9 @@ GEM guard (~> 2.2) guard-compat (~> 1.1) guard-compat (1.2.0) + guard-ctags-bundler (1.4.0) + guard (>= 2.0) + guard-compat (>= 0.1.0) guard-rails (0.7.0) guard (~> 2.0) guard-rspec (4.5.0) @@ -344,6 +347,7 @@ DEPENDENCIES faker foreman guard-bundler + guard-ctags-bundler guard-rails guard-rspec haml-lint @@ -376,3 +380,6 @@ DEPENDENCIES turbolinks uglifier (>= 1.3.0) upmin-admin + +BUNDLED WITH + 1.10.6 diff --git a/Guardfile b/Guardfile index 8397fe2..795e12d 100644 --- a/Guardfile +++ b/Guardfile @@ -50,7 +50,7 @@ end # * zeus: 'zeus rspec' (requires the server to be started separately) # * 'just' rspec: 'rspec' -guard :rspec, cmd: "bundle exec spring rspec" do +guard :rspec, cmd: "bundle exec spring rspec", all_after_pass: true, failed_mode: :focus do require "guard/rspec/dsl" dsl = Guard::RSpec::Dsl.new(self) @@ -93,3 +93,8 @@ guard :rspec, cmd: "bundle exec spring rspec" do Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" end end + +guard 'ctags-bundler', src_path: ["app", "lib", "spec/support"], project_file: "tags" do + watch(/^(app|lib|spec\/support)\/.*\.rb$/) + watch('Gemfile.lock') +end diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb index 6671af3..3173571 100644 --- a/app/controllers/games_controller.rb +++ b/app/controllers/games_controller.rb @@ -23,25 +23,45 @@ class GamesController < ApplicationController def play_hand # TODO: validate player's turn, current_user == @game.current_player.user # TODO: move this logic out of controller maybe to model(s) - @cards_to_play = @game.current_player.player_cards.find(params[:player_card_ids]) + + # If cards are played + if params[:player_card_ids] + @cards_to_play = @game.current_player.player_cards.order(:value).find(params[:player_card_ids]) + else + # No cards are played + @cards_to_play = [] + end # Instantiate the play @play = @game.current_player.plays.new @play.game_id = @game.id + @play.player_cards << @cards_to_play @play.save - @cards_to_play.each do |card| - card.play_id = @play.id - card.save + #@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 - # TODO: Ensure active player only changes when valid hand is played - @game.set_active_player 'next' - @game.save + #@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 diff --git a/app/models/game.rb b/app/models/game.rb index 93aa9fb..465823a 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -4,6 +4,7 @@ class Game < ActiveRecord::Base has_many :users, through: :players has_many :player_cards, through: :players has_many :plays, through: :players + belongs_to :play_to_beat accepts_nested_attributes_for :players validates :title, presence: true @@ -106,4 +107,12 @@ class Game < ActiveRecord::Base def control_user self.players.find(self.control_player_id).user end + + def hand_to_beat + self.play_to_beat.play.hand + end + + def player_to_beat + self.play_to_beat.play.player unless self.play_to_beat.play.nil? + end end diff --git a/app/models/play.rb b/app/models/play.rb index bf41195..0eb6332 100644 --- a/app/models/play.rb +++ b/app/models/play.rb @@ -2,4 +2,183 @@ 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? + + 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 + self.game.play_to_beat = @pb + self.game.save! + end + end + + # Validation methods + + def hand_valid? + @truths = [] + + # Is there a hand_to_beat ? + if self.game.play_to_beat + # current player is not the player to beat + if (self.game.player_to_beat != self.player) + @truths << (self.hand_type == self.game.play_to_beat.play.hand_type) + # Compare this hand to the hand_to_beat, + @truths << (self.beats? self.game.play_to_beat.play) + end + end + + if has_player_cards + + @truths << !hand_type.empty? + + if first_play? + @truths << contains_lowest_card? + end + + end + if @truths.include? false + errors.add(:player_cards, "invalid hand") + end + return (!@truths.include? false) + end + + # Check if the value of this hand is higher than the hand_to_beat + def beats?(play_to_beat) + @truth = self.high_card_value > play_to_beat.high_card_value + return @truth + 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 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 = 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 ) + true + else + false + end + end + + def contains_lowest_card? + # 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}") + 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 diff --git a/app/models/play_to_beat.rb b/app/models/play_to_beat.rb new file mode 100644 index 0000000..d23b47c --- /dev/null +++ b/app/models/play_to_beat.rb @@ -0,0 +1,4 @@ +class PlayToBeat < ActiveRecord::Base + belongs_to :game + belongs_to :play +end diff --git a/app/views/games/show.html.haml b/app/views/games/show.html.haml index 2457d02..a7c6295 100644 --- a/app/views/games/show.html.haml +++ b/app/views/games/show.html.haml @@ -29,7 +29,8 @@ = "Me" if player.user == current_user // Show whose turn it is - %strong= "My Turn " if @game.control_player == player + - if @game.active_player_id + %strong= "My Turn " if @game.active_player == player // Show the players' cards - unless player.inventory.empty? @@ -45,15 +46,22 @@ -# TODO better logic, not in view - unless @game.status == nil .table - - if @game.current_hand - %h1 Hand to beat + - if @game.play_to_beat + - if @game.play_to_beat.play + %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 diff --git a/bin/guard b/bin/guard new file mode 100755 index 0000000..0c1a532 --- /dev/null +++ b/bin/guard @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# +# This file was generated by Bundler. +# +# The application 'guard' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('guard', 'guard') diff --git a/bin/rake b/bin/rake index 8017a02..0fb4e07 100755 --- a/bin/rake +++ b/bin/rake @@ -3,6 +3,5 @@ begin load File.expand_path("../spring", __FILE__) rescue LoadError end -require_relative '../config/boot' -require 'rake' -Rake.application.run +require 'bundler/setup' +load Gem.bin_path('rake', 'rake') diff --git a/db/migrate/20150810114449_add_play_to_beat_to_game.rb b/db/migrate/20150810114449_add_play_to_beat_to_game.rb new file mode 100644 index 0000000..aa165bd --- /dev/null +++ b/db/migrate/20150810114449_add_play_to_beat_to_game.rb @@ -0,0 +1,5 @@ +class AddPlayToBeatToGame < ActiveRecord::Migration + def change + add_reference :games, :play_to_beat, index: true + end +end diff --git a/db/migrate/20150810115450_play_to_beat.rb b/db/migrate/20150810115450_play_to_beat.rb new file mode 100644 index 0000000..2860ef1 --- /dev/null +++ b/db/migrate/20150810115450_play_to_beat.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 0af6ddd..8c663da 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -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| diff --git a/spec/factories/play_to_beats.rb b/spec/factories/play_to_beats.rb new file mode 100644 index 0000000..d2567ad --- /dev/null +++ b/spec/factories/play_to_beats.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :play_to_beat do + + end + +end diff --git a/spec/features/plays_spec.rb b/spec/features/plays_spec.rb index f92fa8a..824632b 100644 --- a/spec/features/plays_spec.rb +++ b/spec/features/plays_spec.rb @@ -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 diff --git a/spec/models/game_spec.rb b/spec/models/game_spec.rb index acf5536..6cc4401 100644 --- a/spec/models/game_spec.rb +++ b/spec/models/game_spec.rb @@ -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' diff --git a/spec/models/play_spec.rb b/spec/models/play_spec.rb index b42be7f..f632062 100644 --- a/spec/models/play_spec.rb +++ b/spec/models/play_spec.rb @@ -1,5 +1,3 @@ -require 'rails_helper' - RSpec.describe Play, type: :model do before(:each) do @game = FactoryGirl.create(:game) @@ -17,23 +15,315 @@ RSpec.describe Play, type: :model do subject { @play } it { should respond_to(:game_id) } + it { should respond_to(:game) } + it { should respond_to(:player_id) } + it { should respond_to(:player) } + it { should respond_to(:player_cards) } + it { should respond_to(:hand) } + it { should respond_to(:hand_type) } + it { should respond_to(:to_s) } - it '#game_id returns an integer' do - expect(@play.game_id).to match @game.id + it '#game_id returns a game id' do + expect(@play.game).to eq Game.find(@play.game_id) + end + + it '#player_id return a player id' do + expect(@play.player).to eq Player.find(@play.player_id) + + end + it '#player_cards returns a PlayerCard collection' do + expect(@play.player_cards.class).to eq(PlayerCard::ActiveRecord_Associations_CollectionProxy) + end + + it '#hand returns a PlayerCard collection' do + expect(@play.hand.class).to eq(PlayerCard::ActiveRecord_Associations_CollectionProxy) + end + + it '#hand_type returns a string' do + @play.player_cards << create_hand(1, 'single') + expect(@play.hand_type).to eq 'single' + end + + it '#to_s returns a string of the hand' do + @play.player_cards << create_hand(1, 'single') + expect(@play.to_s).to eq '3 Spade' end 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) - 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 - expect(@play.player_cards.include? @game.lowest_card).to eq true + 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) + # 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(subject.valid?).to eq(true) + end + end + describe 'wins?' do + context 'it does not beat the hand_to_beat' do + it 'single' do + @play_to_beat = Play.new + @play_to_beat.player = @game.players.where.not(id: @game.active_player.id).last + @play_to_beat.game = @game + @play_to_beat.player_cards << @play_to_beat.player.player_cards.order(:value).last + @play_to_beat.save + @play.game.reload + + expect(@play.game.play_to_beat.play).to eq(@play_to_beat) + # Losing card + @cards_to_play = @play.player.player_cards.order(:value).first + @play.player_cards << @cards_to_play + @play.save + expect(@game.player_to_beat).to_not eq(@play.player) + expect(@play.hand_valid?).to eq(false) + expect(@play.errors[:player_cards].include?('invalid hand')).to eq(true) + end + + it 'double' do + @deck = Deck.new + @cards_to_beat = [ + @spade5 = PlayerCard.new(@deck.cards[8].instance_values), + @club5 = PlayerCard.new(@deck.cards[9].instance_values) + ] + + @play_to_beat = Play.new + @play_to_beat.player = @game.players.first + @play_to_beat.player_cards << @cards_to_beat + @play_to_beat.game = @game + @play_to_beat.save + + @play.player_cards = [ + @spade4 = PlayerCard.new(@deck.cards[4].instance_values), + @club4 = PlayerCard.new(@deck.cards[5].instance_values) + ] + + # Shared tests + [@play_to_beat,@play].each do |play| + expect(play.errors[:player_cards]).to eq([]) + expect(play.valid?).to eq(true) + expect(play.player_cards.count).to eq(2) + expect(play.hand_type).to eq('double') + end + + expect(@play.beats? @play_to_beat).to eq(false) + + end + + + end + context 'it beats the hand_to_beat' do + it 'single' do + @play_to_beat = Play.new + @play_to_beat.player = @game.players.last + @play_to_beat.game = @game + @play_to_beat.player_cards << @play_to_beat.player.player_cards.order(:value).first + @play_to_beat.save + + @cards_to_play = @play.player.player_cards.order(:value).last + @play.player_cards << @cards_to_play + @play.save + expect(@play.valid?).to eq(true) + expect(@play.errors[:player_cards]).to eq([]) + end + + it 'double' do + @deck = Deck.new + @cards_to_beat = [ + @spade3 = PlayerCard.new(@deck.cards[0].instance_values), + @club3 = PlayerCard.new(@deck.cards[1].instance_values) + ] + + @play_to_beat = Play.new + @play_to_beat.player = @game.players.first + @play_to_beat.player_cards << @cards_to_beat + @play_to_beat.game = @game + @play_to_beat.save + + @play.player_cards = [ + @spade4 = PlayerCard.new(@deck.cards[4].instance_values), + @club4 = PlayerCard.new(@deck.cards[5].instance_values) + ] + + # Shared tests + [@play_to_beat,@play].each do |play| + expect(play.errors[:player_cards]).to eq([]) + expect(play.valid?).to eq(true) + expect(play.player_cards.count).to eq(2) + expect(play.hand_type).to eq('double') + end + + expect(@play.beats? @play_to_beat).to eq(true) + + end + + it 'becomes the new play_to_beat' do + expect(@game.play_to_beat).to eq(nil) + # Play the hand + @cards_to_play = @game.current_player.inventory[0] + @play.hand << @cards_to_play + @play.save + expect(@play.hand_valid?).to eq(true) + @game.reload + expect(@game.play_to_beat.play).to eq(@play) end 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('double') + 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('double') + 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 diff --git a/spec/models/play_to_beat_spec.rb b/spec/models/play_to_beat_spec.rb new file mode 100644 index 0000000..b00d12c --- /dev/null +++ b/spec/models/play_to_beat_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe PlayToBeat, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/support/helpers.rb b/spec/support/helpers.rb index 275f3a0..3228baf 100644 --- a/spec/support/helpers.rb +++ b/spec/support/helpers.rb @@ -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 diff --git a/spec/support/helpers/play_helpers.rb b/spec/support/helpers/play_helpers.rb new file mode 100644 index 0000000..0c34550 --- /dev/null +++ b/spec/support/helpers/play_helpers.rb @@ -0,0 +1,35 @@ +module Models + module PlayHelpers + def create_hand(count, type) + cards = [] + count.times do + cards << PlayerCard.new + end + + case type + when /single/ + cards[0].rank = Card::RANKS[0] + cards[0].suit = Card::SUITS[0] + 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 + return cards + end + end +end