ttr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+