multifarious 0.1.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.
- data/.rspec +2 -0
- data/LICENSE +24 -0
- data/README.md +20 -0
- data/Rakefile +49 -0
- data/lib/mosaic.rb +99 -0
- data/lib/tile.rb +61 -0
- data/multifarious.rb +4 -0
- data/spec/lib/mosaic_spec.rb +124 -0
- data/spec/lib/tile_spec.rb +57 -0
- data/spec/spec_helper.rb +7 -0
- metadata +90 -0
data/.rspec
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2011 Kristján Pétursson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person
|
6
|
+
obtaining a copy of this software and associated documentation
|
7
|
+
files (the "Software"), to deal in the Software without
|
8
|
+
restriction, including without limitation the rights to use,
|
9
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the
|
11
|
+
Software is furnished to do so, subject to the following
|
12
|
+
conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
### 1-2-3 Go
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'multifarious'
|
5
|
+
mosaic = Mosaic.new(10, 10)
|
6
|
+
mosaic.generate!
|
7
|
+
mosaic.draw
|
8
|
+
|
9
|
+
------------
|
10
|
+
|bx..myheq~|
|
11
|
+
|zp..$$$&&v|
|
12
|
+
|l!!A$$$&&s|
|
13
|
+
|;!!u$$$r::|
|
14
|
+
|wkBCDd++::|
|
15
|
+
|Efa_@@++'=|
|
16
|
+
|/###@@tn^^|
|
17
|
+
|F###%%%G^^|
|
18
|
+
|H###%%%I**|
|
19
|
+
|gojc%%%i**|
|
20
|
+
------------
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "multifarious"
|
8
|
+
gem.summary = %Q{A multi-sized tile layer}
|
9
|
+
gem.description = %Q{Multifarious makes generating a solid block of variously-sized tiles simple.}
|
10
|
+
gem.email = "kristjan@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/kristjan/multifarious"
|
12
|
+
gem.authors = ["Kristján Pétursson"]
|
13
|
+
gem.add_development_dependency "rspec"
|
14
|
+
gem.version = "0.1.1"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
if File.exist?('VERSION')
|
40
|
+
version = File.read('VERSION')
|
41
|
+
else
|
42
|
+
version = ""
|
43
|
+
end
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "multifarious #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/lib/mosaic.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
class Mosaic
|
2
|
+
attr_reader :rows, :cols, :grid, :tiles
|
3
|
+
|
4
|
+
DuplicateTile = Class.new(RuntimeError)
|
5
|
+
|
6
|
+
def initialize(rows, cols)
|
7
|
+
@rows, @cols = rows, cols
|
8
|
+
@grid = Array.new(@rows) { Array.new(@cols) {:empty} }
|
9
|
+
@tiles = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def area
|
13
|
+
@rows * @cols
|
14
|
+
end
|
15
|
+
|
16
|
+
def available_area
|
17
|
+
count = 0
|
18
|
+
(0...@rows).each do |row|
|
19
|
+
(0...@cols).each do |col|
|
20
|
+
count += 1 if available?(row, col)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
count
|
24
|
+
end
|
25
|
+
|
26
|
+
def available?(row, col)
|
27
|
+
@grid[row] && @grid[row][col] == :empty
|
28
|
+
end
|
29
|
+
|
30
|
+
def covered_area
|
31
|
+
area - available_area
|
32
|
+
end
|
33
|
+
|
34
|
+
DRAW_CHARS = %[!@#\$%^&*_=+\/\:;'.~abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ].split('')
|
35
|
+
def draw
|
36
|
+
charmap = Hash[@tiles.zip(DRAW_CHARS.first(@tiles.size))]
|
37
|
+
charmap[:empty] = ' '
|
38
|
+
horizontal = '-'*(@cols + 2)
|
39
|
+
puts horizontal
|
40
|
+
puts(@grid.map do |row|
|
41
|
+
"|#{row.map{|tile| charmap[tile]}.join}|"
|
42
|
+
end.join("\n"))
|
43
|
+
puts horizontal
|
44
|
+
end
|
45
|
+
|
46
|
+
def can_place?(tile, row, col)
|
47
|
+
(row...(row + tile.rows)).each do |r|
|
48
|
+
(col...(col + tile.cols)).each do |c|
|
49
|
+
return false unless available?(r, c)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def generate!
|
56
|
+
candidates = Tile.rand_covering(area)
|
57
|
+
until candidates.empty? || candidates.all?{|tile| tile.is_a?(Tile::Small) }
|
58
|
+
tile = candidates.shift
|
59
|
+
placed = false
|
60
|
+
5.times {
|
61
|
+
row, col = random_location(tile)
|
62
|
+
placed = place!(tile, row, col)
|
63
|
+
break if placed
|
64
|
+
}
|
65
|
+
candidates.concat tile.shatter unless placed
|
66
|
+
end
|
67
|
+
# Fill in missed spaces
|
68
|
+
(0...@rows).each do |row|
|
69
|
+
(0...@cols).each do |col|
|
70
|
+
place!(Tile::Small.new, row, col) if available?(row, col)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def has_tile?(tile)
|
77
|
+
!!@tiles.find {|t| t.equal? tile}
|
78
|
+
end
|
79
|
+
|
80
|
+
def place!(tile, row, col)
|
81
|
+
raise DuplicateTile if has_tile?(tile)
|
82
|
+
return false unless can_place?(tile, row, col)
|
83
|
+
@tiles << tile
|
84
|
+
(row...(row + tile.rows)).each do |r|
|
85
|
+
(col...(col + tile.cols)).each do |c|
|
86
|
+
@grid[r][c] = tile
|
87
|
+
end
|
88
|
+
end
|
89
|
+
true
|
90
|
+
end
|
91
|
+
|
92
|
+
def random_location(tile)
|
93
|
+
[rand(rows - tile.rows + 1), rand(cols - tile.cols + 1)]
|
94
|
+
end
|
95
|
+
|
96
|
+
def space_available?
|
97
|
+
covered_area < area
|
98
|
+
end
|
99
|
+
end
|
data/lib/tile.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
class Tile
|
2
|
+
attr_reader :rows, :cols
|
3
|
+
|
4
|
+
class Large < Tile
|
5
|
+
def initialize
|
6
|
+
@rows = @cols = 3
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Medium < Tile
|
11
|
+
def initialize
|
12
|
+
@rows = @cols = 2
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Small < Tile
|
17
|
+
def initialize
|
18
|
+
@rows = @cols = 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def shatter
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.rand(max_area=nil)
|
27
|
+
tiles = [Large, Medium, Small].map(&:new).select do |tile|
|
28
|
+
max_area.nil? || tile.area <= max_area
|
29
|
+
end
|
30
|
+
tiles[Kernel.rand(tiles.size)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.rand_covering(area)
|
34
|
+
tiles = []
|
35
|
+
while self.area(tiles) < area
|
36
|
+
tiles << Tile.rand(area - self.area(tiles))
|
37
|
+
end
|
38
|
+
tiles
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.area(tiles)
|
42
|
+
tiles.inject(0) {|sum, tile| sum + tile.area}
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(rows, cols)
|
46
|
+
@rows, @cols = rows, cols
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
return false unless other.is_a?(Tile)
|
51
|
+
self.rows == other.rows && self.cols == other.cols
|
52
|
+
end
|
53
|
+
|
54
|
+
def area
|
55
|
+
@rows * @cols
|
56
|
+
end
|
57
|
+
|
58
|
+
def shatter
|
59
|
+
Tile.rand_covering(area)
|
60
|
+
end
|
61
|
+
end
|
data/multifarious.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Mosaic do
|
4
|
+
describe "initializing" do
|
5
|
+
it "stores the rows" do
|
6
|
+
Mosaic.new(1,2).rows.should == 1
|
7
|
+
end
|
8
|
+
|
9
|
+
it "stores the columns" do
|
10
|
+
Mosaic.new(1,2).cols.should == 2
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "knows its area" do
|
15
|
+
Mosaic.new(1,1).area.should == 1
|
16
|
+
Mosaic.new(2,2).area.should == 4
|
17
|
+
Mosaic.new(2,3).area.should == 6
|
18
|
+
end
|
19
|
+
|
20
|
+
it "knows its covered area" do
|
21
|
+
m = Mosaic.new(4,4)
|
22
|
+
m.covered_area.should == 0
|
23
|
+
m.place!(Tile::Small.new, 0, 0)
|
24
|
+
m.covered_area.should == 1
|
25
|
+
m.place!(Tile::Large.new, 1, 1)
|
26
|
+
m.covered_area.should == 10
|
27
|
+
end
|
28
|
+
|
29
|
+
it "knows its available area" do
|
30
|
+
m = Mosaic.new(4,4)
|
31
|
+
m.available_area.should == 16
|
32
|
+
m.place!(Tile::Small.new, 0, 0)
|
33
|
+
m.available_area.should == 15
|
34
|
+
m.place!(Tile::Large.new, 1, 1)
|
35
|
+
m.available_area.should == 6
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "available spaces" do
|
39
|
+
before :each do
|
40
|
+
@mosaic = Mosaic.new(1,2)
|
41
|
+
@mosaic.place!(Tile::Small.new, 0, 1)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "knows when a space is available" do
|
45
|
+
@mosaic.available?(0,0).should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it "knows when a space is unavailable" do
|
49
|
+
@mosaic.available?(0,1).should be_false
|
50
|
+
end
|
51
|
+
|
52
|
+
it "considers out-of-bounds unavailable" do
|
53
|
+
@mosaic.available?(3,3).should be_false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "determining whether a tile can be placed" do
|
58
|
+
before :each do
|
59
|
+
@mosaic = Mosaic.new(4, 4)
|
60
|
+
@tile = Tile::Medium.new
|
61
|
+
@mosaic.place!(@tile, 2, 2)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "returns true when all spaces are clear" do
|
65
|
+
@mosaic.can_place?(@tile, 0, 0).should be_true
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns false when there is overlap" do
|
69
|
+
@mosaic.can_place?(@tile, 1, 1).should be_false
|
70
|
+
end
|
71
|
+
|
72
|
+
it "returns false when the tile is out of bounds" do
|
73
|
+
@mosaic.can_place?(@tile, 0, 3).should be_false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "placing a tile" do
|
78
|
+
before(:each) do
|
79
|
+
@mosaic = Mosaic.new(4, 4)
|
80
|
+
@mosaic.place!(Tile::Medium.new, 2, 2)
|
81
|
+
@tile = Tile::Medium.new
|
82
|
+
end
|
83
|
+
|
84
|
+
it "sets every grid space to the tile" do
|
85
|
+
@mosaic.place!(@tile, 0, 0)
|
86
|
+
[0, 1].each do |row|
|
87
|
+
[0, 1].each do |col|
|
88
|
+
@mosaic.grid[row][col].should == @tile
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "adds the tile to the tile list" do
|
94
|
+
@mosaic.place!(@tile, 0, 0)
|
95
|
+
@mosaic.tiles.should include(@tile)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "returns true when the tile is placed" do
|
99
|
+
@mosaic.place!(@tile, 0, 0).should be_true
|
100
|
+
end
|
101
|
+
|
102
|
+
it "returns false if placement fails" do
|
103
|
+
@mosaic.place!(@tile, 1, 1).should be_false
|
104
|
+
end
|
105
|
+
|
106
|
+
it "doesn't let you place the same tile twice" do
|
107
|
+
@mosaic.place!(@tile, 0, 0)
|
108
|
+
expect {
|
109
|
+
@mosaic.place!(@tile, 0, 0)
|
110
|
+
}.to raise_error(Mosaic::DuplicateTile)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "knows when it contains a tile" do
|
115
|
+
mosaic = Mosaic.new(4,4)
|
116
|
+
tile = Tile::Medium.new
|
117
|
+
other_tile = Tile::Medium.new
|
118
|
+
mosaic.should_not have_tile(tile)
|
119
|
+
mosaic.place!(tile, 0, 0)
|
120
|
+
mosaic.should have_tile(tile)
|
121
|
+
mosaic.should_not have_tile(other_tile)
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Tile do
|
4
|
+
describe "initializing" do
|
5
|
+
it "stores the rows" do
|
6
|
+
Tile.new(1,2).rows.should == 1
|
7
|
+
end
|
8
|
+
|
9
|
+
it "stores the columns" do
|
10
|
+
Tile.new(1,2).cols.should == 2
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "comparison" do
|
15
|
+
it "is == to another tile of the same dimensions" do
|
16
|
+
Tile.new(1,2).should == Tile.new(1,2)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "is not == to a tile with a different width" do
|
20
|
+
Tile.new(1,1).should_not == Tile.new(2,1)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "is not == to a tile with a different height" do
|
24
|
+
Tile.new(1,1).should_not == Tile.new(1,2)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "is #equal to itself" do
|
28
|
+
tile = Tile.new(1,1)
|
29
|
+
tile.should be_equal(tile)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is not #equal to another tile of the same dimensions" do
|
33
|
+
Tile.new(1,1).should_not be_equal(Tile.new(1,1))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "knows its area" do
|
38
|
+
Tile.new(1,1).area.should == 1
|
39
|
+
Tile.new(2,2).area.should == 4
|
40
|
+
Tile.new(2,3).area.should == 6
|
41
|
+
end
|
42
|
+
|
43
|
+
it "knows the area of many tiles" do
|
44
|
+
tiles = 1.upto(3).map{|i| Tile.new(i, i) }
|
45
|
+
Tile.area(tiles).should == 14
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "picking a random tile" do
|
49
|
+
it "returns some tile when there is no area limit" do
|
50
|
+
Tile.rand.should be_a(Tile)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "returns a tile of a maximum area" do
|
54
|
+
Tile.rand(3).should be_a(Tile::Small)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multifarious
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- "Kristj\xC3\xA1n P\xC3\xA9tursson"
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-10 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: Multifarious makes generating a solid block of variously-sized tiles simple.
|
36
|
+
email: kristjan@gmail.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
files:
|
45
|
+
- .rspec
|
46
|
+
- LICENSE
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- lib/mosaic.rb
|
50
|
+
- lib/tile.rb
|
51
|
+
- multifarious.rb
|
52
|
+
- spec/lib/mosaic_spec.rb
|
53
|
+
- spec/lib/tile_spec.rb
|
54
|
+
- spec/spec_helper.rb
|
55
|
+
has_rdoc: true
|
56
|
+
homepage: http://github.com/kristjan/multifarious
|
57
|
+
licenses: []
|
58
|
+
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
hash: 3
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.7
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: A multi-sized tile layer
|
89
|
+
test_files: []
|
90
|
+
|