ludy 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +21 -1
- data/lib/ludy/ludy_ext.rb +6 -3
- data/lib/puzzle_generator/chain.rb +33 -0
- data/lib/puzzle_generator/chained_map.rb +116 -0
- data/lib/puzzle_generator/colored_map.rb +59 -0
- data/lib/puzzle_generator/map.rb +135 -0
- data/lib/puzzle_generator/misc.rb +132 -0
- data/lib/puzzle_generator/puzzle.rb +64 -0
- data/lib/puzzle_generator.rb +35 -0
- data/ludy.gemspec +1 -1
- data/test/tc_ludy_ext.rb +6 -0
- data/test/test_puzzle.rb +177 -0
- metadata +50 -34
data/CHANGES
CHANGED
@@ -1,6 +1,26 @@
|
|
1
1
|
|
2
2
|
==============================
|
3
|
-
ludy
|
3
|
+
ludy TODO
|
4
|
+
Proc#bind
|
5
|
+
multi
|
6
|
+
chain travel
|
7
|
+
method hook
|
8
|
+
|
9
|
+
==============================
|
10
|
+
ludy 0.0.8, 2007.12.06
|
11
|
+
|
12
|
+
1. ludy_ext:
|
13
|
+
added:
|
14
|
+
1. Array#untranspose!
|
15
|
+
2. Array#unzip!
|
16
|
+
|
17
|
+
changed:
|
18
|
+
1. Kernel#curry support Symbol
|
19
|
+
|
20
|
+
2. puzzle_generator added...
|
21
|
+
|
22
|
+
==============================
|
23
|
+
ludy 0.0.7, 2007.10.08
|
4
24
|
|
5
25
|
1. ludy_ext:
|
6
26
|
added:
|
data/lib/ludy/ludy_ext.rb
CHANGED
@@ -85,7 +85,9 @@ class Array
|
|
85
85
|
}
|
86
86
|
result
|
87
87
|
end
|
88
|
-
def
|
88
|
+
def untranspose!; replace untranspose; end
|
89
|
+
def unzip; untranspose.first; end
|
90
|
+
def unzip!; replace unzip; end
|
89
91
|
end
|
90
92
|
|
91
93
|
class Proc
|
@@ -113,7 +115,8 @@ end
|
|
113
115
|
module Kernel
|
114
116
|
def id a = nil; a.nil? ? self : a; end
|
115
117
|
def curry
|
116
|
-
|
118
|
+
result = self.kind_of?(Symbol) ? self.to_proc : self
|
119
|
+
class << result
|
117
120
|
alias_method :orig_call, :call
|
118
121
|
def call *args, &block
|
119
122
|
if self.arity == -1
|
@@ -137,6 +140,6 @@ module Kernel
|
|
137
140
|
end
|
138
141
|
alias_method :[], :call
|
139
142
|
end
|
140
|
-
|
143
|
+
result
|
141
144
|
end
|
142
145
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), 'misc')
|
3
|
+
|
4
|
+
module PuzzleGenerator
|
5
|
+
|
6
|
+
class Chain
|
7
|
+
include Enumerable
|
8
|
+
def initialize position = [0, 0], direct = Up, invoke = DefaultOption[:invoke]
|
9
|
+
@direct = direct
|
10
|
+
x, y = position
|
11
|
+
@data = case @direct
|
12
|
+
when Up : ([x]*invoke).zip((y...y+invoke).to_a)
|
13
|
+
when Right: (x...x+invoke).to_a.zip([y]*invoke)
|
14
|
+
when Left : (x-invoke+1..x).to_a.zip([y]*invoke)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
def <=> rhs; @data <=> rhs.instance_variable_get('@data'); end
|
18
|
+
def == rhs; (self <=> rhs) == 0; end
|
19
|
+
def each █ @data.each █ self end
|
20
|
+
def [] index; @data[index]; end
|
21
|
+
def to_a; @data.clone; end
|
22
|
+
def up?; @direct == Up; end
|
23
|
+
def bound_ok? max_width, max_height
|
24
|
+
not @data.find{ |i|
|
25
|
+
i.first >= max_width ||
|
26
|
+
i.last >= max_height ||
|
27
|
+
i.first < 0 ||
|
28
|
+
i.last < 0
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
|
2
|
+
pwd = File.dirname __FILE__
|
3
|
+
|
4
|
+
require File.join(pwd, 'misc')
|
5
|
+
require File.join(pwd, 'chain')
|
6
|
+
require File.join(pwd, 'map')
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
|
10
|
+
gem 'ludy', '>=0.0.7' # for Array#combine
|
11
|
+
require 'ludy/ludy_ext'
|
12
|
+
|
13
|
+
gem 'facets', '>=2.0.0' # for lots of things
|
14
|
+
require 'facets/random' # for Array#pick
|
15
|
+
|
16
|
+
module PuzzleGenerator
|
17
|
+
|
18
|
+
# 1. put a chain
|
19
|
+
# 2. destroy a chain by chain
|
20
|
+
# 2. a. choose a position by last map
|
21
|
+
# 2. b. choose a direct from 3 direct (memo p+d)
|
22
|
+
# 2. c. check if the position + direct is ok? ok pass, failed choose again
|
23
|
+
# 2. d. goto 2
|
24
|
+
|
25
|
+
class ChainedMap
|
26
|
+
include DisplayMap, MapUtils
|
27
|
+
attr_reader :maps, :option
|
28
|
+
def initialize option = {}
|
29
|
+
@option = DefaultOption.merge option
|
30
|
+
raise_if_bad_argument
|
31
|
+
init_map
|
32
|
+
@result_map = begin next_level until @maps.size >= @option[:level]; put_fire_point
|
33
|
+
rescue GenerationFailed; $! end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def raise_if_bad_argument
|
38
|
+
msg = []
|
39
|
+
@option.each{ |name, value| msg << "#{name} should be greater than 1." if value <= 1 }
|
40
|
+
raise ArgumentError.new(msg.join("\n")) unless msg.empty?
|
41
|
+
end
|
42
|
+
def make_map; Map.new @option; end
|
43
|
+
def init_map
|
44
|
+
@maps = [make_map]
|
45
|
+
@result_map = make_map_array
|
46
|
+
@picked_chain = make_init_chains([(0..@option[:width]-@option[:invoke]), [0]].combos).pick
|
47
|
+
put_picked_chain_on @maps
|
48
|
+
resolve_map
|
49
|
+
end
|
50
|
+
# please make this init chain to variable length chain
|
51
|
+
def make_init_chains positions
|
52
|
+
positions.inject([]){ |result, pos| result << Chain.new(pos, Right, @option[:invoke]) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def chain_ok?
|
56
|
+
raise GenerationFailed.new("ChainedMap: last result_map: #{@result_map.inspect}") if @picked_chain.nil?
|
57
|
+
@maps_preview = @maps.deep_clone
|
58
|
+
put_picked_chain_on @maps_preview
|
59
|
+
|
60
|
+
result = check_overlap_and_resolve_it &&
|
61
|
+
check_broken_except_last &&
|
62
|
+
check_answer_correctness(@result_map_preview) # need this if you gen variable length chain
|
63
|
+
|
64
|
+
@maps_preview = nil
|
65
|
+
@result_map_preview = nil
|
66
|
+
result
|
67
|
+
end
|
68
|
+
def next_level new_map = nil
|
69
|
+
@maps << (new_map || make_map)
|
70
|
+
chains = @maps[-1].break_chains @maps[-2], @result_map
|
71
|
+
|
72
|
+
@picked_chain = chains.pick!
|
73
|
+
@picked_chain = chains.pick! until chain_ok?
|
74
|
+
|
75
|
+
put_picked_chain_on @maps
|
76
|
+
@picked_chain = nil
|
77
|
+
resolve_map
|
78
|
+
end
|
79
|
+
def put_fire_point; next_level Map.new(@option.merge(:invoke => 1, :invoke_max => 1)); end
|
80
|
+
def put_picked_chain_on maps
|
81
|
+
maps.last.chains << @picked_chain
|
82
|
+
@picked_chain.each{ |pos|
|
83
|
+
x, y = pos
|
84
|
+
maps.each{ |map|
|
85
|
+
column = map[x]
|
86
|
+
column.replace(column[0...y] + column[y..-1].rotate)
|
87
|
+
}
|
88
|
+
maps.last[x, y] = maps.size
|
89
|
+
}
|
90
|
+
end
|
91
|
+
def check_overlap_and_resolve_it
|
92
|
+
@result_map_preview = @maps_preview.body.inject(make_map_array){ |result, map|
|
93
|
+
result.each_with_index{ |column, x|
|
94
|
+
column.each_with_index{ |value, y|
|
95
|
+
# assert one of them is zero or they are all zero
|
96
|
+
return false unless value * map[x, y] == 0
|
97
|
+
}
|
98
|
+
}
|
99
|
+
combine_map result, map
|
100
|
+
}
|
101
|
+
end
|
102
|
+
def check_broken_except_last
|
103
|
+
@maps_preview.body.all?{ |map|
|
104
|
+
result = true
|
105
|
+
map.each_with_index_2d{ |i, x, y|
|
106
|
+
result &&= !check_left_chain( map, x, y) &&
|
107
|
+
!check_right_chain(map, x, y) &&
|
108
|
+
!check_up_chain( map, x, y) &&
|
109
|
+
!check_down_chain( map, x, y)
|
110
|
+
}
|
111
|
+
result
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), 'misc')
|
3
|
+
|
4
|
+
module PuzzleGenerator
|
5
|
+
class ColoredMap
|
6
|
+
include DisplayMap, MapUtils
|
7
|
+
def initialize chained_map, colors = (1..chained_map.option[:colors]).to_a
|
8
|
+
@result_map = []
|
9
|
+
@maps = []
|
10
|
+
@chained_map = chained_map
|
11
|
+
@option = @chained_map.option
|
12
|
+
|
13
|
+
debug_init if PuzzleGenerator.debug
|
14
|
+
|
15
|
+
chained_map.maps.each_with_index{ |map, index|
|
16
|
+
@maps << map.clone_with_map{ |color|
|
17
|
+
color == 0 ? 0 : colors[index % @option[:colors]]
|
18
|
+
}
|
19
|
+
}
|
20
|
+
resolve_map
|
21
|
+
@result_map = if check_no_invoke && strip_answer && check_answer_correctness
|
22
|
+
put_answer_back; @result_map
|
23
|
+
else
|
24
|
+
GenerationFailed.new("ColoredMap: last result_map: #{@result_map.inspect}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def debug_init
|
30
|
+
@debug_answer = @chained_map.maps.last.each_with_index_2d{ |i, x, y| break i if i != 0 }
|
31
|
+
end
|
32
|
+
def check_no_invoke
|
33
|
+
m = Map.new :data => @result_map, :option => @option
|
34
|
+
m.each_with_index_2d{ |i, x, y|
|
35
|
+
return false if check_left_chain( m, x, y) ||
|
36
|
+
check_right_chain(m, x, y) ||
|
37
|
+
check_up_chain( m, x, y) ||
|
38
|
+
check_down_chain( m, x, y)
|
39
|
+
}
|
40
|
+
true
|
41
|
+
end
|
42
|
+
def strip_answer
|
43
|
+
@x, @y = @chained_map.maps.last.each_with_index_2d{ |i, x, y| break [x, y] if i != 0 }
|
44
|
+
# map = Map.new :data => @result_map, :option => @option
|
45
|
+
# return false unless !check_left_chain( map, @x, @y) &&
|
46
|
+
# !check_right_chain(map, @x, @y) &&
|
47
|
+
# !check_up_chain( map, @x, @y) &&
|
48
|
+
# !check_down_chain( map, @x, @y)
|
49
|
+
@answer_color = @result_map[@x][@y]
|
50
|
+
@result_map[@x][@y] = 0
|
51
|
+
true
|
52
|
+
end
|
53
|
+
def put_answer_back
|
54
|
+
@result_map[@x][@y] = @debug_answer || @answer_color
|
55
|
+
@x, @y, @answer_color = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
|
2
|
+
pwd = File.dirname __FILE__
|
3
|
+
|
4
|
+
require File.join(pwd, 'misc')
|
5
|
+
require File.join(pwd, 'chain')
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
|
9
|
+
gem 'ludy', '>=0.0.7' # for Array#untranspose
|
10
|
+
require 'ludy/ludy_ext'
|
11
|
+
|
12
|
+
gem 'facets', '>=2.0.0' # for lots of things
|
13
|
+
require 'facets' # for Array#combos
|
14
|
+
require 'facets/random' # for Kernel#maybe
|
15
|
+
|
16
|
+
# a = [[1,2],[3,4],[5,6]]
|
17
|
+
# [a, [1,2,3]].combos
|
18
|
+
# => [[[1, 2], 1], [[1, 2], 2], [[1, 2], 3], [[3, 4], 1], [[3, 4], 2], [[3, 4], 3], [[5, 6], 1], [[5, 6], 2], [[5, 6], 3]]
|
19
|
+
|
20
|
+
module PuzzleGenerator
|
21
|
+
|
22
|
+
class Map
|
23
|
+
include DisplayMap, Enumerable
|
24
|
+
attr_reader :chains
|
25
|
+
def result_map; @data; end
|
26
|
+
def initialize option = {}
|
27
|
+
@option = DefaultOption.merge option
|
28
|
+
@data = @option[:data] || (Array.new(@option[:width])).map{ [0]*@option[:height] }
|
29
|
+
@chains = @option[:chains] || []
|
30
|
+
end
|
31
|
+
# TRAP!! view is not view, it's clone!!
|
32
|
+
# def [] x, y = (0..-1); @data[x][y]; end
|
33
|
+
|
34
|
+
# NOTICE!! this is not view (reference)
|
35
|
+
def [] x, y = nil
|
36
|
+
if y.nil?
|
37
|
+
@data[x]
|
38
|
+
else
|
39
|
+
if x.kind_of? Range
|
40
|
+
@data[x].transpose[y]
|
41
|
+
else
|
42
|
+
@data[x][y]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
def []= x, y, v; @data[x][y] = v; end
|
47
|
+
def each_column_with_index; @data.each_with_index{ |column, index| yield column, index }; end
|
48
|
+
def each_column; @data.each{ |column| yield column }; end
|
49
|
+
def clone_with_map
|
50
|
+
Map.new @option.merge(
|
51
|
+
:data => @data.transpose.map{ |row| row.map{ |i| yield i } }.untranspose,
|
52
|
+
:chains => chains
|
53
|
+
)
|
54
|
+
end
|
55
|
+
def to_a; @data.clone; end
|
56
|
+
def each; @data.flatten.each{ |i| yield i }; end
|
57
|
+
def each_with_index_2d
|
58
|
+
@data.transpose.each_with_index{ |row, y| row.each_with_index{ |i, x| yield i, x, y } }
|
59
|
+
end
|
60
|
+
def break_chains map, result_map
|
61
|
+
@temp_map = map
|
62
|
+
@temp_result_map = result_map
|
63
|
+
result = []
|
64
|
+
map.each_column_with_index{ |column, ix|
|
65
|
+
column.each_with_index{ |y, iy| result << [ix, iy] if y != 0 }
|
66
|
+
} # e.g., [[0, 0], [1, 1]]
|
67
|
+
result = transform result,
|
68
|
+
:with_belows,
|
69
|
+
:with_directs,
|
70
|
+
:to_chains,
|
71
|
+
:strip_duplicated_chain,
|
72
|
+
:strip_out_bounded_chain,
|
73
|
+
:strip_overflow_chain,
|
74
|
+
:strip_floating_chain
|
75
|
+
|
76
|
+
@temp_result_map = nil
|
77
|
+
@temp_map = nil
|
78
|
+
result
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
def transform target, *funs; target = method(funs.shift)[target] until funs.empty?; target; end
|
83
|
+
def with_belows target
|
84
|
+
result = []
|
85
|
+
up_hack = 0 # @temp_map.chains.last.up? ? 1 : 0
|
86
|
+
target.each{ |pos|
|
87
|
+
x, y = pos
|
88
|
+
result.concat( ([x]*(y+1)).zip((up_hack..y).to_a) )
|
89
|
+
}
|
90
|
+
result.uniq
|
91
|
+
# e.g., [[0, 0], [[1, 0], [1, 1]]] =>
|
92
|
+
# [[0, 0], [1, 0], [1, 1]]
|
93
|
+
end
|
94
|
+
def with_directs target
|
95
|
+
# [target, [Up] + [Right, Left]*10].combos
|
96
|
+
[target, [Up, Right, Left]].combos
|
97
|
+
# e.g., [[[0, 0], Up], [[0, 0], Right], [[0, 0], Left], [[1, 0], Up], ...]
|
98
|
+
end
|
99
|
+
def to_chains target
|
100
|
+
target.map{ |pos| Chain.new pos.first, pos.last, gen_chain_length }
|
101
|
+
# e.g., [Chain#0x000, Chain#0x001, ...]
|
102
|
+
end
|
103
|
+
def gen_chain_length
|
104
|
+
@option[:invoke] + (maybe ? 0 : rand(@option[:invoke_max] - @option[:invoke] + 1))
|
105
|
+
end
|
106
|
+
def strip_duplicated_chain target
|
107
|
+
# target.uniq never works for non-num nor non-string :(
|
108
|
+
# target.sort.inject([]){ |r, i| r.last == i ? r : r<<i }
|
109
|
+
target.uniq_by{|i|i}
|
110
|
+
end
|
111
|
+
def strip_out_bounded_chain target
|
112
|
+
target.select{ |chain| chain.bound_ok? @option[:width], @option[:height] }
|
113
|
+
end
|
114
|
+
def strip_overflow_chain target
|
115
|
+
target.select{ |chain|
|
116
|
+
xs = Hash.new 0
|
117
|
+
chain.to_a.transpose.first.each{ |x| xs[x] += 1 }
|
118
|
+
xs.all?{ |pair|
|
119
|
+
x, count = pair
|
120
|
+
height(@temp_result_map[x]) + count <= @option[:height]
|
121
|
+
}
|
122
|
+
}
|
123
|
+
end
|
124
|
+
def strip_floating_chain target
|
125
|
+
target.select{ |chain|
|
126
|
+
chain.all?{ |pos|
|
127
|
+
x, y = pos # if the direct is up, then it's impossible floating.
|
128
|
+
chain.up? || !@temp_result_map[x][0...y].include?(0)
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
def height column; @option[:height] - column.count(0); end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'facets' # for Array#deep_clone
|
4
|
+
# require 'ludy/ludy_ext' # for Kernel#curry
|
5
|
+
|
6
|
+
module PuzzleGenerator
|
7
|
+
Up, Right, Left = (0..2).to_a
|
8
|
+
DefaultOption = {:width => 6, :height => 10, :level => 4, :colors => 4,
|
9
|
+
:invoke => 3, :invoke_max => 5, :timeout => 5}
|
10
|
+
class GenerationFailed < RuntimeError; end
|
11
|
+
class << self
|
12
|
+
attr_writer :debug
|
13
|
+
def debug; @debug || false; end
|
14
|
+
end
|
15
|
+
|
16
|
+
module DisplayMap
|
17
|
+
attr_reader :result_map
|
18
|
+
def display_map
|
19
|
+
result_map.transpose.reverse_each{ |row| puts row.map{ |color| '%2d' % color }.join(' ') }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module MapUtils
|
24
|
+
def make_map_array; (Array.new(@option[:width])).map{ [0]*@option[:height] }; end
|
25
|
+
def resolve_map result_map = @result_map, maps = @maps
|
26
|
+
result_map.replace maps.inject(make_map_array){ |result, map|
|
27
|
+
combine_map result, map
|
28
|
+
}
|
29
|
+
end
|
30
|
+
def combine_map result, map
|
31
|
+
result.zip(map.to_a).map{ |columns|
|
32
|
+
orig, last = columns
|
33
|
+
orig.combine last
|
34
|
+
}
|
35
|
+
end
|
36
|
+
def check_answer_correctness result_map = @result_map
|
37
|
+
map = Map.new @option.merge(:data => result_map.deep_clone)
|
38
|
+
drop_blocks map # because of answer is stripped
|
39
|
+
|
40
|
+
@chained = true
|
41
|
+
while @chained
|
42
|
+
@chained = false
|
43
|
+
destory_chains map
|
44
|
+
drop_blocks map
|
45
|
+
end
|
46
|
+
@chained = nil
|
47
|
+
|
48
|
+
map.all?{ |i| i == 0 }
|
49
|
+
end
|
50
|
+
def destory_chains map
|
51
|
+
map.each_column_with_index{ |column, x|
|
52
|
+
column.each_with_index{ |value, y|
|
53
|
+
next if value == 0
|
54
|
+
|
55
|
+
left = check_left_chain map, x, y
|
56
|
+
right = check_right_chain map, x, y
|
57
|
+
up = check_up_chain map, x, y
|
58
|
+
down = check_down_chain map, x, y
|
59
|
+
|
60
|
+
# left.fill 0 unless left.nil?
|
61
|
+
# right.fill 0 unless right.nil?
|
62
|
+
# up.fill 0 unless up.nil?
|
63
|
+
# down.fill 0 unless down.nil?
|
64
|
+
left.size.times{ |offset|
|
65
|
+
map[x-offset, y] = 0
|
66
|
+
} unless left.nil?
|
67
|
+
right.size.times{ |offset|
|
68
|
+
map[x+offset, y] = 0
|
69
|
+
} unless right.nil?
|
70
|
+
up.size.times{ |offset|
|
71
|
+
map[x, y+offset] = 0
|
72
|
+
} unless up.nil?
|
73
|
+
down.size.times{ |offset|
|
74
|
+
map[x, y-offset] = 0
|
75
|
+
} unless down.nil?
|
76
|
+
|
77
|
+
@chained ||= left || right || up || down
|
78
|
+
}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
def drop_blocks map
|
82
|
+
map.each_column{ |column| column.map!{ |i| i==0 ? nil : i }.compact!.pad!(@option[:height], 0) }
|
83
|
+
end
|
84
|
+
def check_left_chain map, x, y
|
85
|
+
# this should be rewrited
|
86
|
+
return nil if map[x, y] == 0
|
87
|
+
left = x - @option[:invoke] + 1
|
88
|
+
return nil if left < 0
|
89
|
+
# chain = map[left..x, y]
|
90
|
+
# chain if chain.all?{ |i| i == map[x, y] }
|
91
|
+
# map[0..x, y].inject([]){ |result, value| result << value if value == map[x, y] }
|
92
|
+
do_check_chain map[0..x, y].reverse, map[x, y]
|
93
|
+
end
|
94
|
+
def check_right_chain map, x, y
|
95
|
+
return nil if map[x, y] == 0
|
96
|
+
right = x + @option[:invoke] - 1
|
97
|
+
return nil if right >= @option[:width]
|
98
|
+
# chain = map[x..right, y]
|
99
|
+
# chain if chain.all?{ |i| i == map[x, y] }
|
100
|
+
do_check_chain map[x...@option[:width], y], map[x, y]
|
101
|
+
end
|
102
|
+
def check_up_chain map, x, y
|
103
|
+
return nil if map[x, y] == 0
|
104
|
+
up = y + @option[:invoke] - 1
|
105
|
+
return nil if up >= @option[:height]
|
106
|
+
# chain = map[x, y..up]
|
107
|
+
# chain if chain.all?{ |i| i == map[x, y] }
|
108
|
+
do_check_chain map[x, y...@option[:height]], map[x, y]
|
109
|
+
end
|
110
|
+
def check_down_chain map, x, y
|
111
|
+
return nil if map[x, y] == 0
|
112
|
+
down = y - @option[:invoke] - 1
|
113
|
+
return nil if down < 0
|
114
|
+
# chain = map[x, down..y]
|
115
|
+
# chain if chain.all?{ |i| i == map[x, y] }
|
116
|
+
do_check_chain map[x, 0..y].reverse, map[x, y]
|
117
|
+
end
|
118
|
+
# def check_chain target_color, result, value; result << value if value == target_color; end
|
119
|
+
def do_check_chain target, target_color
|
120
|
+
# target.inject([], &method(:check_chain).curry[target_color])
|
121
|
+
chain = target.inject([]){ |result, value|
|
122
|
+
if value == target_color
|
123
|
+
result << value
|
124
|
+
else
|
125
|
+
break result
|
126
|
+
end
|
127
|
+
}
|
128
|
+
chain.size >= @option[:invoke] ? chain : nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), 'misc')
|
3
|
+
require File.join(File.dirname(__FILE__), 'chained_map')
|
4
|
+
require File.join(File.dirname(__FILE__), 'colored_map')
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'facets' # for Array#rotate
|
8
|
+
require 'facets/timer'
|
9
|
+
|
10
|
+
module PuzzleGenerator
|
11
|
+
|
12
|
+
class Puzzle
|
13
|
+
include DisplayMap
|
14
|
+
attr_reader :tried_times, :tried_duration
|
15
|
+
def initialize option = {}
|
16
|
+
@option = DefaultOption.merge option
|
17
|
+
@tried_times, @tried_duration = [0, 0], [0, 0]
|
18
|
+
end
|
19
|
+
def generate
|
20
|
+
raw_colors = (1..@option[:colors]).to_a
|
21
|
+
step_colors = raw_colors.rotate
|
22
|
+
|
23
|
+
make_chain
|
24
|
+
make_color raw_colors
|
25
|
+
until @result_color.result_map.kind_of? Array
|
26
|
+
if step_colors == raw_colors
|
27
|
+
make_chain
|
28
|
+
make_color raw_colors
|
29
|
+
step_colors = raw_colors.rotate
|
30
|
+
else
|
31
|
+
make_color step_colors
|
32
|
+
step_colors.rotate!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@result_map = @result_color.result_map
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
Chain, Color = 0, 1
|
41
|
+
def make_chain
|
42
|
+
begin
|
43
|
+
@result_chain =
|
44
|
+
PuzzleGenerator::generate(
|
45
|
+
@option[:timeout] - @tried_duration[Chain]){ ChainedMap.new @option }
|
46
|
+
ensure
|
47
|
+
update_info Chain, LastTriedInfo
|
48
|
+
end
|
49
|
+
end
|
50
|
+
def make_color colors
|
51
|
+
start = Time.now
|
52
|
+
begin
|
53
|
+
@result_color = ColoredMap.new @result_chain, colors
|
54
|
+
ensure
|
55
|
+
update_info Color, {:tried_times => 1, :tried_duration => Time.now - start}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
def update_info index, info
|
59
|
+
@tried_times[index] += info[:tried_times]
|
60
|
+
@tried_duration[index] += info[:tried_duration]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
# alpha at 2007.10.14
|
3
|
+
|
4
|
+
require File.join(File.dirname(__FILE__), 'puzzle_generator', 'puzzle')
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'facets' # for Hash#reverse_merge
|
8
|
+
require 'facets/timer'
|
9
|
+
|
10
|
+
module PuzzleGenerator
|
11
|
+
|
12
|
+
def self.generate_chained_map option = {}; generate_klass ChainedMap, option; end
|
13
|
+
def self.generate_klass klass, option = {}
|
14
|
+
option.reverse_merge! :timeout => 5
|
15
|
+
generate(option[:timeout]){ klass.new option }
|
16
|
+
end
|
17
|
+
|
18
|
+
LastTriedInfo = {}
|
19
|
+
def self.generate timeout = 5, &generator
|
20
|
+
timer = Timer.new(timeout).start
|
21
|
+
tried_times = 1
|
22
|
+
begin
|
23
|
+
result = generator.call
|
24
|
+
until result.result_map.kind_of? Array
|
25
|
+
tried_times += 1
|
26
|
+
result = generator.call
|
27
|
+
end
|
28
|
+
ensure
|
29
|
+
timer.stop
|
30
|
+
LastTriedInfo.merge! :tried_times => tried_times, :tried_duration => timer.total_time
|
31
|
+
end
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/ludy.gemspec
CHANGED
@@ -18,7 +18,7 @@ require 'rubygems'
|
|
18
18
|
|
19
19
|
spec = Gem::Specification.new{|s|
|
20
20
|
s.name = 'ludy'
|
21
|
-
s.version = '0.0.
|
21
|
+
s.version = '0.0.8'
|
22
22
|
s.author = 'Lin Jen-Shin(a.k.a. godfat)'
|
23
23
|
s.email = 'strip number: 135godfat7911@246godfat.890org'
|
24
24
|
s.homepage = 'http://ludy.rubyforge.org/'
|
data/test/tc_ludy_ext.rb
CHANGED
@@ -76,6 +76,12 @@ class TestLudyExt < Test::Unit::TestCase
|
|
76
76
|
assert_equal (0..4).to_a, lambda{|a,b,c,d,e|[a,b,c,d,e]}.curry[0][1][2][3][4]
|
77
77
|
end
|
78
78
|
|
79
|
+
def test_symbol_curry
|
80
|
+
a = [1,2,3]
|
81
|
+
assert_equal nil, a.find(&:==.curry[0])
|
82
|
+
assert_equal 2, a.find(&:==.curry[2])
|
83
|
+
end
|
84
|
+
|
79
85
|
def test_proc_chain
|
80
86
|
f1 = lambda{|v| v+1}
|
81
87
|
assert_equal 5, f1[4]
|
data/test/test_puzzle.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'puzzle_generator')
|
3
|
+
|
4
|
+
PuzzleGenerator.debug = true
|
5
|
+
p = PuzzleGenerator::Puzzle.new :level => 15, :timeout => 600, :invoke_max => 5
|
6
|
+
begin
|
7
|
+
p.generate
|
8
|
+
p.display_map
|
9
|
+
rescue Timeout::Error
|
10
|
+
puts 'Timeout!!'
|
11
|
+
ensure
|
12
|
+
p p.tried_times
|
13
|
+
p p.tried_duration
|
14
|
+
end
|
15
|
+
|
16
|
+
# level => 7
|
17
|
+
# 0 0 0 0 0 0
|
18
|
+
# 0 0 0 0 0 0
|
19
|
+
# 0 0 3 0 0 0
|
20
|
+
# 0 0 1 0 0 0
|
21
|
+
# 1 3 4 0 0 0
|
22
|
+
# 2 1 1 0 0 0
|
23
|
+
# 2 3 2 0 0 0
|
24
|
+
# 3 4 3 0 0 0
|
25
|
+
# 3 1 2 4 0 0
|
26
|
+
# 2 4 2 1 0 0
|
27
|
+
|
28
|
+
# level => 15
|
29
|
+
# 0 0 1 0 0 0
|
30
|
+
# 2 0 2 1 0 0
|
31
|
+
# 1 0 4 1 0 0
|
32
|
+
# 2 0 3 3 1 0
|
33
|
+
# 2 4 1 4 4 0
|
34
|
+
# 3 2 4 2 2 1
|
35
|
+
# 4 1 1 3 3 2
|
36
|
+
# 3 3 2 3 4 3
|
37
|
+
# 3 1 3 4 1 2
|
38
|
+
# 2 3 4 1 4 2
|
39
|
+
|
40
|
+
# level => 7
|
41
|
+
# 0 0 0 0 0 0
|
42
|
+
# 0 0 0 0 0 0
|
43
|
+
# 0 0 0 0 0 0
|
44
|
+
# 0 0 0 0 0 0
|
45
|
+
# 0 0 1 0 0 0
|
46
|
+
# 0 0 2 2 0 0
|
47
|
+
# 0 1 3 4 4 0
|
48
|
+
# 1 2 4 1 1 0
|
49
|
+
# 3 3 2 2 3 1
|
50
|
+
# 2 2 3 3 4 3
|
51
|
+
# [20, 9]
|
52
|
+
# [1.630587, 0.151272]
|
53
|
+
|
54
|
+
# level => 10
|
55
|
+
# 0 0 1 1 0 0
|
56
|
+
# 0 0 1 2 0 0
|
57
|
+
# 0 0 2 3 0 0
|
58
|
+
# 0 0 2 3 0 0
|
59
|
+
# 0 0 3 4 0 0
|
60
|
+
# 0 1 4 1 0 0
|
61
|
+
# 0 2 1 3 4 0
|
62
|
+
# 2 2 2 2 1 0
|
63
|
+
# 2 3 1 3 2 4
|
64
|
+
# 4 4 1 4 3 2
|
65
|
+
# [28, 1]
|
66
|
+
# [3.26917, 0.020366]
|
67
|
+
|
68
|
+
# level => 12
|
69
|
+
# 0 2 1 0 0 0
|
70
|
+
# 0 2 2 0 0 0
|
71
|
+
# 0 3 3 1 0 0
|
72
|
+
# 0 4 3 2 0 0
|
73
|
+
# 0 4 4 2 0 4
|
74
|
+
# 0 1 4 3 1 1
|
75
|
+
# 0 4 1 1 2 1
|
76
|
+
# 0 3 2 2 1 2
|
77
|
+
# 0 3 4 4 3 1
|
78
|
+
# 2 3 4 3 4 1
|
79
|
+
# [68, 1]
|
80
|
+
# [6.194671, 0.055699]
|
81
|
+
|
82
|
+
# level => 13
|
83
|
+
# 0 3 1 0 0 0
|
84
|
+
# 0 2 1 1 0 0
|
85
|
+
# 0 3 3 2 1 2
|
86
|
+
# 0 3 2 1 1 4
|
87
|
+
# 0 4 2 3 3 4
|
88
|
+
# 0 1 3 3 2 1
|
89
|
+
# 0 4 2 4 3 1
|
90
|
+
# 0 4 2 1 4 2
|
91
|
+
# 0 3 1 4 4 1
|
92
|
+
# 2 3 2 2 1 1
|
93
|
+
# [10, 1]
|
94
|
+
# [1.97002, 0.030499]
|
95
|
+
|
96
|
+
# level => 7
|
97
|
+
# 0 0 0 0 0 0
|
98
|
+
# 0 0 0 2 0 0
|
99
|
+
# 0 0 0 3 0 0
|
100
|
+
# 0 0 0 4 0 0
|
101
|
+
# 0 0 0 1 0 0
|
102
|
+
# 0 0 0 2 0 0
|
103
|
+
# 0 1 1 3 0 0
|
104
|
+
# 0 2 2 4 3 0
|
105
|
+
# 0 4 4 3 4 3
|
106
|
+
# 1 2 2 3 1 1
|
107
|
+
# [53, 17]
|
108
|
+
# [4.614123, 0.264156]
|
109
|
+
|
110
|
+
# level => 14
|
111
|
+
# 0 0 0 0 0 0
|
112
|
+
# 0 0 1 0 0 0
|
113
|
+
# 1 0 4 0 2 0
|
114
|
+
# 1 0 2 4 3 0
|
115
|
+
# 2 1 3 2 3 0
|
116
|
+
# 2 3 1 4 4 1
|
117
|
+
# 3 1 4 3 3 2
|
118
|
+
# 2 4 1 4 3 2
|
119
|
+
# 3 2 2 1 1 3
|
120
|
+
# 2 4 4 1 1 2
|
121
|
+
# [123, 1]
|
122
|
+
# [14.940386, 0.077763]
|
123
|
+
|
124
|
+
# level => 15
|
125
|
+
# 0 0 0 0 1 0
|
126
|
+
# 0 0 2 0 2 0
|
127
|
+
# 0 0 1 0 3 0
|
128
|
+
# 0 4 4 1 4 1
|
129
|
+
# 2 2 4 2 3 3
|
130
|
+
# 3 2 2 3 1 1
|
131
|
+
# 3 3 1 4 4 2
|
132
|
+
# 4 3 1 3 4 2
|
133
|
+
# 2 4 2 1 1 3
|
134
|
+
# 3 3 1 1 4 2
|
135
|
+
# [3650, 17]
|
136
|
+
# [464.755704, 1.220837]
|
137
|
+
|
138
|
+
# level => 15
|
139
|
+
# 0 2 0 0 1 0
|
140
|
+
# 0 4 2 1 3 0
|
141
|
+
# 4 3 3 2 2 0
|
142
|
+
# 2 4 3 3 2 0
|
143
|
+
# 1 2 2 3 3 0
|
144
|
+
# 1 1 4 2 3 0
|
145
|
+
# 2 1 3 4 16 0
|
146
|
+
# 1 2 2 1 3 1
|
147
|
+
# 4 1 4 4 2 3
|
148
|
+
# 1 1 3 4 1 1
|
149
|
+
# [1626, 1]
|
150
|
+
# [159.004594, 0.035997]
|
151
|
+
|
152
|
+
# level => 17 => broken...
|
153
|
+
# 0 2 0 1 1 0
|
154
|
+
# 0 4 2 2 3 0
|
155
|
+
# 4 <3 3 3> 2 1
|
156
|
+
# 2 4 3 3 2 2
|
157
|
+
# 1 2 2 2 3 3
|
158
|
+
# 1 1 4 4 3 2
|
159
|
+
# 2 1 3 1 1 2
|
160
|
+
# 1 2 2 1 3 1
|
161
|
+
# 4 1 4 4 2 3
|
162
|
+
# 1 1 3 4 1 1
|
163
|
+
# hacked from above XD
|
164
|
+
|
165
|
+
# level => 15
|
166
|
+
# 1 0 0 0 0 0
|
167
|
+
# 3 1 1 0 3 0
|
168
|
+
# 4 2 4 4 1 0
|
169
|
+
# 4 3 2 2 4 0
|
170
|
+
# 1 3 2 3 2 0
|
171
|
+
# 1 2 3 1 2 0
|
172
|
+
# 2 4 2 4 3 0
|
173
|
+
# 1 3 3 2 3 0
|
174
|
+
# 1 3 1 1 16 4
|
175
|
+
# 4 1 2 2 3 1
|
176
|
+
# [1139, 1]
|
177
|
+
# [115.270095, 0.031929]
|
metadata
CHANGED
@@ -1,33 +1,26 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: ludy
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-10-08 00:00:00 +08:00
|
8
|
-
summary: Aims to extend Ruby standard library, providing some useful tools that's not existed in the standard library.
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: "strip number: 135godfat7911@246godfat.890org"
|
12
|
-
homepage: http://ludy.rubyforge.org/
|
13
|
-
rubyforge_project:
|
14
|
-
description:
|
15
|
-
autorequire: ludy
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: false
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
version: 0.0.8
|
25
5
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
6
|
authors:
|
30
7
|
- Lin Jen-Shin(a.k.a. godfat)
|
8
|
+
autorequire: ludy
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2007-12-06 00:00:00 +08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: "strip number: 135godfat7911@246godfat.890org"
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
31
24
|
files:
|
32
25
|
- lib/lib
|
33
26
|
- lib/lib/amulti.rb
|
@@ -46,6 +39,14 @@ files:
|
|
46
39
|
- lib/ludy/y_combinator.rb
|
47
40
|
- lib/ludy/z_combinator.rb
|
48
41
|
- lib/ludy.rb
|
42
|
+
- lib/puzzle_generator
|
43
|
+
- lib/puzzle_generator/chain.rb
|
44
|
+
- lib/puzzle_generator/chained_map.rb
|
45
|
+
- lib/puzzle_generator/colored_map.rb
|
46
|
+
- lib/puzzle_generator/map.rb
|
47
|
+
- lib/puzzle_generator/misc.rb
|
48
|
+
- lib/puzzle_generator/puzzle.rb
|
49
|
+
- lib/puzzle_generator.rb
|
49
50
|
- test/tc_callstack.rb
|
50
51
|
- test/tc_curry.rb
|
51
52
|
- test/tc_dice.rb
|
@@ -56,6 +57,7 @@ files:
|
|
56
57
|
- test/tc_variable.rb
|
57
58
|
- test/tc_y_combinator.rb
|
58
59
|
- test/tc_z_combinator.rb
|
60
|
+
- test/test_puzzle.rb
|
59
61
|
- test/ts_ludy.rb
|
60
62
|
- CHANGES
|
61
63
|
- lib
|
@@ -64,17 +66,31 @@ files:
|
|
64
66
|
- NOTICE
|
65
67
|
- README
|
66
68
|
- test
|
67
|
-
|
68
|
-
|
69
|
+
has_rdoc: false
|
70
|
+
homepage: http://ludy.rubyforge.org/
|
71
|
+
post_install_message:
|
69
72
|
rdoc_options: []
|
70
73
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: "0"
|
87
|
+
version:
|
77
88
|
requirements: []
|
78
89
|
|
79
|
-
|
80
|
-
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 0.9.5
|
92
|
+
signing_key:
|
93
|
+
specification_version: 2
|
94
|
+
summary: Aims to extend Ruby standard library, providing some useful tools that's not existed in the standard library.
|
95
|
+
test_files:
|
96
|
+
- test/ts_ludy.rb
|