Move seats from table to game

This commit is contained in:
Jesse C. Fisher 2016-02-22 19:03:25 -08:00
parent e8273c42c4
commit cb172f6e6d
20 changed files with 176 additions and 110 deletions

View File

@ -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({
<PlayerList
game={this.state.data}
current_user={this.props.current_user}
onSeatSubmit={this.props.onSeatSubmit}
onSeatSubmit={this.handleSeatSubmit}
onPlayHandSubmit={this.handlePlayHandSubmit} />
{this.state.data.winner_player_id ? "Winner: " + this.state.data.winner_player_id : null}

View File

@ -0,0 +1,30 @@
var HandToBeat = React.createClass({
render: function() {
if (this.props.cards == null || this.props.cards == "") {
return (
<div id="play-to-beat">
<h4>Hand to beat:</h4>
None
</div>
);
} else {
return (
<div id="play-to-beat">
<h4>Hand to beat:</h4>
<ol id="cards-to-beat">
{this.props.cards.map(function(card) {
return <li key={card.id}>
{/* TODO: Don't hard code face cards here */}
{/* <span>{card.rank.replace('11','J').replace('12','Q').replace('13','K').replace('14','A').replace('15','2')} {card.suit}</span> */}
<img
alt={card.rank + ' ' + card.suit}
src={('/assets/cards/png/' + card.rank.replace('J','jack').replace('Q','queen').replace('K','king').replace('A','ace') + '_of_' + card.suit.toLowerCase() + 's.png').replace('11','jack').replace('12','queen').replace('13','king').replace('14','ace').replace('15','2')} />
</li>;})}
</ol>
</div>
);
}
}
});

View File

@ -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 (
<div className="table" id={"table-" + this.state.data.id}>
<h1>{this.state.data.title}</h1>
{/* TODO: DEPRECATED seats are now rendered within games <SeatList current_user={this.state.data.current_user} seats={this.state.data.seats} onSeatSubmit={this.handleSeatSubmit} /> */}
<Game id={this.state.data.current_game_id} current_user={this.state.data.current_user} onSeatSubmit={this.handleSeatSubmit} />
</div>
);

View File

@ -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

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -0,0 +1,5 @@
class AddPlayerIdToSeat < ActiveRecord::Migration
def change
add_column :seats, :player_id, :integer
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: 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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'

View File

@ -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)

View File

@ -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