gemwarrior 0.15.6 → 0.15.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,610 +1,687 @@
1
- # lib/gemwarrior/repl.rb
2
- # My own, simple, Read Evaluate Print Loop module
3
-
4
- require 'readline'
5
- require 'os'
6
- require 'clocker'
7
- require 'io/console'
8
- require 'gems'
9
-
10
- require_relative 'misc/timer'
11
- require_relative 'misc/wordlist'
12
- require_relative 'evaluator'
13
- require_relative 'game_options'
14
- require_relative 'version'
15
-
16
- module Gemwarrior
17
- class Repl
18
- # CONSTANTS
19
- SCREEN_WIDTH_MIN = 80
20
- SCREEN_WIDTH_MAX = 120
21
- QUIT_MESSAGE = 'Temporal flux detected. Shutting down...'.colorize(:red)
22
- MAIN_MENU_QUIT_MESSAGE = 'Giving up so soon? Jool will be waiting...'.colorize(:red)
23
- SPLASH_MESSAGE = 'Welcome to *Jool*, where randomized fortune is just as likely as mayhem.'
24
- GITHUB_NAME = 'michaelchadwick'
25
- GITHUB_PROJECT = 'gemwarrior'
26
-
27
- attr_accessor :game, :world, :evaluator
28
-
29
- def initialize(game, world, evaluator)
30
- self.game = game
31
- self.world = world
32
- self.evaluator = evaluator
33
-
34
- GameOptions.data['wrap_width'] = get_screen_width
35
- end
36
-
37
- def get_screen_width
38
- screen_width = SCREEN_WIDTH_MIN
39
-
40
- begin
41
- require 'io/console'
42
- screen_width = IO.console.winsize[1]
43
- rescue
44
- if command_exists?('tput')
45
- screen_width = `tput cols`.to_i
46
- elsif command_exists?('stty')
47
- screen_width = `stty size`.split.last.to_i
48
- elsif command_exists?('mode')
49
- mode_output = `mode`.split
50
- screen_width = mode_output[mode_output.index('Columns:')+1].to_i
51
- end
52
- end
53
-
54
- case
55
- when screen_width.nil?, screen_width <= 0
56
- return SCREEN_WIDTH_MIN
57
- else
58
- return [screen_width, SCREEN_WIDTH_MAX].min
59
- end
60
- end
61
-
62
- def start(initial_command, extra_command, new_skip, resume_skip)
63
- setup_screen(initial_command, extra_command, new_skip, resume_skip)
64
-
65
- clocker = Clocker.new
66
-
67
- at_exit do
68
- update_duration(clocker.stop)
69
- game.update_options_file
70
- log_stats(world.duration, world.player)
71
- save_game(world)
72
- end
73
-
74
- clocker.clock do
75
- # main loop
76
- loop do
77
- prompt
78
- begin
79
- main_loop
80
- rescue Interrupt
81
- puts
82
- puts QUIT_MESSAGE
83
- exit
84
- end
85
- end
86
- end
87
- end
88
-
89
- def main_loop(ext_input = nil)
90
- input = ext_input.nil? ? read_line : ext_input
91
- result = evaluator.parse(input)
92
- if result.eql?('exit')
93
- exit
94
- elsif result.eql?('checkupdate')
95
- check_for_new_release
96
- else
97
- puts result
98
- end
99
- end
100
-
101
- # timer observer
102
- #def update(command)
103
- # main_loop(command)
104
- #end
105
-
106
- private
107
-
108
- def clear_screen
109
- OS.windows? ? system('cls') : system('clear')
110
- end
111
-
112
- def read_line
113
- prompt_text = GameOptions.data['debug_mode'] ? ' GW[D]> ' : ' GW> '
114
- Readline.readline(prompt_text, true).to_s
115
- end
116
-
117
- def puts(s = '', width = GameOptions.data['wrap_width'])
118
- super s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n") unless s.nil?
119
- end
120
-
121
- def print_logo
122
- puts '/-+-+-+ +-+-+-+-+-+-+-\\'.colorize(:yellow)
123
- puts '|G|E|M| |W|A|R|R|I|O|R|'.colorize(:yellow)
124
- puts '\\-+-+-+ +-+-+-+-+-+-+-/'.colorize(:yellow)
125
- puts '[[[[[[[DEBUGGING]]]]]]]'.colorize(:white) if GameOptions.data['debug_mode']
126
- end
127
-
128
- def print_splash_message
129
- SPLASH_MESSAGE.length.times { print '=' }
130
- puts
131
- puts SPLASH_MESSAGE
132
- SPLASH_MESSAGE.length.times { print '=' }
133
- puts
134
- end
135
-
136
- def print_fortune
137
- noun1_values = WordList.new('noun-plural')
138
- noun2_values = WordList.new('noun-plural')
139
- noun3_values = WordList.new('noun-plural')
140
-
141
- puts "* Remember: #{noun1_values.get_random_value} and #{noun2_values.get_random_value} are the key to #{noun3_values.get_random_value} *\n\n"
142
- puts
143
- end
144
-
145
- def print_about_text
146
- puts 'Gem Warrior - A Game of Fortune and Mayhem'.colorize(:yellow)
147
- puts '=========================================='.colorize(:yellow)
148
- puts 'Gem Warrior is a text adventure roguelike-lite as a RubyGem created by Michael Chadwick (mike@neb.host) and released as open-source on Github. Take on the task set by Queen Ruby to defeat the evil Emerald and get back the ShinyThing(tm) he stole for terrible, dastardly reasons.'
149
- puts
150
- puts 'Explore the land of Jool with the power of text, fighting enemies to improve your station, grabbing curious items that may or may not come in handy, and finally defeating Mr. Emerald himself to win the game.'
151
- end
152
-
153
- def print_help_text
154
- puts 'Gem Warrior - Some Basic Help Commands'.colorize(:yellow)
155
- puts '======================================'.colorize(:yellow)
156
- puts '* Basic functions: look, go, character, inventory, attack *'
157
- puts '* Type \'help\' while in-game for complete list of commands *'
158
- puts '* Most commands can be abbreviated to their first letter *'
159
- puts '* Note: if something isn\'t working, try: *'
160
- puts '* 1) starting a new game *'
161
- puts '* 2) updating the game *'
162
- end
163
-
164
- def display_log_of_attempts
165
- if File.exist?(GameOptions.data['log_file_path']) and !File.zero?(GameOptions.data['log_file_path'])
166
- File.open(GameOptions.data['log_file_path']).readlines.each do |line|
167
- print "#{line}"
168
- end
169
- if GameOptions.data['debug_mode']
170
- print 'Clear log of attempts? (y/n) '
171
- answer = gets.chomp.downcase
172
-
173
- case answer
174
- when 'y', 'yes'
175
- File.truncate(GameOptions.data['log_file_path'], 0)
176
- puts 'Log of attempts: erased!'
177
- end
178
-
179
- puts
180
- end
181
- else
182
- puts 'No attempts made yet!'
183
- end
184
- end
185
-
186
- def check_for_new_release
187
- new_release_available = false
188
- puts 'Checking releases...'
189
- remote_release = Gems.versions('gemwarrior').first['number']
190
- local_release = Gemwarrior::VERSION
191
-
192
- 0.upto(2) do |i|
193
- if remote_release.split('.')[i].to_i > local_release.split('.')[i].to_i
194
- new_release_available = true
195
- end
196
- end
197
-
198
- if new_release_available
199
- puts "GW v#{remote_release} available! Please exit and run 'gem update' before continuing."
200
- puts
201
- else
202
- puts 'You have the latest version. Fantastic!'
203
- puts
204
- end
205
- end
206
-
207
- def print_options_sound_sytem
208
- puts
209
- puts 'Sound System Selection'.colorize(:yellow)
210
- puts '================================='.colorize(:yellow)
211
- puts
212
- print ' (1) WIN32-SOUND '
213
- print '(SELECTED)'.colorize(:yellow) if GameOptions.data['sound_system'].eql?('win32-sound')
214
- print "\n"
215
- print ' (2) FEEP '
216
- print '(SELECTED)'.colorize(:yellow) if GameOptions.data['sound_system'].eql?('feep')
217
- print "\n"
218
- print ' (3) BLOOPS '
219
- print '(SELECTED)'.colorize(:yellow) if GameOptions.data['sound_system'].eql?('bloops')
220
- print "\n"
221
- puts
222
- puts ' WIN32-SOUND : good quality; Windows-only'
223
- puts ' FEEP : cross-platform; VERY SLOW and BUGGY'
224
- puts ' BLOOPS : cross-platform; requires portaudio'
225
- puts
226
- puts ' NOTE: none of these are required dependencies anymore, as sound is optional.'
227
- puts ' If you do not have the one selected installed on your machine, you will get '
228
- puts ' an error on game load, and no sound will be heard.'
229
- puts
230
- puts '================================='.colorize(:yellow)
231
- puts
232
- puts 'Enter option number to select sound system, or any other key to exit.'
233
- puts
234
- print '[GW_OPTS]-[SOUND_SYSTEM]> '
235
- answer = STDIN.getch.chomp.downcase
236
-
237
- case answer
238
- when '1'
239
- GameOptions.add 'sound_system', 'win32-sound'
240
- print_options_sound_sytem
241
- when '2'
242
- GameOptions.add 'sound_system', 'feep'
243
- print_options_sound_sytem
244
- when '3'
245
- GameOptions.add 'sound_system', 'bloops'
246
- print_options_sound_sytem
247
- else
248
- return
249
- end
250
- end
251
-
252
- def print_options
253
- puts
254
- puts 'Gem Warrior General Options'.colorize(:yellow)
255
- puts '================================='.colorize(:yellow)
256
- puts
257
- puts 'Change several sound options, whether Wordnik is used to generate more dynamic descriptors of entities (valid WORDNIK_API_KEY environment variable must be set), and if attack/fight commands need to have a target or not (if enabled, will attack first monster in vicinty).'
258
- puts
259
- puts " (1) SOUND ENABLED : #{GameOptions.data['sound_enabled']}"
260
- puts " (2) SOUND SYSTEM : #{GameOptions.data['sound_system']}"
261
- puts " (3) SOUND VOLUME : #{GameOptions.data['sound_volume']}"
262
- puts " (4) USE WORDNIK : #{GameOptions.data['use_wordnik']}"
263
- puts " (5) FIGHT COMPLETION : #{GameOptions.data['fight_completion']}"
264
- puts
265
- puts '================================='.colorize(:yellow)
266
- puts
267
- puts 'Enter option number to change value, or any other key to return to main menu.'
268
- puts
269
- print '[GW_OPTS]> '
270
-
271
- answer = STDIN.getch
272
-
273
- case answer
274
- when '1'
275
- print answer
276
- GameOptions.data['sound_enabled'] = !GameOptions.data['sound_enabled']
277
- print_options
278
- when '2'
279
- print answer
280
- print "\n"
281
- print_options_sound_sytem
282
- print_options
283
- when '3'
284
- print answer
285
- print "\n"
286
- print 'Enter a volume from 0.0 to 1.0: '
287
- new_vol = gets.chomp.to_f.abs
288
- if new_vol >= 0.0 and new_vol <= 1.0
289
- GameOptions.data['sound_volume'] = new_vol
290
- else
291
- puts 'Not a valid volume.'
292
- end
293
- print_options
294
- when '4'
295
- print answer
296
- GameOptions.data['use_wordnik'] = !GameOptions.data['use_wordnik']
297
- print_options
298
- when '5'
299
- print answer
300
- GameOptions.data['fight_completion'] = !GameOptions.data['fight_completion']
301
- print_options
302
- else
303
- print answer
304
- return
305
- end
306
- end
307
-
308
- def print_main_menu
309
- puts
310
- puts " GW v#{Gemwarrior::VERSION}"
311
- puts '======================='
312
- puts ' (R)esume Game'.colorize(:green) if save_file_exist?
313
- puts ' (N)ew Game'
314
- puts ' (A)bout'
315
- puts ' (H)elp'
316
- puts ' (O)ptions'
317
- puts ' (L)og of Attempts'
318
- puts ' (C)heck for Updates'
319
- puts ' (E)xit'.colorize(:red)
320
- puts '======================='
321
- puts
322
- end
323
-
324
- def print_main_menu_prompt
325
- print '> '
326
- end
327
-
328
- def run_main_menu(show_choices = true)
329
- print_main_menu if show_choices
330
- print_main_menu_prompt if show_choices
331
-
332
- choice = STDIN.getch.downcase
333
-
334
- case choice
335
- when 'n'
336
- if overwrite_save?
337
- clear_screen
338
- play_intro_tune
339
- print_splash_message
340
- print_fortune
341
- return
342
- else
343
- run_main_menu
344
- end
345
- when 'r'
346
- if save_file_exist?
347
- result = resume_game
348
- if result.nil?
349
- run_main_menu
350
- else
351
- print_errors
352
- load_saved_world(result)
353
- return
354
- end
355
- end
356
- when 'a'
357
- puts choice
358
- print_about_text
359
- run_main_menu
360
- when 'h'
361
- puts choice
362
- print_help_text
363
- run_main_menu
364
- when 'o'
365
- puts choice
366
- print_options
367
- run_main_menu
368
- when 'l'
369
- puts choice
370
- display_log_of_attempts
371
- run_main_menu
372
- when 'c'
373
- puts choice
374
- check_for_new_release
375
- run_main_menu
376
- when 'e', 'x', 'q'
377
- puts choice
378
- puts MAIN_MENU_QUIT_MESSAGE
379
- game.update_options_file
380
- exit
381
- when "\c?" # Backspace/Delete
382
- refresh_menu
383
- when "\e" # ANSI escape sequence
384
- case STDIN.getch
385
- when '[' # CSI
386
- choice = STDIN.getch
387
- puts choice
388
- case choice
389
- when 'A', 'B', 'C', 'D' # arrow keys
390
- refresh_menu
391
- end
392
- end
393
- else # All other invalid options
394
- refresh_menu
395
- end
396
- end
397
-
398
- # need this to handle any non-valid input at the main menu
399
- def refresh_menu
400
- clear_screen
401
- print_logo
402
- run_main_menu
403
- end
404
-
405
- def log_stats(duration, pl)
406
- # display stats upon exit
407
- Hr.print('#')
408
- print 'Gem Warrior'.colorize(color: :white, background: :black)
409
- print " v#{Gemwarrior::VERSION}".colorize(:yellow)
410
- print " played for #{duration[:mins].to_s.colorize(color: :white, background: :black)} min(s),"
411
- print " #{duration[:secs].to_s.colorize(color: :white, background: :black)} sec(s),"
412
- print " and #{duration[:ms].to_s.colorize(color: :white, background: :black)} ms\n"
413
- Hr.print('-')
414
- print "#{pl.name.ljust(10)} killed #{pl.monsters_killed.to_s.colorize(color: :yellow, background: :black)} monster(s)"
415
- print "\n".ljust(12)
416
- print "killed #{pl.bosses_killed.to_s.colorize(color: :yellow, background: :black)} boss(es)"
417
- print "\n".ljust(12)
418
- print "picked up #{pl.items_taken.to_s.colorize(color: :yellow, background: :black)} item(s)"
419
- print "\n".ljust(12)
420
- print "traveled #{pl.movements_made.to_s.colorize(color: :yellow, background: :black)} time(s)"
421
- print "\n".ljust(12)
422
- print "rested #{pl.rests_taken.to_s.colorize(color: :yellow, background: :black)} time(s)"
423
- print "\n".ljust(12)
424
- print "died #{pl.deaths.to_s.colorize(color: :yellow, background: :black)} time(s)"
425
- print "\n"
426
- Hr.print('#')
427
-
428
- # log stats to file in home directory
429
- File.open(GameOptions.data['log_file_path'], 'a') do |f|
430
- f.write "#{Time.now} #{pl.name.rjust(13)} - V:#{Gemwarrior::VERSION} LV:#{pl.level} XP:#{pl.xp} $:#{pl.rox} MK:#{pl.monsters_killed} BK:#{pl.bosses_killed} ITM:#{pl.items_taken} MOV:#{pl.movements_made} RST:#{pl.rests_taken} DTH:#{pl.deaths}\n"
431
- end
432
- end
433
-
434
- def save_game(world)
435
- mode = GameOptions.data['save_file_mode']
436
- puts 'Saving game...'
437
-
438
- if mode.eql? 'Y'
439
- File.open(GameOptions.data['save_file_yaml_path'], 'w') do |f|
440
- f.write YAML.dump(world)
441
- end
442
- elsif mode.eql? 'M'
443
- File.open(GameOptions.data['save_file_bin_path'], 'w') do |f|
444
- f.write Marshal.dump(world)
445
- end
446
- else
447
- puts 'Error: Save file mode not set. Game not saved.'
448
- return
449
- end
450
- puts 'Game saved!'
451
- end
452
-
453
- def save_file_exist?
454
- mode = GameOptions.data['save_file_mode']
455
- if mode.eql? 'Y'
456
- File.exist?(GameOptions.data['save_file_yaml_path'])
457
- elsif mode.eql? 'M'
458
- File.exist?(GameOptions.data['save_file_bin_path'])
459
- else
460
- false
461
- end
462
- end
463
-
464
- def resume_game
465
- mode = GameOptions.data['save_file_mode']
466
- puts 'Resuming game...'
467
-
468
- if mode.eql? 'Y'
469
- if File.exist?(GameOptions.data['save_file_yaml_path'])
470
- File.open(GameOptions.data['save_file_yaml_path'], 'r') do |f|
471
- return YAML.load(f)
472
- end
473
- else
474
- puts 'No save file exists.'
475
- nil
476
- end
477
- elsif mode.eql? 'M'
478
- if File.exist?(GameOptions.data['save_file_marshal_path'])
479
- File.open(GameOptions.data['save_file_marshal_path'], 'r') do |f|
480
- return Marshal.load(f)
481
- end
482
- else
483
- puts 'No save file exists.'
484
- nil
485
- end
486
- end
487
- end
488
-
489
- def overwrite_save?
490
- mode = GameOptions.data['save_file_mode']
491
- save_file_path = ''
492
-
493
- if mode.eql? 'Y'
494
- save_file_path = GameOptions.data['save_file_yaml_path']
495
- elsif mode.eql? 'M'
496
- save_file_path = GameOptions.data['save_file_marshal_path']
497
- end
498
-
499
- if File.exist?(save_file_path)
500
- print 'Overwrite existing save file? (y/n) '
501
- answer = gets.chomp.downcase
502
-
503
- case answer
504
- when 'y', 'yes'
505
- puts 'New game started! Press any key to continue.'
506
- gets
507
- return true
508
- else
509
- puts 'New game aborted.'
510
- return false
511
- end
512
- end
513
- true
514
- end
515
-
516
- def update_duration(new_duration)
517
- new_mins = new_duration[:mins]
518
- new_secs = new_duration[:secs]
519
- new_ms = new_duration[:ms]
520
-
521
- world.duration[:mins] += new_mins
522
- world.duration[:secs] += new_secs
523
- world.duration[:ms] += new_ms
524
-
525
- if world.duration[:ms] > 1000
526
- world.duration[:secs] += world.duration[:ms] / 1000
527
- world.duration[:ms] = world.duration[:ms] % 1000
528
- end
529
-
530
- if world.duration[:secs] > 60
531
- world.duration[:mins] += world.duration[:secs] / 60
532
- world.duration[:secs] = world.duration[:secs] % 60
533
- end
534
- end
535
-
536
- def load_saved_world(result)
537
- self.world = result
538
- self.evaluator = Evaluator.new(self.world)
539
- end
540
-
541
- def setup_screen(initial_command = nil, extra_command = nil, new_skip = false, resume_skip = false)
542
- # welcome player to game
543
- clear_screen
544
- print_logo
545
-
546
- # main menu loop until new game or exit
547
- if new_skip
548
- print_errors
549
- play_intro_tune
550
- print_splash_message
551
- print_fortune
552
- elsif resume_skip
553
- result = resume_game
554
- if result.nil?
555
- run_main_menu
556
- else
557
- print_errors
558
- load_saved_world(result)
559
- end
560
- else
561
- run_main_menu
562
- end
563
-
564
- # hook to do something right off the bat
565
- puts evaluator.parse(initial_command) unless initial_command.nil?
566
- puts evaluator.parse(extra_command) unless extra_command.nil?
567
- end
568
-
569
- def print_errors
570
- if GameOptions.data['errors']
571
- puts GameOptions.data['errors'].colorize(:red)
572
- end
573
- end
574
-
575
- def play_intro_tune
576
- Audio.play_synth(:intro)
577
- end
578
-
579
- def prompt
580
- prompt_template = "\n"
581
- prompt_template += "[LV:%2s][XP:%3s][ROX:%3s][HP:%3s/%-3s] [".colorize(:yellow)
582
- prompt_template += "%s".colorize(:green)
583
- prompt_template += " @ ".colorize(:yellow)
584
- prompt_template += "%s".colorize(:cyan)
585
- prompt_template += "]".colorize(:yellow)
586
- prompt_template += "[%s, %s, %s]".colorize(:yellow) if GameOptions.data['debug_mode']
587
-
588
- prompt_vars_arr = [
589
- world.player.level,
590
- world.player.xp,
591
- world.player.rox,
592
- world.player.hp_cur,
593
- world.player.hp_max,
594
- world.player.name,
595
- world.location_by_coords(world.player.cur_coords).name_display
596
- ]
597
- if GameOptions.data['debug_mode']
598
- prompt_vars_arr.push(world.player.cur_coords[:x], world.player.cur_coords[:y], world.player.cur_coords[:z])
599
- end
600
- print (prompt_template % prompt_vars_arr)
601
- print "\n"
602
- end
603
-
604
- def command_exists?(cmd)
605
- ENV['PATH'].split(File::PATH_SEPARATOR).collect { |d|
606
- Dir.entries d if Dir.exist? d
607
- }.flatten.include?(cmd)
608
- end
609
- end
610
- end
1
+ # lib/gemwarrior/repl.rb
2
+ # My own, simple, Read Evaluate Print Loop module
3
+
4
+ require 'readline'
5
+ require 'os'
6
+ require 'clocker'
7
+ require 'io/console'
8
+ require 'gems'
9
+ require 'ap'
10
+
11
+ require_relative 'misc/audio'
12
+ require_relative 'misc/timer'
13
+ require_relative 'misc/wordlist'
14
+ require_relative 'evaluator'
15
+ require_relative 'game_options'
16
+ require_relative 'version'
17
+
18
+ module Gemwarrior
19
+ class Repl
20
+ # CONSTANTS
21
+ SCREEN_WIDTH_MIN = 80
22
+ SCREEN_WIDTH_MAX = 120
23
+ QUIT_MESSAGE = 'Temporal flux detected. Shutting down...'.colorize(:red)
24
+ MAIN_MENU_QUIT_MESSAGE = 'Giving up so soon? Jool will be waiting...'.colorize(:red)
25
+ SPLASH_MESSAGE = 'Welcome to *Jool*, where randomized fortune is just as likely as mayhem.'
26
+ GITHUB_NAME = 'michaelchadwick'
27
+ GITHUB_PROJECT = 'gemwarrior'
28
+
29
+ attr_accessor :game, :world, :evaluator
30
+
31
+ def initialize(game, world, evaluator)
32
+ self.game = game
33
+ self.world = world
34
+ self.evaluator = evaluator
35
+
36
+ GameOptions.data['wrap_width'] = get_screen_width
37
+ end
38
+
39
+ def get_screen_width
40
+ screen_width = SCREEN_WIDTH_MIN
41
+
42
+ begin
43
+ require 'io/console'
44
+ screen_width = IO.console.winsize[1]
45
+ rescue
46
+ if command_exists?('tput')
47
+ screen_width = `tput cols`.to_i
48
+ elsif command_exists?('stty')
49
+ screen_width = `stty size`.split.last.to_i
50
+ elsif command_exists?('mode')
51
+ mode_output = `mode`.split
52
+ screen_width = mode_output[mode_output.index('Columns:')+1].to_i
53
+ end
54
+ end
55
+
56
+ case
57
+ when screen_width.nil?, screen_width <= 0
58
+ return SCREEN_WIDTH_MIN
59
+ else
60
+ return [screen_width, SCREEN_WIDTH_MAX].min
61
+ end
62
+ end
63
+
64
+ def start(initial_command, extra_command, new_skip, resume_skip)
65
+ setup_screen(initial_command, extra_command, new_skip, resume_skip)
66
+
67
+ clocker = Clocker.new
68
+
69
+ at_exit do
70
+ update_duration(clocker.stop)
71
+ game.update_options_file
72
+ log_stats(world.duration, world.player)
73
+ save_game(world)
74
+ end
75
+
76
+ clocker.clock do
77
+ # main loop
78
+ loop do
79
+ prompt
80
+ begin
81
+ main_loop
82
+ rescue Interrupt
83
+ puts
84
+ puts QUIT_MESSAGE
85
+ exit
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def main_loop(ext_input = nil)
92
+ input = ext_input.nil? ? read_line : ext_input
93
+ result = evaluator.parse(input)
94
+ if result.eql?('exit')
95
+ exit
96
+ elsif result.eql?('checkupdate')
97
+ check_for_new_release
98
+ else
99
+ puts result
100
+ end
101
+ end
102
+
103
+ # timer observer
104
+ #def update(command)
105
+ # main_loop(command)
106
+ #end
107
+
108
+ private
109
+
110
+ def clear_screen
111
+ OS.windows? ? system('cls') : system('clear')
112
+ end
113
+
114
+ def read_line
115
+ prompt_text = GameOptions.data['debug_mode'] ? ' GW[D]> ' : ' GW> '
116
+ Readline.readline(prompt_text, true).to_s
117
+ end
118
+
119
+ def get_save_file_name
120
+ if save_file_exist?
121
+ File.open(GameOptions.data['save_file_yaml_path'], 'r') do |f|
122
+ return YAML.load(f)
123
+ end
124
+ end
125
+ end
126
+
127
+ def puts(s = '', width = GameOptions.data['wrap_width'])
128
+ super s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n") unless s.nil?
129
+ end
130
+
131
+ def print_logo
132
+ puts '/-+-+-+ +-+-+-+-+-+-+-\\'.colorize(:yellow)
133
+ puts '|G|E|M| |W|A|R|R|I|O|R|'.colorize(:yellow)
134
+ puts '\\-+-+-+ +-+-+-+-+-+-+-/'.colorize(:yellow)
135
+ puts '[[[[[[[DEBUGGING]]]]]]]'.colorize(:white) if GameOptions.data['debug_mode']
136
+ end
137
+
138
+ def print_splash_message
139
+ SPLASH_MESSAGE.length.times { print '=' }
140
+ puts
141
+ puts SPLASH_MESSAGE
142
+ SPLASH_MESSAGE.length.times { print '=' }
143
+ puts
144
+ end
145
+
146
+ def print_fortune
147
+ noun1_values = WordList.new('noun-plural')
148
+ noun2_values = WordList.new('noun-plural')
149
+ noun3_values = WordList.new('noun-plural')
150
+
151
+ puts "* Remember: #{noun1_values.get_random_value.colorize(:yellow)} and #{noun2_values.get_random_value.colorize(:yellow)} are the key to #{noun3_values.get_random_value.colorize(:yellow)} *\n\n"
152
+ puts
153
+ end
154
+
155
+ def print_about_text
156
+ puts 'Gem Warrior - A Game of Fortune and Mayhem'.colorize(:yellow)
157
+ puts '=========================================='.colorize(:yellow)
158
+ puts 'Gem Warrior is a text adventure roguelike-lite as a RubyGem created by Michael Chadwick (mike@neb.host) and released as open-source on Github. Take on the task set by Queen Ruby to defeat the evil Emerald and get back the ShinyThing(tm) he stole for terrible, dastardly reasons.'
159
+ puts
160
+ puts 'Explore the land of Jool with the power of text, fighting enemies to improve your station, grabbing curious items that may or may not come in handy, and finally defeating Mr. Emerald himself to win the game.'
161
+ end
162
+
163
+ def print_help_text
164
+ puts 'Gem Warrior - Some Basic Help Commands'.colorize(:yellow)
165
+ puts '======================================'.colorize(:yellow)
166
+ puts '* Basic functions: look, go, character, inventory, attack *'
167
+ puts '* Type \'help\' while in-game for complete list of commands *'
168
+ puts '* Most commands can be abbreviated to their first letter *'
169
+ puts '* Note: if something isn\'t working, try: *'
170
+ puts '* 1) starting a new game *'
171
+ puts '* 2) updating the game *'
172
+ end
173
+
174
+ def display_log_of_attempts
175
+ if File.exist?(GameOptions.data['log_file_path']) and !File.zero?(GameOptions.data['log_file_path'])
176
+ File.open(GameOptions.data['log_file_path']).readlines.each do |line|
177
+ print "#{line}"
178
+ end
179
+ if GameOptions.data['debug_mode']
180
+ print 'Clear log of attempts? (y/n) '
181
+ answer = gets.chomp.downcase
182
+
183
+ case answer
184
+ when 'y', 'yes'
185
+ File.truncate(GameOptions.data['log_file_path'], 0)
186
+ puts 'Log of attempts: erased!'
187
+ end
188
+
189
+ puts
190
+ end
191
+ else
192
+ puts 'No attempts made yet!'
193
+ end
194
+ end
195
+
196
+ def check_for_new_release
197
+ new_release_available = false
198
+ puts 'Checking releases...'
199
+ remote_release = Gems.versions('gemwarrior').first['number']
200
+ local_release = Gemwarrior::VERSION
201
+
202
+ 0.upto(2) do |i|
203
+ if remote_release.split('.')[i].to_i > local_release.split('.')[i].to_i
204
+ new_release_available = true
205
+ end
206
+ end
207
+
208
+ if new_release_available
209
+ puts "GW v#{remote_release} available! Please exit and run 'gem update' before continuing."
210
+ puts
211
+ else
212
+ puts 'You have the latest version. Fantastic!'
213
+ puts
214
+ end
215
+ end
216
+
217
+ def print_options_sound_sytem
218
+ puts
219
+ puts 'Sound System Selection'.colorize(:yellow)
220
+ puts '================================='.colorize(:yellow)
221
+ puts
222
+ win32 = ' (1) WIN32-SOUND '
223
+ OS.windows? ? (print win32) : (print win32.colorize(:light_black))
224
+ print '(SELECTED)'.colorize(:yellow) if GameOptions.data['sound_system'].eql?('win32-sound')
225
+ print "\n"
226
+ print ' (2) FEEP '
227
+ print '(SELECTED)'.colorize(:yellow) if GameOptions.data['sound_system'].eql?('feep')
228
+ print "\n"
229
+ print ' (3) BLOOPS '
230
+ print '(SELECTED)'.colorize(:yellow) if GameOptions.data['sound_system'].eql?('bloops')
231
+ print "\n"
232
+ print "\n"
233
+ print ' (T) TEST SOUND '
234
+ print "\n"
235
+ puts
236
+ puts ' WIN32-SOUND : good quality; Windows-only'
237
+ puts ' FEEP : cross-platform; VERY SLOW and BUGGY'
238
+ puts ' BLOOPS : cross-platform; requires portaudio'
239
+ puts
240
+ puts ' NOTE: none of these are required dependencies anymore, as sound is optional.'
241
+ puts ' If you do not have the one selected installed on your machine, you will get '
242
+ puts ' an error on game load, and no sound will be heard.'
243
+ puts
244
+ puts '================================='.colorize(:yellow)
245
+ puts
246
+ puts 'Enter option number to select sound system, or any other key to exit.'
247
+ puts
248
+ print '[GW_OPTS]-[SOUND_SYSTEM]> '
249
+
250
+ answer = STDIN.getch.chomp.downcase
251
+
252
+ case answer
253
+ when '1'
254
+ if OS.windows?
255
+ puts answer
256
+ GameOptions.add 'sound_system', 'win32-sound'
257
+ else
258
+ puts
259
+ end
260
+ print_options_sound_sytem
261
+ when '2'
262
+ puts answer
263
+ GameOptions.add 'sound_system', 'feep'
264
+ print_options_sound_sytem
265
+ when '3'
266
+ puts answer
267
+ GameOptions.add 'sound_system', 'bloops'
268
+ print_options_sound_sytem
269
+ when 't'
270
+ puts answer
271
+ Audio.init
272
+ play_test_tune
273
+ print_errors
274
+ print_options_sound_sytem
275
+ else
276
+ puts
277
+ return
278
+ end
279
+ end
280
+
281
+ def print_options
282
+ puts
283
+ puts 'Gem Warrior General Options'.colorize(:yellow)
284
+ puts '================================='.colorize(:yellow)
285
+ puts
286
+ puts 'Change several sound options, whether Wordnik is used to generate more dynamic descriptors of entities (valid WORDNIK_API_KEY environment variable must be set), and if attack/fight commands need to have a target or not (if enabled, will attack first monster in vicinty).'
287
+ puts
288
+ puts " (1) SOUND ENABLED : #{GameOptions.data['sound_enabled']}"
289
+ puts " (2) SOUND SYSTEM : #{GameOptions.data['sound_system']}"
290
+ puts " (3) SOUND VOLUME : #{GameOptions.data['sound_volume']}"
291
+ puts " (4) USE WORDNIK : #{GameOptions.data['use_wordnik']}"
292
+ puts " (5) FIGHT COMPLETION : #{GameOptions.data['fight_completion']}"
293
+ puts
294
+ puts '================================='.colorize(:yellow)
295
+ puts
296
+ puts 'Enter option number to change value, or any other key to return to main menu.'
297
+ puts
298
+ print '[GW_OPTS]> '
299
+
300
+ answer = STDIN.getch
301
+
302
+ case answer
303
+ when '1'
304
+ print answer
305
+ GameOptions.data['sound_enabled'] = !GameOptions.data['sound_enabled']
306
+ print_options
307
+ when '2'
308
+ print answer
309
+ print "\n"
310
+ print_options_sound_sytem
311
+ print_options
312
+ when '3'
313
+ print answer
314
+ print "\n"
315
+ print 'Enter a volume from 0.0 to 1.0: '
316
+ new_vol = gets.chomp.to_f.abs
317
+ if new_vol >= 0.0 and new_vol <= 1.0
318
+ GameOptions.data['sound_volume'] = new_vol
319
+ else
320
+ puts 'Not a valid volume.'
321
+ end
322
+ print_options
323
+ when '4'
324
+ print answer
325
+ GameOptions.data['use_wordnik'] = !GameOptions.data['use_wordnik']
326
+ print_options
327
+ when '5'
328
+ print answer
329
+ GameOptions.data['fight_completion'] = !GameOptions.data['fight_completion']
330
+ print_options
331
+ else
332
+ print answer
333
+ return
334
+ end
335
+ end
336
+
337
+ def print_sound_test
338
+ puts
339
+ puts 'Gem Warrior Sound Test'.colorize(:yellow)
340
+ puts '================================='.colorize(:yellow)
341
+ puts
342
+ puts 'Play all the sounds in Gemwarrior.'
343
+ puts
344
+ for cue in Audio.get_cues do
345
+ pp cue[0].to_s
346
+ end
347
+ puts
348
+ puts '================================='.colorize(:yellow)
349
+ puts
350
+ puts 'Enter sound id to play it, or enter "x" to return to the main menu.'
351
+ puts
352
+ print '[GW_SOUND_TEST]> '
353
+
354
+ answer = gets.chomp.downcase
355
+
356
+ # print answer
357
+ if answer.eql?('x')
358
+ return
359
+ else
360
+ Audio.play_synth(answer.to_sym)
361
+ print_sound_test
362
+ end
363
+ end
364
+
365
+ def print_main_menu
366
+ puts
367
+ puts " GW v#{Gemwarrior::VERSION}"
368
+ puts '======================='
369
+
370
+ save_file = get_save_file_name
371
+ if not save_file.nil?
372
+ recent_name = save_file.instance_variable_get(:@player).name
373
+ puts " #{'(R)esume Game'.colorize(:green)} as #{recent_name.colorize(:yellow)}"
374
+ end
375
+
376
+ puts ' (N)ew Game'
377
+ puts ' (A)bout'
378
+ puts ' (H)elp'
379
+ puts ' (O)ptions'
380
+ puts ' (L)og of Attempts'
381
+ puts ' (S)ound Test'
382
+ puts ' (C)heck for Updates'
383
+ puts ' (E)xit'.colorize(:red)
384
+ puts '======================='
385
+ puts
386
+ end
387
+
388
+ def print_main_menu_prompt
389
+ print '> '
390
+ end
391
+
392
+ def run_main_menu(show_choices = true)
393
+ print_main_menu if show_choices
394
+ print_main_menu_prompt if show_choices
395
+
396
+ choice = STDIN.getch.downcase
397
+
398
+ case choice
399
+ when 'n'
400
+ if overwrite_save?
401
+ clear_screen
402
+ play_intro_tune
403
+ print_splash_message
404
+ print_fortune
405
+ return
406
+ else
407
+ run_main_menu
408
+ end
409
+ when 'r'
410
+ if save_file_exist?
411
+ result = resume_game
412
+ if result.nil?
413
+ run_main_menu
414
+ else
415
+ print_errors
416
+ load_saved_world(result)
417
+ return
418
+ end
419
+ end
420
+ when 'a'
421
+ puts choice
422
+ print_about_text
423
+ run_main_menu
424
+ when 'h'
425
+ puts choice
426
+ print_help_text
427
+ run_main_menu
428
+ when 'o'
429
+ puts choice
430
+ print_options
431
+ run_main_menu
432
+ when 'l'
433
+ puts choice
434
+ display_log_of_attempts
435
+ run_main_menu
436
+ when 's'
437
+ puts choice
438
+ print_sound_test
439
+ run_main_menu
440
+ when 'c'
441
+ puts choice
442
+ check_for_new_release
443
+ run_main_menu
444
+ when 'e', 'x', 'q'
445
+ puts choice
446
+ puts MAIN_MENU_QUIT_MESSAGE
447
+ game.update_options_file
448
+ exit
449
+ when "\c?" # Backspace/Delete
450
+ refresh_menu
451
+ when "\e" # ANSI escape sequence
452
+ case STDIN.getch
453
+ when '[' # CSI
454
+ choice = STDIN.getch
455
+ puts choice
456
+ case choice
457
+ when 'A', 'B', 'C', 'D' # arrow keys
458
+ refresh_menu
459
+ end
460
+ end
461
+ else # All other invalid options
462
+ refresh_menu
463
+ end
464
+ end
465
+
466
+ # need this to handle any non-valid input at the main menu
467
+ def refresh_menu
468
+ clear_screen
469
+ print_logo
470
+ run_main_menu
471
+ end
472
+
473
+ def log_stats(duration, pl)
474
+ # display stats upon exit
475
+ Hr.print('#')
476
+ print 'Gem Warrior'.colorize(color: :white, background: :black)
477
+ print " v#{Gemwarrior::VERSION}".colorize(:yellow)
478
+ print " played for #{duration[:mins].to_s.colorize(color: :white, background: :black)} min(s)"
479
+ print ", #{duration[:secs].to_s.colorize(color: :white, background: :black)} sec(s)"
480
+ # print ", and #{duration[:ms].to_s.colorize(color: :white, background: :black)} ms"
481
+ print "\n"
482
+ Hr.print('-')
483
+ print "#{pl.name.ljust(10).colorize(:green)} "
484
+ print "destroyed #{pl.monsters_killed.to_s.colorize(color: :yellow, background: :black)} monster(s)"
485
+ print "\n".ljust(12)
486
+ print "destroyed #{pl.bosses_killed.to_s.colorize(color: :yellow, background: :black)} boss(es)"
487
+ print "\n".ljust(12)
488
+ print "picked up #{pl.items_taken.to_s.colorize(color: :yellow, background: :black)} item(s)"
489
+ print "\n".ljust(12)
490
+ print "traveled #{pl.movements_made.to_s.colorize(color: :yellow, background: :black)} time(s)"
491
+ print "\n".ljust(12)
492
+ print "rested #{pl.rests_taken.to_s.colorize(color: :yellow, background: :black)} time(s)"
493
+ print "\n".ljust(12)
494
+ print "died #{pl.deaths.to_s.colorize(color: :yellow, background: :black)} time(s)"
495
+ print "\n"
496
+ Hr.print('#')
497
+
498
+ # log stats to file in home directory
499
+ File.open(GameOptions.data['log_file_path'], 'a') do |f|
500
+ f.write "#{Time.now} #{pl.name.rjust(13)} - V:#{Gemwarrior::VERSION} LV:#{pl.level} XP:#{pl.xp} $:#{pl.rox} MK:#{pl.monsters_killed} BK:#{pl.bosses_killed} ITM:#{pl.items_taken} MOV:#{pl.movements_made} RST:#{pl.rests_taken} DTH:#{pl.deaths}\n"
501
+ end
502
+ end
503
+
504
+ def save_game(world)
505
+ mode = GameOptions.data['save_file_mode']
506
+ puts 'Saving game...'
507
+
508
+ if mode.eql? 'Y'
509
+ File.open(GameOptions.data['save_file_yaml_path'], 'w') do |f|
510
+ f.write YAML.dump(world)
511
+ end
512
+ elsif mode.eql? 'M'
513
+ File.open(GameOptions.data['save_file_bin_path'], 'w') do |f|
514
+ f.write Marshal.dump(world)
515
+ end
516
+ else
517
+ puts 'Error: Save file mode not set. Game not saved.'
518
+ return
519
+ end
520
+ puts 'Game saved!'
521
+ end
522
+
523
+ def save_file_exist?
524
+ mode = GameOptions.data['save_file_mode']
525
+ if mode.eql? 'Y'
526
+ File.exist?(GameOptions.data['save_file_yaml_path'])
527
+ elsif mode.eql? 'M'
528
+ File.exist?(GameOptions.data['save_file_bin_path'])
529
+ else
530
+ false
531
+ end
532
+ end
533
+
534
+ def resume_game
535
+ mode = GameOptions.data['save_file_mode']
536
+ puts 'Resuming game...'
537
+
538
+ if mode.eql? 'Y'
539
+ if File.exist?(GameOptions.data['save_file_yaml_path'])
540
+ File.open(GameOptions.data['save_file_yaml_path'], 'r') do |f|
541
+ return YAML.load(f)
542
+ end
543
+ else
544
+ puts 'No save file exists.'
545
+ nil
546
+ end
547
+ elsif mode.eql? 'M'
548
+ if File.exist?(GameOptions.data['save_file_marshal_path'])
549
+ File.open(GameOptions.data['save_file_marshal_path'], 'r') do |f|
550
+ return Marshal.load(f)
551
+ end
552
+ else
553
+ puts 'No save file exists.'
554
+ nil
555
+ end
556
+ end
557
+ end
558
+
559
+ def overwrite_save?
560
+ mode = GameOptions.data['save_file_mode']
561
+ save_file_path = ''
562
+
563
+ if mode.eql? 'Y'
564
+ save_file_path = GameOptions.data['save_file_yaml_path']
565
+ elsif mode.eql? 'M'
566
+ save_file_path = GameOptions.data['save_file_marshal_path']
567
+ end
568
+
569
+ if File.exist?(save_file_path)
570
+ print 'Overwrite existing save file? (y/n) '
571
+ answer = gets.chomp.downcase
572
+
573
+ case answer
574
+ when 'y', 'yes'
575
+ puts 'New game started! Press any key to continue.'
576
+ gets
577
+ return true
578
+ else
579
+ puts 'New game aborted.'
580
+ return false
581
+ end
582
+ end
583
+ true
584
+ end
585
+
586
+ def update_duration(new_duration)
587
+ new_mins = new_duration[:mins]
588
+ new_secs = new_duration[:secs]
589
+ new_ms = new_duration[:ms]
590
+
591
+ world.duration[:mins] += new_mins
592
+ world.duration[:secs] += new_secs
593
+ world.duration[:ms] += new_ms
594
+
595
+ if world.duration[:ms] > 1000
596
+ world.duration[:secs] += world.duration[:ms] / 1000
597
+ world.duration[:ms] = world.duration[:ms] % 1000
598
+ end
599
+
600
+ if world.duration[:secs] > 60
601
+ world.duration[:mins] += world.duration[:secs] / 60
602
+ world.duration[:secs] = world.duration[:secs] % 60
603
+ end
604
+ end
605
+
606
+ def load_saved_world(result)
607
+ self.world = result
608
+ self.evaluator = Evaluator.new(self.world)
609
+ end
610
+
611
+ def setup_screen(initial_command = nil, extra_command = nil, new_skip = false, resume_skip = false)
612
+ # welcome player to game
613
+ clear_screen
614
+ print_logo
615
+
616
+ # main menu loop until new game or exit
617
+ if new_skip
618
+ print_errors
619
+ play_intro_tune
620
+ print_splash_message
621
+ print_fortune
622
+ elsif resume_skip
623
+ result = resume_game
624
+ if result.nil?
625
+ run_main_menu
626
+ else
627
+ print_errors
628
+ load_saved_world(result)
629
+ end
630
+ else
631
+ run_main_menu
632
+ end
633
+
634
+ # hook to do something right off the bat
635
+ puts evaluator.parse(initial_command) unless initial_command.nil?
636
+ puts evaluator.parse(extra_command) unless extra_command.nil?
637
+ end
638
+
639
+ def print_errors
640
+ if GameOptions.data['errors']
641
+ puts "\n\n"
642
+ puts "Errors: #{GameOptions.data['errors'].colorize(:red)}"
643
+ end
644
+ end
645
+
646
+ def play_intro_tune
647
+ Audio.play_synth(:intro)
648
+ end
649
+
650
+ def play_test_tune
651
+ Audio.play_synth(:test)
652
+ end
653
+
654
+ def prompt
655
+ prompt_template = "\n"
656
+ prompt_template += "[LV:%2s][XP:%3s][ROX:%3s][HP:%3s/%-3s] [".colorize(:yellow)
657
+ prompt_template += "%s".colorize(:green)
658
+ prompt_template += " @ ".colorize(:yellow)
659
+ prompt_template += "%s".colorize(:cyan)
660
+ prompt_template += "]".colorize(:yellow)
661
+ prompt_template += "[%s, %s, %s]".colorize(:yellow) if GameOptions.data['debug_mode']
662
+ prompt_template += "\n"
663
+ prompt_template += '[c:character][i:inventory][l:look][u:use][t:take]'
664
+
665
+ prompt_vars_arr = [
666
+ world.player.level,
667
+ world.player.xp,
668
+ world.player.rox,
669
+ world.player.hp_cur,
670
+ world.player.hp_max,
671
+ world.player.name,
672
+ world.location_by_coords(world.player.cur_coords).name_display
673
+ ]
674
+ if GameOptions.data['debug_mode']
675
+ prompt_vars_arr.push(world.player.cur_coords[:x], world.player.cur_coords[:y], world.player.cur_coords[:z])
676
+ end
677
+ print (prompt_template % prompt_vars_arr)
678
+ print "\n"
679
+ end
680
+
681
+ def command_exists?(cmd)
682
+ ENV['PATH'].split(File::PATH_SEPARATOR).collect { |d|
683
+ Dir.entries d if Dir.exist? d
684
+ }.flatten.include?(cmd)
685
+ end
686
+ end
687
+ end