0

I am learning C #, and I am creating a hypothetical game for me to understand the language. I want several bots to follow the Player who is moving the rectangle, but I can only move the player, but the automatic bots do not move.

I really researched what I could do to move these bots. And I came to the conclusion that I would have to understand Threads, which simply causes the program not to crash.

I leave here the full code of what I am trying.

public partial class Form1 : Form
{
    public enum Direction { Up, Down, Left, Right }
    private Player player;
    private List<Bot> bots;

    public Form1()
    {
        InitializeComponent();

        this.Paint += Form1_Paint;
        this.KeyPreview = true;
        this.KeyDown += Form1_KeyDown;

        this.player = new Player(new Size(8, 8));
        this.bots = new List<Bot>();

        for (int i = 0; i < 30; i++)
        {
            Bot bot = new Bot(player, new Size(8, 8));
            bot.Follow();
            this.bots.Add(bot);
        }
    }

    private void Form1_KeyDown(object sender, KeyEventArgs e)
    {
        switch (e.KeyCode)
        {
            case Keys.Up:
                player.Move(Direction.Up);
                break;
            case Keys.Down:
                player.Move(Direction.Down);
                break;
            case Keys.Left:
                player.Move(Direction.Left);
                break;
            case Keys.Right:
                player.Move(Direction.Right);
                break;
        }
        this.Invalidate();
    }

    private void Form1_Paint(object sender, PaintEventArgs e)
    {
        List<Rectangle> rs = new List<Rectangle>();
        rs = this.bots.Select(x => x.Rectangle).ToList();
        rs.Add(player.Rectangle);

        if (rs.Count > 0)
        {
            e.Graphics.FillRectangles(new SolidBrush(Color.Red), rs.ToArray());
        }
    }
}

public class Player
{
    private Rectangle rectangle;
    public Rectangle Rectangle { get => rectangle; }
    public Player(Size size)
    {
        this.rectangle = new Rectangle() { Size = size };
    }

    public void Move(Direction direction)
    {
        switch (direction)
        {
            case Direction.Up:
                this.rectangle.Y -= 3;
                break;
            case Direction.Down:
                this.rectangle.Y += 3;
                break;
            case Direction.Left:
                this.rectangle.X -= 3;
                break;
            case Direction.Right:
                this.rectangle.X += 3;
                break;
            default:
                break;
        }
    }
}

public class Bot
{
    private Rectangle rectangle;

    private Player player;
    public Rectangle Rectangle { get => rectangle; }

    public Bot(Player player, Size size)
    {
        this.player = player;
        this.rectangle = new Rectangle() { Size = size };
    }

    public void Follow()
    {
        Task.Run(() =>
        {
            while (true)
            {
                Point p = player.Rectangle.Location;
                Point bot = rectangle.Location;

                for (int i = bot.X; i < p.X; i += 2)
                {
                    Thread.Sleep(100);
                    bot.X = i;
                }

                for (int i = bot.X; i > p.X; i -= 2)
                {
                    Thread.Sleep(100);
                    bot.X = i;
                }

                for (int i = bot.Y; i < p.Y; i += 2)
                {
                    Thread.Sleep(100);
                    bot.Y = i;
                }

                for (int i = bot.Y; i > p.Y; i -= 2)
                {
                    Thread.Sleep(100);
                    bot.Y = i;
                }
            }
        });                  
    }
}

As you can see, I can only move the player, but the bots don't move what can I do to move the bots?

KJSR
  • 1,679
  • 6
  • 28
  • 51
sYsTeM
  • 37
  • 3
  • 18
  • Why do you think you need to use threads to implement this solution? – Tom W Aug 12 '19 at 13:53
  • it's because while crashes the application. Correct me if i'm wrong – sYsTeM Aug 12 '19 at 13:57
  • I don't think you're approaching from the right angle. Like mentioned above you probably don't need bg threads. And `Thread.Sleep` is just wrong... I think your player object needs to raise events upon move, and bots should just subscribe to those and react. – RussKie Aug 12 '19 at 14:14
  • How can I make this possible? – sYsTeM Aug 12 '19 at 14:20
  • @Sérgio that doesn't answer the question. Why do you think that using threads should prevent the application from crashing? – Tom W Aug 12 '19 at 14:25
  • @Sergio, I assume by "crash" you mean without starting a new thread, the infinite `while` loop blocks the UI thread causing it to hang? – steve16351 Aug 12 '19 at 14:40
  • Yes, I suppose the while loop without Task crashes the program. That's why I put Task.Run for prevention. – sYsTeM Aug 12 '19 at 14:44
  • Additional reading: [What's the difference between struct and class in .NET?](https://stackoverflow.com/questions/13049/whats-the-difference-between-struct-and-class-in-net) – Wyck Aug 12 '19 at 14:55

2 Answers2

0

I think a Timer would work better here and remove the requirement for you to fully understanding threading at this point, as it will handle the details for you. I'm assuming you actually want the bots to "follow" instead of only moving when the Player moves and will fall behind if the player is moving quickly.

So to use a Timer, I would adjust your Bot class as below, to remove your usage of threads and only allow it to take a single step towards the player in the Follow method which will be called every 100ms. Note Rectangle is a struct, so it is not mutable - that is why your bots do not move - if you do the following:

Point bot = Rectangle.Location;
bot.X = 5;

You're probably thinking Rectangle.Location.X is now 5; but it is not. So we create a new rectangle using the new position.

public class Bot
{
    private Player player;
    public Rectangle Rectangle { get; set; }

    public Bot(Player player, Size size)
    {
        this.player = player;
        this.Rectangle = new Rectangle() { Size = size };
    }

    public void Follow()
    {
        Point p = player.Rectangle.Location;
        Point bot = Rectangle.Location;

        for (int i = bot.X + 2; i < p.X;)
        {
            bot.X = i;
            break;
        }

        for (int i = bot.X - 2; i > p.X;)
        {
            bot.X = i;
            break;
        }

        for (int i = bot.Y + 2; i < p.Y;)
        {
            bot.Y = i;
            break;
        }

        for (int i = bot.Y - 2; i > p.Y;)
        {
            bot.Y = i;
            break;
        }

        Rectangle = new Rectangle(bot, player.Rectangle.Size);
    }
}

And add the following code to replace your existing constructor and add another method to handle the Timer tick.

private Timer timer;

public Form1()
{
    InitializeComponent();

    this.Paint += Form1_Paint;
    this.KeyPreview = true;
    this.KeyDown += Form1_KeyDown;

    // setup a timer which will call Timer_Tick every 100ms
    timer = new System.Windows.Forms.Timer();
    timer.Interval = 100;
    timer.Tick += Timer_Tick;
    timer.Start();

    this.player = new Player(new Size(8, 8));
    this.bots = new List<Bot>();

    for (int i = 0; i < 30; i++)
    {
        Bot bot = new Bot(player, new Size(8, 8));
        bot.Follow();
        this.bots.Add(bot);
    }
}

private void Timer_Tick(object sender, System.EventArgs e)
{
    foreach (var bot in bots)
        bot.Follow();
    this.Invalidate();
}
steve16351
  • 5,372
  • 2
  • 16
  • 29
0

Point is a value type (a struct). (Read more about this at What's the difference between struct and class in .NET?)

When you did this:

Point bot = Rectangle.Location;
bot.X = i;

...you created a local Point and modified it. This doesn't change the Location of the Rectangle of the Bot. Also, Rectangles are structs too, so you have to modify the original Bot's Rectangle, or assign a new Rectangle to the Bot.

To fix, you could replace:

bot.X = i;

...with...

this.rectangle.X = i;

And make the similar change for .Y (in all your for loop locations)

Spelling it all out:

        public void Follow()
        {
            Task.Run(() =>
            {
                while (true) {
                    Point p = player.Rectangle.Location;
                    Point bot = rectangle.Location;

                    for (int i = bot.X; i < p.X; i += 2) {
                        Thread.Sleep(100);
                        this.rectangle.X = i;
                    }

                    for (int i = bot.X; i > p.X; i -= 2) {
                        Thread.Sleep(100);
                        this.rectangle.X = i;
                    }

                    for (int i = bot.Y; i < p.Y; i += 2) {
                        Thread.Sleep(100);
                        this.rectangle.Y = i;
                    }

                    for (int i = bot.Y; i > p.Y; i -= 2) {
                        Thread.Sleep(100);
                        this.rectangle.Y = i;
                    }
                }
            });
        }
Wyck
  • 10,311
  • 6
  • 39
  • 60
  • It works too, but only only when moving. But anyway, thank you =) – sYsTeM Aug 12 '19 at 15:06
  • @Sérgio, well you will have to `Invalidate` your Form when you modify bot positions and then you will have threading issues I didn't care to address (you did not, but [steve16351's answer](https://stackoverflow.com/a/57463084/1563833) does.) I figured the _struct vs class_ thing was the main issue of misunderstanding. – Wyck Aug 12 '19 at 15:11
  • I confess I don't understand anything about Threads, I'll take a look at what friend said. – sYsTeM Aug 12 '19 at 15:16
  • I could have offered broader advice about threads, but your design is not really "the way", and shows you need to get some more practice designing for threads. I'd like to help you understand but it's a big can of worms. Only use a thread to avoid tying up the main thread with a flow control structure from which it cannot return back to the event loop. At this point, you'll be better off focusing on _event-driven_ programs - like @steve16351 is showing. Get used to timers first, then learn threads later. – Wyck Aug 12 '19 at 15:23
  • In C++ I was used to using loops, as I imagined that the two languages ​​were the same thing as that, so I applied the while loop. In the long run I will understand that. – sYsTeM Aug 12 '19 at 15:29
  • Design like this: On each frame of animation: Update the world with all the changes that are needed to arrive at a model of the world as it will appear on the next render. Then redraw. _Update the world_ means make Bot AI decisions and update positions of game objects. This would be a single-threaded design. Because you are using Windows Forms, unless you are trying to achieve a pristine 60fps, you should be fine with a Timer component and a _double-buffered_ Form to drive your animation. (set `this.DoubleBuffered = true;` for your form) `Invalidate` on `Timer_Tick` will drive the rest. – Wyck Aug 12 '19 at 15:37
  • Great idea this would make the bot more automated – sYsTeM Aug 12 '19 at 15:46
  • More so than automated, it will make it _deterministic_. The behaviour of the bot will now defined as updating once per animation frame. As opposed to just whenever this independent thread happens to get around to it. – Wyck Aug 12 '19 at 17:09
  • I added it on github, if you want to express your idea I will be grateful! https://github.com/sergiologgan/Rectangle_Game.git – sYsTeM Aug 12 '19 at 18:48
  • @Sérgio, Cool, I'll take a look. – Wyck Aug 12 '19 at 19:43