reight 0.1.2
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/release-gem.yml +62 -0
- data/.github/workflows/tag.yml +35 -0
- data/.github/workflows/test.yml +37 -0
- data/.github/workflows/utils.rb +56 -0
- data/.gitignore +1 -0
- data/ChangeLog.md +23 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README.md +62 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/bin/r8 +35 -0
- data/lib/reight/all.rb +59 -0
- data/lib/reight/app/map/brush.rb +27 -0
- data/lib/reight/app/map/brush_base.rb +54 -0
- data/lib/reight/app/map/canvas.rb +150 -0
- data/lib/reight/app/map/chips.rb +84 -0
- data/lib/reight/app/map/editor.rb +117 -0
- data/lib/reight/app/map/line.rb +35 -0
- data/lib/reight/app/map/rect.rb +29 -0
- data/lib/reight/app/map/tool.rb +32 -0
- data/lib/reight/app/map.rb +8 -0
- data/lib/reight/app/music/editor.rb +25 -0
- data/lib/reight/app/music.rb +1 -0
- data/lib/reight/app/navigator.rb +172 -0
- data/lib/reight/app/runner.rb +275 -0
- data/lib/reight/app/sound/editor.rb +25 -0
- data/lib/reight/app/sound.rb +1 -0
- data/lib/reight/app/sprite/brush.rb +43 -0
- data/lib/reight/app/sprite/canvas.rb +254 -0
- data/lib/reight/app/sprite/chips.rb +92 -0
- data/lib/reight/app/sprite/color.rb +30 -0
- data/lib/reight/app/sprite/editor.rb +272 -0
- data/lib/reight/app/sprite/fill.rb +45 -0
- data/lib/reight/app/sprite/line.rb +37 -0
- data/lib/reight/app/sprite/select.rb +58 -0
- data/lib/reight/app/sprite/shape.rb +43 -0
- data/lib/reight/app/sprite/tool.rb +27 -0
- data/lib/reight/app/sprite.rb +10 -0
- data/lib/reight/app.rb +123 -0
- data/lib/reight/button.rb +93 -0
- data/lib/reight/chip.rb +150 -0
- data/lib/reight/extension.rb +24 -0
- data/lib/reight/helpers.rb +69 -0
- data/lib/reight/history.rb +135 -0
- data/lib/reight/map.rb +264 -0
- data/lib/reight/project.rb +115 -0
- data/lib/reight/reight.rb +83 -0
- data/lib/reight.rb +18 -0
- data/reight.gemspec +40 -0
- data/res/icons.png +0 -0
- data/test/helper.rb +15 -0
- data/test/test_chip.rb +108 -0
- data/test/test_chip_list.rb +68 -0
- data/test/test_map.rb +232 -0
- data/test/test_map_chunk.rb +226 -0
- metadata +244 -0
data/lib/reight/chip.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
using Reight
|
2
|
+
|
3
|
+
|
4
|
+
class Reight::Chip
|
5
|
+
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
def initialize(id, image, x, y, w, h, pos: nil, shape: nil, sensor: nil)
|
9
|
+
@id, @image, @x, @y, @w, @h, @pos, @shape, @sensor =
|
10
|
+
id, image, x, y, w, h, pos, shape, (sensor || false)
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_accessor :shape
|
14
|
+
|
15
|
+
attr_writer :sensor
|
16
|
+
|
17
|
+
attr_reader :id, :image, :x, :y, :w, :h, :pos
|
18
|
+
|
19
|
+
def frame = [x, y, w, h]
|
20
|
+
|
21
|
+
def sensor? = @sensor
|
22
|
+
|
23
|
+
def empty?()
|
24
|
+
pixels.all? {red(_1) == 0 && green(_1) == 0 && blue(_1) == 0}
|
25
|
+
end
|
26
|
+
|
27
|
+
def with(**kwargs)
|
28
|
+
id, image, x, y, w, h, pos, shape, sensor =
|
29
|
+
kwargs.values_at :id, :image, :x, :y, :w, :h, :pos, :shape, :sensor
|
30
|
+
self.class.new(
|
31
|
+
id || @id,
|
32
|
+
image || @image,
|
33
|
+
x || @x,
|
34
|
+
y || @y,
|
35
|
+
w || @w,
|
36
|
+
h || @h,
|
37
|
+
pos: kwargs.key?(:pos) ? pos : @pos,
|
38
|
+
shape: kwargs.key?(:shape) ? shape : @shape,
|
39
|
+
sensor: kwargs.key?(:sensor) ? sensor : @sensor)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_sprite()
|
43
|
+
physics, shape =
|
44
|
+
case @shape
|
45
|
+
when :rect then [true, nil]
|
46
|
+
when :circle then [true, RubySketch::Circle.new(0, 0, w)]
|
47
|
+
else [false, nil]
|
48
|
+
end
|
49
|
+
Sprite.new(
|
50
|
+
0, 0, w, h, image: image, offset: [x, y], shape: shape, physics: physics
|
51
|
+
).tap do |sp|
|
52
|
+
sp.x, sp.y = pos.x, pos.y if pos
|
53
|
+
if physics
|
54
|
+
sp.sensor = true if sensor?
|
55
|
+
sp.fix_angle
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_hash()
|
61
|
+
{
|
62
|
+
id: id, x: x, y: y, w: w, h: h
|
63
|
+
}.tap do |h|
|
64
|
+
h[:pos] = pos.to_a(2) if pos
|
65
|
+
h[:shape] = shape if shape
|
66
|
+
h[:sensor] = true if sensor?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def <=>(o)
|
71
|
+
a = [@id, @image.object_id, @x, @y, @w, @h, @pos, @shape, @sensor]
|
72
|
+
b = o.instance_eval {[@id, @image.object_id, @x, @y, @w, @h, @pos, @shape, @sensor]}
|
73
|
+
a <=> b
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.restore(hash, image)
|
77
|
+
id, x, y, w, h, pos, shape, sensor =
|
78
|
+
hash.values_at :id, :x, :y, :w, :h, :pos, :shape, :sensor
|
79
|
+
new(
|
80
|
+
id, image, x, y, w, h, pos: pos&.then {create_vector(*_1)},
|
81
|
+
shape: shape&.to_sym, sensor: sensor || false)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def pixels()
|
87
|
+
g = createGraphics w, h
|
88
|
+
g.beginDraw do
|
89
|
+
g.copy image, x, y, w, h, 0, 0, w, h
|
90
|
+
end
|
91
|
+
g.load_pixels
|
92
|
+
g.pixels
|
93
|
+
end
|
94
|
+
|
95
|
+
end# Chip
|
96
|
+
|
97
|
+
|
98
|
+
class Reight::ChipList
|
99
|
+
|
100
|
+
include Comparable
|
101
|
+
|
102
|
+
def initialize(image)
|
103
|
+
@image = image
|
104
|
+
@next_id, @id2chip, @frame2chip = 1, {}, {}
|
105
|
+
end
|
106
|
+
|
107
|
+
attr_reader :image
|
108
|
+
|
109
|
+
def at(x, y, w, h)
|
110
|
+
@frame2chip[[x, y, w, h]] ||= create_chip x, y, w, h
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_hash()
|
114
|
+
{next_id: @next_id, chips: @id2chip.values.map {_1.to_hash}}
|
115
|
+
end
|
116
|
+
|
117
|
+
def [](id)
|
118
|
+
@id2chip[id]
|
119
|
+
end
|
120
|
+
|
121
|
+
def <=>(o)
|
122
|
+
a = [@image, @next_id, @id2chip, @frame2chip]
|
123
|
+
b = o.instance_eval {[@image, @next_id, @id2chip, @frame2chip]}
|
124
|
+
a <=> b
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.restore(hash, image)
|
128
|
+
hash => {next_id:, chips:}
|
129
|
+
new(image).tap do |obj|
|
130
|
+
obj.instance_eval do
|
131
|
+
@next_id = next_id
|
132
|
+
@id2chip = chips
|
133
|
+
.map {|hash| Reight::Chip.restore hash, image}
|
134
|
+
.map {|chip| [chip.id, chip]}
|
135
|
+
.to_h
|
136
|
+
@frame2chip = @id2chip.each_value
|
137
|
+
.with_object({}) {|chip, hash| hash[chip.frame] = chip}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def create_chip(x, y, w, h)
|
145
|
+
id = @next_id
|
146
|
+
@next_id += 1
|
147
|
+
@id2chip[id] = Reight::Chip.new(id, @image, x, y, w, h, shape: :rect)
|
148
|
+
end
|
149
|
+
|
150
|
+
end# ChipList
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Reight
|
2
|
+
|
3
|
+
|
4
|
+
# @private
|
5
|
+
module Extension
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def name()
|
10
|
+
super.split('::')[-2]
|
11
|
+
end
|
12
|
+
|
13
|
+
def version()
|
14
|
+
File.read(root_dir 'VERSION')[/[\d\.]+/]
|
15
|
+
end
|
16
|
+
|
17
|
+
def root_dir(path = '')
|
18
|
+
File.expand_path "../../#{path}", __dir__
|
19
|
+
end
|
20
|
+
|
21
|
+
end# Extension
|
22
|
+
|
23
|
+
|
24
|
+
end# Reight
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Reight::Activatable
|
2
|
+
|
3
|
+
def initialize(...)
|
4
|
+
super
|
5
|
+
@active, @activateds = false, []
|
6
|
+
end
|
7
|
+
|
8
|
+
def active=(active)
|
9
|
+
active = !!active
|
10
|
+
return if active == @active
|
11
|
+
@active = active
|
12
|
+
activated!
|
13
|
+
end
|
14
|
+
|
15
|
+
def active? = @active
|
16
|
+
|
17
|
+
def activated(&block)
|
18
|
+
@activateds.push block if block
|
19
|
+
end
|
20
|
+
|
21
|
+
def activated!()
|
22
|
+
@activateds.each {_1.call active}
|
23
|
+
end
|
24
|
+
|
25
|
+
end# Activatable
|
26
|
+
|
27
|
+
|
28
|
+
module Reight::Hookable
|
29
|
+
|
30
|
+
def hook(*names)
|
31
|
+
names.each do |name|
|
32
|
+
singleton_class.__send__ :define_method, name do |&block|
|
33
|
+
@hookable_hooks ||= {}
|
34
|
+
(@hookable_hooks[name] ||= []).push block
|
35
|
+
end
|
36
|
+
singleton_class.__send__ :define_method, "#{name}!" do |*args|
|
37
|
+
@hookable_hooks&.[](name)&.each {|block| block.call(*args)}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end# Hookable
|
43
|
+
|
44
|
+
|
45
|
+
module Reight::HasHelp
|
46
|
+
|
47
|
+
def initialize(...)
|
48
|
+
super
|
49
|
+
set_help name: name
|
50
|
+
end
|
51
|
+
|
52
|
+
def name = @name || self.class.name
|
53
|
+
|
54
|
+
def set_help(name: nil, left: nil, right: nil)
|
55
|
+
@helps = {name: name, left: left, right: right}
|
56
|
+
end
|
57
|
+
|
58
|
+
def help()
|
59
|
+
name = @helps[:name]
|
60
|
+
mouses = @helps
|
61
|
+
.values_at(:left, :right)
|
62
|
+
.zip([:L, :R])
|
63
|
+
.map {|help, char| help ? "#{char}: #{help}" : nil}
|
64
|
+
.compact
|
65
|
+
.then {_1.empty? ? nil : _1.join(' ')}
|
66
|
+
[name, mouses].compact.join ' '
|
67
|
+
end
|
68
|
+
|
69
|
+
end# HasHelp
|
@@ -0,0 +1,135 @@
|
|
1
|
+
class Reight::History
|
2
|
+
|
3
|
+
def initialize(undos = [], redos = [])
|
4
|
+
super()
|
5
|
+
@undos, @redos = undos, redos
|
6
|
+
@group, @enabled = nil, true
|
7
|
+
end
|
8
|
+
|
9
|
+
def append(*actions)
|
10
|
+
return false if actions.empty? || disabled?
|
11
|
+
if @group
|
12
|
+
@group.push(*actions)
|
13
|
+
else
|
14
|
+
@undos.push actions
|
15
|
+
@redos.clear
|
16
|
+
update
|
17
|
+
end
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def begin_grouping(&block)
|
22
|
+
raise "Grouping cannot be nested" if @group
|
23
|
+
@group = []
|
24
|
+
block.call if block
|
25
|
+
ensure
|
26
|
+
end_grouping if block
|
27
|
+
end
|
28
|
+
|
29
|
+
alias group begin_grouping
|
30
|
+
|
31
|
+
def end_grouping()
|
32
|
+
raise "'begin_grouping' is missing" unless @group
|
33
|
+
actions, @group = @group, nil
|
34
|
+
append(*actions)
|
35
|
+
end
|
36
|
+
|
37
|
+
def undo(&block)
|
38
|
+
actions = @undos.pop || return
|
39
|
+
disable do
|
40
|
+
actions.reverse.each {|action| block.call action}
|
41
|
+
end
|
42
|
+
@redos.push actions
|
43
|
+
update
|
44
|
+
end
|
45
|
+
|
46
|
+
def redo(&block)
|
47
|
+
actions = @redos.pop || return
|
48
|
+
disable do
|
49
|
+
actions.each {|action| block.call action}
|
50
|
+
end
|
51
|
+
@undos.push actions
|
52
|
+
update
|
53
|
+
end
|
54
|
+
|
55
|
+
def enable(state = true)
|
56
|
+
return if state == @enabled
|
57
|
+
@enabled = state
|
58
|
+
@enabled ? enabled : disabled
|
59
|
+
end
|
60
|
+
|
61
|
+
def disable(&block)
|
62
|
+
old = enabled?
|
63
|
+
enable false
|
64
|
+
if block
|
65
|
+
begin
|
66
|
+
block.call
|
67
|
+
ensure
|
68
|
+
enable old
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def can_undo?()
|
74
|
+
!@undos.empty?
|
75
|
+
end
|
76
|
+
|
77
|
+
def can_redo?()
|
78
|
+
!@redos.empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def updated(&block)
|
82
|
+
@updated = block
|
83
|
+
end
|
84
|
+
|
85
|
+
def enabled?()
|
86
|
+
@enabled
|
87
|
+
end
|
88
|
+
|
89
|
+
def disabled?()
|
90
|
+
!enabled?
|
91
|
+
end
|
92
|
+
|
93
|
+
def enabled()
|
94
|
+
end
|
95
|
+
|
96
|
+
def disabled()
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_h(&dump_object)
|
100
|
+
{
|
101
|
+
version: 1,
|
102
|
+
undos: self.class.dump(@undos, &dump_object),
|
103
|
+
redos: self.class.dump(@redos, &dump_object)
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.load(hash, &restore_object)
|
108
|
+
undos = restore hash['undos'], &restore_object
|
109
|
+
redos = restore hash['redos'], &restore_object
|
110
|
+
self.new undos, redos
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def update()
|
116
|
+
@updated.call if @updated
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.dump(xdos, &dump_object)
|
120
|
+
xdos.map do |actions|
|
121
|
+
actions.map do |action, *args|
|
122
|
+
[action.to_s, *args.map {|obj| dump_object.call(obj) || obj}]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def self.restore(xdos, &restore_object)
|
128
|
+
xdos.map do |actions|
|
129
|
+
actions.map do |action, *args|
|
130
|
+
[action.intern, *args.map {|obj| restore_object.call(obj) || obj}]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end# History
|
data/lib/reight/map.rb
ADDED
@@ -0,0 +1,264 @@
|
|
1
|
+
using Reight
|
2
|
+
|
3
|
+
|
4
|
+
class Reight::Map
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
def initialize(chip_size: 8, chunk_size: 128)
|
10
|
+
raise ArgumentError, "Invalid chip_size: #{chip_size}" if
|
11
|
+
chip_size.to_i != chip_size
|
12
|
+
raise ArgumentError, "Invalid chunk_size: #{chunk_size}" if
|
13
|
+
chunk_size.to_i != chunk_size || chunk_size % chip_size != 0
|
14
|
+
|
15
|
+
@chip_size, @chunk_size = [chip_size, chunk_size].map &:to_i
|
16
|
+
@chunks = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def put(x, y, chip)
|
20
|
+
return unless chip
|
21
|
+
each_chunk x, y, chip.w, chip.h, create: true do |chunk|
|
22
|
+
chunk.put x, y, chip
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(x, y)
|
27
|
+
chip = self[x, y] or return
|
28
|
+
cx, cy, cw, ch = chip.then {[_1.pos.x, _1.pos.y, _1.w, _1.h]}
|
29
|
+
each_chunk cx, cy, cw, ch, create: false do |chunk|
|
30
|
+
each_chip_pos(cx, cy, cw, ch) {|xx, yy| chunk.delete xx, yy}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_chip(chip)
|
35
|
+
delete chip.pos.x, chip.pos.y
|
36
|
+
end
|
37
|
+
|
38
|
+
def each_chip(x = nil, y = nil, w = nil, h = nil, clip_by_chunk: false, &block)
|
39
|
+
return enum_for :each_chip, x, y, w, h, clip_by_chunk: clip_by_chunk unless block
|
40
|
+
enum =
|
41
|
+
case [x, y, w, h]
|
42
|
+
in [nil, nil, nil, nil] then @chunks.values.each
|
43
|
+
in [Numeric, Numeric, Numeric, Numeric] then each_chunk x, y, w, h
|
44
|
+
else raise ArgumentError, "Invalid bounds"
|
45
|
+
end
|
46
|
+
x = y = w = h = nil if clip_by_chunk
|
47
|
+
enum.each do |chunk|
|
48
|
+
chunk.each_chip(x, y, w, h) {|chip, _, _| block.call chip}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def each(&block) = each_chip(&block)
|
53
|
+
|
54
|
+
def to_hash()
|
55
|
+
{
|
56
|
+
chip_size: @chip_size, chunk_size: @chunk_size,
|
57
|
+
chunks: @chunks.values.map(&:to_hash)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](x, y)
|
62
|
+
chunk_at(x, y)&.[](x, y)
|
63
|
+
end
|
64
|
+
|
65
|
+
def <=>(o)
|
66
|
+
a = [@chip_size, @chunk_size, @chunks]
|
67
|
+
b = o.instance_eval {[@chip_size, @chunk_size, @chunks]}
|
68
|
+
a <=> b
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.restore(hash, source_chips)
|
72
|
+
hash => {chip_size:, chunk_size:, chunks:}
|
73
|
+
new(chip_size: chip_size, chunk_size: chunk_size).tap do |obj|
|
74
|
+
obj.instance_eval do
|
75
|
+
@chunks = chunks.each.with_object({}) do |chunk_hash, result|
|
76
|
+
chunk_hash => {x:, y:}
|
77
|
+
result[[x, y]] = Chunk.restore chunk_hash, source_chips
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def each_chunk(x, y, w = 0, h = 0, create: false, &block)
|
86
|
+
return enum_for :each_chunk, x, y, w, h, create: create unless block
|
87
|
+
x, w = x + w, -w if w < 0
|
88
|
+
y, h = y + h, -h if h < 0
|
89
|
+
x1, x2 = x, x + w
|
90
|
+
y1, y2 = y, y + h
|
91
|
+
x2 -= 1 if x2 > x1
|
92
|
+
y2 -= 1 if y2 > y1
|
93
|
+
x1, y1 = align_chunk_pos x1, y1
|
94
|
+
x2, y2 = align_chunk_pos x2, y2
|
95
|
+
(y1..y2).step @chunk_size do |yy|
|
96
|
+
(x1..x2).step @chunk_size do |xx|
|
97
|
+
chunk = chunk_at xx, yy, create: create
|
98
|
+
block.call chunk if chunk
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def chunk_at(x, y, create: false)
|
104
|
+
x, y = align_chunk_pos x, y
|
105
|
+
if create
|
106
|
+
@chunks[[x, y]] ||=
|
107
|
+
Chunk.new x, y, @chunk_size, @chunk_size, chip_size: @chip_size
|
108
|
+
else
|
109
|
+
@chunks[[x, y]]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def each_chip_pos(x, y, w, h, &block)
|
114
|
+
x, w = x + w, -w if w < 0
|
115
|
+
y, h = y + h, -h if h < 0
|
116
|
+
x1, y1 = align_chip_pos x, y
|
117
|
+
x2, y2 = align_chip_pos x + w + @chip_size - 1, y + h + @chip_size - 1
|
118
|
+
(y1...y2).step @chip_size do |yy|
|
119
|
+
(x1...x2).step @chip_size do |xx|
|
120
|
+
block.call xx, yy
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def align_chunk_pos(x, y)
|
126
|
+
s = @chunk_size
|
127
|
+
[x.to_i / s * s, y.to_i / s * s]
|
128
|
+
end
|
129
|
+
|
130
|
+
def align_chip_pos(x, y)
|
131
|
+
s = @chip_size
|
132
|
+
[x.to_i / s * s, y.to_i / s * s]
|
133
|
+
end
|
134
|
+
|
135
|
+
end# Map
|
136
|
+
|
137
|
+
|
138
|
+
class Reight::Map::Chunk
|
139
|
+
|
140
|
+
include Comparable
|
141
|
+
|
142
|
+
def initialize(x, y, w, h, chip_size: 8)
|
143
|
+
raise ArgumentError, "Invalid chip_size: #{chip_size}" if chip_size.to_i != chip_size
|
144
|
+
raise ArgumentError, "Invalid w: #{w}" if w % chip_size != 0
|
145
|
+
raise ArgumentError, "Invalid h: #{h}" if h % chip_size != 0
|
146
|
+
|
147
|
+
@x, @y, @w, @h, @chip_size = [x, y, w, h, chip_size].map &:to_i
|
148
|
+
@chips, @ncolumn = [], @w / @chip_size
|
149
|
+
end
|
150
|
+
|
151
|
+
attr_reader :x, :y, :w, :h
|
152
|
+
|
153
|
+
def put(x, y, chip)
|
154
|
+
x, y = align_chip_pos x, y
|
155
|
+
raise "Invalid chip size" if
|
156
|
+
chip.w % @chip_size != 0 || chip.h % @chip_size != 0
|
157
|
+
raise "Conflicts with other chips" if
|
158
|
+
each_chip_pos(x, y, chip.w, chip.h).any? {|xx, yy| self[xx, yy]}
|
159
|
+
|
160
|
+
new_chip = nil
|
161
|
+
get_chip = -> {new_chip ||= chip.with pos: create_vector(x, y)}
|
162
|
+
each_chip_pos x, y, chip.w, chip.h do |xx, yy|
|
163
|
+
@chips[pos2index xx, yy] = get_chip.call
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def delete(x, y)
|
168
|
+
chip = self[x, y] or return
|
169
|
+
each_chip_pos chip.pos.x, chip.pos.y, chip.w, chip.h do |xx, yy|
|
170
|
+
index = pos2index xx, yy
|
171
|
+
@chips[index] = nil if @chips[index]&.id == chip.id
|
172
|
+
end
|
173
|
+
delete_last_nils
|
174
|
+
end
|
175
|
+
|
176
|
+
def each_chip(x = nil, y = nil, w = nil, h = nil, include_hidden: false, &block)
|
177
|
+
return enum_for(:each_chip, x, y, w, h, include_hidden: include_hidden) unless block
|
178
|
+
x, w = x + w, -w if x && w && w < 0
|
179
|
+
y, h = y + h, -h if y && h && h < 0
|
180
|
+
@chips.each.with_index do |chip, index|
|
181
|
+
next unless chip
|
182
|
+
xx, yy = index2pos index
|
183
|
+
pos = chip.pos
|
184
|
+
next if x && !intersect?(x, y, w, h, pos.x, pos.y, chip.w, chip.h)
|
185
|
+
block.call chip, xx, yy if include_hidden || (xx == pos.x && yy == pos.y)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def each_chip_pos(x, y, w, h, &block)
|
190
|
+
return enum_for :each_chip_pos, x, y, w, h unless block
|
191
|
+
x, w = x + w, -w if w < 0
|
192
|
+
y, h = y + h, -h if h < 0
|
193
|
+
x1, y1 = align_chip_pos x, y
|
194
|
+
x2, y2 = align_chip_pos x + w + @chip_size - 1, y + h + @chip_size - 1
|
195
|
+
x1, x2 = [x1, x2].map {_1.clamp @x, @x + @w}
|
196
|
+
y1, y2 = [y1, y2].map {_1.clamp @y, @y + @h}
|
197
|
+
(y1...y2).step @chip_size do |yy|
|
198
|
+
(x1...x2).step @chip_size do |xx|
|
199
|
+
block.call xx, yy
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def frame = [@x, @y, @w, @h]
|
205
|
+
|
206
|
+
def to_hash()
|
207
|
+
{
|
208
|
+
x: @x, y: @y, w: @w, h: @h, chip_size: @chip_size,
|
209
|
+
chips: @chips.map {|chip| chip ? [chip.id, chip.pos.x, chip.pos.y] : nil}
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
def [](x, y)
|
214
|
+
index = pos2index x, y
|
215
|
+
return nil if index < 0 || (@w * @h) <= index
|
216
|
+
@chips[index]
|
217
|
+
end
|
218
|
+
|
219
|
+
def <=>(o)
|
220
|
+
a = [@x, @y, @w, @h, @chip_size, @chips]
|
221
|
+
b = o.instance_eval {[@x, @y, @w, @h, @chip_size, @chips]}
|
222
|
+
a <=> b
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.restore(hash, source_chips)
|
226
|
+
hash => {x:, y:, w:, h:, chip_size: chip_size, chips: chip_ids}
|
227
|
+
tmp_chips = {}
|
228
|
+
get_chip = -> id, x, y {
|
229
|
+
tmp_chips[[id, x, y]] ||= source_chips[id].with(pos: create_vector(x, y))
|
230
|
+
}
|
231
|
+
new(x, y, w, h, chip_size: chip_size).tap do |obj|
|
232
|
+
obj.instance_eval do
|
233
|
+
@chips = chip_ids.map {|id, x, y| id ? get_chip.call(id, x, y) : nil}
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
private
|
239
|
+
|
240
|
+
def pos2index(x, y) =
|
241
|
+
(y.to_i - @y) / @chip_size * @ncolumn + (x.to_i - @x) / @chip_size
|
242
|
+
|
243
|
+
def index2pos(index) = [
|
244
|
+
@x + (index % @ncolumn) * @chip_size,
|
245
|
+
@y + (index / @ncolumn) * @chip_size
|
246
|
+
]
|
247
|
+
|
248
|
+
def align_chip_pos(x, y)
|
249
|
+
s = @chip_size
|
250
|
+
[x.to_i / s * s, y.to_i / s * s]
|
251
|
+
end
|
252
|
+
|
253
|
+
def delete_last_nils()
|
254
|
+
last = @chips.rindex {_1 != nil}
|
255
|
+
@chips = @chips[..last] if last
|
256
|
+
end
|
257
|
+
|
258
|
+
def intersect?(ax, ay, aw, ah, bx, by, bw, bh)
|
259
|
+
ax2, ay2 = ax + aw, ay + ah
|
260
|
+
bx2, by2 = bx + bw, by + bh
|
261
|
+
ax < bx2 && bx < ax2 && ay < by2 && by < ay2
|
262
|
+
end
|
263
|
+
|
264
|
+
end# Chunk
|