VBA隨機地牢生成
阿新 • • 發佈:2018-11-10
無聊啊……於是,我想做一個隨機地圖。
但是我很懶,不想做。
但是身體很誠實。
這次是直接在Excel中製作的地圖,但是,VB的執行效率很慢,我程式碼的效率也很慢,導致,一旦地圖長寬稍大,就會出現好幾分鐘才能出現結果的效果。
而且,不能忍的是,隨機崩潰!我至今沒有找到原因在哪。
以下是VBA的程式碼
Sheet1全域性
Option Explicit Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean) If Target.Locked = True Then Cancel = True End If End Sub Private Sub Worksheet_Change(ByVal Target As Range) ' Dim temp_coord As New Coord ' Set temp_coord = world_map.tile_obj(Target.Column, Target.Row) ' Call cell_ctrl.Change_cell(temp_coord.x, temp_coord.y, temp_coord.coord_type) End Sub
類模組Cell_controller
Public Enum ENUM_CELL_COLOR BLACK = 1 WHITE = 2 RED = 3 GREEN = 4 BLUE = 5 YELLOW = 6 PINK = 7 LIGHT_BLUE = 8 DEEP_RED = 9 DEEP_GREEN = 10 DEEP_BLUE = 11 DEEP_YELLOW = 12 DEEP_PINK = 13 DEEP_CYAN = 14 LIGHT_GRAY = 15 DEEP_GRAY = 16 End Enum '宣告延時函式 Private Declare PtrSafe Sub Sleep Lib "kernel32.dll" (ByVal dwMilliseconds As Long) Private is_change_style As Boolean Private m_description As String Private Sub Class_Initialize() ActiveWorkbook.Unprotect Application.ScreenUpdating = True is_change_style = False scroll_select(Nothing) = False End Sub '2. 將地圖顯示出來 Public Function Show_map() 'Set m_map = para_map Sheet1.Rows.Clear If Not is_change_style Then Call Init_style(1) End If Dim grid_x As Integer Dim grid_y As Integer For grid_x = 1 To world_map.map_width Step 1 For grid_y = 1 To world_map.map_height Step 1 Call Change_cell(grid_x, grid_y, world_map.tile(grid_x, grid_y)) DoEvents Next Next End Function '1. 更改表格整體樣式,儘量讓表格以正方形顯示 Public Function Init_style(cell_size As Byte) With Sheet1: If cell_size = 0 Then MsgBox "Cell Size Error!" Exit Function End If ActiveWorkbook.Styles("Normal").Font.name = "宋體" ActiveWorkbook.Styles("Normal").Font.Size = 12 ActiveWorkbook.Styles("Normal").Font.Bold = False ActiveWorkbook.Styles("Normal").Font.Italic = False Application.ScreenUpdating = False 'H=3.5+6*W, 宋體 12 'For i = 1 To world_map.map_width .Rows.RowHeight = (1.88 * cell_size) * 6 + 3.72 .Rows.HorizontalAlignment = xlCenter .Rows.VerticalAlignment = xlCenter 'DoEvents 'Next 'For j = 1 To world_map.map_height .Columns.ColumnWidth = 1.88 * cell_size 'DoEvents 'Next Application.ScreenUpdating = True is_change_style = True End With End Function '修改根據地板型別設定單元格的樣式 Public Function Change_cell(x As Integer, y As Integer, val As ENUM_COORD_TYPE) With Sheet1: 'Excel中,二維座標的順序為: 先縱y,後橫x .Cells(y, x).Value = val Dim color_index As Byte Select Case val Case WALL: color_index = LIGHT_GRAY Case GROUND: color_index = WHITE Case GREEN_ENEMY: color_index = GREEN Case RED_ENEMY: color_index = RED Case BLUE_ENEMY: color_index = BLUE 'TODO: Other Color Index. Case Else: MsgBox "val Error! ((y, x) is (" & x & ", " & y & "))" End Select .Cells(y, x).Interior.ColorIndex = color_index End With End Function '單元格的閃爍效果 '一般情況下,此函式要被迴圈呼叫。 '為了效率問題,避免在迴圈中申請記憶體,所以傳入一個 temp_coord 臨時變數用於迴圈 ' 'coords: 設定那些座標塊需要被閃爍 'flick_rate_ms: 閃爍速率,毫秒為單位 'flick_color: 閃爍顏色 'temp_coord: 用於迴圈的臨時變數 ' 'CHECKIT: 此函式中的兩個Sleep函式很有可能不符合要求,因為Sleep的過程中,無法進行其它過程的執行,除非多執行緒。可能需要利用空轉DoEvents的方式來達到延時目的。 'CHECKIT: 此函式暫未經過測試 Public Function Cells_flick(ByRef coords As Object_vector, flick_rate_ms As Integer, flick_color As ENUM_CELL_COLOR, ByRef temp_coord As Coord) Dim i As Long With Sheet1 For i = 1 To coords.arraysize Step 1 Set temp_coord = coords.element(i) .Cells(temp_coord.y, temp_coord.x).Interior.ColorIndex = flick_color Next End With DoEvents Call Sleep(flick_rate_ms) For i = 1 To coords.arraysize Step 1 Set temp_coord = coords.element(i) Call Cell_style_undo(temp_coord) Next DoEvents Call Sleep(flick_rate_ms) End Function '還原單元格的原本樣式 '以記憶體中world_map的地板型別為標準 'CHECKIT: 此函式未經過測試 Private Function Cell_style_undo(ByRef each_coord As Coord) Call Change_cell(each_coord.x, each_coord.y, world_map.tile(each_coord.x, each_coord.y)) '此處並沒有修改記憶體中的Map End Function 'CHECKIT: 此函式未經過測試 Public Function Cell_move(ByVal src_pos As Coord, ByRef offset_coord As Coord) Dim r1 As Range Dim r2 As Range Set r1 = Sheet1.Cells(src_pos.y, src_pos.x) Set r2 = Sheet1.Cells(src_pos.y + offset_coord.y, src_pos.x + offset_coord.x) Call r1.Copy(r2) Call Cell_style_undo(src_pos) End Function '為一個單元格新增批註 Public Property Let description(ByRef where_cell As Coord, desc As String) Sheet1.Cells(where_cell.y, where_cell.x).AddComment Text:=desc End Property '選定某個單元格 Public Function Select_cell(ByRef where_cell As Coord) Sheet1.Cells(where_cell.y, where_cell.x).Select scroll_select(where_cell) = True End Function '鎖定單元格的選擇 Public Property Let scroll_select(ByRef where_cell As Coord, is_scroll As Boolean) If is_scroll Then Sheet1.ScrollArea = Cells(where_cell.y, where_cell.x).Address(False, False) Else Sheet1.ScrollArea = "" End If End Property '保護單元格 Public Property Let locked_cell(ByRef where_cell As Coord, is_lock As Boolean) Dim locked_cell As Range Set locked_cell = Cells(where_cell.y, where_cell.x) If is_lock Then 'locked_cell.Locked = True ActiveSheet.Protect Else 'locked_cell.Locked = False ActiveSheet.Unprotect End If End Property
類模組Coord
Public Enum ENUM_COORD_TYPE NONE = -1 GROUND = 0 WALL = 1 GREEN_ENEMY = 2 RED_ENEMY = 4 BLUE_ENEMY = 8 End Enum Private m_x As Integer Private m_y As Integer Private m_coord_type As ENUM_COORD_TYPE '座標型別 Private Sub Class_Initialize() m_x = -1 m_y = -1 m_coord_type = NONE End Sub Private Sub Class_Terminate() m_x = -1 m_y = -1 m_coord_type = NONE End Sub Public Property Get x() As Integer x = m_x End Property Public Property Let x(para_x As Integer) m_x = para_x End Property Public Property Get y() As Integer y = m_y End Property Public Property Let y(para_y As Integer) m_y = para_y End Property Public Property Get coord_type() As ENUM_COORD_TYPE coord_type = m_coord_type End Property Public Property Let coord_type(para_type As ENUM_COORD_TYPE) m_coord_type = para_type End Property Public Function Is_Equal(ByRef other_coord As Coord) As Boolean If other_coord.x <> m_x Or other_coord.y <> m_y Or other_coord.coord_type <> m_coord_type Then Is_Equal = False Else Is_Equal = True End If End Function
類模組Graphs_Generator
Private Enum GRAPHS_TYPE
GRAPH_NONE = -1
GRAPH_LINE = 0
GRAPH_CIRCLE = 1
GRAPH_COMMON = 2
GRAPH_RHOMBUS = 3
'... and so on
End Enum
Private m_coords As Object_vector
Private m_graph_type As GRAPHS_TYPE
Private Sub Class_Initialize()
Set m_coords = New Object_vector
m_coords.element_type = "Coord"
m_graph_type = GRAPH_NONE
End Sub
Private Sub Class_Terminate()
Set m_coords = Nothing
m_graph_type = GRAPH_NONE
End Sub
Public Property Get coords() As Object_vector
Set coords = m_coords
End Property
Public Function Get_line(ByRef coord_start As Coord, ByRef coord_end As Coord) As Object_vector
Dim edge_max_x As Integer
Dim edge_max_y As Integer
edge_max_x = world_map.map_width
edge_max_y = world_map.map_height
'兩點組成的向量
Dim dx As Integer
Dim dy As Integer
dx = coord_end.x - coord_start.x
dy = coord_end.y - coord_start.y
'我先要知道dx, dy哪個才是最長的
Dim dx_is_longer As Boolean
Dim longer As Integer
Dim shorter As Integer
longer = dx
shorter = dy
dx_is_longer = True
If Abs(dy) > Abs(dx) Then
longer = dy
shorter = dx
dx_is_longer = False
End If
'最長的那個正負值
Dim each_point_step As Integer
each_point_step = IIf(longer > 0, 1, -1)
' '最短的那個正負值
' Dim each_short_step As Integer
' each_short_step = IIf(short > 0, 1, -1)
'斜率
Dim slope As Double
'slope = CDbl(Abs(shorter) / Abs(longer))
slope = CDbl(shorter / longer)
Dim temp_coord As New Coord
Dim i As Integer
'按longer迴圈,否則會出現“斷鏈”情況
For i = 0 To longer Step each_point_step
temp_coord.coord_type = GROUND
'longer上的點每前進一格,shorter上的點就前進slope格(0 <= slope <= 1)
If dx_is_longer Then
temp_coord.x = i
temp_coord.y = Fix(i * slope)
'temp_coord.y = each_short_step * Abs(i) * slope
Else
temp_coord.y = i
temp_coord.x = Fix(i * slope)
'temp_coord.x = each_short_step * Abs(i) * slope
End If
'應用在實際座標系中
temp_coord.x = coord_start.x + temp_coord.x
temp_coord.y = coord_start.y + temp_coord.y
If temp_coord.x > 1 And temp_coord.x < edge_max_x And temp_coord.y > 1 And temp_coord.y < edge_max_y Then
Call m_coords.Push(temp_coord)
End If
Set temp_coord = Nothing
Next
m_graph_type = GRAPH_LINE
Set Get_line = m_coords
End Function
'畫圓
Public Function Get_circle(ByRef coord_center As Coord, radius As Integer) As Object_vector
Dim edge_max_x As Integer
Dim edge_max_y As Integer
edge_max_x = world_map.map_width
edge_max_y = world_map.map_height
Dim res_circle_coords As New Object_vector
Dim temp_coord As New Coord
Dim grid_x As Integer
Dim grid_y As Integer
For grid_x = coord_center.x - radius To coord_center.x + radius Step 1
For grid_y = coord_center.y - radius To coord_center.y + radius Step 1
If (grid_x > 1 And grid_x < edge_max_x And grid_y > 1 And grid_y < edge_max_y) And ((grid_x - coord_center.x) * (grid_x - coord_center.x) + (grid_y - coord_center.y) * (grid_y - coord_center.y) <= radius * radius) Then
temp_coord.x = grid_x
temp_coord.y = grid_y
temp_coord.coord_type = GROUND
Call res_circle_coords.Push(temp_coord)
Set temp_coord = Nothing
End If
Next
Next
m_graph_type = GRAPH_CIRCLE
Set Get_circle = res_circle_coords
Set m_coords = res_circle_coords
Set res_circle_coords = Nothing
End Function
'畫菱形
Public Function Get_rhombus(ByRef center_coord As Coord, radius As Integer) As Object_vector
Dim edge_max_x As Integer
Dim edge_max_y As Integer
edge_max_x = world_map.map_width
edge_max_y = world_map.map_height
'y = -2|x - r| + 2r - 1
'y = |x - r| + 1
Dim res_coords As New Object_vector
Dim total_coord_count As Long
total_coord_count = 2 * radius * radius - 2 * radius + 1
res_coords.arraysize = total_coord_count
Dim i As Integer
Dim j As Integer
Dim x As Integer
Dim y As Integer
Dim temp_coord As Coord
For i = 1 To (2 * radius - 1) Step 1
For j = 1 To (2 * radius - 2 * Abs(i - radius) - 1) Step 1
x = j + Abs(i - radius) + center_coord.x - radius
y = i + center_coord.y - radius
Set temp_coord = New Coord
temp_coord.x = x
temp_coord.y = y
If (x > 1 And x < edge_max_x And y > 1 And y < edge_max_y) Then
Call res_coords.Push(temp_coord)
End If
Set temp_coord = Nothing
Next
Next
m_graph_type = GRAPH_RHOMBUS
Set m_coords = res_coords
Set Get_rhombus = res_coords
End Function
'最小成本生成樹,Kruskal演算法
'每條線的兩個端點使用ID方式
'第一個ID始終不大於第二個ID
Public Function Get_min_cost_tree(lines As Object_vector, points_count As Integer) As Object_vector
If lines.element_type <> "Shortest_distance" Then
Exit Function
End If
'鄰接矩陣
Dim adjacency_matrix() As Integer
ReDim adjacency_matrix(1 To points_count, 1 To points_count) As Integer
Dim res_lines As New Object_vector
res_lines.element_type = "Shortest_distance"
Dim each_line As New Shortest_distance
Dim i As Integer
i = 1
'生成邊
'一共 points_count 個點,則最小生成樹存在 points_count - 1 條邊
While i < points_count
Set each_line = Find_shortest_distance(lines)
If Not Find_ring(each_line, points_count, adjacency_matrix) Then
Call res_lines.Push(each_line)
i = i + 1
adjacency_matrix(each_line.room1_id, each_line.room2_id) = 1
adjacency_matrix(each_line.room2_id, each_line.room1_id) = 1
End If
Set each_line = Nothing
Wend
m_graph_type = GRAPH_COMMON
Set Get_min_cost_tree = res_lines
Set res_lines = Nothing
End Function
'尋找最短的那條邊
Private Function Find_shortest_distance(ByRef lines As Object_vector)
Dim shortest As Long
shortest = &H7FFFFFFF
Dim shortest_group As New Shortest_distance
Dim shortest_group_index As Long
Dim i As Long
For i = 1 To lines.arraysize Step 1
If shortest > lines.element(i).distance Then
shortest = lines.element(i).distance
Set shortest_group = lines.element(i)
shortest_group_index = i
End If
Next
Set Find_shortest_distance = shortest_group
Set shortest_group = Nothing
Call lines.Delete(CLng(shortest_group_index))
End Function
'判斷新加入的邊是否構成了環
Public Function Find_ring(new_line As Shortest_distance, points_count As Integer, matrix() As Integer) As Boolean
matrix(new_line.room1_id, new_line.room2_id) = 1
matrix(new_line.room2_id, new_line.room1_id) = 1
'每個頂點的度
Dim ranges() As Integer
ReDim ranges(1 To points_count) As Integer
Dim is_found_1_range_point As Boolean
Dim is_found_morethan2_range_point As Boolean
'獲取每個頂點的度
Dim i As Integer
Dim j As Integer
For i = 1 To points_count Step 1
For j = 1 To points_count Step 1
ranges(i) = ranges(i) + matrix(i, j)
Next
If ranges(i) = 1 Then
is_found_1_range_point = True
End If
Next
'將每個度為1的點,和與它相連的點,降度
While is_found_1_range_point = True
is_found_1_range_point = False
For i = 1 To points_count Step 1
If ranges(i) = 1 Then
is_found_1_range_point = True
For j = 1 To points_count Step 1
If matrix(i, j) = 1 Then
ranges(i) = ranges(i) - 1
ranges(j) = ranges(j) - 1
End If
Next
End If
Next
Wend
'是否存在度不小於2的點
For i = 1 To points_count Step 1
If ranges(i) >= 2 Then
Find_ring = True
matrix(new_line.room1_id, new_line.room2_id) = 0
matrix(new_line.room2_id, new_line.room1_id) = 0
Exit Function
End If
Next
Find_ring = False
End Function
Private Function Find_line(lines As Object_vector, found_line As Shortest_distance) As Boolean
Dim i As Long
For i = 1 To lines.arraysize Step 1
Set each_line = lines.element(i)
'無向圖
If (found_line.room1_id = each_line.room1_id And found_line.room2_id = each_line.room2_id) Or (found_line.room1_id = each_line.room2_id And found_line.room2_id = each_line.room1_id) Then
Find_line = True
Exit Function
End If
Set each_line = Nothing
Next
Find_line = False
End Function
'A*尋路演算法
Public Function Find_way(ByRef coord_start As Coord, ByRef coord_end As Coord) As Object_vector
Dim here_coord As New Coord
Dim next_coord As New Coord
Dim queue_coord As New Object_vector
Dim map_flag() As Long
ReDim map_flag(1 To world_map.map_width, 1 To world_map.map_height)
'Call queue_coord.Push(coord_start)
Set here_coord = coord_start
map_flag(coord_start.x, coord_start.y) = 1
'設定能夠行走的方向
Dim offset(1 To 4) As New Coord
Dim temp_coord As Coord
Dim i As Byte
For i = 1 To 4 Step 1
Set temp_coord = New Coord
Select Case i
Case 1
temp_coord.x = 0
temp_coord.y = 1
Case 2
temp_coord.x = 1
temp_coord.y = 0
Case 3
temp_coord.x = 0
temp_coord.y = -1
Case 4
temp_coord.x = -1
temp_coord.y = 0
End Select
Set offset(i) = temp_coord
Set temp_coord = Nothing
Next
'標記行走步數
Dim nbr_coord As Coord
Do
For i = 1 To 4 Step 1
Set nbr_coord = New Coord
'開始逐個遍歷 here_coord 的四個相鄰座標
nbr_coord.x = here_coord.x + offset(i).x
nbr_coord.y = here_coord.y + offset(i).y
If Not world_map.Is_map_edge(nbr_coord.x, nbr_coord.y) Then
If map_flag(nbr_coord.x, nbr_coord.y) = 0 And world_map.tile(nbr_coord.x, nbr_coord.y) = GROUND Then
map_flag(nbr_coord.x, nbr_coord.y) = map_flag(here_coord.x, here_coord.y) + 1
If nbr_coord.x = coord_end.x And nbr_coord.y = coord_end.y Then
GoTo Finish
End If
Call queue_coord.Push(nbr_coord)
End If
End If
DoEvents
Next
If nbr_coord.x = coord_end.x And coord_end.y = nbr_coord.y Then
Finish:
Exit Do
End If
'Set here_coord = Nothing
If queue_coord.Is_empty Then
Set Find_way = Nothing
Exit Function
End If
Set here_coord = queue_coord.element(1)
Call queue_coord.Delete(1)
Loop While True
'記錄路徑
Dim path As New Object_vector
Set here_coord = coord_end
Dim flag As Long
flag = map_flag(coord_end.x, coord_end.y)
Call path.Push(world_map.tile_obj(here_coord.x, here_coord.y))
Do
flag = flag - 1
Set nbr_coord = New Coord
For i = 1 To 4 Step 1
nbr_coord.x = here_coord.x + offset(i).x
nbr_coord.y = here_coord.y + offset(i).y
If map_flag(nbr_coord.x, nbr_coord.y) = flag Then
Call path.Insert(1, nbr_coord)
GoTo Next_step
End If
Next
Next_step:
Set here_coord = nbr_coord
Set nbr_coord = Nothing
Loop While flag > 1
Set Find_way = path
End Function
類模組Map
'地圖類,用於生成地圖
'其中,平滑地圖及清除小房間演算法借鑑於Unity官方
Option Explicit
Private m_map As Object_vector
Private m_width As Integer
Private m_height As Integer
Private m_rooms As Object_vector
Private m_active_rooms As Object_vector
Private m_random_fill_percent As Byte
Private Sub Class_Initialize()
ActiveWorkbook.Unprotect
Application.ScreenUpdating = False
Set m_map = New Object_vector
m_map.element_type = "Coord"
Set m_rooms = New Object_vector
m_rooms.element_type = "Object_vector" 'm_rooms.element.element_type is "Coord"
Set m_active_rooms = New Object_vector
m_active_rooms.element_type = "Room"
m_width = 0
m_height = 0
End Sub
Private Sub Class_Terminate()
Set m_map = Nothing
Set m_rooms = Nothing
Set m_active_rooms = Nothing
End Sub
'根據指定的索引值返回橫座標x
Private Property Get coord_x(array_index As Long) As Integer
Dim res As Integer
res = array_index Mod m_width
coord_x = IIf(res = 0, m_width, res)
End Property
'根據指定的索引值返回縱座標y
Private Property Get coord_y(array_index As Long) As Integer
coord_y = -(Int(-(array_index / m_width)))
End Property
'根據指定的座標(x, y)返回索引值
Private Property Get coord_index(x As Integer, y As Integer) As Long
coord_index = (y - 1) * CLng(m_width) + x
End Property
'檢查座標是否合法
Private Function Check_coord(x As Integer, y As Integer) As Boolean
Dim check_coord_x As Boolean
Dim check_coord_y As Boolean
check_coord_x = True
check_coord_y = True
If x < 1 Or x > m_width Then
check_coord_x = False
MsgBox ("Map::Check_coord: Error Coord X! x/width is: " & x & "/" & m_width)
End If
If y < 1 Or y > m_height Then
check_coord_y = False
MsgBox ("Map::Check_coord: Error Coord Y! y/height is: " & y & "/" & m_height)
End If
Check_coord = check_coord_x And check_coord_y
End Function
'為map中每個座標申請空間
Private Function Init_map(width As Integer, height As Integer)
m_width = width
m_height = height
Dim map_tile_count As Long
map_tile_count = CLng(m_width) * m_height
m_map.arraysize = map_tile_count
Dim i As Long
Dim each_tile As Coord
For i = 1 To map_tile_count Step 1
Set each_tile = New Coord
each_tile.x = coord_x(i)
each_tile.y = coord_y(i)
'each_tile.coord_type = NONE
Call m_map.Insert(i, each_tile)
Set each_tile = Nothing
DoEvents
Next
End Function
'根據指定座標(x, y)獲得地板型別
Public Property Get tile(x As Integer, y As Integer) As ENUM_COORD_TYPE
If Not Check_coord(x, y) Then
Exit Property
End If
tile = m_map.element(coord_index(x, y)).coord_type
End Property
'根據指定座標(x, y)修改該座標的地板型別
Public Property Let tile(x As Integer, y As Integer, tile_type As ENUM_COORD_TYPE)
If Not Check_coord(x, y) Then
Exit Property
End If
m_map.element(coord_index(x, y)).coord_type = tile_type
End Property
Public Property Get tile_obj(x As Integer, y As Integer) As Coord
If Not Check_coord(x, y) Then
Exit Property
End If
Set tile_obj = m_map.element(coord_index(x, y))
End Property
Public Property Get map_width() As Integer
map_width = m_width
End Property
Public Property Get map_height() As Integer
map_height = m_height
End Property
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'0.生成地圖
Public Function Generate_map(width As Integer, height As Integer, random_fill_percent As Byte)
If random_fill_percent < 0 Or random_fill_percent > 100 Then
MsgBox ("random_fill_percent Error! random_fill_percent is " & random_fill_percent & "/[0, 100].")
Exit Function
End If
m_random_fill_percent = random_fill_percent
Call Init_map(width, height)
Call Random_fill_map(random_fill_percent)
Call Smooth_map
Call Get_rooms
Call Erase_little_room(50, True)
Call Connect_room
Set m_rooms = Nothing
Set m_active_rooms = Nothing
End Function
'1.將地圖隨機填充
Private Function Random_fill_map(random_fill_percent As Byte)
Randomize
Dim grid_x As Integer
Dim grid_y As Integer
For grid_x = 1 To m_width Step 1
For grid_y = 1 To m_height Step 1
If Is_map_edge(grid_x, grid_y) Then
tile(grid_x, grid_y) = WALL
Else
tile(grid_x, grid_y) = IIf((Int(Rnd * 100 + 1) > random_fill_percent), WALL, GROUND)
End If
DoEvents
Next
Next
End Function
'2.平滑地圖,生成地圖概括
Private Function Smooth_map()
Dim surr_walls As Byte
Dim grid_x As Integer
Dim grid_y As Integer
For grid_x = 1 To m_width Step 1
For grid_y = 1 To m_height Step 1
'不遍歷地圖邊緣的座標
If Is_map_edge(grid_x, grid_y) Then
GoTo Next_loop
End If
'當前座標周圍的牆壁數量最多8塊:[0,8]
surr_walls = Get_surrounding_wall_count(grid_x, grid_y)
'若當前座標周圍的牆壁(WALL)數量小於4塊,則認為這是一塊空地(GROUND)
If surr_walls < 4 Then
tile(grid_x, grid_y) = GROUND
End If
'若當前座標周圍的牆壁(WALL)數量大於4塊,則認為這是一塊牆壁(WALL)
If surr_walls > 4 Then
tile(grid_x, grid_y) = WALL
End If
DoEvents
Next_loop:
Next
Next
End Function
'根據指定座標(x, y)獲得周圍的WALL數量
Private Function Get_surrounding_wall_count(x As Integer, y As Integer) As Byte
Dim walls As Byte
walls = 0
Dim nbour_x As Integer
Dim nbour_y As Integer
For nbour_x = x - 1 To x + 1 Step 1
' '不必判斷座標是否合法,因為此函式的使用場合都不會遍歷地圖邊緣
' '若座標不處於地圖邊緣,則它周圍的8塊座標一定合法
' If nbour_x < 1 Or nbour_x > m_width Then
' GoTo continue_next_x
' End If
For nbour_y = y - 1 To y + 1 Step 1
' If nbour_y < 1 Or nbour_y > m_height Then
' GoTo continue_next_y
' End If
If Is_map_edge(nbour_x, nbour_y) Then
walls = walls + 1
Else
If nbour_x <> x Or nbour_y <> y Then
walls = walls + Int(tile(nbour_x, nbour_y))
End If
End If
'continue_next_y:
Next
'continue_next_x:
Next
Get_surrounding_wall_count = walls
End Function
'3. 獲得房間列表
Private Function Get_rooms()
Dim temp_tile_type As ENUM_COORD_TYPE
Dim map_flags() As Byte
ReDim map_flags(1 To m_width, 1 To m_height) As Byte
Dim grid_x As Integer
Dim grid_y As Integer
Dim one_room As New Object_vector
' For grid_x = 2 To m_width - 1 Step 1 '不遍歷地圖邊緣
' For grid_y = 2 To m_height - 1 Step 1 '下面的程式碼雖然多了一些執行步...
' If map_flags(grid_x, grid_y) = 0 Then '但是好理解
Dim room_tile_index As Long
For grid_x = 1 To m_width Step 1
For grid_y = 1 To m_height Step 1
If (Not Is_map_edge(grid_x, grid_y)) And (map_flags(grid_x, grid_y) = 0) Then '不遍歷地圖邊緣 和 處理過的房間
Set one_room = Get_region(grid_x, grid_y)
Call m_rooms.Push(one_room)
For room_tile_index = 1 To one_room.arraysize Step 1
map_flags(one_room.element(room_tile_index).x, one_room.element(room_tile_index).y) = 1
Next
End If
DoEvents
Next
Next
' Set Get_rooms = m_rooms 'DEBUG: test
Set one_room = Nothing
End Function
'3.1.獲得一片區域
Private Function Get_region(start_x As Integer, start_y As Integer) As Object_vector
Dim queue As New Object_vector '只許使用 {queue.Push(obj);} 和 {queue.element(1); Delete(1);}. 佇列, 處理被迴圈元素
Dim temp_tile_type As ENUM_COORD_TYPE '獲得區域的地板型別
Dim map_flags() As Byte '標識。被處理過的元素設定為1,否則為0。預設所有Byte型別的標識為0。
ReDim map_flags(1 To m_width, 1 To m_height) As Byte
Dim res_coords As New Object_vector
'初始,將引數中的座標元素壓入佇列,準備處理
Dim start_tile As New Coord
start_tile.x = start_x
start_tile.y = start_y
start_tile.coord_type = tile(start_x, start_y)
Call queue.Push(start_tile)
Call res_coords.Push(start_tile)
map_flags(start_x, start_y) = 1
temp_tile_type = start_tile.coord_type
Dim temp_coord As New Coord
While Not queue.Is_empty
'處理佇列中的元素
Set temp_coord = queue.element(1)
Call queue.Delete(1)
'對佇列中的每個元素進行十字搜尋
Dim grid_x As Integer
Dim grid_y As Integer
For grid_x = temp_coord.x - 1 To temp_coord.x + 1 Step 1
For grid_y = temp_coord.y - 1 To temp_coord.y + 1 Step 1
If Is_map_edge(grid_x, grid_y) Then
'map_flags(grid_x, grid_y) = 1
GoTo Next_grid 'continue;
End If
'十字搜尋
If (grid_x = temp_coord.x Or grid_y = temp_coord.y) And (map_flags(grid_x, grid_y) = 0) Then
map_flags(grid_x, grid_y) = 1
If temp_tile_type = tile(grid_x, grid_y) Then
'地板型別與引數的地板型別相同,則加入佇列,下次處理
Call res_coords.Push(m_map.element(coord_index(grid_x, grid_y)))
Call queue.Push(m_map.element(coord_index(grid_x, grid_y)))
'Sheet1.Cells(grid_y, grid_x).Interior.ColorIndex = 8 'Light Blue 'test code: show region
End If
End If
DoEvents
Next_grid:
Next
Next
Set temp_coord = Nothing
Wend
Set Get_region = res_coords
Set queue = Nothing
Set start_tile = Nothing
End Function
'4. 再次平滑地圖
'4.1 擦除小房間
'4.2 得到可活動的房間 m_active_rooms
Private Function Erase_little_room(little_room_size As Integer, is_dependon_random_fill_percent As Boolean)
If m_rooms.Is_empty Then
MsgBox "Rooms is empty! Call function Map::Get_rooms()."
Exit Function
End If
If is_dependon_random_fill_percent Then
little_room_size = Int(m_random_fill_percent / 2)
End If
'遍歷m_rooms
Dim rooms_count As Integer
rooms_count = m_rooms.arraysize
Dim room_type As ENUM_COORD_TYPE
Dim each_room As New Object_vector
Dim active_room As New Room
Dim each_room_index As Integer
For each_room_index = 1 To rooms_count Step 1
'Set each_room = New Object_vector
Set each_room = m_rooms.element(CLng(each_room_index))
room_type = each_room.element(1).coord_type
Select Case room_type
'地板是可活動的
Case GROUND:
'這不是一個小房間
If Not Erase_room(each_room, little_room_size) Then
'那麼,應該將它加入到可活動房間列表 m_active_rooms 中
active_room.tiles = each_room
active_room.room_edge = Set_room_edge(active_room)
Call m_active_rooms.Push(active_room)
Set active_room = Nothing
End If
Case WALL:
Call Erase_room(each_room, little_room_size)
Case Else:
'CHECK IT: ?? 如果地板型別除了以上兩種,這說明是出錯了。那麼我應該做點兒什麼?
End Select
Set each_room = Nothing
DoEvents
Next
End Function
'尋找房間邊緣(邊緣的型別與房間型別相同)(媽蛋程式結構設計失誤,這個函式不應該在這兒的)
'這裡的地圖邊緣並不是十分精準,因為,如果一個可活動的房間中存在一個已經被擦除過的小房間, 則會造成失誤
'但不會影響最後的計算結果。因為房間邊緣主要用於設定房間通路,即使邊緣存在於房間中央,也不會讓中央的點去與其它房間的邊緣向連線,
'因為只有真正的邊緣和邊緣靠的更近
'正因為這樣,也會導致房間列表中的每個房間的地板會包含不完全情況。但同樣不影響計算。
Private Function Set_room_edge(ByRef para_room As Room) As Object_vector
Dim temp_tile As New Coord
Set Set_room_edge = New Object_vector
Dim i As Long
For i = 1 To para_room.room_size Step 1
Set temp_tile = para_room.tiles.element(i) 'm_tiles.element(i)
If Get_surrounding_wall_count(temp_tile.x, temp_tile.y) > 0 Then
Call Set_room_edge.Push(temp_tile)
End If
DoEvents
Next
Set temp_tile = Nothing
End Function
'4.1 擦除小房間
Private Function Erase_room(ByRef one_room As Object_vector, erase_room_size_min As Integer) As Boolean
If (Not one_room.Is_empty) And (one_room.arraysize < erase_room_size_min) Then
Dim tile_type As ENUM_COORD_TYPE
tile_type = one_room.element(1).coord_type
'執行擦除
Dim each_tile_index As Long
For each_tile_index = 1 To one_room.arraysize Step 1
tile(one_room.element(each_tile_index).x, one_room.element(each_tile_index).y) = IIf(tile_type <> NONE And tile_type = GROUND, WALL, GROUND)
DoEvents
Next
'如果這是一個小房間,則返回True
Erase_room = True
Else
'如果這不是一個小房間,則返回False
Erase_room = False
End If
End Function
'5.建立房間通路
Public Function Connect_room()
Dim distance_rooms As New Object_vector
Set distance_rooms = Get_shortest_distance_all_room
Dim graph_creater As New Graphs_Generator
Dim passage As New Object_vector
Set passage = graph_creater.Get_min_cost_tree(distance_rooms, m_active_rooms.arraysize)
Dim coord1 As New Coord
Dim coord2 As New Coord
Dim i As Integer
For i = 1 To passage.arraysize Step 1
Set coord1 = passage.element(CLng(i)).shortest_coord1
Set coord2 = passage.element(CLng(i)).shortest_coord2
Call Draw_passage(coord1, coord2)
Set coord2 = Nothing
Set coord1 = Nothing
Next
End Function
'5.3 繪製兩點之間的通路
Private Function Draw_passage(ByRef coord1 As Coord, ByRef coord2 As Coord)
Dim graph As New Graphs_Generator
Dim coords_line As New Object_vector
Dim coords_circle As New Object_vector
Set coords_line = graph.Get_line(coord1, coord2)
Dim coord_center As New Coord
Dim grid_count As Integer
For grid_count = 1 To coords_line.arraysize Step 1
Set coords_circle = graph.Get_circle(coords_line.element(CLng(grid_count)), 2)
Dim circle_grid_count As Integer
For circle_grid_count = 1 To coords_circle.arraysize Step 1
tile(coords_circle.element(CLng(circle_grid_count)).x, coords_circle.element(CLng(circle_grid_count)).y) = GROUND
DoEvents
Next
Set coords_circle = Nothing
Next
Set graph = Nothing
Set coords_line = Nothing
End Function
'5.1.獲得所有房間之間的最短距離
Public Function Get_shortest_distance_all_room() As Object_vector
Dim room_a As New Room
Dim room_b As New Room
Dim active_room_count As Integer
Dim rooms_distance As New Object_vector
Dim distance As New Shortest_distance
active_room_count = m_active_rooms.arraysize
Dim a As Integer
Dim b As Integer
For a = 1 To active_room_count Step 1
Set room_a = m_active_rooms.element(CLng(a))
For b = a + 1 To active_room_count Step 1
If a <> b Then
Set room_b = m_active_rooms.element(CLng(b))
Set distance = Get_shortest_distance(room_a, room_b)
distance.room1_id = a
distance.room2_id = b
Call rooms_distance.Push(distance)
Set distance = Nothing
Set room_b = Nothing
End If
DoEvents
Next
Set room_a = Nothing
Next
Set Get_shortest_distance_all_room = rooms_distance
End Function
'5.2.獲得兩個房間的最短距離
Private Function Get_shortest_distance(ByRef room_a As Room, ByRef room_b As Room) As Shortest_distance
Dim shortest_dis As Long
shortest_dis = &H7FFFFFFF
Dim res_distance As New Shortest_distance
Dim shortest_tile_A As New Coord
Dim shortest_tile_B As New Coord
Dim edge_tiles_count_a As Long
Dim edge_tiles_count_b As Long
Dim temp_distance As Long
For edge_tiles_count_a = 1 To room_a.room_edge.arraysize Step 1
Set shortest_tile_A = room_a.room_edge.element(edge_tiles_count_a)
For edge_tiles_count_b = 1 To room_b.room_edge.arraysize Step 1
Set shortest_tile_B = room_b.room_edge.element(edge_tiles_count_b)
temp_distance = CLng((shortest_tile_A.x - shortest_tile_B.x)) * (shortest_tile_A.x - shortest_tile_B.x) + CLng((shortest_tile_A.y - shortest_tile_B.y)) * (shortest_tile_A.y - shortest_tile_B.y)
If temp_distance < shortest_dis Then
shortest_dis = temp_distance
Set res_distance.shortest_coord1 = shortest_tile_A
Set res_distance.shortest_coord2 = shortest_tile_B
res_distance.distance = shortest_dis
End If
Set shortest_tile_B = Nothing
Next
Set shortest_tile_A = Nothing
Next
Set Get_shortest_distance = res_distance
Set res_distance = Nothing
End Function
'判斷指定座標(x, y)是否是地圖邊緣
Public Function Is_map_edge(x As Integer, y As Integer) As Boolean
Is_map_edge = Not (x > 1 And x < m_width And y > 1 And y < m_height)
End Function
類模組Object_vector
'1.可變空間陣列,陣列中的值型別為物件型別
'2.只能儲存相同型別的物件
'3.陣列中的值傳遞方式為引用傳遞
'4.有可能造成環形依賴
''''''''''''''''''''''''''''''''''''''''''''''
Option Explicit
Private m_datas() As Object '儲存的資料
Private m_length As Long '資料元素數量
Private m_useable_length As Long '可用空間長度
Private m_element_type As String '物件型別
Private Const ex_space_coe As Double = 0.5 '可用空間擴張係數
Private Const init_space As Integer = 10 '預設初始空間
'初始化空間為 10
Private Sub Class_Initialize()
ReDim m_datas(1 To init_space)
Dim i As Integer
For i = 1 To init_space Step 1
'無論何時,物件之間賦值需要 Set 關鍵字
Set m_datas(i) = Nothing
Next
m_length = 0
m_useable_length = init_space
m_element_type = ""
End Sub
Private Sub Class_Terminate()
'Erase m_datas
Call Clean
ReDim m_datas(0)
End Sub
Public Property Get element_type() As String
element_type = m_element_type
End Property
Public Property Let element_type(ele_type As String)
If m_element_type = "" Then
m_element_type = ele_type
Else
'TODO: Not modfity value "m_element_type"
End If
End Property
'獲取陣列長度
Public Property Get arraysize() As Long
arraysize = m_length
End Property
'重設可用空間大小
Public Property Let arraysize(new_size As Long)
ReDim Preserve m_datas(1 To new_size)
m_useable_length = new_size
If m_length > m_useable_length Then
m_length = m_useable_length
End If
End Property
'獲得索引為 index 的資料元素
Public Property Get element(index As Long) As Object
If True = Check_index(index) Then
Set element = m_datas(index)
Else
MsgBox ("Get_element: Index Error!")
Exit Property
End If
End Property
'將索引為 index 的資料元素設定為 element_data
Public Property Let element(index As Long, ByRef element_data As Object)
If Not Check_type(m_element_type, element_data) Then
MsgBox ("Let element: Object Type Error!")
Exit Property
End If
If True = Check_index(index) Then
Set m_datas(index) = element_data
Else
MsgBox ("Let_element: Index Error!")
Exit Property
End If
End Property
Public Function Insert(index As Long, ByRef element_data As Object)
'陣列中只能儲存相同型別的物件
If (m_element_type = "") Then
m_element_type = TypeName(element_data)
Else
If Not Check_type(m_element_type, element_data) Then
MsgBox ("Insert: Object Type Error!")
Exit Function
End If
End If
'一旦可用空間不足,則將可用空間擴大0.5倍
If m_length = m_useable_length Then
arraysize = m_useable_length + Int(m_useable_length * ex_space_coe)
End If
'如果 index 為 -1,在末尾插入
index = Switch_index(index)
'index 值非法
If index < 1 Or index > m_length + 1 Then
MsgBox ("Insert: Index Error!")
Exit Function
Else
'index 後的資料向後移位
Dim i As Long
For i = m_length To index Step -1
'MsgBox ("move: " & i) 'It is used for test
Set m_datas(i + 1) = m_datas(i)
Next
'在index的位置插入值
Set m_datas(index) = element_data
'陣列長度 +1,可用空間不變
m_length = m_length + 1
End If
End Function
'刪除元素
Public Function Delete(index As Long)
'index 值非法
If Not Check_index(index) Then
MsgBox ("Delete: Index Error!" & "(index is " & index & ")")
Exit Function
'開始刪除元素
Else
'釋放元素
Set m_datas(index) = Nothing
'index 之後的元素向前移動 1
Dim i As Long
For i = index + 1 To m_length Step 1
Set m_datas(i - 1) = m_datas(i)
Next
Set m_datas(m_length) = Nothing
'元素數量 -1
m_length = m_length - 1
End If
End Function
'清除所有資料
Public Function Clean()
Dim i As Long
For i = 1 To m_length
Set m_datas(i) = Nothing
Next
m_length = 0
End Function
'彈出陣列最後一個元素並返回
Public Function Pop() As Object
Set Pop = m_datas(m_length)
Call Delete(m_length)
End Function
'將元素壓入末尾
Public Function Push(ByRef element As Object)
Call Insert(m_length + 1, element)
End Function
'類似於將“=”過載
Public Property Let datas(ByRef para_datas As Object_vector)
'檢查陣列中的元素型別是否為 “Object_vector”
If Not Check_type("Object_vector", para_datas) Then
MsgBox ("Let datas: Object Type Error!")
Exit Property
End If
'清除所有資料準備被賦值
Call Clean
'獲取右值(para_datas)的元素數量
Dim new_length As Long
new_length = para_datas.arraysize
'重設可用空間
If new_length < init_space Then
arraysize = init_space
Else
arraysize = new_length + Int(new_length * ex_space_coe)
End If
m_element_type = para_datas.element_type
'將右值的每個元素賦值給左值
Dim i As Long
For i = 1 To new_length Step 1
Set m_datas(i) = para_datas.element(i)
m_length = m_length + 1
Next
End Property
'是否為空
Public Function Is_empty() As Boolean
If m_length = 0 Then
Is_empty = True
Else
Is_empty = False
End If
End Function
'檢查輸入的索引值
Private Function Check_index(index As Long) As Boolean
Check_index = (index >= 1 And index <= m_length)
End Function
'檢查元素型別
Private Function Check_type(type_name As String, obj As Object) As Boolean
Check_type = (type_name = TypeName(obj))
End Function
'若index為 -1 ,則認為 index 是末尾元素索引+1
Private Function Switch_index(index As Long) As Long
Switch_index = IIf(index = -1, m_length + 1, index)
End Function
類模組Room
Private m_edge As Object_vector
Private m_tiles As Object_vector
Private m_type As ENUM_COORD_TYPE
Private m_size As Long
Private Sub Class_Initialize()
Set m_tiles = New Object_vector
Set m_edge = New Object_vector
m_tiles.element_type = "Coord"
m_edge.element_type = "Coord"
m_type = NONE
m_size = 0
End Sub
Private Sub Class_Terminate()
Set m_tiles = Nothing
Set m_edge = Nothing
End Sub
'獲得房間的大小(地板數量)
Public Property Get room_size() As Long
room_size = m_size
End Property
'獲得房間型別(房間內所有地板有且僅有的型別)
Public Property Get room_type() As ENUM_COORD_TYPE
room_type = m_type
End Property
'更改房間型別
Public Property Let room_type(new_type As ENUM_COORD_TYPE)
'房間型別為NONE、並且房間地板數量為0時,以引數new_type為準
If m_type = NONE And m_size = 0 Then
m_type = new_type
'如果房間不為空,則以房間的第一個地板的型別為標準
ElseIf m_size > 0 Then
m_type = m_tiles.element(1).coord_type
End If
End Property
'獲得房間的所有地板
Public Property Get tiles() As Object_vector
Set tiles = m_tiles
End Property
'類似於“=”過載:將房間的所有地板更改為引數other_tiles
Public Property Let tiles(ByRef other_tiles As Object_vector)
'm_tiles.datas = other_tiles '有疑問:為什麼這句和下一句的結果是相同的?既然是引用傳遞,那麼若釋放other_tiles,則m_tiles中的元素也應該不存在啊?
Set m_tiles = other_tiles '但實際上(應用這句程式碼而不是上一句),即使釋放了other_tiles,m_tiles中的元素卻被正常賦值了。
m_size = other_tiles.arraysize
If other_tiles.arraysize <> 0 Then
m_type = other_tiles.element(1).coord_type
End If
End Property
Public Property Let room_edge(ByRef para_room_edge As Object_vector)
'm_edge.datas = para_room_edge
Set m_edge = para_room_edge
End Property
Public Property Get room_edge() As Object_vector
Set room_edge = m_edge
End Property
'在房間中尋找一塊地板
'FIXME: 複雜度過高
Private Function Find_tile(ByRef tile_is_found As Coord) As Boolean
Dim i As Long
Dim temp_tile As Coord
For i = 1 To m_size Step 1
temp_tile = m_tiles.element(i)
If tile_is_found.x = temp_tile.x And tile_is_found.y = temp_tile.y Then
Find_tile = True
Exit For
End If
Next
Find_tile = False
End Function
'尋找房間邊緣(邊緣的型別與房間型別相同)(無引數)
'KILL: 複雜度過高,不使用
Private Function Set_room_edge_noarg()
Dim i As Long
For i = 1 To m_size Step 1
Dim temp_tile As Coord
Dim surr_tile As New Coord
Set temp_tile = m_tiles.element(i)
Dim w As Integer
Dim h As Integer
For w = temp_tile.x - 1 To temp_tile.x + 1 Step 1
For h = temp_tile.y - 1 To temp_tile.y + 1 Step 1
If w <> temp_tile.x Or h <> temp_tile.y Then
surr_tile.x = w
surr_tile.y = h
If Not Find_tile(surr_tile) Then
Call m_edge.Push(temp_tile)
GoTo Next_tile
End If
End If
Next
Next
Next_tile:
Next
End Function
'兩個房間是否是同一個房間
Public Function Is_Equal(ByRef other_room As Room) As Boolean
If m_size > 0 Then
'兩個房間如果大小不同,則不認為是同一個房間
If m_size <> other_room.room_size Then
GoTo Lable_Not_Equal
End If
Dim one_tile As Coord
Dim other_tile As Coord
Set one_tile = m_tiles.element(1)
Set other_tile = other_room.tiles().element(1)
'因為任意兩個房間不存在相交情況
'所以如果兩個房間的第一塊地板是相同(座標與地板型別都相同)的,則認為這兩個房間為同一個房間
If Not one_tile.Is_Equal(other_tile) Then
GoTo Lable_Not_Equal
End If
Lable_Is_Equal:
Is_Equal = True
Else
Lable_Not_Equal:
Is_Equal = False
End If
End Function
類模組Shortest_distance
'(命名失誤)線段類,用來定義一條線段
Public room1_id As Integer
Public room2_id As Integer
Public distance As Long
Public shortest_coord1 As Coord
Public shortest_coord2 As Coord
Private Sub Class_Initialize()
room1_id = 0
room2_id = 0
distance = 0
Set shortest_coord1 = New Coord
Set shortest_coord2 = New Coord
End Sub
Private Sub Class_Terminate()
room1_id = 0
room2_id = 0
distance = 0
Set shortest_coord1 = Nothing
Set shortest_coord2 = Nothing
End Sub
測試主函式test
Option Explicit
Public world_map As New Map
Public cell_ctrl As New Cell_controller
Sub test()
Const width As Integer = 400
Const height As Integer = 240
Const random_percent As Integer = 50
Call world_map.Generate_map(width, height, random_percent)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Dim gg As New Graphs_Generator
' Dim coord1 As New Coord
' Dim coord2 As New Coord
' Dim ran1 As Integer
' Dim ran2 As Integer
' '隨機取起點
' While coord1.coord_type <> GROUND
' ran1 = Int(Rnd * world_map.map_width + 1)
' ran2 = Int(Rnd * world_map.map_height + 1)
' coord1.x = ran1
' coord1.y = ran2
' coord1.coord_type = world_map.tile(ran1, ran2)
' Wend
' '隨機取終點
' While coord2.coord_type <> GROUND
' ran1 = Int(Rnd * world_map.map_width + 1)
' ran2 = Int(Rnd * world_map.map_height + 1)
' coord2.x = ran1
' coord2.y = ran2
' coord2.coord_type = world_map.tile(ran1, ran2)
' Wend
'
'測試Map::Generate_map函式
Application.EnableEvents = False
Call cell_ctrl.Show_map
Application.EnableEvents = True
End Sub
執行結果