ludy 0.0.7 → 0.0.8
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/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
|