rich_engine 0.0.0
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.
- checksums.yaml +7 -0
- data/.github/workflows/tests-and-linter.yml +28 -0
- data/.gitignore +1 -0
- data/.standard.yml +1 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +196 -0
- data/Guardfile +27 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +16 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/examples/background.rb +138 -0
- data/examples/grains_of_sand.rb +149 -0
- data/examples/noise.rb +29 -0
- data/examples/timer.rb +22 -0
- data/lib/rich_engine/canvas.rb +130 -0
- data/lib/rich_engine/chance.rb +19 -0
- data/lib/rich_engine/cooldown.rb +27 -0
- data/lib/rich_engine/enum/mixin.rb +21 -0
- data/lib/rich_engine/enum/value.rb +49 -0
- data/lib/rich_engine/enum.rb +29 -0
- data/lib/rich_engine/game.rb +105 -0
- data/lib/rich_engine/io.rb +110 -0
- data/lib/rich_engine/matrix.rb +73 -0
- data/lib/rich_engine/string_colors.rb +183 -0
- data/lib/rich_engine/terminal/cursor.rb +21 -0
- data/lib/rich_engine/terminal.rb +29 -0
- data/lib/rich_engine/timer/every.rb +40 -0
- data/lib/rich_engine/timer.rb +25 -0
- data/lib/rich_engine/ui/textures.rb +47 -0
- data/lib/rich_engine/version.rb +5 -0
- data/lib/rich_engine.rb +17 -0
- data/mise.toml +2 -0
- data/rich_engine.gemspec +28 -0
- metadata +74 -0
data/examples/timer.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rich_engine"
|
|
4
|
+
|
|
5
|
+
class TimerExample < RichEngine::Game
|
|
6
|
+
def on_create
|
|
7
|
+
@timer = RichEngine::Timer.new
|
|
8
|
+
@canvas.bg = "·"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def on_update(elapsed_time, key)
|
|
12
|
+
quit! if key == :q
|
|
13
|
+
|
|
14
|
+
@timer.update(elapsed_time)
|
|
15
|
+
@canvas.clear
|
|
16
|
+
@canvas.write_string("Elapsed: #{@timer.get.round(1)}s", x: 1, y: 1)
|
|
17
|
+
|
|
18
|
+
quit! if @timer.get > 10
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
TimerExample.play
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "string_colors"
|
|
4
|
+
|
|
5
|
+
module RichEngine
|
|
6
|
+
class Canvas
|
|
7
|
+
using StringColors
|
|
8
|
+
|
|
9
|
+
attr_reader :canvas, :bg
|
|
10
|
+
|
|
11
|
+
def initialize(width, height, bg: " ")
|
|
12
|
+
@width = width
|
|
13
|
+
@height = height
|
|
14
|
+
@bg = bg
|
|
15
|
+
clear
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def dimensions
|
|
19
|
+
[@width, @height]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def each_coord(&block)
|
|
23
|
+
(0...@width).each do |x|
|
|
24
|
+
(0...@height).each do |y|
|
|
25
|
+
block.call(x, y)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def rows
|
|
31
|
+
@canvas.each_slice(@width)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def draw_sprite(sprite, x: 0, y: 0, fg: :white)
|
|
35
|
+
sprite.split("\n").each.with_index do |line, i|
|
|
36
|
+
line.each_char.with_index do |char, j|
|
|
37
|
+
next if char == " "
|
|
38
|
+
|
|
39
|
+
self[x + j, y + i] = char.fg(fg)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def write_string(str, x: 0, y: 0, fg: :white, bg: :transparent)
|
|
45
|
+
if x == :center
|
|
46
|
+
x = (@width - str.length) / 2
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if y == :center
|
|
50
|
+
y = (@height - str.length) / 2
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
fg = Array(fg).cycle
|
|
54
|
+
bg = Array(bg).cycle
|
|
55
|
+
|
|
56
|
+
str.to_s.each_char.with_index do |char, i|
|
|
57
|
+
self[x + i, y] = char.fg(fg.next).bg(bg.next)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def draw_rect(x:, y:, width:, height:, char: "█", color: :white)
|
|
62
|
+
x = x.round
|
|
63
|
+
y = y.round
|
|
64
|
+
width = width.round
|
|
65
|
+
height = height.round
|
|
66
|
+
|
|
67
|
+
(x..(x + width - 1)).each do |x_pos|
|
|
68
|
+
(y..(y + height - 1)).each do |y_pos|
|
|
69
|
+
self[x_pos, y_pos] = char.fg(color)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def draw_circle(x:, y:, radius:, char: "█", color: :white)
|
|
75
|
+
x = x.round
|
|
76
|
+
y = y.round
|
|
77
|
+
|
|
78
|
+
(x - radius..x + radius).each do |x_pos|
|
|
79
|
+
(y - radius..y + radius).each do |y_pos|
|
|
80
|
+
next if (x_pos - x)**2 + (y_pos - y)**2 > radius**2
|
|
81
|
+
|
|
82
|
+
self[x_pos, y_pos] = char.fg(color)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def out_of_bounds?(x, y)
|
|
88
|
+
return true if x < 0
|
|
89
|
+
return true if x >= @width
|
|
90
|
+
return true if y < 0
|
|
91
|
+
return true if y >= @height
|
|
92
|
+
|
|
93
|
+
false
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def [](x, y)
|
|
97
|
+
x = x.round
|
|
98
|
+
y = y.round
|
|
99
|
+
|
|
100
|
+
@canvas[at(x, y)]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def []=(x, y, value)
|
|
104
|
+
x = x.round
|
|
105
|
+
y = y.round
|
|
106
|
+
return if out_of_bounds?(x, y)
|
|
107
|
+
|
|
108
|
+
@canvas[at(x, y)] = value
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def clear
|
|
112
|
+
@canvas = create_blank_canvas
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def bg=(bg)
|
|
116
|
+
@bg = bg
|
|
117
|
+
clear
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
def at(x, y)
|
|
123
|
+
y * @width + x
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def create_blank_canvas
|
|
127
|
+
(0...(@width * @height)).map { @bg }
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RichEngine
|
|
4
|
+
module Chance
|
|
5
|
+
def self.of(value, rand_gen: method(:rand))
|
|
6
|
+
percent = if value > 1
|
|
7
|
+
value / 100.0
|
|
8
|
+
else
|
|
9
|
+
value
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
rand_gen.call < percent
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.of_one_in(value, rand_gen: method(:rand))
|
|
16
|
+
of(1 / value.to_f, rand_gen: rand_gen)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RichEngine
|
|
4
|
+
class Cooldown
|
|
5
|
+
def initialize(target_time)
|
|
6
|
+
@timer = 0
|
|
7
|
+
@target_time = target_time
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def update(dt)
|
|
11
|
+
@timer += dt
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get
|
|
15
|
+
@timer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def reset!
|
|
19
|
+
@timer = 0
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def finished?
|
|
23
|
+
@timer >= @target_time
|
|
24
|
+
end
|
|
25
|
+
alias_method :ready?, :finished?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module RichEngine
|
|
2
|
+
class Enum
|
|
3
|
+
module Mixin
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.extend ClassMethods
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module ClassMethods
|
|
9
|
+
def enum(name, enum_options, enum_name: "#{name}s")
|
|
10
|
+
define_singleton_method(enum_name) do
|
|
11
|
+
Enum.new(name, enum_options)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
define_method(name) do
|
|
15
|
+
self.class.public_send(enum_name).public_send(instance_variable_get("@#{name}"))
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module RichEngine
|
|
2
|
+
class Enum
|
|
3
|
+
class Value
|
|
4
|
+
include Comparable
|
|
5
|
+
|
|
6
|
+
attr_reader :enum, :selected
|
|
7
|
+
|
|
8
|
+
def initialize(enum:, selected:)
|
|
9
|
+
@enum = enum
|
|
10
|
+
@selected = selected
|
|
11
|
+
|
|
12
|
+
check_selected_is_a_valid_option
|
|
13
|
+
define_query_methods
|
|
14
|
+
|
|
15
|
+
freeze
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def value
|
|
19
|
+
@enum[@selected]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def <=>(other)
|
|
23
|
+
raise ArgumentError, "Can't compare values from different enums" if enum != other.enum
|
|
24
|
+
|
|
25
|
+
value <=> other.value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ==(other)
|
|
29
|
+
return @enum == other.enum && selected == other.selected if other.is_a? self.class
|
|
30
|
+
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def check_selected_is_a_valid_option
|
|
37
|
+
msg = "Unknown enum value `#{@selected}`. Options are `#{@enum.options.keys}`"
|
|
38
|
+
|
|
39
|
+
raise(ArgumentError, msg) unless @enum.options.has_key? @selected
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def define_query_methods
|
|
43
|
+
@enum.options.each do |enum_option, _value|
|
|
44
|
+
define_singleton_method("#{enum_option}?") { enum_option == @selected }
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require_relative "./enum/value"
|
|
2
|
+
require_relative "./enum/mixin"
|
|
3
|
+
|
|
4
|
+
module RichEngine
|
|
5
|
+
class Enum
|
|
6
|
+
attr_reader :name, :options
|
|
7
|
+
|
|
8
|
+
def initialize(name, options)
|
|
9
|
+
@name = name
|
|
10
|
+
@options = if options.respond_to? :each_pair
|
|
11
|
+
options
|
|
12
|
+
else
|
|
13
|
+
options.map.with_index.to_h
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@options.each_pair do |option, _value|
|
|
17
|
+
define_singleton_method(option) do
|
|
18
|
+
Enum::Value.new(enum: self, selected: option)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def [](option)
|
|
26
|
+
@options.fetch(option)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "canvas"
|
|
4
|
+
require_relative "io"
|
|
5
|
+
|
|
6
|
+
module RichEngine
|
|
7
|
+
# Example:
|
|
8
|
+
#
|
|
9
|
+
# class MyGame < RichEngine::Game
|
|
10
|
+
# def on_create
|
|
11
|
+
# @title = "My Awesome Game"
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# def on_update(elapsed_time, key)
|
|
15
|
+
# quit! if key == :q
|
|
16
|
+
#
|
|
17
|
+
# @canvas.write_string(@title, x: 1, y: 1)
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# MyGame.play
|
|
22
|
+
#
|
|
23
|
+
class Game
|
|
24
|
+
class Exit < StandardError; end
|
|
25
|
+
|
|
26
|
+
def initialize(width, height)
|
|
27
|
+
@width = width
|
|
28
|
+
@height = height
|
|
29
|
+
@config = {screen_width: @width, screen_height: @height}
|
|
30
|
+
@io = RichEngine::IO.new(width, height)
|
|
31
|
+
@canvas = RichEngine::Canvas.new(width, height)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.play(width: 50, height: 10)
|
|
35
|
+
new(width, height).play
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def play
|
|
39
|
+
prepare_screen
|
|
40
|
+
on_create
|
|
41
|
+
|
|
42
|
+
previous_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
|
|
44
|
+
loop do
|
|
45
|
+
current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
46
|
+
elapsed_time = current_time - previous_time
|
|
47
|
+
previous_time = current_time
|
|
48
|
+
|
|
49
|
+
check_game_exit do
|
|
50
|
+
game_loop(elapsed_time)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
on_destroy
|
|
55
|
+
ensure
|
|
56
|
+
restore_screen
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def on_create
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def on_update(_elapsed_time, _key)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def on_destroy
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def quit!
|
|
69
|
+
raise Exit
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def game_loop(elapsed_time)
|
|
73
|
+
key = read_input
|
|
74
|
+
on_update(elapsed_time, key)
|
|
75
|
+
render
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def prepare_screen
|
|
81
|
+
Terminal.clear
|
|
82
|
+
Terminal.hide_cursor
|
|
83
|
+
Terminal.disable_echo
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def restore_screen
|
|
87
|
+
Terminal.display_cursor
|
|
88
|
+
Terminal.enable_echo
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def read_input
|
|
92
|
+
@io.read_async
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def render(use_caching: true)
|
|
96
|
+
@io.write(@canvas.canvas, use_caching: use_caching)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def check_game_exit
|
|
100
|
+
yield
|
|
101
|
+
rescue Exit
|
|
102
|
+
raise StopIteration
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
require "io/console"
|
|
5
|
+
|
|
6
|
+
module RichEngine
|
|
7
|
+
class IO
|
|
8
|
+
Signal.trap("INT") { raise Game::Exit }
|
|
9
|
+
|
|
10
|
+
def initialize(width, height)
|
|
11
|
+
@screen_width = width
|
|
12
|
+
@screen_height = height
|
|
13
|
+
delete_cache
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def write(canvas, use_caching:)
|
|
17
|
+
delete_cache unless use_caching
|
|
18
|
+
|
|
19
|
+
with_caching(canvas) do
|
|
20
|
+
Terminal::Cursor.goto(0, 0)
|
|
21
|
+
output = build_output(canvas)
|
|
22
|
+
$stdout.write output
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def read_async
|
|
27
|
+
key = $stdin.read_nonblock(2)
|
|
28
|
+
_c1, c2 = key.chars
|
|
29
|
+
|
|
30
|
+
if c2 && csi?(key)
|
|
31
|
+
c3, c4 = $stdin.read_nonblock(2).chars
|
|
32
|
+
|
|
33
|
+
if digit?(c3)
|
|
34
|
+
symbolize_key("#{key}#{c3}#{c4}")
|
|
35
|
+
else
|
|
36
|
+
symbolize_key("#{key}#{c3}")
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
symbolize_key(key)
|
|
40
|
+
end
|
|
41
|
+
rescue ::IO::WaitReadable
|
|
42
|
+
nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def delete_cache
|
|
48
|
+
@canvas_cache = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def with_caching(canvas)
|
|
52
|
+
return :cache_hit if canvas == @canvas_cache
|
|
53
|
+
|
|
54
|
+
yield
|
|
55
|
+
@canvas_cache = canvas
|
|
56
|
+
|
|
57
|
+
:cache_miss
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_output(canvas)
|
|
61
|
+
output = +""
|
|
62
|
+
|
|
63
|
+
i = 0
|
|
64
|
+
while i < canvas_size
|
|
65
|
+
output << "#{canvas[i...(i + @screen_width)].join}\n"
|
|
66
|
+
|
|
67
|
+
i += @screen_width
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
output
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def canvas_size
|
|
74
|
+
@canvas_size ||= @screen_height * @screen_width
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def symbolize_key(key)
|
|
78
|
+
return key.downcase.to_sym unless key.start_with?("\e", " ", "\n")
|
|
79
|
+
|
|
80
|
+
case key
|
|
81
|
+
when "\e[A" then :up
|
|
82
|
+
when "\e[B" then :down
|
|
83
|
+
when "\e[C" then :right
|
|
84
|
+
when "\e[D" then :left
|
|
85
|
+
when "\e" then :esc
|
|
86
|
+
when " " then :space
|
|
87
|
+
when "\n" then :enter
|
|
88
|
+
when "\e[2~" then :insert
|
|
89
|
+
when "\e[3~" then :delete
|
|
90
|
+
when "\e[5~" then :pg_up
|
|
91
|
+
when "\e[6~" then :pg_down
|
|
92
|
+
when "\e[H" then :home
|
|
93
|
+
when "\e[F" then :end
|
|
94
|
+
else raise "Unknown key #{key.inspect}" if ENV["DEBUG"] == "all"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def escape?(char)
|
|
99
|
+
char == "\e"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def csi?(str)
|
|
103
|
+
str == "\e["
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def digit?(char)
|
|
107
|
+
char.between?("0", "9")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RichEngine
|
|
4
|
+
class Matrix
|
|
5
|
+
# TODO: implement Enumerable
|
|
6
|
+
|
|
7
|
+
attr_accessor :vec
|
|
8
|
+
|
|
9
|
+
def initialize(width: 1, height: 1, fill_with: nil)
|
|
10
|
+
@vec = Array.new(width) { Array.new(height) { fill_with } }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def [](x, y)
|
|
14
|
+
@vec[x][y]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def []=(x, y, value)
|
|
18
|
+
@vec[x][y] = value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def any?(&block)
|
|
22
|
+
@vec.any? { |row| row.any?(&block) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def each
|
|
26
|
+
@vec.each do |row|
|
|
27
|
+
row.each do |tile|
|
|
28
|
+
yield(tile)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def map(&block)
|
|
34
|
+
@vec.map do |row|
|
|
35
|
+
row.map { |value| block.call(value) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def zip(other)
|
|
40
|
+
new_matrix = Matrix.new
|
|
41
|
+
new_matrix.vec = @vec.map.with_index do |row, i|
|
|
42
|
+
row.map.with_index { |value, j| [value, other[i, j]] }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
new_matrix
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def each_with_indexes
|
|
49
|
+
@vec.each_with_index do |row, i|
|
|
50
|
+
row.each_with_index do |tile, j|
|
|
51
|
+
yield(tile, i, j)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fill(x:, y:, with:)
|
|
57
|
+
xs = Iterable(x)
|
|
58
|
+
ys = Iterable(y)
|
|
59
|
+
|
|
60
|
+
xs.each do |x|
|
|
61
|
+
ys.each do |y|
|
|
62
|
+
@vec[x][y] = with
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def Iterable(value)
|
|
70
|
+
value.respond_to?(:each) ? value : [value]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|