Example #1
0
        public WorldState Update(Direction direction)
        {
            var tailEnd = Snake.GetTailEnd();

            Snake.Move(direction);

            if (options.WarpAroundEdges)
            {
                for (int i = 0; i < Snake.Count; i++)
                {
                    var body = Snake[i];
                    Snake[i] = ((Size.X + body.X) % Size.X, (Size.Y + body.Y) % Size.Y);
                }
            }

            if (Snake.IsInvalidState(Size))
            {
                return(WorldState.Invalid);
            }

            var head = Snake.GetHead();

            // Grow if apple is inside snake and create another apple
            if (head == Apple)
            {
                Snake.Add(tailEnd);

                // memory vs processing speed? the classic optimization decision.

                // from a range containing all possible values in grid without the ones by snake.
                var notSnake = Size.Range().Where((p) => !Snake.Contains(p)).ToList();

                if (notSnake.Count <= 0)
                {
                    return(WorldState.Won);
                }
                else
                {
                    // try to generate apple to be near snake head, or the game will be boring

                    // get inclusive lower bound and inclusive upper bound of coordinates close to snake head, within size
                    int xOffset = Size.X / 10;
                    int x0      = (head.X - xOffset).EnsuredWithin();
                    int x1      = (head.X + xOffset).EnsuredWithin(Size.X);

                    int yOffset = Size.Y / 10;
                    int y0      = (head.Y - xOffset).EnsuredWithin();
                    int y1      = (head.Y + xOffset).EnsuredWithin(Size.Y);

                    var nearHead = notSnake.Where((p) => p.X >= x0 && p.X <= x1 && p.Y >= y0 && p.Y <= y1).ToArray();
                    if (nearHead.Length > 0)
                    {
                        Apple = nearHead[rand.Next(nearHead.Length)];
                    }
                    else if (notSnake.Count == 1)
                    {
                        Apple = notSnake[0];
                    }
                    else
                    {
                        // pick number closer to snake head more likely

                        // sort closest to snake head first
                        notSnake.Sort((point1, point2) => point1.CompareTo(point2, head));

                        // https://stackoverflow.com/a/9956791/12347502 reference
                        // https://github.com/Stiles-X/WeightedRandomSelector switch to this if things get messy

                        // we want [0,1]

                        // [2, 1]
                        var discreteChunks = Enumerable.Range(1, notSnake.Count + 1).Reverse().ToArray();

                        // [2, 3]
                        var distribution = new int[discreteChunks.Length];
                        {
                            int current = distribution[0] = discreteChunks[0];
                            for (int i = 1; i < distribution.Length; i++)
                            {
                                distribution[i] = current += discreteChunks[i];
                            }
                        }

                        // linear search
                        int max        = distribution[^ 1];