zarta 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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