This repository has been archived by the owner on Feb 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRoom.cs
329 lines (309 loc) · 11.6 KB
/
Room.cs
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
using System;
using System.Collections.Generic;
namespace LevelGenerator
{
/// The types of rooms that a dungeon may have.
///
/// A normal room is a free access room that does not contain items.
/// A key room is a free access room that contains a key.
/// A locked room is a room that requires a key to be accessed by a player.
public enum RoomType
{
Normal = 0, // Normal room
Key = 1, // Room with a key
Locked = 2, // Locked room
};
/// This class represents a room of a dungeon.
public class Room
{
/// Counter of IDs (a sequential identifier) of rooms.
private static int ID = 0;
/// Calculate and return the next room ID.
public static int GetNextId()
{
return ID++;
}
/// 90 degree rotation.
private static readonly int DEGREE_90 = 90;
/// 360 degree rotation.
private static readonly int DEGREE_360 = 360;
/// The room ID.
public int id = -1;
/// The type of the room.
public RoomType type = RoomType.Normal;
/// The ID of the key that opens this room. The ID is equal to -1 when
/// the room is not locked and does not have a key.
public int key = -1;
/// The number of enemies in this room.
public int enemies = 0;
/// The depth of the room in the tree. This is used to control the
/// depth of the dungeon level.
public int depth = 0;
/// The x position of the room in the grid.
public int x = 0;
/// The y position of the room in the grid.
public int y = 0;
/// The rotation of the individual's parent position is related to the
/// normal cartesian orientation. 0 means that the parent is in the
/// North (above) of the child, 90 the parent is in the East (right),
/// and so on. This is used to build the dungeon grid.
public int rotation = 0;
/// The room's left child.
public Room left = null;
/// The room's bottom child.
public Room bottom = null;
/// The room's right child.
public Room right = null;
/// The room's parent.
public Room parent = null;
/// The direction from what the parent connects with this room.
/// This attribute reduces operations at crossover.
public Common.Direction parentDirection = Common.Direction.Down;
/// Room constructor.
///
/// The default is a normal room without a predefined room ID. The key
/// room defines its key to open as its room ID. The locked room
/// defines its key to open as the entered key to open.
public Room(
RoomType _type = RoomType.Normal,
int _key = -1,
int _id = -1
) {
type = _type;
id = _id == -1 ? Room.GetNextId() : _id;
key = type == RoomType.Key ? id : key;
key = type == RoomType.Locked ? _key : key;
}
/// Return a clone this room.
public Room Clone()
{
Room room = new Room(type, key, id);
room.enemies = enemies;
room.depth = depth;
room.x = x;
room.y = y;
room.rotation = rotation;
room.left = left;
room.bottom = bottom;
room.right = right;
room.parent = parent;
room.parentDirection = parentDirection;
return room;
}
/// Return an array with the left, bottom, and right children.
public Room[] GetChildren()
{
return new Room[] {
left,
bottom,
right,
};
}
/// Return an array with all the neighbors (parent and children).
public Room[] GetNeighbors()
{
return new Room[] {
parent,
left,
bottom,
right,
};
}
/// Return a tuple corresponding to the position in the dungeon grid of
/// the child of a parent in the entered direction.
private (int, int) GetChildPositionInGrid(
Common.Direction _dir
) {
int cx = 0;
int cy = 0;
int rot = (rotation / DEGREE_90) % 2;
switch (_dir)
{
case Common.Direction.Right:
if (rot != 0)
{
cx = x;
cy = rotation == DEGREE_90 ? y + 1 : y - 1;
}
else
{
cx = rotation == 0 ? x + 1 : x - 1;
cy = y;
}
break;
case Common.Direction.Down:
if (rot != 0)
{
cx = rotation == DEGREE_90 ? x + 1 : x - 1;
cy = y;
}
else
{
cx = x;
cy = rotation == 0 ? y - 1 : y + 1;
}
break;
case Common.Direction.Left:
if (rot != 0)
{
cx = x;
cy = rotation == DEGREE_90 ? y - 1 : y + 1;
}
else
{
cx = rotation == 0 ? x - 1 : x + 1;
cy = y;
}
break;
}
return (cx, cy);
}
/// Check if a room can have a new child in the entered direction.
///
/// Return true if a room can be placed as a child of the entered room
/// parent and in the entered position (i.e., the position in the grid
/// is empty), and false, otherwise.
public bool ValidateChild(
Common.Direction _dir,
RoomGrid _grid
) {
(int x, int y) = GetChildPositionInGrid(_dir);
return _grid[x, y] is null;
}
/// Insert the entered room in the dungeon (ternary heap and grid).
///
/// First, this method calculates both X and Y positions of the entered
/// child room based on the parent rotation and coordinates, and on the
/// direction of insertion. Then, it checks the position is empty in
/// the dungeon grid, if so, then, it places the entered child room in
/// the calculated position and rotation.
public void InsertChild(
ref RoomGrid _grid,
ref Room _child,
Common.Direction _dir
) {
(int x, int y) = GetChildPositionInGrid(_dir);
_child.x = x;
_child.y = y;
_child.rotation = (rotation + DEGREE_90) % DEGREE_360;
Room room = _grid[x, y];
if (room != null) { return; }
switch (_dir)
{
case Common.Direction.Right:
right = _child;
right.parent = this;
right.depth = depth + 1;
break;
case Common.Direction.Down:
bottom = _child;
bottom.parent = this;
bottom.depth = depth + 1;
break;
case Common.Direction.Left:
left = _child;
left.parent = this;
left.depth = depth + 1;
break;
}
}
/// Fix the branch that starts in this node.
///
/// This method must be called after a successful crossover. This fix
/// reinserts the mission rooms in the old branch to maintain the
/// occurring order of missions to guarantee feasibility.
public void FixBranch(
List<int> _missions,
ref Random _rand
) {
// If both lock and key are in the branch, assign to them new IDs,
// and add all the missions in the new missions list
Queue<int> newMissions = new Queue<int>();
for (int i = 0; i < _missions.Count - 1; i++)
{
for (int j = i + 1; j < _missions.Count; j++)
{
if (_missions[i] == -_missions[j])
{
int newId = Room.GetNextId();
_missions[i] = _missions[i] > 0 ? newId : -newId;
_missions[j] = -_missions[i];
}
}
newMissions.Enqueue(_missions[i]);
}
// Also add the last mission, if it exists
if (_missions.Count > 0)
{
newMissions.Enqueue(_missions[_missions.Count - 1]);
}
// Gather all the rooms of the branch
Queue<Room> branch = new Queue<Room>();
Queue<Room> toVisit = new Queue<Room>();
toVisit.Enqueue(this);
while (toVisit.Count > 0)
{
Room current = toVisit.Dequeue();
branch.Enqueue(current);
foreach (Room child in current.GetChildren())
{
if (child != null && current.Equals(child.parent))
{
toVisit.Enqueue(child);
}
}
}
// Place missions in the branch randomly while there are more rooms than missions; otherwise, this while stops
while (branch.Count > newMissions.Count)
{
Room current = branch.Dequeue();
int prob = Common.RandomPercent(ref _rand);
// If there are no missions left, then assign the remaining
// rooms as normal rooms; otherwise, check if the current room
// will not receive a mission
if (newMissions.Count == 0 ||
RoomFactory.PROB_NORMAL_ROOM > prob
) {
current.type = RoomType.Normal;
current.key = -1;
}
else
{
// The current room will receive a mission
int missionId = newMissions.Dequeue();
// Assign the mission ID to the current room
FixMission(ref current, missionId);
}
}
// If new missions are remaining, it means that the number of
// remaining rooms is the same as the number of mission rooms;
// thus, place the missions in those rooms
while (branch.Count > 0 && newMissions.Count > 0)
{
// The current room will receive a mission
Room current = branch.Dequeue();
int missionId = newMissions.Dequeue();
// Assign the mission ID to the current room
FixMission(ref current, missionId);
}
}
/// Auxiliary method for fixing the branch; it assigns the entered
/// mission to the entered room.
private void FixMission(
ref Room _room,
int _mission
) {
if (_mission > 0)
{
_room.type = RoomType.Key;
_room.id = _mission;
_room.key = _mission;
}
else
{
_room.type = RoomType.Locked;
_room.key = -_mission;
}
}
}
}