Hunt The Wumpus/CSharp

From Rosetta Code
Hunt The Wumpus/CSharp is part of Hunt_The_Wumpus. You may find other members of Hunt_The_Wumpus at Category:Hunt_The_Wumpus.

In this version, arrows only fire a distance of one room. Since bats relocate, we don't have to worry about the wumpus room being unreachable.
There are also 3 bats instead of 2, and you get 3 arrows instead of 5.

Code

Translation of: Java
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;

namespace HuntTheWumpus
{
    public partial class Form1 : Form
    {
        enum Hazard { Wumpus, Bat, Pit }

        static readonly string[] warnings = { "there's an awful smell", "you hear a rustling", "you feel a draft" };
        static readonly Random rand = new Random();

        readonly int roomSize = 45;
        readonly int playerSize = 16;

        bool gameOver = true;
        int currRoom, numArrows, wumpusRoom;
        List<string> messages;
        HashSet<Hazard>[] hazards;

        Font font = new Font("SansSerif", 10, FontStyle.Bold);

        public Form1()
        {
            Width = 721;
            Height = 687;
            StartPosition = FormStartPosition.CenterScreen;
            SetStyle(
                ControlStyles.AllPaintingInWmPaint |
                ControlStyles.UserPaint |
                ControlStyles.DoubleBuffer,
                true);

            MouseDown += HandleMouseDown;
        }

        private void HandleMouseDown(object sender, MouseEventArgs e)
        {
            if (gameOver)
            {
                StartNewGame();
            }
            else
            {
                int selectedRoom = -1;

                for (int i = 0; i < links.GetLength(1); i++)
                {
                    int link = links[currRoom, i];
                    int cx = rooms[link, 0];
                    int cy = rooms[link, 1];
                    if (InsideRoom(e.X,  e.Y, cx, cy))
                    {
                        selectedRoom = link;
                        break;
                    }
                }

                if (selectedRoom == -1)
                {
                    return;
                }

                if (MouseButtons.Left == e.Button)
                {
                    currRoom = selectedRoom;
                    Situation();

                }
                else if (MouseButtons.Right == e.Button)
                {
                    Shoot(selectedRoom);
                }
            }
            Refresh();

            bool InsideRoom(int ex, int ey, int cx, int cy)
            {
                return ((ex > cx && ex < cx + roomSize)
                        && (ey > cy && ey < cy + roomSize));
            }
        }

        // don't place hazards close to the starting room
        private bool TooClose(int room)
        {
            if (currRoom == room)
            {
                return true;
            }

            for (int i = 0; i < links.GetLength(1); i++)
            {
                int link = links[currRoom, i];
                if (room == link)
                    return true;
            }
            return false;
        }

        private void Shoot(int room)
        {
            if (hazards[room].Contains(Hazard.Wumpus))
            {
                messages.Add("You win! You've killed the Wumpus!");
                gameOver = true;
            }
            else
            {
                numArrows--;
                if (numArrows == 0)
                {
                    messages.Add("You ran out of arrows.");
                    gameOver = true;
                }
                else if (rand.Next(4) != 0) // 75 %
                {
                    hazards[wumpusRoom].Remove(Hazard.Wumpus);
                    wumpusRoom = links[wumpusRoom, rand.Next(3)];

                    if (wumpusRoom == currRoom)
                    {
                        messages.Add("You woke the Wumpus and he ate you");
                        gameOver = true;
                    }
                    else
                    {
                        messages.Add("You woke the Wumpus and he moved");
                        hazards[wumpusRoom].Add(Hazard.Wumpus);
                    }
                }
            }
        }

        private void Situation()
        {
            var set = hazards[currRoom];

            if (set.Contains(Hazard.Wumpus))
            {
                messages.Add("you've been eaten by the Wumpus");
                gameOver = true;
            }
            else if (set.Contains(Hazard.Pit))
            {
                messages.Add("you fell into a pit");
                gameOver = true;
            }
            else if (set.Contains(Hazard.Bat))
            {
                messages.Add("a bat dropped you in a random room");

                // teleport, but avoid 2 teleports in a row
                do
                {
                    currRoom = rand.Next(rooms.GetLength(0));
                }
                while (hazards[currRoom].Contains(Hazard.Bat));

                // relocate the bat, but not to the player room or a room with a bat
                set.Remove(Hazard.Bat);
                int newRoom;
                do
                {
                    newRoom = rand.Next(rooms.GetLength(0));
                }
                while (newRoom == currRoom || hazards[newRoom].Contains(Hazard.Bat));
                hazards[newRoom].Add(Hazard.Bat);

                // re-evaluate
                Situation();
            }
            else
            {
                // look around
                for (int i = 0; i < links.GetLength(1); i++)
                {
                    int link = links[currRoom, i];
                    foreach (var hazard in hazards[link])
                        messages.Add(warnings[(Int32)hazard]);
                }
            }
        }

        private void StartNewGame()
        {
            numArrows = 3;
            currRoom = rand.Next(rooms.GetLength(0));
            messages = new List<string>();

            hazards = new HashSet<Hazard>[rooms.GetLength(0)];
            for (int i = 0; i < rooms.GetLength(0); i++)
                hazards[i] = new HashSet<Hazard>();

            // hazards can share rooms (unless they are identical)
            int[] ordinals = { 0, 1, 1, 1, 2, 2 };
            var values = Enum.GetValues(typeof(Hazard));

            foreach (int ord in ordinals)
            {
                int room;
                do
                {
                    room = rand.Next(rooms.GetLength(0));
                }
                while (TooClose(room) || hazards[room].Contains((Hazard)values.GetValue(ord)));

                if (ord == 0)
                    wumpusRoom = room;

                hazards[room].Add((Hazard)values.GetValue(ord));
            }
            gameOver = false;
        }

        protected override void OnPaint(PaintEventArgs args)
        {
            Graphics g = args.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.Clear(Color.White);

            DrawRooms(g);
            if (gameOver)
            {
                DrawStartScreen(g);
            }
            else
            {
                DrawPlayer(g);
            }
            DrawMessage(g);
        }

        private void DrawPlayer(Graphics g)
        {
            int x = rooms[currRoom, 0] + (roomSize - playerSize) / 2;
            int y = rooms[currRoom, 1] + (roomSize - playerSize) - 2;

            var player = new GraphicsPath();
            player.AddLine(x, y, x + playerSize, y);
            player.AddLine(x + playerSize, y, x + playerSize / 2, y - playerSize);
            player.CloseFigure();

            g.FillPath(Brushes.White, player);
            g.DrawPath(Pens.Black, player);
        }

        private void DrawStartScreen(Graphics g)
        {
            var brush = new SolidBrush(Color.FromArgb(0xDD, Color.White));
            g.FillRectangle(brush, 0, 0, Width, Height - 60);

            g.DrawString("HUNT THE WUMPUS", font, Brushes.DarkGray, 280, 240);
            g.DrawString("left-click to move, right-click to shoot", font, Brushes.DarkGray, 230, 310);
            g.DrawString("be aware that hazards may be in the same room", font, Brushes.DarkGray, 195, 345);
            g.DrawString("click to start", font, Brushes.DarkGray, 315, 380);
        }

        private void DrawRooms(Graphics g)
        {
            var fatLine = new Pen(Color.DarkGray, 2);

            for (int i = 0; i < links.GetLength(0); i++)
            {
                for (int j = 0; j < links.GetLength(1); j++)
                {
                    int link = links[i, j];
                    int x1 = rooms[i, 0] + roomSize / 2;
                    int y1 = rooms[i, 1] + roomSize / 2;
                    int x2 = rooms[link, 0] + roomSize / 2;
                    int y2 = rooms[link, 1] + roomSize / 2;
                    g.DrawLine(fatLine, x1, y1, x2, y2);
                }
            }

            for (int i = 0; i < rooms.GetLength(0); i++)
            {
                g.FillEllipse(Brushes.Orange, rooms[i, 0], rooms[i, 1], roomSize, roomSize);
            }

            if (!gameOver)
            {
                for (int i = 0; i < links.GetLength(1); i++)
                {
                    var link = links[currRoom, i];
                    g.FillEllipse(Brushes.Magenta, rooms[link, 0], rooms[link, 1], roomSize, roomSize);
                }
            }

            for (int i = 0; i < rooms.GetLength(0); i++)
            {
                g.DrawEllipse(fatLine, rooms[i, 0], rooms[i, 1], roomSize, roomSize);
            }
        }

        private void DrawMessage(Graphics g)
        {
            if (!gameOver)
                g.DrawString("arrows  " + numArrows, font, Brushes.Black, 610, 30);

            if (messages != null)
            {
                // collapse identical messages
                messages = messages.Distinct().ToList();

                // join at most three
                var msg = string.Join(" & ", messages.Take(3));
                g.DrawString(msg, font, Brushes.Black, 20, Height - 80);

                // if there's more, print underneath
                if (messages.Count > 3)
                {
                    g.DrawString("& " + messages[3], font, Brushes.Black, 20, Height - 60);
                }

                messages.Clear();
            }
        }

        private int[,] rooms = {{334, 20}, {609, 220}, {499, 540}, {169, 540}, {62, 220},
            {169, 255}, {232, 168}, {334, 136}, {435, 168}, {499, 255}, {499, 361},
            {435, 447}, {334, 480}, {232, 447}, {169, 361}, {254, 336}, {285, 238},
            {387, 238}, {418, 336}, {334, 393}};

        private int[,] links = {{4, 7, 1}, {0, 9, 2}, {1, 11, 3}, {4, 13, 2}, {0, 5, 3},
            {4, 6, 14}, {7, 16, 5}, {6, 0, 8}, {7, 17, 9}, {8, 1, 10}, {9, 18, 11},
            {10, 2, 12}, {13, 19, 11}, {14, 3, 12}, {5, 15, 13}, {14, 16, 19},
            {6, 17, 15}, {16, 8, 18}, {19, 10, 17}, {15, 12, 18}};
    }
}