diff --git a/app/assets/javascripts/components/game.js.jsx b/app/assets/javascripts/components/game.js.jsx index 80ca117..2eafada 100644 --- a/app/assets/javascripts/components/game.js.jsx +++ b/app/assets/javascripts/components/game.js.jsx @@ -39,7 +39,6 @@ var Game = React.createClass({ }, handlePlayHandSubmit: function(card_ids) { - console.log("Hello from handlePlayHandSubmit()"); $.ajax({ url: this.url() + '/play_hand', type: 'PATCH', @@ -55,6 +54,21 @@ var Game = React.createClass({ }); }, + handleSeatSubmit: function(seat_id, action) { + $.ajax({ + url: window.location.pathname + '/seats/' + seat_id + '/' + action, + type: 'PATCH', + dataType: 'json', + success: function (data, textStatus, jqXHR) { + // success callback + this.loadGameFromServer(); + }.bind(this), + error: function (jqXHR, textStatus, errorThrown) { + // error callback + } + }); + }, + handleStart: function() { $.ajax({ url: this.url() + '/start', @@ -114,7 +128,7 @@ var Game = React.createClass({ {this.state.data.winner_player_id ? "Winner: " + this.state.data.winner_player_id : null} diff --git a/app/assets/javascripts/components/hand_to_beat.js.jsx b/app/assets/javascripts/components/hand_to_beat.js.jsx new file mode 100644 index 0000000..ab4c505 --- /dev/null +++ b/app/assets/javascripts/components/hand_to_beat.js.jsx @@ -0,0 +1,30 @@ +var HandToBeat = React.createClass({ + + render: function() { + if (this.props.cards == null || this.props.cards == "") { + return ( +
+

Hand to beat:

+ None +
+ ); + } else { + + return ( +
+

Hand to beat:

+
    + {this.props.cards.map(function(card) { + return
  1. + {/* TODO: Don't hard code face cards here */} + {/* {card.rank.replace('11','J').replace('12','Q').replace('13','K').replace('14','A').replace('15','2')} {card.suit} */} + {card.rank +
  2. ;})} +
+
+ ); + } + } +}); diff --git a/app/assets/javascripts/components/table.js.jsx b/app/assets/javascripts/components/table.js.jsx index 6b28004..483e797 100644 --- a/app/assets/javascripts/components/table.js.jsx +++ b/app/assets/javascripts/components/table.js.jsx @@ -28,19 +28,16 @@ var Table = React.createClass({ }, handleSeatSubmit: function(seat_id, action) { - console.log("handleSeatSubmit"); $.ajax({ url: window.location.pathname + '/seats/' + seat_id + '/' + action, type: 'PATCH', dataType: 'json', success: function (data, textStatus, jqXHR) { // success callback - console.log(data.message); this.loadTableFromServer(); }.bind(this), error: function (jqXHR, textStatus, errorThrown) { // error callback - console.log(data.message); } }); }, @@ -52,7 +49,6 @@ var Table = React.createClass({ return (

{this.state.data.title}

- {/* TODO: DEPRECATED seats are now rendered within games */}
); diff --git a/app/models/game.rb b/app/models/game.rb index 13c0568..9627ab8 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -100,8 +100,12 @@ class Game < ActiveRecord::Base end def has_user? user - return true if self.players.exists?(user_id: user.id) - return true if self.players.exists?(soft_token: user.soft_token) + 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 @@ -118,11 +122,16 @@ class Game < ActiveRecord::Base end def joinable? user - !self.already_has?(user) && !self.full? + !self.has_user?(user) && !self.full? + end + + def player_ids_by_seat_order + player_ids = self.seats.order(:position).select {|s| s.player_id}.map {|s| s.player_id} end def next_player_id - player_ids[(player_ids.index(controlling_player_id) + 1 ) % player_ids.length] + @controlling_player_index = self.player_ids_by_seat_order.index(controlling_player_id) + player_ids_by_seat_order[(@controlling_player_index + 1) % self.player_ids.length] end # The player whose turn it is diff --git a/app/models/play.rb b/app/models/play.rb index fa137bf..eab57c1 100644 --- a/app/models/play.rb +++ b/app/models/play.rb @@ -40,7 +40,7 @@ class Play < ActiveRecord::Base # Validations # Is there a hand_to_beat ? - if game.try(: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? diff --git a/app/models/seat.rb b/app/models/seat.rb index 06b20c4..e00008f 100644 --- a/app/models/seat.rb +++ b/app/models/seat.rb @@ -2,6 +2,7 @@ class Seat < ActiveRecord::Base belongs_to :table belongs_to :game belongs_to :user + belongs_to :player def sit(current_user) if self.occupied? @@ -10,11 +11,12 @@ class Seat < ActiveRecord::Base # Stand up from any other seats at this table current_user.stand_from(self.table) + self.reload + self.player_id = self.table.current_game.add_player_from_user(current_user).id self.user_id = current_user.id self.user_soft_token = current_user.soft_token self.save - self.table.current_game.add_player_from_user(current_user) return "Successfully sat." end end @@ -23,11 +25,11 @@ class Seat < ActiveRecord::Base unless self.occupied_by?(current_user) return "Error. Seat not occupied by you." else + self.table.current_game.remove_player_from_user(current_user) self.user_id = nil self.user_soft_token = nil + self.player_id = nil self.save - - self.table.current_game.remove_player_from_user(current_user) return "Successfully stood up." end end diff --git a/app/models/user.rb b/app/models/user.rb index 285f330..1fb78bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -29,7 +29,9 @@ class User < ActiveRecord::Base @seats << table.seats.where(user_soft_token: self.soft_token) end @seats.flatten.each do |seat| + seat.player.destroy seat.user_id = nil + seat.player_id = nil seat.user_soft_token = nil seat.save end diff --git a/config/routes.rb b/config/routes.rb index 969cfff..62d517b 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,12 @@ Rails.application.routes.draw do post 'join' patch 'play_hand' end + resources :seats do + member do + patch 'sit' + patch 'stand' + end + end end mount Upmin::Engine => '/admin' diff --git a/db/migrate/20160222131306_add_player_id_to_seat.rb b/db/migrate/20160222131306_add_player_id_to_seat.rb new file mode 100644 index 0000000..9fc587a --- /dev/null +++ b/db/migrate/20160222131306_add_player_id_to_seat.rb @@ -0,0 +1,5 @@ +class AddPlayerIdToSeat < ActiveRecord::Migration + def change + add_column :seats, :player_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index d77beb4..dfbe485 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: 20160219012611) do +ActiveRecord::Schema.define(version: 20160222131306) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -79,6 +79,7 @@ ActiveRecord::Schema.define(version: 20160219012611) do t.integer "position" t.string "user_soft_token" t.integer "game_id" + t.integer "player_id" end add_index "seats", ["table_id"], name: "index_seats_on_table_id", using: :btree diff --git a/spec/features/plays_spec.rb b/spec/features/plays_spec.rb index 846600d..070f965 100644 --- a/spec/features/plays_spec.rb +++ b/spec/features/plays_spec.rb @@ -8,7 +8,6 @@ feature 'Play a hand', type: :feature, js: true do @game.reload @player = @game.controlling_player signin(@player.user.email,'password') - #login_as(@player.user, scope: :user) visit game_path @game end @@ -32,7 +31,7 @@ feature 'Play a hand', type: :feature, js: true do # Play the highest card @card_to_play = @player.inventory.last - find("[data-card-name='#{@card_to_play.to_s}'] ~ img").click + find_card(@card_to_play).click #click_button 'Play Hand' click_button "play_hand_button_#{@player.id}" @@ -50,11 +49,10 @@ feature 'Play a hand', type: :feature, js: true do # Then I see the hand to beat is this play scenario 'first play contains lowest card' do @card_to_play = @game.lowest_card - find("[data-card-name='#{@card_to_play.to_s}'] ~ img").click + find_card(@card_to_play).click #click_button 'Play Hand' click_button "play_hand_button_#{@player.id}" - #expect(page).to have_content(/Hand to beat.*#{@game.lowest_card.to_s}/) - expect(page).to have_content("Hand to beat: #{@game.lowest_card.to_s}") + expect(cards_to_beat).to eq @game.lowest_card.to_s end # Scenario: Played cards leave the players inventory @@ -65,13 +63,15 @@ feature 'Play a hand', type: :feature, js: true do # Store initial state of players inventory @player_inventory = @player.inventory.join(' ') @card_to_play = @player.inventory.first - find("[data-card-name='#{@card_to_play.to_s}'] ~ img").click + find_card(@card_to_play).click #click_button 'Play Hand' click_button "play_hand_button_#{@player.id}" #wait_for_ajax + expect(page).to have_content " " # Played hand is rendered - expect(page).to have_content(/Hand to beat.*#{@game.lowest_card.to_s}/) + expect(cards_to_beat).to eq @game.lowest_card.to_s # Player inventory is rendered without played cards + #TODO: Deprecated have_content for inventory. Write new expectation. expect(page.find("#player_controls_#{@player.id}")).to_not have_content(@card_to_play.to_s) end @@ -114,27 +114,19 @@ feature 'Play a hand', type: :feature, js: true do # Then the next player becomes the current player scenario 'active player rotates after a valid hand' do @game.reload + @player = @game.controlling_player @next_player_id = @game.next_player_id - @controlling_player_id = @player.id - @controlling_player_index = @game.player_ids.index(@game.controlling_player_id) + @controlling_player_id = @game.controlling_player.id + @controlling_player_index = @game.player_ids_by_seat_order.index(@game.controlling_player_id) @card_to_play = @player.inventory.first - find("[data-card-name='#{@card_to_play.to_s}'] ~ img").click + find_card(@card_to_play).click #click_button 'Play Hand' click_button "play_hand_button_#{@player.id}" - - # reload the game object instance - @game.reload - expect(page).to have_content(/Hand to beat.*#{@card_to_play.to_s}/) - - # render active player style - #expect(page).to have_content(@game.players.find(@next_player_id).user.email + " My Turn") - - # next player is now the active player - #expect(@game.controlling_player_id).to_not eq(@controlling_player_id) - #expect(@game.controlling_player_id).to eq(@next_player_id) + expect(page).to have_content "Hand to beat" + expect(cards_to_beat).to eq @card_to_play.to_s expect(page).to have_selector("#player-#{@next_player_id}.has-control") - end + end # Scenario: Player can not play an invalid hand on their turn # Given I am the active player @@ -211,7 +203,10 @@ feature 'Play a hand', type: :feature, js: true do #click_button 'Play Hand' click_button "play_hand_button_#{@player.id}" - expect(page).to have_content "Hand to beat: #{@card_to_play.to_s}" + #TODO: Fix race condition + sleep 4 + expect(page).to have_content "Hand to beat" + expect(cards_to_beat).to eq @card_to_play.to_s @game.reload expect(@game.player_to_beat).to eq @player diff --git a/spec/features/seats/seats_sit_spec.rb b/spec/features/seats/seats_sit_spec.rb index ceca71c..534a187 100644 --- a/spec/features/seats/seats_sit_spec.rb +++ b/spec/features/seats/seats_sit_spec.rb @@ -31,6 +31,10 @@ feature 'Seats', :devise, js: true do scenario 'table seats match up with game seats' do click_button 'Sit', match: :first + expect(page).to have_content "Guest" + expect(page.find(:css, ".game-seat.position-1")).to have_content "Guest" + click_button 'Sit', match: :first + expect(page).to have_content "Guest" expect(page.find(:css, ".game-seat.position-2")).to have_content "Guest" end @@ -38,30 +42,31 @@ feature 'Seats', :devise, js: true do scenario 'visitor changes seats' do click_button 'Sit', match: :first expect(page).to have_content "Guest" - #TODO: Fix race condition - sleep 3 + expect(page.find(:css, ".game-seat.position-1")).to have_content "Guest" click_button 'Sit', match: :first expect(page).to have_content "Guest" - expect(page.text.scan('Guest').count).to eq 2 + expect(page.find(:css, ".game-seat.position-2")).to have_content "Guest" + expect(page.text.scan('Guest').count).to eq 1 end scenario 'visitor can join the current game' do click_button 'Sit', match: :first - #TODO: Fix race condition. Trigger game react component reload when player joins a table - sleep 4 + sleep 3 expect(page).to have_content "Guest" - expect(page.text.scan('Guest').count).to eq 2 + expect(page.text.scan('Guest').count).to eq 1 + end + + def sit_buttons + find_all(:button, "Sit") end scenario 'visitor can leave the current game' do click_button 'Sit', match: :first - #TODO: Fix race condition - sleep 4 - expect(page.text.scan('Guest').count).to eq 2 + expect(page).to have_content 'Guest' + expect(page.text.scan('Guest').count).to eq 1 click_button 'Stand', match: :first - expect(page).to have_content "Sit" - #TODO: Fix race condition - sleep 4 + expect(page.find('.game-seat.position-1')).to have_content 'Sit' + expect(sit_buttons.length).to eq 4 expect(page.text.scan('Guest').count).to eq 0 end @@ -85,22 +90,18 @@ feature 'Seats', :devise, js: true do scenario 'user can join the current game' do expect(page).to have_content "Sit" click_button 'Sit', match: :first - #TODO: Fix race condition. Trigger game react component reload when player joins a table - sleep 4 expect(page).to have_content @user.email - expect(page.text.scan(@user.email).count).to eq 2 + expect(page.text.scan(@user.email).count).to eq 1 end scenario 'user can leave the current game' do expect(page).to have_content "Sit" click_button 'Sit', match: :first - #TODO: Fix race condition - sleep 4 - expect(page.text.scan(@user.email).count).to eq 2 + expect(page).to have_content @user.email + expect(page.text.scan(@user.email).count).to eq 1 click_button 'Stand', match: :first expect(page).to have_content "Sit" - #TODO: Fix race condition - sleep 4 + sleep 1 expect(page.text.scan(@user.email).count).to eq 0 end diff --git a/spec/features/tables/table_join_spec.rb b/spec/features/tables/table_join_spec.rb index d7057e6..3ec9e20 100644 --- a/spec/features/tables/table_join_spec.rb +++ b/spec/features/tables/table_join_spec.rb @@ -17,21 +17,16 @@ feature 'Table show', :devise do scenario 'visitor can join the current game', js: true do click_button 'Sit', match: :first - #TODO: Fix race condition. Trigger game react component reload when player joins a table - sleep 4 expect(page).to have_content "Guest" - expect(page.text.scan('Guest').count).to eq 2 + expect(page.text.scan('Guest').count).to eq 1 end scenario 'visitor can leave the current game', js: true do click_button 'Sit', match: :first - #TODO: Fix race condition - sleep 4 - expect(page.text.scan('Guest').count).to eq 2 + expect(page).to have_content "Guest" + expect(page.text.scan('Guest').count).to eq 1 click_button 'Stand', match: :first - expect(page).to have_content "Sit" - #TODO: Fix race condition - sleep 4 + expect(page.find('.game-seat.position-1')).to have_content "Sit" expect(page.text.scan('Guest').count).to eq 0 end @@ -55,22 +50,17 @@ feature 'Table show', :devise do scenario 'user can join the current game', js: true do expect(page).to have_content "Sit" click_button 'Sit', match: :first - #TODO: Fix race condition. Trigger game react component reload when player joins a table - sleep 4 expect(page).to have_content @user.email - expect(page.text.scan(@user.email).count).to eq 2 + expect(page.text.scan(@user.email).count).to eq 1 end scenario 'user can leave the current game', js: true do expect(page).to have_content "Sit" click_button 'Sit', match: :first - #TODO: Fix race condition - sleep 4 - expect(page.text.scan(@user.email).count).to eq 2 + expect(page).to have_content @user.email + expect(page.text.scan(@user.email).count).to eq 1 click_button 'Stand', match: :first - expect(page).to have_content "Sit" - #TODO: Fix race condition - sleep 4 + expect(page.find('.game-seat.position-1')).to have_content "Sit" expect(page.text.scan(@user.email).count).to eq 0 end diff --git a/spec/features/tables/table_show_spec.rb b/spec/features/tables/table_show_spec.rb index 15bf470..6f0f6d8 100644 --- a/spec/features/tables/table_show_spec.rb +++ b/spec/features/tables/table_show_spec.rb @@ -35,6 +35,8 @@ feature 'Table show', :devise do @table = FactoryGirl.create :table @user = FactoryGirl.create :user signin(@user.email, @user.password) + visit tables_path + click_link @table.title end # Scenario: User can view a table @@ -42,18 +44,13 @@ feature 'Table show', :devise do # When I click a table # Then I see the table attributes scenario 'user can see the table', js: true do - visit tables_path - click_link @table.title expect(page).to have_content "Sign out" expect(page).to have_selector "#table-#{@table.id} > h1", @table.title expect(current_path).to eq table_path(@table) end scenario 'user can see the current game', js: true do - #TODO: Fix race condition - visit table_path @table - #TODO: Write a better expectation - expect(page).to have_content "Inventory" + expect(page).to have_content "Sit" end end diff --git a/spec/features/users/user_join_game_spec.rb b/spec/features/users/user_join_game_spec.rb index 78d8906..86af26b 100644 --- a/spec/features/users/user_join_game_spec.rb +++ b/spec/features/users/user_join_game_spec.rb @@ -25,10 +25,8 @@ feature 'Join a game', type: :feature, js: true do @user = FactoryGirl.create :user login_as(@user, scope: :user) visit game_path @game - click_button "Join" - #TODO: Fix this asynchronous race condition - visit current_path - expect(page).to have_content @user.email + click_button "Sit", match: :first + expect(page).to have_content @user.display_name end # Scenario: The user is unable to join a game if they are already joined @@ -40,12 +38,10 @@ feature 'Join a game', type: :feature, js: true do @user = FactoryGirl.create :user login_as(@user, scope: :user) visit game_path @game - click_button "Join" - #TODO: Fix this asynchronous race condition - visit current_path - expect(page).to_not have_content "Join" + click_button "Sit", match: :first + expect(page).to have_content @user.display_name + click_button "Sit", match: :first + expect(page).to have_content @user.display_name + expect(page.text.scan(@user.display_name).count).to eq 1 end - - - end diff --git a/spec/features/visitor_plays_spec.rb b/spec/features/visitor_plays_spec.rb index 1bfdab4..d34c3af 100644 --- a/spec/features/visitor_plays_spec.rb +++ b/spec/features/visitor_plays_spec.rb @@ -6,8 +6,9 @@ feature 'Play a hand', type: :feature, js: true do before(:each) do @game = setup_visitor_game visit game_path @game - click_button "Join" - visit game_path @game + click_button "Sit", match: :first + #TODO: Fix race condition + sleep 2 click_button "Start" @game.reload end @@ -17,15 +18,14 @@ feature 'Play a hand', type: :feature, js: true do end scenario 'player can see their own 13 card faces' do - #TODO: Remove race condition - sleep 3 - @player = @game.players.last - expect(page.find_all("#player-#{@player.id} #inventory img").length).to eq(13) + #TODO: Fix race condition + sleep 2 + expect(page.find_all("#inventory img").length).to eq(13) end scenario 'player can see other players 13 card backs' do #TODO: Remove race condition - sleep 3 + sleep 2 inventories = page.find_all(".inventory") inventories.each do |inventory| #TODO: This test should verify that other players card fronts are NOT seen diff --git a/spec/features/visitors/game_join_spec.rb b/spec/features/visitors/game_join_spec.rb index c630517..ba92cb5 100644 --- a/spec/features/visitors/game_join_spec.rb +++ b/spec/features/visitors/game_join_spec.rb @@ -11,8 +11,8 @@ feature 'Join game', :devise do visit new_game_path fill_in 'game_title', with: 'Test Game' click_button 'Create Game' - expect(page).to have_button("Join") - click_button 'Join' + expect(page).to have_button("Sit") + click_button 'Sit', match: :first #TODO: Fix this asynchronous race condition visit current_path @game = Game.find(current_path.split('/').last) diff --git a/spec/features/visitors/sign_up_spec.rb b/spec/features/visitors/sign_up_spec.rb index 3d5a825..98e3484 100644 --- a/spec/features/visitors/sign_up_spec.rb +++ b/spec/features/visitors/sign_up_spec.rb @@ -22,7 +22,7 @@ feature 'Sign Up', :devise do visit new_game_path fill_in 'game_title', with: 'Test Game' click_button 'Create Game' - click_button 'Join' + click_button 'Sit', match: :first sign_up_with('test@example.com', 'please123', 'please123') visit games_path click_link 'Test Game' diff --git a/spec/models/play_spec.rb b/spec/models/play_spec.rb index 2ec4143..556e238 100644 --- a/spec/models/play_spec.rb +++ b/spec/models/play_spec.rb @@ -335,6 +335,7 @@ RSpec.describe Play, type: :model do @play_to_beat = Play.new(game_id: @game.id, player_id: @game.next_player_id, player_cards: @cards_to_beat) @play_to_beat.save @game.play_to_beat_id = @play_to_beat.id + @play.reload # Setup the play @spade3 = PlayerCard.new(rank: "3", suit: "Spade", value: 1) diff --git a/spec/support/helpers/game_helpers.rb b/spec/support/helpers/game_helpers.rb index 1862c10..a4d4712 100644 --- a/spec/support/helpers/game_helpers.rb +++ b/spec/support/helpers/game_helpers.rb @@ -18,11 +18,32 @@ module Features def setup_visitor_game # Default game has 3 players game = FactoryGirl.create :game - player1 = FactoryGirl.create :player, game: game - player2 = FactoryGirl.create :player, game: game - player3 = FactoryGirl.create :player, game: game + + u = FactoryGirl.build :user + game.seats[0].sit u + + u = FactoryGirl.build :user + game.seats[1].sit u + + u = FactoryGirl.build :user + game.seats[2].sit u + return game end + + def find_card card + find("[data-card-name='#{card.to_s}'] ~ img") + end + + def cards_to_beat + @cards = find("#cards-to-beat") + if @cards + @cards = @cards.find_all('img') + end + @array = [] + @cards.map {|c| @array << c[:alt]} + return @array.join " " + end end end @@ -36,10 +57,10 @@ module Models user2 = FactoryGirl.create :user user3 = FactoryGirl.create :user user4 = FactoryGirl.create :user - game.add_player_from_user user1 - game.add_player_from_user user2 - game.add_player_from_user user3 - game.add_player_from_user user4 + game.seats[0].sit user1 + game.seats[1].sit user2 + game.seats[2].sit user3 + game.seats[3].sit user4 game.start return game end