zarta 0.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.
data/lib/zarta/main.rb ADDED
@@ -0,0 +1,214 @@
1
+ require 'tty'
2
+ require 'terminal-table'
3
+ require 'pastel'
4
+ require 'artii'
5
+
6
+ BOSS_RARITY = 5
7
+ ENEMY_CHANCE_BASE = 40
8
+ ENEMY_CHANCE_MOD = 5
9
+ ENEMY_LEVEL_MAX_MOD = 3
10
+ ENEMY_LEVEL_MIN_MOD = 2
11
+ ENEMY_MAX_HEALTH_MOD = 2
12
+ FLEE_CHANCE = 0.5
13
+ HEALTH_INCREASE = 3
14
+ MAX_NEXT_ROOMS = 4
15
+ MIN_NEXT_ROOMS = 2
16
+ NEXT_LEVEL_XP = 10
17
+ SPAWN_CHANCE_MOD = 1.2
18
+ STAIRS_CHANCE = 5
19
+ WEAPON_CHANCE_BASE = 5
20
+ WEAPON_CHANCE_MOD = 5
21
+ WEAPON_MAX_MOD = 2
22
+ WEAPON_MIN_MOD = 0
23
+
24
+ # The catch-all module for ZARTA
25
+ module Zarta
26
+ # Runs the game
27
+ class Engine
28
+ def initialize
29
+ @dungeon = Zarta::Dungeon.new
30
+ end
31
+
32
+ # Main game loop
33
+ def play
34
+ Zarta::StartScreen.new(@dungeon)
35
+ end
36
+ end
37
+
38
+ class StartScreen
39
+ def initialize(dungeon)
40
+ @dungeon = dungeon
41
+ @prompt = TTY::Prompt.new
42
+ @pastel = Pastel.new
43
+ show_splash
44
+ end
45
+
46
+ def show_splash
47
+ loop do
48
+ system 'clear'
49
+ a = Artii::Base.new font: 'isometric3'
50
+ title = a.asciify('Zarta')
51
+ puts @pastel.red.bold(title)
52
+ splash_option = @prompt.select('') do |menu|
53
+ menu.choice 'New Game'
54
+ menu.choice 'Load Game'
55
+ menu.choice 'Leaderboards'
56
+ menu.choice 'Quit'
57
+ end
58
+
59
+ new_game if splash_option == 'New Game'
60
+ load_game if splash_option == 'Load Game'
61
+ leaderboard if splash_option == 'Leaderboards'
62
+ exit[0] if splash_option == 'Quit' && @prompt.yes?('Are you sure?')
63
+ end
64
+ end
65
+
66
+ def new_game
67
+ player_name = @prompt.ask("What's your name, adventurer?", required: true)
68
+ @dungeon.player.name = player_name
69
+ Zarta::Screen.new(@dungeon)
70
+ end
71
+
72
+ def load_game
73
+ puts 'Load Game not implemented yet...'
74
+ gets
75
+ end
76
+
77
+ def leaderboard
78
+ puts 'Leaderboard not implemented yet...'
79
+ gets
80
+ end
81
+ end
82
+
83
+ # Writes the current game screen. No idea why I didn't just do all this stuff
84
+ # in the Engine class. Look how empty that thing is. You probably didn't even
85
+ # see it up there.
86
+ class Screen
87
+ def initialize(dungeon)
88
+ @dungeon = dungeon
89
+ @player = @dungeon.player
90
+ @room = @dungeon.room
91
+ @prompt = TTY::Prompt.new
92
+
93
+ refresh
94
+ end
95
+
96
+ # When the player moves into a new room, this function checks if anything
97
+ # has spawned in it and calls the appropriate functions. Looking at it now,
98
+ # it seems that all of this fucnctionality could be off-handed to the room
99
+ # class. I'm just creating HUD objects in any function where I need to
100
+ # refresh it anyway. Feels redundant.
101
+ def refresh
102
+ loop do
103
+ Zarta::HUD.new(@dungeon)
104
+ @player.handle_enemy if @dungeon.room.enemy.is_a?(Zarta::Enemy)
105
+ @player.handle_weapon if @dungeon.room.weapon.is_a?(Zarta::Weapon)
106
+ handle_stairs if @dungeon.room.stairs
107
+
108
+ @dungeon.room = next_rooms_prompt
109
+ end
110
+ end
111
+
112
+ # Another function that could be handed off to the Room class. Like that
113
+ # whale of a beast needs more methods. Maybe it would make more sense to
114
+ # move some functionality into the Engine class.
115
+ def handle_stairs
116
+ Zarta::HUD.new(@dungeon)
117
+ puts 'You see stairs leading down here.'
118
+ return unless @prompt.yes?('Go down?')
119
+ @dungeon.level += 1
120
+ @dungeon.room = Zarta::Room.new(@dungeon)
121
+ refresh
122
+ end
123
+
124
+ # Not one to be left out, this guy could also piss off somewhere else. Seems
125
+ # like this class is doomed. When I can get round to it, that is. So maybe
126
+ # it will be ok...
127
+ def next_rooms_prompt
128
+ next_rooms_options = []
129
+ @dungeon.room.new_rooms
130
+ @dungeon.room.next_rooms.each do |room|
131
+ next_rooms_options << room.description
132
+ end
133
+
134
+ Zarta::HUD.new(@dungeon)
135
+ puts 'You see these rooms ahead of you,'
136
+ next_room_choice = @prompt.select('Choose one:', next_rooms_options)
137
+
138
+ @dungeon.room.next_rooms.each do |room|
139
+ return room if next_room_choice == room.description
140
+ end
141
+ end
142
+ end
143
+
144
+ # Resfreshes the top of the screen when I need it to. I spawn instances of
145
+ # this guy a lot.
146
+ class HUD
147
+ def initialize(dungeon)
148
+ @dungeon = dungeon
149
+ @player = @dungeon.player
150
+ @pastel = Pastel.new
151
+
152
+ hud_table
153
+ end
154
+
155
+ # Lays out the top of the screen in a nice way for me. It would be great if
156
+ # I could get the whole game to display in a bordered table, but alas, the
157
+ # gem I'm using can't do it, and I appear to lack the technichal knowhow.
158
+ def hud_table
159
+ table = Terminal::Table.new
160
+ table.title = @pastel.bright_red(@dungeon.name)
161
+ table.style = { width: 80, padding_left: 3, border_x: '=' }
162
+ table.rows = build_table_rows
163
+ system 'clear'
164
+ puts table
165
+ puts room_description
166
+ end
167
+
168
+ # Pulled this and the functions it calls below out here to make it a little
169
+ # clearer and to be able to make changes without breaking my brain.
170
+ def build_table_rows
171
+ t = []
172
+ t << [@player.name, "LVL: #{@player.level}"]
173
+ t << [display_health, display_xp]
174
+ t << [display_weapon, display_dungeon_level]
175
+ end
176
+
177
+ def display_health
178
+ current_health = @player.health[0]
179
+ max_health = @player.health[1]
180
+ hud_health = "HP: #{current_health}/#{max_health}"
181
+
182
+ return @pastel.red(hud_health) if current_health < max_health / 2
183
+ @pastel.green(hud_health)
184
+ end
185
+
186
+ def display_xp
187
+ "EXP: #{@player.xp}/#{@player.level * 10}"
188
+ end
189
+
190
+ def display_weapon
191
+ "Weapon: #{@player.weapon.name} (#{@player.weapon.damage})"
192
+ end
193
+
194
+ def display_dungeon_level
195
+ "Dungeon Level: #{@dungeon.level}/#{@dungeon.max_level}"
196
+ end
197
+
198
+ # Pretty straightforward but I have plans for the room description
199
+ # generation to be far more interesting. So check back here sometime in the
200
+ # next millenium.
201
+ def room_description
202
+ word_start = beginning(@dungeon.room.description)
203
+ puts "You are in #{word_start} #{@dungeon.room.description} room."
204
+ end
205
+
206
+ # Checks if a word is an 'an' word or an 'a' word.
207
+ # http://stackoverflow.com/a/18463759/1576860
208
+ def beginning(word)
209
+ # That weird looking thing below is interpolated as an array, so it would
210
+ # come out looking like: [a, e, i, o ,u]
211
+ %w(a e i o u).include?(word[0]) ? 'an' : 'a'
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,188 @@
1
+ # The catch-all module for Zarta
2
+ module Zarta
3
+ # The Bloated Beast. The Mother of Methods. The Function Fornicator.
4
+ class Player
5
+ # The player's name
6
+ attr_accessor :name
7
+
8
+ # The player's health in an array
9
+ # [current health / max health]
10
+ attr_accessor :health
11
+
12
+ # The player's current experience level
13
+ attr_accessor :level
14
+
15
+ # The player's accumulated experience points
16
+ attr_accessor :xp
17
+
18
+ # The player's current weapon
19
+ attr_accessor :weapon
20
+
21
+ def initialize(dungeon)
22
+ @name = 'Testy McTestface'
23
+ @health = [100, 100]
24
+ @level = 1
25
+ @xp = 0
26
+ @dungeon = dungeon
27
+ @weapon = Zarta::Weapon.new(@dungeon)
28
+ @prompt = TTY::Prompt.new
29
+ @pastel = Pastel.new
30
+ end
31
+
32
+ def handle_weapon
33
+ @room_weapon = @dungeon.room.weapon
34
+ @room_weapon_c = @pastel.cyan.bold(@room_weapon.name)
35
+ @weapon_handled = false
36
+ Zarta::HUD.new(@dungeon)
37
+ puts "You see a #{@room_weapon_c} in this room."
38
+
39
+ # Repeat until they pick it up or leave it
40
+ prompt_weapon until @weapon_handled
41
+ end
42
+
43
+ def prompt_weapon
44
+ weapon_choice = @prompt.select('What do you want to do?') do |menu|
45
+ menu.choice 'Pick it up'
46
+ menu.choice 'Look at it'
47
+ menu.choice 'Leave it'
48
+ end
49
+ pickup_weapon if weapon_choice == 'Pick it up'
50
+ @room_weapon.inspect_weapon if weapon_choice == 'Look at it'
51
+ leave_weapon if weapon_choice == 'Leave it'
52
+ end
53
+
54
+ def pickup_weapon
55
+ puts 'Your current weapon will be replaced.'
56
+ return unless @prompt.yes?('Are you sure?')
57
+ @weapon = @room_weapon
58
+ @weapon_handled = true
59
+ Zarta::HUD.new(@dungeon)
60
+ end
61
+
62
+ def leave_weapon
63
+ return unless @prompt.yes?('Are you sure?')
64
+ @weapon_handled = true
65
+ end
66
+
67
+ def handle_enemy
68
+ @enemy = @dungeon.room.enemy
69
+ @enemy_c = @pastel.magenta.bold(@enemy.name)
70
+ puts "There is a #{@enemy_c} in here!"
71
+
72
+ # Loop until the enemy is dealt with.
73
+ prompt_enemy until @enemy.dealt_with
74
+ end
75
+
76
+ def prompt_enemy
77
+ enemy_choice = @prompt.select('What do you want to do?') do |menu|
78
+ menu.choice 'Fight!'
79
+ menu.choice 'Flee!'
80
+ menu.choice 'Look!'
81
+ end
82
+
83
+ fight if enemy_choice == 'Fight!'
84
+ flee if enemy_choice == 'Flee!'
85
+ @enemy.inspect if enemy_choice == 'Look!'
86
+ end
87
+
88
+ def fight
89
+ Zarta::HUD.new(@dungeon)
90
+ player_turn
91
+ enemy_killed && return if @enemy.dealt_with
92
+ @enemy.enemy_turn
93
+ end
94
+
95
+ def player_turn
96
+ Zarta::HUD.new(@dungeon)
97
+ hit = player_damage
98
+ @enemy.take_damage(hit)
99
+ puts "You attack the #{@enemy_c}!"
100
+ puts "You hit for #{@pastel.bright_yellow.bold(hit)} damage."
101
+ gets
102
+ end
103
+
104
+ # Another Turing-level algorithm for determining how hard the player hits.
105
+ # It would be cool to generate critical hits in here.
106
+ def player_damage
107
+ base_damage = @weapon.damage + rand(@weapon.damage)
108
+ hit = base_damage + rand(@level) + @level
109
+ hit.round
110
+ end
111
+
112
+ def flee
113
+ return unless @prompt.yes?('Are you sure?')
114
+ puts "You try to get away from the #{@enemy_c}"
115
+ flee_hit
116
+ @enemy.dealt_with = true
117
+ gets
118
+ end
119
+
120
+ def flee_hit
121
+ base = @dungeon.level + @level
122
+ level_difference = @enemy.level - @level
123
+ flee_chance = (rand(base) + level_difference) * FLEE_CHANCE
124
+ flee_damage && return if rand(base) <= flee_chance
125
+ puts 'You are successful!'
126
+ @enemy.dealt_with = true
127
+ end
128
+
129
+ def flee_damage
130
+ enemy_hit = @enemy.enemy_damage
131
+ take_damage(enemy_hit)
132
+ puts "The #{@enemy_c} hits you as you try to flee!"
133
+ puts "You take #{@pastel.red.bold(enemy_hit)} hits you as you flee!"
134
+ end
135
+
136
+ def take_damage(damage)
137
+ @health[0] -= damage
138
+ death if @health[0] <= 0
139
+ end
140
+
141
+ def death
142
+ puts 'You have been killed!'
143
+ puts @pastel.bright_red.bold('GAME OVER')
144
+ gets
145
+ exit[0]
146
+ end
147
+
148
+ def enemy_killed
149
+ xp_gained = gain_xp
150
+ puts "You have slain the #{@enemy_c}!"
151
+ puts "You gain #{@pastel.bright_blue.bold(xp_gained)} Experience."
152
+ level_up if @xp >= @level * NEXT_LEVEL_XP
153
+ @enemy.dealt_with = true
154
+ player_wins if @enemy.name == 'BOSS!'
155
+ gets
156
+ end
157
+
158
+ # I've never made this method call, so I'll just assume that it works...
159
+ def player_wins
160
+ puts 'Congrats, you won the game!'
161
+ gets
162
+ exit[0]
163
+ end
164
+
165
+ def gain_xp
166
+ xp_gained = @enemy.level + @level + @dungeon.level
167
+ @xp += xp_gained
168
+ xp_gained
169
+ end
170
+
171
+ def level_up
172
+ @xp -= @level * NEXT_LEVEL_XP
173
+ @level += 1
174
+ puts @pastel.bright_blue.bold('You gain a level!')
175
+ puts "You are now level #{@pastel.bright_blue.bold(@level)}"
176
+ health_increase
177
+ end
178
+
179
+ def health_increase
180
+ base_increase = (@level - 1) + @dungeon.level
181
+ increase = rand(base_increase..base_increase * HEALTH_INCREASE)
182
+ @health[1] += increase
183
+ @health[0] = @health[1]
184
+ puts 'Your health is replenished!'
185
+ puts "You gain #{@pastel.bright_green.bold(increase)} to max health."
186
+ end
187
+ end
188
+ end