1
2
3
4
5
6
7
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
|
extends Spatial
enum { Y, X }
# hex board represented in square-grid form like so (e.g., 3-length-side hex grid):
# x x x
# x x x x
# x x x x x
# x x x x
# x x x
# going up and to the right is done by decreasing the row by 1
# going up and to the left is done by decreasing the row by 1 and the column by 1
var board: Array = [] # 2D Array of JSON objects describing the board, which can be turned into objects
var board_display: Array = []
var available_board_coords: Array = [] # for population purposes
var airports = {} # id : HexSpace of cell_type airport
var side_len: int
# Y R B G
var airport_colors = [ Color(1, 1, 0), Color(1, 0, 0), Color(0.3, 0.3, 1), Color(0, 0.8, 0) ]
enum { NUMBER, COLOR }
onready var hex_space = preload("res://objects/HexSpace.tscn")
# cell types
enum { PLAIN, HILLS, MOUNTAINS, AIRPORT }
# directions: E, NE, NW, W, SW, SE
const adjacent_offsets = [ [0,1] , [-1, 0], [-1, -1], [0, -1], [1, 0], [1, 1] ]
# indices of the offsets that are valid cells to approach from
const approaches_i: Array= [ [0, 1, 2, 3, 4, 5], [0,1,3,4], [0,3] ]
func _ready():
pass
func reset_board():
for node in get_children():
node.queue_free()
board.clear()
board_display.clear()
available_board_coords.clear()
func create_board_base(hex_side_length : int):
side_len = hex_side_length
var number_of_cells = 3*( pow(hex_side_length, 2) - hex_side_length) + 1
reset_board()
var board_diameter = hex_side_length * 2 - 1
for r in range(board_diameter):
var row_length: int = board_diameter - abs(r-(hex_side_length-1))
var row = []
row.resize(board_diameter)
row.fill(null) # not in hex grid
var offset : int = 0
if r > (hex_side_length - 1): offset = r - (hex_side_length - 1)
for i in range(row_length):
row[offset+i] = { "cell_type" : PLAIN, "pos" : [r, offset+i] } # ground cell
available_board_coords.push_back( [r, offset+i] )
board.append(row)
func display_board():
var cell_size_x = 1 # distance between center of two adjacent hex cells
var row_offset_y:float = cos(deg2rad(30)) * cell_size_x
var board_diam:int = len(board)
var side_len:int = ( board_diam + 1 ) / 2
for r in range(board_diam):
var row_display = []
row_display.resize(board_diam)
row_display.fill(null)
var row = board[r]
var z = row_offset_y * (r - board_diam/2)
var offset_x = abs(side_len - (r+1)) * (cell_size_x / 2.0) if (r+1) <= side_len else -1*abs(side_len - (r+1)) * (cell_size_x/2.0)
offset_x -= board_diam/2 * cell_size_x
for c in range(board_diam):
if row[c] == null: continue
var x = offset_x + c * cell_size_x
var new_cell = hex_space.instance()
new_cell.call_deferred("set", "global_position", Vector3(x, randf()/15, z))
new_cell.set_up(row[c])
add_child(new_cell)
row_display[c] = new_cell
board_display.push_back(row_display)
# populate board with airports, hills, and mountains
# depending on game settings
func populate_board(num_mountains : int, num_hills : int, num_airports : int, runway_count : int, use_names : bool = false) -> bool:
var board_diam:int = len(board)
for _m in range(num_mountains):
if len(available_board_coords) < 1: return false
var spot_i:int = randi() % len(available_board_coords)
var spot = available_board_coords[ spot_i ]
var args = {"cell_type" : MOUNTAINS, "orientation" : randi() % 6, "pos" : [spot[Y], spot[X]]}
board[spot[Y]][spot[X]] = args
available_board_coords.pop_at(spot_i)
for _h in range(num_hills):
if len(available_board_coords) < 1: return false
var spot_i:int = randi() % len(available_board_coords)
var spot = available_board_coords[ spot_i ]
var args = { "cell_type" : HILLS, "orientation" : randi() % 6, "pos" : [spot[Y], spot[X]] }
board[spot[Y]][spot[X]] = args
available_board_coords.pop_at(spot_i)
# airport identification
var used_airports : Array = []
var airport_id:int = 0
for a in range(num_airports):
var airport_display
if use_names:
airport_display = Globals.get_random_airport_name(used_airports)
else:
airport_display = [ randi() % 9 + 1, randi() % 4 ] # number, color
while airport_display in used_airports:
airport_display = [ randi() % 9 + 1, randi() % 4 ]
# find valid spot
var spot_okay:bool = false
var rot:int
var spot_r:int
var spot_c:int
var spot_i:int
var valid_approaches = []
var runways = (randi() % 3 + 1) if (runway_count == 0) else runway_count
while (not spot_okay) and (len(available_board_coords) > 0):
spot_i = randi() % len(available_board_coords)
var spot = available_board_coords[ spot_i ]
spot_r = spot[Y]
spot_c = spot[X]
var has_adjacent_airport = false
for offset in adjacent_offsets: # away from other airports
var new_r: int = spot_r + offset[Y]
var new_c: int = spot_c + offset[X]
if new_r < 0 or new_c < 0 or new_r >= board_diam or new_c >= board_diam: # offset out of square grid
continue
var adjacent_cell = board[new_r][new_c]
if adjacent_cell != null and adjacent_cell["cell_type"] == AIRPORT:
has_adjacent_airport = true
break
if has_adjacent_airport:
available_board_coords.pop_at(spot_i)
continue
spot_okay = true
# find rotation that leaves at least 1 runway open
rot = randi() % 3
var rot_okay = false
for _i in range(3):
var rot_approaches = adjacent_offsets.slice(rot, 5)
if rot != 0: rot_approaches += adjacent_offsets.slice(0, rot - 1)
var possible_approaches = []
for approach_index in approaches_i[runways]:
possible_approaches.push_back(rot_approaches[approach_index])
var has_runway = false
for approach in possible_approaches:
var app_r: int = spot_r + approach[0]
var app_c: int = spot_c + approach[1]
if app_r < 0 or app_r >= board_diam or app_c < 0 or app_c >= board_diam: continue # out of square map
if board[app_r][app_c] == null: continue # out of hex map
if board[app_r][app_c]["cell_type"] in [HILLS, MOUNTAINS]: continue # invalid approach square
has_runway = true
valid_approaches.push_back(approach)
if has_runway:
rot_okay = true
break
else:
rot += 1 # rotate 60 deg (effectively)
if not rot_okay:
available_board_coords.pop_at(spot_i)
continue
if not spot_okay:
return false # could not form valid map
var args = {"cell_type" : AIRPORT , "pos" : [spot_r, spot_c], "orientation" : rot, "airport_id" : airport_id, "runways" : runways, 'valid_approach_offsets' : valid_approaches, "use_names" : use_names}
if use_names:
args["airport_name"] = airport_display
else:
args["airport_color"] = airport_colors[airport_display[COLOR]]
args["airport_number"] = airport_display[NUMBER]
board[spot_r][spot_c] = args
available_board_coords.pop_at(spot_i)
airport_id += 1
return true
func get_json():
return board
func generate_board(hex_side_len: int, num_mountains : int, num_hills : int, num_airports : int, runway_count : int, use_names : bool = false) -> bool:
create_board_base(hex_side_len)
if not populate_board(num_mountains, num_hills, num_airports, runway_count, use_names):
reset_board()
print("Invalid board creation parameters")
return false
display_board()
return true
|