# Please Review My First Ruby Program

**URL:** <https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053>\
**Category:** Ruby\
**Created:** [October 20, 2009, 7:24pm UTC](https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053 "2009-10-20T19:24:42Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![dmbfansim](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ed8c4c/32.png) [@dmbfansim](https://www.ruby-forum.com/u/dmbfansim)\
**Post date:** [October 20, 2009, 7:24pm UTC](https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053/1 "2009-10-20T19:24:42Z")

</div>

I’ve written a simple program to simulate a Soccer game. Its the first  
full program I’ve written in Ruby, so please forgive any obvious  
mistakes. 🙂 In advance, thanks for your comments. It requires Ruby  
1.9. Please run the unit tests to see the program in action.

Simeon

$ ruby TestSoccerGame.rb

* * *

### TestSoccerGame.rb

require “SoccerGame”  
require “test/unit”

class TestSoccerGame \< Test::Unit::TestCase

# MÃ©s que un club

FC\_BARCELONA = {  
‘club’ =\> ‘FC Barcelona’,  
‘players’ =\> [  
[1, ‘Victor Valdes Arribas’, :keeper],  
[20, ‘Daniel Alves da Silva’, :defender],  
[3, ‘Gerard Pique Bernabeu’, :defender],  
[4, ‘Rafael Marquez Alvarez’, :defender],  
[5, ‘Carles Puyol Saforcada’, :defender],  
[6, ‘Xavier Hernandez Creus’, :midfielder],  
[8, ‘Andres Iniesta Lujan’, :midfielder],  
[15, ‘Seydou Keita’, :midfielder],  
[9, ‘Zlatan Ibrahimovic’, :striker],  
[10, ‘Lionel Andres Messi’, :striker],  
[14, ‘Thierry Henry’, :striker]  
]  
}

# You’ll Never Walk Alone

LIVERPOOL\_FC = {  
‘club’ =\> ‘Liverpool FC’,  
‘players’ =\> [  
[25, ‘Pepe Reina’, :keeper],  
[2, ‘Glen J.’, :defender],  
[23, ‘Jamie Carragher’, :defender],  
[37, ‘Martin Skrtel’, :defender],  
[22, ‘Emiliano Insua’, :defender],  
[20, ‘Javier Mascherano’,:midfielder],  
[8, ‘Steven Gerrard’, :midfielder],  
[11, ‘Albert Riera’, :midfielder],  
[15, ‘Yossi Benayoun’, :midfielder],  
[9, ‘Fernando Torres’, :striker],  
[18, ‘Dirk Kuyt’, :striker]  
]  
}

def setup  
if RUBY\_VERSION.to\_f \< 1.9  
flunk(“SoccerGame requires Ruby 1.9 or greater”)  
end

```
# @g used for all tests except full game test
@g = SoccerGame.new FC_BARCELONA, LIVERPOOL_FC

```

end

# Full game test

def test\_champions\_league\_final  
@f = SoccerGame.new FC\_BARCELONA, LIVERPOOL\_FC  
@f.title = “UEFA Champions League Final”

```
# Starting lineups
assert @f.home
assert @f.away
assert_equal 11, @f.home.players.length
assert_equal 11, @f.away.players.length

# Kickoff
assert @f.events(:first_half_kickoff)
assert @f.first_half
assert_equal false, @f.second_half

# Show Alves yellow
assert_block "Dani Alves" do
  @f.yellow_card(:home, 20)
end
assert_equal 1, @f.yellow_cards[:home].length

# El Nino sneaks one in
@f.goal(:away, 9)
assert_equal 1, @f.score[:away]
assert_equal 1, @f.goals[:away][9]

# Halftime
assert @f.events(:half_time)
assert_equal false, @f.first_half
assert_equal false, @f.second_half

# Start second half
assert @f.events(:second_half_kickoff)
assert @f.second_half
assert_equal false, @f.first_half

# Show Skrtel red
away_team_length = @f.away.players.length
assert_block "Skrtel wasn't sent to the showers" do
  @f.red_card(:away, 37)
end
assert_equal away_team_length-1, @f.away.players.length
assert_equal 1, @f.red_cards[:away].length

# Xavi ties the match
@f.goal(:home, 6)
assert_equal 1, @f.score[:home]
assert_equal 1, @f.goals[:home][6]

# Messi seals the victory
@f.goal(:home, 10)
assert_equal 2, @f.score[:home]
assert_equal 1, @f.goals[:home][10]

# Fulltime
assert @f.events(:final)
assert_equal false, @f.second_half
assert_equal false, @f.first_half

# Champions League Final stats
puts @f.stats

```

end

# A home and away team are required

def test\_home\_away  
assert @g.home  
assert @g.away  
end

# Both home and away teams must have 11 players

def test\_starting\_lineups  
assert\_equal 11, @g.home.players.length  
assert\_equal 11, @g.away.players.length  
end

# Positions normally have at least a certian number players

def test\_players\_per\_position  
assert\_equal 1, @g.home.keepers.length  
assert\_operator 3, :\<=, @g.home.defenders.length  
assert\_operator 3, :\<=, @g.home.midfielders.length  
assert\_operator 1, :\<=, @g.home.strikers.length  
assert\_equal 1, @g.away.keepers.length  
assert\_operator 3, :\<=, @g.away.defenders.length  
assert\_operator 3, :\<=, @g.away.midfielders.length  
assert\_operator 1, :\<=, @g.away.strikers.length  
end

def test\_first\_half\_kickoff  
assert @g.events(:first\_half\_kickoff)  
assert @g.first\_half  
assert\_equal false, @g.second\_half  
end

def test\_half\_time  
assert @g.events(:half\_time)  
assert\_equal false, @g.first\_half  
assert\_equal false, @g.second\_half  
end

def test\_second\_half\_kickoff  
assert @g.events(:second\_half\_kickoff)  
assert @g.second\_half  
assert\_equal false, @g.first\_half  
end

def test\_final\_whistle  
assert @g.events(:final)  
assert\_equal false, @g.second\_half  
assert\_equal false, @g.first\_half  
end

def test\_cards  
# Show Pique yellow  
assert\_block “Pique isn’t cautioned” do  
@g.yellow\_card(:home, 3)  
end  
assert\_equal 1, @g.yellow\_cards[:home].length

```
# Show Skrtel red
away_team_length = @g.away.players.length
assert_block "Skrtel wasn't sent to the showers" do
  @g.red_card(:away, 37)
end
assert_equal away_team_length-1, @g.away.players.length
assert_equal 1, @g.away.showers.length
assert_equal 1, @g.red_cards[:away].length

# Show Pique second yellow then red
home_team_length = @g.home.players.length
assert_block "Pique wasn't sent off" do
  @g.yellow_card(:home, 3)
end
assert_equal home_team_length-1, @g.home.players.length
assert_equal 1, @g.red_cards[:home].length

```

end

def test\_scoring\_a\_goals  
# Messi opens the scoring  
@g.goal(:home, 10)  
assert\_equal 1, @g.score[:home]  
assert\_equal 1, @g.goals[:home][10]

```
# El Nino sneaks one in
@g.goal(:away, 9)
assert_equal 1, @g.score[:away]
assert_equal 1, @g.goals[:away][9]

# Ibra is so smooth
@g.goal(:home, 9)
assert_equal 2, @g.score[:home]
assert_equal 1, @g.goals[:home][9]

# Messi seals the victory
@g.goal(:home, 10)
assert_equal 3, @g.score[:home]
assert_equal 2, @g.goals[:home][10]

```

end

end

### SoccerGame.rb

require “SoccerTeam”  
require “SoccerStats”

class SoccerGame

attr\_accessor :title

attr\_reader :home, :away,  
:yellow\_cards, :red\_cards,  
:clock, :score, :goals,  
:first\_half, :second\_half

def initialize(home, away)  
@home = SoccerTeam.new(home[‘club’],home[‘players’])  
@away = SoccerTeam.new(away[‘club’],away[‘players’])  
@yellow\_cards = {:home =\> [], :away =\> []}  
@red\_cards = {:home =\> [], :away =\> []}  
@score = {:home =\> 0, :away =\> 0}  
@goals = {:home =\> {}, :away =\> {}}  
@first\_half = false  
@second\_half = false  
end

def stats  
s = SoccerStats.new  
stats = “\n”+s.title(self.title)  
stats \<\< s.teams(@home.club, @away.club)  
stats \<\< s.final\_score(@score[:home], @score[:away])  
stats \<\< “\n”+s.cards(:yellow, @yellow\_cards[:away], @away)  
stats \<\< s.cards(:yellow, @yellow\_cards[:home], @home)  
stats \<\< s.cards(:red, @red\_cards[:away], @away)  
stats \<\< s.cards(:red, @red\_cards[:home], @home)  
stats \<\< s.scorers(@goals, @home, @away)  
stats  
end

def events(whistle)  
case whistle  
when :first\_half\_kickoff then @first\_half = true  
when :half\_time then @first\_half = false  
when :second\_half\_kickoff then @second\_half = true  
when :final then @second\_half = false  
end  
@clock = Time.now.to\_i  
end

def count\_players\_on\_pitch  
msg = “team doesn’t have the right number of players on the pitch”  
if @home.players.length != 11  
raise “Home” + msg  
elsif @away.players.length != 11  
raise “Away” + msg  
end  
end

def yellow\_card(team, player)  
# Second yellow card so, show a red  
if @yellow\_cards[team].index(player)  
@yellow\_cards[team].push player  
red\_card(team, player)  
else  
@yellow\_cards[team].push player  
end  
end

def red\_card(team, player)  
@red\_cards[team].push player  
if team == :home  
# Send player to the showers  
@home.showers[player] = @home.players[player]  
@home.players.delete(player)  
else  
@away.showers[player] = @away.players[player]  
@away.players.delete(player)  
end  
end

def goal(team, player)  
@score[team] += 1  
if @goals[team][player]  
@goals[team][player] += 1  
else  
@goals[team][player] = 1  
end  
end

end

### SoccerTeam.rb

class SoccerTeam

attr\_accessor :showers

attr\_reader :club, :players,  
:keepers, :defenders, :midfielders, :strikers

def initialize(club, players)  
@club = club  
@players = {}  
@keepers = {}  
@defenders = {}  
@midfielders = {}  
@strikers = {}  
@showers = {}  
players.each do |number, name, position|  
@players[number] = name  
# Push player into position  
case position  
when :keeper then @keepers[number] = name  
when :defender then @defenders[number] = name  
when :midfielder then @midfielders[number] = name  
when :striker then @strikers[number] = name  
end  
end  
end

end

### SoccerStats.rb

class SoccerStats

def title(name)  
self.own\_line(name)  
end

def teams(home,away)  
self.own\_line(away+" visiting "+home)  
end

def final\_score(home,away)  
self.own\_line(away.to\_s+" / "+home.to\_s)  
end

def cards(type, cards, team)  
str = “”  
if !cards.empty?  
if (type == :yellow)  
str \<\< “Yellow”  
# Player hasn’t been deleted from the team  
# He was only shown a yellow card  
player\_hash = team.players  
else  
str \<\< “Red”  
# Player was deleted from the team  
# He was shown a red card  
player\_hash = team.showers  
end  
str \<\< " Cards:\n"

```
  cards.each do |number|
    str << number.to_s+"/"+player_hash[number]+"/"+team.club+"\n"
  end

  str = self.own_line(str)
end
str

```

end

def scorers(goals, home, away)  
str = “”  
if !goals.empty?  
# Loop through each teams’ goals, and print player number, name  
and goals scored  
{:home=\>home,:away=\>away}.each do |key, team|  
if goals.has\_key?(key)  
goals[key].each do |number, goals|  
str \<\< number.to\_s+"/"+team.players[number]+"/"+team.club+"  
scores “+goals.to\_s+” goal"  
if goals \> 1  
str \<\< “s”  
end  
str \<\< “\n”  
end  
end  
end  
str = self.own\_line(“Goals:”)+self.own\_line(str)  
end  
str  
end

def own\_line(str)  
str+"\n"  
end  
end

---

<div class="post-metadata">

**Author:** ![dmbfansim](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ed8c4c/32.png) [@dmbfansim](https://www.ruby-forum.com/u/dmbfansim)\
**Post date:** [October 20, 2009, 11:16pm UTC](https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053/2 "2009-10-20T23:16:18Z")

</div>

Hi Simeon –

On Wed, 21 Oct 2009, Simeon W. wrote:

> I’ve written a simple program to simulate a Soccer game. Its the first  
> full program I’ve written in Ruby, so please forgive any obvious  
> mistakes. 🙂 In advance, thanks for your comments. It requires Ruby  
> 1.9. Please run the unit tests to see the program in action.

Your code looks very nice and overall very idiomatic. I’ve got a few  
things to suggest. They’re not necessarily things you urgently need to  
do, but I think they’ll interest you as things to think about for this  
or future programs.

> :clock, :score, :goals,  
> :first\_half, :second\_half
> 
> def initialize(home, away)  
> @home = SoccerTeam.new(home[‘club’],home[‘players’])  
> @away = SoccerTeam.new(away[‘club’],away[‘players’])  
> @yellow\_cards = {:home =\> [], :away =\> []}  
> @red\_cards = {:home =\> [], :away =\> []}  
> @score = {:home =\> 0, :away =\> 0}  
> @goals = {:home =\> {}, :away =\> {}}

If you change that to:

@goals = { :home =\> Hash.new(0), :away =\> Hash.new(0) }

then later, you can do:

def goal(team, player)  
@score[team] += 1  
@goals[team][player] += 1  
end

without having to test for the presence of the player key.

This method:

> def yellow\_card(team, player)
> 
> # Second yellow card so, show a red
> 
> if @yellow\_cards[team].index(player)  
> @yellow\_cards[team].push player  
> red\_card(team, player)  
> else  
> @yellow\_cards[team].push player  
> end  
> end

could be:

def yellow\_card(team, player)  
if @yellow\_cards[team].include?(player)  
red\_card(team, player)  
end  
@yellow\_cards[team].push player  
end

which represents the logic a little more cleanly, I think.

This one:

> end  
> could conceivably be this:

def red\_card(team, player)  
@red\_cards[team].push player  
send(team).send\_to\_showers(player)  
end

However, you have to write a send\_to\_showers method in the SoccerTeam  
class:

def send\_to\_showers(player)  
showers[player] = players.delete(player)  
end

That’s actually good anyway, because it puts the business logic of the  
team in the Team class. (Note that Hash#delete returns the deleted  
value, so you can assign it directly to the other hash in one  
operation.)

As for the “send(team)” bit: what I’m doing is taking advantage of the  
fact that you have “home” and “away” reader attributes, and your team  
variable is (we hope) either :home or :away. So I’m sending that  
symbol to myself… and that will return either my home team or my  
away team. That way, you don’t have to inline essentially the same  
code twice.

The other thing I played around with was creating a SoccerPlayer  
class, and adding SoccerPlayer objects to the players hash in the team  
object. Ultimately I think that’s a good idea, so that players can be  
traded, queried for career stats outside of one particular team, etc.

Finally, I’d recommend splitting out the constants, so that you have:

module Soccer  
class Team

and so on. It’s a little more idiomatic, and makes the namespacing  
clear.

David

–  
The Ruby training with D. Black, G. Brown, J.McAnally  
Compleat Jan 22-23, 2010, Tampa, FL  
Rubyist [http://www.thecompleatrubyist.com](http://www.thecompleatrubyist.com)

David A. Black/Ruby Power and Light, LLC ([http://www.rubypal.com](http://www.rubypal.com))

---

<div class="post-metadata">

**Author:** ![dmbfansim](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ed8c4c/32.png) [@dmbfansim](https://www.ruby-forum.com/u/dmbfansim)\
**Post date:** [October 21, 2009, 1:59am UTC](https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053/3 "2009-10-21T01:59:36Z")

</div>

David,

I appreciate the feedback. I’ll work on incorporating your suggestions,  
and I’ll repost the results.

Thanks,  
Simeon

---

<div class="post-metadata">

**Author:** ![dmbfansim](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ed8c4c/32.png) [@dmbfansim](https://www.ruby-forum.com/u/dmbfansim)\
**Post date:** [October 21, 2009, 7:20pm UTC](https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053/4 "2009-10-21T19:20:38Z")

</div>

David,  
I would like to see your code to your first ruby program so that I my  
give you what I think of too.  
JamesÂ

— On Tue, 10/20/09, Simeon W. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

From: Simeon W. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Subject: Re: Please Review My First Ruby P.  
To: “ruby-talk ML” [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Date: Tuesday, October 20, 2009, 5:59 PM

David,

I appreciate the feedback.Â I’ll work on incorporating your suggestions,  
and I’ll repost the results.

Thanks,  
Simeon

---

<div class="post-metadata">

**Author:** ![dmbfansim](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/d/ed8c4c/32.png) [@dmbfansim](https://www.ruby-forum.com/u/dmbfansim)\
**Post date:** [October 21, 2009, 7:29pm UTC](https://www.ruby-forum.com/t/please-review-my-first-ruby-program/226053/5 "2009-10-21T19:29:25Z")

</div>

Hi –

On Thu, 22 Oct 2009, James N. wrote:

> David,  
> I would like to see your code to your first ruby program so that I  
> my give you what I think of too.

[http://www.wobblini.net/jotto.rb](http://www.wobblini.net/jotto.rb)

David

–  
The Ruby training with D. Black, G. Brown, J.McAnally  
Compleat Jan 22-23, 2010, Tampa, FL  
Rubyist [http://www.thecompleatrubyist.com](http://www.thecompleatrubyist.com)

David A. Black/Ruby Power and Light, LLC ([http://www.rubypal.com](http://www.rubypal.com))
