diff --git a/.gitignore b/.gitignore
index 6a502e9..83e5b73 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
# Ignore bundler config.
/.bundle
+.rvm
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
@@ -14,3 +15,5 @@
# Ignore all logfiles and tempfiles.
/log/*.log
/tmp
+*.sw*
+/**/*.sw*
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index bf01ad1..b41e4c0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -6,14 +6,14 @@ class ApplicationController < ActionController::Base
# run these methods on page load
before_filter :current_user
- private
-
- # Finds the User with the ID stored in the session with the key
- # :current_user_id This is a common way to handle user login in
- # a Rails application; logging in sets the session value and
- # logging out removes it.
+ helper_method :current_user
+
def current_user
- session[:id] ||= request.remote_ip.hash + rand(1000)
- @_current_user = session[:id]
+ @current_user ||= unless @current_user
+ session[:id] ||= request.remote_ip.hash + rand(1000)
+ end
end
+
+ private
+
end
diff --git a/app/controllers/games_controller.rb b/app/controllers/games_controller.rb
index fc00435..38cb550 100644
--- a/app/controllers/games_controller.rb
+++ b/app/controllers/games_controller.rb
@@ -1,74 +1,48 @@
class GamesController < ApplicationController
- before_action :set_game, only: [:show, :edit, :update, :destroy]
+ before_action :set_game, only: [:add_player, :deal, :player_action, :show]
def add_player
- unless @_current_user.nil?
- set_game
- state = @game.load_state
- if state[:players].size < 5
- unless state[:players].include?(@_current_user)
- if state[:cards_in_play]
- redirect_to @game, notice: 'Cant join once the game has already started'
- return
- end
- state[:players].push @_current_user
- while state[:names].include? params[:username]
- # make sure username is unique by appending random numbers
- params[:username] += rand(10).to_s
- end
- state[:names].push params[:username]
- @game.save_state state
- if @game.save
- redirect_to @game, notice: 'Joined the game!'
- else
- redirect_to @game, notice: 'Failed to join the game'
- end
- else
- redirect_to @game, notice: 'Youre already in the game'
- end
- else
- redirect_to @game, notice: 'There are too many players in the game already'
- end
+ if @game.state_data[:players].size > 5
+ redirect_to @game, notice: 'There are too many players in the game already'
+ elsif @game.has_player?(current_user)
+ redirect_to @game, notice: 'Youre already in the game'
+ elsif !@game.waiting_for_players?
+ redirect_to @game, notice: 'Cant join once the game has already started'
+ else
+ session[:player_name] = @game.add_player(current_user, params[:username])
+ redirect_to @game, notice: "Welcome to the game"
end
end
-
+
def deal
- set_game
- state = @game.load_state
- if state[:players].size < 3 or state[:players].size > 5
- redirect_to @game, notice: 'Must have between 3 and 5 players to start'
- return
- end
- if state[:players].first == @_current_user
- if state[:cards_in_play].nil?
- @game.save_state @game.deal_cards(state)
- @game.save
- redirect_to @game, notice: 'Game has started!'
- else
- redirect_to @game, notice: 'Game has already started'
- end
- else
- redirect_to @game, notice: 'Only the creator of the game can start it'
- end
+ message = if @game.state_data[:players].size < 3 or @game.state_data[:players].size > 5
+ 'Must have between 3 and 5 players to start'
+ elsif !@game.waiting_for_players?
+ 'Game has already started'
+ elsif @game.can_deal?(current_user)
+ 'Only the creator of the game can start it'
+ else
+ @game.deal_cards(@game.state_data)
+ @game.state_data[:current_status] = :in_play
+ @game.save!
+ 'Game has started!'
+ end
+ redirect_to @game, notice: message
end
def player_action
- set_game
- state = @game.load_state
-
- # player isnt allowed to do anything if it's not their turn
- if state[:waiting_on] != @_current_user
- redirect_to @game, notice: "Dude... It's not your turn"
+ if @game.state_data[:waiting_on] != current_user
+ redirect_to @game, notice: "Bro... It's not your turn"
return
end
-
+
if params[:bid]
# user is making a bid
- if @game.done_bidding? state
+ if @game.done_bidding?
redirect_to @game, notice: 'Bidding is over BRO'
return
else
- if @game.player_action(@_current_user, params[:bid])
+ if @game.player_action(current_user, params[:bid])
@game.save
redirect_to @game, notice: 'Placed bid, YEAAA!'
else
@@ -78,7 +52,7 @@ def player_action
else
# user is playing a card
card = Card.new(params[:suit], params[:value])
- if @game.player_action(@_current_user, card)
+ if @game.player_action(current_user, card)
@game.save
redirect_to @game, notice: "Played a card, niceee!"
else
@@ -96,49 +70,47 @@ def index
# GET /games/1
# GET /games/1.json
def show
- state = @game.load_state
-
- @is_playing = state[:players].include?(@_current_user)
- @game_started = !state[:bids].nil?
- player_index = state[:players].index(@_current_user) || 0
+ player_index = @game.state_data[:players].index(current_user) || 0
+ @is_playing = @game.state_data[:player].has_key?(current_user)
+ @game_started = !@game.state_data[:bids].nil?
# round number
- @round = state[:total_rounds] - state[:rounds_played]
+ @round = @game.state_data[:total_rounds] - @game.state_data[:rounds_played]
# names/scores around the board
# add in different order so the user is always on the bottom
@names = []
@round_scores = []
- @game.iterate_through_list_with_start_index(player_index, state[:names]) do |name, i|
- tricks_taken = (state[:tricks_taken] and state[:tricks_taken][i]) ? state[:tricks_taken][i].size : 0
- bid = (state[:bids] and state[:bids][i]) ? state[:bids][i] : 'No bid yet'
+ @game.iterate_through_list_with_start_index(player_index, @game.state_data[:players]) do |player_id, i|
+ tricks_taken = (@game.state_data[:tricks_taken] and @game.state_data[:tricks_taken][i]) ? @game.state_data[:tricks_taken][i].size : 0
+ bid = (@game.state_data[:bids] and @game.state_data[:bids][i]) ? @game.state_data[:bids][i] : 'No bid yet'
score = 0
- if state[:score] and state[:score][i]
- score = state[:score][i].inject :+
+ if @game.state_data[:score] and @game.state_data[:score][i]
+ score = @game.state_data[:score][i].inject :+
end
- @names.push name
- @round_scores.push (name.nil?) ? nil : "Tricks taken: #{tricks_taken} | Bid: #{bid} | Score: #{score}"
+ @names.push @game.state_data[:names][i]
+ @round_scores.push (player_id.nil?) ? nil : "Tricks taken: #{tricks_taken} | Bid: #{bid} | Score: #{score}"
end
-
+
# cards that have been played
- @played_cards = state[:cards_in_play]
+ @played_cards = @game.state_data[:cards_in_play]
if @game_started
# display cards in different order since the user is on the bottom
@played_cards = []
- @game.iterate_through_list_with_start_index(player_index, state[:players]) do |player,i|
- @played_cards.push state[:cards_in_play][i]
+ @game.iterate_through_list_with_start_index(player_index, @game.state_data[:players]) do |player,i|
+ @played_cards.push @game.state_data[:cards_in_play][i]
end
end
# players hand
@cards = []
- if @is_playing
- @cards = state[:player_hands][player_index] || @cards
+ if @is_playing && @game_started
+ @cards = @game.state_data[:player_hands][player_index] || @cards
# can't play any cards unless it's your turn
playable_cards = []
- if state[:waiting_on] == @_current_user
- playable_cards = @game.get_playable_cards(state[:first_suit_played], state[:player_hands][player_index])
+ if @game.state_data[:waiting_on] == current_user
+ playable_cards = @game.get_playable_cards(@game.state_data[:first_suit_played], @game.state_data[:player_hands][player_index])
end
@cards.each do |card|
if playable_cards.include? card
@@ -149,21 +121,22 @@ def show
@cards.sort! { |a,b| a.suit_order b }
# game status (ie. who we're waiting on)
- if state[:waiting_on]
- waiting_on_index = state[:players].index(state[:waiting_on])
- @waiting_on = waiting_on_index ? state[:names][waiting_on_index] : "Table to clear"
- @waiting_on = 'YOU' if @_current_user == state[:waiting_on]
- @done_bidding = @game.done_bidding? state
+ if @game.state_data[:waiting_on]
+ waiting_on_index = @game.state_data[:players].index(@game.state_data[:waiting_on])
+ @waiting_on = @game.state_data[:names][waiting_on_index] || "Table to clear"
+ @waiting_on = 'YOU' if current_user == @game.state_data[:waiting_on]
+
+ @done_bidding = @game.done_bidding?
unless @done_bidding
@waiting_on += " (BIDDING)"
end
else
@waiting_on = 'Game to start'
end
-
+
# show ace of spades if game hasnt started
- @trump = state[:trump_card] || Card.new('Spades', 12)
-
+ @trump = @game.state_data[:trump_card] || Card.new('Spades', 12)
+
# make sure show.js.erb is executed in the views folder
respond_to do |format|
format.js
@@ -176,59 +149,24 @@ def new
@game = Game.new
end
- # GET /games/1/edit
- def edit
- end
-
- # POST /games
- # POST /games.json
def create
- @game = Game.new(game_params)
- @game.set_up
+ @game = Game.create!(game_params)
+ @game.state_data[:players] << current_user
+ @game.save!
respond_to do |format|
- if @game.save
- format.html { redirect_to @game, notice: 'Game was successfully created.' }
- format.json { render :show, status: :created, location: @game }
- else
- format.html { render :new }
- format.json { render json: @game.errors, status: :unprocessable_entity }
- end
+ format.html { redirect_to @game, notice: 'Game was successfully created.' }
+ format.json { render :show, status: :created, location: @game }
end
end
- # PATCH/PUT /games/1
- # PATCH/PUT /games/1.json
- def update
- respond_to do |format|
- if @game.update(game_params)
- format.html { redirect_to @game, notice: 'Game was successfully updated.' }
- format.json { render :show, status: :ok, location: @game }
- else
- format.html { render :edit }
- format.json { render json: @game.errors, status: :unprocessable_entity }
- end
- end
- end
+ private
- # DELETE /games/1
- # DELETE /games/1.json
- def destroy
- @game.destroy
- respond_to do |format|
- format.html { redirect_to games_url, notice: 'Game was successfully destroyed.' }
- format.json { head :no_content }
- end
+ def set_game
+ @game = Game.find(params[:id])
end
- private
- # Use callbacks to share common setup or constraints between actions.
- def set_game
- @game = Game.find(params[:id])
- end
-
- # Never trust parameters from the scary internet, only allow the white list through.
- def game_params
- params.require(:game).permit(:name, :state)
- end
+ def game_params
+ params.require(:game).permit(:name, :state)
+ end
end
diff --git a/app/models/game.rb b/app/models/game.rb
index da726e1..d927cf4 100644
--- a/app/models/game.rb
+++ b/app/models/game.rb
@@ -1,22 +1,43 @@
class Game < ActiveRecord::Base
require 'yaml'
+ after_initialize :state_data
+ before_save :serialize_state
- def set_up
- state = {}
+ def enough_players?
+ state_data[:player].size > 2 && state_data[:player].size <= 5
+ end
- state[:total_rounds] = 10
- state[:rounds_played] = 0
- state[:players] = []
- state[:player_hands] = []
- state[:score] = []
- state[:deck] = []
- state[:names] = []
+ def has_player?(player_id)
+ state_data[:player].has_key?(player_id)
+ end
- state[:players].shuffle!
+ def serialize_state
+ self.state = state_data.to_yaml
+ end
- save_state state
+ def can_deal?(player_id)
+ state_data[:players].first == player_id && state_data[:player].has_key?(player_id)
+ end
+
+ def needs_to_bid?(current_user)
+ raise "Not a player in this game" unless has_player? current_user
+ end
+
+ def waiting_for_players?
+ state_data[:current_status] == :waiting_for_players && state_data[:player].size < 5
end
-
+
+ def add_player(player_id, player_name)
+ state_data[:players] << player_id
+ while state_data[:names].include? player_name
+ player_name += rand(10).to_s
+ end
+ state_data[:names] << player_name
+ state_data[:player][player_id] = player_name
+ save!
+ player_name
+ end
+
def deal_cards state
# need to have at least 3 players to start the game
raise 'Must have between 3 and 5 players to start' if state[:players].size < 3 or state[:players].size > 5
@@ -32,7 +53,7 @@ def deal_cards state
# person to the 'left' of the dealer bids first
state[:bids] = []
state[:waiting_on] = get_next_player state[:dealer], state[:players]
-
+
# deal out number of cards equal to whatever round we are on
# to each player
# deal cards first to player on 'right' of dealer
@@ -40,7 +61,7 @@ def deal_cards state
iterate_through_list_with_start_index(state[:players].find_index(state[:waiting_on]), state[:players]) { |player, i|
state[:player_hands][i] = state[:deck].slice!(0..(num_cards_per_player-1))
}
-
+
# the next card in the deck is trump
# whatever suit is trump is valued higher than non-trump suits
# an Ace as trump means there is "no trump"
@@ -56,25 +77,25 @@ def deal_cards state
# player either bids or plays a card if it's their turn
def player_action user_id, user_input=nil
- state = load_state
-
+ state = game_state
+
# return false if it's not the players turn
return false if user_id != state[:waiting_on]
current_player_index = state[:players].find_index user_id
-
+
# check if we are in bidding or playing a card
if !done_bidding? state
# player is making a bid
bid = user_input.to_i
-
+
# dealer cannot bid the same amount as the number of cards dealt
total_bids = state[:bids].select { |bid| !bid.nil? }.inject(:+)
num_cards_per_player = state[:total_rounds] - state[:rounds_played]
if state[:dealer] == user_id and total_bids + bid == num_cards_per_player
return false
end
-
+
# record the bid
state[:bids][current_player_index] = bid
@@ -97,7 +118,7 @@ def player_action user_id, user_input=nil
elsif not state[:player_hands][current_player_index].empty?
# player is playing a card
card = user_input
-
+
# ensure that the card is in their inventory
return false unless state[:player_hands][current_player_index].any? { |player_card| player_card == card }
@@ -105,7 +126,7 @@ def player_action user_id, user_input=nil
state[:first_suit_played] ||= card.suit
playable_cards = get_playable_cards(state[:first_suit_played], state[:player_hands][current_player_index])
return false unless playable_cards.include? card
-
+
# actually play the card
state[:cards_in_play][current_player_index] = state[:player_hands][current_player_index].delete(card)
@@ -119,11 +140,35 @@ def player_action user_id, user_input=nil
state[:waiting_on] = get_next_player state[:waiting_on], state[:players]
end
end
-
+
save_state state
return true
end
+ def state_data
+ current_time = self.updated_at || self.created_at
+ @state_read_at ||= current_time
+ if @state_data.nil? || (@state_read_at && current_time > @state_read_at)
+ if self.state.nil?
+ self.state = {
+ current_status: :waiting_for_players,
+ total_rounds: 10,
+ rounds_played: 0,
+ players: nil,
+ player_hands: nil,
+ score: nil,
+ deck: nil,
+ names: nil
+ }.to_yaml
+ end
+ @state_data = load_state
+ @state_data[:players] ||= Array.new
+ @state_data[:names] ||= Array.new
+ @state_data[:player] ||= Hash.new
+ end
+ @state_data
+ end
+
# clear the table of cards and calculate who won the trick/game
def clear_table current_player_index, state
# sleep a bit so the table isn't cleared immediately
@@ -189,10 +234,10 @@ def clear_table current_player_index, state
end
def load_state
- return YAML.load(self.state)
+ return self.state.nil? ? Hash.new : YAML.load(self.state)
end
-
- def save_state state
+
+ def save_state(state = state_data)
self.state = state.to_yaml
end
@@ -207,14 +252,14 @@ def get_highest_card cards, first_suit_played, trump, start_index
# only cards that are the same suit as the first card played matter
same_suit_cards = cards.select { |c| c.suit == first_suit_played }
card_set = trump_cards.empty? ? same_suit_cards : trump_cards
-
+
# now simply get the highest card left
return card_set.max
#iterate_through_list_with_start_index(start_index, card_set) do |card|
# return card if card.value == highest_value
#end
end
-
+
def get_playable_cards first_suit_played, cards
# player can play any card if they are the first to play a card
# or if they only have a single card left
@@ -248,7 +293,7 @@ def player_size_and_nil_check arr, state
# return true or false if we're done bidding
# done bidding if there are the same number of valids bids
# as there are players in the game
- def done_bidding? state
+ def done_bidding? state = game_state
return player_size_and_nil_check(state[:bids], state)
end
@@ -256,81 +301,3 @@ def all_players_played_a_card? state
return player_size_and_nil_check(state[:cards_in_play], state)
end
end
-
-class Card
- include Comparable
-
- SUITS = %w{Spades Hearts Diamonds Clubs}
- attr_accessor :suit, :value, :playable
-
- # create a single card
- def initialize suit, value
- raise 'Invalid card suit' unless SUITS.include? suit
- raise 'Invalid card value' unless value.to_i >= 0 and value.to_i < 13
- @suit = suit
- @value = value
- end
-
- def value_name
- card_names = %w[Two Three Four Five Six Seven Eight Nine Ten Jack Queen King Ace]
- return card_names[@value]
- end
-
- def abbreviated_name
- %w[2 3 4 5 6 7 8 9 10 J Q K A][@value]
- end
-
- def <=> other
- return 1 if other.nil?
- return 0 if @value.nil? and other.value.nil?
- return 1 if other.value.nil?
- return -1 if @value.nil?
- @value.to_i <=> other.value.to_i
- end
-
- def == other
- return false if other.nil?
- return false if (@value.nil? or other.value.nil?) and @value != other.value
- return (@value.to_i == other.value.to_i and @suit == other.suit)
- end
-
- def suit_order other
- if @suit != other.suit
- SUITS.index( @suit ) <=> SUITS.index( other.suit )
- else
- @value <=> other.value
- end
- end
-
- # creates a standard deck of 52 cards, Ace high
- # the '0' represents 2, and '12' is Ace
- def self.get_deck
- cards = []
- SUITS.each do |suit|
- 1..13.times do |i|
- cards.push Card.new(suit, i)
- end
- end
- return cards
- end
-end
-
-class Player
- attr_accessor :name, :inventory
-
- def initialize name
- raise 'Invalid player name' if name.nil? or name.empty?
- @name = name
- @inventory = []
- end
-
- def place_bid
- raise 'TODO: implement me!' # TODO: implement this
- return 0
- end
-
- # play a card from the inventory
- def play_card
- raise 'TODO: implement me!' # TODO: implement this
- end
-end
diff --git a/app/views/games/_form.html.erb b/app/views/games/_form.html.erb
index 44beb73..4acc0df 100644
--- a/app/views/games/_form.html.erb
+++ b/app/views/games/_form.html.erb
@@ -15,10 +15,6 @@
<%= f.label :name %>
<%= f.text_field :name %>
-
+ <% end %>
+ <% else %>
+ Waiting for more players or the creator to start the game.
+ <% end %>
-
+ <% end %>
-
- Your Cards
-
- <%= render "card_pile" %>
-
+
+
+
+ Your Cards
+
+ <%= render "card_pile" %>
-
+
+
-
-
-
You did it!
-
-
-
+
+
+
You did it!
+
+
+
<%= javascript_include_tag "cards.js" %>
diff --git a/config/application.rb b/config/application.rb
index dfc4a8b..e6029f6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -12,6 +12,8 @@ class Application < Rails::Application
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
+ config.autoload_paths += %W(#{config.root}/lib)
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
# config.time_zone = 'Central Time (US & Canada)'
diff --git a/config/routes.rb b/config/routes.rb
index d59edd3..878be0f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,6 @@
Rails.application.routes.draw do
resources :games
+ # TODO these should be member vars
post 'games/add_player'
post 'games/deal'
post 'games/player_action'
diff --git a/lib/card.rb b/lib/card.rb
new file mode 100644
index 0000000..df7c328
--- /dev/null
+++ b/lib/card.rb
@@ -0,0 +1,57 @@
+class Card
+ include Comparable
+
+ SUITS = %w{Spades Hearts Diamonds Clubs}
+ attr_accessor :suit, :value, :playable
+
+ # create a single card
+ def initialize suit, value
+ raise 'Invalid card suit' unless SUITS.include? suit
+ raise 'Invalid card value' unless value.to_i >= 0 and value.to_i < 13
+ @suit = suit
+ @value = value
+ end
+
+ def value_name
+ card_names = %w[Two Three Four Five Six Seven Eight Nine Ten Jack Queen King Ace]
+ return card_names[@value]
+ end
+
+ def abbreviated_name
+ %w[2 3 4 5 6 7 8 9 10 J Q K A][@value]
+ end
+
+ def <=> other
+ return 1 if other.nil?
+ return 0 if @value.nil? and other.value.nil?
+ return 1 if other.value.nil?
+ return -1 if @value.nil?
+ @value.to_i <=> other.value.to_i
+ end
+
+ def == other
+ return false if other.nil?
+ return false if (@value.nil? or other.value.nil?) and @value != other.value
+ return (@value.to_i == other.value.to_i and @suit == other.suit)
+ end
+
+ def suit_order other
+ if @suit != other.suit
+ SUITS.index( @suit ) <=> SUITS.index( other.suit )
+ else
+ @value <=> other.value
+ end
+ end
+
+ # creates a standard deck of 52 cards, Ace high
+ # the '0' represents 2, and '12' is Ace
+ def self.get_deck
+ cards = []
+ SUITS.each do |suit|
+ 1..13.times do |i|
+ cards.push Card.new(suit, i)
+ end
+ end
+ return cards
+ end
+end
diff --git a/todo.md b/todo.md
new file mode 100644
index 0000000..e69de29