diff --git a/README.md b/README.md index ee6f48d..27de954 100644 --- a/README.md +++ b/README.md @@ -36,4 +36,5 @@ Contributors * [Aleks Gorbenko](mailto:aleksedgorbenko@gmail.com) | [GitHub](https://github.com/aleksgorbenko) | [LinkedIn](https://uk.linkedin.com/in/aleks-gorbenko-web-developer) | [Portfolio](https://aleksgorbenko.github.io/) | [Blog](https://aleksgorbenko.com) -* [Ethan He](mailto:ethanhe.dev@gmail.com) | [GitHub](https://github.com/Se7enB2st) +* [Ethan He](mailto:ethanhe.dev@gmail.com) | [GitHub](https://github.com/Se7enB2st) | +[LinkedIn](https://www.linkedin.com/in/ethan-he) diff --git a/app/models/game.rb b/app/models/game.rb index cb258a6..c09d537 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -8,6 +8,7 @@ class Game < ActiveRecord::Base after_save :create_players! def populate! + first_turn! populate_left_black_half! populate_right_black_half! populate_black_pawns! @@ -16,6 +17,10 @@ def populate! populate_right_white_half! end + def end_turn! + increment!(:turn) + end + def check?(color) king = king(color) enemy_pcs(color).each do |p| @@ -96,4 +101,8 @@ def create_piece(type, color, x_pos, y_pos) color: 'black', game_id: id) end end + + def first_turn! + update(turn: 1) + end end diff --git a/app/models/pawn.rb b/app/models/pawn.rb index 01991f4..068586d 100644 --- a/app/models/pawn.rb +++ b/app/models/pawn.rb @@ -6,7 +6,7 @@ class Pawn < Piece # takes place by moving forward. Check, and checkmate logic # are not implemented yet and thus ignored. def valid_move?(x, y) - return true if fwd_diagonal_attack?(x, y) + return true if fwd_diagonal_attack?(x, y) || en_passant_capture?(x, y) moved ? one_fwd_move?(x, y) : first_fwd_move?(x, y) && !fwd_attack?(x, y) end @@ -20,6 +20,49 @@ def promote!(new_type) private + # En Passant Logic: + # 1)target pawn just moved 2 space forward + # 2)only capture within the next turn + # 3)can only capture by opponent's pawn + # 4)opponent pawn must be on the left or right of target piece's y axis + + # En Passant Capture logic + def en_passant_capture?(x, y) + return true if en_passant_possible?(x, y) || en_passant_y_diff?(x, y) + end + + # Return false if victim piece is nil or same color + # Return true if victim piece's y axis difference is equal to 1 + def en_passant_y_diff?(x, y) + victim = last_moved_piece + return false if victim.nil? || victim.color == color + return true if x == victim.x_position && + (y == victim.y_position - 1 || y == victim.y_position + 1) + end + + # Return false if last move piece is nil (Start of game) + # Return false last moved piece is not pawn or its y axis difference is !2 + # Check both both adjacent side of pawn + # If any have valid En Passant pawn piece, return true + def en_passant_possible?(x, y) + return false if last_moved_piece.nil? + return false unless last_moved_piece.y_distance(y) == 2 && + type == 'Pawn' + [valid_en_passant_pawn?(x, y, true), valid_en_passant_pawn?(x, y, false)] + .include?(true) + end + + # Check for any adjacent piece + # Return false if none if found + # Return true if opponent's pawn is found + def valid_en_passant_pawn?(x, y, adjacent) + adjacent_piece = occupant_piece(x + (adjacent ? 1 : -1), y) + return false if adjacent_piece.nil? + return true if adjacent_piece.type == 'Pawn' && + adjacent_piece.color != color + false + end + # Returns true if the coordinates provided are 1 tile forward # from the piece's origin and no capture occured. def one_fwd_move?(x, y) @@ -63,4 +106,9 @@ def fwd_diagonal_attack?(x, y) y_position - 1 end end + + # Find the last moved piece by subtracting current move number - 1 + def last_moved_piece + game.pieces.find_by(turn_last_moved: game.turn - 1) + end end diff --git a/app/models/piece.rb b/app/models/piece.rb index c5cb19c..1de86f3 100644 --- a/app/models/piece.rb +++ b/app/models/piece.rb @@ -24,14 +24,22 @@ def move!(x, y) return false unless enemy?(victim) capture!(victim) end - update(x_position: x, y_position: y, moved: true) + update(x_position: x, y_position: y, moved: true, + turn_last_moved: game.turn) + game.end_turn! # if checked_king(white_king) # elsif checked_king(black_king) # end - true end + # Compares a piece's y_position with the + # coordinate provided and returns the + # distance between the two. + def y_distance(new_y_coordinate) + (y_position - new_y_coordinate).abs + end + # All validation assumes white player is on the # 6-7 rows of the array, and black player is on # 0-1 rows of the array. @@ -61,13 +69,6 @@ def x_distance(new_x_coordinate) (x_position - new_x_coordinate).abs end - # Compares a piece's y_position with the - # coordinate provided and returns the - # distance between the two. - def y_distance(new_y_coordinate) - (y_position - new_y_coordinate).abs - end - # Returns true if the coordinates provided # are different from the piece's starting # position. diff --git a/config/application.rb b/config/application.rb index f7aa353..f549a2a 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,3 +1,4 @@ +#Application.rb require File.expand_path('../boot', __FILE__) require 'rails/all' diff --git a/config/environments/production.rb b/config/environments/production.rb index 6297e09..04ee300 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,5 +1,7 @@ +# Production.rb StoicChess::Application.configure do - config.action_mailer.default_url_options = { host: 'stoicchess-teamstoic.herokuapp.com' } + config.action_mailer.default_url_options = + { host: 'stoicchess-teamstoic.herokuapp.com' } # Settings specified here will take precedence over those in # config/application.rb. diff --git a/config/environments/test.rb b/config/environments/test.rb index cc312e1..7fa3a2c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,3 +1,4 @@ +#test.rb StoicChess::Application.configure do # Settings specified here will take precedence over those in # config/application.rb. @@ -19,7 +20,8 @@ # Configure static asset server for tests with Cache-Control for performance. config.public_file_server.enabled = true - config.public_file_server.headers = { 'Cache-Control' => 'public, max-age=3600' } + config.public_file_server.headers = + { 'Cache-Control' => 'public, max-age=3600' } # Show full error reports and disable caching. config.consider_all_requests_local = true diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb index 59385cd..573be5b 100644 --- a/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -1,7 +1,9 @@ # Be sure to restart your server when you modify this file. -# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# You can add backtrace silencers for libraries that you're using +# but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } -# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# You can also remove all the silencers if you're trying to debug a problem +# that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index a10c673..5ac41f2 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -33,7 +33,9 @@ # :subdomain], so for authenticating a user, both parameters # are required. # - # Remember that those parameters are used only when authenticating and not when retrieving from session. If you need permissions, you should implement that in a before filter. + # Remember that those parameters are used only when authenticating + # and not when retrieving from session. If you need permissions, + # you should implement that in a before filter. # # You can also supply a hash where the value is a boolean # determining whether diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index f1d9d1c..4ec6070 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -9,4 +9,7 @@ # Make sure your secret_key_base is kept private # if you're sharing your code publicly. -StoicChess::Application.config.secret_key_base = 'b02af4ed78836e0a007c890e7f38df540669ad77d076cffe3899647bfbfd000e5e5080fc5841fff22763a5ce871d3f4f077b4c2693f964cdd67c0da25d6fad29' +StoicChess::Application.config.secret_key_base = +'b02af4ed78836e0a007c890e7f38df540669ad77d076cff +e3899647bfbfd000e5e5080fc5841fff22763a5ce871d3f4 +f077b4c2693f964cdd67c0da25d6fad29' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index e6bb783..f1bbec8 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,3 +1,4 @@ # Be sure to restart your server when you modify this file. -StoicChess::Application.config.session_store :cookie_store, key: '_StoicChess_session' +StoicChess::Application.config.session_store :cookie_store, key: + '_StoicChess_session' diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb index 33725e9..8aa4151 100644 --- a/config/initializers/wrap_parameters.rb +++ b/config/initializers/wrap_parameters.rb @@ -3,7 +3,8 @@ # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. -# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +# Enable parameter wrapping for JSON. You can disable this by setting +# :format to an empty array. ActiveSupport.on_load(:action_controller) do wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end diff --git a/db/migrate/20160629091417_devise_create_users.rb b/db/migrate/20160629091417_devise_create_users.rb index 8873c8c..ff45f41 100644 --- a/db/migrate/20160629091417_devise_create_users.rb +++ b/db/migrate/20160629091417_devise_create_users.rb @@ -1,3 +1,4 @@ +#devise auto migration class DeviseCreateUsers < ActiveRecord::Migration def change create_table(:users) do |t| diff --git a/db/migrate/20160712160707_create_games.rb b/db/migrate/20160712160707_create_games.rb index b83e64f..05860a3 100644 --- a/db/migrate/20160712160707_create_games.rb +++ b/db/migrate/20160712160707_create_games.rb @@ -1,9 +1,10 @@ +# game model class CreateGames < ActiveRecord::Migration def change create_table :games do |t| t.string :winning_player t.string :game_status - + t.integer :counter t.integer :turn t.integer :user_id diff --git a/db/migrate/20160719140518_add_moved_to_pieces.rb b/db/migrate/20160719140518_add_moved_to_pieces.rb index b9520ae..e9f1b07 100644 --- a/db/migrate/20160719140518_add_moved_to_pieces.rb +++ b/db/migrate/20160719140518_add_moved_to_pieces.rb @@ -1,3 +1,4 @@ +# add moved to pieces table class AddMovedToPieces < ActiveRecord::Migration[5.0] def change add_column :pieces, :moved, :boolean, default: false diff --git a/db/migrate/20160729214941_add_move_number_to_games.rb b/db/migrate/20160729214941_add_move_number_to_games.rb new file mode 100644 index 0000000..8b0a63e --- /dev/null +++ b/db/migrate/20160729214941_add_move_number_to_games.rb @@ -0,0 +1,5 @@ +class AddMoveNumberToGames < ActiveRecord::Migration[5.0] + def change + add_column :games, :move_number, :integer + end +end diff --git a/db/migrate/20160730005057_add_en_passant_to_games.rb b/db/migrate/20160730005057_add_en_passant_to_games.rb new file mode 100644 index 0000000..2c712d5 --- /dev/null +++ b/db/migrate/20160730005057_add_en_passant_to_games.rb @@ -0,0 +1,5 @@ +class AddEnPassantToGames < ActiveRecord::Migration[5.0] + def change + add_column :games, :en_passant, :boolean, default: false + end +end diff --git a/db/migrate/20160730144006_change_turn_type_to_string.rb b/db/migrate/20160730144006_change_turn_type_to_string.rb new file mode 100644 index 0000000..5f1ed28 --- /dev/null +++ b/db/migrate/20160730144006_change_turn_type_to_string.rb @@ -0,0 +1,5 @@ +class ChangeTurnTypeToString < ActiveRecord::Migration[5.0] + def change + change_column :games, :turn, :string + end +end diff --git a/db/migrate/20160730194959_add_last_moved_piece_to_pieces_table.rb b/db/migrate/20160730194959_add_last_moved_piece_to_pieces_table.rb new file mode 100644 index 0000000..8d83960 --- /dev/null +++ b/db/migrate/20160730194959_add_last_moved_piece_to_pieces_table.rb @@ -0,0 +1,5 @@ +class AddLastMovedPieceToPiecesTable < ActiveRecord::Migration[5.0] + def change + add_column :pieces, :last_moved_piece, :integer + end +end diff --git a/db/migrate/20160809143920_remove_en_passant_from_game.rb b/db/migrate/20160809143920_remove_en_passant_from_game.rb new file mode 100644 index 0000000..6641e88 --- /dev/null +++ b/db/migrate/20160809143920_remove_en_passant_from_game.rb @@ -0,0 +1,5 @@ +class RemoveEnPassantFromGame < ActiveRecord::Migration[5.0] + def change + remove_column :games, :en_passant, :boolean + end +end diff --git a/db/migrate/20160809145214_change_turn_from_games.rb b/db/migrate/20160809145214_change_turn_from_games.rb new file mode 100644 index 0000000..3b2ae0b --- /dev/null +++ b/db/migrate/20160809145214_change_turn_from_games.rb @@ -0,0 +1,6 @@ +class ChangeTurnFromGames < ActiveRecord::Migration[5.0] + def change + remove_column :games, :turn, :integer + remove_column :games, :move_number, :integer + end +end diff --git a/db/migrate/20160809150350_add_turn_to_games.rb b/db/migrate/20160809150350_add_turn_to_games.rb new file mode 100644 index 0000000..e8e3f9d --- /dev/null +++ b/db/migrate/20160809150350_add_turn_to_games.rb @@ -0,0 +1,5 @@ +class AddTurnToGames < ActiveRecord::Migration[5.0] + def change + add_column :games, :turn, :integer + end +end diff --git a/db/migrate/20160809151435_change_last_moved_piece_from_pieces.rb b/db/migrate/20160809151435_change_last_moved_piece_from_pieces.rb new file mode 100644 index 0000000..2823704 --- /dev/null +++ b/db/migrate/20160809151435_change_last_moved_piece_from_pieces.rb @@ -0,0 +1,5 @@ +class ChangeLastMovedPieceFromPieces < ActiveRecord::Migration[5.0] + def change + rename_column :pieces, :last_moved_piece, :turn_last_moved + end +end diff --git a/db/schema.rb b/db/schema.rb index 99c1b0e..d01aa5c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160806192848) do +ActiveRecord::Schema.define(version: 20160809151435) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -19,9 +19,9 @@ t.string "winning_player" t.string "game_status" t.integer "counter" - t.integer "turn" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "turn" end create_table "pieces", force: :cascade do |t| @@ -31,11 +31,12 @@ t.integer "y_position" t.integer "game_id" t.integer "player_id" - t.boolean "captured", default: false - t.boolean "checkmate", default: false + t.boolean "captured", default: false + t.boolean "checkmate", default: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "moved", default: false + t.boolean "moved", default: false + t.integer "turn_last_moved" end create_table "players", force: :cascade do |t| diff --git a/spec/models/game_spec.rb b/spec/models/game_spec.rb index 5a22985..6af6cf3 100644 --- a/spec/models/game_spec.rb +++ b/spec/models/game_spec.rb @@ -7,18 +7,40 @@ it 'should give us 32 pieces upon board population' do expect(game.pieces.count).to eq 32 end + it 'should give me the last x position of population' do expect(game.pieces.last.x_position).to eq 7 end + it 'should give me the last y position of population' do expect(game.pieces.last.y_position).to eq 7 end + it 'should give me the last piece of the population as the King' do expect(game.pieces.last.type).to eq 'Rook' end + it 'should give me the last pieces color' do expect(game.pieces.last.color).to eq 'white' end + + it 'should show turn as 1' do + expect(game.turn).to eq 1 + end + + describe 'end_turn!' do + it 'should increment turn by 1 once the turn ended' do + game.end_turn! + + expect(game.turn).to eq 2 + end + end + + describe 'first_turn' do + it 'should show turn as 1' do + expect(game.turn).to eq 1 + end + end end describe 'check?' do diff --git a/spec/models/pawn_spec.rb b/spec/models/pawn_spec.rb index face078..6a1621a 100644 --- a/spec/models/pawn_spec.rb +++ b/spec/models/pawn_spec.rb @@ -1,5 +1,4 @@ require 'rails_helper' - RSpec.describe Pawn, type: :model do let(:game) { FactoryGirl.create(:game, :populated) } let(:pawn) do @@ -10,6 +9,7 @@ y_position: 6 ) end + let(:moved_pawn) do game.pieces.create( type: 'Pawn', @@ -20,9 +20,28 @@ ) end + let(:white_pawn) do + game.pieces.create( + type: 'Pawn', + color: 'white', + x_position: 7, + y_position: 4 + ) + end + + let(:black_pawn) do + game.pieces.find_by( + type: 'Pawn', + color: 'black', + x_position: 6, + y_position: 1 + ) + end + describe 'creation' do it 'should create a white pawn' do pawn = FactoryGirl.create(:pawn, color: 'white') + expect(pawn.type).to eq('Pawn') end @@ -95,6 +114,7 @@ x_position: 5, y_position: 5 ) + expect(pawn.move!(5, 4)).to eq false expect(pawn.x_position).to eq 5 expect(pawn.y_position).to eq 6 @@ -108,6 +128,7 @@ x_position: 5, y_position: 5 ) + expect(pawn.move!(5, 5)).to eq false expect(pawn.x_position).to eq 5 expect(pawn.y_position).to eq 6 @@ -156,5 +177,46 @@ expect(moved_pawn.y_position).to eq 4 expect(moved_pawn.moved).to eq true end + + it 'should return false if no piece is found adjacent to the piece' do + expect(pawn.move!(3, 4)).to eq false + end + + # Check edge case at the start of game + it 'should return false if last moved piece is nil' do + expect(pawn.move!(3, 4)).to eq false + end + + it 'should return true if opponent pawn move 2 space in last turn' do + white_pawn.move!(7, 3) + black_pawn.move!(6, 3) + + expect(white_pawn.move!(6, 2)).to eq true + end + + it 'should return false if adjacent pawn is the same color' do + another_white_pawn = game.pieces.create( + type: 'Pawn', + color: 'white', + x_position: 6, + y_position: 5 + ) + + white_pawn.move!(7, 3) + another_white_pawn.move!(6, 3) + + expect(white_pawn.move!(6, 2)).to eq false + end + + it 'should return false if adjacent piece is not pawn' do + game.pieces.create( + type: 'Rook', + color: 'black', + x_position: 6, + y_position: 4 + ) + + expect(white_pawn.move!(6, 3)).to eq false + end end end diff --git a/spec/models/piece_spec.rb b/spec/models/piece_spec.rb index b70b4f7..7f5a615 100644 --- a/spec/models/piece_spec.rb +++ b/spec/models/piece_spec.rb @@ -31,6 +31,7 @@ expect(victim.x_position).to eq nil expect(victim.y_position).to eq nil expect(victim.captured).to eq true + expect(game.turn).to eq 2 end it 'should return false on a move against a friendly piece' do @@ -42,6 +43,7 @@ victim.reload expect(victim.x_position).to eq 3 expect(victim.y_position).to eq 6 + expect(game.turn).to eq 1 expect(victim.captured).to eq false end end