@@ -12,6 +12,7 @@ namespace Z2Randomizer.RandomizerCore.Sidescroll;
1212
1313public abstract class ShapeFirstCoordinatePalaceGenerator ( ) : CoordinatePalaceGenerator ( )
1414{
15+ public const int FILL_SHAPE_TRIES = 10 ;
1516
1617 internal override async Task < Palace > GeneratePalace ( RandomizerProperties props , RoomPool rooms , Random r , int roomCount , int palaceNumber )
1718 {
@@ -47,168 +48,182 @@ internal override async Task<Palace> GeneratePalace(RandomizerProperties props,
4748 //prepopulatedCoordinates.Add(palace.AllRooms.First(i => i.IsThunderBirdRoom).coords);
4849
4950 //Add rooms
50- roomsByExitType = roomPool . CategorizeNormalRoomExits ( true ) ;
51- Dictionary < RoomExitType , bool > stubOnlyExitTypes = new ( ) ;
52- foreach ( KeyValuePair < Coord , RoomExitType > item in shape . OrderBy ( i => i . Key . X ) . ThenByDescending ( i => i . Key . Y ) )
51+ bool success = false ;
52+ for ( int i = 0 ; i < FILL_SHAPE_TRIES ; i ++ )
5353 {
54- if ( prepopulatedCoordinates . Contains ( item . Key ) )
54+ if ( await FillShape ( ) )
5555 {
56- continue ;
56+ success = true ;
57+ break ;
5758 }
58- var ( x , y ) = item . Key ;
59- RoomExitType roomExitType = item . Value ;
60-
61- bool stubOnly ;
62- List < Room > ? roomCandidates ;
63- Room ? newRoom = null ;
64- if ( ! stubOnlyExitTypes . TryGetValue ( roomExitType , out stubOnly ) )
65- {
66- roomsByExitType . TryGetValue ( roomExitType , out roomCandidates ) ;
67- stubOnly = roomCandidates == null || roomCandidates . Count == 0 ;
68- stubOnlyExitTypes [ roomExitType ] = stubOnly ;
69- }
70- else
71- {
72- roomCandidates = stubOnly ? null : roomsByExitType . GetValueOrDefault ( roomExitType ) ;
73- }
74- if ( ! stubOnly )
59+ }
60+ if ( ! success )
61+ {
62+ palace . IsValid = false ;
63+ return palace ;
64+ }
65+ async Task < bool > FillShape ( )
66+ {
67+ roomsByExitType = roomPool . CategorizeNormalRoomExits ( true ) ;
68+ Dictionary < RoomExitType , bool > stubOnlyExitTypes = new ( ) ;
69+ foreach ( KeyValuePair < Coord , RoomExitType > item in shape . OrderBy ( i => i . Key . X ) . ThenByDescending ( i => i . Key . Y ) )
7570 {
76- Debug . Assert ( roomCandidates != null ) ;
77- if ( duplicateProtection && roomCandidates ! . Count == 0 )
71+ if ( prepopulatedCoordinates . Contains ( item . Key ) )
72+ {
73+ continue ;
74+ }
75+ var ( x , y ) = item . Key ;
76+ RoomExitType roomExitType = item . Value ;
77+
78+ bool stubOnly ;
79+ List < Room > ? roomCandidates ;
80+ Room ? newRoom = null ;
81+ if ( ! stubOnlyExitTypes . TryGetValue ( roomExitType , out stubOnly ) )
82+ {
83+ roomsByExitType . TryGetValue ( roomExitType , out roomCandidates ) ;
84+ stubOnly = roomCandidates == null || roomCandidates . Count == 0 ;
85+ stubOnlyExitTypes [ roomExitType ] = stubOnly ;
86+ }
87+ else
7888 {
79- roomCandidates = roomPool . GetNormalRoomsForExitType ( roomExitType , true ) ;
80- Debug . Assert ( roomCandidates . Count ( ) > 0 ) ;
81- roomsByExitType [ roomExitType ] = roomCandidates ;
82- logger . Debug ( $ "RandomWalk ran out of rooms of exit type: { roomExitType } in palace { palaceNumber } . Starting to use duplicate rooms.") ;
89+ roomCandidates = stubOnly ? null : roomsByExitType . GetValueOrDefault ( roomExitType ) ;
8390 }
84- roomCandidates ! . FisherYatesShuffle ( r ) ;
85- Room ? upRoom = palace . AllRooms . FirstOrDefault ( i => i . coords == new Coord ( x , y + 1 ) ) ;
86- foreach ( Room roomCandidate in roomCandidates ! )
91+ if ( ! stubOnly )
8792 {
88- if ( upRoom == null || ! upRoom . HasDrop || roomCandidate . IsDropZone )
93+ Debug . Assert ( roomCandidates != null ) ;
94+ if ( duplicateProtection && roomCandidates ! . Count == 0 )
8995 {
90- Debug . Assert ( roomCandidate . IsNormalRoom ( ) ) ;
91- newRoom = roomCandidate ;
92- break ;
96+ roomCandidates = roomPool . GetNormalRoomsForExitType ( roomExitType , true ) ;
97+ Debug . Assert ( roomCandidates . Count ( ) > 0 ) ;
98+ roomsByExitType [ roomExitType ] = roomCandidates ;
99+ logger . Debug ( $ "RandomWalk ran out of rooms of exit type: { roomExitType } in palace { palaceNumber } . Starting to use duplicate rooms.") ;
93100 }
101+ roomCandidates ! . FisherYatesShuffle ( r ) ;
102+ Room ? upRoom = palace . AllRooms . FirstOrDefault ( i => i . coords == new Coord ( x , y + 1 ) ) ;
103+ foreach ( Room roomCandidate in roomCandidates ! )
104+ {
105+ if ( upRoom == null || ! upRoom . HasDrop || roomCandidate . IsDropZone )
106+ {
107+ Debug . Assert ( roomCandidate . IsNormalRoom ( ) ) ;
108+ newRoom = roomCandidate ;
109+ break ;
110+ }
111+ }
112+ if ( newRoom != null && duplicateProtection ) { roomPool . RemoveDuplicates ( props , newRoom ) ; }
94113 }
95- if ( newRoom != null && duplicateProtection ) { roomPool . RemoveDuplicates ( props , newRoom ) ; }
96- }
97114
98- if ( newRoom == null )
99- {
100- Room ? upRoom = palace . AllRooms . FirstOrDefault ( i => i . coords == new Coord ( x , y + 1 ) ) ;
101- roomPool . DefaultStubsByDirection . TryGetValue ( roomExitType , out newRoom ) ;
102- if ( newRoom != null && upRoom != null && upRoom . HasDrop && ! newRoom . IsDropZone )
115+ if ( newRoom == null )
103116 {
104- //We need to use a drop zone stub but one does not (and cannot) exist so this graph is doomed.
105- //Debug.WriteLine(GetLayoutDebug(walkGraph, false));
106- palace . IsValid = false ;
107- return palace ;
117+ Room ? upRoom = palace . AllRooms . FirstOrDefault ( i => i . coords == new Coord ( x , y + 1 ) ) ;
118+ roomPool . DefaultStubsByDirection . TryGetValue ( roomExitType , out newRoom ) ;
119+ if ( newRoom != null && upRoom != null && upRoom . HasDrop && ! newRoom . IsDropZone )
120+ {
121+ //We need to use a drop zone stub but one does not (and cannot) exist so this graph is doomed.
122+ //Debug.WriteLine(GetLayoutDebug(walkGraph, false));
123+ return false ;
124+ }
125+ }
126+ if ( newRoom == null )
127+ {
128+ return false ;
129+ }
130+ else
131+ {
132+ newRoom = new ( newRoom ) ;
108133 }
109- }
110- if ( newRoom == null )
111- {
112- palace . IsValid = false ;
113- return palace ;
114- }
115- else
116- {
117- newRoom = new ( newRoom ) ;
118- }
119134
120- newRoom . coords = item . Key ;
121- if ( newRoom . LinkedRoomName == null )
122- {
123- palace . AllRooms . Add ( newRoom ) ;
124- }
125- else
126- {
127- Room linkedRoom = new ( roomPool . LinkedRooms [ newRoom . LinkedRoomName ] ) ;
128- newRoom . LinkedRoom = linkedRoom ;
129- linkedRoom . LinkedRoom = newRoom ;
130- linkedRoom . coords = item . Key ;
131- Room mergedRoom = newRoom . Merge ( linkedRoom ) ;
132- palace . AllRooms . Add ( mergedRoom ) ;
135+ newRoom . coords = item . Key ;
136+ if ( newRoom . LinkedRoomName == null )
137+ {
138+ palace . AllRooms . Add ( newRoom ) ;
139+ }
140+ else
141+ {
142+ Room linkedRoom = new ( roomPool . LinkedRooms [ newRoom . LinkedRoomName ] ) ;
143+ newRoom . LinkedRoom = linkedRoom ;
144+ linkedRoom . LinkedRoom = newRoom ;
145+ linkedRoom . coords = item . Key ;
146+ Room mergedRoom = newRoom . Merge ( linkedRoom ) ;
147+ palace . AllRooms . Add ( mergedRoom ) ;
148+ }
133149 }
134- }
135-
136- //Connect adjacent rooms if they exist
137- foreach ( Room room in palace . AllRooms )
138- {
139- await Task . Yield ( ) ;
140- Room [ ] leftRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { X = room . coords . X - 1 } ) . ToArray ( ) ;
141- Room [ ] downRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { Y = room . coords . Y - 1 } ) . ToArray ( ) ;
142- Room [ ] upRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { Y = room . coords . Y + 1 } ) . ToArray ( ) ;
143- Room [ ] rightRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { X = room . coords . X + 1 } ) . ToArray ( ) ;
144150
145- foreach ( Room left in leftRooms )
151+ //Connect adjacent rooms if they exist
152+ foreach ( Room room in palace . AllRooms )
146153 {
147- if ( left != null && room . FitsWithLeft ( left ) > 0 )
154+ await Task . Yield ( ) ;
155+ Room [ ] leftRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { X = room . coords . X - 1 } ) . ToArray ( ) ;
156+ Room [ ] downRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { Y = room . coords . Y - 1 } ) . ToArray ( ) ;
157+ Room [ ] upRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { Y = room . coords . Y + 1 } ) . ToArray ( ) ;
158+ Room [ ] rightRooms = palace . AllRooms . Where ( i => i . coords == room . coords with { X = room . coords . X + 1 } ) . ToArray ( ) ;
159+
160+ foreach ( Room left in leftRooms )
148161 {
149- room . Left = left ;
150- left . Right = room ;
162+ if ( left != null && room . FitsWithLeft ( left ) > 0 )
163+ {
164+ room . Left = left ;
165+ left . Right = room ;
166+ }
151167 }
152- }
153168
154- foreach ( Room down in downRooms )
155- {
156- if ( down != null && room . FitsWithDown ( down ) > 0 )
169+ foreach ( Room down in downRooms )
157170 {
158- room . Down = down ;
159- if ( ! room . HasDrop )
171+ if ( down != null && room . FitsWithDown ( down ) > 0 )
160172 {
161- down . Up = room ;
173+ room . Down = down ;
174+ if ( ! room . HasDrop )
175+ {
176+ down . Up = room ;
177+ }
162178 }
163179 }
164- }
165- foreach ( Room up in upRooms )
166- {
167- if ( up != null && room . FitsWithUp ( up ) > 0 )
180+ foreach ( Room up in upRooms )
168181 {
169- if ( ! up . HasDrop )
182+ if ( up != null && room . FitsWithUp ( up ) > 0 )
170183 {
171- room . Up = up ;
184+ if ( ! up . HasDrop )
185+ {
186+ room . Up = up ;
187+ }
188+ up . Down = room ;
172189 }
173- up . Down = room ;
174190 }
175- }
176- foreach ( Room right in rightRooms )
177- {
178- if ( right != null && room . FitsWithRight ( right ) > 0 )
191+ foreach ( Room right in rightRooms )
179192 {
180- room . Right = right ;
181- right . Left = room ;
193+ if ( right != null && room . FitsWithRight ( right ) > 0 )
194+ {
195+ room . Right = right ;
196+ right . Left = room ;
197+ }
182198 }
183199 }
184- }
185200
186- ConnectNonEuclideanPaths ( palace ) ;
201+ ConnectNonEuclideanPaths ( palace ) ;
187202
188- //Some percentage of the time, dropifying some rooms causes part of the palace to become
189- //unreachable because up was the only way to get there.
190- if ( ! palace . AllReachable ( ) )
191- {
192- palace . IsValid = false ;
193- return palace ;
194- }
203+ //Some percentage of the time, dropifying some rooms causes part of the palace to become
204+ //unreachable because up was the only way to get there.
205+ if ( ! palace . AllReachable ( ) )
206+ {
207+ return false ;
208+ }
195209
196210
197- if ( ! AddSpecialRoomsByReplacement ( palace , roomPool , r , props , GetItemRoomSelectionStrategy ( ) ) )
198- {
199- palace . IsValid = false ;
200- return palace ;
201- }
211+ if ( ! AddSpecialRoomsByReplacement ( palace , roomPool , r , props , GetItemRoomSelectionStrategy ( ) ) )
212+ {
213+ return false ;
214+ }
202215
203- if ( palace . AllRooms . Count ( i => i . Enabled ) != roomCount )
204- {
205- throw new Exception ( "Generated palace has the incorrect number of rooms" ) ;
206- }
216+ if ( palace . AllRooms . Count ( i => i . Enabled ) != roomCount )
217+ {
218+ throw new Exception ( "Generated palace has the incorrect number of rooms" ) ;
219+ }
207220
208- if ( palace . HasDisallowedDrop ( props . BossRoomsExitToPalace [ palace . Number - 1 ] , props . PalaceDropStyle , r ) )
209- {
210- palace . IsValid = false ;
211- return palace ;
221+ if ( palace . HasDisallowedDrop ( props . BossRoomsExitToPalace [ palace . Number - 1 ] , props . PalaceDropStyle , r ) )
222+ {
223+ return false ;
224+ }
225+
226+ return true ;
212227 }
213228
214229 palace . AllRooms . ForEach ( i => i . PalaceNumber = palaceNumber ) ;
0 commit comments