ifmapper 0.5 → 0.6

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.
Binary file
@@ -0,0 +1,39 @@
1
+ /* XPM */
2
+ static char * room_left_xpm[] = {
3
+ "32 32 4 1",
4
+ " c #000000",
5
+ ". c #404040",
6
+ "+ c #7F7F7F",
7
+ "@ c #FFFFFF",
8
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
9
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
10
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
11
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
12
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
13
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
14
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
15
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
16
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
17
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
18
+ "@@@@@@@@.++++++++++++++.@@@@@@@@",
19
+ "@@@@@@@@+@@@@@@@@@@@@@@+@@@@@@@@",
20
+ "@@@@@@@@+@@@@@@@@@@@@@@+@@@@@@@@",
21
+ "@@@@@@@@+@@@@@@@@@@@@@@+@@@@@@@@",
22
+ "@ @@@@@@@@@@@@@@+@@@@@@@@",
23
+ "@ @@@@@@@@@@@@@@+@@@@@@@@",
24
+ "@ @@@@@@@@@@@@@@+@@@@@@@@",
25
+ "@ @@@@@@@@@@@@@@+@@@@@@@@",
26
+ "@@@@@@@@+@@@@@@@@@@@@@@+@@@@@@@@",
27
+ "@@@@@@@@+@@@@@@@@@@@@@@+@@@@@@@@",
28
+ "@@@@@@@@+@@@@@@@@@@@@@@+@@@@@@@@",
29
+ "@@@@@@@@.++++++++++++++.@@@@@@@@",
30
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
31
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
32
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
33
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
34
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
35
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
36
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
37
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
38
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@",
39
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"};
@@ -0,0 +1,243 @@
1
+
2
+
3
+
4
+ #
5
+ # A class used for path finding.
6
+ # This is largely based on C++ code by Justin Heyes-Jones, albeit simplified
7
+ #
8
+ class AStar
9
+
10
+ attr :start, :goal
11
+ attr_reader :state
12
+ attr :successors
13
+ attr :open_list
14
+ attr :closed_list
15
+
16
+ SEARCH_STATE_NOT_INITIALIZED = 0
17
+ SEARCH_STATE_SEARCHING = 1
18
+ SEARCH_STATE_SUCCEEDED = 2
19
+ SEARCH_STATE_FAILED = 3
20
+
21
+ def open_list
22
+ puts "Open List:"
23
+ @open_list.each { |n|
24
+ p n
25
+ }
26
+ puts "Closed List # of nodes: #{@open_list.size}"
27
+ end
28
+
29
+ def closed_list
30
+ puts "Closed List:"
31
+ @closed_list.each { |n|
32
+ p n
33
+ }
34
+ puts "Closed List # of nodes: #{@closed_list.size}"
35
+ end
36
+
37
+
38
+ class Node
39
+ attr_accessor :parent, :child
40
+ attr_accessor :h, :g, :f
41
+
42
+ attr_reader :info
43
+
44
+ def <=>(b)
45
+ return -1 if @f > b.f
46
+ return 0 if @f == b.f
47
+ return 1
48
+ end
49
+
50
+ def inspect
51
+ "AStarNode: #{@f} = #{@g} + #{@h} #{@info.inspect}"
52
+ end
53
+
54
+ def initialize( info = nil )
55
+ @h = @g = @f = 0.0
56
+ @info = info
57
+ end
58
+ end
59
+
60
+ # sets start and end goals and initializes A* search
61
+ def goals( start, goal )
62
+ @start = Node.new( start )
63
+ @start.g = 0.0
64
+ @start.h = start.distance_estimate( goal )
65
+ @start.f = @start.h
66
+
67
+ @open_list = []
68
+ @open_list.push( @start )
69
+ @closed_list = []
70
+
71
+ @goal = Node.new( goal )
72
+
73
+ @state = SEARCH_STATE_SEARCHING
74
+ end
75
+
76
+ # Advances one search step
77
+ def search_step
78
+ if @open_list.empty?
79
+ @state = SEARCH_STATE_FAILED
80
+ return @state
81
+ end
82
+
83
+ # Pop the best node (the one with the lowest f)
84
+ n = @open_list.pop
85
+
86
+ # Check for the goal. Once we pop that, we are done
87
+ if n.info.is_goal?( @goal.info )
88
+ # The user is going to use the Goal Node he passed in
89
+ # so copy the parent pointer of n
90
+ @goal.parent = n.parent
91
+ # Special case is that the goal was passed in as the start state
92
+ # so handle that here
93
+ if n != @start
94
+ child = @goal
95
+ parent = @goal.parent
96
+
97
+ while child != @start
98
+ parent.child = child
99
+ child = parent
100
+ parent = parent.parent
101
+ end
102
+ end
103
+ @state = SEARCH_STATE_SUCCEEDED
104
+ return @state
105
+ else
106
+ # Not goal, get successors
107
+ n.info.successors( self, n.parent )
108
+ @successors.each { |s|
109
+ newg = n.g + n.info.cost( s.info )
110
+
111
+ # Now, we need to find out if this node is on the open or close lists
112
+ # If it is, but the node that is already on them is better (lower g)
113
+ # then we can forget about this successor
114
+ open = @open_list.find { |e| e.info.is_same?( s.info ) }
115
+ # State in open list is cheaper than this successor
116
+ next if open and open.g <= newg
117
+
118
+ closed = @closed_list.find { |e| e.info.is_same?( s.info ) }
119
+ # We found a cheaper state in closed list
120
+ next if closed and closed.g <= newg
121
+
122
+ # This node is the best node so far so let's keep it and set up
123
+ # its A* specific data
124
+ s.parent = n
125
+ s.g = newg
126
+ s.h = s.info.distance_estimate( @goal.info )
127
+ s.f = s.g + s.h
128
+
129
+ # Remove succesor from closed list if it was on it
130
+ @closed_list.delete(closed) if closed
131
+ # Remove succesor from open list if it was on it
132
+ @open_list.delete(open) if open
133
+ @open_list.push(s)
134
+ # Make sure open list stays sorted based on f
135
+ @open_list.sort!
136
+ }
137
+ @closed_list.push(n)
138
+ @successors.clear
139
+ end
140
+ return @state
141
+ end
142
+
143
+ def add_successor( info )
144
+ @successors << Node.new(info)
145
+ end
146
+
147
+ # return solution as an path (ie. an array of [x,y] coordinates)
148
+ def path
149
+ p = []
150
+ curr = @start
151
+ while curr
152
+ p << [curr.info.x, curr.info.y]
153
+ curr = curr.child
154
+ end
155
+ return p
156
+ end
157
+
158
+ def initialize
159
+ @state = SEARCH_STATE_NOT_INITIALIZED
160
+ @successors = []
161
+ end
162
+ end
163
+
164
+
165
+ #
166
+ # Simple class used as a
167
+ #
168
+ class MapNode
169
+ attr_reader :x, :y
170
+ @@pmap = nil
171
+
172
+ def self.map(pmap)
173
+ @@pmap = pmap
174
+ end
175
+
176
+ def initialize(x, y)
177
+ @x = x
178
+ @y = y
179
+ end
180
+
181
+ def is_same?(node)
182
+ return true if node and @x == node.x and @y == node.y
183
+ return false
184
+ end
185
+
186
+ def is_goal?( goal )
187
+ return true if @x == goal.x and @y == goal.y
188
+ return false
189
+ end
190
+
191
+ def distance_estimate( goal )
192
+ dx = @x - goal.x
193
+ dy = @y - goal.y
194
+ return dx*dx + dy*dy
195
+ end
196
+
197
+ MAX_SCALE = 9
198
+
199
+ def get_map(x, y)
200
+ if x < 0 or y < 0 or x >= @@pmap.size or y >= @@pmap[0].size
201
+ return MAX_SCALE
202
+ else
203
+ t = @@pmap[x][y]
204
+ return MAX_SCALE if t.kind_of?(Room)
205
+ return MAX_SCALE-1 if t.kind_of?(Connection)
206
+ return 1
207
+ end
208
+ end
209
+
210
+ def successors( astar, parent = nil )
211
+ info = nil
212
+ info = parent.info if parent
213
+ Room::DIR_TO_VECTOR.each_value { |x, y|
214
+ new_node = MapNode.new(@x + x, @y + y)
215
+ if get_map( new_node.x, new_node.y ) < MAX_SCALE and
216
+ not new_node.is_same?(info)
217
+ astar.add_successor(new_node)
218
+ end
219
+ }
220
+ end
221
+
222
+ def inspect
223
+ "pos: #{@x}, #{@y}"
224
+ end
225
+
226
+ def to_s
227
+ inspect
228
+ end
229
+
230
+ def cost( successor )
231
+ g = get_map(@x,@y)
232
+
233
+ # dx = (@x - successor.x).abs
234
+ # dy = (@y - successor.y).abs
235
+ # if dx + dy == 1
236
+ # g += 0 # straight move
237
+ # else
238
+ # g += 4 # diagonal move
239
+ # end
240
+ return g
241
+ end
242
+
243
+ end
@@ -55,27 +55,27 @@ class FXConnection < Connection
55
55
  [ @selected, @type, @dir, @roomA, @roomB, @exitAtext, @exitBtext ]
56
56
  end
57
57
 
58
+ #
59
+ # Change selection state of connection. If Connection Properties
60
+ # window is open, copy the connection data to it.
61
+ #
58
62
  def selected=(value)
59
- if value and @@win
60
- @@win.copy_from(self)
61
- end
63
+ @@win.copy_from(self) if value and @@win
62
64
  @selected = value
63
65
  end
64
66
 
67
+ #
68
+ # Change direction of connection
69
+ #
65
70
  def toggle_direction
66
71
  # If a self-loop, we cannot change dir
67
- if @roomA == @roomB
68
- return if @roomA.exits.index(self) == @roomA.exits.rindex(self)
69
- end
72
+ return if @roomA == @roomB and
73
+ @roomA.exits.index(self) == @roomA.exits.rindex(self)
70
74
 
71
75
  @dir += 1
72
- if @dir > BtoA
73
- @dir = BOTH
74
- end
76
+ @dir = BOTH if @dir > BtoA
75
77
 
76
- if @@win
77
- @@win.copy_from(self)
78
- end
78
+ @@win.copy_from(self) if @@win
79
79
  end
80
80
 
81
81
  #
@@ -107,6 +107,75 @@ class FXConnection < Connection
107
107
  end
108
108
  end
109
109
 
110
+ #
111
+ # Main draw function
112
+ #
113
+ def draw(dc, zoom, opt)
114
+ if @selected
115
+ dc.foreground = 'yellow'
116
+ elsif @failed
117
+ dc.foreground = 'red'
118
+ else
119
+ dc.foreground = opt['Arrow Color']
120
+ end
121
+
122
+ draw_exit_text(dc, zoom)
123
+ if @type == SPECIAL
124
+ dc.lineStyle = LINE_ONOFF_DASH
125
+ else
126
+ dc.lineStyle = LINE_SOLID
127
+ end
128
+ if pts.size > 0
129
+ draw_complex(dc, zoom, opt)
130
+ else
131
+ draw_simple(dc, zoom)
132
+ end
133
+ end
134
+
135
+
136
+ def properties(map, event = nil)
137
+ if not @@win
138
+ @@win = FXConnectionDialogBox.new(map, self, event)
139
+ else
140
+ @@win.map = map
141
+ end
142
+ @@win.copy_from(self)
143
+ @@win.show
144
+ return
145
+ end
146
+
147
+ protected
148
+
149
+ RAD_45 = 45 * Math::PI / 180
150
+ SIN_45 = Math.sin(RAD_45)
151
+ COS_45 = Math.cos(RAD_45)
152
+
153
+ def _arrow_info( x1, y1, x2, y2, zoom = 1.0 )
154
+ pt1 = []
155
+ dir = 0
156
+ if @dir == AtoB
157
+ dir = @roomB.exits.rindex(self)
158
+ pt1 = [ x2, y2 ]
159
+ else
160
+ dir = @roomA.exits.index(self)
161
+ pt1 = [ x1, y1 ]
162
+ end
163
+ x, y = Room::DIR_TO_VECTOR[dir]
164
+ arrow_len = 20.0 / Math.sqrt(x * x + y * y)
165
+ x *= arrow_len * zoom
166
+ y *= arrow_len * -zoom
167
+
168
+ d = [
169
+ x * COS_45 + y * SIN_45,
170
+ x * SIN_45 - y * COS_45
171
+ ]
172
+ return pt1, d
173
+ end
174
+
175
+
176
+ #
177
+ # Draw a complex connection as a line path
178
+ #
110
179
  def draw_complex_as_lines(dc, zoom)
111
180
  p = []
112
181
  @pts.each { |pt|
@@ -116,6 +185,9 @@ class FXConnection < Connection
116
185
  return p
117
186
  end
118
187
 
188
+ #
189
+ # Draw a complex connection as a bspline path
190
+ #
119
191
  def draw_complex_as_bspline(dc, zoom)
120
192
  p = []
121
193
  p << [ @pts[0][0] * zoom, @pts[0][1] * zoom ]
@@ -130,6 +202,9 @@ class FXConnection < Connection
130
202
  return dc.drawBSpline( p )
131
203
  end
132
204
 
205
+ #
206
+ # Draw a complex connection
207
+ #
133
208
  def draw_complex(dc, zoom, opt)
134
209
  if opt['Paths as Curves']
135
210
  p = draw_complex_as_bspline(dc, zoom)
@@ -142,6 +217,10 @@ class FXConnection < Connection
142
217
  draw_arrow(dc, zoom, x1, y1, x2, y2)
143
218
  end
144
219
 
220
+ #
221
+ # Draw a simple connection. Simple connections are straight
222
+ # line connections or 'stub' connections.
223
+ #
145
224
  def draw_simple(dc, zoom)
146
225
  dir = @roomA.exits.index(self)
147
226
  x1, y1 = @roomA.corner(self, zoom, dir)
@@ -157,6 +236,7 @@ class FXConnection < Connection
157
236
  dc.drawLine(x1, y1, x2, y2)
158
237
  end
159
238
  else
239
+ # Complex connection in progress or "stub" exit
160
240
  v = FXRoom::DIR_TO_VECTOR[dir]
161
241
  x2, y2 = [ @roomA.x + v[0] * 0.5, @roomA.y + v[1] * 0.5 ]
162
242
  x2 *= WW * zoom
@@ -167,33 +247,10 @@ class FXConnection < Connection
167
247
  end
168
248
  end
169
249
 
170
- RAD_45 = 45 * Math::PI / 180
171
- SIN_45 = Math.sin(RAD_45)
172
- COS_45 = Math.cos(RAD_45)
173
-
174
- def _arrow_info( x1, y1, x2, y2, zoom = 1.0 )
175
- pt1 = []
176
- dir = 0
177
- if @dir == AtoB
178
- dir = @roomB.exits.rindex(self)
179
- pt1 = [ x2, y2 ]
180
- else
181
- dir = @roomA.exits.index(self)
182
- pt1 = [ x1, y1 ]
183
- end
184
- x, y = Room::DIR_TO_VECTOR[dir]
185
- arrow_len = 20.0 / Math.sqrt(x * x + y * y)
186
- x *= arrow_len * zoom
187
- y *= arrow_len * -zoom
188
-
189
- d = [
190
- x * COS_45 + y * SIN_45,
191
- x * SIN_45 - y * COS_45
192
- ]
193
- return pt1, d
194
- end
195
-
196
250
 
251
+ #
252
+ # Draw an arrow at one end of the path
253
+ #
197
254
  def draw_arrow( dc, zoom, x1, y1, x2, y2 )
198
255
  return if @dir == BOTH
199
256
  pt1, d = _arrow_info( x1, y1, x2, y2, zoom )
@@ -208,6 +265,9 @@ class FXConnection < Connection
208
265
  dc.fillPolygon(p)
209
266
  end
210
267
 
268
+ #
269
+ # Draw the connection text next to the arrow ('I', 'O', etc)
270
+ #
211
271
  def draw_text(dc, zoom, x, y, dir, text, arrow)
212
272
  if dir == 7 or dir < 6 and dir != 1
213
273
  if arrow and (dir == 0 or dir == 4)
@@ -230,6 +290,9 @@ class FXConnection < Connection
230
290
  dc.drawText(x, y, text)
231
291
  end
232
292
 
293
+ #
294
+ # Draw any exit text if available (exit text is 'U', 'D', 'I', 'O', etc)
295
+ #
233
296
  def draw_exit_text(dc, zoom)
234
297
  if @exitAtext != 0
235
298
  dir = @roomA.exits.index(self)
@@ -245,39 +308,5 @@ class FXConnection < Connection
245
308
  end
246
309
  end
247
310
 
248
- def draw(dc, zoom, opt)
249
- if @selected
250
- dc.foreground = 'yellow'
251
- elsif @failed
252
- dc.foreground = 'red'
253
- else
254
- dc.foreground = opt['Arrow Color']
255
- end
256
-
257
- draw_exit_text(dc, zoom)
258
- if @type == SPECIAL
259
- dc.lineStyle = LINE_ONOFF_DASH
260
- else
261
- dc.lineStyle = LINE_SOLID
262
- end
263
- if pts.size > 0
264
- draw_complex(dc, zoom, opt)
265
- else
266
- draw_simple(dc, zoom)
267
- end
268
- end
269
-
270
-
271
- def properties(map, event = nil)
272
- if not @@win
273
- @@win = FXConnectionDialogBox.new(map, self, event)
274
- else
275
- @@win.map = map
276
- end
277
- @@win.copy_from(self)
278
- @@win.show
279
- return
280
- end
281
-
282
311
  end
283
312