├── .gitignore
├── README.md
├── bin
└── lots
├── lib
├── character.rb
├── enemy.rb
├── main.rb
├── story.rb
├── ui.rb
└── world.rb
├── lots.gemspec
└── lots.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Legend of the Sourcerer
2 |
3 | Legend of the Sourcerer is an open-source text-based adventure game written in Ruby.
4 |
5 | https://blog.sourcerer.io/legend-of-the-sourcerer-a-text-based-adventure-game-in-ruby-9220b385ca1e
6 |
7 | # Credits
8 | Written by Robert W. Oliver II
9 |
10 | * Email: robert@cidergrove.com
11 | * Sourcerer: https://sourcerer.io/rwoliver2
12 | * Twitter: @rwoliver2
13 |
14 | # Copyright
15 |
16 | Copyright (C) 2018 Sourcerer, All Rights Reserved.
17 |
18 | # Installing LOTS
19 |
20 | ```
21 | gem install lots
22 | ```
23 |
24 | # Running the Game
25 |
26 | Just type "lots" on the console.
27 |
28 | # Platform Specific Notes
29 |
30 | ## Windows
31 |
32 | I highly recommend the cmdr console prompt with the Consolas font.
33 |
34 | ## Mac
35 |
36 | For best results, use the "Professional" terminal profile.
37 |
38 | ## Linux
39 |
40 | The default terminal for KDE or XFCE works great with LOTS. I tested it with those, but others should work well.
41 |
42 | # License
43 |
44 | ```
45 | This program is free software: you can redistribute it and/or modify
46 | it under the terms of the GNU General Public License as published by
47 | the Free Software Foundation, either version 3 of the License, or
48 | (at your option) any later version.
49 |
50 | This program is distributed in the hope that it will be useful,
51 | but WITHOUT ANY WARRANTY; without even the implied warranty of
52 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
53 | GNU General Public License for more details.
54 |
55 | You should have received a copy of the GNU General Public License
56 | along with this program. If not, see .
57 | ```
58 |
--------------------------------------------------------------------------------
/bin/lots:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | Dir.chdir(File.expand_path(File.dirname(__FILE__).to_s + "/.."))
4 | load "lots.rb"
5 |
--------------------------------------------------------------------------------
/lib/character.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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
125 |
--------------------------------------------------------------------------------
/lib/enemy.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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
72 |
--------------------------------------------------------------------------------
/lib/main.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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 "~"
41 | binding.pry
42 | when "map", "m"
43 | map = world.get_map({:player => player})
44 | ui.draw_frame({:text => map})
45 | when "version", "ver"
46 | ui.display_version
47 | when "clear", "cls"
48 | ui.clear
49 | when "name", "whoami"
50 | ui.display_name({:player => player})
51 | when "location", "loc", "where", "whereami"
52 | ui.show_location({:player => player})
53 | when "look", "what", "around"
54 | world.check_area({:player => player, :ui => ui, :story => story})
55 | when "up", "north", "u", "n"
56 | unless player.in_combat
57 | if !player.move({:direction => :up, :world => world, :ui => ui, :story => story})
58 | player.in_combat = 1
59 | end
60 | else
61 | ui.cannot_travel_combat
62 | end
63 | when "down", "south", "d", "s"
64 | unless player.in_combat
65 | if !player.move({:direction => :down, :world => world, :ui => ui, :story => story})
66 | player.in_combat = 1
67 | end
68 | else
69 | ui.cannot_travel_combat
70 | end
71 | when "left", "west", "l", "w"
72 | unless player.in_combat
73 | if !player.move({:direction => :left, :world => world, :ui => ui, :story => story})
74 | player.in_combat = 1
75 | end
76 | else
77 | ui.cannot_travel_combat
78 | end
79 | when "right", "east", "r", "e"
80 | unless player.in_combat
81 | if !player.move({:direction => :right, :world => world, :ui => ui, :story => story})
82 | player.in_combat = 1
83 | end
84 | else
85 | ui.cannot_travel_combat
86 | end
87 | when "attack", "a"
88 | if player.in_combat
89 | retval = player.attack({:enemy => player.current_enemy, :ui => ui})
90 | if retval == LOTS::ENEMY_KILLED
91 | player.lines += player.current_enemy.lines
92 | # Remove enemy from map
93 | world.the_map[player.y-1][player.x-1] = LOTS::MAP_KEY_GRASS
94 | # Take player out of combat
95 | player.current_enemy = nil
96 | player.in_combat = false
97 | end
98 | if retval.is_a? Numeric
99 | player.current_enemy.health -= retval
100 | retval = player.current_enemy.attack({:player => player})
101 | if retval.is_a? Numeric
102 | player.health -= retval
103 | end
104 | if retval == LOTS::PLAYER_DEAD
105 | player.dead = 1
106 | end
107 | end
108 | else
109 | ui.not_in_combat
110 | end
111 | when "player", "me", "info", "status", "i"
112 | ui.player_info({:player => player})
113 | when "enemy"
114 | if player.in_combat
115 | ui.enemy_info({:player => player})
116 | else
117 | ui.not_in_combat
118 | end
119 | when "lines", "score"
120 | ui.lines({:player => player})
121 | when "suicide"
122 | player.dead = 1
123 | when "help", "h", "?"
124 | ui.help
125 | when "quit", "exit"
126 | ui.quit
127 | running = nil
128 | else
129 | ui.not_found
130 | end
131 | # Is player in combat but has no enemy? Assign one.
132 | if player.in_combat && !player.current_enemy
133 | enemy = LOTS::Enemy.new
134 | player.current_enemy = enemy
135 | ui.enemy_greet({:enemy => enemy})
136 | end
137 | # Player is dead!
138 | if player.dead == 1
139 | ui.player_dead({:story => story})
140 | exit
141 | end
142 | # If player has reached Sourcerer
143 | if player.x == LOTS::MAP_WIDTH && player.y == 1
144 | ui.draw_frame({:text => story.ending})
145 | ui.new_line
146 | running = false
147 | end
148 | end
149 |
--------------------------------------------------------------------------------
/lib/story.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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
94 |
--------------------------------------------------------------------------------
/lib/ui.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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
263 |
--------------------------------------------------------------------------------
/lib/world.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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
153 |
--------------------------------------------------------------------------------
/lots.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.name = 'lots'
3 | s.version = '1.0.1'
4 | s.date = '2018-04-01'
5 | s.summary = "Legend of the Sourcerer is an open-source text-based adventure game written in Ruby."
6 | s.authors = ["Robert W. Oliver II"]
7 | s.email = "robert@cidergrove.com"
8 | s.files = ["lots.rb", "lib/main.rb", "lib/character.rb", "lib/world.rb", "lib/enemy.rb", "lib/ui.rb", "lib/story.rb"]
9 | s.add_runtime_dependency 'colorize', '~> 0'
10 | s.executables << 'lots'
11 | s.homepage = "https://github.com/sourcerer-io/lots"
12 | s.license = "GPL-3.0"
13 | end
14 |
--------------------------------------------------------------------------------
/lots.rb:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | #
3 | # Legend of the Sourcerer
4 | # Written by Robert W. Oliver II
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 'pry'
36 |
37 | # Require libraries
38 | load "lib/ui.rb"
39 | load "lib/world.rb"
40 | load "lib/character.rb"
41 | load "lib/story.rb"
42 | load "lib/enemy.rb"
43 |
44 | # Start
45 | load "lib/main.rb"
46 |
--------------------------------------------------------------------------------