├── wang1.png ├── wang2.png ├── TerrainGenDemo ├── bin │ └── data │ │ ├── glob.png │ │ ├── pcb.png │ │ ├── wang.png │ │ ├── ground.png │ │ ├── patch.png │ │ ├── seasand.png │ │ └── terrain.png ├── packages.config ├── App.config ├── OpenTK.dll.config ├── TerrainGenDemo.csproj └── Program.cs ├── WangTiles ├── bin │ └── data │ │ ├── tileset0.png │ │ ├── tileset1.png │ │ ├── tileset10.png │ │ ├── tileset11.png │ │ ├── tileset12.png │ │ ├── tileset13.png │ │ ├── tileset14.png │ │ ├── tileset15.png │ │ ├── tileset16.png │ │ ├── tileset17.png │ │ ├── tileset18.png │ │ ├── tileset19.png │ │ ├── tileset2.png │ │ ├── tileset20.png │ │ ├── tileset21.png │ │ ├── tileset22.png │ │ ├── tileset3.png │ │ ├── tileset4.png │ │ ├── tileset5.png │ │ ├── tileset6.png │ │ ├── tileset7.png │ │ ├── tileset8.png │ │ ├── tileset9.png │ │ ├── tileset0_16.png │ │ └── tileset0_32.png ├── packages.config ├── App.config ├── Properties │ └── AssemblyInfo.cs ├── OpenTK.dll.config ├── WangTilesDemo.csproj └── Program.cs ├── DungeonGenDemo ├── bin │ └── data │ │ ├── tileset0.png │ │ ├── tileset0_16.png │ │ └── tileset0_32.png ├── packages.config ├── App.config ├── OpenTK.dll.config ├── DungeonGenDemo.csproj └── Program.cs ├── WangTileLib ├── packages.config ├── Properties │ └── AssemblyInfo.cs ├── OpenTK.dll.config ├── WangTileLib.csproj ├── Tileset.cs ├── MathUtility.cs ├── WangUtils.cs └── DungeonPlanner.cs ├── .gitignore ├── LICENSE ├── README.md └── WangTiles.sln /wang1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/wang1.png -------------------------------------------------------------------------------- /wang2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/wang2.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/glob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/glob.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/pcb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/pcb.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/wang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/wang.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset0.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset1.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset10.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset11.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset12.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset13.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset14.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset15.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset16.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset17.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset18.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset19.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset2.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset20.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset21.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset22.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset3.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset4.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset5.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset6.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset7.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset8.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset9.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/ground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/ground.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/patch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/patch.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset0_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset0_16.png -------------------------------------------------------------------------------- /WangTiles/bin/data/tileset0_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/WangTiles/bin/data/tileset0_32.png -------------------------------------------------------------------------------- /DungeonGenDemo/bin/data/tileset0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/DungeonGenDemo/bin/data/tileset0.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/seasand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/seasand.png -------------------------------------------------------------------------------- /TerrainGenDemo/bin/data/terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/TerrainGenDemo/bin/data/terrain.png -------------------------------------------------------------------------------- /DungeonGenDemo/bin/data/tileset0_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/DungeonGenDemo/bin/data/tileset0_16.png -------------------------------------------------------------------------------- /DungeonGenDemo/bin/data/tileset0_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Relfos/WangTiles/master/DungeonGenDemo/bin/data/tileset0_32.png -------------------------------------------------------------------------------- /WangTileLib/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /WangTiles/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /DungeonGenDemo/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /TerrainGenDemo/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /WangTiles/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /DungeonGenDemo/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /TerrainGenDemo/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | WangTiles/bin/Debug/ 2 | WangTiles/obj/ 3 | packages/ 4 | .vs/ 5 | DungeonGenDemo/bin/Debug/ 6 | TerrainGenDemo/bin/Debug/ 7 | WangTileLib/bin/ 8 | WangTileLib/obj/ 9 | TerrainGenDemo/obj/ 10 | DungeonGenDemo/obj/ 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wang Tiles 2 | ============ 3 | 4 | Implements a random map generator using Wang Tiles, in C#. 5 | 6 | * Author: [Sérgio Flores](https://github.com/relfos) 7 | * License: [MIT](https://opensource.org/licenses/MIT) 8 | * [Reporting Issues](https://github.com/relfos/WangTiles/issues) 9 | * Support can be obtained via [Email](mailto:sergio.flores@lunarlabs.pt) 10 | * If you require some specific feature please contact for a quote. 11 | 12 | ============ 13 | Wang Tiles are a very simple but useful concept that can be used to generate an infinite set of connecting tiles. 14 | This small project was implemented based on the information found [here](http://s358455341.websitehome.co.uk/stagecast/wang/intro.html). 15 | 16 | 17 | Note that is just a small proof of concept done in 1 hour, using C# and OpenTK for rendering the output (you can easily swap it out for any other rendering system, the algoritm only gives you a list of tile IDs). 18 | 19 | 20 | When running the sample, you can change the tileset using the number keys. 21 | 22 | A sample output, using a simple tileset with roads. 23 | 24 | ![Sample Output](/wang1.png) 25 | 26 | ============ 27 | Same map, using a different tileset that shows tile IDs. 28 | ![Numbers Output](/wang2.png) 29 | -------------------------------------------------------------------------------- /WangTiles/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WangTiles")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WangTiles")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("729fb5b7-138f-48df-9bde-7fa3e72bc373")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WangTileLib/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("WangTileLib")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("WangTileLib")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f179fa9f-2e56-429e-9f1a-aba91fa6c9ce")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /WangTileLib/OpenTK.dll.config: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /WangTiles/OpenTK.dll.config: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /DungeonGenDemo/OpenTK.dll.config: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /TerrainGenDemo/OpenTK.dll.config: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /WangTiles.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WangTilesDemo", "WangTiles\WangTilesDemo.csproj", "{729FB5B7-138F-48DF-9BDE-7FA3E72BC373}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WangTileLib", "WangTileLib\WangTileLib.csproj", "{F179FA9F-2E56-429E-9F1A-ABA91FA6C9CE}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DungeonGenDemo", "DungeonGenDemo\DungeonGenDemo.csproj", "{98659412-205D-456C-A8EB-D4E3608B8DEB}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TerrainGenDemo", "TerrainGenDemo\TerrainGenDemo.csproj", "{90B6F5E9-CF85-45E4-B8C6-D05313A4309A}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {729FB5B7-138F-48DF-9BDE-7FA3E72BC373}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {729FB5B7-138F-48DF-9BDE-7FA3E72BC373}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {729FB5B7-138F-48DF-9BDE-7FA3E72BC373}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {729FB5B7-138F-48DF-9BDE-7FA3E72BC373}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {F179FA9F-2E56-429E-9F1A-ABA91FA6C9CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {F179FA9F-2E56-429E-9F1A-ABA91FA6C9CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {F179FA9F-2E56-429E-9F1A-ABA91FA6C9CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {F179FA9F-2E56-429E-9F1A-ABA91FA6C9CE}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {98659412-205D-456C-A8EB-D4E3608B8DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {98659412-205D-456C-A8EB-D4E3608B8DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {98659412-205D-456C-A8EB-D4E3608B8DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {98659412-205D-456C-A8EB-D4E3608B8DEB}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {90B6F5E9-CF85-45E4-B8C6-D05313A4309A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {90B6F5E9-CF85-45E4-B8C6-D05313A4309A}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {90B6F5E9-CF85-45E4-B8C6-D05313A4309A}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {90B6F5E9-CF85-45E4-B8C6-D05313A4309A}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /DungeonGenDemo/DungeonGenDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {98659412-205D-456C-A8EB-D4E3608B8DEB} 8 | Exe 9 | Properties 10 | DungeonGenDemo 11 | DungeonGenDemo 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll 46 | True 47 | 48 | 49 | 50 | 51 | 52 | 53 | {f179fa9f-2e56-429e-9f1a-aba91fa6c9ce} 54 | WangTileLib 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /TerrainGenDemo/TerrainGenDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {90B6F5E9-CF85-45E4-B8C6-D05313A4309A} 8 | Exe 9 | Properties 10 | TerrainGenDemo 11 | TerrainGenDemo 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | ..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll 46 | True 47 | 48 | 49 | 50 | 51 | 52 | 53 | {f179fa9f-2e56-429e-9f1a-aba91fa6c9ce} 54 | WangTileLib 55 | 56 | 57 | 58 | 65 | -------------------------------------------------------------------------------- /WangTileLib/WangTileLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {F179FA9F-2E56-429E-9F1A-ABA91FA6C9CE} 8 | Library 9 | Properties 10 | WangTileLib 11 | WangTileLib 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll 35 | True 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 | 67 | -------------------------------------------------------------------------------- /WangTiles/WangTilesDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {729FB5B7-138F-48DF-9BDE-7FA3E72BC373} 8 | Exe 9 | Properties 10 | WangTiles 11 | WangTiles 12 | v4.5.2 13 | 512 14 | true 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\packages\OpenTK.2.0.0\lib\net20\OpenTK.dll 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {f179fa9f-2e56-429e-9f1a-aba91fa6c9ce} 62 | WangTileLib 63 | 64 | 65 | 66 | 73 | -------------------------------------------------------------------------------- /WangTileLib/Tileset.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Drawing; 3 | using WangTiles.Core; 4 | 5 | namespace WangTiles.Utils 6 | { 7 | public class Tileset 8 | { 9 | private Dictionary> tiles = new Dictionary>(); 10 | private int _tileSize; 11 | public int TileSize { get { return _tileSize; } } 12 | 13 | public Tileset(string fileName) 14 | { 15 | var source = new Bitmap(fileName); 16 | 17 | this._tileSize = source.Width / 16; 18 | 19 | for (int i = 0; i < 16; i++) 20 | { 21 | List variations = new List(); 22 | 23 | int maxVariations = source.Height / _tileSize; 24 | for (int j = 0; j < maxVariations; j++) 25 | { 26 | Bitmap tile = new Bitmap(_tileSize, _tileSize); 27 | bool isEmpty = true; 28 | for (int y = 0; y < _tileSize; y++) 29 | { 30 | for (int x = 0; x < _tileSize; x++) 31 | { 32 | var color = source.GetPixel(x + i * _tileSize, y + j * _tileSize); 33 | if (color.A <= 0) 34 | { 35 | continue; 36 | } 37 | 38 | isEmpty = false; 39 | tile.SetPixel(x, y, color); 40 | } 41 | } 42 | 43 | if (isEmpty) 44 | { 45 | break; 46 | } 47 | 48 | variations.Add(tile); 49 | } 50 | 51 | tiles[i] = variations; 52 | } 53 | } 54 | 55 | 56 | /// 57 | /// Draws a tile in the texture buffer at specified position 58 | /// 59 | private void DrawTile(byte[] buffer, int bufferWidth, int bufferHeight, int targetX, int targetY, int tileID, int variation, Color borderColor, int drawScale) 60 | { 61 | var variations = tiles[tileID]; 62 | Bitmap tile = variations[variation % variations.Count]; 63 | 64 | for (int y = 0; y < _tileSize; y++) 65 | { 66 | for (int x = 0; x < _tileSize; x++) 67 | { 68 | var c = tile.GetPixel(x, y); 69 | 70 | if (borderColor.A > 0 && (x == 0 || y == 0 || x == _tileSize - 1 || y == _tileSize - 1)) 71 | { 72 | c = borderColor; 73 | } 74 | 75 | for (int iy = 0; iy < drawScale; iy++) 76 | { 77 | for (int ix = 0; ix < drawScale; ix++) 78 | { 79 | int tx = targetX + x * drawScale + ix; 80 | int ty = targetY + y * drawScale + iy; 81 | if (tx >= bufferWidth || tx < 0) { continue; } 82 | if (ty >= bufferHeight || ty < 0) { continue; } 83 | 84 | int destOfs = (tx + bufferWidth * ty) * 4; 85 | buffer[destOfs + 0] = c.R; 86 | buffer[destOfs + 1] = c.G; 87 | buffer[destOfs + 2] = c.B; 88 | buffer[destOfs + 3] = c.A; 89 | } 90 | } 91 | } 92 | } 93 | } 94 | 95 | public void RedrawWithTileset(byte[] buffer, int bufferWidth, int bufferHeight, WangMap map, bool drawBorders, int drawScale) 96 | { 97 | for (int j = 0; j < map.Height; j++) 98 | { 99 | for (int i = 0; i < map.Width; i++) 100 | { 101 | var tile = map.GetTileAt(i, j); 102 | if (tile.tileID < 0) { continue; } 103 | 104 | int variation = tile.variationID; 105 | 106 | DrawTile(buffer, bufferWidth, bufferHeight, i * _tileSize * drawScale, j * _tileSize * drawScale, tile.tileID, variation, drawBorders ? WangArea.GetColor(tile.areaID) : Color.FromArgb(0), drawScale); 107 | } 108 | } 109 | } 110 | 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /TerrainGenDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OpenTK.Graphics.OpenGL; 4 | using OpenTK.Input; 5 | using System.Drawing; 6 | using System.Net; 7 | using WangTiles.Utils; 8 | using WangTiles.Core; 9 | 10 | namespace WangTiles 11 | { 12 | public class LayoutGenExample 13 | { 14 | #region TEXTURE_BUFFER_UTILS 15 | private static int bufferWidth = 600; 16 | private static int bufferHeight = 400; 17 | private static byte[] buffer = new byte[bufferWidth * bufferHeight * 4]; 18 | 19 | static void UpdateBuffer(byte[] buffer, int width, int height, int texID) 20 | { 21 | GL.BindTexture(TextureTarget.Texture2D, texID); 22 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer); 23 | } 24 | 25 | public static void DrawBuffer(OpenTK.GameWindow game, int textureID) 26 | { 27 | float u1 = 0; 28 | float u2 = 1; 29 | float v1 = 0; 30 | float v2 = 1; 31 | 32 | float w = 1; 33 | float h = 1; 34 | 35 | 36 | float px = 0; 37 | float py = 0; 38 | 39 | GL.Enable(EnableCap.Texture2D); 40 | GL.BindTexture(TextureTarget.Texture2D, textureID); 41 | 42 | GL.Begin(PrimitiveType.Quads); 43 | 44 | GL.Color3(Color.White); 45 | GL.TexCoord2(u1, v1); 46 | GL.Vertex2(px + 0, py + h); 47 | 48 | GL.TexCoord2(u1, v2); 49 | GL.Vertex2(px + 0, py + 0); 50 | 51 | GL.TexCoord2(u2, v2); 52 | GL.Vertex2(px + w, py + 0); 53 | 54 | GL.TexCoord2(u2, v1); 55 | GL.Vertex2(px + w, py + h); 56 | 57 | GL.End(); 58 | 59 | GL.Disable(EnableCap.Texture2D); 60 | } 61 | #endregion 62 | 63 | 64 | 65 | private static void DownloadTileset(string name) 66 | { 67 | using (var client = new WebClient()) 68 | { 69 | Bitmap target = new Bitmap(512, 32); 70 | for (int i = 0; i < 16; i++) 71 | { 72 | var url = "http://cr31.co.uk/stagecast/art/corn/" + name + "/" + i + ".gif"; 73 | var tempName = "temp" + i + ".gif"; 74 | client.DownloadFile(url, tempName); 75 | Bitmap temp = new Bitmap(tempName); 76 | Graphics g = Graphics.FromImage(target); 77 | g.DrawImage(temp, i * 32, 0); 78 | } 79 | target.Save(@"..\data\"+name+".png"); 80 | } 81 | } 82 | 83 | 84 | public static void Main(string[] args) 85 | { 86 | //DownloadTileset("glob"); 87 | 88 | bool drawBorders = false; 89 | int drawScale = 1; 90 | 91 | var tileset = new Tileset("../data/seasand.png"); 92 | 93 | #region WANG_GENERATION 94 | var map = new WangCornerMap(18, 12, 1424); 95 | 96 | // not everything needs to be random, we can pre-fill parts of the map with our own data 97 | for (int j = 2; j <= 6; j++) 98 | { 99 | for (int i = 2; i <= 6; i++) 100 | { 101 | map.SetTileIDAt(i, j, 0); 102 | } 103 | } 104 | 105 | for (int j = 8; j <= 11; j++) 106 | { 107 | for (int i = 8; i <= 14; i++) 108 | { 109 | map.SetTileIDAt(i, j, 15); 110 | } 111 | } 112 | 113 | // now fill the missing tiles randomly 114 | map.Generate(); 115 | map.Invert(); 116 | #endregion 117 | 118 | // now render the map to a pixel array 119 | tileset.RedrawWithTileset(buffer, bufferWidth, bufferHeight, map, drawBorders, drawScale); 120 | 121 | int bufferTexID = 0; 122 | 123 | int selX = -1; 124 | int selY = -1; 125 | 126 | using (var game = new OpenTK.GameWindow(bufferWidth, bufferHeight, OpenTK.Graphics.GraphicsMode.Default, "Wang Tiles")) 127 | { 128 | game.Load += (sender, e) => 129 | { 130 | // setup settings, load textures, sounds 131 | game.VSync = OpenTK.VSyncMode.Off; 132 | 133 | // generate a texture and copy the pixel array there 134 | bufferTexID = GL.GenTexture(); 135 | UpdateBuffer(buffer, bufferWidth, bufferHeight, bufferTexID); 136 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); 137 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); 138 | }; 139 | 140 | game.Resize += (sender, e) => 141 | { 142 | GL.Viewport(0, 0, game.Width, game.Height); 143 | }; 144 | 145 | 146 | game.Mouse.Move += (object sender, MouseMoveEventArgs mouseEvent) => 147 | { 148 | var mousePos = mouseEvent.Position; 149 | //mousePos = game.PointToClient(mousePos); 150 | 151 | //Console.WriteLine(mousePos.X + " " + mousePos.Y); 152 | 153 | selX = (mousePos.X) / (drawScale * tileset.TileSize); 154 | selY = (mousePos.Y) / (drawScale * tileset.TileSize); 155 | 156 | if (selX >= map.Width) { selX = -1; } 157 | if (selY >= map.Height) { selY = -1; } 158 | }; 159 | 160 | game.Mouse.ButtonDown += (object sender, MouseButtonEventArgs buttonEvent) => { 161 | }; 162 | 163 | game.UpdateFrame += (sender, e) => 164 | { 165 | if (game.Keyboard[Key.Escape]) 166 | { 167 | game.Exit(); 168 | Environment.Exit(0); 169 | } 170 | 171 | }; 172 | 173 | 174 | game.RenderFrame += (sender, e) => 175 | { 176 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 177 | 178 | GL.MatrixMode(MatrixMode.Projection); 179 | GL.LoadIdentity(); 180 | GL.Ortho(0.0, 1.0, 0.0, 1.0, 0.0, 4.0); 181 | 182 | 183 | // draw the texture to the screen, stretched to fill the whole window 184 | DrawBuffer(game, bufferTexID); 185 | 186 | 187 | game.SwapBuffers(); 188 | }; 189 | 190 | game.Run(); 191 | } 192 | } 193 | 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /WangTiles/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OpenTK.Graphics.OpenGL; 4 | using OpenTK.Input; 5 | using System.Drawing; 6 | using System.Net; 7 | using WangTiles.Utils; 8 | using WangTiles.Core; 9 | 10 | namespace WangTiles 11 | { 12 | public class LayoutGenExample 13 | { 14 | #region TEXTURE_BUFFER_UTILS 15 | private static int bufferWidth = 600; 16 | private static int bufferHeight = 400; 17 | private static byte[] buffer = new byte[bufferWidth * bufferHeight * 4]; 18 | 19 | static void UpdateBuffer(byte[] buffer, int width, int height, int texID) 20 | { 21 | GL.BindTexture(TextureTarget.Texture2D, texID); 22 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer); 23 | } 24 | 25 | public static void DrawBuffer(OpenTK.GameWindow game, int textureID) 26 | { 27 | float u1 = 0; 28 | float u2 = 1; 29 | float v1 = 0; 30 | float v2 = 1; 31 | 32 | float w = 1; 33 | float h = 1; 34 | 35 | 36 | float px = 0; 37 | float py = 0; 38 | 39 | GL.Enable(EnableCap.Texture2D); 40 | GL.BindTexture(TextureTarget.Texture2D, textureID); 41 | 42 | GL.Begin(PrimitiveType.Quads); 43 | 44 | GL.Color3(Color.White); 45 | GL.TexCoord2(u1, v1); 46 | GL.Vertex2(px + 0, py + h); 47 | 48 | GL.TexCoord2(u1, v2); 49 | GL.Vertex2(px + 0, py + 0); 50 | 51 | GL.TexCoord2(u2, v2); 52 | GL.Vertex2(px + w, py + 0); 53 | 54 | GL.TexCoord2(u2, v1); 55 | GL.Vertex2(px + w, py + h); 56 | 57 | GL.End(); 58 | 59 | GL.Disable(EnableCap.Texture2D); 60 | } 61 | #endregion 62 | 63 | 64 | 65 | private static void DownloadTileset(string name) 66 | { 67 | using (var client = new WebClient()) 68 | { 69 | Bitmap target = new Bitmap(512, 32); 70 | for (int i = 0; i < 16; i++) 71 | { 72 | var url = "http://s358455341.websitehome.co.uk/stagecast/art/edge/" + name + "/" + i + ".gif"; 73 | var tempName = "temp" + i + ".gif"; 74 | client.DownloadFile(url, tempName); 75 | Bitmap temp = new Bitmap(tempName); 76 | Graphics g = Graphics.FromImage(target); 77 | g.DrawImage(temp, i * 32, 0); 78 | } 79 | target.Save("tileset.png"); 80 | } 81 | } 82 | 83 | 84 | public static void Main(string[] args) 85 | { 86 | //DownloadTileset("walkway"); 87 | 88 | bool drawBorders = false; 89 | int drawScale = 4; 90 | 91 | var tilesets = new List(); 92 | // load tilesets 93 | for (int i=0; i<=9; i++) 94 | { 95 | var tileset = new Tileset("../data/tileset"+i+".png"); 96 | tilesets.Add(tileset); 97 | } 98 | 99 | 100 | int exitX = 1; 101 | int exitY = -1; 102 | 103 | #region WANG_GENERATION 104 | var map = new WangEdgeMap(14, 9, 3424); 105 | map.AddExit(exitX, exitY); 106 | map.Generate(); 107 | map.FixConnectivity(); 108 | #endregion 109 | 110 | 111 | // now render the map to a pixel array 112 | int currentTileset = 0; 113 | tilesets[currentTileset].RedrawWithTileset(buffer, bufferWidth, bufferHeight, map, drawBorders, drawScale); 114 | 115 | 116 | int bufferTexID = 0; 117 | 118 | int selX = -1; 119 | int selY = -1; 120 | 121 | using (var game = new OpenTK.GameWindow(bufferWidth, bufferHeight, OpenTK.Graphics.GraphicsMode.Default, "Wang Tiles")) 122 | { 123 | game.Load += (sender, e) => 124 | { 125 | // setup settings, load textures, sounds 126 | game.VSync = OpenTK.VSyncMode.Off; 127 | 128 | // generate a texture and copy the pixel array there 129 | bufferTexID = GL.GenTexture(); 130 | UpdateBuffer(buffer, bufferWidth, bufferHeight, bufferTexID); 131 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); 132 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); 133 | }; 134 | 135 | game.Resize += (sender, e) => 136 | { 137 | GL.Viewport(0, 0, game.Width, game.Height); 138 | }; 139 | 140 | 141 | game.Mouse.Move += (object sender, MouseMoveEventArgs mouseEvent) => 142 | { 143 | var mousePos = mouseEvent.Position; 144 | //mousePos = game.PointToClient(mousePos); 145 | 146 | //Console.WriteLine(mousePos.X + " " + mousePos.Y); 147 | 148 | selX = (mousePos.X) / (drawScale * tilesets[currentTileset].TileSize); 149 | selY = (mousePos.Y) / (drawScale * tilesets[currentTileset].TileSize); 150 | 151 | if (selX >= map.Width) { selX = -1; } 152 | if (selY >= map.Height) { selY = -1; } 153 | }; 154 | 155 | game.Mouse.ButtonDown += (object sender, MouseButtonEventArgs buttonEvent) => { 156 | }; 157 | 158 | game.UpdateFrame += (sender, e) => 159 | { 160 | if (game.Keyboard[Key.Escape]) 161 | { 162 | game.Exit(); 163 | Environment.Exit(0); 164 | } 165 | 166 | int oldTileset = currentTileset; 167 | if (game.Keyboard[Key.Number1]) 168 | { 169 | currentTileset = 0; 170 | } 171 | if (game.Keyboard[Key.Number2]) 172 | { 173 | currentTileset = 1; 174 | } 175 | if (game.Keyboard[Key.Number3]) 176 | { 177 | currentTileset = 2; 178 | } 179 | if (game.Keyboard[Key.Number4]) 180 | { 181 | currentTileset = 3; 182 | } 183 | if (game.Keyboard[Key.Number5]) 184 | { 185 | currentTileset = 4; 186 | } 187 | if (game.Keyboard[Key.Number6]) 188 | { 189 | currentTileset = 5; 190 | } 191 | 192 | if (game.Keyboard[Key.Number7]) 193 | { 194 | currentTileset = 6; 195 | } 196 | 197 | if (game.Keyboard[Key.Number8]) 198 | { 199 | currentTileset = 7; 200 | } 201 | 202 | if (game.Keyboard[Key.Number9]) 203 | { 204 | currentTileset = 8; 205 | } 206 | 207 | if (oldTileset != currentTileset) 208 | { 209 | tilesets[currentTileset].RedrawWithTileset(buffer, bufferWidth, bufferHeight, map, drawBorders, drawScale); 210 | UpdateBuffer(buffer, bufferWidth, bufferHeight, bufferTexID); 211 | } 212 | }; 213 | 214 | 215 | game.RenderFrame += (sender, e) => 216 | { 217 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 218 | 219 | GL.MatrixMode(MatrixMode.Projection); 220 | GL.LoadIdentity(); 221 | GL.Ortho(0.0, 1.0, 0.0, 1.0, 0.0, 4.0); 222 | 223 | 224 | // draw the texture to the screen, stretched to fill the whole window 225 | DrawBuffer(game, bufferTexID); 226 | 227 | 228 | game.SwapBuffers(); 229 | }; 230 | 231 | game.Run(); 232 | } 233 | } 234 | 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /DungeonGenDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using OpenTK.Graphics.OpenGL; 4 | using OpenTK.Input; 5 | using System.Drawing; 6 | using System.Net; 7 | using WangTiles.Utils; 8 | using WangTiles.DungeonPlanner; 9 | using WangTiles.Core; 10 | 11 | namespace DungeonDemo 12 | { 13 | public class DungeonExample 14 | { 15 | #region TEXTURE_BUFFER_UTILS 16 | private static int bufferWidth = 600; 17 | private static int bufferHeight = 400; 18 | private static byte[] buffer = new byte[bufferWidth * bufferHeight * 4]; 19 | 20 | static void UpdateBuffer(byte[] buffer, int width, int height, int texID) 21 | { 22 | GL.BindTexture(TextureTarget.Texture2D, texID); 23 | GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgba, PixelType.UnsignedByte, buffer); 24 | } 25 | 26 | public static void DrawBuffer(OpenTK.GameWindow game, int textureID) 27 | { 28 | float u1 = 0; 29 | float u2 = 1; 30 | float v1 = 0; 31 | float v2 = 1; 32 | 33 | float w = 1; 34 | float h = 1; 35 | 36 | 37 | float px = 0; 38 | float py = 0; 39 | 40 | GL.Enable(EnableCap.Texture2D); 41 | GL.BindTexture(TextureTarget.Texture2D, textureID); 42 | 43 | GL.Begin(PrimitiveType.Quads); 44 | 45 | GL.Color3(Color.White); 46 | GL.TexCoord2(u1, v1); 47 | GL.Vertex2(px + 0, py + h); 48 | 49 | GL.TexCoord2(u1, v2); 50 | GL.Vertex2(px + 0, py + 0); 51 | 52 | GL.TexCoord2(u2, v2); 53 | GL.Vertex2(px + w, py + 0); 54 | 55 | GL.TexCoord2(u2, v1); 56 | GL.Vertex2(px + w, py + h); 57 | 58 | GL.End(); 59 | 60 | GL.Disable(EnableCap.Texture2D); 61 | } 62 | #endregion 63 | 64 | public static void Main(string[] args) 65 | { 66 | //DownloadTileset("walkway"); 67 | 68 | bool drawBorders = false; 69 | int drawScale = 4; 70 | 71 | var tileset = new Tileset("../data/tileset0.png"); 72 | 73 | int exitX = 1; 74 | int exitY = -1; 75 | 76 | #region WANG_GENERATION 77 | var map = new WangEdgeMap(14, 9, 3424); 78 | map.AddExit(exitX, exitY); 79 | map.Generate(); 80 | map.FixConnectivity(); 81 | #endregion 82 | 83 | #region DUNGEON_PLANNING 84 | LayoutPlanner planner = new LayoutPlanner(4343); 85 | for (int j = 0; j < map.Height; j++) 86 | { 87 | for (int i = 0; i < map.Width; i++) 88 | { 89 | var tile = map.GetTileAt(i, j); 90 | int tileID = tile.tileID; 91 | if (tileID <= 0) 92 | { 93 | continue; 94 | } 95 | 96 | bool north, south, east, west; 97 | WangEdgeUtils.GetConnectionsForTile(tileID, out north, out east, out south, out west); 98 | 99 | if (north) { planner.AddConnection(new LayoutCoord(i, j), WangEdgeDirection.North); } 100 | if (south) { planner.AddConnection(new LayoutCoord(i, j), WangEdgeDirection.South); } 101 | if (east) { planner.AddConnection(new LayoutCoord(i, j), WangEdgeDirection.East); } 102 | if (west) { planner.AddConnection(new LayoutCoord(i, j), WangEdgeDirection.West); } 103 | 104 | var room = planner.FindRoomAt(new LayoutCoord(i, j)); 105 | room.tileID = tileID; 106 | room.variationID = tile.variationID; 107 | } 108 | } 109 | planner.entrance = planner.FindRoomAt(new LayoutCoord(exitX, exitY)); 110 | Console.WriteLine("Selected entrance: " + planner.entrance); 111 | 112 | var goal = planner.FindGoal(); 113 | 114 | goal = goal.previous; 115 | while (goal.GetShape() == LayoutRoom.RoomShape.Corridor) 116 | { 117 | goal = goal.previous; 118 | } 119 | 120 | planner.SetGoal(goal); 121 | Console.WriteLine("Selected goal: " + goal); 122 | 123 | List keys = new List(); 124 | keys.Add(new LayoutKey("Copper", 0)); 125 | keys.Add(new LayoutKey("Bronze", 1)); 126 | keys.Add(new LayoutKey("Silver", 2)); 127 | keys.Add(new LayoutKey("Gold", 3)); 128 | 129 | planner.GenerateProgression(); 130 | planner.GenerateRoomTypes(); 131 | planner.GenerateLocks(keys); 132 | #endregion 133 | 134 | 135 | // now render the map to a pixel array 136 | tileset.RedrawWithTileset(buffer, bufferWidth, bufferHeight, map, drawBorders, drawScale); 137 | 138 | 139 | int bufferTexID = 0; 140 | 141 | int selX = -1; 142 | int selY = -1; 143 | 144 | using (var game = new OpenTK.GameWindow(bufferWidth, bufferHeight, OpenTK.Graphics.GraphicsMode.Default, "Wang Tiles")) 145 | { 146 | game.Load += (sender, e) => 147 | { 148 | // setup settings, load textures, sounds 149 | game.VSync = OpenTK.VSyncMode.Off; 150 | 151 | // generate a texture and copy the pixel array there 152 | bufferTexID = GL.GenTexture(); 153 | UpdateBuffer(buffer, bufferWidth, bufferHeight, bufferTexID); 154 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); 155 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); 156 | }; 157 | 158 | game.Resize += (sender, e) => 159 | { 160 | GL.Viewport(0, 0, game.Width, game.Height); 161 | }; 162 | 163 | 164 | game.Mouse.Move += (object sender, MouseMoveEventArgs mouseEvent) => 165 | { 166 | var mousePos = mouseEvent.Position; 167 | //mousePos = game.PointToClient(mousePos); 168 | 169 | //Console.WriteLine(mousePos.X + " " + mousePos.Y); 170 | 171 | selX = (mousePos.X) / (drawScale * tileset.TileSize); 172 | selY = (mousePos.Y) / (drawScale * tileset.TileSize); 173 | 174 | if (selX >= map.Width) { selX = -1; } 175 | if (selY >= map.Height) { selY = -1; } 176 | }; 177 | 178 | game.Mouse.ButtonDown += (object sender, MouseButtonEventArgs buttonEvent) => { 179 | }; 180 | 181 | game.UpdateFrame += (sender, e) => 182 | { 183 | if (game.Keyboard[Key.Escape]) 184 | { 185 | game.Exit(); 186 | Environment.Exit(0); 187 | } 188 | 189 | }; 190 | 191 | 192 | game.RenderFrame += (sender, e) => 193 | { 194 | GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); 195 | 196 | GL.MatrixMode(MatrixMode.Projection); 197 | GL.LoadIdentity(); 198 | GL.Ortho(0.0, 1.0, 0.0, 1.0, 0.0, 4.0); 199 | 200 | 201 | // draw the texture to the screen, stretched to fill the whole window 202 | DrawBuffer(game, bufferTexID); 203 | 204 | 205 | if (selX >= 0 && selY >= 0) 206 | { 207 | var selRoom = planner.FindRoomAt(new LayoutCoord(selX, selY), false); 208 | if (selRoom != null) 209 | { 210 | int percent = ((int)(selRoom.intensity * 100)); 211 | string s = selRoom.order + "# " + selRoom.ToString(); 212 | string s2 = selRoom.GetShape() + "/" + selRoom.category + "(" + selRoom.distanceFromMainPath + ") " + percent + "%"; 213 | 214 | if (selRoom.contains != null) 215 | { 216 | s += "(Contains " + selRoom.contains + ")"; 217 | } 218 | 219 | if (selRoom.locked != null) 220 | { 221 | s += "(Requires " + selRoom.locked + ")"; 222 | } 223 | 224 | if (selRoom.isLoop) 225 | { 226 | s += "(Loop)"; 227 | } 228 | 229 | FontUtils.DrawText(game, s, 4, 20, 0.6f, Color.White); 230 | FontUtils.DrawText(game, s2, 4, 0, 0.6f, Color.White); 231 | } 232 | } 233 | 234 | game.SwapBuffers(); 235 | }; 236 | 237 | game.Run(); 238 | } 239 | } 240 | 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /WangTileLib/MathUtility.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_5 2 | #define UNITY 3 | #endif 4 | 5 | #if UNITY 6 | using System; 7 | using UnityEngine; 8 | #else 9 | using System; 10 | #endif 11 | 12 | namespace WangTiles.Utils 13 | { 14 | public static class MathUtils 15 | { 16 | #if UNITY 17 | public const float PI = Mathf.PI; 18 | public const float Deg2Rad = Mathf.Deg2Rad; 19 | #else 20 | public const float PI = 3.14159265359f; 21 | public const float Deg2Rad = PI / 180.0f; 22 | #endif 23 | 24 | #region CORE 25 | public static int Sign(float x) 26 | { 27 | if (x == 0) 28 | { 29 | return 0; 30 | } 31 | 32 | return x < 0 ? -1 : 1; 33 | } 34 | 35 | public static float Frac(float x) 36 | { 37 | #if UNITY 38 | return x - Mathf.Floor(x); 39 | #else 40 | return x - (int)(x); 41 | #endif 42 | } 43 | 44 | public static float Round(float x) 45 | { 46 | #if UNITY 47 | return Mathf.Round(x); 48 | #else 49 | return (float)Math.Round(x); 50 | #endif 51 | } 52 | 53 | 54 | #if UNITY 55 | public static Vector4 Frac(Vector4 v) 56 | { 57 | return new Vector4(Frac(v.x), Frac(v.y), Frac(v.z), Frac(v.w)); 58 | } 59 | #endif 60 | 61 | public static float Sqr(float x) 62 | { 63 | return x * x; 64 | } 65 | 66 | public static float Sqrt(float x) 67 | { 68 | #if UNITY 69 | return Mathf.Sqrt(x); 70 | #else 71 | return (float)Math.Sqrt(x); 72 | #endif 73 | } 74 | 75 | public static float Pow(float f, float p) 76 | { 77 | #if UNITY 78 | return Mathf.Pow(f, p); 79 | #else 80 | return (float)Math.Pow(f, p); 81 | #endif 82 | } 83 | 84 | public static float Log(float x, float power) 85 | { 86 | #if UNITY 87 | return Mathf.Log(x, power); 88 | #else 89 | return (float)Math.Log(x, power); 90 | #endif 91 | } 92 | 93 | public static float Log(float x) 94 | { 95 | #if UNITY 96 | return Mathf.Log(x); 97 | #else 98 | return (float)Math.Log(x); 99 | #endif 100 | } 101 | 102 | public static float Abs(float x) 103 | { 104 | #if UNITY 105 | return Mathf.Abs(x); 106 | #else 107 | return (float)Math.Abs(x); 108 | #endif 109 | } 110 | 111 | public static int Abs(int x) 112 | { 113 | #if UNITY 114 | return Mathf.Abs(x); 115 | #else 116 | return Math.Abs(x); 117 | #endif 118 | } 119 | 120 | public static float Floor(float x) 121 | { 122 | #if UNITY 123 | return Mathf.Floor(x); 124 | #else 125 | return (float)Math.Floor(x); 126 | #endif 127 | } 128 | 129 | 130 | public static float Sin(float x) 131 | { 132 | #if UNITY 133 | return Mathf.Sin(x); 134 | #else 135 | return (float)Math.Sin(x); 136 | #endif 137 | } 138 | 139 | public static float Cos(float x) 140 | { 141 | #if UNITY 142 | return Mathf.Cos(x); 143 | #else 144 | return (float)Math.Cos(x); 145 | #endif 146 | } 147 | 148 | public static float Asin(float x) 149 | { 150 | #if UNITY 151 | return Mathf.Asin(x); 152 | #else 153 | return (float)Math.Asin(x); 154 | #endif 155 | } 156 | 157 | public static float Acos(float x) 158 | { 159 | #if UNITY 160 | return Mathf.Acos(x); 161 | #else 162 | return (float)Math.Acos(x); 163 | #endif 164 | } 165 | 166 | 167 | public static float Min(float a, float b) 168 | { 169 | #if UNITY 170 | return Mathf.Min(a, b); 171 | #else 172 | return (float)Math.Min(a, b); 173 | #endif 174 | } 175 | 176 | public static float Max(float a, float b) 177 | { 178 | #if UNITY 179 | return Mathf.Max(a, b); 180 | #else 181 | return (float)Math.Max(a, b); 182 | #endif 183 | } 184 | 185 | public static int Min(int a, int b) 186 | { 187 | #if UNITY 188 | return Mathf.Min(a, b); 189 | #else 190 | return Math.Min(a, b); 191 | #endif 192 | } 193 | 194 | public static int Max(int a, int b) 195 | { 196 | #if UNITY 197 | return Mathf.Max(a, b); 198 | #else 199 | return Math.Max(a, b); 200 | #endif 201 | } 202 | 203 | public static void GetDirectionForAngle(float angle, float speed, out float dx, out float dy) 204 | { 205 | dx = Cos(angle) * speed; 206 | dy = Sin(angle) * speed; 207 | } 208 | 209 | #endregion 210 | 211 | #region DISTANCES 212 | public static float Distance(float x1, float y1, float x2, float y2) 213 | { 214 | float dx = x1 - x2; 215 | float dy = y1 - y2; 216 | dx *= dx; 217 | dy *= dy; 218 | return Sqrt(dx + dy); 219 | } 220 | 221 | #if UNITY 222 | public static float Distance(Vector2 A, Vector2 B) 223 | { 224 | return Distance(A.x, A.y, B.x, B.y); 225 | } 226 | #endif 227 | 228 | public static float DotProduct(float x1, float y1, float x2, float y2) 229 | { 230 | return x1 * x2 + y1 * y2; 231 | } 232 | 233 | public static float Angle(float x1, float y1, float x2, float y2) 234 | { 235 | return Acos(DotProduct(x1, y1, x2, y2)); 236 | } 237 | 238 | #endregion 239 | 240 | #region CURVES 241 | public static float SmoothCurveWithOffset(float delta, float offset) 242 | { 243 | if (delta < offset) 244 | { 245 | delta = (delta / offset); 246 | return Abs(Sin(delta * PI * 0.5f)); 247 | } 248 | else 249 | { 250 | delta = delta - offset; 251 | delta = (delta / (1.0f - offset)); 252 | return Abs(Cos(delta * PI * 0.5f)); 253 | } 254 | } 255 | 256 | public static float SmoothCurve(float delta) 257 | { 258 | return Abs(Sin(delta * PI)); 259 | } 260 | 261 | public static float CubicInterpolate(float y0, float y1, float y2, float y3, float mu) 262 | { 263 | float mu2 = (mu * mu); 264 | float a0 = y3 - y2 - y0 + y1; 265 | float a1 = y0 - y1 - a0; 266 | float a2 = y2 - y0; 267 | float a3 = y1; 268 | return (a0 * mu * mu2) + (a1 * mu2) + (a2 * mu) + a3; 269 | } 270 | 271 | public static float CatmullRomInterpolate(float y0, float y1, float y2, float y3, float mu) 272 | { 273 | float mu2 = (mu * mu); 274 | float a0 = (-0.5f * y0) + (1.5f * y1) - (1.5f * y2) + (0.5f * y3); 275 | float a1 = y0 - (2.5f * y1) + (2.0f * y2) - (0.5f * y3); 276 | float a2 = (-0.5f * y0) + (0.5f * y2); 277 | float a3 = y1; 278 | return (a0 * mu * mu2) + (a1 * mu2) + (a2 * mu) + a3; 279 | } 280 | 281 | public static float HermiteInterpolate(float pA, float pB, float vA, float vB, float u) 282 | { 283 | float u2 = (u * u); 284 | float u3 = u2 * u; 285 | float B0 = 2.0f * u3 - 3.0f * u2 + 1.0f; 286 | float B1 = -2.0f * u3 + 3.0f * u2; 287 | float B2 = u3 - 2.0f * u2 + u; 288 | float B3 = u3 - u; 289 | return (B0 * pA + B1 * pB + B2 * vA + B3 * vB); 290 | 291 | } 292 | 293 | public static float QuadraticBezierCurve(float y0, float y1, float y2, float mu) 294 | { 295 | 296 | return Sqr(1 - mu) * y0 + 2 * (1 - mu) * y1 + Sqr(mu) * y2; 297 | } 298 | 299 | public static float CubicBezierCurve(float y0, float y1, float y2, float y3, float mu) 300 | { 301 | return (1 - mu) * Sqr(1 - mu) * y0 + 3 * Sqr(1 - mu) * y1 + 3 * (1 - mu) * Sqr(mu) * y2 + Sqr(mu) * mu * y3; ; 302 | } 303 | #endregion 304 | 305 | #region LERPING 306 | public static float Lerp(float min, float max, float delta) 307 | { 308 | #if UNITY 309 | return Mathf.Lerp(min, max, delta); 310 | #else 311 | delta = delta > 1 ? 1 : delta < 0 ? 0 : delta; 312 | return min * delta + max * (1.0f- delta); 313 | #endif 314 | } 315 | 316 | public static float InverseLerp(float min, float max, float value) 317 | { 318 | #if UNITY 319 | return Mathf.InverseLerp(min, max, value); 320 | #else 321 | return (value - min) / (max - min); 322 | #endif 323 | } 324 | 325 | 326 | // Some quadrilateral with position vectors a, b, c, and d. 327 | // a---b 328 | // | | 329 | // c---d 330 | 331 | // u = relative position on the "horizontal" axis between a and b, or c and d. 332 | // v = relative position on the "vertical" axis between a and c, or b and d. 333 | 334 | #if UNITY 335 | public static Vector3 BilinearLerp(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float u, float v) 336 | { 337 | Vector3 ab = Vector3.Lerp(a, b, u); // interpolation of a and b by u 338 | Vector3 cd = Vector3.Lerp(c, d, u); // interpolation of c and d by u 339 | return Vector3.Lerp(ab, cd, v); // interpolation of the interpolation of a and b and c and d by u, by v 340 | } 341 | #endif 342 | 343 | #endregion 344 | 345 | 346 | #region COLOR 347 | #if UNITY 348 | //NOTE: values only valid for 1024 x 32 349 | private static Vector3 coord_scale = new Vector4(0.0302734375f, 0.96875f, 31.0f); 350 | private static Vector4 coord_offset = new Vector4(0.00048828125f, 0.015625f, 0.0f, 0.0f); 351 | private static Vector2 texel_height_X0 = new Vector2(0.03125f, 0.0f); 352 | 353 | private static Color LUTSample(Texture2D LUT, int red0, int green0, int blue, float u, float v) 354 | { 355 | int red1 = red0 < 31 ? red0 + 1 : red0; 356 | int green1 = green0 < 31 ? green0 + 1 : green0; 357 | 358 | Color c00 = LUT.GetPixel(blue * 32 + red0, green0); 359 | Color c10 = LUT.GetPixel(blue * 32 + red1, green0); 360 | Color c11 = LUT.GetPixel(blue * 32 + red1, green1); 361 | Color c01 = LUT.GetPixel(blue * 32 + red0, green1); 362 | 363 | Color ab = Color.Lerp(c00, c10, u); // interpolation of a and b by u 364 | Color cd = Color.Lerp(c01, c11, u); // interpolation of c and d by u 365 | return Color.Lerp(ab, cd, v); // interpolation of the interpolation of a and b and c and d by u, by v 366 | } 367 | 368 | public static Color32 LUTTransform(Color32 color, Texture2D LUT) 369 | { 370 | /*Vector4 coord = new Vector4(color.r * coord_scale.x, color.g * coord_scale.y, color.b * coord_scale.z, 0); 371 | coord += coord_offset; 372 | 373 | Vector4 coord_frac = Frac(coord); 374 | Vector4 coord_floor = coord - coord_frac; 375 | 376 | Vector2 coord_bot = new Vector2(coord.x + coord_floor.z * texel_height_X0.x, coord.y + coord_floor.z * texel_height_X0.y); 377 | Vector2 coord_top = coord_bot + texel_height_X0; 378 | 379 | Color lutcol_bot = LUT.GetPixelBilinear(coord_bot.x, coord_bot.y); 380 | Color lutcol_top = LUT.GetPixelBilinear(coord_top.x, coord_top.y); 381 | 382 | //Color lutcol_bot = LUT.GetPixel((int)(coord_bot.x * LUT.width), (int)(coord_bot.y * LUT.height)); 383 | //Color lutcol_top = LUT.GetPixel((int)(coord_top.x * LUT.width), (int)(coord_top.x * LUT.height)); 384 | 385 | return Color.Lerp(lutcol_bot, lutcol_top, coord_frac.z); 386 | */ 387 | 388 | float div = 1.0f / 8.0f; 389 | float red = (float)color.r * div; 390 | float green = (float)color.g * div; 391 | float blue = (float)color.b * div; 392 | 393 | float u = Frac(red); 394 | float v = Frac(green); 395 | float w = Frac(blue); 396 | 397 | int x = Mathf.FloorToInt(red); 398 | int y = Mathf.FloorToInt(green); 399 | int z0 = Mathf.FloorToInt(blue); 400 | int z1 = z0 < 31 ? z0 + 1 : z0; 401 | 402 | Color A = LUTSample(LUT, x, y, z0, u, v); 403 | Color B = LUTSample(LUT, x, y, z0, u, v); 404 | 405 | return Color.Lerp(A, B, w); 406 | } 407 | 408 | #endif 409 | #endregion 410 | 411 | #region RANDOM 412 | /// 413 | /// Generates normally distributed numbers. Each operation makes two Gaussians for the price of one, and apparently they can be cached or something for better performance, but who cares. 414 | /// 415 | /// 416 | /// Mean of the distribution 417 | /// Standard deviation 418 | /// 419 | public static float RandomGaussian(float mu = 0, float sigma = 1) 420 | { 421 | var u1 = RandomFloat(0, 1); 422 | var u2 = RandomFloat(0, 1); 423 | 424 | var rand_std_normal = Sqrt(-2.0f * Log(u1)) * Sin(2.0f * PI * u2); 425 | 426 | var rand_normal = mu + sigma * rand_std_normal; 427 | 428 | return rand_normal; 429 | } 430 | 431 | public static float RandomAngle(float minDegrees, float maxDegrees, float step = 1.0f) 432 | { 433 | return Deg2Rad * RandomFloat(minDegrees, maxDegrees); 434 | } 435 | 436 | #if !UNITY 437 | private static Random _random = new Random(); 438 | #endif 439 | 440 | public static float RandomFloat(float min, float max) 441 | { 442 | #if UNITY 443 | return UnityEngine.Random.Range(min, max); 444 | #else 445 | return min + (float)(_random.NextDouble() * (max - min)); 446 | #endif 447 | } 448 | 449 | /// 450 | /// Returns a value between min and max - 1 451 | /// 452 | /// 453 | /// 454 | /// 455 | public static int RandomInt(int min, int max) 456 | { 457 | #if UNITY 458 | return UnityEngine.Random.Range(min, max); 459 | #else 460 | return min + _random.Next(max - min); 461 | #endif 462 | } 463 | 464 | public static T RandomEnum() 465 | { 466 | var v = Enum.GetValues(typeof(T)); 467 | return (T)v.GetValue(RandomInt(0, v.Length)); 468 | } 469 | 470 | #endregion 471 | 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /WangTileLib/WangUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | 5 | //http://s358455341.websitehome.co.uk/stagecast/wang/intro.html 6 | namespace WangTiles.Core 7 | { 8 | public enum WangEdgeDirection 9 | { 10 | North = 0, 11 | East = 1, 12 | South = 2, 13 | West = 3 14 | } 15 | 16 | #region EDGE 17 | public static class WangEdgeUtils 18 | { 19 | /// 20 | /// Returns bools for all 4 directions, if true, the tile has a connection in that direction 21 | /// 22 | /// 23 | /// 24 | /// 25 | /// 26 | /// 27 | public static void GetConnectionsForTile(int tileID, out bool north, out bool east, out bool south, out bool west) 28 | { 29 | north = GetConnectionForTile(tileID, WangEdgeDirection.North); 30 | east = GetConnectionForTile(tileID, WangEdgeDirection.East); 31 | south = GetConnectionForTile(tileID, WangEdgeDirection.South); 32 | west = GetConnectionForTile(tileID, WangEdgeDirection.West); 33 | } 34 | 35 | public static bool GetConnectionForTile(int tileID, WangEdgeDirection direction) 36 | { 37 | int mask = 1 << ((int)direction); 38 | return (tileID & mask) != 0; 39 | } 40 | 41 | /// 42 | /// Returns the opposite direction (eg: North -> South) 43 | /// 44 | /// 45 | /// 46 | public static WangEdgeDirection InvertDirection(WangEdgeDirection direction) 47 | { 48 | switch (direction) 49 | { 50 | case WangEdgeDirection.North: return WangEdgeDirection.South; 51 | case WangEdgeDirection.South: return WangEdgeDirection.North; 52 | case WangEdgeDirection.East: return WangEdgeDirection.West; 53 | default: return WangEdgeDirection.East; 54 | } 55 | } 56 | 57 | /// 58 | /// Returns a list of all possible tiles that can connect with a specified set of neighbors 59 | /// 60 | /// 61 | /// 62 | /// 63 | public static List GetPossibleMatches(int left, int right, int up, int down) 64 | { 65 | //ushort matchID = (ushort)(left + (right << 4) + (up << 8) + (down << 12)); 66 | 67 | List result = new List(); 68 | for (int newID = 0; newID < 16; newID++) 69 | { 70 | if (!MatchTile(left, newID, WangEdgeDirection.East)) { continue; } 71 | if (!MatchTile(right, newID, WangEdgeDirection.West)) { continue; } 72 | if (!MatchTile(up, newID, WangEdgeDirection.South)) { continue; } 73 | if (!MatchTile(down, newID, WangEdgeDirection.North)) { continue; } 74 | 75 | result.Add(newID); 76 | } 77 | 78 | return result; 79 | } 80 | 81 | /// 82 | /// Checks if the two tiles can be placed in an adjancent manner 83 | /// 84 | /// 85 | /// 86 | /// 87 | public static bool MatchTile(int currentID, int newID, WangEdgeDirection newDirection) 88 | { 89 | if (currentID == -1 || newID == -1) 90 | { 91 | return true; 92 | } 93 | 94 | bool currentSide = GetConnectionForTile(currentID, newDirection); 95 | bool newSide = GetConnectionForTile(newID, InvertDirection(newDirection)); 96 | return currentSide == newSide; 97 | } 98 | 99 | public static int AddConnection(int tileID, WangEdgeDirection direction) 100 | { 101 | int mask = 1 << ((int)direction); 102 | return tileID | mask; 103 | } 104 | } 105 | #endregion 106 | 107 | public enum WangCornerDirection 108 | { 109 | Northeast = 0, 110 | Southeast = 1, 111 | Southwest = 2, 112 | Northwest = 3 113 | } 114 | 115 | #region CORNER 116 | public static class WangCornerUtils 117 | { 118 | /// 119 | /// Returns bools for all 4 directions, if true, the tile has a connection in that direction 120 | /// 121 | /// 122 | /// 123 | /// 124 | /// 125 | /// 126 | public static void GetConnectionsForTile(int tileID, out bool northeast, out bool northwest, out bool southeast, out bool southwest) 127 | { 128 | northeast = GetConnectionForTile(tileID, WangCornerDirection.Northeast); 129 | northwest = GetConnectionForTile(tileID, WangCornerDirection.Northwest); 130 | southeast = GetConnectionForTile(tileID, WangCornerDirection.Southeast); 131 | southwest = GetConnectionForTile(tileID, WangCornerDirection.Southwest); 132 | } 133 | 134 | public static bool GetConnectionForTile(int tileID, WangCornerDirection direction) 135 | { 136 | int mask = 1 << ((int)direction); 137 | return (tileID & mask) != 0; 138 | } 139 | 140 | /// 141 | /// Returns the opposite direction (eg: North -> South) 142 | /// 143 | /// 144 | /// 145 | public static WangCornerDirection InvertDirection(WangCornerDirection direction) 146 | { 147 | switch (direction) 148 | { 149 | case WangCornerDirection.Northeast: return WangCornerDirection.Southwest; 150 | case WangCornerDirection.Northwest: return WangCornerDirection.Southeast; 151 | case WangCornerDirection.Southeast: return WangCornerDirection.Northwest; 152 | default: return WangCornerDirection.Northeast; 153 | } 154 | } 155 | 156 | /// 157 | /// Returns a list of all possible tiles that can connect with a specified set of neighbors 158 | /// 159 | /// 160 | /// 161 | /// 162 | public static List GetPossibleMatches(int left, int right, int up, int down, int northwest, int northeast, int southwest, int southeast) 163 | { 164 | List result = new List(); 165 | for (int newID = 0; newID < 16; newID++) 166 | { 167 | if (!MatchTile(newID, WangCornerDirection.Northeast, northeast, WangCornerDirection.Southwest)) { continue; } 168 | if (!MatchTile(newID, WangCornerDirection.Northwest, northwest, WangCornerDirection.Southeast)) { continue; } 169 | 170 | if (!MatchTile(newID, WangCornerDirection.Southeast, southeast, WangCornerDirection.Northwest)) { continue; } 171 | if (!MatchTile(newID, WangCornerDirection.Southwest, southwest, WangCornerDirection.Northeast)) { continue; } 172 | 173 | if (!MatchTile(newID, WangCornerDirection.Northeast, right, WangCornerDirection.Northwest)) { continue; } 174 | if (!MatchTile(newID, WangCornerDirection.Southeast, right, WangCornerDirection.Southwest)) { continue; } 175 | 176 | if (!MatchTile(newID, WangCornerDirection.Northwest, left, WangCornerDirection.Northeast)) { continue; } 177 | if (!MatchTile(newID, WangCornerDirection.Southwest, left, WangCornerDirection.Southeast)) { continue; } 178 | 179 | if (!MatchTile(newID, WangCornerDirection.Northeast, up, WangCornerDirection.Southeast)) { continue; } 180 | if (!MatchTile(newID, WangCornerDirection.Northwest, up, WangCornerDirection.Southwest)) { continue; } 181 | 182 | if (!MatchTile(newID, WangCornerDirection.Southeast, down, WangCornerDirection.Northeast)) { continue; } 183 | if (!MatchTile(newID, WangCornerDirection.Southwest, down, WangCornerDirection.Northwest)) { continue; } 184 | 185 | result.Add(newID); 186 | } 187 | 188 | return result; 189 | } 190 | 191 | /// 192 | /// Checks if the two tiles can be placed in an adjancent manner 193 | /// 194 | /// 195 | /// 196 | /// 197 | public static bool MatchTile(int currentID, WangCornerDirection currentDirection, int newID, WangCornerDirection newDirection) 198 | { 199 | if (currentID == -1 || newID == -1) 200 | { 201 | return true; 202 | } 203 | 204 | bool currentSide = GetConnectionForTile(currentID, currentDirection); 205 | bool newSide = GetConnectionForTile(newID, newDirection); 206 | return currentSide == newSide; 207 | } 208 | 209 | public static int AddConnection(int tileID, WangEdgeDirection direction) 210 | { 211 | int mask = 1 << ((int)direction); 212 | return tileID | mask; 213 | } 214 | } 215 | #endregion 216 | 217 | public class WangArea 218 | { 219 | public int ID; 220 | public List children = new List(); 221 | 222 | public static Color GetColor(WangArea area) 223 | { 224 | if (area == null) 225 | { 226 | return Color.FromArgb(0); 227 | } 228 | 229 | return area.GetColor(); 230 | } 231 | 232 | public override string ToString() 233 | { 234 | return "Area "+ID.ToString()+ " ("+ GetColor().ToString() +")"; 235 | } 236 | 237 | public Color GetColor() 238 | { 239 | switch (ID) 240 | { 241 | case 0: return Color.Red; 242 | case 1: return Color.Green; 243 | case 2: return Color.Blue; 244 | case 3: return Color.Yellow; 245 | case 4: return Color.Magenta; 246 | case 5: return Color.Cyan; 247 | case 6: return Color.Orange; 248 | case 7: return Color.Purple; 249 | case 8: return Color.Gray; 250 | case 9: return Color.Beige; 251 | case 10: return Color.Chocolate; 252 | default: return Color.Black; 253 | } 254 | } 255 | } 256 | 257 | public struct WangTile 258 | { 259 | public int tileID; 260 | public int variationID; 261 | public WangArea areaID; 262 | } 263 | 264 | public struct WangMapExit 265 | { 266 | public int x; 267 | public int y; 268 | public WangEdgeDirection direction; 269 | 270 | public WangMapExit(int x, int y, WangEdgeDirection dir) 271 | { 272 | this.x = x; 273 | this.y = y; 274 | this.direction = dir; 275 | } 276 | } 277 | 278 | public struct WangEdgeTile 279 | { 280 | public int x1; 281 | public int y1; 282 | public int x2; 283 | public int y2; 284 | public WangEdgeDirection exit; 285 | } 286 | 287 | public abstract class WangMap 288 | { 289 | protected int _width = 16; 290 | public int Width { get { return _width; } } 291 | 292 | protected int _height = 10; 293 | public int Height { get { return _height; } } 294 | 295 | protected WangTile[] tiles; 296 | protected Random rnd; 297 | 298 | public WangMap(int width, int height, int seed = 0) 299 | { 300 | if (seed == 0) 301 | { 302 | seed = Environment.TickCount; 303 | } 304 | 305 | this.rnd = new Random(seed); 306 | 307 | this._width = width; 308 | this._height = height; 309 | tiles = new WangTile[_width * _height]; 310 | 311 | // first clear all tiles 312 | for (int j = 0; j < _height; j++) 313 | { 314 | for (int i = 0; i < _width; i++) 315 | { 316 | int ofs = GetTileOffset(i, j); 317 | tiles[ofs].areaID = null; 318 | tiles[ofs].tileID = -1; 319 | } 320 | } 321 | } 322 | 323 | public void SetTileIDAt(int x, int y, int val) 324 | { 325 | int ofs = GetTileOffset(x, y); 326 | tiles[ofs].tileID = val; 327 | 328 | int n = (x * y) + (x | y); 329 | int variation; 330 | switch (val) 331 | { 332 | case 0: variation = 0; break; 333 | case 1: case 2: case 4: case 8: variation = n % 3; break; 334 | case 3: case 6: case 9: case 12: variation = n % 3; break; 335 | case 5: case 10: variation = n % 4; break; 336 | case 15: variation = n % 2; break; 337 | 338 | default: variation = n % 3; break; 339 | } 340 | 341 | 342 | tiles[ofs].variationID = variation; 343 | } 344 | 345 | public abstract WangTile GetTileAt(int x, int y); 346 | 347 | public int GetTileOffset(int x, int y) 348 | { 349 | return x + y * _width; 350 | } 351 | 352 | 353 | public void Invert() 354 | { 355 | for (int j = 0; j < _height; j++) 356 | { 357 | for (int i = 0; i < _width; i++) 358 | { 359 | int current = GetTileAt(i, j).tileID; 360 | if (current < 0) // if tile is not set, skip 361 | { 362 | continue; 363 | } 364 | 365 | SetTileIDAt(i, j, 15 - current); 366 | } 367 | } 368 | } 369 | } 370 | 371 | 372 | 373 | #region EDGE MAP 374 | public class WangEdgeMap : WangMap 375 | { 376 | private bool _wrapX = false; 377 | private bool _wrapY = false; 378 | 379 | 380 | private List exits = new List(); 381 | 382 | public WangEdgeMap(int width, int height, int seed = 0, bool wrapX = false, bool wrapY = false) : base (width, height, seed) 383 | { 384 | this._wrapX = wrapX; 385 | this._wrapY = wrapY; 386 | } 387 | 388 | public override WangTile GetTileAt(int x, int y) 389 | { 390 | if (x < 0 || y < 0 || x >= Width || y >= Height) 391 | { 392 | foreach (var exit in exits) 393 | { 394 | if (exit.x == x && exit.y == y) 395 | { 396 | var result = new WangTile(); 397 | result.tileID = WangEdgeUtils.AddConnection(0, exit.direction); 398 | result.areaID = null; 399 | return result; 400 | } 401 | } 402 | } 403 | 404 | if (_wrapX && x < 0) 405 | { 406 | x += _width; 407 | } 408 | 409 | if (_wrapX && x >= _width) 410 | { 411 | x -= _width; 412 | } 413 | 414 | if (_wrapY && y < 0) 415 | { 416 | y += _height; 417 | } 418 | 419 | if (_wrapY && y >= _height) 420 | { 421 | y -= _height; 422 | } 423 | 424 | if (x < 0 || y < 0 || x >= _width || y >= _height) 425 | { 426 | var result = new WangTile(); 427 | result.tileID = 0; 428 | result.areaID = null; 429 | return result; 430 | } 431 | return tiles[GetTileOffset(x, y)]; 432 | } 433 | 434 | public bool AddExit(int x, int y) 435 | { 436 | WangEdgeDirection dir; 437 | 438 | if (y < 0) { dir = WangEdgeDirection.South; } 439 | else 440 | if (y >= Height) { dir = WangEdgeDirection.North; } 441 | else 442 | if (x < 0) { dir = WangEdgeDirection.East; } 443 | else 444 | if (x >= Width) { dir = WangEdgeDirection.West; } 445 | else 446 | { 447 | return false; 448 | } 449 | 450 | exits.Add(new WangMapExit(x, y, dir)); 451 | return true; 452 | } 453 | 454 | private static void AddEdgeTile(Dictionary> result, WangArea otherArea, int x1, int y1, int x2, int y2, WangEdgeDirection exitDir) 455 | { 456 | if (!result.ContainsKey(otherArea)) 457 | { 458 | result[otherArea] = new List(); 459 | } 460 | 461 | WangEdgeTile et = new WangEdgeTile(); 462 | et.x1 = x1; 463 | et.y1 = y1; 464 | et.x2 = x2; 465 | et.y2 = y2; 466 | et.exit = exitDir; 467 | result[otherArea].Add(et); 468 | } 469 | 470 | private Dictionary> FindTilesInAreaEdges(WangArea curArea) 471 | { 472 | Dictionary> result = new Dictionary>(); 473 | for (int j = 0; j < this.Height; j++) 474 | { 475 | for (int i = 0; i < this.Width; i++) 476 | { 477 | int ofs = GetTileOffset(i, j); 478 | if (tiles[ofs].areaID != curArea) // check if this tile belongs to the area we are testing 479 | { 480 | continue; 481 | } 482 | 483 | var otherArea = GetTileAt(i - 1, j).areaID; 484 | if (i > 0 && otherArea != curArea && otherArea != null) 485 | { 486 | AddEdgeTile(result, otherArea, i, j, i - 1, j, WangEdgeDirection.West); 487 | } 488 | 489 | otherArea = GetTileAt(i, j - 1).areaID; 490 | if (j > 0 && otherArea != curArea && otherArea != null) 491 | { 492 | AddEdgeTile(result, otherArea, i, j, i, j - 1, WangEdgeDirection.North); 493 | } 494 | 495 | otherArea = GetTileAt(i + 1, j).areaID; 496 | if (i < _width - 1 && otherArea != curArea && otherArea != null) 497 | { 498 | AddEdgeTile(result, otherArea, i, j, i + 1, j, WangEdgeDirection.East); 499 | } 500 | 501 | otherArea = GetTileAt(i, j + 1).areaID; 502 | if (j < _height - 1 && otherArea != curArea && otherArea != null) 503 | { 504 | AddEdgeTile(result, otherArea, i, j, i, j + 1, WangEdgeDirection.South); 505 | } 506 | } 507 | } 508 | 509 | return result; 510 | } 511 | 512 | 513 | private void FloodFillArea(int x, int y, WangArea areaID) 514 | { 515 | int ofs = GetTileOffset(x, y); 516 | tiles[ofs].areaID = areaID; 517 | 518 | // flood all adjacent tiles if possible 519 | FloodFillArea(x - 1, y, areaID, WangEdgeDirection.East); 520 | FloodFillArea(x + 1, y, areaID, WangEdgeDirection.West); 521 | FloodFillArea(x, y - 1, areaID, WangEdgeDirection.South); 522 | FloodFillArea(x, y + 1, areaID, WangEdgeDirection.North); 523 | } 524 | 525 | private void FloodFillArea(int x, int y, WangArea areaID, WangEdgeDirection direction) 526 | { 527 | if (x < 0 || y < 0 || x >= _width || y >= _height) // if out of bounds, return 528 | { 529 | return; 530 | } 531 | 532 | int ofs = GetTileOffset(x, y); 533 | if (tiles[ofs].areaID != null) // if already filled then stop 534 | { 535 | return; 536 | } 537 | 538 | if (tiles[ofs].tileID == 0) // empty tiles should not be filled 539 | { 540 | return; 541 | } 542 | 543 | if (WangEdgeUtils.GetConnectionForTile(tiles[ofs].tileID, direction) == false) 544 | { 545 | return; 546 | } 547 | 548 | FloodFillArea(x, y, areaID); 549 | } 550 | 551 | private bool JoinArea(WangArea parent, WangArea area, HashSet areaSet) 552 | { 553 | if (areaSet.Contains(area)) 554 | { 555 | return false; 556 | } 557 | 558 | areaSet.Add(area); 559 | 560 | //Console.WriteLine("Testing area: " + GetAreaColor(area)); 561 | 562 | var result = FindTilesInAreaEdges(area); 563 | if (result.Count <= 0) 564 | { 565 | return true; 566 | } 567 | 568 | foreach (var temp in result) 569 | { 570 | if (JoinArea(area, temp.Key, areaSet)) 571 | { 572 | area.children.Add(temp.Key); 573 | 574 | var tiles = temp.Value; 575 | var tile = tiles[rnd.Next(tiles.Count)]; 576 | 577 | //Console.WriteLine("Joined " + GetAreaColor(area) + " to " + GetAreaColor(temp.Key)); 578 | 579 | int ofs = GetTileOffset(tile.x1, tile.y1); 580 | SetTileIDAt(tile.x1, tile.y1, WangEdgeUtils.AddConnection(this.tiles[ofs].tileID, tile.exit)); 581 | 582 | ofs = GetTileOffset(tile.x2, tile.y2); 583 | SetTileIDAt(tile.x2, tile.y2, WangEdgeUtils.AddConnection(this.tiles[ofs].tileID, WangEdgeUtils.InvertDirection(tile.exit))); 584 | } 585 | } 586 | 587 | return true; 588 | } 589 | 590 | public void FixConnectivity() 591 | { 592 | // optional, detect isolate areas 593 | List areas = new List(); 594 | for (int j = 0; j < Height; j++) 595 | { 596 | for (int i = 0; i < _width; i++) 597 | { 598 | int ofs = GetTileOffset(i, j); 599 | if (tiles[ofs].areaID != null) // check if this tile already have an area assigned 600 | { 601 | continue; 602 | } 603 | 604 | if (tiles[ofs].tileID == 0) // empty tiles should not be filled 605 | { 606 | continue; 607 | } 608 | 609 | var area = new WangArea(); 610 | area.ID = areas.Count; 611 | areas.Add(area); 612 | FloodFillArea(i, j, area); 613 | } 614 | } 615 | 616 | // join the isolated areas by adding exits to some tiles 617 | HashSet areaSet = new HashSet(); 618 | for (int k = 0; k < areas.Count; k++) 619 | { 620 | var area = areas[k]; 621 | if (k == 0) 622 | { 623 | JoinArea(null, area, areaSet); 624 | } 625 | else 626 | if (!areaSet.Contains(area)) 627 | { 628 | DeleteArea(area); 629 | } 630 | } 631 | } 632 | 633 | private void DeleteArea(WangArea area) 634 | { 635 | for (int j = 0; j < _height; j++) 636 | { 637 | for (int i = 0; i < _width; i++) 638 | { 639 | if (GetTileAt(i, j).areaID == area) 640 | { 641 | SetTileIDAt(i, j, 0); 642 | } 643 | } 644 | } 645 | } 646 | 647 | public void Generate() 648 | { 649 | // first pass places random tiles in a checkerboard pattern 650 | for (int j = 0; j < _height; j++) 651 | { 652 | for (int i = 0; i < _width; i++) 653 | { 654 | if (i % 2 == j % 2) 655 | { 656 | continue; 657 | } 658 | 659 | TryPlacingRandomTile(i, j); 660 | } 661 | } 662 | 663 | // second pass places random tiles in the mising holes, checking for matching edges 664 | for (int j = 0; j < _height; j++) 665 | { 666 | for (int i = 0; i < _width; i++) 667 | { 668 | int current = GetTileAt(i, j).tileID; 669 | if (current >= 0) // if tile already set, skip 670 | { 671 | continue; 672 | } 673 | 674 | //continue; 675 | 676 | TryPlacingRandomTile(i, j); 677 | } 678 | } 679 | } 680 | 681 | protected void TryPlacingRandomTile(int i, int j) 682 | { 683 | int left = GetTileAt(i - 1, j).tileID; 684 | int right = GetTileAt(i + 1, j).tileID; 685 | int up = GetTileAt(i, j - 1).tileID; 686 | int down = GetTileAt(i, j + 1).tileID; 687 | 688 | var matches = WangEdgeUtils.GetPossibleMatches(left, right, up, down); 689 | 690 | if (matches.Count <= 0) 691 | { 692 | return; 693 | } 694 | 695 | int n = matches[rnd.Next(matches.Count)]; 696 | SetTileIDAt(i, j, n); 697 | } 698 | 699 | } 700 | 701 | #endregion 702 | 703 | #region CORNER MAP 704 | public class WangCornerMap : WangMap 705 | { 706 | public WangCornerMap(int width, int height, int seed = 0) : base(width, height, seed) 707 | { 708 | } 709 | 710 | public override WangTile GetTileAt(int x, int y) 711 | { 712 | if (x < 0 || y < 0 || x >= _width || y >= _height) 713 | { 714 | var result = new WangTile(); 715 | result.tileID = -1; 716 | result.areaID = null; 717 | return result; 718 | } 719 | return tiles[GetTileOffset(x, y)]; 720 | } 721 | 722 | 723 | public void Generate() 724 | { 725 | // first pass places random tiles in a checkerboard pattern 726 | int n = 12; 727 | for (int j = 0; j < _height; j++) 728 | { 729 | for (int i = 0; i < _width; i++) 730 | { 731 | TryPlacingRandomTile(i, j); 732 | //n--; if (n == 0) return; 733 | } 734 | } 735 | } 736 | 737 | 738 | protected void TryPlacingRandomTile(int i, int j) 739 | { 740 | int west = GetTileAt(i - 1, j).tileID; 741 | int east = GetTileAt(i + 1, j).tileID; 742 | int north = GetTileAt(i, j - 1).tileID; 743 | int south = GetTileAt(i, j + 1).tileID; 744 | 745 | int northwest = GetTileAt(i - 1, j - 1).tileID; 746 | int northeast = GetTileAt(i + 1, j - 1).tileID; 747 | int southwest = GetTileAt(i - 1, j + 1).tileID; 748 | int southeast = GetTileAt(i + 1, j + 1).tileID; 749 | 750 | var matches = WangCornerUtils.GetPossibleMatches(west, east, north, south, northwest, northeast, southwest, southeast); 751 | 752 | if (matches.Count <= 0) 753 | { 754 | return; 755 | } 756 | 757 | int n = matches[rnd.Next(matches.Count)]; 758 | SetTileIDAt(i, j, n); 759 | } 760 | } 761 | 762 | #endregion 763 | 764 | } 765 | -------------------------------------------------------------------------------- /WangTileLib/DungeonPlanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | using WangTiles.Core; 5 | 6 | namespace WangTiles.DungeonPlanner 7 | { 8 | public struct LayoutCoord 9 | { 10 | public int X; 11 | public int Y; 12 | 13 | public LayoutCoord(int x, int y) 14 | { 15 | this.X = x; 16 | this.Y = y; 17 | } 18 | 19 | public override int GetHashCode() 20 | { 21 | unchecked // Overflow is fine, just wrap 22 | { 23 | int hash = 17; 24 | // Suitable nullity checks etc, of course :) 25 | hash = hash * 23 + X.GetHashCode(); 26 | hash = hash * 23 + Y.GetHashCode(); 27 | return hash; 28 | } 29 | } 30 | } 31 | 32 | public class LayoutConnection 33 | { 34 | public LayoutRoom roomA; 35 | public LayoutRoom roomB; 36 | public bool active; 37 | public int weight; 38 | public int pathOrder; 39 | 40 | public LayoutConnection(LayoutRoom roomA, LayoutRoom roomB, Random rnd) 41 | { 42 | active = true; 43 | weight = 1 + rnd.Next(10); 44 | pathOrder = -1; 45 | this.roomA = roomA; 46 | this.roomB = roomB; 47 | } 48 | 49 | public static void QuickSort(List edgesList, int nLeft, int nRight) 50 | { 51 | int i, j, x; 52 | i = nLeft; j = nRight; 53 | x = edgesList[(nLeft + nRight) / 2].weight; 54 | 55 | do 56 | { 57 | while ((edgesList[i].weight < x) && (i < nRight)) i++; 58 | while ((x < edgesList[j].weight) && (j > nLeft)) j--; 59 | 60 | if (i <= j) 61 | { 62 | LayoutConnection y = edgesList[i]; 63 | edgesList[i] = edgesList[j]; 64 | edgesList[j] = y; 65 | i++; j--; 66 | } 67 | } while (i <= j); 68 | 69 | if (nLeft < j) QuickSort(edgesList, nLeft, j); 70 | if (i < nRight) QuickSort(edgesList, i, nRight); 71 | } 72 | } 73 | 74 | public class LayoutRoom 75 | { 76 | public enum RoomKind 77 | { 78 | Invalid, 79 | Entrance, 80 | Hall, 81 | Puzzle, 82 | Monster, 83 | Farm, 84 | Shrine, 85 | Treasure, 86 | Goal 87 | } 88 | 89 | public enum RoomShape 90 | { 91 | Empty, 92 | Curve, 93 | Corridor, 94 | Room, 95 | Split 96 | } 97 | 98 | public enum RoomCategory 99 | { 100 | Unknown, 101 | Main, 102 | Side, 103 | Distant, 104 | Secret 105 | } 106 | 107 | public string name; 108 | private LayoutRoom root; 109 | public int rank; 110 | 111 | public LayoutCoord coord; 112 | 113 | public List connections = new List(); 114 | 115 | public int distance; 116 | public LayoutRoom previous; 117 | 118 | public RoomKind kind; 119 | 120 | 121 | public RoomCategory category; 122 | public bool isLoop; 123 | public int distanceFromMainPath; 124 | 125 | public float intensity; 126 | public bool spike; 127 | 128 | public bool lockable; 129 | public bool important; 130 | 131 | public LayoutKey require; 132 | public LayoutKey contains; 133 | public LayoutKey locked; 134 | 135 | public int order; 136 | 137 | public int tileID; 138 | public int variationID; 139 | 140 | public LayoutRoom parent; 141 | public List children = new List(); 142 | 143 | public LayoutRoom(LayoutCoord coord) 144 | { 145 | this.name = coord.X + " : "+ coord.Y; 146 | this.coord = coord; 147 | this.rank = 0; 148 | this.root = this; 149 | 150 | this.kind = RoomKind.Hall; 151 | this.intensity = -1; 152 | this.lockable = true; 153 | } 154 | 155 | public override string ToString() 156 | { 157 | return this.kind + " ("+ name+")"; 158 | } 159 | 160 | public bool isDeadEnd() 161 | { 162 | switch (tileID) 163 | { 164 | case 1: case 2: case 4: case 8: return true; 165 | default: return false; 166 | } 167 | } 168 | 169 | 170 | public RoomShape GetShape() 171 | { 172 | if (tileID>0 && variationID == 1) 173 | { 174 | return RoomShape.Room; 175 | } 176 | 177 | 178 | switch (tileID) 179 | { 180 | case 1: case 2: case 4: case 8: return RoomShape.Room; 181 | case 3: case 6: case 9: case 12: return RoomShape.Curve; 182 | case 5: case 10: return RoomShape.Corridor; 183 | case 7: case 11: case 13: case 14: case 15: return RoomShape.Split; 184 | default: return RoomShape.Empty; 185 | } 186 | } 187 | 188 | internal LayoutRoom GetRoot() 189 | { 190 | if (this.root != this)// am I my own parent ? (am i the root ?) 191 | { 192 | this.root = this.root.GetRoot();// No? then get my parent 193 | } 194 | return this.root; 195 | } 196 | 197 | internal static void Join(LayoutRoom vRoot1, LayoutRoom vRoot2) 198 | { 199 | 200 | if (vRoot2.rank < vRoot1.rank)//is the rank of Root2 less than that of Root1 ? 201 | { 202 | vRoot2.root = vRoot1;//yes! then Root1 is the parent of Root2 (since it has the higher rank) 203 | } 204 | else //rank of Root2 is greater than or equal to that of Root1 205 | { 206 | vRoot1.root = vRoot2;//make Root2 the parent 207 | if (vRoot1.rank == vRoot2.rank)//both ranks are equal ? 208 | { 209 | vRoot1.rank++;//increment one of them, we need to reach a single root for the whole tree 210 | } 211 | } 212 | } 213 | 214 | public LayoutConnection FindConnection(LayoutRoom room) 215 | { 216 | foreach (var conn in connections) 217 | { 218 | if (conn.roomA == room || conn.roomB == room) 219 | { 220 | return conn; 221 | } 222 | } 223 | 224 | return null; 225 | } 226 | } 227 | 228 | public class LayoutKey 229 | { 230 | public string name; 231 | public LayoutRoom sourceRoom; 232 | public LayoutRoom targetRoom; 233 | 234 | public int order; 235 | public int condition; 236 | 237 | public LayoutKey(string name, int order) 238 | { 239 | this.name = name; 240 | this.order = order; 241 | } 242 | 243 | public override string ToString() 244 | { 245 | return this.name; 246 | } 247 | } 248 | 249 | 250 | public class LayoutPlanner 251 | { 252 | private Random randomGenerator; 253 | 254 | public LayoutRoom entrance; 255 | public LayoutRoom goal; 256 | 257 | public List connections = new List(); 258 | private Dictionary rooms = new Dictionary(); 259 | 260 | public LayoutPlanner(int seed) 261 | { 262 | if (seed == 0) 263 | { 264 | seed = Environment.TickCount; 265 | } 266 | this.randomGenerator = new Random(seed); 267 | } 268 | 269 | #region LAYOUT_SETUP 270 | public LayoutConnection AddConnection(LayoutCoord a, LayoutCoord b) 271 | { 272 | return AddConnection(FindRoomAt(a), FindRoomAt(b)); 273 | } 274 | 275 | public LayoutConnection AddConnection(LayoutCoord a, WangEdgeDirection dir) 276 | { 277 | LayoutCoord b; 278 | 279 | switch (dir) 280 | { 281 | case WangEdgeDirection.East: b = new LayoutCoord(a.X + 1, a.Y); break; 282 | case WangEdgeDirection.West: b = new LayoutCoord(a.X - 1, a.Y); break; 283 | case WangEdgeDirection.North: b = new LayoutCoord(a.X, a.Y - 1); break; 284 | case WangEdgeDirection.South: b = new LayoutCoord(a.X, a.Y + 1); break; 285 | default:return null; 286 | } 287 | 288 | return AddConnection(FindRoomAt(a), FindRoomAt(b)); 289 | } 290 | 291 | public LayoutConnection AddConnection(LayoutRoom roomA, LayoutRoom roomB) 292 | { 293 | if (roomA == roomB) 294 | { 295 | return null; 296 | } 297 | 298 | LayoutConnection conn = roomA.FindConnection(roomB); 299 | if (conn != null) 300 | { 301 | return conn; 302 | } 303 | 304 | conn = new LayoutConnection(roomA, roomB, this.randomGenerator); 305 | roomA.connections.Add(conn); 306 | roomB.connections.Add(conn); 307 | 308 | this.connections.Add(conn); 309 | 310 | return conn; 311 | } 312 | 313 | public LayoutRoom FindRoomAt(LayoutCoord coord, bool canCreate = true) 314 | { 315 | if (rooms.ContainsKey(coord)) 316 | { 317 | return rooms[coord]; 318 | } 319 | 320 | if (!canCreate) 321 | { 322 | return null; 323 | } 324 | 325 | var room = new LayoutRoom(coord); 326 | rooms[coord] = room; 327 | return room; 328 | } 329 | #endregion 330 | 331 | public struct RoomScore 332 | { 333 | public int score; 334 | public LayoutRoom room; 335 | public RoomScore(int score, LayoutRoom room) 336 | { 337 | this.score = score; 338 | this.room = room; 339 | } 340 | } 341 | 342 | protected int GetRoomScore(LayoutRoom room) 343 | { 344 | return 10; 345 | } 346 | 347 | public LayoutRoom FindEntrance() 348 | { 349 | entrance = null; 350 | 351 | List temp = new List(); 352 | foreach (var room in rooms.Values) 353 | { 354 | int score = GetRoomScore(room); 355 | 356 | if (score < 0) 357 | { 358 | continue; 359 | } 360 | 361 | if (room.isDeadEnd()) 362 | { 363 | score *= 2; 364 | } 365 | 366 | temp.Add(new RoomScore(score, room)); 367 | } 368 | 369 | if (temp.Count == 0) 370 | { 371 | //LogError("Could not find any room that matches entrance settings"); 372 | return null; 373 | } 374 | 375 | temp.Sort(new Comparison((RoomScore x, RoomScore y) => 376 | { 377 | //if (x.order == y.order) return x.height.CompareTo(y.height); else return x.order.CompareTo(y.order); 378 | return y.score.CompareTo(x.score); 379 | })); 380 | 381 | entrance = temp[randomGenerator.Next((temp.Count < 3 ? temp.Count : 3))].room; 382 | 383 | entrance.kind = LayoutRoom.RoomKind.Entrance; 384 | 385 | return entrance; 386 | } 387 | 388 | 389 | public LayoutRoom FindGoal() 390 | { 391 | if (entrance == null) 392 | { 393 | Console.WriteLine("Entrance is not set..."); 394 | return null; 395 | } 396 | 397 | // run dijkstra to find shortest paths 398 | List Q = new List(); 399 | foreach (var room in rooms.Values) 400 | { 401 | room.distance = 99999; // Unknown distance from source to v 402 | room.previous = null; // Previous node in optimal path from source 403 | Q.Add(room); // All nodes initially in Q (unvisited nodes) 404 | } 405 | 406 | entrance.distance = 0; // Distance from source to source 407 | while (Q.Count > 0) 408 | { 409 | LayoutRoom best = null; 410 | int min = 9999; 411 | foreach (LayoutRoom other in Q) 412 | { 413 | if (other.distance < min) 414 | { 415 | min = other.distance; 416 | best = other; 417 | } 418 | } 419 | 420 | Q.Remove(best); 421 | foreach (var path in best.connections) // where V is still in Q. 422 | { 423 | LayoutRoom V = path.roomA == best ? path.roomB : path.roomA; 424 | if (!path.active) 425 | { 426 | continue; 427 | } 428 | 429 | if (!Q.Contains(V)) 430 | { 431 | continue; 432 | } 433 | 434 | int alt = best.distance + path.weight; 435 | if (alt < V.distance) 436 | { 437 | V.distance = alt; 438 | V.previous = best; 439 | } 440 | } 441 | } 442 | 443 | // find goal room 444 | goal = null; 445 | int maxGoal = 0; 446 | foreach (var room in rooms.Values) 447 | { 448 | if (room == entrance) 449 | { 450 | continue; 451 | } 452 | 453 | if (!room.isDeadEnd()) 454 | { 455 | continue; 456 | } 457 | 458 | int dist = room.distance; 459 | if (dist > maxGoal) 460 | { 461 | int score = GetRoomScore(room); 462 | if (score > 0) 463 | { 464 | maxGoal = dist; 465 | goal = room; 466 | } 467 | 468 | } 469 | } 470 | 471 | if (goal == null) 472 | { 473 | //LogError("Could not find a goal..."); 474 | return null; 475 | } 476 | 477 | //Debug.LogWarning("Found goal: " + goal.FloorLevel); 478 | return goal; 479 | } 480 | 481 | public void SetGoal(LayoutRoom goal) 482 | { 483 | this.goal = goal; 484 | goal.kind = LayoutRoom.RoomKind.Goal; 485 | 486 | this.GenerateIntensity(); 487 | } 488 | 489 | /// 490 | /// Selects a category for each room, based if its a main path, a side path, secret path etc 491 | /// 492 | private void CategorizeRooms() 493 | { 494 | // first set any room in the shortest path from entrance to goal as a 'main' room 495 | var temp = goal; 496 | while (temp != null) 497 | { 498 | temp.category = LayoutRoom.RoomCategory.Main; 499 | temp.distanceFromMainPath = 0; 500 | temp = temp.previous; 501 | } 502 | 503 | foreach (var room in rooms.Values) 504 | { 505 | if (room.category != LayoutRoom.RoomCategory.Main) 506 | { 507 | continue; 508 | } 509 | 510 | foreach (var path in room.connections) 511 | { 512 | var other = path.roomA == room ? path.roomB : path.roomA; 513 | 514 | if (other.category == LayoutRoom.RoomCategory.Unknown) 515 | { 516 | FloodAdjacentsWithCategory(other, 1, room); 517 | } 518 | } 519 | } 520 | } 521 | 522 | private void FloodChildrenWithCategory(LayoutRoom room, int distance) 523 | { 524 | room.category = LayoutRoom.RoomCategory.Distant; 525 | room.distanceFromMainPath = distance; 526 | 527 | foreach (var child in room.children) 528 | { 529 | FloodChildrenWithCategory(child, distance + 1); 530 | } 531 | } 532 | 533 | private void FloodLoopCategory(LayoutRoom source, LayoutRoom dest) 534 | { 535 | if (source.isDeadEnd()) 536 | { 537 | return; 538 | } 539 | 540 | source.isLoop = true; 541 | 542 | if (source == dest) 543 | { 544 | return; 545 | } 546 | 547 | foreach (var path in source.connections) 548 | { 549 | var other = path.roomA == source ? path.roomB : path.roomA; 550 | 551 | if (other.order < source.order) 552 | { 553 | continue; 554 | } 555 | 556 | FloodLoopCategory(other, dest); 557 | } 558 | } 559 | 560 | private void FixLoopCategory(LayoutRoom source, LayoutRoom dest, LayoutRoom room) 561 | { 562 | if (source.order > dest.order) 563 | { 564 | var temp = source; 565 | source = dest; 566 | dest = temp; 567 | } 568 | 569 | /*while (room != source) 570 | { 571 | 572 | }*/ 573 | 574 | FloodLoopCategory(source, dest); 575 | } 576 | 577 | private void FloodAdjacentsWithCategory(LayoutRoom room, int distance, LayoutRoom parent) 578 | { 579 | if (room.connections.Count>2) 580 | { 581 | FloodChildrenWithCategory(room, distance); 582 | return; 583 | } 584 | 585 | room.category = LayoutRoom.RoomCategory.Side; 586 | room.distanceFromMainPath = distance; 587 | 588 | foreach (var path in room.connections) 589 | { 590 | var other = path.roomA == room ? path.roomB : path.roomA; 591 | 592 | if (other.category == LayoutRoom.RoomCategory.Main && other != parent) 593 | { 594 | FixLoopCategory(other, parent, room); 595 | return; 596 | } 597 | 598 | if (other.category != LayoutRoom.RoomCategory.Unknown) 599 | { 600 | continue; 601 | } 602 | 603 | FloodAdjacentsWithCategory(other, distance + 1, parent); 604 | } 605 | } 606 | 607 | /// 608 | /// Runs Kruskals-Minimum-Spanning-Tree on the dungeon network 609 | /// 610 | /// 611 | private void SolveGraph(List edges) 612 | { 613 | int nTotalCost = 0; 614 | int currentOrder = 0; 615 | 616 | if (edges.Count <= 0) 617 | { 618 | return; 619 | } 620 | 621 | LayoutConnection.QuickSort(edges, 0, edges.Count - 1); 622 | foreach (var ed in edges) 623 | { 624 | if (!ed.active) 625 | { 626 | continue; 627 | } 628 | 629 | LayoutRoom vRoot1, vRoot2; 630 | vRoot1 = ed.roomA.GetRoot(); 631 | vRoot2 = ed.roomB.GetRoot(); 632 | 633 | if (vRoot1 != vRoot2) 634 | { 635 | nTotalCost += ed.weight; 636 | LayoutRoom.Join(vRoot1, vRoot2); 637 | 638 | ed.pathOrder = currentOrder; 639 | ed.active = true; 640 | currentOrder++; 641 | } 642 | } 643 | } 644 | 645 | public void GenerateProgression() 646 | { 647 | this.SolveGraph(this.connections); 648 | 649 | // generate dungeon semantic tree (which represents the logical progression inside the dungeon) 650 | int currentOrder = 1; 651 | FindChildrenProgression(ref currentOrder, entrance, new List()); 652 | 653 | CategorizeRooms(); 654 | } 655 | 656 | protected void FindChildrenProgression(ref int currentOrder, LayoutRoom startRoom, List visitedRooms) 657 | { 658 | startRoom.order = currentOrder; 659 | currentOrder++; 660 | visitedRooms.Add(startRoom); 661 | 662 | startRoom.children = new List(); 663 | foreach (var path in startRoom.connections) 664 | { 665 | if (path.pathOrder < 0) 666 | { 667 | continue; 668 | } 669 | 670 | var other = path.roomA == startRoom ? path.roomB : path.roomA; 671 | //Console.WriteLine("Found connection to " + other.ToString() + " -> " + other.visited + " order: " + path.pathOrder); 672 | 673 | if (!visitedRooms.Contains(other)) 674 | { 675 | other.parent = startRoom; 676 | startRoom.children.Add(other); 677 | FindChildrenProgression(ref currentOrder, other, visitedRooms); 678 | } 679 | } 680 | } 681 | 682 | protected void CalculateIntensity(LayoutRoom room, float value) 683 | { 684 | if (room.intensity >= 0) 685 | { 686 | return; 687 | } 688 | 689 | room.spike = false; 690 | 691 | if (room.kind == LayoutRoom.RoomKind.Goal) 692 | { 693 | value += 1.5f; 694 | room.spike = true; 695 | } 696 | else 697 | { 698 | if (room.contains != null) 699 | { 700 | value += 1.25f; 701 | room.spike = true; 702 | } 703 | else 704 | if (room.important) 705 | { 706 | value += 1; 707 | } 708 | else 709 | { 710 | value += 0.25f; 711 | } 712 | 713 | if (room.kind == LayoutRoom.RoomKind.Treasure) 714 | { 715 | value += 0.5f; 716 | room.spike = true; 717 | } 718 | 719 | if (room.parent != null && room.parent.spike) 720 | { 721 | value -= 2; 722 | } 723 | } 724 | 725 | room.intensity = value; 726 | 727 | foreach (var child in room.children) 728 | { 729 | CalculateIntensity(child, value); 730 | } 731 | } 732 | 733 | protected void GenerateIntensity() 734 | { 735 | if (goal == null) 736 | { 737 | return; 738 | } 739 | 740 | foreach (var room in rooms.Values) 741 | { 742 | room.intensity = -1; 743 | } 744 | 745 | CalculateIntensity(entrance, 0); 746 | 747 | float max = goal.intensity; 748 | 749 | foreach (var room in rooms.Values) 750 | { 751 | if (room.intensity >= max) 752 | { 753 | room.intensity = max; 754 | } 755 | } 756 | 757 | goal.intensity = max; 758 | 759 | // normalize intensities 760 | foreach (var room in rooms.Values) 761 | { 762 | room.intensity /= max; 763 | // Debug.Log(room.Name + " => " + ((int)(room.intensity * 100.0f)).ToString()); 764 | } 765 | } 766 | 767 | #region LOCKS_AND_KEYS 768 | /*protected bool CanBeLocked(LayoutRoom room) 769 | { 770 | if (room.parent == null) 771 | { 772 | return false; 773 | } 774 | 775 | if (!room.lockable) 776 | { 777 | return false; 778 | } 779 | 780 | if (room.kind == LayoutRoom.RoomKind.Entrance) 781 | { 782 | return false; 783 | } 784 | 785 | return room.GetShape() != LayoutRoom.RoomShape.DeadEnd; 786 | //return parent.GetBranchesCount() > 1; 787 | } 788 | 789 | 790 | protected LayoutRoom FindLockableRoom(LayoutRoom currentRoom, List testedRooms, float maxIntensity) 791 | { 792 | if (testedRooms.Contains(currentRoom)) 793 | { 794 | return null; 795 | } 796 | 797 | testedRooms.Add(currentRoom); 798 | 799 | if (currentRoom.locked == null) 800 | { 801 | if (currentRoom.require != null || (currentRoom.intensity < maxIntensity && CanBeLocked(currentRoom))) 802 | { 803 | return currentRoom; 804 | } 805 | 806 | } 807 | 808 | if (currentRoom.parent == null) 809 | { 810 | return null; 811 | } 812 | 813 | return FindLockableRoom(currentRoom.parent, testedRooms, maxIntensity); 814 | } 815 | 816 | protected void LockRoom(LayoutRoom room, LayoutKey keylock) 817 | { 818 | if (room.locked == null) 819 | { 820 | room.locked = keylock; 821 | Console.WriteLine("Locked " + room.ToString()); 822 | } 823 | 824 | foreach (var child in room.children) 825 | { 826 | LockRoom(child, keylock); 827 | } 828 | } 829 | 830 | public bool CanPlaceKey(LayoutRoom room, bool deadEndsonly) 831 | { 832 | if (deadEndsonly && room.GetShape() != LayoutRoom.RoomShape.DeadEnd) 833 | { 834 | return false; 835 | } 836 | 837 | return (room.kind != LayoutRoom.RoomKind.Goal && room.kind != LayoutRoom.RoomKind.Entrance); 838 | }*/ 839 | 840 | /*public void PlaceKey(LayoutRoom sourceRoom, LayoutKey key, LayoutRoom targetRoom, List testedRooms, int count, bool deadEndsonly) 841 | { 842 | if (testedRooms.Contains(sourceRoom)) 843 | { 844 | return; 845 | } 846 | 847 | if (sourceRoom.locked != null) 848 | { 849 | return; 850 | } 851 | 852 | Console.WriteLine("Testing " + this.ToString()); 853 | testedRooms.Add(sourceRoom); 854 | 855 | if (key.room != null) 856 | { 857 | return; 858 | } 859 | 860 | 861 | if (sourceRoom != targetRoom && CanPlaceKey(sourceRoom, deadEndsonly)) 862 | { 863 | if (sourceRoom.contains != null) 864 | { 865 | return; 866 | } 867 | 868 | if (count <= 0) 869 | { 870 | Console.WriteLine("Placed " + key.name + " in room " + sourceRoom); 871 | 872 | key.room = sourceRoom; 873 | sourceRoom.contains = key; 874 | return; 875 | } 876 | } 877 | 878 | List rooms = new List(); 879 | if (sourceRoom.parent != null && !testedRooms.Contains(sourceRoom.parent)) 880 | { 881 | rooms.Add(sourceRoom.parent); 882 | } 883 | 884 | 885 | if (sourceRoom != targetRoom) 886 | { 887 | foreach (var child in sourceRoom.children) 888 | { 889 | if (!testedRooms.Contains(child)) 890 | { 891 | rooms.Add(child); 892 | } 893 | 894 | } 895 | 896 | } 897 | 898 | if (rooms.Count <= 0) 899 | { 900 | return; 901 | } 902 | 903 | while (rooms.Count > 0) 904 | { 905 | int n = this.randomGenerator.Next(rooms.Count); 906 | LayoutRoom target = rooms[n]; 907 | 908 | PlaceKey(target, key, targetRoom, testedRooms, count - 1, deadEndsonly); 909 | if (key.room != null) 910 | { 911 | return; 912 | } 913 | 914 | rooms.RemoveAt(n); 915 | } 916 | }*/ 917 | 918 | protected bool IsRoomImportant(LayoutRoom room) 919 | { 920 | if (room.important) 921 | { 922 | return true; 923 | } 924 | 925 | if (room.contains != null) 926 | { 927 | return true; 928 | } 929 | 930 | foreach (var child in room.children) 931 | { 932 | if (IsRoomImportant(child)) 933 | { 934 | return true; 935 | } 936 | } 937 | 938 | return false; 939 | } 940 | 941 | /*private void FindRoomsForKey(LayoutRoom room, List possibleRooms, List visitedRooms) 942 | { 943 | if (visitedRooms.Contains(room)) 944 | { 945 | return; 946 | } 947 | visitedRooms.Add(room); 948 | 949 | var shape = room.GetShape(); 950 | if (room.category == LayoutRoom.RoomCategory.Distant && (shape == LayoutRoom.RoomShape.DeadEnd || shape == LayoutRoom.RoomShape.Room)) 951 | { 952 | possibleRooms.Add(room); 953 | } 954 | 955 | foreach (var child in room.children) 956 | { 957 | FindRoomsForKey(child, possibleRooms, visitedRooms); 958 | } 959 | 960 | if (room.parent != null) 961 | { 962 | FindRoomsForKey(room.parent, possibleRooms, visitedRooms); 963 | } 964 | }*/ 965 | 966 | private bool isValidKeyRoom(LayoutRoom room) 967 | { 968 | return room.category == LayoutRoom.RoomCategory.Distant && room.GetShape() == LayoutRoom.RoomShape.Room; 969 | } 970 | 971 | public void GenerateLocks(List keys) 972 | { 973 | if (keys == null || keys.Count <= 0) 974 | { 975 | return; 976 | } 977 | 978 | if (goal == null) 979 | { 980 | Console.WriteLine("Goal is not set..."); 981 | return; 982 | } 983 | 984 | keys.Sort((x, y) => x.order.CompareTo(y.order)); 985 | 986 | int firstKeyRoom = rooms.Count + 1; 987 | foreach (var room in rooms.Values) 988 | { 989 | if (isValidKeyRoom(room) && room.order < firstKeyRoom) 990 | { 991 | firstKeyRoom = room.order; 992 | } 993 | } 994 | 995 | List possibleLockedRooms = new List(); 996 | List possibleKeyRooms = new List(); 997 | foreach (var room in rooms.Values) 998 | { 999 | if (room == entrance || room == goal) 1000 | { 1001 | continue; 1002 | } 1003 | 1004 | if (isValidKeyRoom(room)) 1005 | { 1006 | possibleKeyRooms.Add(room); 1007 | } 1008 | 1009 | if (room.category == LayoutRoom.RoomCategory.Main && !room.isLoop && room.order > firstKeyRoom) 1010 | { 1011 | possibleLockedRooms.Add(room); 1012 | 1013 | if (room.require != null) 1014 | { 1015 | var key = room.require; 1016 | key.targetRoom = room; 1017 | } 1018 | } 1019 | } 1020 | 1021 | Dictionary> lockableRooms = new Dictionary>(); 1022 | int roomSpread = possibleLockedRooms.Count / keys.Count; 1023 | 1024 | possibleLockedRooms.Sort((x, y) => x.order.CompareTo(y.order)); 1025 | 1026 | for (int k=0; k=possibleLockedRooms.Count) 1031 | { 1032 | lastRoom = possibleLockedRooms.Count - 1; 1033 | } 1034 | 1035 | var key = keys[k]; 1036 | lockableRooms[key] = new List(); 1037 | for (int i=startRoom; i<=lastRoom; i++) 1038 | { 1039 | lockableRooms[key].Add(possibleLockedRooms[i]); 1040 | } 1041 | } 1042 | 1043 | foreach (LayoutKey key in keys) 1044 | { 1045 | if (key.targetRoom != null) 1046 | { 1047 | continue; 1048 | } 1049 | 1050 | var possibleTargets = lockableRooms[key]; 1051 | 1052 | if (possibleTargets.Count == 0) 1053 | { 1054 | continue; 1055 | } 1056 | 1057 | LayoutRoom targetRoom = possibleTargets[randomGenerator.Next(possibleTargets.Count)]; 1058 | key.targetRoom = targetRoom; 1059 | } 1060 | 1061 | List originalKeys = new List(); 1062 | foreach (LayoutKey key in keys) 1063 | { 1064 | originalKeys.Add(key); 1065 | } 1066 | 1067 | keys.Sort((x, y) => y.order.CompareTo(x.order)); 1068 | for (int k=0; k possibleSources = new List(); 1083 | foreach (var room in possibleKeyRooms) 1084 | { 1085 | if (room.order > key.targetRoom.order) 1086 | { 1087 | continue; 1088 | } 1089 | 1090 | if (nextKey!=null && room.order<=nextKey.targetRoom.order) 1091 | { 1092 | continue; 1093 | } 1094 | 1095 | possibleSources.Add(room); 1096 | } 1097 | 1098 | if (possibleSources.Count == 0) 1099 | { 1100 | foreach (var room in rooms.Values) 1101 | { 1102 | if (room.category != LayoutRoom.RoomCategory.Side) 1103 | { 1104 | continue; 1105 | } 1106 | 1107 | if (room.order > key.targetRoom.order) 1108 | { 1109 | continue; 1110 | } 1111 | 1112 | if (nextKey != null && room.order <= nextKey.targetRoom.order) 1113 | { 1114 | continue; 1115 | } 1116 | 1117 | possibleSources.Add(room); 1118 | } 1119 | } 1120 | 1121 | if (possibleSources.Count == 0) 1122 | { 1123 | continue; 1124 | } 1125 | 1126 | var sourceRoom = possibleSources[randomGenerator.Next(possibleSources.Count)]; 1127 | key.sourceRoom = sourceRoom; 1128 | sourceRoom.contains = key; 1129 | 1130 | possibleKeyRooms.Remove(sourceRoom); 1131 | 1132 | Console.WriteLine("Placed " + key.name + " in room " + sourceRoom); 1133 | } 1134 | 1135 | foreach (LayoutKey key in originalKeys) 1136 | { 1137 | if (key.sourceRoom == null) 1138 | { 1139 | Console.WriteLine("Key '" + key.name + "' was not placed..."); 1140 | } 1141 | } 1142 | 1143 | // after generating the locks, its now possible to understand which rooms are important and which ones are optional 1144 | goal.important = true; 1145 | entrance.important = true; 1146 | foreach (var room in rooms.Values) 1147 | { 1148 | room.important = IsRoomImportant(room); 1149 | } 1150 | 1151 | // generate intensity for rooms based on tension curve 1152 | GenerateIntensity(); 1153 | } 1154 | 1155 | /*public void GenerateLocks(List keys) 1156 | { 1157 | if (keys == null || keys.Count <= 0) 1158 | { 1159 | return; 1160 | } 1161 | 1162 | if (goal == null) 1163 | { 1164 | Console.WriteLine("Goal is not set..."); 1165 | return; 1166 | } 1167 | 1168 | List openKeys = new List(); 1169 | foreach (LayoutKey key in keys) 1170 | { 1171 | openKeys.Add(key); 1172 | } 1173 | 1174 | LayoutRoom currentRoom = goal; 1175 | bool found = true; 1176 | int currentCondition = openKeys.Count; 1177 | float maxLockableIntensity = 1.0f; 1178 | while (openKeys.Count > 0) 1179 | { 1180 | 1181 | found = false; 1182 | 1183 | Console.WriteLine("Trying to find lockable room with max intensity " + (int)(maxLockableIntensity * 100)); 1184 | 1185 | LayoutRoom targetRoom = FindLockableRoom(currentRoom, new List(), maxLockableIntensity); 1186 | if (targetRoom != null) 1187 | { 1188 | Console.WriteLine("Found lockable room " + targetRoom.ToString()); 1189 | LayoutKey targetKey = null; 1190 | 1191 | if (targetRoom.require != null && openKeys.Contains(targetRoom.require)) 1192 | { 1193 | targetKey = targetRoom.require; 1194 | } 1195 | else 1196 | { 1197 | int n = this.randomGenerator.Next(openKeys.Count); 1198 | targetKey = openKeys[n]; 1199 | } 1200 | 1201 | int minKeyDist = 1; 1202 | int keyDistance = minKeyDist + this.randomGenerator.Next(6 - minKeyDist); 1203 | //keyDistance = 20; 1204 | while (keyDistance >= minKeyDist) 1205 | { 1206 | Console.WriteLine("Trying to find room for " + targetKey.name + " at distance " + keyDistance); 1207 | 1208 | 1209 | PlaceKey(targetRoom.parent, targetKey, targetRoom, new List(), keyDistance, true); 1210 | 1211 | if (targetKey.room == null) 1212 | { 1213 | PlaceKey(targetRoom.parent, targetKey, targetRoom, new List(), keyDistance, false); 1214 | } 1215 | 1216 | if (targetKey.room != null) 1217 | { 1218 | LockRoom(targetRoom, targetKey); 1219 | targetRoom.require = targetKey; 1220 | targetKey.condition = currentCondition; 1221 | currentCondition--; 1222 | 1223 | maxLockableIntensity = targetRoom.intensity * 0.8f; 1224 | float intensityLimit = 0.4f; 1225 | if (maxLockableIntensity < intensityLimit) 1226 | { 1227 | maxLockableIntensity = intensityLimit; 1228 | } 1229 | 1230 | 1231 | openKeys.Remove(targetKey); 1232 | currentRoom = targetRoom.parent; 1233 | 1234 | found = true; 1235 | break; 1236 | } 1237 | else 1238 | { 1239 | targetRoom.require = null; 1240 | Console.WriteLine("Unable to place key " + targetKey.name); 1241 | } 1242 | 1243 | keyDistance--; 1244 | } 1245 | 1246 | } 1247 | else 1248 | { 1249 | Console.WriteLine("Unable to find lockable room"); 1250 | } 1251 | 1252 | 1253 | if (!found) 1254 | { 1255 | break; 1256 | } 1257 | } 1258 | 1259 | foreach (LayoutKey key in openKeys) 1260 | { 1261 | Console.WriteLine("Key " + key.name + " was not placed..."); 1262 | } 1263 | 1264 | // after generating the locks, its now possible to understand which rooms are important and which ones are optional 1265 | goal.important = true; 1266 | entrance.important = true; 1267 | foreach (var room in rooms.Values) 1268 | { 1269 | room.important = IsRoomImportant(room); 1270 | } 1271 | 1272 | // convert non-deadend with keys into puzzle rooms 1273 | foreach (var room in rooms.Values) 1274 | { 1275 | if (room.contains != null) 1276 | { 1277 | int n = this.randomGenerator.Next(8); 1278 | if (n > 2) 1279 | { 1280 | room.kind = LayoutRoom.RoomKind.Puzzle; 1281 | } 1282 | else 1283 | { 1284 | room.kind = LayoutRoom.RoomKind.Treasure; 1285 | } 1286 | 1287 | } 1288 | } 1289 | 1290 | // generate intensity for rooms based on tension curve 1291 | GenerateIntensity(); 1292 | }*/ 1293 | 1294 | protected int GetRoomCondition(LayoutRoom room) 1295 | { 1296 | if (room.locked != null) 1297 | { 1298 | return room.locked.condition; 1299 | } 1300 | return -1; 1301 | } 1302 | 1303 | #endregion 1304 | 1305 | public void GenerateBacktracking(float linearity) 1306 | { 1307 | List temp = new List(); 1308 | foreach (var path in connections) 1309 | { 1310 | if (!path.active && path.pathOrder < 0) 1311 | { 1312 | temp.Add(path); 1313 | } 1314 | } 1315 | 1316 | int totalLeft = (int)(temp.Count * (1.0f - linearity)); 1317 | 1318 | while (temp.Count > 0 && totalLeft > 0) 1319 | { 1320 | int n = this.randomGenerator.Next(temp.Count); 1321 | LayoutConnection path = temp[n]; 1322 | temp.RemoveAt(n); 1323 | 1324 | 1325 | if (GetRoomCondition(path.roomA) == GetRoomCondition(path.roomB)) 1326 | { 1327 | path.active = true; 1328 | totalLeft--; 1329 | } 1330 | } 1331 | } 1332 | 1333 | public void GenerateRoomTypes() 1334 | { 1335 | // re-generate intensity 1336 | this.GenerateIntensity(); 1337 | 1338 | // generate monster rooms 1339 | foreach (var room in rooms.Values) 1340 | { 1341 | if (room.kind == LayoutRoom.RoomKind.Hall) 1342 | { 1343 | int n = this.randomGenerator.Next(6); 1344 | 1345 | switch (room.GetShape()) 1346 | { 1347 | case LayoutRoom.RoomShape.Room: 1348 | { 1349 | switch (n) 1350 | { 1351 | case 0: room.kind = LayoutRoom.RoomKind.Shrine; break; 1352 | case 1: room.kind = LayoutRoom.RoomKind.Farm; break; 1353 | case 2: room.kind = LayoutRoom.RoomKind.Puzzle; break; 1354 | default: room.kind = LayoutRoom.RoomKind.Treasure; break; 1355 | } 1356 | break; 1357 | } 1358 | 1359 | case LayoutRoom.RoomShape.Curve: 1360 | { 1361 | switch (n) 1362 | { 1363 | case 0: room.kind = LayoutRoom.RoomKind.Puzzle; break; 1364 | case 1: room.kind = LayoutRoom.RoomKind.Monster; break; 1365 | } 1366 | break; 1367 | } 1368 | 1369 | case LayoutRoom.RoomShape.Split: 1370 | { 1371 | switch (n) 1372 | { 1373 | case 0: room.kind = LayoutRoom.RoomKind.Puzzle; break; 1374 | case 2: room.kind = LayoutRoom.RoomKind.Monster; break; 1375 | case 3: room.kind = LayoutRoom.RoomKind.Shrine; break; 1376 | case 4: room.kind = LayoutRoom.RoomKind.Farm; break; 1377 | } 1378 | break; 1379 | } 1380 | 1381 | case LayoutRoom.RoomShape.Corridor: 1382 | { 1383 | switch (n) 1384 | { 1385 | case 0: room.kind = LayoutRoom.RoomKind.Monster; break; 1386 | } 1387 | break; 1388 | } 1389 | 1390 | } 1391 | 1392 | } 1393 | } 1394 | 1395 | // re-generate intensity 1396 | this.GenerateIntensity(); 1397 | } 1398 | 1399 | } 1400 | } 1401 | --------------------------------------------------------------------------------