-
Notifications
You must be signed in to change notification settings - Fork 0
/
game.rb
306 lines (257 loc) · 10.8 KB
/
game.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
class Game < ApplicationRecord
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
has_many :rooms, dependent: :destroy
has_many :messages, dependent: :destroy
has_one :hero, dependent: :destroy
belongs_to :user
validates_associated :user
validates_with BooleanOverStart
before_save :max_games_number
MONSTERS = ["Python serpent", "Java owlbear", "C# troll", "PHP-gnoll"]
BOSS = ["Go Lord"]
CHOICE = ["bridge", "grail"]
TREASURES = ["Pair of Climbing Shoes 👟", "MacBook 💻"]
EMPTY = %w(None) * 6
UPPER_WALL = [1, 2, 3, 4]
LOWER_WALL = [13, 14, 15, 16]
LEFT_WALL = [1, 5, 9, 13]
RIGHT_WALL = [4, 8, 12, 16]
ROOM_MOVEMENT = { "UP" => -4, "DOWN" => 4, "LEFT" => -1, "RIGHT" => 1 }
MESSAGES = { empty: ["This room is empty - Nothing to see here..."],
fight_boss: ["You've encountered you Nemesis, the dreadful Go Lord 👹.", "Time to fight!"],
victory: ["Congratulations, you won.", "These languages were no match for your Ruby sword, and your Rails armour!"],
defeat: ["Gravely wounded, you have to retreat.", "But you know you'll be back. Hasta la vista babe."] }
SCENARII = { bridge: {
description: ["You're facing the Bridge of Death 💀.", "A blind guardian asks you: 'There are 10 types of people.'", "'Which are you ?'"],
choices: ["Those who get ternary.", "Those who don't.", "Those who thought this was going to be a binary joke."],
positive_msg: ["You've crossed the bridge.", "The guardian, happy with your answer, gives you a kettlebell.", "You train and earn +5 strength 💪."],
negative_msg: ["You've crossed the bridge, but the guardian, unhappy, shot you in the face.", "As he's visually impaired, he only got your shield arm.", "You lost 2 defense 🤢."],
valid_choices: ['1', '3'],
consequence: { success: { strength: 5 }, failure: { defense: -2} }
},
grail: {
description: ["You've found 3 full cups 🏺.", "All doors are closed, and you understand that you need to drink from one of them.", " Which one will you choose?"],
choices: ["The cup decorated with diamonds.", "The cup decorated with rubies.", "The simple cup."],
positive_msg: ["The doors open, and you feel great.", "You've healed and earned 10 health points 🩹."],
negative_msg: ["The doors open, but you feel weak.", "You've lost 3 strength 🤢."],
valid_choices: ['2'],
consequence: { success: { health: 10 }, failure: { strength: -3} }
}
}
def start_setup
create_rooms
create_hero
Event.new(self, nil).add_message("Move to start your adventure.")
end
def generate_board
board_array = []
sorted_board(rooms).each do |room|
board_array << symbol(room)
end
board_array
end
def avalaible_commands
commands = {}
hero_room_base_16 = hero.room_number - sorted_board(rooms).first.id + 1
commands[:up] = !UPPER_WALL.include?(hero_room_base_16)
commands[:right] = !RIGHT_WALL.include?(hero_room_base_16)
commands[:down] = !LOWER_WALL.include?(hero_room_base_16)
commands[:left] = !LEFT_WALL.include?(hero_room_base_16)
commands
end
def symbol(room)
return ' ' if room.encounter == "None" && !room.visited
return '❌' if room.encounter == "None" && room.visited
return '🐺' if MONSTERS.include?(room.encounter)
return '🔮' if CHOICE.include?(room.encounter)
return '💰' if TREASURES.include?(room.encounter)
return '👹' if BOSS.include?(room.encounter)
'🦸' # Hero if non empty room with no opponent, choice or treasure
end
def start_adventure
update(start: false)
end
def clean_hero_room
former_room = rooms.find(hero.room_number)
former_room.update(encounter: "None", visited: true)
end
def update_hero_room(command)
new_hero_room = hero.room_number + ROOM_MOVEMENT[command]
hero.update(room_number: new_hero_room) # Change hero room
update_event(rooms.find(new_hero_room)) # Deals with encounter
rooms.find(hero.room_number).update(encounter: "Hero") unless !hero.alive # Add hero symbol to new room unless he died this turn
end
def resolve_choice(num)
Event.new(self, nil, num).choice_consequence
update(choice: nil) # Deleting this choice
check_alive
end
private
def create_rooms
rooms_content = design_balanced_rooms
rooms_content.each { |content| rooms.create(encounter: content, visited: false) } # Create them
rooms.create(encounter: "Hero", visited: false) # Populate last room with hero
end
def create_hero
self.hero = Hero.create(alive: true, health: 50, strength: 10, defense: 5, experience: 5, room_number: rooms.last.id)
end
def sorted_board(rooms)
rooms.sort_by { |room| room.id }
end
def design_balanced_rooms
result = []
loop do
result = (MONSTERS + BOSS + CHOICE + TREASURES + EMPTY).shuffle # Shuffle 15 non starting rooms
break unless result.each_slice(4).to_a[0, 3].any? { |arr| arr.uniq.size == 1 } # Recreate rooms if line 1, 2, or 3 has no encounter at all.
end
result
end
def max_games_number
return if user.games.count < 3
errors.add :base, "Can't have more than 3 games"
false
end
def is_boolean?(el)
el.is_a?(TrueClass) || el.is_a?(FalseClass)
end
def update_event(room)
Event.new(self, room).play_event
check_alive
end
def check_alive
if hero.health <= 0
hero.update(alive: false)
Event.new(self).end_game
end
end
class Event
def initialize(game, room = nil, num = nil)
@game = game
@hero = @game.hero
@encounter = room.encounter if room
@choice_num = num
@visited = room.visited if room
end
def play_event
case
when (MONSTERS + BOSS).include?(@encounter) then fight
when (TREASURES).include?(@encounter) then treasure
when (CHOICE).include?(@encounter) then choice
else @visited ? visited_room : add_message(MESSAGES[:empty], true)
end
end
def choice_consequence
scenario = SCENARII[@game.choice.to_sym]
if scenario[:valid_choices].include?(@choice_num)
add_message(scenario[:positive_msg], true)
hash = scenario[:consequence][:success]
else
add_message(scenario[:negative_msg], true)
hash = scenario[:consequence][:failure]
end
carac = hash.keys.first # Carac to be modified
new_value = @hero.send(carac) + hash.values.first # Bonus or malus to carac.
@hero.update({ hash.keys.first => new_value })
end
def add_message(msg, array = false, br = true)
messages = array ? msg : [msg]
messages.each { |message| @game.messages.create(body: message) }
@game.messages.create(body: "br") if br # Skip a line by default after adding series of messages.
end
def end_game(defeat = true)
msg = defeat ? MESSAGES[:defeat] : MESSAGES[:victory]
add_message(msg, true)
@game.update(over: true)
end
private
def visited_room
num = [rand(10) + 1, @hero.health].min # Can't lose more HP than current HP.
new_health = @hero.health - num
@hero.update(health: new_health)
messages = ["You already visited this room.", "A Ruby Dev should know better: Don't Repeat Yourself!", "Ashamed, you lost #{num} health points 🩸."]
add_message(messages, true)
end
def fight
battle_loop
end
def treasure
carac = ["defense", "strength"].sample
symbol = carac == "defense" ? '🛡' : '🔪'
value = rand(10) + 1
add_message(["You've opened the chest, and found an Epic #{@encounter}.", "Every good Ruby dev needs one.", "You've increased your #{carac} by #{value} #{symbol}."], true)
carac == "defense" ? @hero.update(defense: @hero.defense + value) : @hero.update(strength: @hero.strength + value)
end
def choice
dilemma = SCENARII[@encounter.to_sym]
@game.update(choice: @encounter)
add_message(dilemma[:description], true)
dilemma[:choices].each_with_index do |choice, idx|
add_message("#{idx + 1}: #{choice}")
end
end
def add_xp(new_xp)
@hero.update(experience: @hero.experience + new_xp)
end
def battle_loop
fighters = [Fighter.new(@hero), Fighter.new(@encounter)]
current_fighter = fighters.first
other_fighter = fighters.last
str = @encounter == "Go Lord" ? MESSAGES[:fight_boss] : ["You've encountered a #{@encounter} 🐺.", "Time to fight !"]
add_message(str, true) #Presentation of opponent
until fighters.any?(&:dead?)
current_fighter.harm(other_fighter)
add_message("#{current_fighter.symbol} 🔪 attacks 🛡 #{other_fighter.symbol} (health ❤️ #{[other_fighter.health, 0].max})", false, false)
other_fighter.actualize_hero_stats if other_fighter.symbol == '🦸'
current_fighter, other_fighter = other_fighter, current_fighter
end
add_message("br", false, false) # Skip a line after combat loop.
if @hero.health > 0 # Hero won the fight
combat_xp = @encounter == "Go Lord" ? 20 : 5
add_xp(combat_xp)
add_message(["You've vanquished the #{@encounter}!", "You've earned #{combat_xp} points of experience ✊."], true)
end_game(false) if @encounter == "Go Lord" # Over if killed boss
end
end
end
class Fighter
attr_reader :health, :strength, :defense, :experience, :symbol
def initialize(fighter)
if fighter.class == Hero
@hero = fighter
create_hero_fighter
else
@strength = 10
@defense = 5
if fighter == "Go Lord"
@symbol = '👹'
@health = 70
@experience = 20
else
@symbol = '🐺'
@health = 30
@experience = 5
end
end
end
def create_hero_fighter
@symbol = '🦸'
@health = @hero.health
@strength = @hero.strength
@defense = @hero.defense
@experience = @hero.experience
end
def actualize_hero_stats
@health = [0, @health].max
@hero.update(health: @health)
end
def harm(other)
other.health -= (strength + experience - other.defense)
end
def dead?
@health <= 0
end
protected
attr_writer :health
end
end