lots 1.0.1
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/bin/lots +4 -0
- data/lib/character.rb +124 -0
- data/lib/enemy.rb +71 -0
- data/lib/main.rb +146 -0
- data/lib/story.rb +93 -0
- data/lib/ui.rb +262 -0
- data/lib/world.rb +152 -0
- data/lots.rb +43 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4ea6a48ec0e11748ce6abb80dcfafaa15e52d4bc
|
4
|
+
data.tar.gz: d442fadaa33099b3d26c8f85135cfdf3df65a0de
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 91ab6cf3f8fede65c5444a24f74c0e8dfd9ee2ce11b82aefb4e4c49a9ddb7f21af7f9df5bca4f554896b2351f3b0545e3b2b78b638047a90f9747d854b6cd7ca
|
7
|
+
data.tar.gz: 9c2d12d07804768ba03653c3addaf62e9768afacc40430c17ec8ca70597c5469832aeb6e47791ae6cd72ef14e07ad13fe2ac92bceabeff5e67851b9b93480da7
|
data/bin/lots
ADDED
data/lib/character.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
|
8
|
+
module LOTS
|
9
|
+
|
10
|
+
ENEMY_KILLED = "KILLED"
|
11
|
+
|
12
|
+
HIT_CHANCE_MODIFIER = 5
|
13
|
+
ATTACK_VALUE_MODIFIER = 1
|
14
|
+
|
15
|
+
class Character
|
16
|
+
|
17
|
+
attr_accessor :name
|
18
|
+
attr_accessor :health
|
19
|
+
attr_accessor :mana
|
20
|
+
attr_accessor :x
|
21
|
+
attr_accessor :y
|
22
|
+
attr_accessor :level
|
23
|
+
attr_accessor :str
|
24
|
+
attr_accessor :int
|
25
|
+
attr_accessor :in_combat
|
26
|
+
attr_accessor :current_enemy
|
27
|
+
attr_accessor :lines
|
28
|
+
attr_accessor :dead
|
29
|
+
|
30
|
+
def initialize (args)
|
31
|
+
name = args[:name]
|
32
|
+
world = args[:world]
|
33
|
+
@name = name
|
34
|
+
@level = 1
|
35
|
+
@health = 100
|
36
|
+
@mana = 100
|
37
|
+
@str = 5
|
38
|
+
@int = 5
|
39
|
+
@x = 1
|
40
|
+
@y = world.get_height
|
41
|
+
@in_combat = false
|
42
|
+
@current_enemy = nil
|
43
|
+
@lines = 0
|
44
|
+
@dead = 0
|
45
|
+
return "Welcome %{name}! Let's play Legend of the Sourcerer!"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Player attacks enemy
|
49
|
+
def attack(args)
|
50
|
+
player = self
|
51
|
+
enemy = args[:enemy]
|
52
|
+
ui = args[:ui]
|
53
|
+
|
54
|
+
# Does the player even hit the enemy?
|
55
|
+
# We could use a hit chance stat here, but since we don't have one,
|
56
|
+
# we'll just base it off the player/enemy stength discrepency.
|
57
|
+
ui.enemy_info({:player => player})
|
58
|
+
ui.player_info({:player => player})
|
59
|
+
str_diff = (player.str - enemy.str) * 2
|
60
|
+
hit_chance = rand(1...100) + str_diff + HIT_CHANCE_MODIFIER
|
61
|
+
|
62
|
+
if (hit_chance > 50)
|
63
|
+
# Determine value of the attack
|
64
|
+
attack_value = rand(1...player.str) + ATTACK_VALUE_MODIFIER
|
65
|
+
if attack_value > enemy.health
|
66
|
+
print "You swing and " + "hit".light_yellow + " the " + enemy.name.light_red + " for " + attack_value.to_s.light_white + " damage, killing it!\n"
|
67
|
+
print "You gain " + enemy.lines.to_s.light_white + " lines of code.\n"
|
68
|
+
return ENEMY_KILLED
|
69
|
+
else
|
70
|
+
print "You swing and " + "hit".light_yellow + " the " + enemy.name.light_red + " for " + attack_value.to_s.light_white + " damage!\n"
|
71
|
+
return attack_value
|
72
|
+
end
|
73
|
+
else
|
74
|
+
print "You swing and " + "miss".light_red + " the " + enemy.name + "!\n"
|
75
|
+
return 0
|
76
|
+
end
|
77
|
+
return true
|
78
|
+
end
|
79
|
+
|
80
|
+
def move(args)
|
81
|
+
direction = args[:direction]
|
82
|
+
world = args[:world]
|
83
|
+
ui = args[:ui]
|
84
|
+
story = args[:story]
|
85
|
+
case direction
|
86
|
+
when :up
|
87
|
+
if @y > 1
|
88
|
+
@y -= 1
|
89
|
+
else
|
90
|
+
ui.out_of_bounds
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
when :down
|
94
|
+
if @y < world.get_height
|
95
|
+
@y += 1
|
96
|
+
else
|
97
|
+
ui.out_of_bounds
|
98
|
+
return false
|
99
|
+
end
|
100
|
+
when :left
|
101
|
+
if @x > 1
|
102
|
+
@x -= 1
|
103
|
+
else
|
104
|
+
ui.out_of_bounds
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
when :right
|
108
|
+
if @x < world.get_width
|
109
|
+
@x += 1
|
110
|
+
else
|
111
|
+
ui.out_of_bounds
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
end
|
115
|
+
unless world.check_area({:player => self, :ui => ui, :story => story})
|
116
|
+
return false
|
117
|
+
else
|
118
|
+
return true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
data/lib/enemy.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
#
|
8
|
+
# Enemy Class
|
9
|
+
#
|
10
|
+
|
11
|
+
module LOTS
|
12
|
+
|
13
|
+
ENEMY_CATALOG = [
|
14
|
+
[:name => "Imp", :health => 3, :mana => 0, :str => 3, :lines => 1],
|
15
|
+
[:name => "Rabid Wolf", :health => 4, :mana => 0, :str => 4, :lines => 2],
|
16
|
+
[:name => "Rock Giant", :health => 7, :mana => 3, :str => 5, :lines => 5],
|
17
|
+
[:name => "Toxic Toad", :health => 2, :mana => 6, :str => 1, :lines => 2],
|
18
|
+
[:name => "Rabid Bear", :health => 5, :mana => 0, :str => 4, :lines => 3],
|
19
|
+
[:name => "Angry Fae", :health => 3, :mana => 10, :str => 2, :lines => 5]
|
20
|
+
]
|
21
|
+
|
22
|
+
PLAYER_DEAD = "PLAYER_DEAD"
|
23
|
+
|
24
|
+
class Enemy
|
25
|
+
|
26
|
+
attr_accessor :name
|
27
|
+
attr_accessor :health
|
28
|
+
attr_accessor :mana
|
29
|
+
attr_accessor :str
|
30
|
+
attr_accessor :int
|
31
|
+
attr_accessor :lines
|
32
|
+
|
33
|
+
def initialize(args = nil)
|
34
|
+
# Pick a random enemy
|
35
|
+
selected_enemy = ENEMY_CATALOG.sample[0]
|
36
|
+
@name = selected_enemy[:name]
|
37
|
+
@health = selected_enemy[:health] + rand(0..3)
|
38
|
+
@mana = selected_enemy[:mana] + rand(0..3)
|
39
|
+
@str = selected_enemy[:str]
|
40
|
+
@lines = selected_enemy[:lines]
|
41
|
+
@int = rand(2..6)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Enemy attacks player
|
45
|
+
def attack(args)
|
46
|
+
enemy = self
|
47
|
+
player = args[:player]
|
48
|
+
|
49
|
+
# Does the enemy even hit the player?
|
50
|
+
str_diff = (enemy.str - player.str) * 2
|
51
|
+
hit_chance = rand(1...100) + str_diff
|
52
|
+
|
53
|
+
if (hit_chance > 30)
|
54
|
+
# Determine value of the attack
|
55
|
+
attack_value = rand(1...player.str)
|
56
|
+
print enemy.name.light_red + " hits you for " + attack_value.to_s.light_yellow + " damage!\n"
|
57
|
+
if attack_value > player.health
|
58
|
+
return PLAYER_DEAD
|
59
|
+
else
|
60
|
+
return attack_value
|
61
|
+
end
|
62
|
+
else
|
63
|
+
print enemy.name.light_red + " misses you!\n"
|
64
|
+
end
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/lib/main.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
|
8
|
+
LOTS_VERSION = "1.00"
|
9
|
+
|
10
|
+
# Create a new UI and world
|
11
|
+
ui = LOTS::UI.new
|
12
|
+
world = LOTS::World.new
|
13
|
+
|
14
|
+
# Clear the screen and print welcome message
|
15
|
+
ui.clear
|
16
|
+
ui.welcome
|
17
|
+
|
18
|
+
# Ask name
|
19
|
+
name = ui.ask("What is your name?", /\w/)
|
20
|
+
|
21
|
+
# Create a new player
|
22
|
+
player = LOTS::Character.new({:name => name, :world => world})
|
23
|
+
|
24
|
+
# Show intro story
|
25
|
+
ui.new_line
|
26
|
+
story = LOTS::Story.new
|
27
|
+
ui.draw_frame({:text => story.intro})
|
28
|
+
|
29
|
+
# Show the map for the first time
|
30
|
+
map = world.get_map({:player => player})
|
31
|
+
ui.draw_frame({:text => map})
|
32
|
+
|
33
|
+
# MAIN INPUT LOOP
|
34
|
+
running = 1
|
35
|
+
while running
|
36
|
+
ui.new_line
|
37
|
+
# Get command from user
|
38
|
+
cmd = ui.get_cmd
|
39
|
+
case cmd
|
40
|
+
when "map", "m"
|
41
|
+
map = world.get_map({:player => player})
|
42
|
+
ui.draw_frame({:text => map})
|
43
|
+
when "version", "ver"
|
44
|
+
ui.display_version
|
45
|
+
when "clear", "cls"
|
46
|
+
ui.clear
|
47
|
+
when "name", "whoami"
|
48
|
+
ui.display_name({:player => player})
|
49
|
+
when "location", "loc", "where", "whereami"
|
50
|
+
ui.show_location({:player => player})
|
51
|
+
when "look", "what", "around"
|
52
|
+
world.check_area({:player => player, :ui => ui, :story => story})
|
53
|
+
when "up", "north", "u", "n"
|
54
|
+
unless player.in_combat
|
55
|
+
if !player.move({:direction => :up, :world => world, :ui => ui, :story => story})
|
56
|
+
player.in_combat = 1
|
57
|
+
end
|
58
|
+
else
|
59
|
+
ui.cannot_travel_combat
|
60
|
+
end
|
61
|
+
when "down", "south", "d", "s"
|
62
|
+
unless player.in_combat
|
63
|
+
if !player.move({:direction => :down, :world => world, :ui => ui, :story => story})
|
64
|
+
player.in_combat = 1
|
65
|
+
end
|
66
|
+
else
|
67
|
+
ui.cannot_travel_combat
|
68
|
+
end
|
69
|
+
when "left", "west", "l", "w"
|
70
|
+
unless player.in_combat
|
71
|
+
if !player.move({:direction => :left, :world => world, :ui => ui, :story => story})
|
72
|
+
player.in_combat = 1
|
73
|
+
end
|
74
|
+
else
|
75
|
+
ui.cannot_travel_combat
|
76
|
+
end
|
77
|
+
when "right", "east", "r", "e"
|
78
|
+
unless player.in_combat
|
79
|
+
if !player.move({:direction => :right, :world => world, :ui => ui, :story => story})
|
80
|
+
player.in_combat = 1
|
81
|
+
end
|
82
|
+
else
|
83
|
+
ui.cannot_travel_combat
|
84
|
+
end
|
85
|
+
when "attack", "a"
|
86
|
+
if player.in_combat
|
87
|
+
retval = player.attack({:enemy => player.current_enemy, :ui => ui})
|
88
|
+
if retval == LOTS::ENEMY_KILLED
|
89
|
+
player.lines += player.current_enemy.lines
|
90
|
+
# Remove enemy from map
|
91
|
+
world.the_map[player.y-1][player.x-1] = LOTS::MAP_KEY_GRASS
|
92
|
+
# Take player out of combat
|
93
|
+
player.current_enemy = nil
|
94
|
+
player.in_combat = false
|
95
|
+
end
|
96
|
+
if retval.is_a? Numeric
|
97
|
+
player.current_enemy.health -= retval
|
98
|
+
retval = player.current_enemy.attack({:player => player})
|
99
|
+
if retval.is_a? Numeric
|
100
|
+
player.health -= retval
|
101
|
+
end
|
102
|
+
if retval == LOTS::PLAYER_DEAD
|
103
|
+
player.dead = 1
|
104
|
+
end
|
105
|
+
end
|
106
|
+
else
|
107
|
+
ui.not_in_combat
|
108
|
+
end
|
109
|
+
when "player", "me", "info", "status", "i"
|
110
|
+
ui.player_info({:player => player})
|
111
|
+
when "enemy"
|
112
|
+
if player.in_combat
|
113
|
+
ui.enemy_info({:player => player})
|
114
|
+
else
|
115
|
+
ui.not_in_combat
|
116
|
+
end
|
117
|
+
when "lines", "score"
|
118
|
+
ui.lines({:player => player})
|
119
|
+
when "suicide"
|
120
|
+
player.dead = 1
|
121
|
+
when "help", "h", "?"
|
122
|
+
ui.help
|
123
|
+
when "quit", "exit"
|
124
|
+
ui.quit
|
125
|
+
running = nil
|
126
|
+
else
|
127
|
+
ui.not_found
|
128
|
+
end
|
129
|
+
# Is player in combat but has no enemy? Assign one.
|
130
|
+
if player.in_combat && !player.current_enemy
|
131
|
+
enemy = LOTS::Enemy.new
|
132
|
+
player.current_enemy = enemy
|
133
|
+
ui.enemy_greet({:enemy => enemy})
|
134
|
+
end
|
135
|
+
# Player is dead!
|
136
|
+
if player.dead == 1
|
137
|
+
ui.player_dead({:story => story})
|
138
|
+
exit
|
139
|
+
end
|
140
|
+
# If player has reached Sourcerer
|
141
|
+
if player.x == LOTS::MAP_WIDTH && player.y == 1
|
142
|
+
ui.draw_frame({:text => story.ending})
|
143
|
+
ui.new_line
|
144
|
+
running = false
|
145
|
+
end
|
146
|
+
end
|
data/lib/story.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
#
|
8
|
+
# Story Class
|
9
|
+
#
|
10
|
+
|
11
|
+
module LOTS
|
12
|
+
|
13
|
+
STORY_INTRO = [
|
14
|
+
"Your journey to discover your true potential has been arduous, but a glimmer of hope shines in the darkness.",
|
15
|
+
"A vivid dream stirrs you from your sleep. You are instructed to visit the " + "Sourcerer".light_white + ", the Oracle of the Path.",
|
16
|
+
"You feel certain he will provide the insight you need. Inspired, you carry on.",
|
17
|
+
"",
|
18
|
+
"At the end of the day's travel, you set up camp and find an old " + "map".light_white + " at the base of a tree.",
|
19
|
+
"You gaze at the ancient parchment, studying the path ahead."
|
20
|
+
]
|
21
|
+
|
22
|
+
STORY_AREA_TREE = [
|
23
|
+
"You see a magnificent tree standing tall above you."
|
24
|
+
]
|
25
|
+
|
26
|
+
STORY_AREA_WATER = [
|
27
|
+
"You stand on the banks of a crystal-clear lake."
|
28
|
+
]
|
29
|
+
|
30
|
+
STORY_AREA_MOUNTAIN = [
|
31
|
+
"A majestic snow-topped mountain range graces the horizon."
|
32
|
+
]
|
33
|
+
|
34
|
+
STORY_AREA_ENEMY = [
|
35
|
+
"You encounter an enemy!"
|
36
|
+
]
|
37
|
+
|
38
|
+
STORY_PLAYER_DEAD = [
|
39
|
+
"You have died.",
|
40
|
+
"",
|
41
|
+
"You failed to reach the Sourcerer. Please try again.",
|
42
|
+
]
|
43
|
+
|
44
|
+
STORY_END = [
|
45
|
+
"You've reached the Sourcerer!".light_white,
|
46
|
+
"",
|
47
|
+
"He stares deeply into your eyes. His knowledable, seeking gaze inspires you to recall",
|
48
|
+
"the long journey you have endured. You recall your battles with vicious enemies and are",
|
49
|
+
"proud of the many lines of code you have earned.",
|
50
|
+
"",
|
51
|
+
"The Sourcerer speaks:",
|
52
|
+
"",
|
53
|
+
"To be a better programmer, you must learn from your journey. By taking careful note".light_yellow,
|
54
|
+
"of your successes and analyizing your performance, you can find the information you".light_yellow,
|
55
|
+
"seek to better yourself.".light_yellow,
|
56
|
+
"",
|
57
|
+
"THANK YOU FOR PLAYING!".light_red,
|
58
|
+
"",
|
59
|
+
]
|
60
|
+
|
61
|
+
class Story
|
62
|
+
|
63
|
+
def intro
|
64
|
+
return STORY_INTRO
|
65
|
+
end
|
66
|
+
|
67
|
+
def ending
|
68
|
+
return STORY_END
|
69
|
+
end
|
70
|
+
|
71
|
+
def area_tree
|
72
|
+
return STORY_AREA_TREE
|
73
|
+
end
|
74
|
+
|
75
|
+
def area_water
|
76
|
+
return STORY_AREA_WATER
|
77
|
+
end
|
78
|
+
|
79
|
+
def area_mountain
|
80
|
+
return STORY_AREA_MOUNTAIN
|
81
|
+
end
|
82
|
+
|
83
|
+
def area_enemy
|
84
|
+
return STORY_AREA_ENEMY
|
85
|
+
end
|
86
|
+
|
87
|
+
def player_dead
|
88
|
+
return STORY_PLAYER_DEAD
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/lib/ui.rb
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
#
|
8
|
+
# UI Class
|
9
|
+
#
|
10
|
+
|
11
|
+
module LOTS
|
12
|
+
|
13
|
+
UI_FRAME_HORIZONTAL = "\u2501"
|
14
|
+
UI_FRAME_VERTICAL = "\u2503"
|
15
|
+
UI_FRAME_UPPER_LEFT = "\u250F"
|
16
|
+
UI_FRAME_LOWER_LEFT = "\u2517"
|
17
|
+
UI_FRAME_UPPER_RIGHT = "\u2513"
|
18
|
+
UI_FRAME_LOWER_RIGHT = "\u251B"
|
19
|
+
|
20
|
+
UI_COPYRIGHT = "\u00A9"
|
21
|
+
UI_EMAIL = "\u2709"
|
22
|
+
UI_ARROW = "\u2712"
|
23
|
+
|
24
|
+
class UI
|
25
|
+
|
26
|
+
# Clear the screen
|
27
|
+
def clear
|
28
|
+
print "\e[H\e[2J"
|
29
|
+
end
|
30
|
+
|
31
|
+
def display_map(args)
|
32
|
+
map = args[:map]
|
33
|
+
new_line
|
34
|
+
draw_frame({:text => map})
|
35
|
+
new_line
|
36
|
+
end
|
37
|
+
|
38
|
+
def help
|
39
|
+
new_line
|
40
|
+
print "Valid Commands".light_green
|
41
|
+
new_line(2)
|
42
|
+
print UI_ARROW.light_yellow + " " + "east, e, right, ".light_white + "or " + "r".light_white + " - Move east (right)"
|
43
|
+
new_line
|
44
|
+
print UI_ARROW.light_yellow + " " + "south, s, down, ".light_white + "or " + "d".light_white + " - Move south (down)"
|
45
|
+
new_line
|
46
|
+
print UI_ARROW.light_yellow + " " + "west, w, left, ".light_white + "or " + "l".light_white + " - Move west (left)"
|
47
|
+
new_line
|
48
|
+
print UI_ARROW.light_yellow + " " + "north, n, up, ".light_white + "or " + "u".light_white + " - Move north (up)"
|
49
|
+
new_line
|
50
|
+
print UI_ARROW.light_yellow + " " + "map".light_white + " - Display map"
|
51
|
+
new_line
|
52
|
+
print UI_ARROW.light_yellow + " " + "where".light_white + " - Describe current surroundings"
|
53
|
+
new_line
|
54
|
+
print UI_ARROW.light_yellow + " " + "attack".light_white + " - Attack (only in combat)"
|
55
|
+
new_line
|
56
|
+
print UI_ARROW.light_yellow + " " + "enemy".light_white + " - Display information about your enemy"
|
57
|
+
new_line
|
58
|
+
print UI_ARROW.light_yellow + " " + "lines, score, status, info".light_white + " - Display lines of code (score)"
|
59
|
+
new_line
|
60
|
+
print UI_ARROW.light_yellow + " " + "clear, cls".light_white + " - Clears the screen"
|
61
|
+
new_line
|
62
|
+
print UI_ARROW.light_yellow + " " + "quit, exit".light_white + " - Quits the game"
|
63
|
+
new_line
|
64
|
+
end
|
65
|
+
|
66
|
+
def lines(args)
|
67
|
+
player = args[:player]
|
68
|
+
print "You currently have " + player.lines.to_s.light_white + " lines of code."
|
69
|
+
new_line
|
70
|
+
end
|
71
|
+
|
72
|
+
def enemy_info(args)
|
73
|
+
player = args[:player]
|
74
|
+
enemy = player.current_enemy
|
75
|
+
print enemy.name.light_red + " has " + enemy.str.to_s.light_white + " strength and " + enemy.health.to_s.light_white + " health."
|
76
|
+
new_line
|
77
|
+
end
|
78
|
+
|
79
|
+
def player_info(args)
|
80
|
+
player = args[:player]
|
81
|
+
print "You have " + player.health.to_s.light_white + " health and have " + player.lines.to_s.light_white + " lines of code."
|
82
|
+
new_line
|
83
|
+
end
|
84
|
+
|
85
|
+
# Ask user a question. A regular expression filter can be applied.
|
86
|
+
def ask(question, filter = nil)
|
87
|
+
if filter
|
88
|
+
match = false
|
89
|
+
answer = nil
|
90
|
+
while match == false
|
91
|
+
print UI_ARROW.red + question.light_white + " "
|
92
|
+
answer = gets.chomp
|
93
|
+
if answer.match(filter)
|
94
|
+
return answer
|
95
|
+
else
|
96
|
+
print "Sorry, please try again.".red
|
97
|
+
new_line
|
98
|
+
new_line
|
99
|
+
end
|
100
|
+
end
|
101
|
+
else
|
102
|
+
print "\u2712 ".red + question.light_white + " "
|
103
|
+
return gets.chomp
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Display welcome
|
108
|
+
def welcome
|
109
|
+
text = Array.new
|
110
|
+
text << "Legend of the Sourcerer".light_green
|
111
|
+
text << "Written by Robert W. Oliver II ".white + UI_EMAIL.light_white + " robert@cidergrove.com".white
|
112
|
+
text << "Copyright " + UI_COPYRIGHT + " Sourcerer, All Rights Reserved.".white
|
113
|
+
text << "Licensed under GPLv3.".white
|
114
|
+
draw_frame({:text => text})
|
115
|
+
new_line
|
116
|
+
end
|
117
|
+
|
118
|
+
# Prints a new line. Optinally can print multiple lines.
|
119
|
+
def new_line(times = 1)
|
120
|
+
times.times do
|
121
|
+
print "\n"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Draw text surrounded in a nice frame
|
126
|
+
def draw_frame(args)
|
127
|
+
# Figure out width automatically
|
128
|
+
text = args[:text]
|
129
|
+
width = get_max_size_from_array(text)
|
130
|
+
draw_top_frame(width)
|
131
|
+
text.each do |t|
|
132
|
+
t_size = get_real_size(t)
|
133
|
+
draw_vert_frame_begin
|
134
|
+
if t.kind_of?(Array)
|
135
|
+
t.each do |s|
|
136
|
+
print s
|
137
|
+
end
|
138
|
+
else
|
139
|
+
print t
|
140
|
+
end
|
141
|
+
(width - (t_size + 4)).times do
|
142
|
+
print " "
|
143
|
+
end
|
144
|
+
draw_vert_frame_end
|
145
|
+
new_line
|
146
|
+
end
|
147
|
+
draw_bottom_frame(width)
|
148
|
+
end
|
149
|
+
|
150
|
+
def display_version
|
151
|
+
puts "This is " + "Legend of the Sourcerer".light_red + " Version " + LOTS_VERSION.light_white
|
152
|
+
new_line
|
153
|
+
end
|
154
|
+
|
155
|
+
def not_found
|
156
|
+
print "Command not understood. Please try again.".red
|
157
|
+
new_line
|
158
|
+
end
|
159
|
+
|
160
|
+
def show_location(args)
|
161
|
+
player = args[:player]
|
162
|
+
print "You are currently on row " + player.y.to_s.light_white + ", column " + player.x.to_s.light_white
|
163
|
+
new_line
|
164
|
+
print "Use the " + "map".light_white + " command to see the map."
|
165
|
+
new_line
|
166
|
+
end
|
167
|
+
|
168
|
+
def cannot_travel_combat
|
169
|
+
puts "You are in combat and cannot travel!"
|
170
|
+
end
|
171
|
+
|
172
|
+
def not_in_combat
|
173
|
+
puts "You are not in combat."
|
174
|
+
end
|
175
|
+
|
176
|
+
def quit
|
177
|
+
new_line
|
178
|
+
print "You abandoned your journey.".red
|
179
|
+
new_line(2)
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_cmd
|
183
|
+
print "Type ".white + "help".light_white + " for possible commands.\n"
|
184
|
+
print "\u2712 ".red + "Your command? ".light_white
|
185
|
+
return gets.chomp.downcase
|
186
|
+
end
|
187
|
+
|
188
|
+
def out_of_bounds
|
189
|
+
print "x".red + " Requested move out of bounds."
|
190
|
+
new_line
|
191
|
+
end
|
192
|
+
|
193
|
+
def display_name(args)
|
194
|
+
player = args[:player]
|
195
|
+
print "You are " + player.name.light_white + ". Have you forgotten your own name?"
|
196
|
+
new_line
|
197
|
+
end
|
198
|
+
|
199
|
+
def player_dead(args)
|
200
|
+
story = args[:story]
|
201
|
+
new_line
|
202
|
+
text = story.player_dead
|
203
|
+
draw_frame(:text => text)
|
204
|
+
new_line
|
205
|
+
end
|
206
|
+
|
207
|
+
def enemy_greet(args)
|
208
|
+
enemy = args[:enemy]
|
209
|
+
print enemy.name.light_white + " attacks!"
|
210
|
+
new_line
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def draw_vert_frame_begin
|
216
|
+
print UI_FRAME_VERTICAL.yellow + " "
|
217
|
+
end
|
218
|
+
|
219
|
+
def draw_vert_frame_end
|
220
|
+
print " " + UI_FRAME_VERTICAL.yellow
|
221
|
+
end
|
222
|
+
|
223
|
+
def draw_top_frame(width)
|
224
|
+
print UI_FRAME_UPPER_LEFT.yellow
|
225
|
+
(width - 2).times do
|
226
|
+
print UI_FRAME_HORIZONTAL.yellow
|
227
|
+
end
|
228
|
+
print UI_FRAME_UPPER_RIGHT.yellow
|
229
|
+
new_line
|
230
|
+
end
|
231
|
+
|
232
|
+
def draw_bottom_frame(width)
|
233
|
+
print UI_FRAME_LOWER_LEFT.yellow
|
234
|
+
(width - 2).times do
|
235
|
+
print UI_FRAME_HORIZONTAL.yellow
|
236
|
+
end
|
237
|
+
print UI_FRAME_LOWER_RIGHT.yellow
|
238
|
+
new_line
|
239
|
+
end
|
240
|
+
|
241
|
+
# Returns actual length of text accounting for UTF-8 and ANSI
|
242
|
+
def get_real_size(text)
|
243
|
+
if text.kind_of?(Array)
|
244
|
+
text.size
|
245
|
+
else
|
246
|
+
text.uncolorize.size
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Returns size of longest string in array
|
251
|
+
def get_max_size_from_array(array)
|
252
|
+
max = 0
|
253
|
+
array.each do |s|
|
254
|
+
s_size = get_real_size(s)
|
255
|
+
max = s_size if s_size >= max
|
256
|
+
end
|
257
|
+
max + 4
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
data/lib/world.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
|
8
|
+
module LOTS
|
9
|
+
|
10
|
+
MAP_WIDTH = 64
|
11
|
+
MAP_HEIGHT = 14
|
12
|
+
|
13
|
+
MAP_KEY_TREE = "\u2663"
|
14
|
+
MAP_KEY_WATER = "\u2248"
|
15
|
+
MAP_KEY_GRASS = "\u2588"
|
16
|
+
MAP_KEY_MOUNTAIN = "\u25B2"
|
17
|
+
MAP_KEY_ENEMY = "\u263A"
|
18
|
+
MAP_KEY_SOURCERER = "\u263B"
|
19
|
+
MAP_KEY_PLAYER = "\u1330"
|
20
|
+
|
21
|
+
# Weighted
|
22
|
+
MAP_POSSIBLE_KEYS = [
|
23
|
+
MAP_KEY_TREE,
|
24
|
+
MAP_KEY_TREE,
|
25
|
+
MAP_KEY_TREE,
|
26
|
+
MAP_KEY_TREE,
|
27
|
+
MAP_KEY_WATER,
|
28
|
+
MAP_KEY_GRASS,
|
29
|
+
MAP_KEY_MOUNTAIN,
|
30
|
+
MAP_KEY_ENEMY,
|
31
|
+
MAP_KEY_ENEMY
|
32
|
+
]
|
33
|
+
|
34
|
+
# Make grass more common
|
35
|
+
32.times.each do
|
36
|
+
MAP_POSSIBLE_KEYS << MAP_KEY_GRASS
|
37
|
+
end
|
38
|
+
|
39
|
+
MAP_SOURCERER_X = MAP_WIDTH
|
40
|
+
MAP_SOURCERER_Y = 1
|
41
|
+
|
42
|
+
class World
|
43
|
+
|
44
|
+
attr_reader :the_map
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
# Set initial world map
|
48
|
+
generate_map
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_width
|
52
|
+
MAP_WIDTH
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_height
|
56
|
+
MAP_HEIGHT
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return map data in a display format
|
60
|
+
def get_map(args)
|
61
|
+
player = args[:player]
|
62
|
+
buffer = Array.new
|
63
|
+
x = 1
|
64
|
+
y = 1
|
65
|
+
@the_map.each do |row|
|
66
|
+
tmp_row = Array.new
|
67
|
+
x = 1
|
68
|
+
row.each do |col|
|
69
|
+
placed = 0
|
70
|
+
# Place sourcerer
|
71
|
+
if x == MAP_SOURCERER_X and y == MAP_SOURCERER_Y
|
72
|
+
tmp_row << MAP_KEY_SOURCERER.colorize(:color => :white, :background => :red)
|
73
|
+
placed = 1
|
74
|
+
end
|
75
|
+
# If player is here, display them
|
76
|
+
if x == player.x and y == player.y
|
77
|
+
tmp_row << MAP_KEY_PLAYER.colorize(:color => :red, :background => :white)
|
78
|
+
placed = 1
|
79
|
+
end
|
80
|
+
# If we haven't already placed the character, run through the rest of the options
|
81
|
+
if placed == 0
|
82
|
+
case col
|
83
|
+
when MAP_KEY_TREE
|
84
|
+
tmp_row << col.colorize(:color => :light_green, :background => :green)
|
85
|
+
when MAP_KEY_GRASS
|
86
|
+
tmp_row << col.colorize(:color => :green, :background => :green)
|
87
|
+
when MAP_KEY_WATER
|
88
|
+
tmp_row << col.colorize(:color => :white, :background => :blue)
|
89
|
+
when MAP_KEY_MOUNTAIN
|
90
|
+
tmp_row << col.colorize(:color => :yellow, :background => :green)
|
91
|
+
when MAP_KEY_ENEMY
|
92
|
+
tmp_row << col.colorize(:color => :red, :background => :green)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
x += 1
|
96
|
+
end
|
97
|
+
buffer << tmp_row
|
98
|
+
y += 1
|
99
|
+
end
|
100
|
+
|
101
|
+
return buffer
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check the current area on the map and describe it
|
105
|
+
def check_area(args)
|
106
|
+
player = args[:player]
|
107
|
+
ui = args[:ui]
|
108
|
+
story = args[:story]
|
109
|
+
x = player.x
|
110
|
+
y = player.y
|
111
|
+
current_area = @the_map[y-1][x-1]
|
112
|
+
case current_area
|
113
|
+
when MAP_KEY_TREE
|
114
|
+
ui.draw_frame({:text => story.area_tree})
|
115
|
+
when MAP_KEY_WATER
|
116
|
+
ui.draw_frame({:text => story.area_water})
|
117
|
+
when MAP_KEY_MOUNTAIN
|
118
|
+
ui.draw_frame({:text => story.area_mountain})
|
119
|
+
when MAP_KEY_ENEMY
|
120
|
+
ui.draw_frame({:text => story.area_enemy})
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def new_line
|
129
|
+
print "\n"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Create a random world map
|
133
|
+
def generate_map
|
134
|
+
tmp_map = Array.new
|
135
|
+
|
136
|
+
# Step through MAX_HEIGHT times
|
137
|
+
MAP_HEIGHT.times do
|
138
|
+
tmp_row = Array.new
|
139
|
+
MAP_WIDTH.times do
|
140
|
+
tmp_row << MAP_POSSIBLE_KEYS.sample
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add our assembled row to the map
|
144
|
+
tmp_map << tmp_row
|
145
|
+
tmp_row = nil
|
146
|
+
end
|
147
|
+
@the_map = tmp_map
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
data/lots.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Legend of the Sourcerer
|
4
|
+
# Written by Robert W. Oliver II <robert@cidergrove.com>
|
5
|
+
# Copyright (C) 2018 Sourcerer, All Rights Reserved.
|
6
|
+
# Licensed under GPLv3.
|
7
|
+
|
8
|
+
LOTS_VERSION = "1.00"
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'colorize'
|
12
|
+
rescue LoadError
|
13
|
+
puts
|
14
|
+
puts "Legend of the Sourcerer requires the 'colorize' gem to run."
|
15
|
+
puts
|
16
|
+
puts "Installation Instructions"
|
17
|
+
puts "-------------------------"
|
18
|
+
puts
|
19
|
+
puts "Debian/Ubuntu Linux:"
|
20
|
+
puts " sudo apt install ruby-colorize"
|
21
|
+
puts
|
22
|
+
puts "Other Linux Distros:"
|
23
|
+
puts " gem install colorize"
|
24
|
+
puts
|
25
|
+
puts "Windows:"
|
26
|
+
puts " gem install colorize"
|
27
|
+
puts
|
28
|
+
puts "macOS:"
|
29
|
+
puts " gem install colorize"
|
30
|
+
puts
|
31
|
+
puts
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
# Require libraries
|
36
|
+
load "lib/ui.rb"
|
37
|
+
load "lib/world.rb"
|
38
|
+
load "lib/character.rb"
|
39
|
+
load "lib/story.rb"
|
40
|
+
load "lib/enemy.rb"
|
41
|
+
|
42
|
+
# Start
|
43
|
+
load "lib/main.rb"
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lots
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert W. Oliver II
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-04-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: colorize
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description:
|
28
|
+
email: robert@cidergrove.com
|
29
|
+
executables:
|
30
|
+
- lots
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- bin/lots
|
35
|
+
- lib/character.rb
|
36
|
+
- lib/enemy.rb
|
37
|
+
- lib/main.rb
|
38
|
+
- lib/story.rb
|
39
|
+
- lib/ui.rb
|
40
|
+
- lib/world.rb
|
41
|
+
- lots.rb
|
42
|
+
homepage: https://github.com/rwoliver2/lots
|
43
|
+
licenses:
|
44
|
+
- GPL-3.0
|
45
|
+
metadata: {}
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
requirements: []
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 2.5.2.1
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: Legend of the Sourcerer is an open-source text-based adventure game written
|
66
|
+
in Ruby.
|
67
|
+
test_files: []
|