1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
|
=begin
APIs listing that haven't been encountered in previous sample apps:
- lambda: A way to define a block and its parameters with special syntax.
For example, the syntax of lambda looks like this:
my_lambda = -> { puts "This is my lambda" }
Reminders:
- args.outputs.labels: An array. The values generate a label.
The parameters are [X, Y, TEXT, SIZE, ALIGNMENT, RED, GREEN, BLUE, ALPHA, FONT STYLE]
- ARRAY#inside_rect?: Returns whether or not the point is inside a rect.
- product: Returns an array of all combinations of elements from all arrays.
- find: Finds all elements of a collection that meet requirements.
- abs: Returns the absolute value.
=end
# This sample app allows the player to move around in the dungeon, which becomes more or less visible
# depending on the player's location, and also has enemies.
class Game
attr_accessor :args, :state, :inputs, :outputs, :grid
# Calls all the methods needed for the game to run properly.
def tick
defaults
render_canvas
render_dungeon
render_player
render_enemies
print_cell_coordinates
calc_canvas
input_move
input_click_map
end
# Sets default values and initializes variables
def defaults
outputs.background_color = [0, 0, 0] # black background
# Initializes empty canvas, dungeon, and enemies collections.
state.canvas ||= []
state.dungeon ||= []
state.enemies ||= []
# If state.area doesn't have value, load_area_one and derive_dungeon_from_area methods are called
if !state.area
load_area_one
derive_dungeon_from_area
# Changing these values will change the position of player
state.x = 7
state.y = 5
# Creates new enemies, sets their values, and adds them to the enemies collection.
state.enemies << state.new_entity(:enemy) do |e| # declares each enemy as new entity
e.x = 13 # position
e.y = 5
e.previous_hp = 3
e.hp = 3
e.max_hp = 3
e.is_dead = false # the enemy is alive
end
update_line_of_sight # updates line of sight by adding newly visible cells
end
end
# Adds elements into the state.area collection
# The dungeon is derived using the coordinates of this collection
def load_area_one
state.area ||= []
state.area << [8, 6]
state.area << [7, 6]
state.area << [7, 7]
state.area << [8, 9]
state.area << [7, 8]
state.area << [7, 9]
state.area << [6, 4]
state.area << [7, 3]
state.area << [7, 4]
state.area << [6, 5]
state.area << [7, 5]
state.area << [8, 5]
state.area << [8, 4]
state.area << [1, 1]
state.area << [0, 1]
state.area << [0, 2]
state.area << [1, 2]
state.area << [2, 2]
state.area << [2, 1]
state.area << [2, 3]
state.area << [1, 3]
state.area << [1, 4]
state.area << [2, 4]
state.area << [2, 5]
state.area << [1, 5]
state.area << [2, 6]
state.area << [3, 6]
state.area << [4, 6]
state.area << [4, 7]
state.area << [4, 8]
state.area << [5, 8]
state.area << [5, 9]
state.area << [6, 9]
state.area << [7, 10]
state.area << [7, 11]
state.area << [7, 12]
state.area << [7, 12]
state.area << [7, 13]
state.area << [8, 13]
state.area << [9, 13]
state.area << [10, 13]
state.area << [11, 13]
state.area << [12, 13]
state.area << [12, 12]
state.area << [8, 12]
state.area << [9, 12]
state.area << [10, 12]
state.area << [11, 12]
state.area << [12, 11]
state.area << [13, 11]
state.area << [13, 10]
state.area << [13, 9]
state.area << [13, 8]
state.area << [13, 7]
state.area << [13, 6]
state.area << [12, 6]
state.area << [14, 6]
state.area << [14, 5]
state.area << [13, 5]
state.area << [12, 5]
state.area << [12, 4]
state.area << [13, 4]
state.area << [14, 4]
state.area << [1, 6]
state.area << [6, 6]
end
# Starts with an empty dungeon collection, and adds dungeon cells into it.
def derive_dungeon_from_area
state.dungeon = [] # starts as empty collection
state.area.each do |a| # for each element of the area collection
state.dungeon << state.new_entity(:dungeon_cell) do |d| # declares each dungeon cell as new entity
d.x = a.x # dungeon cell position using coordinates from area
d.y = a.y
d.is_visible = false # cell is not visible
d.alpha = 0 # not transparent at all
d.border = [left_margin + a.x * grid_size,
bottom_margin + a.y * grid_size,
grid_size,
grid_size,
*blue,
255] # sets border definition for dungeon cell
d # returns dungeon cell
end
end
end
def left_margin
40 # sets left margin
end
def bottom_margin
60 # sets bottom margin
end
def grid_size
40 # sets size of grid square
end
# Updates the line of sight by calling the thick_line_of_sight method and
# adding dungeon cells to the newly_visible collection
def update_line_of_sight
variations = [-1, 0, 1]
# creates collection of newly visible dungeon cells
newly_visible = variations.product(variations).flat_map do |rise, run| # combo of all elements
thick_line_of_sight state.x, state.y, rise, run, 15, # calls thick_line_of_sight method
lambda { |x, y| dungeon_cell_exists? x, y } # checks whether or not cell exists
end.uniq# removes duplicates
state.dungeon.each do |d| # perform action on each element of dungeons collection
d.is_visible = newly_visible.find { |v| v.x == d.x && v.y == d.y } # finds match inside newly_visible collection
end
end
#Returns a boolean value
def dungeon_cell_exists? x, y
# Finds cell coordinates inside dungeon collection to determine if dungeon cell exists
state.dungeon.find { |d| d.x == x && d.y == y }
end
# Calls line_of_sight method to add elements to result collection
def thick_line_of_sight start_x, start_y, rise, run, distance, cell_exists_lambda
result = []
result += line_of_sight start_x, start_y, rise, run, distance, cell_exists_lambda
result += line_of_sight start_x - 1, start_y, rise, run, distance, cell_exists_lambda # one left
result += line_of_sight start_x + 1, start_y, rise, run, distance, cell_exists_lambda # one right
result
end
# Adds points to the result collection to create the player's line of sight
def line_of_sight start_x, start_y, rise, run, distance, cell_exists_lambda
result = [] # starts as empty collection
points = points_on_line start_x, start_y, rise, run, distance # calls points_on_line method
points.each do |p| # for each point in collection
if cell_exists_lambda.call(p.x, p.y) # if the cell exists
result << p # add it to result collection
else # if cell does not exist
return result # return result collection as it is
end
end
result # return result collection
end
# Finds the coordinates of the points on the line by performing calculations
def points_on_line start_x, start_y, rise, run, distance
distance.times.map do |i| # perform an action
[start_x + run * i, start_y + rise * i] # definition of point
end
end
def render_canvas
return
outputs.borders << state.canvas.map do |c| # on each element of canvas collection
c.border # outputs border
end
end
# Outputs the dungeon cells.
def render_dungeon
outputs.solids << [0, 0, grid.w, grid.h] # outputs black background for grid
# Sets the alpha value (opacity) for each dungeon cell and calls the cell_border method.
outputs.borders << state.dungeon.map do |d| # for each element in dungeon collection
d.alpha += if d.is_visible # if cell is visible
255.fdiv(30) # increment opacity (transparency)
else # if cell is not visible
255.fdiv(600) * -1 # decrease opacity
end
d.alpha = d.alpha.cap_min_max(0, 255)
cell_border d.x, d.y, [*blue, d.alpha] # sets blue border using alpha value
end.reject_nil
end
# Sets definition of a cell border using the parameters
def cell_border x, y, color = nil
[left_margin + x * grid_size,
bottom_margin + y * grid_size,
grid_size,
grid_size,
*color]
end
# Sets the values for the player and outputs it as a label
def render_player
outputs.labels << [grid_x(state.x) + 20, # positions "@" text in center of grid square
grid_y(state.y) + 35,
"@", # player is represented by a white "@" character
1, 1, *white]
end
def grid_x x
left_margin + x * grid_size # positions horizontally on grid
end
def grid_y y
bottom_margin + y * grid_size # positions vertically on grid
end
# Outputs enemies onto the screen.
def render_enemies
state.enemies.map do |e| # for each enemy in the collection
alpha = 255 # set opacity (full transparency)
# Outputs an enemy using a label.
outputs.labels << [
left_margin + 20 + e.x * grid_size, # positions enemy's "r" text in center of grid square
bottom_margin + 35 + e.y * grid_size,
"r", # enemy's text
1, 1, *white, alpha]
# Creates a red border around an enemy.
outputs.borders << [grid_x(e.x), grid_y(e.y), grid_size, grid_size, *red]
end
end
#White labels are output for the cell coordinates of each element in the dungeon collection.
def print_cell_coordinates
return unless state.debug
state.dungeon.each do |d|
outputs.labels << [grid_x(d.x) + 2,
grid_y(d.y) - 2,
"#{d.x},#{d.y}",
-2, 0, *white]
end
end
# Adds new elements into the canvas collection and sets their values.
def calc_canvas
return if state.canvas.length > 0 # return if canvas collection has at least one element
15.times do |x| # 15 times perform an action
15.times do |y|
state.canvas << state.new_entity(:canvas) do |c| # declare canvas element as new entity
c.x = x # set position
c.y = y
c.border = [left_margin + x * grid_size,
bottom_margin + y * grid_size,
grid_size,
grid_size,
*white, 30] # sets border definition
end
end
end
end
# Updates x and y values of the player, and updates player's line of sight
def input_move
x, y, x_diff, y_diff = input_target_cell
return unless dungeon_cell_exists? x, y # player can't move there if a dungeon cell doesn't exist in that location
return if enemy_at x, y # player can't move there if there is an enemy in that location
state.x += x_diff # increments x by x_diff (so player moves left or right)
state.y += y_diff # same with y and y_diff ( so player moves up or down)
update_line_of_sight # updates visible cells
end
def enemy_at x, y
# Finds if coordinates exist in enemies collection and enemy is not dead
state.enemies.find { |e| e.x == x && e.y == y && !e.is_dead }
end
#M oves the user based on their keyboard input and sets values for target cell
def input_target_cell
if inputs.keyboard.key_down.up # if "up" key is in "down" state
[state.x, state.y + 1, 0, 1] # user moves up
elsif inputs.keyboard.key_down.down # if "down" key is pressed
[state.x, state.y - 1, 0, -1] # user moves down
elsif inputs.keyboard.key_down.left # if "left" key is pressed
[state.x - 1, state.y, -1, 0] # user moves left
elsif inputs.keyboard.key_down.right # if "right" key is pressed
[state.x + 1, state.y, 1, 0] # user moves right
else
nil # otherwise, empty
end
end
# Goes through the canvas collection to find if the mouse was clicked inside of the borders of an element.
def input_click_map
return unless inputs.mouse.click # return unless the mouse is clicked
canvas_entry = state.canvas.find do |c| # find element from canvas collection that meets requirements
inputs.mouse.click.inside_rect? c.border # find border that mouse was clicked inside of
end
puts canvas_entry # prints canvas_entry value
end
# Sets the definition of a label using the parameters.
def label text, x, y, color = nil
color ||= white # color is initialized to white
[x, y, text, 1, 1, *color] # sets label definition
end
def green
[60, 200, 100] # sets color saturation to shade of green
end
def blue
[50, 50, 210] # sets color saturation to shade of blue
end
def white
[255, 255, 255] # sets color saturation to white
end
def red
[230, 80, 80] # sets color saturation to shade of red
end
def orange
[255, 80, 60] # sets color saturation to shade of orange
end
def pink
[255, 0, 200] # sets color saturation to shade of pink
end
def gray
[75, 75, 75] # sets color saturation to shade of gray
end
# Recolors the border using the parameters.
def recolor_border border, r, g, b
border[4] = r
border[5] = g
border[6] = b
border
end
# Returns a boolean value.
def visible? cell
# finds cell's coordinates inside visible_cells collections to determine if cell is visible
state.visible_cells.find { |c| c.x == cell.x && c.y == cell.y}
end
# Exports dungeon by printing dungeon cell coordinates
def export_dungeon
state.dungeon.each do |d| # on each element of dungeon collection
puts "state.dungeon << [#{d.x}, #{d.y}]" # prints cell coordinates
end
end
def distance_to_cell cell
distance_to state.x, cell.x, state.y, cell.y # calls distance_to method
end
def distance_to from_x, x, from_y, y
(from_x - x).abs + (from_y - y).abs # finds distance between two cells using coordinates
end
end
$game = Game.new
def tick args
$game.args = args
$game.state = args.state
$game.inputs = args.inputs
$game.outputs = args.outputs
$game.grid = args.grid
$game.tick
end
|