source: 2015/23/HenriK/dungeoncrawlar/dungeoncrawlar/dungeoncrawlar/luolaGen.cs @ 7234

Revision 7234, 21.5 KB checked in by sieerinn, 3 years ago (diff)
Line 
1using Microsoft.Xna.Framework;
2using System;
3using System.Collections.Generic;
4using System.Linq;
5using System.Text;
6
7class LuolaGeneraattori
8{
9    public enum Direction
10    {
11        North, South, East, West
12    }
13
14    public enum Tile
15    {
16        Unused, Corridor, Downstairs, Upstairs, DirtWall, DirtFloor, StoneWall, StoneFloor, Chest, Door
17    }
18
19    const string MsgXSize = "X size of dungeon: \t";
20
21    const string MsgYSize = "Y size of dungeon: \t";
22
23    const string MsgMaxObjects = "max # of objects: \t";
24
25    const string MsgNumObjects = "# of objects made: \t";
26
27    // max size of the map
28    int xmax = 800; //columns
29    int ymax = 800; //rows
30
31    int minRoomSize = 8;
32    int maxRoomSize = 14;
33
34    int maxchests;
35
36    // size of the map
37    int _xsize;
38    int _ysize;
39
40    // number of "objects" to generate on the map
41    int _objects;
42
43    // define the %chance to generate either a room or a corridor on the map
44    // BTW, rooms are 1st priority so actually it's enough to just define the chance
45    // of generating a room
46    const int ChanceRoom = 98;
47
48    // our map
49    Tile[] _dungeonMap = { };
50
51    readonly Random _rnd;
52
53    readonly Action<string> _logger;
54
55
56    public LuolaGeneraattori(Action<string> logger)
57    {
58        _rnd = new Random();
59        _logger = logger;
60    }
61
62    public int Corridors
63    {
64        get;
65        private set;
66    }
67
68    public static bool IsWall(int x, int y, int xlen, int ylen, int xt, int yt, Direction d)
69    {
70        Func<int, int, int> a = GetFeatureLowerBound;
71
72        Func<int, int, int> b = IsFeatureWallBound;
73        switch (d)
74        {
75            case Direction.North:
76                return xt == a(x, xlen) || xt == b(x, xlen) || yt == y || yt == y - ylen + 1;
77            case Direction.East:
78                return xt == x || xt == x + xlen - 1 || yt == a(y, ylen) || yt == b(y, ylen);
79            case Direction.South:
80                return xt == a(x, xlen) || xt == b(x, xlen) || yt == y || yt == y + ylen - 1;
81            case Direction.West:
82                return xt == x || xt == x - xlen + 1 || yt == a(y, ylen) || yt == b(y, ylen);
83        }
84
85        throw new InvalidOperationException();
86    }
87
88    public static int GetFeatureLowerBound(int c, int len)
89    {
90        return c - len / 2;
91    }
92
93    public static int IsFeatureWallBound(int c, int len)
94    {
95        return c + (len - 1) / 2;
96    }
97
98    public static int GetFeatureUpperBound(int c, int len)
99    {
100        return c + (len + 1) / 2;
101    }
102
103    public static IEnumerable<Point> GetRoomPoints(int x, int y, int xlen, int ylen, Direction d)
104    {
105        // north and south share the same x strategy
106        // east and west share the same y strategy
107        Func<int, int, int> a = GetFeatureLowerBound;
108        Func<int, int, int> b = GetFeatureUpperBound;
109
110        switch (d)
111        {
112            case Direction.North:
113                for (var xt = a(x, xlen); xt < b(x, xlen); xt++) for (var yt = y; yt > y - ylen; yt--) yield return new Point { X = xt, Y = yt };
114                break;
115            case Direction.East:
116                for (var xt = x; xt < x + xlen; xt++) for (var yt = a(y, ylen); yt < b(y, ylen); yt++) yield return new Point { X = xt, Y = yt };
117                break;
118            case Direction.South:
119                for (var xt = a(x, xlen); xt < b(x, xlen); xt++) for (var yt = y; yt < y + ylen; yt++) yield return new Point { X = xt, Y = yt };
120                break;
121            case Direction.West:
122                for (var xt = x; xt > x - xlen; xt--) for (var yt = a(y, ylen); yt < b(y, ylen); yt++) yield return new Point { X = xt, Y = yt };
123                break;
124            default:
125                yield break;
126        }
127    }
128
129    public Tile GetCellType(int x, int y)
130    {
131        try
132        {
133            return this._dungeonMap[x + this._xsize * y];
134        }
135        catch (IndexOutOfRangeException)
136        {
137            //new { x, y }.Dump("exceptional");
138            throw;
139        }
140    }
141
142    public int GetRand(int min, int max)
143    {
144        return _rnd.Next(min, max);
145    }
146
147    public bool MakeCorridor(int x, int y, int length, Direction direction)
148    {
149        // define the dimensions of the corridor (er.. only the width and height..)
150        int len = this.GetRand(4, length);
151        const Tile Floor = Tile.Corridor;
152
153        int xtemp;
154        int ytemp = 0;
155
156        switch (direction)
157        {
158            case Direction.North:
159                // north
160                // check if there's enough space for the corridor
161                // start with checking it's not out of the boundaries
162                if (x < 0 || x > this._xsize) return false;
163                xtemp = x;
164
165                // same thing here, to make sure it's not out of the boundaries
166                for (ytemp = y; ytemp > (y - len); ytemp--)
167                {
168                    if (ytemp < 0 || ytemp > this._ysize) return false; // oh boho, it was!
169                    if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
170                }
171
172                // if we're still here, let's start building
173                Corridors++;
174                for (ytemp = y; ytemp > (y - len); ytemp--)
175                {
176                    this.SetCell(xtemp, ytemp, Floor);
177                }
178
179                break;
180
181            case Direction.East:
182                // east
183                if (y < 0 || y > this._ysize) return false;
184                ytemp = y;
185
186                for (xtemp = x; xtemp < (x + len); xtemp++)
187                {
188                    if (xtemp < 0 || xtemp > this._xsize) return false;
189                    if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
190                }
191
192                Corridors++;
193                for (xtemp = x; xtemp < (x + len); xtemp++)
194                {
195                    this.SetCell(xtemp, ytemp, Floor);
196                }
197
198                break;
199
200            case Direction.South:
201                // south
202                if (x < 0 || x > this._xsize) return false;
203                xtemp = x;
204
205                for (ytemp = y; ytemp < (y + len); ytemp++)
206                {
207                    if (ytemp < 0 || ytemp > this._ysize) return false;
208                    if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
209                }
210
211                Corridors++;
212                for (ytemp = y; ytemp < (y + len); ytemp++)
213                {
214                    this.SetCell(xtemp, ytemp, Floor);
215                }
216
217                break;
218            case Direction.West:
219                // west
220                if (ytemp < 0 || ytemp > this._ysize) return false;
221                ytemp = y;
222
223                for (xtemp = x; xtemp > (x - len); xtemp--)
224                {
225                    if (xtemp < 0 || xtemp > this._xsize) return false;
226                    if (GetCellType(xtemp, ytemp) != Tile.Unused) return false;
227                }
228
229                Corridors++;
230                for (xtemp = x; xtemp > (x - len); xtemp--)
231                {
232                    this.SetCell(xtemp, ytemp, Floor);
233                }
234
235                break;
236        }
237
238        // woot, we're still here! let's tell the other guys we're done!!
239        return true;
240    }
241
242    public IEnumerable<Tuple<Point, Direction>> GetSurroundingPoints(Point v)
243    {
244        var points = new[]
245                         {
246                                 Tuple.Create(new Point { X = v.X, Y = v.Y + 1 }, Direction.North),
247                                 Tuple.Create(new Point { X = v.X - 1, Y = v.Y }, Direction.East),
248                                 Tuple.Create(new Point { X = v.X , Y = v.Y-1 }, Direction.South),
249                                 Tuple.Create(new Point { X = v.X +1, Y = v.}, Direction.West),
250 
251                             };
252        return points.Where(p => InBounds(p.Item1));
253    }
254
255    public IEnumerable<Tuple<Point, Direction, Tile>> GetSurroundings(Point v)
256    {
257        return
258            this.GetSurroundingPoints(v)
259                .Select(r => Tuple.Create(r.Item1, r.Item2, this.GetCellType(r.Item1.X, r.Item1.Y)));
260    }
261
262    public bool InBounds(int x, int y)
263    {
264        return x > 0 && x < this.xmax && y > 0 && y < this.ymax;
265    }
266
267    public bool InBounds(Point v)
268    {
269        return this.InBounds(v.X, v.Y);
270    }
271
272    public bool MakeRoom(int x, int y, int xlength, int ylength, Direction direction)
273    {
274        // define the dimensions of the room, it should be at least 4x4 tiles (2x2 for walking on, the rest is walls)
275        int xlen = xlength; //this.GetRand(4, xlength);
276        int ylen = ylength; //this.GetRand(4, ylength);
277
278        // the tile type it's going to be filled with
279        const Tile Floor = Tile.DirtFloor;
280
281        const Tile Wall = Tile.DirtWall;
282        // choose the way it's pointing at
283
284        var points = GetRoomPoints(x, y, xlen, ylen, direction).ToArray();
285
286        // Check if there's enough space left for it
287        if (
288            points.Any(
289                s =>
290                s.Y < 0 || s.Y > this._ysize || s.X < 0 || s.X > this._xsize || this.GetCellType(s.X, s.Y) != Tile.Unused)) return false;
291        _logger(
292                  string.Format(
293                      "Making room:int x={0}, int y={1}, int xlength={2}, int ylength={3}, int direction={4}",
294                      x,
295                      y,
296                      xlength,
297                      ylength,
298                      direction));
299
300        foreach (var p in points)
301        {
302            this.SetCell(p.X, p.Y, IsWall(x, y, xlen, ylen, p.X, p.Y, direction) ? Wall : Floor);
303        }
304
305        // yay, all done
306        return true;
307    }
308
309    public Tile[] GetDungeonTiles()
310    {
311        return this._dungeonMap;
312    }
313
314    public char GetCellTile(int x, int y)
315    {
316        switch (GetCellType(x, y))
317        {
318            case Tile.Unused:
319                return '.';
320            case Tile.DirtWall:
321                return '|';
322            case Tile.DirtFloor:
323                return '_';
324            case Tile.StoneWall:
325                return 'S';
326            case Tile.Corridor:
327                return '#';
328            case Tile.Door:
329                return 'D';
330            case Tile.Upstairs:
331                return '+';
332            case Tile.Downstairs:
333                return '-';
334            case Tile.Chest:
335                return 'C';
336            default:
337                throw new ArgumentOutOfRangeException("x,y");
338        }
339    }
340
341    //used to print the map on the screen
342    public char[,] GetDungeon()
343    {
344        char[,] tiles = new char[_ysize, _xsize];
345
346        for (int y = 0; y < this._ysize; y++)
347        {
348            string s = "";
349            for (int x = 0; x < this._xsize; x++)
350            {
351                tiles[y, x] = GetCellTile(x, y);
352                s += GetCellTile(x, y);
353            }
354            Console.WriteLine(s);
355        }
356
357        return tiles;
358    }
359
360    public Direction RandomDirection()
361    {
362        int dir = this.GetRand(0, 4);
363        switch (dir)
364        {
365            case 0:
366                return Direction.North;
367            case 1:
368                return Direction.East;
369            case 2:
370                return Direction.South;
371            case 3:
372                return Direction.West;
373            default:
374                throw new InvalidOperationException();
375        }
376    }
377
378    //and here's the one generating the whole map
379    public bool CreateDungeon(int width, int height, int features, int chests)
380    {
381        maxchests = chests;
382        this._objects = features < 1 ? 10 : features;
383
384        // adjust the size of the map, if it's smaller or bigger than the limits
385        if (width < 3) this._xsize = 3;
386        else if (width > xmax) this._xsize = xmax;
387        else this._xsize = width;
388
389        if (height < 3) this._ysize = 3;
390        else if (height > ymax) this._ysize = ymax;
391        else this._ysize = height;
392
393        //Console.WriteLine(MsgXSize + this._xsize);
394        //Console.WriteLine(MsgYSize + this._ysize);
395        //Console.WriteLine(MsgMaxObjects + this._objects);
396
397        // redefine the map var, so it's adjusted to our new map size
398        this._dungeonMap = new Tile[this._xsize * this._ysize];
399
400        // start with making the "standard stuff" on the map
401        this.Initialize();
402
403        /*******************************************************************************
404        And now the code of the random-map-generation-algorithm begins!
405        *******************************************************************************/
406
407        // start with making a room in the middle, which we can start building upon
408        this.MakeRoom(this._xsize / 2, this._ysize / 2, 8, 6, RandomDirection()); // getrand saken f????r att slumpa fram riktning p?? rummet
409
410        // keep count of the number of "objects" we've made
411        int currentFeatures = 1; // +1 for the first room we just made
412
413        // then we sart the main loop
414        for (int countingTries = 0; countingTries < 1000; countingTries++)
415        {
416            // check if we've reached our quota
417            if (currentFeatures == this._objects)
418            {
419                break;
420            }
421
422            // start with a random wall
423            int newx = 0;
424            int xmod = 0;
425            int newy = 0;
426            int ymod = 0;
427            Direction? validTile = null;
428
429            // 1000 chances to find a suitable object (room or corridor)..
430            for (int testing = 0; testing < 1000; testing++)
431            {
432                newx = this.GetRand(1, this._xsize - 1);
433                newy = this.GetRand(1, this._ysize - 1);
434
435                if (GetCellType(newx, newy) == Tile.DirtWall || GetCellType(newx, newy) == Tile.Corridor)
436                {
437                    var surroundings = this.GetSurroundings(new Point { X = newx, Y = newy });
438
439                    // check if we can reach the place
440                    var canReach =
441                        surroundings.FirstOrDefault(s => s.Item3 == Tile.Corridor || s.Item3 == Tile.DirtFloor);
442                    if (canReach == null)
443                    {
444                        continue;
445                    }
446                    validTile = canReach.Item2;
447                    switch (canReach.Item2)
448                    {
449                        case Direction.North:
450                            xmod = 0;
451                            ymod = -1;
452                            break;
453                        case Direction.East:
454                            xmod = 1;
455                            ymod = 0;
456                            break;
457                        case Direction.South:
458                            xmod = 0;
459                            ymod = 1;
460                            break;
461                        case Direction.West:
462                            xmod = -1;
463                            ymod = 0;
464                            break;
465                        default:
466                            throw new InvalidOperationException();
467                    }
468
469
470                    // check that we haven't got another door nearby, so we won't get alot of openings besides
471                    // each other
472
473                    if (GetCellType(newx, newy + 1) == Tile.Door) // north
474                    {
475                        validTile = null;
476
477                    }
478
479                    else if (GetCellType(newx - 1, newy) == Tile.Door) // east
480                        validTile = null;
481                    else if (GetCellType(newx, newy - 1) == Tile.Door) // south
482                        validTile = null;
483                    else if (GetCellType(newx + 1, newy) == Tile.Door) // west
484                        validTile = null;
485
486
487                    // if we can, jump out of the loop and continue with the rest
488                    if (validTile.HasValue) break;
489                }
490            }
491
492            if (validTile.HasValue)
493            {
494                // choose what to build now at our newly found place, and at what direction
495                int feature = this.GetRand(0, 100);
496                if (feature <= ChanceRoom)
497                { // a new room
498                    int randomWidth = _rnd.Next(minRoomSize, maxRoomSize);
499                    int randomHeight = _rnd.Next(minRoomSize, maxRoomSize);
500                    if (this.MakeRoom(newx + xmod, newy + ymod, randomWidth, randomHeight, validTile.Value))
501                    {
502                        currentFeatures++; // add to our quota
503
504                        // then we mark the wall opening with a door
505                        this.SetCell(newx, newy, Tile.Door);
506
507                        // clean up infront of the door so we can reach it
508                        this.SetCell(newx + xmod, newy + ymod, Tile.DirtFloor);
509                    }
510                }
511                else if (feature >= ChanceRoom)
512                { // new corridor
513                    if (this.MakeCorridor(newx + xmod, newy + ymod, 8, validTile.Value))
514                    {
515                        // same thing here, add to the quota and a door
516                        currentFeatures++;
517
518                        this.SetCell(newx, newy, Tile.Door);
519                    }
520                }
521            }
522        }
523
524        /*******************************************************************************
525        All done with the building, let's finish this one off
526        *******************************************************************************/
527        AddSprinkles();
528
529        // all done with the map generation, tell the user about it and finish
530        Console.WriteLine(MsgNumObjects + currentFeatures);
531
532        return true;
533    }
534
535    void Initialize()
536    {
537        for (int y = 0; y < this._ysize; y++)
538        {
539            for (int x = 0; x < this._xsize; x++)
540            {
541                // ie, making the borders of unwalkable walls
542                if (y == 0 || y == this._ysize - 1 || x == 0 || x == this._xsize - 1)
543                {
544                    this.SetCell(x, y, Tile.StoneWall);
545                }
546                else
547                {                        // and fill the rest with dirt
548                    this.SetCell(x, y, Tile.Unused);
549                }
550            }
551        }
552    }
553
554    // setting a tile's type
555    void SetCell(int x, int y, Tile celltype)
556    {
557        this._dungeonMap[x + this._xsize * y] = celltype;
558    }
559
560    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
561    void AddSprinkles()
562    {
563        // sprinkle out the bonusstuff (stairs, chests etc.) over the map
564        int chests = 0;
565        int state = 0; // the state the loop is in, start with the stairs
566        while (state != 10)
567        {
568            for (int testing = 0; testing < 1000; testing++)
569            {
570                var newx = this.GetRand(1, this._xsize - 1);
571                int newy = this.GetRand(1, this._ysize - 2);
572
573                // Console.WriteLine("x: " + newx + "\ty: " + newy);
574                int ways = 4; // from how many directions we can reach the random spot from
575
576                // check if we can reach the spot
577                if (GetCellType(newx, newy + 1) == Tile.DirtFloor || GetCellType(newx, newy + 1) == Tile.Corridor)
578                {
579                    // north
580                    if (GetCellType(newx, newy + 1) != Tile.Door)
581                        ways--;
582                }
583
584                if (GetCellType(newx - 1, newy) == Tile.DirtFloor || GetCellType(newx - 1, newy) == Tile.Corridor)
585                {
586                    // east
587                    if (GetCellType(newx - 1, newy) != Tile.Door)
588                        ways--;
589                }
590
591                if (GetCellType(newx, newy - 1) == Tile.DirtFloor || GetCellType(newx, newy - 1) == Tile.Corridor)
592                {
593                    // south
594                    if (GetCellType(newx, newy - 1) != Tile.Door)
595                        ways--;
596                }
597
598                if (GetCellType(newx + 1, newy) == Tile.DirtFloor || GetCellType(newx + 1, newy) == Tile.Corridor)
599                {
600                    // west
601                    if (GetCellType(newx + 1, newy) != Tile.Door)
602                        ways--;
603                }
604
605                if (state == 1)
606                {
607                    if (ways == 0)
608                    {
609                        this.SetCell(newx, newy, Tile.Upstairs);
610                        state = 2;
611                        break;
612                    }
613                }
614                else if (state == 2)
615                {
616                    if (ways == 0)
617                    {
618                        this.SetCell(newx, newy, Tile.Downstairs);
619                        state = 10;
620                        break;
621                    }
622                }
623                else if (state == 0)
624                {
625                    if (ways == 0)
626                    {
627                        this.SetCell(newx, newy, Tile.Chest);
628                        //this.SetCell(newx, newy, Tile.Corridor);
629                        chests++;
630                    }
631
632                    if (chests > maxchests)
633                        state = 1;
634                }
635            }
636        }
637    }
638}
Note: See TracBrowser for help on using the repository browser.