splendor_game 0.1.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +27 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/splendor_game.rb +23 -0
- data/lib/splendor_game/card.rb +24 -0
- data/lib/splendor_game/cli.rb +255 -0
- data/lib/splendor_game/coloured_object.rb +17 -0
- data/lib/splendor_game/game.rb +64 -0
- data/lib/splendor_game/load_cards.rb +126 -0
- data/lib/splendor_game/noble.rb +22 -0
- data/lib/splendor_game/options.rb +48 -0
- data/lib/splendor_game/player.rb +33 -0
- data/lib/splendor_game/tableau.rb +128 -0
- data/lib/splendor_game/turn.rb +144 -0
- data/lib/splendor_game/version.rb +3 -0
- data/splendor_game.gemspec +28 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 547dab96b270b9680b85697dcfdebf09fac4f8db
|
4
|
+
data.tar.gz: 9fbdd1e06feb465f45e47b43018fc0b497a913b0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e63c1ad7284bafc6844d171967b9eeada48d1cc973b0de224531e31a4a9981f14cf22db2d06a16bcc36f190304440b840bdd1b437952432d2fa368b61f8c29a7
|
7
|
+
data.tar.gz: 2675112afc9482cb891145bb3a88e32ef96dfe26de643d4ecaba1fd1cb179ae6e3163fbaf9c1091cdf8524268baa23b95d58110234797d2dbe37f74b00e761e1
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
engines:
|
3
|
+
duplication:
|
4
|
+
enabled: true
|
5
|
+
config:
|
6
|
+
languages:
|
7
|
+
ruby:
|
8
|
+
mass_threshold: 22
|
9
|
+
javascript:
|
10
|
+
python:
|
11
|
+
php:
|
12
|
+
fixme:
|
13
|
+
enabled: true
|
14
|
+
rubocop:
|
15
|
+
enabled: true
|
16
|
+
ratings:
|
17
|
+
paths:
|
18
|
+
- "**.inc"
|
19
|
+
- "**.js"
|
20
|
+
- "**.jsx"
|
21
|
+
- "**.module"
|
22
|
+
- "**.php"
|
23
|
+
- "**.py"
|
24
|
+
- "**.rb"
|
25
|
+
exclude_paths:
|
26
|
+
- spec/
|
27
|
+
- lib/splendor_game/load_cards.rb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 reedstonefood
|
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,35 @@
|
|
1
|
+
# Splendor_game
|
2
|
+
|
3
|
+
[](https://codeclimate.com/github/reedstonefood/splendor_game)
|
4
|
+
|
5
|
+
A Ruby implementation of the board game Splendor.
|
6
|
+
|
7
|
+
Find out more about the game at [BoardGameGeek](https://boardgamegeek.com/boardgame/148228/splendor).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Not yet ready for use - still in development.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
There is a simple CLI you can use. Simply run the following in irb or put in a .rb file and run it.
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'splendor_game'
|
19
|
+
SplendorGame::CLI.new
|
20
|
+
```
|
21
|
+
|
22
|
+
Or, hook this up to any front end you might desire to use.
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
|
26
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/reedstonefood/splendor_game.
|
27
|
+
|
28
|
+
## With thanks...
|
29
|
+
|
30
|
+
Whoever typed up a list of all the cards in the board game into this spreadsheet... https://drive.google.com/file/d/0B4yyYVH10iE5VlBFME9QelBVUnc/edit
|
31
|
+
|
32
|
+
## License
|
33
|
+
|
34
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
35
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "splendor_game"
|
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,23 @@
|
|
1
|
+
require "splendor_game/version"
|
2
|
+
require "splendor_game/options"
|
3
|
+
require "splendor_game/coloured_object"
|
4
|
+
require "splendor_game/card"
|
5
|
+
require "splendor_game/noble"
|
6
|
+
require "splendor_game/tableau"
|
7
|
+
require "splendor_game/player"
|
8
|
+
require "splendor_game/turn"
|
9
|
+
require "splendor_game/load_cards"
|
10
|
+
require "splendor_game/game"
|
11
|
+
require "splendor_game/cli"
|
12
|
+
|
13
|
+
module SplendorGame
|
14
|
+
# This is to validate when loading cards
|
15
|
+
VALID_COLOUR_LIST = ["RED", "BLUE", "BLACK", "GREEN", "WHITE"]
|
16
|
+
# This is used to validate certain calls
|
17
|
+
VALID_COLOUR_SYMBOLS = VALID_COLOUR_LIST.map { |x| x.downcase.to_sym }
|
18
|
+
# Note gold is a special colour so shouldn't be in VALID_COLOUR_LIST
|
19
|
+
VALID_COLOUR_SYMBOLS << :gold
|
20
|
+
|
21
|
+
MAX_PLAYER_COUNT = 4
|
22
|
+
MIN_PLAYER_COUNT = 2
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
class Card < ColouredObject
|
4
|
+
attr_reader :level, :points, :colour, :cost
|
5
|
+
|
6
|
+
def initialize(level, colour, cost, points = 0)
|
7
|
+
@level = level
|
8
|
+
@points = points
|
9
|
+
@colour = colour
|
10
|
+
@cost, @cost_error = Hash.new(), Hash.new()
|
11
|
+
# if the colour is valid, load it, if not, put it in an error hash
|
12
|
+
cost.each do |key, value|
|
13
|
+
new_key_name = validate_colour(key)
|
14
|
+
if new_key_name==false
|
15
|
+
@cost_error[key] = value
|
16
|
+
else
|
17
|
+
@cost[new_key_name] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'highline'
|
2
|
+
|
3
|
+
module SplendorGame
|
4
|
+
|
5
|
+
class CLI
|
6
|
+
@@cli = HighLine.new
|
7
|
+
attr_reader :level, :points, :colour, :cost
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@g = SplendorGame::Game.new Hash[*ARGV]
|
11
|
+
choose_players
|
12
|
+
main
|
13
|
+
end
|
14
|
+
|
15
|
+
def choose_players
|
16
|
+
count = 1
|
17
|
+
@@cli.say "Name all the players. Input 'done' when you are done."
|
18
|
+
loop do
|
19
|
+
pname = @@cli.ask("Enter name of player #{count} > ") do |q|
|
20
|
+
q.validate = lambda { |a| a.length >= 1 && a.length <= 19 }
|
21
|
+
end
|
22
|
+
if pname.downcase == 'done'
|
23
|
+
break if count > MIN_PLAYER_COUNT
|
24
|
+
@@cli.say "You need to input at least 2 players!"
|
25
|
+
elsif @g.add_player(pname)== true
|
26
|
+
count += 1
|
27
|
+
@@cli.say "*** #{pname} successfully added"
|
28
|
+
else
|
29
|
+
@@cli.say "*** Sorry, there was a problem adding player #{pname}"
|
30
|
+
end
|
31
|
+
break if count > MAX_PLAYER_COUNT
|
32
|
+
end
|
33
|
+
@@cli.say "Succesfully added #{count-1} players. Game is ready to start."
|
34
|
+
end
|
35
|
+
|
36
|
+
def puts_help
|
37
|
+
@@cli.say "************************ HELP! ************************"
|
38
|
+
@@cli.say "<%= color('(b)uy', BOLD) %> = Buy a card"
|
39
|
+
@@cli.say "<%= color('(r)eserve', BOLD) %> = Reserve a card"
|
40
|
+
@@cli.say "<%= color('(t)okens', BOLD) %> = Pick up tokens from the bank"
|
41
|
+
@@cli.say "<%= color('(n)obles', BOLD) %> = Look at the available nobles"
|
42
|
+
@@cli.say "<%= color('(h)elp', BOLD) %> = This help page"
|
43
|
+
@@cli.say "<%= color('e(x)it', BOLD) %> = Exit the program"
|
44
|
+
end
|
45
|
+
|
46
|
+
def full_display
|
47
|
+
@g.display.each do |row, deck|
|
48
|
+
@@cli.say "ROW #{row}"
|
49
|
+
deck.each do |card|
|
50
|
+
@@cli.say card_display(card)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#practicing using args rather than fixed list of parameters
|
56
|
+
def purchase_card(args)
|
57
|
+
#if args[:turn].player.tableau.reserved_cards.include?(args[:card])
|
58
|
+
# args[:turn].reserve_card
|
59
|
+
if args[:turn].purchase_card(args[:card])
|
60
|
+
true
|
61
|
+
else
|
62
|
+
@@cli.say "Oops, you can't afford that"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def reserve_card(args)
|
67
|
+
if args[:card].is_a?(SplendorGame::Card) && args[:turn].reserve_displayed_card(args[:card])
|
68
|
+
true
|
69
|
+
elsif args[:card].is_a?(Integer) && args[:turn].reserve_random_card(args[:card])
|
70
|
+
true
|
71
|
+
else
|
72
|
+
@@cli.say "Sorry, you can't reserve that (maybe you have reserved too many cards)"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def do_turn(turn)
|
77
|
+
while turn.action_done == false
|
78
|
+
output_all_player_details(turn.player)
|
79
|
+
input = @@cli.ask "What do you want to do, <%= color('#{turn.player.name}', BOLD) %>? "
|
80
|
+
command_result = process_command(input.downcase, turn)
|
81
|
+
if !command_result
|
82
|
+
@@cli.say "Sorry, I did not understand that. Press h for help"
|
83
|
+
elsif command_result==:exit
|
84
|
+
break
|
85
|
+
end
|
86
|
+
end
|
87
|
+
consider_nobles(turn)
|
88
|
+
turn.end_turn
|
89
|
+
@@cli.say "*** END OF TURN***"
|
90
|
+
command_result==:exit ? false : true
|
91
|
+
end
|
92
|
+
|
93
|
+
def process_command(input, turn)
|
94
|
+
case
|
95
|
+
when input[0]=='b'
|
96
|
+
card = choose_card(:buy, turn.player)
|
97
|
+
purchase_card(:card => card, :turn => turn) if card
|
98
|
+
when input[0]=='r'
|
99
|
+
card = choose_card(:reserve, turn.player)
|
100
|
+
reserve_card(:card => card, :turn => turn) if card
|
101
|
+
when input[0]=='h'
|
102
|
+
puts_help
|
103
|
+
when input[0]=='n'
|
104
|
+
display_nobles
|
105
|
+
when input[0]=='t'
|
106
|
+
@@cli.say bank_details + " "
|
107
|
+
take_tokens(turn)
|
108
|
+
when input[0]=='x'
|
109
|
+
return :exit
|
110
|
+
else
|
111
|
+
return false
|
112
|
+
end
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def card_display(card)
|
118
|
+
text = "#{card.points}pts "
|
119
|
+
text << "(#{card.colour}) => " if card.instance_variable_defined?(:@colour)
|
120
|
+
card.cost.each do |k,v|
|
121
|
+
text << "#{v} x #{k}, "
|
122
|
+
end
|
123
|
+
text[0..-3]
|
124
|
+
end
|
125
|
+
|
126
|
+
def output_all_player_details(highlighted_player=nil)
|
127
|
+
@g.players.each do |p|
|
128
|
+
if p==highlighted_player
|
129
|
+
str = "<%= color('"
|
130
|
+
str << player_details(p)
|
131
|
+
str << "', BOLD) %>"
|
132
|
+
else
|
133
|
+
str = player_details(p)
|
134
|
+
end
|
135
|
+
@@cli.say str
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def player_details(player)
|
140
|
+
str = "#{player.name.ljust(19)}: #{player.points.to_s.ljust(2)}pts. "
|
141
|
+
reserved_card_count = player.tableau.reserved_cards.count
|
142
|
+
str << "(#{reserved_card_count}R) " if reserved_card_count > 0
|
143
|
+
str << "Cards (#{player.tableau.cards.count}): "
|
144
|
+
player.tableau.all_colours_on_cards.sort.to_h.each do |colour, count|
|
145
|
+
str << "#{colour}=#{count} " if count > 0
|
146
|
+
end
|
147
|
+
str << "Tokens: "
|
148
|
+
player.tableau.tokens.sort.to_h.each do |colour,count|
|
149
|
+
str << "#{colour}=#{count} " if count > 0
|
150
|
+
end
|
151
|
+
str[0..-2]
|
152
|
+
end
|
153
|
+
|
154
|
+
def bank_details
|
155
|
+
str = "Bank tokens = "
|
156
|
+
@g.bank.tokens.sort.to_h.each do |colour, count|
|
157
|
+
str << "#{colour}=#{count} " if count > 0
|
158
|
+
end
|
159
|
+
str
|
160
|
+
end
|
161
|
+
|
162
|
+
def choose_card(mode, player = nil)
|
163
|
+
displayed_cards_list = @g.all_displayed_cards.collect { |c| [card_display(c),c] }.to_h
|
164
|
+
if mode==:reserve
|
165
|
+
(1..3).each { |i| displayed_cards_list["Reserve mystery level #{i} card"]= i }
|
166
|
+
end
|
167
|
+
if mode==:buy
|
168
|
+
player.tableau.reserved_cards.each_with_index do |card, index|
|
169
|
+
displayed_cards_list["R#{index+1} - #{card_display(card)}"]= card
|
170
|
+
end
|
171
|
+
end
|
172
|
+
@@cli.choose do |menu|
|
173
|
+
menu.prompt = "Which card do you want to #{mode}? "
|
174
|
+
menu.choices(*displayed_cards_list.keys) do |chosen|
|
175
|
+
@@cli.say "Nice, you chose #{chosen}."
|
176
|
+
displayed_cards_list[chosen]
|
177
|
+
end
|
178
|
+
menu.choice(:cancel) { return false }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
#def validate_token_choice(t)
|
183
|
+
# return false if [2,3].include?(t.count)
|
184
|
+
# t.each { |c| return false if !VALID_COLOUR_SYMBOLS.include?(c.upcase) || c==:gold}
|
185
|
+
# return false if t.count==2 && t[0] != t[1]
|
186
|
+
# true
|
187
|
+
#end
|
188
|
+
|
189
|
+
def take_tokens(turn)
|
190
|
+
input = @@cli.ask "Which tokens would you like (CSV format)? "
|
191
|
+
requested_tokens = input.split(",")
|
192
|
+
#return false if !validate_token_choice(requested_tokens)
|
193
|
+
if requested_tokens.count==2
|
194
|
+
response = turn.take_two_tokens_same_colour(requested_tokens[0])
|
195
|
+
elsif requested_tokens.count==3
|
196
|
+
response = turn.take_different_tokens(requested_tokens)
|
197
|
+
end
|
198
|
+
@@cli.say "Oops, that's not a valid selection" if !response
|
199
|
+
end
|
200
|
+
|
201
|
+
def consider_nobles(turn)
|
202
|
+
possibles = turn.claimable_nobles
|
203
|
+
return false if possibles.empty?
|
204
|
+
return assign_only_valid_noble(turn, possibles) if possibles.count==1
|
205
|
+
displayed_nobles_list = possibles.collect { |c| [card_display(c),c] }.to_h
|
206
|
+
@@cli.choose do |menu|
|
207
|
+
menu.prompt = "You qualify for multiple nobles! Pick one... "
|
208
|
+
menu.choices(*displayed_nobles_list.keys) do |chosen|
|
209
|
+
turn.claim_noble(displayed_nobles_list[chosen])
|
210
|
+
@@cli.say "Nice, you chose #{chosen}."
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
def display_nobles
|
217
|
+
@g.nobles.each do |noble|
|
218
|
+
@@cli.say "NOBLE - #{card_display(noble)}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def end_game_detail
|
223
|
+
@@cli.say "The game consisted of #{@g.turns.count} turns"
|
224
|
+
@@cli.ask "It's the end of the game. Press enter to end the program."
|
225
|
+
@@cli.say "Goodbye!"
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
def main
|
230
|
+
@g.start_game
|
231
|
+
catch :exit do
|
232
|
+
loop do
|
233
|
+
turn = @g.next_turn
|
234
|
+
throw :exit if turn===false # or @exit_flag==true ??
|
235
|
+
throw :exit if !do_turn(turn)
|
236
|
+
end #end of the game - only reachable by throwing an :exit
|
237
|
+
end
|
238
|
+
end_game_detail
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
def assign_only_valid_noble(turn,noble_array)
|
243
|
+
the_noble = noble_array.first
|
244
|
+
if !turn.claim_noble(the_noble)
|
245
|
+
@@cli.say "Looks like you qualify for a noble, but it didn't work"
|
246
|
+
return false
|
247
|
+
else
|
248
|
+
@@cli.say "You have been given a noble! #{the_noble.points} points"
|
249
|
+
return true
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
class ColouredObject
|
4
|
+
private
|
5
|
+
# format any inputted colours to the downcase symbol version
|
6
|
+
# return false if an invalid input is passed in
|
7
|
+
def validate_colour(input)
|
8
|
+
if VALID_COLOUR_LIST.include?(input.to_s.upcase)
|
9
|
+
input.downcase.to_sym
|
10
|
+
else
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
class Object
|
2
|
+
def is_natural_number?
|
3
|
+
return false if !self.is_a?(Numeric)
|
4
|
+
return false if self < 0
|
5
|
+
return false if self != self.abs
|
6
|
+
true
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module SplendorGame
|
11
|
+
|
12
|
+
|
13
|
+
class Game
|
14
|
+
attr_reader :deck, :bank, :players, :nobles, :options, :display, :turns
|
15
|
+
def initialize(user_options = nil)
|
16
|
+
@options = Options.new(user_options).give_options
|
17
|
+
load_cards # puts all the cards into shuffled decks... a hash of arrays of cards. @deck[level]
|
18
|
+
@bank = Tableau.new(0) # 0 means no limit on token capacity
|
19
|
+
@players = Array.new()
|
20
|
+
@turns = Array.new()
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_player(player_name)
|
24
|
+
return false if @players.count >= MAX_PLAYER_COUNT
|
25
|
+
@players << Player.new(player_name, @players.count+1,@options[:player_token_limit])
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def next_player
|
30
|
+
if !@turns.empty?
|
31
|
+
@players.rotate!
|
32
|
+
return @players.first
|
33
|
+
end
|
34
|
+
@players.shuffle!
|
35
|
+
@starting_player = @players.first
|
36
|
+
end
|
37
|
+
|
38
|
+
def game_over?
|
39
|
+
return false if @players.map { |p| p.points }.max < @options[:winning_score]
|
40
|
+
return true if @players[1] == @starting_player
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_game
|
45
|
+
@bank.seed_bank({:options=> @options, :player_count => @players.count})
|
46
|
+
@nobles = noble_sample(@options[:nobles_available][@players.count])
|
47
|
+
@display = Hash.new()
|
48
|
+
@deck.each { |level, subdeck| @display[level] = subdeck.pop(@options[:display_cards_per_row]) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def next_turn
|
52
|
+
return false if game_over? || !defined? @display
|
53
|
+
t = SplendorGame::Turn.new(self, next_player)
|
54
|
+
@turns << t
|
55
|
+
t
|
56
|
+
end
|
57
|
+
|
58
|
+
def all_displayed_cards
|
59
|
+
@display.values.flatten
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
class Game
|
3
|
+
#Could read from an external file eventually, but this works just fine :)
|
4
|
+
def load_cards
|
5
|
+
all_cards = Array.new()
|
6
|
+
all_cards << Card.new(1, :white, {:blue => 3})
|
7
|
+
all_cards << Card.new(1, :white, {:red => 2, :black => 1})
|
8
|
+
all_cards << Card.new(1, :white, {:blue => 1, :green => 1, :red => 1, :black => 1})
|
9
|
+
all_cards << Card.new(1, :white, {:blue => 2, :black => 2})
|
10
|
+
all_cards << Card.new(1, :white, {:green => 4}, 1)
|
11
|
+
all_cards << Card.new(1, :white, {:blue => 1, :green => 2, :red => 1, :black => 1})
|
12
|
+
all_cards << Card.new(1, :white, {:blue => 2, :green => 2, :black => 1})
|
13
|
+
all_cards << Card.new(1, :white, {:white => 3, :blue => 1, :black => 1})
|
14
|
+
all_cards << Card.new(1, :blue, {:white => 1, :black => 2})
|
15
|
+
all_cards << Card.new(1, :blue, {:black => 3})
|
16
|
+
all_cards << Card.new(1, :blue, {:white => 1, :green => 1, :red => 1, :black => 1})
|
17
|
+
all_cards << Card.new(1, :blue, {:green => 2, :black => 2})
|
18
|
+
all_cards << Card.new(1, :blue, {:red => 4}, 1)
|
19
|
+
all_cards << Card.new(1, :blue, {:white => 1, :green => 1, :red => 2, :black => 1})
|
20
|
+
all_cards << Card.new(1, :blue, {:white => 1, :green => 2, :red => 2})
|
21
|
+
all_cards << Card.new(1, :blue, {:blue => 1, :green => 3, :red => 1})
|
22
|
+
all_cards << Card.new(1, :green, {:white => 2, :blue => 1})
|
23
|
+
all_cards << Card.new(1, :green, {:red => 3})
|
24
|
+
all_cards << Card.new(1, :green, {:white => 1, :blue => 1, :red => 1, :black => 1})
|
25
|
+
all_cards << Card.new(1, :green, {:blue => 2, :red => 2})
|
26
|
+
all_cards << Card.new(1, :green, {:black => 4}, 1)
|
27
|
+
all_cards << Card.new(1, :green, {:white => 1, :blue => 1, :red => 1, :black => 2})
|
28
|
+
all_cards << Card.new(1, :green, {:blue => 1, :red => 2, :black => 2})
|
29
|
+
all_cards << Card.new(1, :green, {:white => 1, :blue => 3, :green => 1})
|
30
|
+
all_cards << Card.new(1, :red, {:blue => 2, :green => 1})
|
31
|
+
all_cards << Card.new(1, :red, {:white => 3})
|
32
|
+
all_cards << Card.new(1, :red, {:white => 1, :blue => 1, :green => 1, :black => 1})
|
33
|
+
all_cards << Card.new(1, :red, {:white => 2, :red => 2})
|
34
|
+
all_cards << Card.new(1, :red, {:white => 4}, 1)
|
35
|
+
all_cards << Card.new(1, :red, {:white => 2, :blue => 1, :green => 1, :black => 1})
|
36
|
+
all_cards << Card.new(1, :red, {:white => 2, :green => 1, :black => 2})
|
37
|
+
all_cards << Card.new(1, :red, {:white => 1, :red => 1, :black => 3})
|
38
|
+
all_cards << Card.new(1, :black, {:green => 2, :red => 1})
|
39
|
+
all_cards << Card.new(1, :black, {:green => 3})
|
40
|
+
all_cards << Card.new(1, :black, {:white => 1, :blue => 1, :green => 1, :red => 1})
|
41
|
+
all_cards << Card.new(1, :black, {:white => 2, :green => 2})
|
42
|
+
all_cards << Card.new(1, :black, {:blue => 4}, 1)
|
43
|
+
all_cards << Card.new(1, :black, {:white => 1, :blue => 2, :green => 1, :red => 1})
|
44
|
+
all_cards << Card.new(1, :black, {:white => 2, :blue => 2, :red => 1})
|
45
|
+
all_cards << Card.new(1, :black, {:green => 1, :red => 3, :black => 1})
|
46
|
+
|
47
|
+
all_cards << Card.new(2, :white, {:red => 5}, 2)
|
48
|
+
all_cards << Card.new(2, :white, {:white => 6}, 3)
|
49
|
+
all_cards << Card.new(2, :white, {:green => 3, :red => 2, :black => 2}, 1)
|
50
|
+
all_cards << Card.new(2, :white, {:green => 1, :red => 4, :black => 2}, 2)
|
51
|
+
all_cards << Card.new(2, :white, {:white => 2, :blue => 3, :red => 3}, 1)
|
52
|
+
all_cards << Card.new(2, :white, {:red => 5, :black => 3}, 2)
|
53
|
+
all_cards << Card.new(2, :blue, {:blue => 5}, 2)
|
54
|
+
all_cards << Card.new(2, :blue, {:blue => 6}, 3)
|
55
|
+
all_cards << Card.new(2, :blue, {:blue => 2, :green => 2, :red => 3}, 1)
|
56
|
+
all_cards << Card.new(2, :blue, {:white => 2, :red => 1, :black => 4}, 2)
|
57
|
+
all_cards << Card.new(2, :blue, {:blue => 2, :green => 3, :black => 3}, 1)
|
58
|
+
all_cards << Card.new(2, :blue, {:white => 5, :blue => 3}, 2)
|
59
|
+
all_cards << Card.new(2, :green, {:green => 5}, 2)
|
60
|
+
all_cards << Card.new(2, :green, {:green => 6}, 3)
|
61
|
+
all_cards << Card.new(2, :green, {:white => 2, :blue => 3, :black => 2}, 1)
|
62
|
+
all_cards << Card.new(2, :green, {:white => 3, :green => 2, :red => 3}, 1)
|
63
|
+
all_cards << Card.new(2, :green, {:white => 4, :blue => 2, :black => 1}, 2)
|
64
|
+
all_cards << Card.new(2, :green, {:blue => 5, :green => 3}, 2)
|
65
|
+
all_cards << Card.new(2, :red, {:black => 5}, 2)
|
66
|
+
all_cards << Card.new(2, :red, {:red => 6}, 3)
|
67
|
+
all_cards << Card.new(2, :red, {:white => 2, :red => 2, :black => 3}, 1)
|
68
|
+
all_cards << Card.new(2, :red, {:white => 1, :blue => 4, :green => 2}, 2)
|
69
|
+
all_cards << Card.new(2, :red, {:blue => 3, :red => 2, :black => 3}, 1)
|
70
|
+
all_cards << Card.new(2, :red, {:white => 3, :black => 5}, 2)
|
71
|
+
all_cards << Card.new(2, :black, {:white => 5}, 2)
|
72
|
+
all_cards << Card.new(2, :black, {:black => 6}, 3)
|
73
|
+
all_cards << Card.new(2, :black, {:white => 3, :blue => 2, :green => 2}, 1)
|
74
|
+
all_cards << Card.new(2, :black, {:blue => 1, :green => 4, :red => 2}, 2)
|
75
|
+
all_cards << Card.new(2, :black, {:white => 3, :green => 3, :black => 2}, 1)
|
76
|
+
all_cards << Card.new(2, :black, {:green => 5, :red => 3}, 2)
|
77
|
+
|
78
|
+
all_cards << Card.new(3, :white, {:black => 7}, 4)
|
79
|
+
all_cards << Card.new(3, :white, {:white => 3, :black => 7}, 5)
|
80
|
+
all_cards << Card.new(3, :white, {:white => 3, :red => 3, :black => 6}, 4)
|
81
|
+
all_cards << Card.new(3, :white, {:blue => 3, :green => 3, :red => 5, :black => 3}, 3)
|
82
|
+
all_cards << Card.new(3, :blue, {:white => 7}, 4)
|
83
|
+
all_cards << Card.new(3, :blue, {:white => 7, :blue => 3}, 5)
|
84
|
+
all_cards << Card.new(3, :blue, {:white => 6, :blue => 3, :black => 3}, 4)
|
85
|
+
all_cards << Card.new(3, :blue, {:white => 3, :green => 3, :red => 3, :black => 5}, 3)
|
86
|
+
all_cards << Card.new(3, :green, {:blue => 7}, 4)
|
87
|
+
all_cards << Card.new(3, :green, {:blue => 7, :green => 3}, 5)
|
88
|
+
all_cards << Card.new(3, :green, {:white => 3, :blue => 6, :green => 3}, 4)
|
89
|
+
all_cards << Card.new(3, :green, {:white => 5, :blue => 3, :red => 3, :black => 3}, 3)
|
90
|
+
all_cards << Card.new(3, :red, {:green => 7}, 4)
|
91
|
+
all_cards << Card.new(3, :red, {:green => 7, :red => 3}, 5)
|
92
|
+
all_cards << Card.new(3, :red, {:blue => 3, :green => 6, :red => 3}, 4)
|
93
|
+
all_cards << Card.new(3, :red, {:white => 3, :blue => 5, :green => 3, :black => 3}, 3)
|
94
|
+
all_cards << Card.new(3, :black, {:red => 7}, 4)
|
95
|
+
all_cards << Card.new(3, :black, {:red => 7, :black => 3}, 5)
|
96
|
+
all_cards << Card.new(3, :black, {:green => 3, :red => 6, :black => 3}, 4)
|
97
|
+
all_cards << Card.new(3, :black, {:white => 3, :blue => 3, :green => 5, :red => 3}, 3)
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
# Now all cards are in all_cards, distribute them into all_cards
|
102
|
+
@deck = Hash.new()
|
103
|
+
all_cards.each do |card|
|
104
|
+
@deck[card.level] = Array.new() if !@deck.include?(card.level)
|
105
|
+
@deck[card.level] << card
|
106
|
+
end
|
107
|
+
# shuffle them
|
108
|
+
@deck.each { |_deck_num, smaller_deck| smaller_deck.shuffle! }
|
109
|
+
end
|
110
|
+
|
111
|
+
def noble_sample(number_of_cards_to_load)
|
112
|
+
all_nobles = Array.new()
|
113
|
+
all_nobles << Noble.new({:white => 3, :blue => 3, :black => 3}, 3)
|
114
|
+
all_nobles << Noble.new({:blue => 3, :green => 3, :red => 3}, 3)
|
115
|
+
all_nobles << Noble.new({:white => 3, :red => 3, :black => 3}, 3)
|
116
|
+
all_nobles << Noble.new({:white => 3, :blue => 3, :green => 3}, 3)
|
117
|
+
all_nobles << Noble.new({:green => 3, :red => 3, :black => 3}, 3)
|
118
|
+
all_nobles << Noble.new({:green => 4, :red => 4}, 3)
|
119
|
+
all_nobles << Noble.new({:blue => 4, :green => 4}, 3)
|
120
|
+
all_nobles << Noble.new({:red => 4, :black => 4}, 3)
|
121
|
+
all_nobles << Noble.new({:white => 4, :black => 4}, 3)
|
122
|
+
all_nobles << Noble.new({:white => 4, :blue => 4}, 3)
|
123
|
+
all_nobles.shuffle!.first(number_of_cards_to_load)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
#Players become eligible for Nobles when they have cards that meet the cost (NOT tokens)
|
4
|
+
class Noble < ColouredObject
|
5
|
+
attr_reader :cost, :points
|
6
|
+
|
7
|
+
def initialize(cost, points = 0)
|
8
|
+
@points = points
|
9
|
+
@cost, @cost_error = Hash.new(), Hash.new()
|
10
|
+
# if the colour is valid, load it, if not, put it in an error hash
|
11
|
+
cost.each do |key, value|
|
12
|
+
new_key_name = validate_colour(key)
|
13
|
+
if new_key_name==false
|
14
|
+
@cost_error[key] = value
|
15
|
+
else
|
16
|
+
@cost[new_key_name] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
class Options
|
4
|
+
NOBLES_AVAILABLE = { 2 => 3, 3 => 4, 4 => 5}
|
5
|
+
STARTING_GOLD_TOKENS = 5
|
6
|
+
STARTING_NON_GOLD_TOKENS = { 2 => 4, 3 => 5, 4 => 7}
|
7
|
+
DISPLAY_CARDS_PER_ROW = 4
|
8
|
+
WINNING_SCORE = 15
|
9
|
+
MIN_TO_TAKE_TWO = 4
|
10
|
+
PLAYER_TOKEN_LIMIT = 10
|
11
|
+
attr_reader :deck, :bank, :players, :nobles, :options, :display
|
12
|
+
def initialize(user_options = nil)
|
13
|
+
if user_options.is_a?(Hash)
|
14
|
+
@user_options = user_options
|
15
|
+
else
|
16
|
+
@user_options = Hash.new()
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def clean_user_options
|
21
|
+
[:starting_non_gold_tokens, :nobles_available].each do |key|
|
22
|
+
if @user_options[key].respond_to?(:keys)
|
23
|
+
@user_options.delete(key) if !([2,3,4] - @user_options[key].keys).empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def default_options
|
29
|
+
output = Hash.new()
|
30
|
+
output[:display_cards_per_row] = DISPLAY_CARDS_PER_ROW
|
31
|
+
output[:winning_score] = WINNING_SCORE
|
32
|
+
output[:min_to_take_two] = MIN_TO_TAKE_TWO
|
33
|
+
output[:starting_gold_tokens] = STARTING_GOLD_TOKENS
|
34
|
+
output[:starting_non_gold_tokens] = STARTING_NON_GOLD_TOKENS
|
35
|
+
output[:nobles_available] = NOBLES_AVAILABLE
|
36
|
+
output[:player_token_limit] = PLAYER_TOKEN_LIMIT
|
37
|
+
output
|
38
|
+
end
|
39
|
+
|
40
|
+
#Take the user values if they are valid, else use defaults
|
41
|
+
def give_options
|
42
|
+
clean_user_options
|
43
|
+
@user_options.merge(default_options)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
class Player
|
4
|
+
|
5
|
+
attr_reader :name, :turn_order, :tableau, :nobles
|
6
|
+
|
7
|
+
def initialize(name, turn_order, token_limit)
|
8
|
+
@name = name
|
9
|
+
@turn_order = turn_order
|
10
|
+
@tableau = Tableau.new(token_limit)
|
11
|
+
@nobles = Array.new()
|
12
|
+
end
|
13
|
+
|
14
|
+
def points
|
15
|
+
card_points = @tableau.cards.inject(0) { |sum,c| sum + c.points }
|
16
|
+
card_points + @nobles.inject(0) { |sum,c| sum + c.points }
|
17
|
+
end
|
18
|
+
|
19
|
+
def can_afford_noble?(noble)
|
20
|
+
noble.cost.each do |k, v|
|
21
|
+
return false if @tableau.colours_on_cards(k) < v
|
22
|
+
end
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def claim_noble(noble)
|
27
|
+
return false if !can_afford_noble?(noble)
|
28
|
+
@nobles << noble
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
|
4
|
+
class Tableau
|
5
|
+
@@max_reserved_cards = 3
|
6
|
+
attr_reader :cards, :tokens, :reserved_cards
|
7
|
+
|
8
|
+
def initialize(token_limit)
|
9
|
+
@cards = Array.new()
|
10
|
+
@tokens = Hash.new(0)
|
11
|
+
@unlimited = token_limit <= 0 ? true : false
|
12
|
+
@token_limit = token_limit
|
13
|
+
@reserved_cards = Array.new()
|
14
|
+
end
|
15
|
+
|
16
|
+
def seed_bank(args)
|
17
|
+
seed_bank_non_gold(args[:options][:starting_non_gold_tokens][args[:player_count]])
|
18
|
+
seed_bank_gold(args[:options][:starting_gold_tokens])
|
19
|
+
end
|
20
|
+
|
21
|
+
### add tokens, remove tokens, counting tokens
|
22
|
+
|
23
|
+
def add_token(token_colour)
|
24
|
+
return false if !VALID_COLOUR_SYMBOLS.include?(token_colour)
|
25
|
+
return false if !@unlimited && token_count >= @token_limit
|
26
|
+
if @tokens.include?(token_colour)
|
27
|
+
@tokens[token_colour] += 1
|
28
|
+
else
|
29
|
+
@tokens[token_colour] = 1
|
30
|
+
end
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def remove_token(token_colour)
|
35
|
+
return false if !@tokens.key?(token_colour)
|
36
|
+
return false if @tokens[token_colour] <= 0
|
37
|
+
@tokens[token_colour] -= 1
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def token_count
|
42
|
+
@tokens.inject(0) { |sum,(_k,v)| sum + v }
|
43
|
+
end
|
44
|
+
|
45
|
+
def token_space_remaining
|
46
|
+
return nil if @unlimited == true #meaning, undefined
|
47
|
+
@token_limit - token_count
|
48
|
+
end
|
49
|
+
|
50
|
+
def distinct_token_colour_count
|
51
|
+
@tokens.count { |_k,v| v >0 }
|
52
|
+
end
|
53
|
+
|
54
|
+
#### reserving cards
|
55
|
+
|
56
|
+
def can_reserve_card?
|
57
|
+
return false if @reserved_cards.size >= @@max_reserved_cards
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def reserve_card(card)
|
62
|
+
return false if !can_reserve_card?
|
63
|
+
@reserved_cards << card
|
64
|
+
end
|
65
|
+
|
66
|
+
def play_reserved_card(card)
|
67
|
+
return false if tokens_required(card) == false
|
68
|
+
return false unless @reserved_cards.include?(card)
|
69
|
+
@cards << card
|
70
|
+
@reserved_cards.delete(card)
|
71
|
+
end
|
72
|
+
|
73
|
+
### related to purchasing cards
|
74
|
+
|
75
|
+
def purchase_card(card)
|
76
|
+
return false if tokens_required(card) == false
|
77
|
+
@cards << card
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns a Hash of the tokens required to buy the card
|
81
|
+
# If the tableau does not have sufficient cards/tokens, it returns false
|
82
|
+
def tokens_required(card)
|
83
|
+
answer = Hash.new(0)
|
84
|
+
card.cost.each do |colour, col_cost|
|
85
|
+
theoretical_tokens = col_cost - colours_on_cards(colour)
|
86
|
+
if theoretical_tokens <= @tokens[colour]
|
87
|
+
answer[colour] = theoretical_tokens if theoretical_tokens > 0
|
88
|
+
else
|
89
|
+
answer[colour] = @tokens[colour]
|
90
|
+
answer[:gold] += theoretical_tokens - @tokens[colour]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
return false if answer[:gold] > @tokens[:gold]
|
94
|
+
answer
|
95
|
+
end
|
96
|
+
|
97
|
+
def all_colours_on_cards
|
98
|
+
output = Hash.new()
|
99
|
+
VALID_COLOUR_SYMBOLS.each { |c| output[c] = colours_on_cards(c) if colours_on_cards(c) > 0 }
|
100
|
+
output
|
101
|
+
end
|
102
|
+
|
103
|
+
def colours_on_cards(colour)
|
104
|
+
@cards.inject(0) { |sum,card| card.colour == colour ? sum+1 : sum }.to_i
|
105
|
+
end
|
106
|
+
|
107
|
+
### Other
|
108
|
+
|
109
|
+
def is_empty?
|
110
|
+
@cards.size==0 && @tokens.size==0 ? true : false
|
111
|
+
end
|
112
|
+
|
113
|
+
### setup
|
114
|
+
private
|
115
|
+
|
116
|
+
def seed_bank_non_gold(token_count)
|
117
|
+
VALID_COLOUR_SYMBOLS.each do |colour|
|
118
|
+
@tokens[colour] = token_count if colour != :gold
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def seed_bank_gold(token_count)
|
123
|
+
@tokens[:gold] = token_count
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module SplendorGame
|
2
|
+
|
3
|
+
class Turn
|
4
|
+
attr_reader :player, :action_done
|
5
|
+
|
6
|
+
def initialize(game, player)
|
7
|
+
@game = game
|
8
|
+
@player = player
|
9
|
+
@action_done = false
|
10
|
+
@noble_claimed = false
|
11
|
+
end
|
12
|
+
|
13
|
+
#from the 12 cards on display plus up to 3 reserved ones, return the ones affordable, in an Array
|
14
|
+
def affordable_cards
|
15
|
+
answer = Array.new()
|
16
|
+
(@game.all_displayed_cards + @player.tableau.reserved_cards).each do |card|
|
17
|
+
@cost = @player.tableau.tokens_required(card)
|
18
|
+
answer << card if !@cost==false
|
19
|
+
end
|
20
|
+
answer
|
21
|
+
end
|
22
|
+
|
23
|
+
def purchase_card(card) # do the requisite card/token changes, if the card is in affordable_cards
|
24
|
+
return false if @action_done
|
25
|
+
return false if !affordable_cards.include?(card)
|
26
|
+
cost_to_player = @player.tableau.tokens_required(card)
|
27
|
+
if @player.tableau.reserved_cards.include?(card)
|
28
|
+
@player.tableau.play_reserved_card(card)
|
29
|
+
else
|
30
|
+
@player.tableau.purchase_card(card)
|
31
|
+
end
|
32
|
+
cost_to_player.each do |colour, val|
|
33
|
+
val.times { @player.tableau.remove_token(colour) }
|
34
|
+
val.times { @game.bank.add_token(colour) }
|
35
|
+
end
|
36
|
+
display_row = @game.display[card.level]
|
37
|
+
display_row.delete_at(display_row.index(card) || display_row.length)
|
38
|
+
@action_done = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def reserve_card_checks?
|
42
|
+
return false if @action_done
|
43
|
+
return false if !@player.tableau.can_reserve_card?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def give_gold_token
|
48
|
+
if @game.bank.tokens[:gold] > 0 && @player.tableau.token_space_remaining > 0
|
49
|
+
@game.bank.remove_token(:gold)
|
50
|
+
@player.tableau.add_token(:gold)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def reserve_displayed_card(card) # put cards in to player's reserved set. Remove card from display
|
55
|
+
return false if !reserve_card_checks?
|
56
|
+
return false if !@game.display.flatten(2).include?(card)
|
57
|
+
@player.tableau.reserve_card(card)
|
58
|
+
display_row = @game.display[card.level]
|
59
|
+
display_row.delete_at(display_row.index(card) || display_row.length)
|
60
|
+
give_gold_token
|
61
|
+
@action_done = true
|
62
|
+
end
|
63
|
+
|
64
|
+
def reserve_random_card(deck_number)
|
65
|
+
return false if !reserve_card_checks?
|
66
|
+
return false if @game.deck[deck_number].nil?
|
67
|
+
return false if @game.deck[deck_number].count==0
|
68
|
+
card = @game.deck[deck_number].pop
|
69
|
+
@player.tableau.reserve_card(card)
|
70
|
+
give_gold_token
|
71
|
+
@action_done = true
|
72
|
+
end
|
73
|
+
|
74
|
+
def validate_token_pickup?(colour)
|
75
|
+
return false if @action_done
|
76
|
+
return false if Array(colour).include?(:gold)
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
def take_two_tokens_same_colour(colour)
|
81
|
+
colour = colour.to_sym
|
82
|
+
return false if !validate_token_pickup?(colour)
|
83
|
+
return false if @game.bank.tokens[colour] < @game.options[:min_to_take_two]
|
84
|
+
return false if @player.tableau.token_space_remaining < 2
|
85
|
+
2.times { @player.tableau.add_token(colour) }
|
86
|
+
2.times { @game.bank.remove_token(colour) }
|
87
|
+
@action_done = true
|
88
|
+
end
|
89
|
+
|
90
|
+
def take_different_tokens(colours)
|
91
|
+
colours.map!{|c| c.to_sym}
|
92
|
+
return false if !validate_token_pickup?(colours)
|
93
|
+
return false if colours.count != 3
|
94
|
+
return false if colours.uniq.length != colours.length
|
95
|
+
return false if colours.select { |c| @game.bank.tokens[c]==0}.length > 0
|
96
|
+
return false if @player.tableau.token_space_remaining < 3
|
97
|
+
colours.each do |c|
|
98
|
+
@player.tableau.add_token(c)
|
99
|
+
@game.bank.remove_token(c)
|
100
|
+
end
|
101
|
+
@action_done = true
|
102
|
+
end
|
103
|
+
|
104
|
+
# in order to claim tokens you may need to return tokens, eg pick up a gold when
|
105
|
+
# you have 10, or you have 8 and want to take 3 different.
|
106
|
+
|
107
|
+
def action_return_token(colour)
|
108
|
+
return false if !@player.tableau.tokens.include?(colour)
|
109
|
+
return false if @player.tableau.tokens[colour] <= 0
|
110
|
+
@player.tableau.remove_token(colour)
|
111
|
+
@game.bank.add_token(colour)
|
112
|
+
end
|
113
|
+
|
114
|
+
def claim_noble(noble) # Move noble from bank to player, assuming it's affordable
|
115
|
+
return false if @noble_claimed # you can only claim 1 per turn
|
116
|
+
return false if !noble_affordable?(noble)
|
117
|
+
@game.nobles.delete_at(@game.nobles.index(noble) || display_row.length)
|
118
|
+
@player.nobles << noble
|
119
|
+
@noble_claimed = true
|
120
|
+
end
|
121
|
+
|
122
|
+
def claimable_nobles
|
123
|
+
@game.nobles.find_all { |noble| noble_affordable?(noble) }
|
124
|
+
end
|
125
|
+
|
126
|
+
def noble_affordable?(noble)
|
127
|
+
noble.cost.each do |colour, cost|
|
128
|
+
return false if @player.tableau.colours_on_cards(colour) < cost
|
129
|
+
end
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
def end_turn # make sure there are 4 cards showing from each deck, assuming there are any left
|
134
|
+
#assumption : max 1 card is missing from each row
|
135
|
+
nonfull_rows = @game.display.select { |_level, subdeck| subdeck.count < @game.options[:display_cards_per_row] }
|
136
|
+
nonfull_rows.each do |row_num, display_row|
|
137
|
+
relevant_deck = @game.deck[row_num]
|
138
|
+
display_row << relevant_deck.pop if relevant_deck.count > 0
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'splendor_game/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "splendor_game"
|
8
|
+
spec.version = SplendorGame::VERSION
|
9
|
+
spec.authors = ["reedstonefood"]
|
10
|
+
spec.email = ["reedstonefood@users.noreply.github.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{An implementation of the board game, Splendor.}
|
13
|
+
spec.homepage = "https://github.com/reedstonefood/splendor_game"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
26
|
+
|
27
|
+
spec.add_dependency "highline", "~> 1.7"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: splendor_game
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- reedstonefood
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-08-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.13'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.13'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: highline
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- reedstonefood@users.noreply.github.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".codeclimate.yml"
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".travis.yml"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- lib/splendor_game.rb
|
87
|
+
- lib/splendor_game/card.rb
|
88
|
+
- lib/splendor_game/cli.rb
|
89
|
+
- lib/splendor_game/coloured_object.rb
|
90
|
+
- lib/splendor_game/game.rb
|
91
|
+
- lib/splendor_game/load_cards.rb
|
92
|
+
- lib/splendor_game/noble.rb
|
93
|
+
- lib/splendor_game/options.rb
|
94
|
+
- lib/splendor_game/player.rb
|
95
|
+
- lib/splendor_game/tableau.rb
|
96
|
+
- lib/splendor_game/turn.rb
|
97
|
+
- lib/splendor_game/version.rb
|
98
|
+
- splendor_game.gemspec
|
99
|
+
homepage: https://github.com/reedstonefood/splendor_game
|
100
|
+
licenses:
|
101
|
+
- MIT
|
102
|
+
metadata: {}
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 2.6.6
|
120
|
+
signing_key:
|
121
|
+
specification_version: 4
|
122
|
+
summary: An implementation of the board game, Splendor.
|
123
|
+
test_files: []
|