summaryrefslogtreecommitdiff
path: root/scripts/Board.gd
blob: 04f2d902aaaf1d9c7de993a3ec45e553d197317e (plain)
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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

const BOARD_GEN_ATTEMPTS : int = 5


# 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,3], [0,1,3,4], [0, 1, 2, 3, 4, 5] ]


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))
			add_child(new_cell)
			new_cell.set_up(row[c]) 
			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 ]
		used_airports.push_back(airport_display)
		# 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, "runway_count" : 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]].to_html()
			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_board():
	return self.board

func set_board(board_array):
	reset_board()
	self.board = board_array 
	display_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)
	var attempts : int = 1
	while attempts < BOARD_GEN_ATTEMPTS and (not populate_board(num_mountains, num_hills, num_airports, runway_count, use_names)):
		attempts += 1
		reset_board()
	
	if attempts >= BOARD_GEN_ATTEMPTS:
		reset_board()
		display_board()
		return false
	display_board()
	return true