monotony 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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