Skip to content

Instantly share code, notes, and snippets.

@akshaysasidrn
Last active June 26, 2017 08:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akshaysasidrn/42014dfeb5e9cc80ecc9c8319677ded1 to your computer and use it in GitHub Desktop.
Save akshaysasidrn/42014dfeb5e9cc80ecc9c8319677ded1 to your computer and use it in GitHub Desktop.
Cricket simulation
module CricketSimulation
WIN = :win
LOSE = :lose
TIE = :tie
# Team class holds the team attributes.
class Team
attr_accessor :teamname, :player_line_up, :first_batting, :result
def initialize(team_name, players)
@teamname = team_name
@player_line_up = players
@first_batting = false
set_player_team(players)
end
def set_player_team(players)
players.each do |player|
player.team = self
end
end
def score
self.player_line_up.map(&:score).reduce(:+)
end
end
# Player class holds the player attributes.
class Player
attr_accessor :name, :team, :balls, :score
def initialize(name, stats)
@name = name
raise "Stats: Probability distribution must add up to 1" if stat_summation_invalid?(stats)
@stats = stats
@balls = 0
@score = 0
end
def stat_summation_invalid?(stats)
stats.values.reduce(:+).round(2) != 1.0
end
def bat
result = @stats.max_by {|_, weight| rand ** (1.0 / weight)}.first
if result.is_a? Integer
@balls += 1
@score += result
end
return result
end
end
# Umpire class makes all the decisions regarding the match.
class Umpire
attr_accessor :player_line_up
def initialize(match_details, commentator)
@match_details = match_details
@commentator = commentator
@end_game = false
end
def decrement_over
@match_details.overs -= 1
end
def switch_player
@match_details.active_player = @match_details.inactive_player
end
def process_output(output)
return {active: @match_details.inactive_player, score: output} if [1, 3, 5].include? output
return {active: @match_details.active_player, score: output} if [0, 2, 4, 6, :out].include? output
end
def player_out
out = @match_details.in_crease.shift # active player leaves crease
@match_details.in_crease.unshift(@match_details.player_line_up.shift) unless @match_details.wickets_over? # new lined up player enters
return out
end
def ends_game?
@end_game
end
def set_win(ball)
@end_game = true
unless @match_details.first_batting?
@match_details.set_result(WIN)
@commentator.win(@match_details.overs, ball, @match_details.players, @match_details.wicket_pending)
end
end
def set_defeat(ball)
@end_game = true
unless @match_details.first_batting?
@match_details.set_result(LOSE)
@commentator.lose(@match_details.overs, ball, @match_details.players, @match_details.to_win)
end
end
def set_tie
@end_game = true
unless @match_details.first_batting?
@match_details.set_result(TIE)
@commentator.tie
end
end
def end_game(result, ball)
if result == :win
set_win(ball)
elsif result == :lose
set_defeat(ball)
else
@match_details.to_win == 1 ? set_tie : set_defeat(ball)
end
end
def outlook_over(over, ball)
output = @match_details.active_batsman.bat
result = process_output(output)
determine_decision(result, over, ball)
end
def determine_decision(result, over, ball)
if result[:score] == :out
player = player_out
@commentator.out(player.name, over, ball)
end_game(:lose, ball) if @match_details.wickets_over?
else
@match_details.to_win -= result[:score] unless @match_details.first_batting?
@commentator.realtime_info(@match_details.active_batsman.name, result[:score], over, ball)
end_game(:win, ball) if @match_details.to_win <= 0 && !@match_details.first_batting?
end
@match_details.active_player = result[:active]
end
end
# MatchDetails class holds and handles the information regarding the match.
class MatchDetails
attr_accessor :overs, :to_win, :player_line_up, :players, :in_crease, :active_player
def initialize(overs, team, to_win)
@overs = overs
@to_win = to_win
set_team(team)
@player_line_up = batting_team.player_line_up.dup
@players = @player_line_up.dup
@in_crease = @player_line_up.shift(2)
@active_player = 0
end
def set_team(team)
@@teams ||= []
@@teams << team
end
def teams
@@teams
end
def inactive_player
@active_player == 0 ? 1 : 0
end
def active_batsman
@in_crease[@active_player]
end
def wickets_over?
true if @player_line_up.empty? && @in_crease.count == 1
end
def wicket_pending
@player_line_up.count
end
def first_batting?
batting_team.first_batting
end
def batting_team
teams.last
end
def bowling_team
(teams - [batting_team]).first
end
def set_result(status)
case status
when WIN
batting_team.result = WIN
bowling_team.result = LOSE if teams.count > 1
when LOSE
batting_team.result = LOSE
bowling_team.result = WIN if teams.count > 1
when TIE
batting_team.result = TIE
bowling_team.result = TIE if teams.count > 1
else
puts "invalid status"
end
end
def result
result = {}
teams.each {|team| result[team.teamname] = team.result}
return result
end
end
# Cricket class handles the simulation of the match
class Cricket
attr_accessor :match_details
def initialize(overs, team, to_win, commentator)
@match_details = MatchDetails.new(overs, team, to_win)
@umpire = Umpire.new(@match_details, commentator)
@commentator = commentator
end
def simulate_match
@match_details.overs.times do |over|
@commentator.over_sum_up(@match_details.overs, @match_details.to_win)
simulate_over(over)
@umpire.decrement_over
return if @umpire.ends_game?
end
@umpire.end_game(:overs_up, 6)
end
def simulate_over(over)
1.upto(6) do |ball|
break if @umpire.ends_game?
@umpire.outlook_over(over, ball)
end
@umpire.switch_player
end
def self.simulate_tie_breaker(overs, team, to_win, commentator)
commentator.innings(team)
team_game = Cricket.new(overs, team, to_win, commentator)
team_game.simulate_match
team_score = team.score
return team_score
end
def self.tie_breaker_game(team1, team2, overs)
commentator = Commentator.new
commentator.tie_breaker = true
team1_score = Cricket.simulate_tie_breaker(overs, team1, 0, commentator)
team2_score = Cricket.simulate_tie_breaker(overs, team2, team1_score + 1, commentator)
commentator.tie_breaker_summary([team1, team2])
end
end
# Commentator class deals with printing out comments.
class Commentator
attr_accessor :tie_breaker
def over_sum_up(over, to_win)
puts "\n#{over} overs left. #{to_win} runs to win" unless @tie_breaker
end
def realtime_info(player_name, score, over, ball)
puts "#{over}.#{ball} #{player_name} scores #{score} run"
end
def out(player_name, over, ball)
puts "#{over}.#{ball} #{player_name} is out"
end
def win(overs_left, balls_thrown, players, wicket_pending)
remaining_balls = CommentHelper.calc_remaining_balls(balls_thrown, overs_left)
puts "\n#{players.first.team.teamname} won by #{wicket_pending} wickets and #{remaining_balls} balls remaining"
player_scores(players) unless @tie_breaker
end
def lose(overs_left, balls_thrown, players, to_win)
remaining_balls = CommentHelper.calc_remaining_balls(balls_thrown, overs_left)
puts "\n#{players.first.team.teamname} lost by #{to_win} runs and #{remaining_balls} balls remaining"
player_scores(players) unless @tie_breaker
end
def tie
puts "\nThe match has resulted in a tie!"
end
def tie_breaker_summary(teams)
teams.each do |team|
puts "\n#{team.teamname}"
player_scores(team.player_line_up)
end
end
def innings(team)
puts "\n#{team.teamname} innings:"
end
def player_scores(players)
players.each do |player|
puts "#{player.name} - #{player.score}(#{player.balls})"
end
end
end
# CommentHelper class holds helper methods for Commentator class.
class CommentHelper
def self.calc_remaining_balls(balls_thrown, overs_left)
remaining_balls = (6 - balls_thrown)
remaining_balls += (overs_left - 1 ) * 6 unless overs_left.zero?
return remaining_balls
end
end
end
require_relative "cricket_simulation"
require "test/unit"
include CricketSimulation
TEAM1 = "Outer-Heaven"
PLAYER1 = "Solid Snake"
PLAYER2 = "Liquid Snake"
TEAM2 = "Konoha"
PLAYER3 = "Uzumaki Naruto"
PLAYER4 = "Hatake Kakashi"
class PlayerTest < Test::Unit::TestCase
def test_player_create
p1 = Player.new(PLAYER1, {
0 => 0.05,
1 => 0.30,
2 => 0.25,
3 => 0.10,
4 => 0.15,
5 => 0.01,
6 => 0.09,
:out => 0.05
})
assert_equal PLAYER1, p1.name
end
def test_probabilty_distribution_validity
assert_raises RuntimeError do
p1 = Player.new(PLAYER2, {
0 => 1,
1 => 2,
2 => 3,
3 => 4,
4 => 5,
5 => 6,
6 => 7,
:out => 8
})
end
end
end
class TeamTest < Test::Unit::TestCase
def test_team_create
p1 = Player.new(PLAYER1, {
0 => 0.05,
1 => 0.30,
2 => 0.25,
3 => 0.10,
4 => 0.15,
5 => 0.01,
6 => 0.09,
:out => 0.05
})
team = Team.new(TEAM1, [p1])
assert_equal PLAYER1, p1.name
assert_equal team, p1.team
end
end
# stats given probability 1, will only occur indefinitely. Pretty cool to test.
class UmpireTest < Test::Unit::TestCase
def test_change_player_on_over
puts "\n\nTEST: PLAYER CHANGE ON OVER"
p1 = Player.new(PLAYER1, {
0 => 1,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
p2 = Player.new(PLAYER2, {
0 => 0,
1 => 0.3,
2 => 0,
3 => 0.3,
4 => 0,
5 => 0.4,
6 => 0,
:out => 0
})
player_line_up = [p1, p2]
team = Team.new(TEAM1, player_line_up)
team.first_batting = true
commentator = Commentator.new
game = Cricket.new(2, team, 40, commentator)
game.simulate_over(0)
assert_equal PLAYER2, game.match_details.active_batsman.name
end
def test_change_player_on_odd_score
puts "\n\nTEST: PLAYER CHANGE ON ODD SCORES"
p1 = Player.new(PLAYER1, {
0 => 1,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
p2 = Player.new(PLAYER2, {
0 => 0,
1 => 0.3,
2 => 0,
3 => 0.3,
4 => 0,
5 => 0.4,
6 => 0,
:out => 0
})
player_line_up = [p1, p2]
team = Team.new(TEAM1, player_line_up)
team.first_batting = true
commentator = Commentator.new
game = Cricket.new(2, team, 40, commentator)
game.simulate_over(0)
assert_equal PLAYER2, game.match_details.active_batsman.name
game.simulate_over(1)
assert_equal PLAYER2, game.match_details.active_batsman.name
end
def test_out_of_balls
puts "\n\nTEST SCENARIO: OUT OF BALLS"
p1 = Player.new(PLAYER1, {
0 => 1,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
p2 = Player.new(PLAYER2, {
0 => 1,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
player_line_up = [p1, p2]
team = Team.new(TEAM1, player_line_up)
team.first_batting = true
commentator = Commentator.new
game = Cricket.new(4, team, 40, commentator)
game.simulate_match
assert_equal 0, game.match_details.teams.last.score
end
def test_run_out
puts "\n\nTEST SCENARIO: RUN OUT"
p1 = Player.new(PLAYER1, {
0 => 0,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 1
})
p2 = Player.new(PLAYER2, {
0 => 0,
1 => 0,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 1
})
player_line_up = [p1, p2]
commentator = Commentator.new
team = Team.new(TEAM1, player_line_up)
team.first_batting = true
game = Cricket.new(4, team, 40, commentator)
game.simulate_match
assert_equal 0, game.match_details.wicket_pending
end
def test_tie
puts "\n\nTEST SCENARIO: TIE"
p1 = Player.new(PLAYER1, {
0 => 0,
1 => 1,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
p2 = Player.new(PLAYER2, {
0 => 0,
1 => 1,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
player_line_up = [p1, p2]
commentator = Commentator.new
team = Team.new(TEAM1, player_line_up)
game = Cricket.new(4, team, 25, commentator)
game.simulate_match
assert_equal TIE, game.match_details.result[TEAM1]
end
def test_win
puts "\n\nTEST SCENARIO: WIN"
p1 = Player.new(PLAYER1, {
0 => 0,
1 => 1,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
p2 = Player.new(PLAYER2, {
0 => 0,
1 => 1,
2 => 0,
3 => 0,
4 => 0,
5 => 0,
6 => 0,
:out => 0
})
player_line_up = [p1, p2]
team = Team.new(TEAM1, player_line_up)
commentator = Commentator.new
game = Cricket.new(4, team, 5, commentator)
game.simulate_match
assert_equal WIN, game.match_details.result[TEAM1]
end
end
require_relative "cricket_simulation"
include CricketSimulation
# set-up
p1 = Player.new("Kirat Boli", {
0 => 0.05,
1 => 0.30,
2 => 0.25,
3 => 0.10,
4 => 0.15,
5 => 0.01,
6 => 0.09,
:out => 0.05
})
p2 = Player.new("N.S Nodhi", {
0 => 0.10,
1 => 0.40,
2 => 0.20,
3 => 0.05,
4 => 0.10,
5 => 0.01,
6 => 0.04,
:out => 0.10
})
p3 = Player.new("R Rumrah", {
0 => 0.20,
1 => 0.30,
2 => 0.15,
3 => 0.05,
4 => 0.05,
5 => 0.01,
6 => 0.04,
:out => 0.20
})
p4 = Player.new("Sashi Henra", {
0 => 0.30,
1 => 0.25,
2 => 0.05,
3 => 0.00,
4 => 0.05,
5 => 0.01,
6 => 0.04,
:out => 0.30
})
player_line_up = [p1, p2, p3, p4]
team = Team.new("Lengaburu", player_line_up)
commentator = Commentator.new
overs = 4
to_win = 40
game = Cricket.new(overs, team, to_win, commentator)
# simulation
game.simulate_match
require_relative "cricket_simulation"
include CricketSimulation
# set-up
p1 = Player.new("Kirat Boli", {
0 => 0.05,
1 => 0.10,
2 => 0.25,
3 => 0.10,
4 => 0.25,
5 => 0.01,
6 => 0.14,
:out => 0.10
})
p2 = Player.new("N.S Nodhi", {
0 => 0.05,
1 => 0.15,
2 => 0.15,
3 => 0.10,
4 => 0.20,
5 => 0.01,
6 => 0.19,
:out => 0.15
})
team1 = Team.new("Lengaburu", [p1, p2])
team1.first_batting = true
p3 = Player.new("DB Vellyers", {
0 => 0.05,
1 => 0.10,
2 => 0.25,
3 => 0.10,
4 => 0.25,
5 => 0.01,
6 => 0.14,
:out => 0.10
})
p4 = Player.new("H Mamia", {
0 => 0.10,
1 => 0.15,
2 => 0.15,
3 => 0.10,
4 => 0.20,
5 => 0.01,
6 => 0.19,
:out => 0.10
})
team2 = Team.new("Enchai", [p3, p4])
overs = 1
Cricket.tie_breaker_game(team1, team2, overs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment