From e9d243536c1b07780e1e6050e24ebe79321cd39f Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Thu, 18 Feb 2016 21:33:52 -0800 Subject: [PATCH 01/13] Toggle automatch flag --- slack-gamebot/commands.rb | 1 + slack-gamebot/commands/automatch.rb | 30 +++++++++++++++++++++++++++++ slack-gamebot/models/user.rb | 1 + 3 files changed, 32 insertions(+) create mode 100644 slack-gamebot/commands/automatch.rb diff --git a/slack-gamebot/commands.rb b/slack-gamebot/commands.rb index c4405b3c..2bd59776 100644 --- a/slack-gamebot/commands.rb +++ b/slack-gamebot/commands.rb @@ -1,4 +1,5 @@ require 'slack-gamebot/commands/accept' +require 'slack-gamebot/commands/automatch' require 'slack-gamebot/commands/cancel' require 'slack-gamebot/commands/challenge' require 'slack-gamebot/commands/challenges' diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb new file mode 100644 index 00000000..6c50ed51 --- /dev/null +++ b/slack-gamebot/commands/automatch.rb @@ -0,0 +1,30 @@ +module SlackGamebot + module Commands + class Automatch < SlackRubyBot::Commands::Base + def self.call(client, data, match) + challenger = ::User.find_create_or_update_by_slack_id!(client, data.user) + + case match['expression'] + when 'on' + challenger.automatch = true + when 'off' + challenger.automatch = false + when nil + challenger.automatch = !challenger.automatch + end + + challenger.save! + + if challenger.automatch + state = 'on' + gif_word = 'ready' + else + state = 'off' + gif_word = 'leave' + end + client.say(channel: data.channel, text: "Automatch is #{state} for #{challenger.user_name}", gif: gif_word) + logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{state}" + end + end + end +end diff --git a/slack-gamebot/models/user.rb b/slack-gamebot/models/user.rb index 6da6f12b..137a74b6 100644 --- a/slack-gamebot/models/user.rb +++ b/slack-gamebot/models/user.rb @@ -11,6 +11,7 @@ class User field :tau, type: Float, default: 0 field :rank, type: Integer field :captain, type: Boolean, default: false + field :automatch, type: Boolean, default: false belongs_to :team, index: true validates_presence_of :team From 4c9c3ea3874e854acdf81ed199f0fd4c65c86228 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Thu, 18 Feb 2016 21:46:46 -0800 Subject: [PATCH 02/13] Count users ready to play --- slack-gamebot/commands/automatch.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index 6c50ed51..5dee9760 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -22,7 +22,9 @@ def self.call(client, data, match) state = 'off' gif_word = 'leave' end - client.say(channel: data.channel, text: "Automatch is #{state} for #{challenger.user_name}", gif: gif_word) + + automatch_count = User.where(automatch: true).count + client.say(channel: data.channel, text: "Automatch is #{state} for #{challenger.user_name} (#{automatch_count} users ready to play!)", gif: gif_word) logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{state}" end end From daf5877ffc69a19474d4f72abcd31eac68becdd6 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Thu, 18 Feb 2016 23:00:54 -0800 Subject: [PATCH 03/13] Setup automatch and add tests --- slack-gamebot/commands/automatch.rb | 29 ++++++- spec/slack-gamebot/commands/automatch_spec.rb | 85 +++++++++++++++++++ 2 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 spec/slack-gamebot/commands/automatch_spec.rb diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index 5dee9760..55aebc0f 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -11,6 +11,8 @@ def self.call(client, data, match) challenger.automatch = false when nil challenger.automatch = !challenger.automatch + else + fail "Invalid automatch argument #{match['expression']}" end challenger.save! @@ -20,12 +22,33 @@ def self.call(client, data, match) gif_word = 'ready' else state = 'off' - gif_word = 'leave' + gif_word = 'gone' end - automatch_count = User.where(automatch: true).count - client.say(channel: data.channel, text: "Automatch is #{state} for #{challenger.user_name} (#{automatch_count} users ready to play!)", gif: gif_word) logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{state}" + + automatch_users = User.where(automatch: true).order(elo: :asc).limit(4) + if automatch_users.count == 4 + challenge = ::Challenge.create!( + team: client.owner, + channel: data.channel, + created_by: challenger, + updated_by: automatch_users[1], + challengers: [automatch_users[0], automatch_users[3]], + challenged: [automatch_users[1], automatch_users[2]], + state: ChallengeState::ACCEPTED + ) + + automatch_users.each do |user| + user.automatch = false + user.save! + end + + client.say(channel: data.channel, text: "Automatch: #{challenge.challengers.map(&:user_name).and} vs #{challenge.challenged.map(&:user_name).and}!", gif: 'challenge') + logger.info "CHALLENGE: #{client.owner} - #{challenge}" + else + client.say(channel: data.channel, text: "Automatch is #{state} for #{challenger.user_name} (#{automatch_users.count} users ready to play!)", gif: gif_word) + end end end end diff --git a/spec/slack-gamebot/commands/automatch_spec.rb b/spec/slack-gamebot/commands/automatch_spec.rb new file mode 100644 index 00000000..8621e504 --- /dev/null +++ b/spec/slack-gamebot/commands/automatch_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe SlackGamebot::Commands::Automatch, vcr: { cassette_name: 'user_info' } do + let!(:team) { Fabricate(:team) } + let(:app) { SlackGamebot::Server.new(team: team) } + let(:user1) { Fabricate(:user, user_name: 'username1', elo: 1) } + let(:user2) { Fabricate(:user, user_name: 'username2', elo: 2) } + let(:user3) { Fabricate(:user, user_name: 'username3', elo: 3) } + let(:user4) { Fabricate(:user, user_name: 'username4', elo: 4) } + let(:opponent) { Fabricate(:user) } + + it 'sets automatch flag to true when user turns it on' do + expect(message: "#{SlackRubyBot.config.user} automatch on", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) + + user1.reload + expect(user1.automatch).to be(true) + end + + it 'sets automatch flag to false when user turns it off' do + expect(message: "#{SlackRubyBot.config.user} automatch off", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is off for username (0 users ready to play!)' + ) + + user1.reload + expect(user1.automatch).to be(false) + end + + it 'toggles automatch flag from off to on when there is no argument' do + user1.automatch = false + user1.save! + + expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) + + user1.reload + expect(user1.automatch).to be(true) + end + + it 'toggles automatch flag from on to off when there is no argument' do + user1.automatch = true + user1.save! + + expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is off for username (0 users ready to play!)' + ) + + user1.reload + expect(user1.automatch).to be(false) + end + + it 'creates a doubles match when four users have automatch on' do + [user1, user2, user3].each do |user| + user.automatch = true + user.save! + end + + expect do + expect(message: "#{SlackRubyBot.config.user} automatch", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch: username1 and username vs username2 and username3!' + ) + end.to change(Challenge, :count).by(1) + end + + it 'matches top and bottom elo vs middle two elo' do + [user1, user2, user3].each do |user| + user.automatch = true + user.save! + end + + expect(message: "#{SlackRubyBot.config.user} automatch", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch: username1 and username vs username2 and username3!' + ) + + challenge = Challenge.last + expect(challenge.challengers.length).to eq(2) + expect(challenge.challengers.include?(user1)).to eq(true) + expect(challenge.challengers.include?(user4)).to eq(true) + expect(challenge.challenged.length).to eq(2) + expect(challenge.challenged.include?(user2)).to eq(true) + expect(challenge.challenged.include?(user3)).to eq(true) + end +end From e5198950927aedf8551e0a6003c0f0b5b274682d Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 11:21:23 -0800 Subject: [PATCH 04/13] Add test for recording an automatch lost --- spec/fabricators/challenge_fabricator.rb | 11 +++++++++++ spec/slack-gamebot/commands/lost_spec.rb | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/spec/fabricators/challenge_fabricator.rb b/spec/fabricators/challenge_fabricator.rb index f4c4d637..baa1d44a 100644 --- a/spec/fabricators/challenge_fabricator.rb +++ b/spec/fabricators/challenge_fabricator.rb @@ -42,3 +42,14 @@ instance.updated_by = instance.challenged.first end end + +Fabricator(:automatch_challenge, from: :challenge) do + state ChallengeState::ACCEPTED + after_build do |instance| + instance.challengers = [Fabricate(:user, team: instance.team), Fabricate(:user, team: instance.team)] unless instance.challengers.any? + instance.challenged = [Fabricate(:user, team: instance.team), Fabricate(:user, team: instance.team)] unless instance.challenged.any? + end + before_create do |instance| + instance.updated_by = instance.challenged.first + end +end diff --git a/spec/slack-gamebot/commands/lost_spec.rb b/spec/slack-gamebot/commands/lost_spec.rb index 773b64fe..3780f5a8 100644 --- a/spec/slack-gamebot/commands/lost_spec.rb +++ b/spec/slack-gamebot/commands/lost_spec.rb @@ -5,7 +5,7 @@ let(:app) { SlackGamebot::Server.new(team: team) } context 'with an existing challenge' do let(:challenged) { Fabricate(:user, user_name: 'username') } - let!(:challenge) { Fabricate(:challenge, challenged: [challenged]) } + let(:challenge) { Fabricate(:challenge, challenged: [challenged]) } before do challenge.accept!(challenged) end @@ -85,6 +85,20 @@ ) end end + context 'automatch challenge' do + let(:challenged) { Fabricate(:user, user_name: 'username') } + let(:teammate) { Fabricate(:user) } + let!(:challenge) { Fabricate(:automatch_challenge, challenged: [challenged, teammate]) } + it 'lost' do + expect(message: "#{SlackRubyBot.config.user} lost", user: challenged.user_id, channel: challenge.channel).to respond_with_slack_message( + "Match has been recorded! #{challenge.challengers.map(&:user_name).and} defeated #{challenge.challenged.map(&:user_name).and}." + ) + challenge.reload + expect(challenge.state).to eq ChallengeState::PLAYED + expect(challenge.match.winners).to eq challenge.challengers + expect(challenge.match.losers).to eq challenge.challenged + end + end context 'lost to' do let(:loser) { Fabricate(:user, user_name: 'username') } let(:winner) { Fabricate(:user) } From 1c0c60e959fa6cc643d2ee8560a7576bd89b841d Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 12:05:20 -0800 Subject: [PATCH 05/13] Time out automatches after 5 minutes --- Gemfile | 1 + Gemfile.lock | 2 + slack-gamebot/commands/automatch.rb | 16 +++-- slack-gamebot/models/user.rb | 2 +- spec/slack-gamebot/commands/automatch_spec.rb | 59 +++++++++++++------ 5 files changed, 55 insertions(+), 25 deletions(-) diff --git a/Gemfile b/Gemfile index d3a85b6d..91618595 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ group :development, :test do gem 'rake', '~> 10.4' gem 'rubocop', '0.34.2' gem 'foreman' + gem 'timecop' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index a36f1da1..443cbc6e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -246,6 +246,7 @@ GEM thor (0.19.1) thread_safe (0.3.5) time_ago_in_words (0.1.1) + timecop (0.8.0) tzinfo (1.2.2) thread_safe (~> 0.1) uber (0.0.15) @@ -299,6 +300,7 @@ DEPENDENCIES slack-ruby-bot! slack-ruby-client! time_ago_in_words + timecop unicorn vcr wannabe_bool diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index 55aebc0f..e936e668 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -6,18 +6,22 @@ def self.call(client, data, match) case match['expression'] when 'on' - challenger.automatch = true + challenger.automatch_time = 5.minutes.from_now when 'off' - challenger.automatch = false + challenger.automatch_time = nil when nil - challenger.automatch = !challenger.automatch + if challenger.automatch_time && challenger.automatch_time > Time.now + challenger.automatch_time = nil + else + challenger.automatch_time = 5.minutes.from_now + end else fail "Invalid automatch argument #{match['expression']}" end challenger.save! - if challenger.automatch + if challenger.automatch_time state = 'on' gif_word = 'ready' else @@ -27,7 +31,7 @@ def self.call(client, data, match) logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{state}" - automatch_users = User.where(automatch: true).order(elo: :asc).limit(4) + automatch_users = User.where(:automatch_time.gt => Time.now).order(elo: :asc).limit(4) if automatch_users.count == 4 challenge = ::Challenge.create!( team: client.owner, @@ -40,7 +44,7 @@ def self.call(client, data, match) ) automatch_users.each do |user| - user.automatch = false + user.automatch_time = nil user.save! end diff --git a/slack-gamebot/models/user.rb b/slack-gamebot/models/user.rb index 137a74b6..b0715007 100644 --- a/slack-gamebot/models/user.rb +++ b/slack-gamebot/models/user.rb @@ -11,7 +11,7 @@ class User field :tau, type: Float, default: 0 field :rank, type: Integer field :captain, type: Boolean, default: false - field :automatch, type: Boolean, default: false + field :automatch_time, type: Time belongs_to :team, index: true validates_presence_of :team diff --git a/spec/slack-gamebot/commands/automatch_spec.rb b/spec/slack-gamebot/commands/automatch_spec.rb index 8621e504..3d430aa9 100644 --- a/spec/slack-gamebot/commands/automatch_spec.rb +++ b/spec/slack-gamebot/commands/automatch_spec.rb @@ -9,13 +9,15 @@ let(:user4) { Fabricate(:user, user_name: 'username4', elo: 4) } let(:opponent) { Fabricate(:user) } - it 'sets automatch flag to true when user turns it on' do - expect(message: "#{SlackRubyBot.config.user} automatch on", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( - 'Automatch is on for username (1 users ready to play!)' - ) + it 'sets automatch time to 5 minutes in the future when user turns it on' do + Timecop.freeze(Time.now.beginning_of_minute) do + expect(message: "#{SlackRubyBot.config.user} automatch on", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) - user1.reload - expect(user1.automatch).to be(true) + user1.reload + expect(user1.automatch_time).to eq(5.minutes.from_now) + end end it 'sets automatch flag to false when user turns it off' do @@ -24,23 +26,25 @@ ) user1.reload - expect(user1.automatch).to be(false) + expect(user1.automatch_time).to be(nil) end it 'toggles automatch flag from off to on when there is no argument' do - user1.automatch = false - user1.save! + Timecop.freeze(Time.now.beginning_of_minute) do + user1.automatch_time = nil + user1.save! - expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( - 'Automatch is on for username (1 users ready to play!)' - ) + expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) - user1.reload - expect(user1.automatch).to be(true) + user1.reload + expect(user1.automatch_time).to eq(5.minutes.from_now) + end end it 'toggles automatch flag from on to off when there is no argument' do - user1.automatch = true + user1.automatch_time = 5.minutes.from_now user1.save! expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( @@ -48,12 +52,12 @@ ) user1.reload - expect(user1.automatch).to be(false) + expect(user1.automatch_time).to be(nil) end it 'creates a doubles match when four users have automatch on' do [user1, user2, user3].each do |user| - user.automatch = true + user.automatch_time = 5.minutes.from_now user.save! end @@ -66,7 +70,26 @@ it 'matches top and bottom elo vs middle two elo' do [user1, user2, user3].each do |user| - user.automatch = true + user.automatch_time = 5.minutes.from_now + user.save! + end + + expect(message: "#{SlackRubyBot.config.user} automatch", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch: username1 and username vs username2 and username3!' + ) + + challenge = Challenge.last + expect(challenge.challengers.length).to eq(2) + expect(challenge.challengers.include?(user1)).to eq(true) + expect(challenge.challengers.include?(user4)).to eq(true) + expect(challenge.challenged.length).to eq(2) + expect(challenge.challenged.include?(user2)).to eq(true) + expect(challenge.challenged.include?(user3)).to eq(true) + end + + it 'times out after 5 minutes' do + [user1, user2, user3].each do |user| + user.automatch_time = 5.minutes.from_now user.save! end From 053a9aaf0d75285680ae8d267d9f6ed6ffd72238 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 13:15:53 -0800 Subject: [PATCH 06/13] Add ability to set automatch until a specified time --- Gemfile | 1 + Gemfile.lock | 2 ++ slack-gamebot/commands/automatch.rb | 7 ++++- spec/slack-gamebot/commands/automatch_spec.rb | 30 +++++++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 91618595..be5d7fcd 100644 --- a/Gemfile +++ b/Gemfile @@ -19,6 +19,7 @@ gem 'newrelic_rpm' gem 'newrelic-slack-ruby-bot' gem 'rack-rewrite' gem 'wannabe_bool' +gem 'chronic' group :development, :test do gem 'rake', '~> 10.4' diff --git a/Gemfile.lock b/Gemfile.lock index 443cbc6e..12f3487d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -56,6 +56,7 @@ GEM thread_safe (~> 0.3, >= 0.3.1) bson (4.0.1) builder (3.2.2) + chronic (0.10.2) coercible (1.0.0) descendants_tracker (~> 0.0.1) crack (0.4.3) @@ -274,6 +275,7 @@ PLATFORMS ruby DEPENDENCIES + chronic database_cleaner fabrication faker diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index e936e668..fa63d773 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -9,6 +9,11 @@ def self.call(client, data, match) challenger.automatch_time = 5.minutes.from_now when 'off' challenger.automatch_time = nil + when /^until\b/i + parsed_time = Chronic.parse(match['expression'].sub(/^until\W*/, '')) + fail SlackGamebot::Error, "Can't understand time specified" unless parsed_time + + challenger.automatch_time = parsed_time when nil if challenger.automatch_time && challenger.automatch_time > Time.now challenger.automatch_time = nil @@ -16,7 +21,7 @@ def self.call(client, data, match) challenger.automatch_time = 5.minutes.from_now end else - fail "Invalid automatch argument #{match['expression']}" + fail SlackGamebot::Error, "Invalid automatch argument #{match['expression']}" end challenger.save! diff --git a/spec/slack-gamebot/commands/automatch_spec.rb b/spec/slack-gamebot/commands/automatch_spec.rb index 3d430aa9..f401996c 100644 --- a/spec/slack-gamebot/commands/automatch_spec.rb +++ b/spec/slack-gamebot/commands/automatch_spec.rb @@ -105,4 +105,34 @@ expect(challenge.challenged.include?(user2)).to eq(true) expect(challenge.challenged.include?(user3)).to eq(true) end + + context 'until' do + it 'sets automatch time for relative time' do + Timecop.freeze(Time.now.beginning_of_minute) do + expect(message: "#{SlackRubyBot.config.user} automatch until 30 minutes from now", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) + + user1.reload + expect(user1.automatch_time).to eq(30.minutes.from_now) + end + end + + it 'sets automatch time for specific time' do + Timecop.freeze(DateTime.parse('2016-1-1')) do + expect(message: "#{SlackRubyBot.config.user} automatch until June 20th, 2016 at 8pm", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) + + user1.reload + expect(user1.automatch_time).to eq(DateTime.parse('2016-06-20 20:00-07:00').to_s) + end + end + + it 'indicates when the time cannot be interpreted' do + expect(message: "#{SlackRubyBot.config.user} automatch until the cows come home", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + "Can't understand time specified" + ) + end + end end From 05b14eb6cefbe96e2a45366fb25bc55cace7a9fc Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 13:23:07 -0800 Subject: [PATCH 07/13] Add automatch until a specified time or for a specified duration --- Gemfile | 1 + Gemfile.lock | 4 ++++ slack-gamebot/commands/automatch.rb | 7 ++++++- spec/slack-gamebot/commands/automatch_spec.rb | 19 +++++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index be5d7fcd..a99657b3 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,7 @@ gem 'newrelic-slack-ruby-bot' gem 'rack-rewrite' gem 'wannabe_bool' gem 'chronic' +gem 'chronic_duration' group :development, :test do gem 'rake', '~> 10.4' diff --git a/Gemfile.lock b/Gemfile.lock index 12f3487d..45a924b0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -57,6 +57,8 @@ GEM bson (4.0.1) builder (3.2.2) chronic (0.10.2) + chronic_duration (0.10.6) + numerizer (~> 0.1.1) coercible (1.0.0) descendants_tracker (~> 0.0.1) crack (0.4.3) @@ -184,6 +186,7 @@ GEM newrelic_rpm (3.14.3.313) nokogiri (1.6.7.2) mini_portile2 (~> 2.0.0.rc2) + numerizer (0.1.1) oj (2.14.4) origin (2.2.0) parser (2.3.0.5) @@ -276,6 +279,7 @@ PLATFORMS DEPENDENCIES chronic + chronic_duration database_cleaner fabrication faker diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index fa63d773..f9b44a00 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -14,6 +14,11 @@ def self.call(client, data, match) fail SlackGamebot::Error, "Can't understand time specified" unless parsed_time challenger.automatch_time = parsed_time + when /^for\b/i + parsed_time = ChronicDuration.parse(match['expression'].sub(/^for\W*/, '')) + fail SlackGamebot::Error, "Can't understand time specified" unless parsed_time + + challenger.automatch_time = Time.now + parsed_time when nil if challenger.automatch_time && challenger.automatch_time > Time.now challenger.automatch_time = nil @@ -21,7 +26,7 @@ def self.call(client, data, match) challenger.automatch_time = 5.minutes.from_now end else - fail SlackGamebot::Error, "Invalid automatch argument #{match['expression']}" + fail SlackGamebot::Error, "Invalid automatch argument '#{match['expression']}'" end challenger.save! diff --git a/spec/slack-gamebot/commands/automatch_spec.rb b/spec/slack-gamebot/commands/automatch_spec.rb index f401996c..04362c30 100644 --- a/spec/slack-gamebot/commands/automatch_spec.rb +++ b/spec/slack-gamebot/commands/automatch_spec.rb @@ -135,4 +135,23 @@ ) end end + + context 'for' do + it 'sets automatch time for relative time' do + Timecop.freeze(Time.now.beginning_of_minute) do + expect(message: "#{SlackRubyBot.config.user} automatch for 2 hours", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + 'Automatch is on for username (1 users ready to play!)' + ) + + user1.reload + expect(user1.automatch_time).to eq(2.hours.from_now) + end + end + + it 'indicates when the time cannot be interpreted' do + expect(message: "#{SlackRubyBot.config.user} automatch for a really really long time", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + "Can't understand time specified" + ) + end + end end From fb36db191966e5ca63cecbcfa5321cfdc7dabaa9 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 13:57:24 -0800 Subject: [PATCH 08/13] Show automatch times remaining when no argument --- slack-gamebot/commands/automatch.rb | 16 +++-- spec/slack-gamebot/commands/automatch_spec.rb | 64 ++++++++++--------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index f9b44a00..b0ca7ab5 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -20,11 +20,19 @@ def self.call(client, data, match) challenger.automatch_time = Time.now + parsed_time when nil - if challenger.automatch_time && challenger.automatch_time > Time.now - challenger.automatch_time = nil + automatch_users = User.where(:automatch_time.gt => Time.now).order(automatch_time: :asc) + if (automatch_users.count == 0) + client.say(channel: data.channel, text: 'No users currently have automatch turned on') else - challenger.automatch_time = 5.minutes.from_now + times = automatch_users.map do |user| + duration = ChronicDuration.output(user.automatch_time - Time.new, keep_zero: true) + "#{user.user_name} for #{duration}" + end.join("\n") + + client.say(channel: data.channel, text: times) end + + return else fail SlackGamebot::Error, "Invalid automatch argument '#{match['expression']}'" end @@ -39,7 +47,7 @@ def self.call(client, data, match) gif_word = 'gone' end - logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{state}" + logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{challenger.automatch_time}" automatch_users = User.where(:automatch_time.gt => Time.now).order(elo: :asc).limit(4) if automatch_users.count == 4 diff --git a/spec/slack-gamebot/commands/automatch_spec.rb b/spec/slack-gamebot/commands/automatch_spec.rb index 04362c30..5aa3a804 100644 --- a/spec/slack-gamebot/commands/automatch_spec.rb +++ b/spec/slack-gamebot/commands/automatch_spec.rb @@ -29,32 +29,6 @@ expect(user1.automatch_time).to be(nil) end - it 'toggles automatch flag from off to on when there is no argument' do - Timecop.freeze(Time.now.beginning_of_minute) do - user1.automatch_time = nil - user1.save! - - expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( - 'Automatch is on for username (1 users ready to play!)' - ) - - user1.reload - expect(user1.automatch_time).to eq(5.minutes.from_now) - end - end - - it 'toggles automatch flag from on to off when there is no argument' do - user1.automatch_time = 5.minutes.from_now - user1.save! - - expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( - 'Automatch is off for username (0 users ready to play!)' - ) - - user1.reload - expect(user1.automatch_time).to be(nil) - end - it 'creates a doubles match when four users have automatch on' do [user1, user2, user3].each do |user| user.automatch_time = 5.minutes.from_now @@ -62,7 +36,7 @@ end expect do - expect(message: "#{SlackRubyBot.config.user} automatch", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( + expect(message: "#{SlackRubyBot.config.user} automatch on", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( 'Automatch: username1 and username vs username2 and username3!' ) end.to change(Challenge, :count).by(1) @@ -74,7 +48,7 @@ user.save! end - expect(message: "#{SlackRubyBot.config.user} automatch", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( + expect(message: "#{SlackRubyBot.config.user} automatch on", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( 'Automatch: username1 and username vs username2 and username3!' ) @@ -93,7 +67,7 @@ user.save! end - expect(message: "#{SlackRubyBot.config.user} automatch", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( + expect(message: "#{SlackRubyBot.config.user} automatch on", user: user4.user_id, channel: 'pongbot').to respond_with_slack_message( 'Automatch: username1 and username vs username2 and username3!' ) @@ -125,7 +99,7 @@ ) user1.reload - expect(user1.automatch_time).to eq(DateTime.parse('2016-06-20 20:00-07:00').to_s) + expect(user1.automatch_time).to eq(DateTime.parse('2016-06-20 20:00-07:00')) end end @@ -154,4 +128,34 @@ ) end end + + context 'with no arguments' do + it 'lists users and the times they are set to automatch until' do + Timecop.freeze(Time.now.beginning_of_minute) do + user1.automatch_time = 4.minutes.from_now + user1.save + + user2.automatch_time = 1.hour.from_now + user2.save + + expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + "username for 4 mins 0 secs\nusername2 for 1 hr 0 secs" + ) + end + end + + it 'does not include users with times set in the past' do + Timecop.freeze(Time.now.beginning_of_minute) do + user1.automatch_time = 4.minutes.from_now + user1.save + + user2.automatch_time = 1.minute.ago + user2.save + + expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( + "username for 4 mins 0 secs" + ) + end + end + end end From 9bd515401201cf2e7f34d42b949d590770645117 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 14:00:35 -0800 Subject: [PATCH 09/13] Eliminate fractional seconds in output --- slack-gamebot/commands/automatch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index b0ca7ab5..0ec64156 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -25,7 +25,7 @@ def self.call(client, data, match) client.say(channel: data.channel, text: 'No users currently have automatch turned on') else times = automatch_users.map do |user| - duration = ChronicDuration.output(user.automatch_time - Time.new, keep_zero: true) + duration = ChronicDuration.output((user.automatch_time - Time.new).to_i, keep_zero: true) "#{user.user_name} for #{duration}" end.join("\n") From 4e2f854f94fe2222a626db76f328cdc556293d23 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 14:09:47 -0800 Subject: [PATCH 10/13] Add help message for automatch --- slack-gamebot/commands/help.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/slack-gamebot/commands/help.rb b/slack-gamebot/commands/help.rb index bbd14269..4c4550f5 100644 --- a/slack-gamebot/commands/help.rb +++ b/slack-gamebot/commands/help.rb @@ -17,6 +17,7 @@ class Help < SlackRubyBot::Commands::Base ----- challenge , ... [with , ...]: challenge opponent(s) to a game accept: accept a challenge +automatch: turn on automatch mode, eg. automatch for 10 minutes, or automatch until 8pm decline: decline a previous challenge cancel: cancel a previous challenge lost [score, ...]: record your loss From 54e390044f88635d0cd79a33b99d52d49c4c7d9b Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 15:48:43 -0800 Subject: [PATCH 11/13] Rubocop issue --- spec/slack-gamebot/commands/automatch_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/slack-gamebot/commands/automatch_spec.rb b/spec/slack-gamebot/commands/automatch_spec.rb index 5aa3a804..10aae740 100644 --- a/spec/slack-gamebot/commands/automatch_spec.rb +++ b/spec/slack-gamebot/commands/automatch_spec.rb @@ -153,7 +153,7 @@ user2.save expect(message: "#{SlackRubyBot.config.user} automatch", user: user1.user_id, channel: 'pongbot').to respond_with_slack_message( - "username for 4 mins 0 secs" + 'username for 4 mins 0 secs' ) end end From 44a82775d99c814d57fd7cf789f3cd8220ebfd46 Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 16:22:27 -0800 Subject: [PATCH 12/13] Refactor automatch command --- slack-gamebot/commands/automatch.rb | 31 +++++++++++++++++------------ 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/slack-gamebot/commands/automatch.rb b/slack-gamebot/commands/automatch.rb index 0ec64156..63e4466f 100644 --- a/slack-gamebot/commands/automatch.rb +++ b/slack-gamebot/commands/automatch.rb @@ -7,18 +7,26 @@ def self.call(client, data, match) case match['expression'] when 'on' challenger.automatch_time = 5.minutes.from_now + challenger.save! + automatch_on(challenger, client, data) when 'off' challenger.automatch_time = nil + challenger.save! + automatch_off(challenger, client, data) when /^until\b/i parsed_time = Chronic.parse(match['expression'].sub(/^until\W*/, '')) fail SlackGamebot::Error, "Can't understand time specified" unless parsed_time challenger.automatch_time = parsed_time + challenger.save! + automatch_on(challenger, client, data) when /^for\b/i parsed_time = ChronicDuration.parse(match['expression'].sub(/^for\W*/, '')) fail SlackGamebot::Error, "Can't understand time specified" unless parsed_time challenger.automatch_time = Time.now + parsed_time + challenger.save! + automatch_on(challenger, client, data) when nil automatch_users = User.where(:automatch_time.gt => Time.now).order(automatch_time: :asc) if (automatch_users.count == 0) @@ -31,22 +39,12 @@ def self.call(client, data, match) client.say(channel: data.channel, text: times) end - - return else fail SlackGamebot::Error, "Invalid automatch argument '#{match['expression']}'" end + end - challenger.save! - - if challenger.automatch_time - state = 'on' - gif_word = 'ready' - else - state = 'off' - gif_word = 'gone' - end - + def self.automatch_on(challenger, client, data) logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: #{challenger.automatch_time}" automatch_users = User.where(:automatch_time.gt => Time.now).order(elo: :asc).limit(4) @@ -69,9 +67,16 @@ def self.call(client, data, match) client.say(channel: data.channel, text: "Automatch: #{challenge.challengers.map(&:user_name).and} vs #{challenge.challenged.map(&:user_name).and}!", gif: 'challenge') logger.info "CHALLENGE: #{client.owner} - #{challenge}" else - client.say(channel: data.channel, text: "Automatch is #{state} for #{challenger.user_name} (#{automatch_users.count} users ready to play!)", gif: gif_word) + client.say(channel: data.channel, text: "Automatch is on for #{challenger.user_name} (#{automatch_users.count} users ready to play!)", gif: 'ready') end end + + def self.automatch_off(challenger, client, data) + automatch_count = User.where(:automatch_time.gt => Time.now).count + logger.info "AUTOMATCH: #{client.owner} - #{challenger.user_name}: OFF" + + client.say(channel: data.channel, text: "Automatch is off for #{challenger.user_name} (#{automatch_count} users ready to play!)", gif: 'leave') + end end end end From 99bc52bb239cb3f703014e07cecc19625f2acecd Mon Sep 17 00:00:00 2001 From: Alf Mikula Date: Fri, 19 Feb 2016 16:24:55 -0800 Subject: [PATCH 13/13] Update docs --- CHANGELOG.md | 1 + README.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc1bdf69..9a3fb3ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### Changelog +* Added automatch support for doubles matches - [@amikula](https://github.com/amikula). * [#94](https://github.com/dblock/slack-gamebot/issues/94): De-registering and re-registering a team just reactivates the old team - [@dblock](https://github.com/dblock). * [#92](https://github.com/dblock/slack-gamebot/issues/92): Leaderboard without ranked players now says that there're no ranked players - [@dblock](https://github.com/dblock). * [#80](https://github.com/dblock/slack-gamebot/issues/80): Empty season produces `undefined method 'map' for nil:NilClass` error - [@dblock](https://github.com/dblock). diff --git a/README.md b/README.md index 1aff55de..14f61775 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,58 @@ gamebot cancel Victor Barna and Deng Yaping canceled a challenge against Wang Hoe and Zhang Jike. ``` +#### gamebot automatch on|off + +Turn doubles automatch on for 5 minutes, or turn it off. When 4 players have automatch on, gamebot will create a new challenge with the players with the highest and lowest ELO scores vs the players with the middle scores. + +``` +gamebot automatch on + +Automatch is on for alf (1 users ready to play!) +``` + +``` +gamebot automatch off + +Automatch is off for alf (0 users ready to play!) +``` + +#### gamebot automatch for <duration> + +Turn doubles automatch on for the specified duration. Accepts natural language duration like "5 minutes". + +``` +gamebot automatch for 5 minutes + +Automatch is on for alf (1 users ready to play!) +``` + +#### gamebot automatch until <time> + +Turn doubles automatch on until the specified time. Accepts natural language times. + +``` +gamebot automatch until 10 minutes from now + +Automatch is on for alf (1 users ready to play!) +``` + +``` +gamebot automatch until 8pm + +Automatch is on for alf (1 users ready to play!) +``` + +#### gamebot automatch + +List players with automatch on, and the durations they have remaining. + +``` +gamebot automatch + +alf for 4 mins 57 secs +``` + #### gamebot leaderboard [number|infinity] Get the leaderboard.