├── .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 | --------------------------------------------------------------------------------