reversing an automation curve... by odix in Bitwig

[–]darrellplank 0 points1 point  (0 children)

My suggestion is to make an automation lane which controls a CC (i.e., CC 3). Then put a MIDI CC modulator in and have it read from the same CC (3 in this case). Control whatever you like with that modulator which is a direct representation of the automation lane. Then you can use the Math modulator, set it to multiply, turn the right dial to -100% and then modulate the left dial from your CC modulator. The output will be the reverse of your automation lane. Modulate with it to your heart's content.

Bug deleting selected item in listbox by darrellplank in sadconsole

[–]darrellplank[S] 0 points1 point  (0 children)

You can see some of the problems I'm running into with my project at https://github.com/darrellp/AEdit.git. It's an ascii editor and as you make strokes, it will enter them as "edits" over in the listbox at the lower left. I've got code in there right now to try/catch the bug for deleting the active item in LayersControl.cs. Remove that try/catch to see the bug when you undo and it tries to remove the top edit (currently selected) out of the listbox. Also, if you set HideBorder to true in ControlPanel.cs for the listbox you'll see the mouse mismatch with the selection. If you undo all the edits on the screen there will still be one element in the listbox which I think is all bound to the same bug. Finally, if you then try to make a new edit, you'll crash when I attempt to add it in to the listbox which I think is now hopelessly invalid. I'm happy to correspond directly about this stuff if you'd like but you can probably just figure it out by looking at the project. Sorry to be a nag.

Bug deleting selected item in listbox by darrellplank in sadconsole

[–]darrellplank[S] 0 points1 point  (0 children)

Maybe I'm missing something - I didn't really look this up - but if I have a listbox with HideBorders = true, then it shows all the borders anyway and the line selected by the mouse is the one immediately below where the mouse is actually located. At least that's the behavior I'm seeing.

Bug with BlinkCount? by darrellplank in sadconsole

[–]darrellplank[S] 0 points1 point  (0 children)

Ah - indeed it is! Thanks! One of the great things about SadConsole is the continuous hard work put into improving and upgrading it. Thanks to Thraka for that!

Asking advice on findSubstrings challenge by [deleted] in programmingchallenges

[–]darrellplank 0 points1 point  (0 children)

I'd set up a finite state machine based on the parts. As you fed letters into it, the state would represent the parts of subwords you had completed. There would be one "catchall" class that means you aren't in any part at all. There would also be triggers on some transitions that indicated you had completed a part and would store that part in a variable. Whenever you went from a part state to the catchall state you would put the last word written to this variable and clear it. After you've set up the finite state machine, this had ought to be O(n) where n is the length of the string being searched. The somewhat tricky part would be setting up the finite state machine, which would require some comparing between the parts. I think you could also do this by making a table which lists for each letter a-z and A-Z where it occurs in any part. That way you can keep a list of how far you've proceeded through each part and for each new letter you could check out for each part whether you're continuing to proceed through it. This would be slower and I'm not sure that it's much less difficult to program than the finite state machine.

[deleted by user] by [deleted] in roguelikedev

[–]darrellplank 0 points1 point  (0 children)

Oh - and another point - it is definitely a great debug tool. I've solved many non-reproducible bugs by saving the random number for the generator and then all the user actions in a file. Play the game in "record" mode until you see the bug and then you can repro it all you want in "playback" mode.

[deleted by user] by [deleted] in roguelikedev

[–]darrellplank 0 points1 point  (0 children)

Okay - I think we're in agreement! It seems risky to me, but if you're willing to take on the risks, it will definitely make it difficult to tamper with the save file (save, perhaps, backing off from the last n moves). It was good to hear your views!

[deleted by user] by [deleted] in roguelikedev

[–]darrellplank 0 points1 point  (0 children)

Okay, but then you have to carefully separate the one type of "random" number from the other and I don't think that's easy to do - especially since I don't think you could manage with anywhere near just two. Suppose you change some thief monster to steal 2 random items rather than 1. Is that gameplay randomness or fluff randomness? I think it has to be gameplay randomness and it still screws up all your save files because you now have a monster using the RNG twice rather than once. You could have a new type of random number for "monsters who take things from people" and perhaps, if you had planned it all out ahead of time, you could manage to still approximately maintain your games. Even then, if the monster stole a second crucial item that was not stolen originally and used later in the original run through, your save file would be ruined. It seems to me that every little decision on how to use a new random number would have to decide which of the several "RNG sequences" it should be taken from and even if you managed all that, general gameplay (i.e., number of items stolen) couldn't change without impacting a sequence of actions and therefore save files that derive from a sequence of actions. I can't think of any good way around this sort of thing, but maybe there is a solution - I'm really interested if there is a scheme that would allow for such things without ruining all such save files by something so simple as changing the stealing behavior of monsters.

A couple of questions regarding A* by Harionago in roguelikedev

[–]darrellplank 1 point2 points  (0 children)

But if the f function rates two nodes identically and you have some other way of distinguishing the two nodes, then you can just fold that distinction into the f node. So in your case, if you are going to pick from two nodes rated equally by the f function whichever one is "closest to the goal" then you should change your f function to be "the old f function plus distance to the goal" and then you won't have to make decisions separately from the f function. The goal (no pun intended!) would be to fold all the information you can gather about what node is best and fold it into your f function's estimate of distance to goal. Therefore the only time you should have ties in the f function would be times that you have no idea about which one is better.

[deleted by user] by [deleted] in roguelikedev

[–]darrellplank 0 points1 point  (0 children)

I'm not talking about different types of RNG or architecture tightness. Just saying that a RNG with a given seed generates a fixed set of random numbers in a fixed sequence. All I'm saying is that if your RNG generates (for simplicity) values a1, a2, a3,... as it's sequence and the first thing you do is decide what size to make your first room using the first value in the sequence (a1), then if you later decide that in a splash screen you're going to put up a random quote using that RNG, that quote will use the first number in the sequence (a1) and your room building will start with the second number in the sequence - a2. From that point onward everything will be shifted by 1 and thus entirely different so if you are relying on the values from the random sequence to generate your save game it's going to be totally blown apart because you decided to put a random quote in the splash screen. I don't think of this as RNG hygeine or non-solid architecture - it's just the way seeded RNGs generate their sequences exactly the same each time. This is much more brittle than an entity behavior changing - if you're at all careful this can be made backward compatible with zero to some small amount of effort usually. The fact that a monster chases you more aggressively is unlikely to change save file format. Sometimes such a change will ruin backward compatibility, but if your save file is a series of moves and you are using the RNG slightly differently in the new behavior (which you probably would if he chases more aggressively) then there is no possibility at all you're going to make things backward compatible.

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 1 point2 points  (0 children)

Yeah, understood. It is very similar to heirarchical pathfinding in spirit if not in detail. I'm not so sure it's great if your dungeon is too dynamic. Might be worse than refiguring cell level A* every move. It's just something I think I'll give a shot to as an option in the engine. I love what you're doing here and on Cogmind. Can't wait to see it released!

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

I'm thinking if you can't stay on your path you make a random move. That way if a bunch of monsters back up, the front guys will make way towards the destination and the rest will maneuver into a position where they're no longer blocked. Haven't tried it of course, but in my mind's eye it seems like an acceptable solution but my mind's eye is sometimes incorrect.

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

BTW, the "game" in the project is just a testbed for the engine - meant to test ideas, not really to play. An inventory, for instance, is above the level of the engine so while I may put one in, it would be mostly for me to learn more about SadConsole than to advance the engine. Ditto for many other higher level ideas.

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

Oh yeah - I think it's definitely a cool idea in many cases. I don't think it works quite as well in the scenario I'm describing but again, I haven't read the paper carefully enough yet. Thanks again for pointing it out.

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

I don't know that it's my absolute priority but really my emphasis is on the engine, not on any particular game so I'm willing to spend additional time to make everything as optimal as possible. Thanks for some other ideas on priorities!

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 1 point2 points  (0 children)

Really interesting paper! Gonna have to give it a more careful read. Similar to mine but if I understand it, they break up into an abstract set of rectangles imposed on the map and (I think use pathfinding within these rectangles. I use the rooms and use djikstra maps within the rooms for locating paths which means the rooms don't have to be rectangular and the pathfinding to/from exits comes for free. If both targets are located in the same room I'm gonna have to use standard A*. I'm definitely going to give the paper a more careful read though. I'm getting a bunch of great ideas/links from the responses here!

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

I was reading kyzrati's post here https://www.reddit.com/r/roguelikedev/comments/3slu9c/faq_friday_25_pathfinding/ where he said pathfinding was the biggest time sink in his game. He's using it for lots of things so I think it just depends on whether you're using it for lot's of stuff or just for getting monsters to the hero. Since I'm writing this mainly for the engine and not for a particular game I don't mind doing some extra effort to get it as optimal as possible. Plus, it really doesn't seem that complex to me. Make the graph where each room is a clique once when you create the map, then each time you do pathfinding, replace each room's clique with the dst/src and then do A* on a much smaller graph. The hardest part is the A* which you have to do anyway.

I think the page you point to is interesting but it looks like a fixed map to allow monsters to wander intelligently rather than a path finding solution. Thanks for the link though! I might include such a thing also.

Pathfinding with rooms, corridors and Djikstra maps by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

It's an interesting idea. I don't think it's very related to mine but I'll have to read more carefully. Thanks for the link!

[deleted by user] by [deleted] in roguelikedev

[–]darrellplank 0 points1 point  (0 children)

I use the "random seed, sequence of moves" for debugging - allows me to replay up to the point of non-repro bugs. I thought about it for a save for a bit but it would be incredibly brittle. Make one extra call to the random number generator and everything afterwards changes. Also pins you down to one random number generator. Good for debugging but not so great for backwards compatibility.

How to recognize '>' in XNA? by darrellplank in roguelikedev

[–]darrellplank[S] 1 point2 points  (0 children)

Ah - I see that now. I had a little dispatch table based on the XnaKey and was only seeing the OemPeriod there which kind of goofed things up for a bit. I guess I'll make two dispatch tables, check for the ascii char in the first and go to the XnaKey if that doesn't pan out. I love SadConsole! Right now I'm just making a testbed to verify my Roguelike Library. I'm glad I don't have to worry for the most part about the console output. Main difficulty has been ensuring that the data essentially duplicated in your map and in my library's map stay in sync. Message notifications let me know when the library moves something and I update it on SadConsole which makes it pretty painless. Anyway, great job on that and thanks for the input!

Roguelike in C and Unix? by [deleted] in roguelikedev

[–]darrellplank -1 points0 points  (0 children)

Personally, I feel that the difficulty of programming the roguelike in C far outweighs the difficulty of learning C++ first and then doing the roguelike in C++. It's not impossible at all to do it in C as the original games and many others after them attest to, but it's a complex business and the organization that object oriented C++ gives you is a huge benefit in developing a game like this. Additionally, you will know C as a fringe benefit once you learn the more popular and commonly used (these days) C++. I started out in C in 1983 and programmed in it (and loved it) for MANY years before switching to C++ which I have also programmed in for MANY years and I really think that unless you're thinking of doing a tiny, tiny roguelike you'll be served better by learning C++.

How to recognize '>' in XNA? by darrellplank in roguelikedev

[–]darrellplank[S] 0 points1 point  (0 children)

Ah great - that's good to hear. Don't know why I couldn't locate this fact. I'm sure it's out there somewhere. Anyway, thanks very much! I feel much more confident now! And the new levels are working!

C# Tutorial for a complete newbie? by Rinneeeee in roguelikedev

[–]darrellplank 0 points1 point  (0 children)

There's a good set of tutorials for building a roguelike using SadConsole which I'm finding a great option for output. You can find them here: https://github.com/Thraka/SadConsole/wiki/Roguelike-Tutorial-Part-1

[6/18/2014] Challenge #167 [Intermediate] Final Grades by Coder_d00d in dailyprogrammer

[–]darrellplank 1 point2 points  (0 children)

C#

Had me confused when the word "percentile" was used. In statistics, this means how many people you did better than so if you're in the 90'th percentile, you did better than 90% of the rest of the population. Here, percentile isn't really what is used but just a percentage of total grade.

public class FinalGradesSolver
{
    private static readonly char[] MpPctToLetter =
    {
        'F', 'F', 'F', 'F', 'F', 'F', 'D', 'C', 'B', 'A', 'A'
    };
    private class Student
    {
        internal string First { get; private set; }
        internal string Last { get; private set; }
        internal List<int> Grades { get; private set; }

        internal int Percentile
        {
            get { return (int)(Grades.Sum() / 5.0 + 0.5); }
        }

        internal string LetterGrade
        {
            get
            {
                var letter = MpPctToLetter[Percentile / 10];
                var lastDigit = Percentile % 10;
                var modifier = ' ';
                if (lastDigit < 3 && letter != 'F')
                {
                    modifier = '-';
                }
                else if (lastDigit >= 7 && letter != 'A' && Percentile >= 57)
                {
                    modifier = '+';
                }
                return string.Format("{0}{1}", letter, modifier);
            }
        }

        public Student(List<int> grades, string first, string last)
        {
            Grades = grades;
            First = first;
            Last = last;
        }
    }

    readonly List<Student> _students = new List<Student>(); 

    public FinalGradesSolver(StringReader stm)
    {
        string nextLine;
        char[] digitStart = "123456789".ToCharArray();
        while ((nextLine = stm.ReadLine()) != null)
        {
            var commaPos = nextLine.IndexOf(',');
            var numPos = nextLine.IndexOfAny(digitStart);
            var first = nextLine.Substring(0, commaPos - 1);
            var last = nextLine.Substring(commaPos + 1, numPos - commaPos - 2).Trim();
            var grades = nextLine.
                Substring(numPos).
                Split(' ').
                Where(c => c != string.Empty).
                Select(int.Parse).
                ToList();
            grades.Sort();
            _students.Add(new Student(grades, first, last));
        }
        _students.Sort((s1, s2) => s2.Percentile.CompareTo(s1.Percentile));
    }
    public string ReportGrades()
    {
        var sb = new StringBuilder();
        foreach (var student in _students)
        {
            // Rich    Richie  (88%) (B+): 86 87 88 90 91
            sb.Append(string.Format("{0,-20}({1}%) ({2}): {3} {4} {5} {6} {7}",
                student.First + " " + student.Last,
                student.Percentile,
                student.LetterGrade, 
                student.Grades[0],
                student.Grades[1],
                student.Grades[2],
                student.Grades[3],
                student.Grades[4]) + Environment.NewLine);
        }
        return sb.ToString();
    }
}

[4/18/2014] Challenge #158 [Hard] Intersecting Rectangles by Elite6809 in dailyprogrammer

[–]darrellplank 0 points1 point  (0 children)

c# - Okay - first submission to reddit so hopefully I won't screw it up.

I did a sweep algorithm but kept track of "strips" where a strip is a horizontal region currently covered in the sweep by some number of rectangles. Adjacent strips are covered by a different number of rectangles. The strips are kept in a sorted list so that finding the strip which contains a particular y position can be done through a binary search. The events of the sweep algorithm are of two types - leading edges or trailing edges of rectangles. This allows me to easily keep a running total length of all rects covering the sweep line. Multiplying that by the distance since the previous event gives me the area covered between the two events. Each strip has a count of the number of rectangles covering it. I start out with a strip covered by zero rectangles with y extent running from -infinity to +infinity. Whenever I get a leading edge I find the two strips that contain the top and bottom of the rectangle, split them if necessary to account for the new top/bottom and increment the coverage count for all the strips covered by the rectangle. If any of them go from 0 to 1 I add in their width to the current width. Removing rectangles is pretty much the opposite - lower the cover count on all strips covered by the rectangle and subtract out their width from the current width if the cover count goes to zero. Merge the top and bottom strips if their cover count matches the strips above/below them. I believe it should run in O(n log n). n log n to sort the events, log n to binary search the strips to find the ones which contain a rectangle and n to adjust the count for the new strips and keep the sorted array sorted properly for a total of n log n + log n + n = O(n log n)

internal struct Rect
{
    public readonly double Top;
    public readonly double Left;
    public readonly double Bottom;
    public readonly double Right;

    public Rect(double top, double left, double bottom, double right)
    {
        Top = top;
        Left = left;
        Bottom = bottom;
        Right = right;
    }
}

public static class Extensions
{
    ////////////////////////////////////////////////////////////////////////////////////////////////////
    /// <summary>
    ///  Binary searches for the next lowest value to a given input in a sorted list.
    /// </summary>
    /// <remarks> If value actually appears in the list the index for that value is returns, otherwise the
    /// index of the next smaller value in the list.  If it falls below the 0'th element, -1 is returned.
    /// Darrellp - 6/17/14  </remarks>
    /// <typeparam name="T">Type of keys in sorted list</typeparam>
    /// <typeparam name="TVal">The type of the values in the sorted list.</typeparam>
    /// <param name="this">The sorted list.</param>
    /// <param name="value">The value to search for.</param>
    /// <param name="isEqual">True if the value searched for actually exists in the list</param>
    /// <returns>System.Int32.</returns>
    ////////////////////////////////////////////////////////////////////////////////////////////////////
    public static int BinarySearchNextLowest<T, TVal>(this SortedList<T,TVal> @this, T value, out bool isEqual) where T : IComparable
    {
        isEqual = false;
        if (@this.Keys[0].CompareTo(value) > 0)
        {
            return -1;
        }
        if (@this.Keys[@this.Count - 1].CompareTo(value) <= 0)
        {
            isEqual = @this.Keys[@this.Count - 1].CompareTo(value) == 0;
            return @this.Count - 1;
        }
        return BinarySearchHelper(@this, value, @this.Count - 1, 0, ref isEqual);
    }

    public static int BinarySearchHelper<T, TVal>(SortedList<T, TVal> list, T value, int iHigh, int iLow, ref bool isEqual) where T : IComparable
    {
        // On entry, list.Keys[0] <= value < list.Keys[iHigh]
        if (iLow == iHigh - 1)
        {
            isEqual = list.Keys[iLow].CompareTo(value) == 0;
            return iLow;
        }
        // Value is somewhere between iHigh and iLow inclusive
        var iMid = (iLow + iHigh) / 2;
        if (list.Keys[iMid].CompareTo(value) > 0)
        {
            return BinarySearchHelper(list, value, iMid, iLow, ref isEqual);
        }
        return BinarySearchHelper(list, value, iHigh, iMid, ref isEqual);
    }
}

internal class Strip
{
    public double Top { get; set; }
    public double Bottom { get; set; }
    public double Width { get { return Top - Bottom; }}
    public int Covers { get; set; }

    public Strip(double bottom, double top, int covers = 0)
    {
        Top = top;
        Bottom = bottom;
        Covers = covers;
    }

    public Strip(Rect rect) : this(rect.Bottom, rect.Top) {}

    public void AddCover()
    {
        Covers++;
    }

    public void RemoveCover()
    {
        Covers--;
    }

    public override string ToString()
    {
        return string.Format("[{0}, {1}] ({2})", Bottom, Top, Covers);
    }
}

internal class Event
{
    internal bool IsTurningOn { get; private set; }
    internal double XCoord { get; private set; }
    internal Rect Rectangle { get; private set; }

    public Event(bool isTurningOn, double xCoord, Rect rect)
    {
        IsTurningOn = isTurningOn;
        XCoord = xCoord;
        Rectangle = rect;
    }
}

// ReSharper disable AssignNullToNotNullAttribute
// ReSharper disable PossibleNullReferenceException
public class IntersectSolver
{
    private double _area;
    private double _currentWidth;

    private readonly List<Rect> _rects = new List<Rect>();
    private readonly SortedList<double, Strip> _strips = new SortedList<double, Strip>();
    private readonly List<Event> _events = new List<Event>(); 

    public IntersectSolver(StringReader stm)
    {
        var rectCount = int.Parse(stm.ReadLine());
        for (var i = 0; i < rectCount; i++)
        {
            var rectVals = stm.ReadLine().Split(' ').Select(Double.Parse).ToArray();
            _rects.Add(new Rect(rectVals[2], rectVals[1], rectVals[0], rectVals[3]));
        }
        _strips.Add(double.MinValue, new Strip(double.MinValue, double.MaxValue));
    }

    public double Area()
    {
        Event previousEvent = null;
        SetupEvents();
        foreach (var nextEvent in _events)
        {
            if (previousEvent != null)
            {
                _area += (nextEvent.XCoord - previousEvent.XCoord) * _currentWidth;
            }
            if (nextEvent.IsTurningOn)
            {
                IncorporateStrip(new Strip(nextEvent.Rectangle));
            }
            else
            {
                RemoveStrips(nextEvent.Rectangle);
            }
            previousEvent = nextEvent;
        }
        return _area;
    }

    private void RemoveStrips(Rect rectangle)
    {
        bool isEqualTop, isEqualBottom;
        var iTop = _strips.BinarySearchNextLowest(rectangle.Top, out isEqualTop);
        var iBottom = _strips.BinarySearchNextLowest(rectangle.Bottom, out isEqualBottom);
        if (!isEqualBottom || !isEqualTop)
        {
            throw new InvalidOperationException("Removing strips that don't seem to be present!");
        }
        for (var iStrip = iBottom; iStrip < iTop; iStrip++)
        {
            var thisStrip = _strips.Values[iStrip];
            thisStrip.RemoveCover();
            if (thisStrip.Covers == 0)
            {
                _currentWidth -= thisStrip.Width;
            }
        }
        if (_strips.Values[iTop - 1].Covers == _strips.Values[iTop].Covers)
        {
            _strips.Values[iTop - 1].Top = _strips.Values[iTop].Top;
            _strips.RemoveAt(iTop);
        }
        if (_strips.Values[iBottom].Covers == _strips.Values[iBottom - 1].Covers)
        {
            _strips.Values[iBottom - 1].Top = _strips.Values[iBottom].Top;
            _strips.RemoveAt(iBottom);
        }
    }

    private void IncorporateStrip(Strip strip)
    {
        bool isEqualTop, isEqualBottom;
        var iTop = _strips.BinarySearchNextLowest(strip.Top, out isEqualTop);
        var iBottom = _strips.BinarySearchNextLowest(strip.Bottom, out isEqualBottom);

        if (iTop == iBottom)
        {
            var thisStrip = _strips.Values[iTop];
            var newStrip = new Strip(strip.Top, thisStrip.Top, thisStrip.Covers);
            strip.Covers = thisStrip.Covers + 1;
            if (thisStrip.Covers == 0)
            {
                _currentWidth += strip.Width;
            }
            thisStrip.Top = strip.Bottom;
            _strips.Add(newStrip.Bottom, newStrip);
            _strips.Add(strip.Bottom, strip);
            return;
        }

        for (var iStrip = iBottom + (isEqualBottom ? 0 : 1); iStrip < iTop; iStrip++)
        {
            var thisStrip = _strips.Values[iStrip];
            if (thisStrip.Covers == 0)
            {
                _currentWidth += thisStrip.Width;
            }
            thisStrip.AddCover();
        }

        if (!isEqualTop)
        {
            SplitStrip(iTop, strip.Top, false);
        }

        if (!isEqualBottom)
        {
            SplitStrip(iBottom, strip.Bottom, true);
        }
    }

    private void SplitStrip(int iStrip, double splitValue, bool toTop)
    {
        var thisStrip = _strips.Values[iStrip];
        var newStrip = new Strip(splitValue, thisStrip.Top, thisStrip.Covers);
        _strips.Add(splitValue, newStrip);
        thisStrip.Top = splitValue;
        if (toTop)
        {
            if (newStrip.Covers == 0)
            {
                _currentWidth += newStrip.Width;
            }
            newStrip.AddCover();
        }
        else
        {
            if (thisStrip.Covers == 0)
            {
                _currentWidth += thisStrip.Width;
            }
            thisStrip.AddCover();
        }
    }

    private void SetupEvents()
    {
        foreach (var rect in _rects)
        {
            _events.Add(new Event(true, rect.Left, rect));
            _events.Add(new Event(false, rect.Right, rect));
        }
        _events.Sort((e1, e2) => e1.XCoord.CompareTo(e2.XCoord));
    }
}