ifmapper 0.5 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
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