monotony 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.rdoc_options +17 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +78 -0
  7. data/Rakefile +1 -0
  8. data/bin/console +14 -0
  9. data/bin/setup +7 -0
  10. data/lib/monotony.rb +22 -0
  11. data/lib/monotony/.yardoc/checksums +0 -0
  12. data/lib/monotony/.yardoc/object_types +0 -0
  13. data/lib/monotony/.yardoc/objects/root.dat +0 -0
  14. data/lib/monotony/.yardoc/proxy_types +0 -0
  15. data/lib/monotony/basicproperty.rb +174 -0
  16. data/lib/monotony/behaviour.rb +103 -0
  17. data/lib/monotony/chance.rb +53 -0
  18. data/lib/monotony/communitychest.rb +57 -0
  19. data/lib/monotony/doc/_index.html +88 -0
  20. data/lib/monotony/doc/class_list.html +58 -0
  21. data/lib/monotony/doc/css/common.css +1 -0
  22. data/lib/monotony/doc/css/full_list.css +57 -0
  23. data/lib/monotony/doc/css/style.css +339 -0
  24. data/lib/monotony/doc/file_list.html +57 -0
  25. data/lib/monotony/doc/frames.html +26 -0
  26. data/lib/monotony/doc/index.html +88 -0
  27. data/lib/monotony/doc/js/app.js +219 -0
  28. data/lib/monotony/doc/js/full_list.js +181 -0
  29. data/lib/monotony/doc/js/jquery.js +4 -0
  30. data/lib/monotony/doc/method_list.html +57 -0
  31. data/lib/monotony/doc/top-level-namespace.html +102 -0
  32. data/lib/monotony/game.rb +307 -0
  33. data/lib/monotony/player.rb +191 -0
  34. data/lib/monotony/purchasable.rb +131 -0
  35. data/lib/monotony/square.rb +23 -0
  36. data/lib/monotony/station.rb +24 -0
  37. data/lib/monotony/utility.rb +23 -0
  38. data/lib/monotony/variants.rb +405 -0
  39. data/lib/monotony/version.rb +4 -0
  40. data/monotony.gemspec +33 -0
  41. metadata +125 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 794754b6881e940805c2ece03fb4a204ef8f40bf
4
+ data.tar.gz: 2e5a91ef47ee68cbc1c920ad8af18949881a2d63
5
+ SHA512:
6
+ metadata.gz: 0dbbdc07c2e4007d6335b3edaa17f2fd5634599b5fb4dfe865066e312ef43d84f271934f0d03d075715092e2c34b5f84c66935c12751db60ea869faab52f9fe0
7
+ data.tar.gz: f68ae2180ee5880fa4d90479e164498d96fcd99d2b166db16aa4e02fe73c973f8e55c3aaf6f3fb591cbddc70784b9ebf3f3a3e899f03f068c1e9fa2c6a2f6dc9
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rdoc_options ADDED
@@ -0,0 +1,17 @@
1
+ --- !ruby/object:RDoc::Options
2
+ encoding: UTF-8
3
+ static_path: []
4
+ rdoc_include:
5
+ - .
6
+ charset: UTF-8
7
+ exclude:
8
+ hyperlink_all: false
9
+ line_numbers: false
10
+ main_page:
11
+ markup: markdown
12
+ page_dir:
13
+ show_hash: false
14
+ tab_width: 8
15
+ title:
16
+ visibility: :protected
17
+ webcvs:
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in monotony.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 James Denness
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Monotony
2
+
3
+ This gem is an engine to simulate games of Monopoly.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'monotony'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install monotony
20
+
21
+ ## Usage
22
+
23
+ To play a quick game of Monopoly, with classic board layout and four randomly generated players"
24
+
25
+ ```ruby
26
+
27
+ game = Monotony::Game.new
28
+ game.play
29
+
30
+ # See the results of the game
31
+ game.summary
32
+
33
+ ```
34
+
35
+ You can step through the game a few turns at a time, and use the ```summary``` method to view an ASCII representation of the state of the game.
36
+
37
+ ```ruby
38
+ game.play(10).summary
39
+ ```
40
+
41
+ ```ruby
42
+ monopoly_players = [
43
+ Player.new( name: 'James', behaviour: behaviour ),
44
+ Player.new( name: 'Jody', behaviour: behaviour ),
45
+ Player.new( name: 'Ryan', behaviour: behaviour ),
46
+ Player.new( name: 'Tine', behaviour: behaviour )
47
+ ]
48
+
49
+ monopoly = Monotony::Game.new(
50
+ board: monopoly_board,
51
+ chance: chance,
52
+ community_chest: community_chest,
53
+ num_dice: 2,
54
+ die_size: 6,
55
+ starting_currency: 1500,
56
+ bank_balance: 12755,
57
+ num_hotels: 12,
58
+ num_houses: 48,
59
+ go_amount: 200,
60
+ max_turns_in_jail: 3,
61
+ players: monopoly_players
62
+ )```
63
+
64
+ ## Development
65
+
66
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
67
+
68
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
69
+
70
+ ## Contributing
71
+
72
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/monotony.
73
+
74
+
75
+ ## License
76
+
77
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
78
+
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "monotony"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/lib/monotony.rb ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'pp'
4
+ require 'colorize'
5
+
6
+ require 'monotony/basicproperty'
7
+ require 'monotony/chance'
8
+ require 'monotony/communitychest'
9
+ require 'monotony/game'
10
+ require 'monotony/player'
11
+ require 'monotony/purchasable'
12
+ require 'monotony/square'
13
+ require 'monotony/station'
14
+ require 'monotony/utility'
15
+ require 'monotony/variants'
16
+ require 'monotony/behaviour'
17
+
18
+ # The Monopoly Engine!
19
+ # @author James Denness <james@recordlive.net>
20
+ module Monotony
21
+
22
+ end
File without changes
Binary file
Binary file
@@ -0,0 +1,174 @@
1
+ require 'monotony/square'
2
+ require 'monotony/purchasable'
3
+
4
+ module Monotony
5
+ # A property class representing the majority of properties on the board.
6
+ class BasicProperty < PurchasableProperty
7
+ # @return [Integer] Returns the cost of purchasing a house on this property.
8
+ attr_accessor :house_cost
9
+ # @return [Integer] Returns the cost of purchasing a hotel on this property.
10
+ attr_accessor :hotel_cost
11
+ # @return [Symbol] Returns the name of the set containing this property.
12
+ attr_accessor :set
13
+ # @return [Integer] Returns the number of houses on this property.
14
+ attr_accessor :num_houses
15
+ # @return [Integer] Returns the number of hotels on this property.
16
+ attr_accessor :num_hotels
17
+ # @return [Array<Integer>] Returns an array of six elements containing rent values for an property with no houses, one house, two houses, three houses, four houses, and a hotel.
18
+ attr_accessor :rent
19
+ # @param opts [Hash]
20
+ # @option opts [Array<Integer>] :rent An array of six elements containing rent values for an property with no houses, one house, two houses, three houses, four houses, and a hotel.
21
+ # @option opts [Integer] :house_cost The cost of purchasing a house on this property.
22
+ # @option opts [Integer] :hotel_cost The cost of purchasing a hotel on this property. Traditionally equal to house_cost.
23
+ # @option opts [Symbol] :set A symbol identifying this property as a member of a set of properties.
24
+ def initialize(opts)
25
+ super
26
+ @rent = opts[:rent]
27
+ @house_cost = opts[:house_cost]
28
+ @hotel_cost = opts[:hotel_cost]
29
+ @set = opts[:set]
30
+ @num_houses = 0
31
+ @num_hotels = 0
32
+ @action = Proc.new do |game, owner, player, property|
33
+ if owner
34
+ if set_owned?
35
+ rent_to_pay = (property.num_hotels == 1 ? property.rent[5] : ( property.num_houses == 0 ? (@rent[0] * 2) : property.rent[property.num_houses] ) )
36
+ else
37
+ rent_to_pay = property.rent[0]
38
+ end
39
+ if owner != player
40
+ if not owner.is_out? and not is_mortgaged?
41
+ puts '[%s] Due to pay £%d rent to %s for landing on %s with %s' % [ player.name, rent_to_pay, owner.name, property.name, ( property.num_hotels == 1 ? 'a hotel' : '%d houses' % property.num_houses) ]
42
+ player.pay(owner, rent_to_pay)
43
+ end
44
+ end
45
+ else
46
+ player.behaviour[:purchase_possible].call(game, player, self) if player.currency >= cost
47
+ end
48
+ end
49
+ end
50
+
51
+ # Mortgage the property to raise cash for its owner.
52
+ # @return [self]
53
+ def mortgage
54
+ super
55
+ properties_in_set(@owner.game).each do |other|
56
+ other.sell_hotel if @num_hotels > 0
57
+ other.sell_houses if @num_houses > 0
58
+ end
59
+ self
60
+ end
61
+
62
+ # Buy houses on the property.
63
+ # @param [Integer] number number of houses to add to the property.
64
+ # @return [self]
65
+ def add_houses(number)
66
+ housing_value = @house_cost * number
67
+ if @owner.game.num_houses >= number
68
+ if (@num_houses + number) > 4
69
+ puts '[%s] Cannot place more than 4 houses on %s' % [ @owner.name, @name ]
70
+ else
71
+ if @owner.currency < housing_value
72
+ puts '[%s] Unable to buy %d houses! (short of cash by £%d)' % [ @owner.name, number, (housing_value - @owner.currency) ]
73
+ false
74
+ else
75
+ @owner.currency = @owner.currency - housing_value
76
+ @owner.game.num_houses = @owner.game.num_houses - number
77
+ @num_houses = @num_houses + number
78
+ puts '[%s] Purchased %d houses on %s for £%d (new balance: £%d)' % [ @owner.name, number, @name, housing_value, @owner.currency ]
79
+ true
80
+ end
81
+ end
82
+ else
83
+ puts '[%s] Not enough houses left to purchase %d more for %s' % [ @owner.name, number, @name ]
84
+ end
85
+ self
86
+ end
87
+
88
+ # Sell houses from the property.
89
+ # @param [Integer] number number of houses to sell from the property.
90
+ # @return [self]
91
+ def sell_houses(number)
92
+ housing_value = (@house_cost / 2) * number
93
+ if number > @num_houses
94
+ puts "[%s] Can't sell %d houses on %s, as there are only %d" % [ @owner.name, number, @name, @num_houses ]
95
+ false
96
+ else
97
+ @num_houses = @num_houses - number
98
+ @owner.game.num_houses = @owner.game.num_houses + number
99
+ @owner.currency = @owner.currency + housing_value
100
+ puts '[%s] Sold %d houses on %s for £%d (%d remaining)' % [ @owner.name, number, @name, housing_value, @num_houses ]
101
+ end
102
+ self
103
+ end
104
+
105
+ # Buy a hotel on the property.
106
+ # @return [self]
107
+ def add_hotel
108
+ if @num_houses == 4
109
+ if @owner.game.num_houses > 0
110
+ if @owner.currency < @hotel_cost
111
+ puts '[%s] Unable to buy a hotel! (short of cash by £%d)' % [ @owner.name, (@hotel_cost - @owner.currency) ]
112
+ else
113
+ @owner.currency = @owner.currency - @hotel_cost
114
+ @num_houses, @num_hotels = 0, 1
115
+ @owner.game.num_houses = @owner.game.num_houses + 4
116
+ @owner.game.num_hotels = @owner.game.num_hotels - 1
117
+ puts '[%s] Purchased a hotel on %s for £%d (new balance: £%d)' % [ @owner.name, @name, @hotel_cost, @owner.currency ]
118
+ end
119
+ else
120
+ puts '[%s] Not enough hotels left to purchase one for %s' % [ @owner.name, @name ]
121
+ end
122
+ end
123
+ self
124
+ end
125
+
126
+ # Sell hotels from the property.
127
+ # @return [self]
128
+ def sell_hotel
129
+ if @num_hotels < 1
130
+ puts "[%s] Can't sell hotel on %s, as there isn't one!" % [ @owner.name, @name ]
131
+ else
132
+ housing_value = (@hotel_cost / 2)
133
+ @num_hotels = 0
134
+ @owner.game.num_hotels = @owner.game.num_hotels + 1
135
+ @owner.currency = @owner.currency + housing_value
136
+ puts '[%s] Sold hotel on %s for £%d' % [ @owner.name, @name, housing_value ]
137
+ case @owner.game.num_houses
138
+ when 1..3
139
+ sell_houses(4 - @owner.game.num_houses)
140
+ puts '[%s] Devolved %s to %d houses as 4 were not available' % [ @owner.name, @name, @num_houses ]
141
+ when 0
142
+ sell_houses(4)
143
+ puts '[%s] Devolved to undeveloped site as no houses were available' % [ @owner.name, @name, @num_houses ]
144
+ else
145
+ @owner.game.num_houses = @owner.game.num_houses - 4
146
+ @num_houses = 4
147
+ puts '[%s] Devolved %s to %d houses' % [ @owner.name, @name, @num_houses ]
148
+ end
149
+ end
150
+ self
151
+ end
152
+
153
+ # Draw an ASCII representation of this property's housing.
154
+ # @return [void]
155
+ def display_house_ascii
156
+ house_array = []
157
+ house_string = ''
158
+
159
+ if @num_hotels == 1
160
+ house_string << ' '.colorize(:background => :light_black) + ' '.colorize(:background => :red) + ' '.colorize(:background => :light_black)
161
+ else
162
+ until house_array.length == @num_houses
163
+ house_array << ' '.colorize(:background => :green)
164
+ end
165
+ until house_array.length == 4
166
+ house_array << ' '.colorize(:background => :light_black)
167
+ end
168
+
169
+ house_string = house_array.join(' '.colorize(:background => :light_black))
170
+ end
171
+ house_string + ' '.colorize(:color => :default)
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,103 @@
1
+ module Monotony
2
+ # Conains predefined behaviours
3
+ class DefaultBehaviour
4
+ DEFAULT = {
5
+ purchase_possible: Proc.new { |game, player, property|
6
+ if player.properties.collect{ |p| p.set }.include? property.set
7
+ # Will definitely buy if player already owns one or more of this set
8
+ property.sell_to(player)
9
+ elsif game.players.collect { |p| p.properties.collect { |p| p.set } }.flatten.include? property.set
10
+ # Less likely to buy if another player already owns one of the set
11
+ property.sell_to(player) if Random.rand(0..100) >= 75
12
+ else
13
+ # Will probably buy if nobody has bought any of this set yet
14
+ property.sell_to(player) if Random.rand(0..100) >= 25
15
+ end
16
+ },
17
+ unmortgage_possible: Proc.new { |game, player, property|
18
+ # Only bother unmortgaging something if I have the rest of the set, or it's less than 15% of my cash
19
+ if player.sets_owned.include? property.set
20
+ property.unmortgage
21
+ elsif ( property.cost.to_f / player.currency.to_f * 100.0 ) < 15.0
22
+ property.unmortgage
23
+ end
24
+ },
25
+ houses_available: Proc.new {|game, player, property|
26
+ # Buy houses when possible, but don't spend more than 40% of my money on them in any one turn
27
+ can_afford = ( ( player.currency * 0.4 ) / property.house_cost ).floor
28
+ max_available = 4 - property.num_houses
29
+ to_buy = [ can_afford, max_available ].min
30
+ property.add_houses(to_buy) if to_buy > 0 unless game.active_players == 1
31
+ },
32
+ hotel_available: Proc.new {|game, player, property|
33
+ # Buy a hotel, unless it's more than half my current balance.
34
+ property.add_hotel unless ( property.hotel_cost.to_f / player.currency.to_f * 100.0) > 50.0
35
+ },
36
+ money_trouble: Proc.new {|game, player, amount|
37
+ portfolio = player.properties.sort_by { |p| p.mortgage_value }
38
+ while player.currency < amount do
39
+ if portfolio.length > 0
40
+ property = portfolio.shift
41
+ if property.is_a? BasicProperty
42
+ if property.num_hotels == 1
43
+ property = property.sell_hotel
44
+ end
45
+ break if player.currency >= amount
46
+
47
+ while property.num_houses > 0
48
+ property = property.sell_houses(1)
49
+ break if player.currency >= amount
50
+ end
51
+ break if player.currency >= amount
52
+
53
+ property = property.mortgage
54
+ end
55
+ else
56
+ break
57
+ end
58
+ end
59
+ },
60
+ use_jail_card: Proc.new {|game, player|
61
+ # Unless less than 50% of active sets are mine, get out of jail with a card when possible
62
+ player.use_jail_card! unless ( player.sets_owned.count.to_f / game.all_sets_owned.count.to_f * 100.0 ) < 50
63
+ },
64
+ trade_possible: Proc.new {|game, player|
65
+ puts '[%s] Considering possible trades' % player.name
66
+ invested_colours = player.properties.collect(&:set).uniq
67
+ player.opponents.each do |opponent|
68
+ opponent.properties.select { |r| invested_colours.include? r.set }.each do |desirable_property|
69
+ factors = {}
70
+ # e.g. 66% chance of buying if one property is owned, 99% chance of buying if two are
71
+ factors[:number_owned] = ( desirable_property.number_of_set_owned.to_f + 1.0 ) / desirable_property.number_in_set(game).to_f
72
+ # More likely to trade if player has over £1000
73
+ factors[:currency] = player.currency.to_f / 1000.to_f
74
+ # More likely to trade if close to GO
75
+ factors[:proximity_to_go] = 1 - ( player.distance_to_go.to_f / game.board.length.to_f )
76
+
77
+ # We use these factors to work out how much to offer relative to how much we have
78
+ offer_amount = player.currency * factors.values.inject(&:*)
79
+ if offer_amount > desirable_property.cost and player.currency >= offer_amount
80
+ puts '[%s] Placing offer of £%d on %s (owned by %s) [%f]' % [ player.name, offer_amount, desirable_property.name, desirable_property.owner.name, factors.values.inject(&:*) * 100 ]
81
+
82
+ desirable_property.place_offer(player, offer_amount)
83
+ end
84
+ end
85
+ end
86
+ },
87
+ trade_proposed: Proc.new {|game, player, proposer, property, amount|
88
+ factors = {}
89
+ # More likely to accept a trade the longer the game has been going on for (definitely at 100 turns)
90
+ factors[:longevity] = ( [0, game.turn, 100].sort[1].to_f / 100.0 ).to_f
91
+ # More likely to accept a trade if it is far over the list price
92
+ factors[:value_added] = 1 - ( property.cost.to_f / amount.to_f )
93
+ # More likely to accept a trade if low on cash
94
+ factors[:currency] = 1 - ( player.currency.to_f / 1000.to_f )
95
+
96
+ # Random element
97
+ factors[:random] = Random.rand(1..100)
98
+ puts '[%s] Considering offer of £%d for %s (from %s) [%f]' % [ player.name, amount, property.name, proposer.name, ( factors.values.collect{ |f| ( 100 / factors.count ) * f }.inject(:+) / 100 ) ]
99
+ property.sell_to(proposer, amount) if Random.rand(1..100) > ( factors.values.collect{ |f| ( 100 / factors.count ) * f }.inject(:+) / 100 )
100
+ }
101
+ }
102
+ end
103
+ end