diff --git a/app/assets/javascripts/battletype.js b/app/assets/javascripts/battletype.js index da91c23..df79323 100644 --- a/app/assets/javascripts/battletype.js +++ b/app/assets/javascripts/battletype.js @@ -12,6 +12,7 @@ (function () { this.Battletype = { attacking: true, + running: true, _eventsRelay: Object.create(PS2EventRelay), @@ -108,6 +109,7 @@ } break; case "game_won": + this.running = false; this._logs.displayMessage(payload.code); if (payload.player_id == this.playerId) { @@ -139,6 +141,8 @@ } }, scanForShip: function (text) { + if (!this.running) { return } + if (!this.attacking) { var target = this.$combatZone.find("[id^='" + Ship._shipIdFromWord(text) + "']"); if (target) { @@ -150,6 +154,8 @@ } }, transmitEntry: function (entry) { + if (!this.running) { return } + this.attacking ? this._transmitAttack(entry) : this._transmitDefense(entry); this._stdin.reset(); }, @@ -165,6 +171,8 @@ $(this.defenseFrequency).trigger("submit.rails"); }, _transmitBombing: function (ship) { + if (!this.running) { return } + ship.className += " leaving"; // The bombing ship leaves the scene this.mothership.hit = true; // The mothership takes a hit @@ -172,6 +180,8 @@ $(this.bombingFrequency).trigger("submit.rails"); }, switchMode: function () { + if (!this.running) { return } + Battletype.attacking = !Battletype.attacking; if (Battletype.attacking) { diff --git a/app/assets/javascripts/battletype/logs.js b/app/assets/javascripts/battletype/logs.js index e9a3b5d..c16a1b3 100644 --- a/app/assets/javascripts/battletype/logs.js +++ b/app/assets/javascripts/battletype/logs.js @@ -27,16 +27,19 @@ "attack": { "english_word": "Must be an English word", "long_enough": "Word is not long enough", + "not_running": "Game is not running!", "unique_case_insensitive_word": "Word must be unique" }, "bomb": { "attacked_player_ship": "You tried to bomb your mothership!!", + "not_running": "Game is not running!", "ship_not_found": "Ship to bomb not found" }, "defense": { "already_destroyed": "Ship is already destroyed!", "bomb_already_dropped": "Bomb already dropped", "player_ship": "It's your own ship!", + "not_running": "Game is not running!", "ship_not_found": "Ship not found", "wrong_case": "Words are case sensitive" } diff --git a/app/dispatches/attacks/launch.rb b/app/dispatches/attacks/launch.rb index 4e8cd7d..3fbd525 100644 --- a/app/dispatches/attacks/launch.rb +++ b/app/dispatches/attacks/launch.rb @@ -28,7 +28,11 @@ def call private def failed_payload - { code: 'failed_attack', player_id: player.id, word: word, error_codes: attack.errors[:word] } + { code: 'failed_attack', player_id: player.id, word: word, error_codes: error_codes } + end + + def error_codes + attack.errors.messages.values.flatten end def save_word diff --git a/app/dispatches/bombs/drop.rb b/app/dispatches/bombs/drop.rb index 855c92d..bc4cfcd 100644 --- a/app/dispatches/bombs/drop.rb +++ b/app/dispatches/bombs/drop.rb @@ -46,10 +46,14 @@ def failed_payloads code: 'failed_bombing', player_id: player.id, word: word, - error_codes: bomb.errors[:word] + error_codes: error_codes }] end + def error_codes + bomb.errors.messages.values.flatten + end + def game_won? attacked_player.life == 0 end diff --git a/app/dispatches/defenses/launch.rb b/app/dispatches/defenses/launch.rb index 2bbd099..87d62ce 100644 --- a/app/dispatches/defenses/launch.rb +++ b/app/dispatches/defenses/launch.rb @@ -32,7 +32,11 @@ def shot_down_ship end def failed_payload - { code: 'failed_defense', player_id: player.id, word: word, error_codes: defense.errors[:word] } + { code: 'failed_defense', player_id: player.id, word: word, error_codes: error_codes } + end + + def error_codes + defense.errors.messages.values.flatten end def successful_payload diff --git a/app/models/attack.rb b/app/models/attack.rb index fd6cd5e..5b5b44e 100644 --- a/app/models/attack.rb +++ b/app/models/attack.rb @@ -2,7 +2,7 @@ class Attack include ActiveModel::Validations MIN_WORD_SIZE = 2 - validate :unique_case_insensitive_word?, :english_word?, :long_enough? + validate :game_running?, :unique_case_insensitive_word?, :english_word?, :long_enough? private attr_reader :game, :word, :player @@ -30,6 +30,12 @@ def initialize(game:, word:, player:) private + def game_running? + unless game.running? + errors.add(:game, "not_running") + end + end + def unique_case_insensitive_word? unless Word.where(game: game).where('LOWER(value) = LOWER(?)', word).empty? errors.add(:word, "unique_case_insensitive_word") diff --git a/app/models/bomb.rb b/app/models/bomb.rb index b434b6b..2b0fba0 100644 --- a/app/models/bomb.rb +++ b/app/models/bomb.rb @@ -1,7 +1,7 @@ class Bomb include ActiveModel::Validations - validate :ship_exists, :not_matching_attacked_player_ship + validate :game_running, :ship_exists, :not_matching_attacked_player_ship private attr_reader :player, :word @@ -18,6 +18,12 @@ def ship private + def game_running + unless player.game.state == 'running' + errors.add(:game, "not_running") + end + end + def not_matching_attacked_player_ship matching = ships.where.not(player_id: player.id).where(words: { value: word }).exists? diff --git a/app/models/defense.rb b/app/models/defense.rb index 12cae1c..b7752d5 100644 --- a/app/models/defense.rb +++ b/app/models/defense.rb @@ -1,7 +1,12 @@ class Defense include ActiveModel::Validations - validate :ship_exists, :matching_case_sensitive, :not_matching_own_ship, :ship_not_destroyed_yet, :mission_not_accomplished_yet + validate :game_running, + :ship_exists, + :matching_case_sensitive, + :not_matching_own_ship, + :ship_not_destroyed_yet, + :mission_not_accomplished_yet private attr_reader :player, :perfect_typing, :word @@ -35,6 +40,12 @@ def unlocked_strike private + def game_running + unless player.game.state == 'running' + errors.add(:game, "not_running") + end + end + def ship_not_destroyed_yet if ship&.state == 'destroyed' errors.add(:word, "already_destroyed") diff --git a/spec/dispatches/attacks/launch_spec.rb b/spec/dispatches/attacks/launch_spec.rb index 41b44a5..5a4cf47 100644 --- a/spec/dispatches/attacks/launch_spec.rb +++ b/spec/dispatches/attacks/launch_spec.rb @@ -2,13 +2,16 @@ RSpec.describe "Attacks::Launch", type: :dispatch do subject(:dispatch) { Attacks::Launch } - let(:game) { Game.create! } + let(:game) { Game.create!(state: 'running') } let(:player) { Player.create!(game: game, nickname: "Rico") } describe ".call" do context 'when word is valid' do let(:word) { 'curry' } - before { allow_words(word) } + + before :each do + allow_words(word) + end it 'saves the Word' do expect { dispatch.call(player: player, word: word) }.to change { Word.where(value: word, game: game).count }.from(0).to(1) @@ -64,6 +67,7 @@ let(:word) { 'duplicate' } before :each do + allow_words(word) Word.create!(game: game, value: word) end @@ -82,7 +86,7 @@ end it "returns a payload with the invalid word and the attacker's id" do - expect(dispatch.call(player: player, word: word)).to include(code: 'failed_attack', word: "duplicate", player_id: player.id, error_codes: ["unique_case_insensitive_word", "english_word"]) + expect(dispatch.call(player: player, word: word)).to include(code: 'failed_attack', word: "duplicate", player_id: player.id, error_codes: ["unique_case_insensitive_word"]) end end end diff --git a/spec/dispatches/bombs/drop_spec.rb b/spec/dispatches/bombs/drop_spec.rb index b66e4c5..1e2d3a1 100644 --- a/spec/dispatches/bombs/drop_spec.rb +++ b/spec/dispatches/bombs/drop_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "Bombs::Drop", type: :dispatch do subject(:dispatch) { Bombs::Drop } - let(:game) { Game.create! } + let(:game) { Game.create!(state: 'running') } let(:attacked_player) { Player.create!(game: game, life: 10) } let(:attacked_word) { Word.create!(game: game, value: 'go') } let(:attacker) { Player.create!(game: game) } diff --git a/spec/dispatches/defenses/launch_spec.rb b/spec/dispatches/defenses/launch_spec.rb index 92f9fe7..9cc61e9 100644 --- a/spec/dispatches/defenses/launch_spec.rb +++ b/spec/dispatches/defenses/launch_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "Defenses::Launch", type: :dispatch do subject(:dispatch) { Defenses::Launch } - let(:game) { Game.create! } + let(:game) { Game.create!(state: 'running') } let(:attacker) { Player.create!(game: game) } let(:attacker_ship) { attacker.ships.last } let(:attacker_word) { Word.create!(value: 'attacker', game: game) } diff --git a/spec/models/attack_spec.rb b/spec/models/attack_spec.rb index ffa5dbd..010783e 100644 --- a/spec/models/attack_spec.rb +++ b/spec/models/attack_spec.rb @@ -5,7 +5,11 @@ let(:game) { Game.create! } let(:word) { 'attack' } let(:player) { Player.new } - before { allow_words("attack") } + + before do + allow_words("attack") + game.running! + end describe '.reward_for' do describe 'word between 0 and 1 letters' do @@ -97,5 +101,16 @@ it { expect(Attack.new(game: game, word: "AniMal", player: player).valid?).to be true } it { expect(Attack.new(game: game, word: "animal12", player: player).valid?).to be false } end + + context 'when game is finished' do + before :each do + allow_words("finished") + game.finished! + end + + it 'returns false' do + expect(attack.valid?).to eq(false) + end + end end end diff --git a/spec/models/bomb_spec.rb b/spec/models/bomb_spec.rb index c6be651..bcf16f1 100644 --- a/spec/models/bomb_spec.rb +++ b/spec/models/bomb_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "Bomb", type: :model do subject(:bomb) { Bomb.new(player: attacker, word: word) } - let(:game) { Game.create! } + let(:game) { Game.create!(state: 'running') } let(:attacker) { Player.create!(game: game) } let(:attacker_word) { Word.create!(value: 'BOMB', game: game) } let(:perfect_typing) { '1' } @@ -66,5 +66,17 @@ expect(bomb.valid?).to eq(true) end end + + context "when game is finished" do + let(:word) { 'BOMB' } + + before :each do + game.update(state: 'finished') + end + + it 'returns false' do + expect(bomb.valid?).to eq(false) + end + end end end diff --git a/spec/models/defense_spec.rb b/spec/models/defense_spec.rb index 4705399..ffa25b3 100644 --- a/spec/models/defense_spec.rb +++ b/spec/models/defense_spec.rb @@ -3,7 +3,7 @@ RSpec.describe "Defense", type: :model do subject(:defense) { Defense.new(player: player, word: word, perfect_typing: perfect_typing) } - let(:game) { Game.create! } + let(:game) { Game.create!(state: 'running') } let(:attacker) { Player.create!(game: game) } let(:attacker_word) { Word.create!(value: 'HaCkeR', game: game) } let(:perfect_typing) { '1' } @@ -162,5 +162,15 @@ expect(defense.valid?).to eq(true) end end + + context 'when game is finished' do + before :each do + game.update(state: 'finished') + end + + it 'returns false' do + expect(defense.valid?).to eq(false) + end + end end end diff --git a/spec/requests/attacks_spec.rb b/spec/requests/attacks_spec.rb index a734cf1..23749d4 100644 --- a/spec/requests/attacks_spec.rb +++ b/spec/requests/attacks_spec.rb @@ -5,6 +5,7 @@ let(:player) { game.players.create!(nickname: "Rico") } before :each do + game.running! allow_any_instance_of(ApplicationController).to receive(:current_player).and_return(player) end @@ -54,6 +55,7 @@ context 'when provided word has already been played with a different case' do before :each do + allow_words("battletype") game.words.create!(value: 'BattleType') end @@ -65,7 +67,25 @@ it 'broadcasts an error message' do allow(ActionCable.server).to receive(:broadcast) post "/attacks", params: { word: 'battletype' } - expect(ActionCable.server).to have_received(:broadcast).with(anything, code: 'failed_attack', player_id: player.id, word: 'battletype', error_codes: ["unique_case_insensitive_word", "english_word"]) + expect(ActionCable.server).to have_received(:broadcast).with(anything, code: 'failed_attack', player_id: player.id, word: 'battletype', error_codes: ["unique_case_insensitive_word"]) + end + end + + context 'when game is finished' do + before :each do + allow_words("finished") + game.update(state: 'finished') + end + + it 'returns 200 HTTP status' do + post "/attacks", params: { word: 'finished' } + expect(response).to have_http_status(200) + end + + it 'broadcasts an error message' do + allow(ActionCable.server).to receive(:broadcast) + post "/attacks", params: { word: 'finished' } + expect(ActionCable.server).to have_received(:broadcast).with(anything, code: 'failed_attack', word: 'finished', player_id: player.id, error_codes: ['not_running'] ) end end end diff --git a/spec/requests/bombings_spec.rb b/spec/requests/bombings_spec.rb index 1f32ea2..8945eab 100644 --- a/spec/requests/bombings_spec.rb +++ b/spec/requests/bombings_spec.rb @@ -1,8 +1,7 @@ require "rails_helper" RSpec.describe "Bombings", type: :request do - let(:game) { Game.create!(name: 'Starship Battle') } - let(:game) { Game.create! } + let(:game) { Game.create!(name: 'Starship Battle', state: 'running') } let(:attacked_player) { Player.create!(game: game, life: 10) } let(:attacked_word) { Word.create!(game: game, value: 'go') } let(:attacker) { Player.create!(game: game, life: 7) } @@ -168,5 +167,31 @@ ) end end + + context "when game is finished" do + before :each do + game.update(state: 'finished') + end + + it "returns 200 HTTP status" do + post "/bombings", params: { word: 'BOMB' } + expect(response).to have_http_status(200) + end + + it 'broadcasts a failed bombing payload' do + allow(ActionCable.server).to receive(:broadcast) + post "/bombings", params: { word: 'BOMB' } + + expect(ActionCable.server).to have_received(:broadcast).with( + anything, + { + code: 'failed_bombing', + player_id: attacker.id, + word: 'BOMB', + error_codes: ['not_running'] + } + ) + end + end end end diff --git a/spec/requests/defenses_spec.rb b/spec/requests/defenses_spec.rb index cd937e6..2fa72c6 100644 --- a/spec/requests/defenses_spec.rb +++ b/spec/requests/defenses_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe "Defenses", type: :request do - let(:game) { Game.create!(name: 'Starship Battle') } + let(:game) { Game.create!(name: 'Starship Battle', state: 'running') } let(:attacker) { game.players.create! } let(:player) { game.players.create! } @@ -201,5 +201,32 @@ ) end end + + context 'when game is finished' do + before :each do + attacker_word = Word.create!(value: 'finished') + attacker.ships.create!(word: attacker_word) + game.update(state: 'finished') + end + + it "returns 200 HTTP status" do + post "/defenses", params: { word: 'finished', perfect_typing: '0' } + expect(response).to have_http_status(200) + end + + it 'broadcasts a failed defense payload' do + allow(ActionCable.server).to receive(:broadcast) + post "/defenses", params: { word: 'finished' } + expect(ActionCable.server).to have_received(:broadcast).with( + anything, + { + code: 'failed_defense', + player_id: player.id, + word: 'finished', + error_codes: ['not_running'] + } + ) + end + end end end