ttr 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2010-2011 Jesse Sielaff
4
+ #
5
+
6
+ Dir[File.expand_path("../ttr/objects/*.rb", __FILE__)].sort.each {|f| require f }
7
+ Dir[File.expand_path("../ttr/entities/*.rb", __FILE__)].sort.each {|f| require f }
@@ -0,0 +1,38 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class City < Entity
7
+ # Returns true if the two Cities are connected by a single direct Route, false
8
+ # otherwise.
9
+ #
10
+ def connected? (city)
11
+ @obj.connected?(City.object(city))
12
+ end
13
+
14
+ # Returns an Array of Cities connected to the City by a single direct Route.
15
+ #
16
+ def connected_cities
17
+ City.entities(@obj.connected_city_objs)
18
+ end
19
+
20
+ # Returns the name of the City as a Symbol.
21
+ #
22
+ def name
23
+ @obj.name
24
+ end
25
+
26
+ # Returns an Array of Routes leading into and out of the City.
27
+ #
28
+ def routes
29
+ Route.entities(@obj.route_objs)
30
+ end
31
+
32
+ # Returns an Array of Routes leading into and out of the City that are not
33
+ # claimed by any Player.
34
+ #
35
+ def unclaimed_routes
36
+ Route.entities(@obj.route_objs.reject {|obj| obj.player_obj })
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Game < Entity
7
+ # Returns an Array of the colors of the Cards available for selection.
8
+ #
9
+ def available_colors
10
+ @obj.deck.available_colors
11
+ end
12
+
13
+ # Returns an Array of all Cities in the Game.
14
+ #
15
+ def cities
16
+ City.entities(@obj.city_objs)
17
+ end
18
+
19
+ # Returns an Array of all Routes in the Game.
20
+ #
21
+ def routes
22
+ Route.entities(@obj.route_objs)
23
+ end
24
+
25
+ # Returns an Array of all unclaimed Routes in the Game.
26
+ #
27
+ def unclaimed_routes
28
+ Route.entities(@obj.route_objs.reject {|obj| obj.player_obj })
29
+ end
30
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Opponent < Entity
7
+ # Returns the number of Cards held by the Opponent.
8
+ #
9
+ def cards
10
+ @obj.cards.values.flatten.length
11
+ end
12
+
13
+ # Returns an Array of the Routes claimed by the Opponent.
14
+ #
15
+ def claimed_routes
16
+ Route.entities(@obj.claimed_route_objs)
17
+ end
18
+
19
+ # Returns the current score of the Opponent.
20
+ #
21
+ def score
22
+ @obj.score
23
+ end
24
+
25
+ # Returns an Array of the color of the last n Cards selected by the Opponent.
26
+ # Cards drawn at random are represented as :grey.
27
+ #
28
+ def selections (n)
29
+ @obj.selections.first(n)
30
+ end
31
+
32
+ # Returns the number of Tickets held by the Player.
33
+ #
34
+ def tickets
35
+ @obj.kept_ticket_objs.length
36
+ end
37
+
38
+ # Returns the number of unplayed trains held by the Player.
39
+ #
40
+ def trains
41
+ @obj.trains
42
+ end
43
+ end
@@ -0,0 +1,213 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Player < Entity
7
+ # Returns an Array of the Tickets currently available for the Player to choose
8
+ # to keep or discard.
9
+ #
10
+ def candidate_tickets
11
+ Ticket.entities(@obj.candidate_ticket_objs)
12
+ end
13
+
14
+ # Returns the number of Cards of the given color held by the Player.
15
+ #
16
+ def cards (color)
17
+ @obj.cards[color].length
18
+ end
19
+
20
+ # Claims the given Route using Cards of the given color and the minimum number
21
+ # of wild Cards needed, then returns an Array of the Routes claimed by the
22
+ # Player. If the Player is unable to claim the Route with the given color,
23
+ # returns nil.
24
+ #
25
+ def claim (route, color)
26
+ return if @obj.action_begun
27
+ return if route.claimed?
28
+ return unless route.match?(color)
29
+ return if route.length > @obj.trains
30
+ return if (twin = Route.object(route).twin) && (twin.player_obj == Player.object(self))
31
+ return if (twin) && (opponents.length < 2) && (twin.player_obj)
32
+ return unless card_objs = @obj.set_of(route.length, color, wilds)
33
+
34
+ @obj.discard card_objs
35
+ @obj.claim Route.object(route)
36
+ end
37
+
38
+ # Claims the given Route using all the Player's wild Cards and as many Cards
39
+ # of the given color as needed, then returns an Array of the Routes claimed by
40
+ # the Player. If the Player is unable to claim the Route with the given color,
41
+ # returns nil.
42
+ #
43
+ def claim_max_wild (route, color)
44
+ return if @obj.action_begun
45
+ return if route.claimed?
46
+ return unless route.match?(color)
47
+ return if route.length > @obj.trains
48
+ return if (twin = Route.object(route).twin) && (twin.player_obj == Player.object(self))
49
+ return if (twin) && (opponents.length < 2) && (twin.player_obj)
50
+ return unless card_objs = @obj.set_of(route.length, color, wilds, true)
51
+
52
+ @obj.discard card_objs
53
+ @obj.claim Route.object(route)
54
+ end
55
+
56
+ # Claims the given Route using the given number of wild Cards and as many
57
+ # Cards of the given color as needed, then returns an Array of the Routes
58
+ # claimed by the Player. If the Player is unable to claim the Route with the
59
+ # given color, returns nil.
60
+ #
61
+ def claim_n_wild (route, color, n)
62
+ return if @obj.action_begun
63
+ return if route.claimed?
64
+ return unless route.match?(color)
65
+ return if route.length > @obj.trains
66
+ return if (twin = Route.object(route).twin) && (twin.player_obj == Player.object(self))
67
+ return if (twin) && (opponents.length < 2) && (twin.player_obj)
68
+ return unless card_objs = @obj.set_of(route.length, color, n, true)
69
+
70
+ @obj.discard card_objs
71
+ @obj.claim Route.object(route)
72
+ end
73
+
74
+ # Claims the given Route using only Cards of the given color (no wild Cards),
75
+ # then returns an Array of the Routes claimed by the Player. If the Player is
76
+ # unable to claim the Route with the given color, returns nil.
77
+ #
78
+ def claim_no_wild (route, color)
79
+ return if @obj.action_begun
80
+ return if route.claimed?
81
+ return unless route.match?(color)
82
+ return if route.length > @obj.trains
83
+ return if (twin = Route.object(route).twin) && (twin.player_obj == Player.object(self))
84
+ return if (twin) && (opponents.length < 2) && (twin.player_obj)
85
+ return unless card_objs = @obj.set_of(route.length, color, 0)
86
+
87
+ @obj.discard card_objs
88
+ @obj.claim Route.object(route)
89
+ end
90
+
91
+ # Returns an Array of the Routes claimed by the Player.
92
+ #
93
+ def claimed_routes
94
+ Route.entities(@obj.claimed_route_objs)
95
+ end
96
+
97
+ # Removes all candidate Tickets from the Player, marks the Player's turn as
98
+ # complete, and returns true. If the Player is unable to discard Tickets,
99
+ # returns nil.
100
+ #
101
+ def discard_tickets
102
+ return if @obj.kept_ticket_objs.length < 2
103
+ return if [0,3].include?(@obj.candidate_ticket_objs.length)
104
+
105
+ @obj.clear_ticket_objs
106
+ end
107
+
108
+ # Adds one Card to the Player and returns the color of that Card. If the
109
+ # Player is unable to draw a Card, returns nil.
110
+ #
111
+ def draw_random
112
+ return if @obj.draws_remaining < 1
113
+ return unless card = @obj.deck.draw
114
+
115
+ @obj.cards[card.color] << card
116
+ @obj.selections.unshift(:grey)
117
+ @obj.register_draw(1)
118
+
119
+ card.color
120
+ end
121
+
122
+ # Adds three candidate Tickets to the Player and returns an Array of the
123
+ # Tickets. Disallows the Player from claiming Routes, drawing Tickets, and
124
+ # drawing Cards for the remainder of the turn. Returns nil if the Player is
125
+ # unable to draw Tickets.
126
+ #
127
+ def draw_tickets
128
+ return if @obj.action_begun
129
+ return if @obj.remaining_ticket_objs.length < 3
130
+
131
+ Ticket.entities(@obj.add_tickets)
132
+ end
133
+
134
+ # Marks the given Ticket as kept by the Player. If all candidate Tickets have
135
+ # been kept, marks the Player's turn as complete and returns true. Returns
136
+ # false if some candidate Tickets remain, or nil if the Ticket was not a
137
+ # candidate Ticket.
138
+ #
139
+ def keep_ticket (ticket)
140
+ ticket_obj = Ticket.object(ticket)
141
+ return unless @obj.candidate_ticket_objs.delete(ticket_obj)
142
+
143
+ @obj.keep_ticket_obj(ticket_obj) || false
144
+ end
145
+
146
+ # Returns an Array of the Tickets kept by the Player.
147
+ #
148
+ def kept_tickets
149
+ Ticket.entities(@obj.kept_ticket_objs)
150
+ end
151
+
152
+ # Returns an Array of the Player's Opponents.
153
+ #
154
+ def opponents
155
+ Opponent.entities(@obj.all_player_objs - [@obj])
156
+ end
157
+
158
+ # Returns the current score of the Player.
159
+ #
160
+ def score
161
+ @obj.score
162
+ end
163
+
164
+ # Moves one Card of the given Color from the selection to the Player and
165
+ # returns the number of Cards of that color held by the Player. If the Player
166
+ # is not currently allowed to draw a Card of the given color, returns nil.
167
+ #
168
+ def select_card (color)
169
+ return select_wild if color == :wild
170
+
171
+ return if @obj.draws_remaining < 1
172
+ return unless card = @obj.deck.select(color)
173
+
174
+ @obj.cards[color] << card
175
+ @obj.selections.unshift(card.color)
176
+ @obj.register_draw(1)
177
+
178
+ cards(color)
179
+ end
180
+
181
+ # Moves one wild Card from the selection to the Player and returns the number
182
+ # of wild Cards held by the Player. If the Player is not currently allowed to
183
+ # draw a wild Card, returns nil.
184
+ #
185
+ def select_wild
186
+ return if @obj.draws_remaining < 2
187
+ return unless card = @obj.deck.select(:wild)
188
+
189
+ @obj.cards[:wild] << card
190
+ @obj.selections.unshift(:wild)
191
+ @obj.register_draw(2)
192
+
193
+ wilds
194
+ end
195
+
196
+ # Returns the number of unplayed trains held by the Player.
197
+ #
198
+ def trains
199
+ @obj.trains
200
+ end
201
+
202
+ # Returns true if the Player has completed the current turn.
203
+ #
204
+ def turn_completed?
205
+ @obj.turn_completed
206
+ end
207
+
208
+ # Returns the number of wild Cards held by the Player.
209
+ #
210
+ def wilds
211
+ cards(:wild)
212
+ end
213
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Route < Entity
7
+ # Returns an Array of the two Cities at either end of the Route.
8
+ #
9
+ def cities
10
+ City.entities(@obj.city_objs)
11
+ end
12
+
13
+ # Returns true if any Player has claimed the Route, false otherwise.
14
+ #
15
+ def claimed?
16
+ @obj.player_obj != nil
17
+ end
18
+
19
+ # Returns true if the given Player has claimed the route, false otherwise.
20
+ #
21
+ def claimed_by_player? (player)
22
+ @obj.player_obj == Player.object(player)
23
+ end
24
+
25
+ # Returns the color of the Route as a Symbol.
26
+ #
27
+ def color
28
+ @obj.color
29
+ end
30
+
31
+ # Returns false if the color of the Route is :grey, true otherwise.
32
+ #
33
+ def color?
34
+ @obj.color != :grey
35
+ end
36
+
37
+ # Returns true if the Route is the exact same color as the given color, false
38
+ # otherwise.
39
+ #
40
+ def exact_match? (color)
41
+ @obj.color == color
42
+ end
43
+
44
+ # Returns true if the color of the Route is :grey, false otherwise.
45
+ #
46
+ def grey?
47
+ @obj.color == :grey
48
+ end
49
+
50
+ # Returns the length of the Route.
51
+ #
52
+ def length
53
+ @obj.length
54
+ end
55
+
56
+ # Returns true if the given color represents a color match with the Route,
57
+ # false otherwise. The color :grey matches every color.
58
+ #
59
+ def match? (color)
60
+ [color, :grey].include?(@obj.color)
61
+ end
62
+
63
+ # Returns the point value of the Route.
64
+ #
65
+ def points
66
+ @obj.points
67
+ end
68
+
69
+ # Returns true if the Route is unclaimed, false otherwise.
70
+ #
71
+ def unclaimed?
72
+ @obj.player_obj == nil
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Ticket < Entity
7
+ # Returns an Array of the Cities on the Ticket.
8
+ #
9
+ def cities
10
+ City.entities(@obj.city_objs)
11
+ end
12
+
13
+ # Returns the point value of the Ticket.
14
+ #
15
+ def points
16
+ @obj.points
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ require_relative 'colorful'
7
+
8
+ class CardObject
9
+ def initialize (color)
10
+ @color = color.to_sym
11
+ end
12
+
13
+ include Colorful
14
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class CityObject
7
+ def initialize (name)
8
+ @name = name
9
+ @route_objs = []
10
+ @connected_city_objs = []
11
+ end
12
+
13
+ attr_reader :connected_city_objs, :name, :route_objs
14
+
15
+ # Adds the given Route to the City's Routes.
16
+ #
17
+ def add_route_obj (route_obj)
18
+ @route_objs << route_obj
19
+ @connected_city_objs |= (route_obj.city_objs - [self])
20
+ end
21
+
22
+ # Returns true if the two Cities are connected by a single direct Route, false
23
+ # otherwise.
24
+ #
25
+ def connected? (city_obj)
26
+ @connected_city_objs.include?(city_obj)
27
+ end
28
+
29
+ # Returns true if the two Cities are connected using any combination of the
30
+ # given Routes.
31
+ #
32
+ def connected_using? (city_obj, route_objs)
33
+ valid_routes = route_objs & @route_objs
34
+ return false if valid_routes.empty?
35
+
36
+ valid_routes.any? do |obj|
37
+ return true if obj.connects?(self, city_obj)
38
+
39
+ obj.city_objs.any? do |c_obj|
40
+ next if c_obj == self
41
+ c_obj.connected_using?(city_obj, route_objs - valid_routes)
42
+ end
43
+ end
44
+ end
45
+
46
+ # Returns an Array of the Routes directly connecting the two Cities.
47
+ #
48
+ def routes_to (city_obj)
49
+ @route_objs.select {|obj| obj.connects?(self, city_obj) }
50
+ end
51
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ module Colorful
7
+ attr_reader :color
8
+
9
+ # Returns true if the given Symbol or Colorful object represents the exact
10
+ # same color as the receiver, false otherwise. Returns nil if the given object
11
+ # is neither a Symbol nor Colorful.
12
+ #
13
+ def exact_match? (c)
14
+ case c
15
+ when Symbol then @color == c
16
+ when Colorful then exact_match? c.color
17
+ end
18
+ end
19
+
20
+ # Returns true if the given Symbol or Colorful object represents a color match
21
+ # with the receiver, false otherwise. The colors :wild and :grey match every
22
+ # color. Returns nil if the given object is neither a Symbol nor Colorful.
23
+ #
24
+ def match? (c)
25
+ case c
26
+ when Symbol then [:grey, :wild, @color].include? c
27
+ when Colorful then match? c.color
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,81 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class DeckObject
7
+ def initialize
8
+ @draw_pile = []
9
+ @selection = []
10
+ @discard_pile = 14.times.map { CardObject.new(:wild) }
11
+
12
+ %w:black blue green orange pink red white yellow:.each do |color|
13
+ @discard_pile.concat(12.times.map { CardObject.new(color.to_sym) })
14
+ end
15
+ end
16
+
17
+ # Returns an Array of the colors of the Cards in the selection.
18
+ #
19
+ def available_colors
20
+ @selection.map {|card| card.color }
21
+ end
22
+
23
+ # Moves the given Card to the discard pile.
24
+ #
25
+ def discard (card)
26
+ @discard_pile << card
27
+ end
28
+
29
+ # Returns the top Card of the Deck, shuffling first if necessary. If there are
30
+ # no Cards left in the Deck, returns nil.
31
+ #
32
+ def draw
33
+ if @draw_pile.empty?
34
+ @draw_pile.replace(@discard_pile.shuffle)
35
+ @discard_pile.clear
36
+ end
37
+
38
+ @draw_pile.pop
39
+ end
40
+
41
+ # Returns true if there are no Cards remaining in the Deck, false otherwise.
42
+ #
43
+ def empty?
44
+ (@draw_pile + @discard_pile + @selection).empty?
45
+ end
46
+
47
+ # Moves Cards from the draw pile to the selection (shuffling when necessary)
48
+ # until the selection contains 5 Cards, or as many Cards as are left in the
49
+ # Deck. If there are ever 3 wild Cards showing (and at least 3 non-wild cards
50
+ # left in the Deck), moves the selection Cards to the discard pile and
51
+ # attempts to fill the selection again.
52
+ #
53
+ def fill_selection
54
+ until @selection.length > 4
55
+ return unless card = draw
56
+ @selection << card
57
+
58
+ if (@selection.count {|card| card.color == :wild } >= 3) && ((@draw_pile + @discard_pile + @selection).count {|card| card.color != :wild } > 2)
59
+ @selection.each {|card| discard(card) }
60
+ @selection.clear
61
+ end
62
+ end
63
+ end
64
+
65
+ # Removes a Card of the given color from the selection, refills the selection,
66
+ # then returns the Card. If no Card of the given color is found, returns nil.
67
+ #
68
+ def select (color)
69
+ if index = @selection.index {|card| card.exact_match?(color) }
70
+ card = @selection.delete_at(index)
71
+ fill_selection
72
+ card
73
+ end
74
+ end
75
+
76
+ # Returns true if the deck is empty except for wild Cards in the selection.
77
+ #
78
+ def wilds_only?
79
+ (@draw_pile + @discard_pile).empty? && @selection.all? {|card| card.color == :wild }
80
+ end
81
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Entity
7
+ def Entity.inherited (klass)
8
+ klass.define_singleton_method(:object) {|obj| obj.instance_variable_get(:@obj) }
9
+ klass.define_singleton_method(:entities) {|ary| ary.map {|obj| klass.new(obj) } }
10
+ end
11
+
12
+ def initialize (obj)
13
+ @obj = obj
14
+ end
15
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class GameObject
7
+ def initialize (script_names, city_objs, route_objs, ticket_objs)
8
+ n = script_names.length
9
+ raise(NumberOfPlayers, "#{n} player#{"s" if n != 1} given, 2..5 players expected") unless n.between?(2,5)
10
+
11
+ @deck = DeckObject.new
12
+
13
+ @city_objs = city_objs
14
+ @route_objs = route_objs
15
+ @ticket_objs = ticket_objs
16
+
17
+ @player_objs = []
18
+
19
+ script_names.each do |script_name|
20
+ script = Script.method(script_name).to_proc.curry[Game.new(self)]
21
+ @player_objs << PlayerObject.new(@deck, @player_objs, @route_objs, script, @ticket_objs)
22
+ end
23
+
24
+ @deck.fill_selection
25
+ end
26
+
27
+ NumberOfPlayers = Class.new(Exception)
28
+ UnfinishedTurn = Class.new(Exception)
29
+
30
+ attr_reader :deck, :route_objs
31
+
32
+ # Causes Players to take turns until every Player has played a turn in which
33
+ # at least one Player has fewer than 3 trains, then scores the Tickets held by
34
+ # each Player.
35
+ #
36
+ def play
37
+ @player_objs.cycle do |obj|
38
+ obj.take_a_turn
39
+ break if @player_objs.all? {|obj| obj.played_final_round? }
40
+ end
41
+
42
+ @player_objs.map {|obj| obj.score_tickets; obj.score }
43
+ end
44
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class MapObject
7
+ def MapObject.inherited (subclass)
8
+ subclass.define_singleton_method(:play_game) do |*script_names|
9
+ subclass.new.new_game(*script_names).play
10
+ end
11
+ end
12
+
13
+ def initialize
14
+ @city_objs = []
15
+ @route_objs = []
16
+ @ticket_objs = []
17
+
18
+ build
19
+ end
20
+
21
+ # Creates and returns a new City on the Map.
22
+ #
23
+ def city (name)
24
+ @city_objs[0...0] = CityObject.new(name.to_sym)
25
+ end
26
+
27
+ # Returns a new Game played using the Map, using Players running the named
28
+ # Scripts.
29
+ #
30
+ def new_game (*script_names)
31
+ GameObject.new(script_names, @city_objs, @route_objs.each {|r| r.player_obj = nil }, @ticket_objs.shuffle)
32
+ end
33
+
34
+ # Creates and returns a new Route between the given Cities, with the given
35
+ # length and color.
36
+ #
37
+ def route (*city_objs, length, color)
38
+ route_obj = RouteObject.new(city_objs, length, color)
39
+ city_objs.each {|obj| obj.add_route_obj(route_obj) }
40
+ @route_objs[0...0] = route_obj
41
+ end
42
+
43
+ # Creates and returns a new Ticket for the given Cities, with the given point
44
+ # value.
45
+ #
46
+ def ticket (points, *city_objs)
47
+ @ticket_objs[0...0] = TicketObject.new(points, city_objs)
48
+ end
49
+ end
@@ -0,0 +1,170 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class PlayerObject
7
+ def initialize (deck_obj, player_objs, route_objs, script, ticket_objs)
8
+ @deck = deck_obj
9
+ @script = script
10
+
11
+ @all_player_objs = player_objs
12
+ @all_route_objs = route_objs
13
+ @remaining_ticket_objs = ticket_objs
14
+
15
+ @candidate_ticket_objs = []
16
+ @kept_ticket_objs = []
17
+ @claimed_route_objs = []
18
+
19
+ @score = 0
20
+ @trains = 45
21
+
22
+ @selections = []
23
+ @cards = { wild:[], black:[], blue:[], green:[], orange:[], pink:[], red:[], white:[], yellow:[] }
24
+
25
+ 4.times do
26
+ card = @deck.draw
27
+ @cards[card.color] << card
28
+ end
29
+
30
+ @action_begun = false
31
+ @turn_completed = false
32
+ @draws_remaining = 0
33
+ end
34
+
35
+ attr_reader :action_begun, :all_player_objs, :candidate_ticket_objs, :cards,
36
+ :claimed_route_objs, :deck, :draws_remaining, :kept_ticket_objs,
37
+ :remaining_ticket_objs, :score, :selections, :trains,
38
+ :turn_completed
39
+
40
+ # Moves three candidate Tickets from the remaining Tickets to the Player and
41
+ # returns an Array of the Tickets. Disallows the Player from claiming Routes,
42
+ # drawing Tickets, and drawing Cards for the remainder of the turn.
43
+ #
44
+ def add_tickets
45
+ @action_begun = true
46
+ @draws_remaining = 0
47
+
48
+ @candidate_ticket_objs.replace(@remaining_ticket_objs.pop(3))
49
+ end
50
+
51
+ # Marks the given Route as claimed by the Player. Increments the Player's
52
+ # score by the point value of the Route. Decrements the Player's trains by the
53
+ # length of the Route. Disallows claiming Routes, drawing Tickets, and drawing
54
+ # Cards for the remainder of the turn and marks the Player's turn as complete.
55
+ #
56
+ def claim (route_obj)
57
+ route_obj.player_obj = self
58
+
59
+ @claimed_route_objs << route_obj
60
+ @trains -= route_obj.length
61
+ @score += route_obj.points
62
+
63
+ @action_begun = true
64
+ @turn_completed = true
65
+ @draws_remaining = 0
66
+ end
67
+
68
+ # Removes all candidate Tickets from the Player, marks the Player's turn as
69
+ # complete, and returns true.
70
+ #
71
+ def clear_ticket_objs
72
+ @candidate_ticket_objs.clear
73
+ @turn_completed = true
74
+ end
75
+
76
+ # Moves the given Cards from the Player to the Deck's discard pile.
77
+ #
78
+ def discard (card_objs)
79
+ card_objs.each do |obj|
80
+ @cards.each {|k,v| v.delete(obj) }
81
+ @deck.discard(obj)
82
+ end
83
+ end
84
+
85
+ # Marks the given Ticket as kept by the Player. If all candidate Tickets have
86
+ # been kept, marks the Player's turn as complete and returns true, otherwise
87
+ # returns nil.
88
+ #
89
+ def keep_ticket_obj (ticket_obj)
90
+ @kept_ticket_objs << ticket_obj
91
+ @turn_completed = true if @candidate_ticket_objs.empty?
92
+ end
93
+
94
+ # Returns true if the Player has taken a turn during the final round, false
95
+ # otherwise.
96
+ #
97
+ def played_final_round?
98
+ @final_round
99
+ end
100
+
101
+ # Returns true if the Player will be able to draw Cards, claim a Route, or
102
+ # draw Tickets during this turn, false otherwise.
103
+ #
104
+ def possible_to_play?
105
+ return true unless @deck.empty?
106
+ return true unless @remaining_ticket_objs.length < 3
107
+
108
+ max_route = @cards.keys.each_with_object({}) do |color, h|
109
+ next if color == :wild
110
+ h[color] = @cards[color].length + @cards[:wild].length
111
+ end
112
+
113
+ max_route[:grey] = max_route.values.max
114
+
115
+ @all_route_objs.any? do |obj|
116
+ next if obj.player_obj
117
+ next if obj.length > @trains
118
+ next if obj.length > max_route[obj.color]
119
+ next if (twin = obj.twin) && (twin.player_obj == self)
120
+ next if (twin) && (@all_player_objs.length < 3) && (twin.player_obj)
121
+ true
122
+ end
123
+ end
124
+
125
+ # Decrements the number of Cards left to draw by 1 and disallows the Player
126
+ # from drawing Tickets and claiming Routes for the remainder of this turn. If
127
+ # the number of Cards left to draw reaches 0, also disallows the Player from
128
+ # drawing more Cards this turn, and marks the Player's turn as complete.
129
+ #
130
+ def register_draw (n)
131
+ @action_begun = true
132
+ @draws_remaining -= n
133
+
134
+ if (@draws_remaining < 1) || @deck.empty? || @deck.wilds_only?
135
+ @turn_completed = true
136
+ end
137
+ end
138
+
139
+ def score_tickets
140
+ @kept_ticket_objs.each {|obj| @score += (obj.completed? @claimed_route_objs) ? obj.points : -obj.points }
141
+ end
142
+
143
+ # Returns an Array of Cards of the given size. If force_wilds is false, will
144
+ # attempt to fill the Array first with Cards of the given color, then with the
145
+ # given number of wild Cards (if necessary). If force_wilds is true, will
146
+ # attempt to fill the Array first with the given number of wild Cards, then
147
+ # with Cards of the given color (if necessary). If the Array is not filled to
148
+ # the required size, returns nil.
149
+ #
150
+ def set_of (size, color, wilds, force_wilds = false)
151
+ c, w = @cards[color], @cards[:wild].take(wilds)
152
+
153
+ set = (force_wilds) ? (w + c) : (c + w)
154
+ set.take(size) unless set.length < size
155
+ end
156
+
157
+ #
158
+ #
159
+ def take_a_turn
160
+ if possible_to_play?
161
+ @script[Player.new(self)]
162
+ raise Game::UnfinishedTurn unless @turn_completed
163
+ end
164
+
165
+ @final_round = @all_player_objs.any? {|obj| obj.trains < 3 }
166
+ @draws_remaining = 2
167
+ @action_begun = false
168
+ @turn_completed = false
169
+ end
170
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class RouteObject
7
+ def initialize (city_objs, length, color)
8
+ @city_objs = city_objs
9
+ @color = color
10
+ @length = length
11
+ @player_obj = nil
12
+
13
+ x = length.to_f
14
+ @points = (Math.sin(4*x/3)*x*x*x/250 + 23*x*x/78 + 7*x/13).round
15
+ end
16
+
17
+ include Colorful
18
+
19
+ attr_accessor :player_obj
20
+ attr_reader :city_objs, :length, :points
21
+
22
+ # Returns true if the Route connects the given Cities, false otherwise.
23
+ #
24
+ def connects? (*city_objs)
25
+ (@city_objs & city_objs).length == 2
26
+ end
27
+
28
+ # Returns true if the Route connects to the given City, false otherwise.
29
+ #
30
+ def into? (city_obj)
31
+ @city_objs.include?(city_obj)
32
+ end
33
+
34
+ # If there are two Routes connecting the two Cities at the end of this Route,
35
+ # returns the other Route. Otherwise, returns nil.
36
+ #
37
+ def twin
38
+ (@city_objs[0].routes_to(@city_objs[1]) - [self])[0]
39
+ end
40
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class TicketObject
7
+ def initialize (points, city_objs)
8
+ @city_objs = city_objs
9
+ @points = points
10
+ end
11
+
12
+ attr_reader :city_objs, :points
13
+
14
+ # Returns true if the Ticket is completed by any combination of the given
15
+ # Routes, false otherwise.
16
+ #
17
+ def completed? (route_objs)
18
+ c1, c2 = city_objs
19
+ return false unless route_objs.any? {|obj| obj.into?(c1) }
20
+ return false unless route_objs.any? {|obj| obj.into?(c2) }
21
+
22
+ c1.connected_using?(c2, route_objs)
23
+ end
24
+ end
@@ -0,0 +1,177 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class UsaMap < MapObject
7
+ def build
8
+ atlanta, boston, calgary, charleston, chicago, dallas, denver, duluth,
9
+ el_paso, helena, houston, kansas_city, las_vegas, little_rock,
10
+ los_angeles, miami, montreal, nashville, new_orleans, new_york,
11
+ oklahoma_city, omaha, phoenix, pittsburgh, portland, raleigh, st_louis,
12
+ salt_lake_city, san_francisco, santa_fe, sault_ste_marie, seattle,
13
+ toronto, vancouver, washington, winnipeg = %w:
14
+ Atlanta Boston Calgary Charleston Chicago Dallas Denver Duluth
15
+ El\ Paso Helena Houston Kansas\ City Las\ Vegas Little\ Rock
16
+ Los\ Angeles Miami Montreal Nashville New\ Orleans New\ York
17
+ Oklahoma\ City Omaha Phoenix Pittsburgh Portland Raleigh St\ Louis
18
+ Salt\ Lake\ City San\ Francisco Santa\ Fe Sault\ Ste\ Marie Seattle
19
+ Toronto Vancouver Washington Winnipeg
20
+ :.map {|name| city(name) }
21
+
22
+ ticket(4, denver, el_paso)
23
+ ticket(5, kansas_city, houston)
24
+ ticket(6, new_york, atlanta)
25
+ ticket(7, calgary, salt_lake_city)
26
+ ticket(7, chicago, new_orleans)
27
+ ticket(8, duluth, houston)
28
+ ticket(8, helena, los_angeles)
29
+ ticket(8, sault_ste_marie, nashville)
30
+ ticket(9, sault_ste_marie, oklahoma_city)
31
+ ticket(9, montreal, atlanta)
32
+ ticket(9, seattle, los_angeles)
33
+ ticket(9, chicago, santa_fe)
34
+ ticket(10, toronto, miami)
35
+ ticket(10, duluth, el_paso)
36
+ ticket(11, winnipeg, little_rock)
37
+ ticket(11, dallas, new_york)
38
+ ticket(11, portland, phoenix)
39
+ ticket(11, denver, pittsburgh)
40
+ ticket(12, boston, miami)
41
+ ticket(12, winnipeg, houston)
42
+ ticket(13, montreal, new_orleans)
43
+ ticket(13, calgary, phoenix)
44
+ ticket(13, vancouver, santa_fe)
45
+ ticket(16, los_angeles, chicago)
46
+ ticket(17, san_francisco, atlanta)
47
+ ticket(17, portland, nashville)
48
+ ticket(20, los_angeles, miami)
49
+ ticket(20, vancouver, montreal)
50
+ ticket(21, los_angeles, new_york)
51
+ ticket(22, seattle, new_york)
52
+
53
+ route(denver, helena, 4, :green)
54
+ route(denver, kansas_city, 4, :black)
55
+ route(denver, kansas_city, 4, :orange)
56
+ route(denver, omaha, 4, :pink)
57
+ route(denver, oklahoma_city, 4, :red)
58
+ route(denver, phoenix, 5, :white)
59
+ route(denver, santa_fe, 2, :grey)
60
+ route(denver, salt_lake_city, 3, :red)
61
+ route(denver, salt_lake_city, 3, :yellow)
62
+
63
+ route(pittsburgh, chicago, 3, :orange)
64
+ route(pittsburgh, chicago, 3, :black)
65
+ route(pittsburgh, new_york, 2, :white)
66
+ route(pittsburgh, new_york, 2, :green)
67
+ route(pittsburgh, nashville, 4, :yellow)
68
+ route(pittsburgh, raleigh, 2, :grey)
69
+ route(pittsburgh, st_louis, 5, :green)
70
+ route(pittsburgh, toronto, 2, :grey)
71
+ route(pittsburgh, washington, 2, :grey)
72
+
73
+ route(atlanta, charleston, 2, :grey)
74
+ route(atlanta, miami, 5, :blue)
75
+ route(atlanta, nashville, 1, :grey)
76
+ route(atlanta, new_orleans, 4, :yellow)
77
+ route(atlanta, new_orleans, 4, :orange)
78
+ route(atlanta, raleigh, 2, :grey)
79
+ route(atlanta, raleigh, 2, :grey)
80
+
81
+ route(duluth, chicago, 3, :red)
82
+ route(duluth, helena, 6, :orange)
83
+ route(duluth, omaha, 2, :grey)
84
+ route(duluth, omaha, 2, :grey)
85
+ route(duluth, sault_ste_marie, 3, :grey)
86
+ route(duluth, toronto, 6, :pink)
87
+ route(duluth, winnipeg, 4, :black)
88
+
89
+ route(dallas, el_paso, 4, :red)
90
+ route(dallas, houston, 1, :grey)
91
+ route(dallas, houston, 1, :grey)
92
+ route(dallas, little_rock, 2, :grey)
93
+ route(dallas, oklahoma_city, 2, :grey)
94
+ route(dallas, oklahoma_city, 2, :grey)
95
+
96
+ route(kansas_city, omaha, 1, :grey)
97
+ route(kansas_city, omaha, 1, :grey)
98
+ route(kansas_city, oklahoma_city, 2, :grey)
99
+ route(kansas_city, oklahoma_city, 2, :grey)
100
+ route(kansas_city, st_louis, 2, :blue)
101
+ route(kansas_city, st_louis, 2, :pink)
102
+
103
+ route(san_francisco, los_angeles, 3, :yellow)
104
+ route(san_francisco, los_angeles, 3, :pink)
105
+ route(san_francisco, salt_lake_city, 5, :white)
106
+ route(san_francisco, salt_lake_city, 5, :orange)
107
+ route(san_francisco, portland, 5, :pink)
108
+ route(san_francisco, portland, 5, :green)
109
+
110
+ route(seattle, calgary, 4, :grey)
111
+ route(seattle, helena, 6, :yellow)
112
+ route(seattle, portland, 1, :grey)
113
+ route(seattle, portland, 1, :grey)
114
+ route(seattle, vancouver, 1, :grey)
115
+ route(seattle, vancouver, 1, :grey)
116
+
117
+ route(el_paso, houston, 6, :green)
118
+ route(el_paso, los_angeles, 6, :black)
119
+ route(el_paso, oklahoma_city, 5, :yellow)
120
+ route(el_paso, phoenix, 3, :grey)
121
+ route(el_paso, santa_fe, 2, :grey)
122
+
123
+ route(montreal, boston, 2, :grey)
124
+ route(montreal, boston, 2, :grey)
125
+ route(montreal, new_york, 3, :blue)
126
+ route(montreal, sault_ste_marie, 5, :black)
127
+ route(montreal, toronto, 3, :grey)
128
+
129
+ route(chicago, omaha, 4, :blue)
130
+ route(chicago, st_louis, 2, :green)
131
+ route(chicago, st_louis, 2, :white)
132
+ route(chicago, toronto, 4, :white)
133
+
134
+ route(helena, calgary, 4, :grey)
135
+ route(helena, omaha, 5, :red)
136
+ route(helena, salt_lake_city, 3, :pink)
137
+ route(helena, winnipeg, 4, :blue)
138
+
139
+ route(little_rock, nashville, 3, :white)
140
+ route(little_rock, new_orleans, 3, :green)
141
+ route(little_rock, oklahoma_city, 2, :grey)
142
+ route(little_rock, st_louis, 2, :grey)
143
+
144
+ route(new_york, boston, 2, :yellow)
145
+ route(new_york, boston, 2, :red)
146
+ route(new_york, washington, 2, :black)
147
+ route(new_york, washington, 2, :orange)
148
+
149
+ route(raleigh, charleston, 2, :grey)
150
+ route(raleigh, nashville, 3, :black)
151
+ route(raleigh, washington, 2, :grey)
152
+ route(raleigh, washington, 2, :grey)
153
+
154
+ route(calgary, vancouver, 3, :grey)
155
+ route(calgary, winnipeg, 6, :white)
156
+
157
+ route(las_vegas, los_angeles, 2, :grey)
158
+ route(las_vegas, salt_lake_city, 3, :orange)
159
+
160
+ route(miami, charleston, 4, :pink)
161
+ route(miami, new_orleans, 6, :red)
162
+
163
+ route(phoenix, los_angeles, 3, :grey)
164
+ route(phoenix, santa_fe, 3, :grey)
165
+
166
+ route(sault_ste_marie, toronto, 2, :grey)
167
+ route(sault_ste_marie, winnipeg, 6, :grey)
168
+
169
+ route(houston, new_orleans, 2, :grey)
170
+
171
+ route(nashville, st_louis, 2, :grey)
172
+
173
+ route(oklahoma_city, santa_fe, 3, :blue)
174
+
175
+ route(portland, salt_lake_city, 6, :blue)
176
+ end
177
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright © 2011 Jesse Sielaff
4
+ #
5
+
6
+ class Script
7
+ def Script.sample (game, player)
8
+ until player.turn_completed?
9
+ case rand
10
+ when 0...0.01
11
+ player.draw_tickets
12
+ a, b, c = player.candidate_tickets
13
+ player.keep_ticket(a)
14
+ player.keep_ticket(b) if rand < 0.1
15
+ player.keep_ticket(c) if rand < 0.1
16
+ player.discard_tickets
17
+
18
+ when 0.01...0.1
19
+ game.unclaimed_routes.shuffle.any? do |route|
20
+ %w:black blue green orange pink red white yellow:.shuffle.any? do |color|
21
+ player.claim_n_wild(route, color.to_sym, rand(7))
22
+ end
23
+ end
24
+
25
+ when 0.1...0.2
26
+ player.select_card(game.available_colors.sample)
27
+
28
+ else
29
+ player.draw_random
30
+ end
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ttr
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Jesse Sielaff
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-09-26 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description: .
17
+ email: jesse.sielaff@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/ttr/entities/city.rb
26
+ - lib/ttr/entities/game.rb
27
+ - lib/ttr/entities/opponent.rb
28
+ - lib/ttr/entities/player.rb
29
+ - lib/ttr/entities/route.rb
30
+ - lib/ttr/entities/ticket.rb
31
+ - lib/ttr/objects/card.rb
32
+ - lib/ttr/objects/city.rb
33
+ - lib/ttr/objects/colorful.rb
34
+ - lib/ttr/objects/deck.rb
35
+ - lib/ttr/objects/entity.rb
36
+ - lib/ttr/objects/game.rb
37
+ - lib/ttr/objects/map.rb
38
+ - lib/ttr/objects/player.rb
39
+ - lib/ttr/objects/route.rb
40
+ - lib/ttr/objects/ticket.rb
41
+ - lib/ttr/objects/usa_map.rb
42
+ - lib/ttr/sample/script.rb
43
+ - lib/ttr.rb
44
+ homepage:
45
+ licenses: []
46
+
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: 1.9.2
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ requirements: []
65
+
66
+ rubyforge_project:
67
+ rubygems_version: 1.8.8
68
+ signing_key:
69
+ specification_version: 3
70
+ summary: A simulator for AI scripts to play the popular train board game.
71
+ test_files: []
72
+