meiro 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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +97 -0
- data/Rakefile +11 -0
- data/lib/meiro.rb +20 -0
- data/lib/meiro/block.rb +239 -0
- data/lib/meiro/dungeon.rb +64 -0
- data/lib/meiro/floor.rb +198 -0
- data/lib/meiro/map_layer.rb +117 -0
- data/lib/meiro/options.rb +56 -0
- data/lib/meiro/partition.rb +20 -0
- data/lib/meiro/passage.rb +24 -0
- data/lib/meiro/room.rb +233 -0
- data/lib/meiro/tile.rb +806 -0
- data/lib/meiro/tile_manager.rb +92 -0
- data/lib/meiro/tile_manager/binary_tile_manager.rb +15 -0
- data/lib/meiro/tile_manager/detailed_tile_manager.rb +347 -0
- data/lib/meiro/tile_manager/rogue_like_tile_manager.rb +57 -0
- data/lib/meiro/version.rb +3 -0
- data/meiro.gemspec +25 -0
- data/spec/block_spec.rb +460 -0
- data/spec/dungeon_spec.rb +153 -0
- data/spec/floor_spec.rb +526 -0
- data/spec/map_layer_spec.rb +296 -0
- data/spec/meiro_spec.rb +15 -0
- data/spec/room_spec.rb +564 -0
- data/spec/spec_helper.rb +8 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 807c34ccb647483756d96c85136bc6dc68e50826
|
4
|
+
data.tar.gz: e0d74e6acf21e69d114a35bcdbaba7a7baa36d46
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 430f65cfb0a5213494544f83528d696f6e6c3e51167767d7b5d06fd4e082fcb6e5f5ea94026e44ef883e4e05482b7203bdc56809860899a9d0ee05dac962081c
|
7
|
+
data.tar.gz: 7affc64b4ec94c5ddba8fe32a38073cfc7d2a1a8fc2a985e915d7fc786a16503a8a856963f8b0b657cd90e3ebd30f37a835d7ced05c65ef957c6486407d333b4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Yuki Morohoshi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Meiro
|
2
|
+
|
3
|
+
Meiroはいわゆる「ローグライク」系、「不思議のダンジョン」系といわれる
|
4
|
+
ゲームに使われるようなランダムダンジョン生成用のRubyライブラリです。
|
5
|
+
|
6
|
+
Meiro is Random Dungeon Generator for Ruby.
|
7
|
+
It generates maps used for so-called Rogue-like games.
|
8
|
+
|
9
|
+
## インストール - Installation
|
10
|
+
|
11
|
+
TODO:
|
12
|
+
|
13
|
+
## 使用方法 - Usage
|
14
|
+
|
15
|
+
### 基本的な使い方 - Basic Usage
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
require 'meiro'
|
19
|
+
|
20
|
+
options = {
|
21
|
+
width: 40,
|
22
|
+
height: 25,
|
23
|
+
min_room_number: 3,
|
24
|
+
max_room_number: 6,
|
25
|
+
min_room_width: 5,
|
26
|
+
max_room_width: 10,
|
27
|
+
min_room_height: 3,
|
28
|
+
max_room_height: 5,
|
29
|
+
block_split_factor: 3.0,
|
30
|
+
}
|
31
|
+
dungeon = Meiro.create_dungeon(options)
|
32
|
+
floor = dungeon.generate_random_floor
|
33
|
+
|
34
|
+
floor.classify!(:rogue_like)
|
35
|
+
puts floor.to_s
|
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
|
+
### 基本構造 - Basic Structure
|
65
|
+
|
66
|
+
```
|
67
|
+
Dungeon
|
68
|
+
|
|
69
|
+
+--- Floor
|
70
|
+
|
|
71
|
+
+--- MapLayer
|
72
|
+
| |
|
73
|
+
| +--- Tiles
|
74
|
+
|
|
75
|
+
+--- Block(Root)
|
76
|
+
|
|
77
|
+
+--- Block
|
78
|
+
| |
|
79
|
+
| +--- Block --- Room
|
80
|
+
| |
|
81
|
+
| +--- Block --- Room
|
82
|
+
|
|
83
|
+
+--- Block
|
84
|
+
|
|
85
|
+
+--- Block --- Room
|
86
|
+
|
|
87
|
+
+--- Block --- Room
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
## Contributing
|
92
|
+
|
93
|
+
1. Fork it ( http://github.com/hoshi-sano/meiro/fork )
|
94
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
95
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
96
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
97
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
# RSpec::Core::RakeTask.new(:spec)
|
5
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
6
|
+
t.rspec_opts = %w[-c -f progress -r ./spec/spec_helper.rb]
|
7
|
+
t.pattern = 'spec/**/*_spec.rb'
|
8
|
+
t.verbose = true
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => :spec
|
data/lib/meiro.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "meiro/version"
|
2
|
+
require "meiro/options"
|
3
|
+
require "meiro/dungeon"
|
4
|
+
require "meiro/map_layer"
|
5
|
+
require "meiro/floor"
|
6
|
+
require "meiro/block"
|
7
|
+
require "meiro/partition"
|
8
|
+
require "meiro/room"
|
9
|
+
require "meiro/tile"
|
10
|
+
require "meiro/passage"
|
11
|
+
|
12
|
+
module Meiro
|
13
|
+
module ModuleMethods
|
14
|
+
def create_dungeon(options={})
|
15
|
+
Dungeon.new(options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
extend ModuleMethods
|
20
|
+
end
|
data/lib/meiro/block.rb
ADDED
@@ -0,0 +1,239 @@
|
|
1
|
+
module Meiro
|
2
|
+
class Block
|
3
|
+
class << self
|
4
|
+
def set_mixin_room_module(mod)
|
5
|
+
@mixin_room_module = mod
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove_mixin_room_module
|
9
|
+
@mixin_room_module = nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
MIN_WIDTH = FLOOR_MIN_WIDTH * 2 + 1
|
14
|
+
MIN_HEIGHT = FLOOR_MIN_HEIGHT * 2 + 1
|
15
|
+
MARGIN = 1
|
16
|
+
|
17
|
+
attr_reader :x, :y, :width, :height,
|
18
|
+
:upper_left, :lower_right,
|
19
|
+
:partition, :room, :parent
|
20
|
+
|
21
|
+
def initialize(floor, x, y, width, height, parent=nil)
|
22
|
+
@floor = floor
|
23
|
+
@x = x
|
24
|
+
@y = y
|
25
|
+
@width = width
|
26
|
+
@height = height
|
27
|
+
@parent = parent
|
28
|
+
if @width >= @height
|
29
|
+
@shape = :horizontal
|
30
|
+
else
|
31
|
+
@shape = :vertical
|
32
|
+
end
|
33
|
+
@separated = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def separate
|
37
|
+
return false if !separatable?
|
38
|
+
if horizontal?
|
39
|
+
vertical_separate
|
40
|
+
else
|
41
|
+
horizontal_separate
|
42
|
+
end
|
43
|
+
@separated = true
|
44
|
+
end
|
45
|
+
|
46
|
+
def unify
|
47
|
+
@upper_left = nil
|
48
|
+
@lower_right = nil
|
49
|
+
@partition = nil
|
50
|
+
@separated = false
|
51
|
+
end
|
52
|
+
|
53
|
+
def separatable?
|
54
|
+
# 分割済みのBlockはそれ以上分割できない
|
55
|
+
return false if @separated
|
56
|
+
|
57
|
+
if horizontal?
|
58
|
+
(@width / 2) >= MIN_WIDTH
|
59
|
+
else
|
60
|
+
(@height / 2) >= MIN_HEIGHT
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def separated?
|
65
|
+
@separated
|
66
|
+
end
|
67
|
+
|
68
|
+
# 共通の親(先祖)を返す
|
69
|
+
def find_ancestor(other)
|
70
|
+
return nil if !self.parent || !other.parent
|
71
|
+
|
72
|
+
# 世代を揃える
|
73
|
+
if self.generation >= other.generation
|
74
|
+
younger, older = self, other
|
75
|
+
else
|
76
|
+
younger, older = other, self
|
77
|
+
end
|
78
|
+
diff = younger.generation - older.generation
|
79
|
+
diff.times { younger = younger.parent }
|
80
|
+
|
81
|
+
# 親が同一になるまで遡る
|
82
|
+
until younger.parent == older.parent
|
83
|
+
younger = younger.parent
|
84
|
+
older = older.parent
|
85
|
+
end
|
86
|
+
|
87
|
+
younger.parent
|
88
|
+
end
|
89
|
+
|
90
|
+
# 同じ親から分割された片割れを返す。
|
91
|
+
# 親がいない場合はnilを返す。
|
92
|
+
def brother
|
93
|
+
if @parent
|
94
|
+
bros = [@parent.upper_left, @parent.lower_right]
|
95
|
+
bros.delete(self)
|
96
|
+
bros.pop
|
97
|
+
else
|
98
|
+
nil
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# 辺を共有するBlockを返す。そのBlockが親(分割済)であった場合は、分
|
103
|
+
# 割されたいずれかのうち、辺を共有している方を返す。
|
104
|
+
def neighbors
|
105
|
+
got = []
|
106
|
+
neighborhood_xy.each do |x, y|
|
107
|
+
got << @floor.get_block(x, y)
|
108
|
+
end
|
109
|
+
got.compact.uniq
|
110
|
+
end
|
111
|
+
|
112
|
+
# 指定した座標が自身に含まれるか否かを返す
|
113
|
+
def include?(x, y)
|
114
|
+
@x <= x && x <= (@x + @width) &&
|
115
|
+
@y <= y && y <= (@y + @height)
|
116
|
+
end
|
117
|
+
|
118
|
+
# 辺を共有するBlockを検索するため、辺を共有するBlockがある場合には
|
119
|
+
# それに含まれるであろうx座標、y座標を返す
|
120
|
+
def neighborhood_xy
|
121
|
+
res = []
|
122
|
+
c = 2 # Partitionがあるため、端から2マス先を見る必要がある
|
123
|
+
if @x - c >= 0
|
124
|
+
res << (@y..(@y + @height)).map {|y| [@x - c, y] }
|
125
|
+
end
|
126
|
+
if @x + @width + c <= @floor.width
|
127
|
+
res << (@y..(@y + @height)).map {|y| [@x + @width + c, y] }
|
128
|
+
end
|
129
|
+
if @y - c >= 0
|
130
|
+
res << (@x..(@x + @width)).map {|x| [x, @y - c] }
|
131
|
+
end
|
132
|
+
if @y + @height + c <= @floor.height
|
133
|
+
res << (@x..(@x + @width)).map {|x| [x, @y + @height + c] }
|
134
|
+
end
|
135
|
+
res.flatten(1)
|
136
|
+
end
|
137
|
+
|
138
|
+
def horizontal?
|
139
|
+
@shape == :horizontal ? true : false
|
140
|
+
end
|
141
|
+
|
142
|
+
def vertical?
|
143
|
+
@shape == :vertical ? true : false
|
144
|
+
end
|
145
|
+
|
146
|
+
def generation
|
147
|
+
@parent ? @parent.generation + 1 : 1
|
148
|
+
end
|
149
|
+
|
150
|
+
def flatten
|
151
|
+
res = []
|
152
|
+
if separated?
|
153
|
+
res << [@upper_left.flatten, @lower_right.flatten]
|
154
|
+
else
|
155
|
+
res << self
|
156
|
+
end
|
157
|
+
res.flatten
|
158
|
+
end
|
159
|
+
|
160
|
+
# Block内にRoom(部屋)を配置する。
|
161
|
+
# 引数にroomを渡した場合、そのroomが配置される。
|
162
|
+
# 引数にroomを渡さない場合、ランダムに生成された部屋が配置される。
|
163
|
+
def put_room(randomizer_or_room=nil)
|
164
|
+
randomizer_or_room ||= Random.new(Time.now.to_i)
|
165
|
+
case randomizer_or_room
|
166
|
+
when Room
|
167
|
+
room = randomizer_or_room
|
168
|
+
return false if !suitable?(room)
|
169
|
+
@room = room
|
170
|
+
when Random
|
171
|
+
randomizer = randomizer_or_room
|
172
|
+
min_w = @floor.min_room_width
|
173
|
+
min_h = @floor.min_room_height
|
174
|
+
max_w = [@floor.max_room_width, (@width - MARGIN * 2)].min
|
175
|
+
max_h = [@floor.max_room_height, (@height - MARGIN * 2)].min
|
176
|
+
rand_w = randomizer.rand(min_w..max_w)
|
177
|
+
rand_h = randomizer.rand(min_h..max_h)
|
178
|
+
@room = create_room(rand_w, rand_h)
|
179
|
+
else
|
180
|
+
return false
|
181
|
+
end
|
182
|
+
@room.block = self
|
183
|
+
@room.set_random_coordinate(randomizer)
|
184
|
+
true
|
185
|
+
end
|
186
|
+
|
187
|
+
def has_room?
|
188
|
+
!!@room
|
189
|
+
end
|
190
|
+
|
191
|
+
def suitable?(room)
|
192
|
+
@width - room.width >= MARGIN * 2 &&
|
193
|
+
@height - room.height >= MARGIN * 2
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
module RoomInitializer
|
199
|
+
def initialize
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def mixin_room_module
|
204
|
+
self.class.instance_variable_get(:@mixin_room_module)
|
205
|
+
end
|
206
|
+
|
207
|
+
def create_room(width, height)
|
208
|
+
room = Room.new(width, height)
|
209
|
+
room.extend(RoomInitializer)
|
210
|
+
room.extend(mixin_room_module) if mixin_room_module
|
211
|
+
room.instance_eval { initialize }
|
212
|
+
room
|
213
|
+
end
|
214
|
+
|
215
|
+
# 横分割 (上下に新しいBlockが生成される)
|
216
|
+
def horizontal_separate
|
217
|
+
c = @height.even? ? 0 : 1
|
218
|
+
block_height = (@height - c) / 2
|
219
|
+
@upper_left = self.class.new(@floor, @x, @y,
|
220
|
+
@width, block_height, self)
|
221
|
+
@lower_right = self.class.new(@floor, @x, @y + 1 + block_height,
|
222
|
+
@width, @height - (1 + block_height), self)
|
223
|
+
@partition = Partition.new(@x, @y + block_height, @width, :horizontal)
|
224
|
+
self
|
225
|
+
end
|
226
|
+
|
227
|
+
# 縦分割 (左右に新しいBlockが生成される)
|
228
|
+
def vertical_separate
|
229
|
+
c = @width.even? ? 0 : 1
|
230
|
+
block_width = (@width - c) / 2
|
231
|
+
@upper_left = self.class.new(@floor, @x, @y,
|
232
|
+
block_width, @height, self)
|
233
|
+
@lower_right = self.class.new(@floor, @x + 1 + block_width, @y,
|
234
|
+
@width - (1 + block_width), @height, self)
|
235
|
+
@partition = Partition.new(@x + block_width, @y, @height, :vertical)
|
236
|
+
self
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Meiro
|
2
|
+
FLOOR_MIN_WIDTH = 5
|
3
|
+
FLOOR_MIN_HEIGHT = FLOOR_MIN_WIDTH
|
4
|
+
ROOM_MIN_WIDTH = 3
|
5
|
+
ROOM_MIN_HEIGHT = ROOM_MIN_WIDTH
|
6
|
+
|
7
|
+
class Dungeon
|
8
|
+
class Config < Options
|
9
|
+
option :width, Integer, 60, lambda {|w,o| w >= FLOOR_MIN_WIDTH }
|
10
|
+
option :height, Integer, 40, lambda {|h,o| h >= FLOOR_MIN_HEIGHT }
|
11
|
+
option :min_room_number, Integer, 1, lambda {|n,o| n > 0 }
|
12
|
+
option :max_room_number, Integer, 6, lambda {|n,o| n >= o[:min_room_number] }
|
13
|
+
option :min_room_width, Integer, 8, lambda {|w,o| w >= ROOM_MIN_WIDTH }
|
14
|
+
option :min_room_height, Integer, 6, lambda {|h,o| h >= ROOM_MIN_HEIGHT }
|
15
|
+
option :max_room_width, Integer, 20, lambda {|w,o| w >= o[:min_room_width] }
|
16
|
+
option :max_room_height, Integer, 20, lambda {|h,o| h >= o[:min_room_height] }
|
17
|
+
option :block_split_factor, Float, 1.0, lambda {|n,o| n > 0 }
|
18
|
+
end
|
19
|
+
|
20
|
+
module FloorInitializer
|
21
|
+
def initialize
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :width, :height,
|
26
|
+
:min_room_number, :max_room_number,
|
27
|
+
:min_room_width, :min_room_height,
|
28
|
+
:max_room_width, :max_room_height,
|
29
|
+
:block_split_factor
|
30
|
+
|
31
|
+
def initialize(options={})
|
32
|
+
config = Config.new(options)
|
33
|
+
@width = config.width
|
34
|
+
@height = config.height
|
35
|
+
@min_room_number = config.min_room_number
|
36
|
+
@max_room_number = config.max_room_number
|
37
|
+
@min_room_width = config.min_room_width
|
38
|
+
@min_room_height = config.min_room_height
|
39
|
+
@max_room_width = config.max_room_width
|
40
|
+
@max_room_height = config.max_room_height
|
41
|
+
@block_split_factor = config.block_split_factor
|
42
|
+
@randomizer = Random.new(Time.now.to_i)
|
43
|
+
end
|
44
|
+
|
45
|
+
def create_floor(mixin_floor_module=nil, mixin_room_module=nil)
|
46
|
+
args = [self, @width, @height,
|
47
|
+
@min_room_width, @min_room_height,
|
48
|
+
@max_room_width, @max_room_height]
|
49
|
+
f = Floor.new(*args)
|
50
|
+
f.extend(FloorInitializer)
|
51
|
+
f.extend(mixin_floor_module) if mixin_floor_module
|
52
|
+
Block.set_mixin_room_module(mixin_room_module) if mixin_room_module
|
53
|
+
f.instance_eval { initialize }
|
54
|
+
f
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_random_floor(mixin_floor_module=nil, mixin_room_module=nil)
|
58
|
+
floor = create_floor(mixin_floor_module, mixin_room_module)
|
59
|
+
args = [@min_room_number, @max_room_number,
|
60
|
+
@block_split_factor, @randomizer]
|
61
|
+
floor.generate_random_room(*args)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|