gemwarrior 0.15.5 → 0.15.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e974792259f0ed169c712e4b93c2848a063cf97490696a7ee7b198b6d52a26eb
4
- data.tar.gz: 7b7d820704431ad6ac7d03b2a59922b304eeba07d828882a19e4f3363b55d3d7
3
+ metadata.gz: 6362984240883f372d441158745f67e6363a3fa9915949c31cf2b8ccd77b5cf8
4
+ data.tar.gz: 5013a38c0ac68063ac551dd1aece3462cf6363049f5b8978575667de83b91d89
5
5
  SHA512:
6
- metadata.gz: 4e291b78434c5ea2cadbdb6f0b48d03d612fe1f75892ed7f4bb9e4fe08faa2015ff29b566e22a29093eba9f88ed9f52baa85471593742a438373bc3abce4b22d
7
- data.tar.gz: 30ca5643b4f02e8e1c042e594da3d2de28ea733c4447af5615e591b9dc777d0434ca5ce05743900a3405f700037cde9d12f15bf676174c1fc63410441a31756b
6
+ metadata.gz: 316f08a8b23dfbd90975a39bafcf6ec760ab4a45113ef5948880095a52f5673598be3842ba306efe815601472373d4b5df59a0be9fb8ce51221004917a6ea544
7
+ data.tar.gz: ec7e62d2dbb18b2d58c489e53d424de3931f0a989c9ab537eda5697830a9448fd9ec090557fe0bbc1cde688c2c2afa8bcd08f76622112462be04f07d02441756
@@ -22,8 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'os', '~> 0.9', '>= 0.9.6'
23
23
  spec.add_runtime_dependency 'http', '~> 0.8.10'
24
24
  spec.add_runtime_dependency 'json', '~> 1.8.2'
25
- spec.add_runtime_dependency 'colorize', '~> 0.7.7'
26
- spec.add_runtime_dependency 'matrext', '~> 0.4.10'
25
+ spec.add_runtime_dependency 'colorize', '~> 0.8'
26
+ spec.add_runtime_dependency 'matrext', '~> 1'
27
27
  spec.add_runtime_dependency 'clocker', '~> 0.1.6'
28
28
  spec.add_runtime_dependency 'gems', '~> 0.8.3'
29
29
 
@@ -1,591 +1,610 @@
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@codana.me) 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
- else
382
- run_main_menu(show_choices: false)
383
- end
384
- end
385
-
386
- def log_stats(duration, pl)
387
- # display stats upon exit
388
- Hr.print('#')
389
- print 'Gem Warrior'.colorize(color: :white, background: :black)
390
- print " v#{Gemwarrior::VERSION}".colorize(:yellow)
391
- print " played for #{duration[:mins].to_s.colorize(color: :white, background: :black)} min(s),"
392
- print " #{duration[:secs].to_s.colorize(color: :white, background: :black)} sec(s),"
393
- print " and #{duration[:ms].to_s.colorize(color: :white, background: :black)} ms\n"
394
- Hr.print('-')
395
- print "#{pl.name.ljust(10)} killed #{pl.monsters_killed.to_s.colorize(color: :yellow, background: :black)} monster(s)"
396
- print "\n".ljust(12)
397
- print "killed #{pl.bosses_killed.to_s.colorize(color: :yellow, background: :black)} boss(es)"
398
- print "\n".ljust(12)
399
- print "picked up #{pl.items_taken.to_s.colorize(color: :yellow, background: :black)} item(s)"
400
- print "\n".ljust(12)
401
- print "traveled #{pl.movements_made.to_s.colorize(color: :yellow, background: :black)} time(s)"
402
- print "\n".ljust(12)
403
- print "rested #{pl.rests_taken.to_s.colorize(color: :yellow, background: :black)} time(s)"
404
- print "\n".ljust(12)
405
- print "died #{pl.deaths.to_s.colorize(color: :yellow, background: :black)} time(s)"
406
- print "\n"
407
- Hr.print('#')
408
-
409
- # log stats to file in home directory
410
- File.open(GameOptions.data['log_file_path'], 'a') do |f|
411
- 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"
412
- end
413
- end
414
-
415
- def save_game(world)
416
- mode = GameOptions.data['save_file_mode']
417
- puts 'Saving game...'
418
-
419
- if mode.eql? 'Y'
420
- File.open(GameOptions.data['save_file_yaml_path'], 'w') do |f|
421
- f.write YAML.dump(world)
422
- end
423
- elsif mode.eql? 'M'
424
- File.open(GameOptions.data['save_file_bin_path'], 'w') do |f|
425
- f.write Marshal.dump(world)
426
- end
427
- else
428
- puts 'Error: Save file mode not set. Game not saved.'
429
- return
430
- end
431
- puts 'Game saved!'
432
- end
433
-
434
- def save_file_exist?
435
- mode = GameOptions.data['save_file_mode']
436
- if mode.eql? 'Y'
437
- File.exist?(GameOptions.data['save_file_yaml_path'])
438
- elsif mode.eql? 'M'
439
- File.exist?(GameOptions.data['save_file_bin_path'])
440
- else
441
- false
442
- end
443
- end
444
-
445
- def resume_game
446
- mode = GameOptions.data['save_file_mode']
447
- puts 'Resuming game...'
448
-
449
- if mode.eql? 'Y'
450
- if File.exist?(GameOptions.data['save_file_yaml_path'])
451
- File.open(GameOptions.data['save_file_yaml_path'], 'r') do |f|
452
- return YAML.load(f)
453
- end
454
- else
455
- puts 'No save file exists.'
456
- nil
457
- end
458
- elsif mode.eql? 'M'
459
- if File.exist?(GameOptions.data['save_file_marshal_path'])
460
- File.open(GameOptions.data['save_file_marshal_path'], 'r') do |f|
461
- return Marshal.load(f)
462
- end
463
- else
464
- puts 'No save file exists.'
465
- nil
466
- end
467
- end
468
- end
469
-
470
- def overwrite_save?
471
- mode = GameOptions.data['save_file_mode']
472
- save_file_path = ''
473
-
474
- if mode.eql? 'Y'
475
- save_file_path = GameOptions.data['save_file_yaml_path']
476
- elsif mode.eql? 'M'
477
- save_file_path = GameOptions.data['save_file_marshal_path']
478
- end
479
-
480
- if File.exist?(save_file_path)
481
- print 'Overwrite existing save file? (y/n) '
482
- answer = gets.chomp.downcase
483
-
484
- case answer
485
- when 'y', 'yes'
486
- puts 'New game started! Press any key to continue.'
487
- gets
488
- return true
489
- else
490
- puts 'New game aborted.'
491
- return false
492
- end
493
- end
494
- true
495
- end
496
-
497
- def update_duration(new_duration)
498
- new_mins = new_duration[:mins]
499
- new_secs = new_duration[:secs]
500
- new_ms = new_duration[:ms]
501
-
502
- world.duration[:mins] += new_mins
503
- world.duration[:secs] += new_secs
504
- world.duration[:ms] += new_ms
505
-
506
- if world.duration[:ms] > 1000
507
- world.duration[:secs] += world.duration[:ms] / 1000
508
- world.duration[:ms] = world.duration[:ms] % 1000
509
- end
510
-
511
- if world.duration[:secs] > 60
512
- world.duration[:mins] += world.duration[:secs] / 60
513
- world.duration[:secs] = world.duration[:secs] % 60
514
- end
515
- end
516
-
517
- def load_saved_world(result)
518
- self.world = result
519
- self.evaluator = Evaluator.new(self.world)
520
- end
521
-
522
- def setup_screen(initial_command = nil, extra_command = nil, new_skip = false, resume_skip = false)
523
- # welcome player to game
524
- clear_screen
525
- print_logo
526
-
527
- # main menu loop until new game or exit
528
- if new_skip
529
- print_errors
530
- play_intro_tune
531
- print_splash_message
532
- print_fortune
533
- elsif resume_skip
534
- result = resume_game
535
- if result.nil?
536
- run_main_menu
537
- else
538
- print_errors
539
- load_saved_world(result)
540
- end
541
- else
542
- run_main_menu
543
- end
544
-
545
- # hook to do something right off the bat
546
- puts evaluator.parse(initial_command) unless initial_command.nil?
547
- puts evaluator.parse(extra_command) unless extra_command.nil?
548
- end
549
-
550
- def print_errors
551
- if GameOptions.data['errors']
552
- puts GameOptions.data['errors'].colorize(:red)
553
- end
554
- end
555
-
556
- def play_intro_tune
557
- Audio.play_synth(:intro)
558
- end
559
-
560
- def prompt
561
- prompt_template = "\n"
562
- prompt_template += "[LV:%2s][XP:%3s][ROX:%3s][HP:%3s/%-3s] [".colorize(:yellow)
563
- prompt_template += "%s".colorize(:green)
564
- prompt_template += " @ ".colorize(:yellow)
565
- prompt_template += "%s".colorize(:cyan)
566
- prompt_template += "]".colorize(:yellow)
567
- prompt_template += "[%s, %s, %s]".colorize(:yellow) if GameOptions.data['debug_mode']
568
-
569
- prompt_vars_arr = [
570
- world.player.level,
571
- world.player.xp,
572
- world.player.rox,
573
- world.player.hp_cur,
574
- world.player.hp_max,
575
- world.player.name,
576
- world.location_by_coords(world.player.cur_coords).name_display
577
- ]
578
- if GameOptions.data['debug_mode']
579
- prompt_vars_arr.push(world.player.cur_coords[:x], world.player.cur_coords[:y], world.player.cur_coords[:z])
580
- end
581
- print (prompt_template % prompt_vars_arr)
582
- print "\n"
583
- end
584
-
585
- def command_exists?(cmd)
586
- ENV['PATH'].split(File::PATH_SEPARATOR).collect { |d|
587
- Dir.entries d if Dir.exist? d
588
- }.flatten.include?(cmd)
589
- end
590
- end
591
- 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
+
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