Skip to content

Commit

Permalink
Added challenge?, closes #168.
Browse files Browse the repository at this point in the history
  • Loading branch information
dblock committed Aug 11, 2018
1 parent 1fefbe5 commit ce22c7c
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 30 deletions.
9 changes: 7 additions & 2 deletions .rubocop_todo.yml
@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2018-06-12 16:49:02 -0400 using RuboCop version 0.55.0.
# on 2018-08-11 15:57:07 -0400 using RuboCop version 0.55.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
Expand Down Expand Up @@ -58,6 +58,11 @@ Naming/HeredocDelimiterNaming:
- 'slack-gamebot/models/team.rb'
- 'slack-gamebot/server.rb'

# Offense count: 1
Naming/MemoizedInstanceVariableName:
Exclude:
- 'slack-gamebot/models/match.rb'

# Offense count: 14
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id, to, by, on, in, at
Expand All @@ -74,7 +79,7 @@ Naming/VariableNumber:
Exclude:
- 'spec/slack-gamebot/commands/leaderboard_spec.rb'

# Offense count: 3
# Offense count: 2
Style/DateTime:
Exclude:
- 'slack-gamebot/models/team.rb'
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,5 +1,6 @@
### Change Log

* [#168](https://github.com/dblock/slack-gamebot/issues/168): Display elo at stake with `challenge?` - [@dblock](https://github.com/dblock).
* [#161](https://github.com/dblock/slack-gamebot/pull/161): Added `MONGODB_URI` env as production mongoid client uri - [@dsantosmerino](https://github.com/dsantosmerino).
* [#153](https://github.com/dblock/slack-gamebot/pull/153): Added `taunt` that allows users to taunt other users - [@marin-hyatt](https://github.com/marin-hyatt).
* [#155](https://github.com/dblock/slack-gamebot/issues/155): Fix: Players can no longer lose to themselves - [@kanno41](https://github.com/kanno41).
Expand Down
10 changes: 10 additions & 0 deletions README.md
Expand Up @@ -89,6 +89,16 @@ gamebot challenge @WangHoe @ZhangJike with @DengYaping
Victor Barna and Deng Yaping challenged Wang Hoe and Zhang Jike to a match!
```

#### gamebot challenge?

Show elo at stake for each opponent.

```
gamebot challenge? @WangHoe
Victor Barna challenging Wang Hoe to a match is worth 48 and 24 elo.
```

#### gamebot accept

Accept a challenge.
Expand Down
4 changes: 4 additions & 0 deletions config/initializers/array.rb
Expand Up @@ -7,6 +7,10 @@ def or
join_with 'or'
end

def same?
uniq.length == 1
end

private

def join_with(separator)
Expand Down
1 change: 1 addition & 0 deletions slack-gamebot/commands.rb
Expand Up @@ -2,6 +2,7 @@
require 'slack-gamebot/commands/accept'
require 'slack-gamebot/commands/cancel'
require 'slack-gamebot/commands/challenge'
require 'slack-gamebot/commands/challenge_question'
require 'slack-gamebot/commands/challenges'
require 'slack-gamebot/commands/decline'
require 'slack-gamebot/commands/default'
Expand Down
17 changes: 17 additions & 0 deletions slack-gamebot/commands/challenge_question.rb
@@ -0,0 +1,17 @@
module SlackGamebot
module Commands
class ChallengeQuestion < SlackRubyBot::Commands::Base
include SlackGamebot::Commands::Mixins::Subscription

subscribed_command 'challenge?' do |client, data, match|
challenger = ::User.find_create_or_update_by_slack_id!(client, data.user)
arguments = match['expression'].split.reject(&:blank?) if match['expression']
arguments ||= []
challenge = ::Challenge.new_from_teammates_and_opponents(client, data.channel, challenger, arguments)
match = ::Match.new(team: client.owner, winners: challenge.challengers, losers: challenge.challenged, scores: [])
client.say(channel: data.channel, text: "#{challenge.challengers.map(&:slack_mention).and} challenging #{challenge.challenged.map(&:slack_mention).and} to a match is worth #{match.elo_s} elo.", gif: 'challenge')
logger.info "CHALLENGE?: #{client.owner} - #{challenge}"
end
end
end
end
1 change: 1 addition & 0 deletions slack-gamebot/commands/help.rb
Expand Up @@ -18,6 +18,7 @@ class Help < SlackRubyBot::Commands::Base
Games
-----
challenge <opponent> ... [with <teammate> ...]: challenge opponent(s) to a game
challenge? <opponent> ... [with <teammate> ...]: show elo at stake
accept: accept a challenge
decline: decline a previous challenge
cancel: cancel a previous challenge
Expand Down
8 changes: 6 additions & 2 deletions slack-gamebot/models/challenge.rb
Expand Up @@ -60,9 +60,9 @@ def self.split_teammates_and_opponents(client, challenger, names, separator = 'w
[teammates, opponents]
end

def self.create_from_teammates_and_opponents!(client, channel, challenger, names, separator = 'with')
def self.new_from_teammates_and_opponents(client, channel, challenger, names, separator = 'with')
teammates, opponents = split_teammates_and_opponents(client, challenger, names, separator)
Challenge.create!(
Challenge.new(
team: client.owner,
channel: channel,
created_by: challenger,
Expand All @@ -72,6 +72,10 @@ def self.create_from_teammates_and_opponents!(client, channel, challenger, names
)
end

def self.create_from_teammates_and_opponents!(client, channel, challenger, names, separator = 'with')
new_from_teammates_and_opponents(client, channel, challenger, names, separator).tap(&:save!)
end

def accept!(challenger)
raise SlackGamebot::Error, "Challenge has already been #{state}." unless state == ChallengeState::PROPOSED
update_attributes!(updated_by: challenger, state: ChallengeState::ACCEPTED)
Expand Down
77 changes: 51 additions & 26 deletions slack-gamebot/models/match.rb
Expand Up @@ -65,6 +65,17 @@ def update_users!
User.rank!(team)
end

def elo_s
winners_delta, losers_delta = calculated_elo
if (winners_delta | losers_delta).same?
winners_delta.first.to_i.to_s
elsif winners_delta.same? && losers_delta.same?
[winners_delta.first, losers_delta.first].map(&:to_i).and
else
(winners_delta | losers_delta).map(&:to_i).and
end
end

private

def validate_teams
Expand Down Expand Up @@ -111,33 +122,47 @@ def score_verb
end
end

def calculate_elo!
winners_elo = Elo.team_elo(winners)
losers_elo = Elo.team_elo(losers)

losers_ratio = losers.any? ? [winners.size.to_f / losers.size, 1].min : 1
winners_ratio = winners.any? ? [losers.size.to_f / winners.size, 1].min : 1

ratio = if winners_elo == losers_elo && tied?
0 # no elo updates when tied and elo is equal
elsif tied?
0.5 # half the elo in a tie
else
1 # whole elo
end

winners.each do |winner|
e = 100 - 1.0 / (1.0 + (10.0**((losers_elo - winner.elo) / 400.0))) * 100
winner.tau += 0.5
winner.elo += e * ratio * (Elo::DELTA_TAU**winner.tau) * winners_ratio
winner.save!
end
def calculated_elo
@calcualted_elo ||= begin
winners_delta = []
losers_delta = []
winners_elo = Elo.team_elo(winners)
losers_elo = Elo.team_elo(losers)

losers_ratio = losers.any? ? [winners.size.to_f / losers.size, 1].min : 1
winners_ratio = winners.any? ? [losers.size.to_f / winners.size, 1].min : 1

ratio = if winners_elo == losers_elo && tied?
0 # no elo updates when tied and elo is equal
elsif tied?
0.5 # half the elo in a tie
else
1 # whole elo
end

winners.each do |winner|
e = 100 - 1.0 / (1.0 + (10.0**((losers_elo - winner.elo) / 400.0))) * 100
winner.tau += 0.5
delta = e * ratio * (Elo::DELTA_TAU**winner.tau) * winners_ratio
winners_delta << delta
winner.elo += delta
end

losers.each do |loser|
e = 100 - 1.0 / (1.0 + (10.0**((loser.elo - winners_elo) / 400.0))) * 100
loser.tau += 0.5
delta = e * ratio * (Elo::DELTA_TAU**loser.tau) * losers_ratio
losers_delta << delta
loser.elo -= delta
end

losers.each do |loser|
e = 100 - 1.0 / (1.0 + (10.0**((loser.elo - winners_elo) / 400.0))) * 100
loser.tau += 0.5
loser.elo -= e * ratio * (Elo::DELTA_TAU**loser.tau) * losers_ratio
loser.save!
[losers_delta, winners_delta]
end
end

def calculate_elo!
calculated_elo
winners.each(&:save!)
losers.each(&:save!)
end
end
16 changes: 16 additions & 0 deletions spec/initializers/array_spec.rb
Expand Up @@ -23,4 +23,20 @@
expect(%w[foo bar baz].or).to eq 'foo, bar or baz'
end
end
context '.same?' do
it 'empty' do
expect([].same?).to be false
end
it 'one' do
expect([1].same?).to be true
end
it 'two' do
expect([1, 1].same?).to be true
expect([1, 2].same?).to be false
end
it 'three' do
expect([2, 2, 2].same?).to be true
expect([1, 2, 3].same?).to be false
end
end
end
49 changes: 49 additions & 0 deletions spec/slack-gamebot/commands/challenge_question_spec.rb
@@ -0,0 +1,49 @@
require 'spec_helper'

describe SlackGamebot::Commands::ChallengeQuestion, vcr: { cassette_name: 'user_info' } do
let!(:team) { Fabricate(:team) }
let(:app) { SlackGamebot::Server.new(team: team) }
let(:client) { app.send(:client) }
let(:user) { Fabricate(:user, user_name: 'username') }
let(:opponent) { Fabricate(:user) }
it 'displays elo at stake for a singles challenge' do
expect do
expect(message: "#{SlackRubyBot.config.user} challenge? <@#{opponent.user_id}>", user: user.user_id, channel: 'pongbot').to respond_with_slack_message(
"#{user.slack_mention} challenging #{opponent.slack_mention} to a match is worth 48 elo."
)
end.to_not change(Challenge, :count)
end
it 'displays elo at stake for a doubles challenge' do
opponent2 = Fabricate(:user, team: team)
teammate = Fabricate(:user, team: team)
expect do
expect(message: "#{SlackRubyBot.config.user} challenge? #{opponent.slack_mention} #{opponent2.user_name} with #{teammate.user_name}", user: user.user_id, channel: 'pongbot').to respond_with_slack_message(
"#{user.slack_mention} and #{teammate.slack_mention} challenging #{opponent.slack_mention} and #{opponent2.slack_mention} to a match is worth 48 elo."
)
end.to_not change(Challenge, :count)
end
context 'with unbalanced option enabled' do
before do
team.update_attributes!(unbalanced: true)
end
it 'displays elo at stake with different number of opponents' do
opponent1 = Fabricate(:user)
opponent2 = Fabricate(:user)
expect do
expect(message: "#{SlackRubyBot.config.user} challenge? #{opponent1.slack_mention} #{opponent2.slack_mention}", user: user.user_id, channel: 'pongbot').to respond_with_slack_message(
"#{user.slack_mention} challenging #{opponent1.slack_mention} and #{opponent2.slack_mention} to a match is worth 24 and 48 elo."
)
end.to_not change(Challenge, :count)
end
end
context 'subscription expiration' do
before do
team.update_attributes!(created_at: 3.weeks.ago)
end
it 'prevents new challenge questions' do
expect(message: "#{SlackRubyBot.config.user} challenge? <@#{opponent.user_id}>", user: user.user_id, channel: 'pongbot').to respond_with_slack_message(
"Your trial subscription has expired. Subscribe your team for $29.99 a year at https://www.playplay.io/subscribe?team_id=#{team.team_id}&game=#{team.game.name}."
)
end
end
end

0 comments on commit ce22c7c

Please sign in to comment.