C# :: Aufgabe #194 :: Lösung #1

2 Lösungen Lösungen öffentlich
#194

Simulation eines Staubsaug-Roboters

Fortgeschrittener - C# von hollst - 22.11.2017 um 22:14 Uhr
Ich bin im Haushalt ein ziemlich fauler Hund. Meine Frau weiß das und so hielt sie mich deshalb kürzlich an, wenigstens hin und wieder das Wohnzimmer zu saugen. Nun gut, mir kam die geniale Idee, dafür einen Staubsaug-Roboter zu kaufen. Gesagt, getan. Meine Frau runzelte zwar, aber letzt­lich war ihr das egal, wichtig war ja nur, dass sie etwas von der Hausarbeit entlastet wird.

Das Gerät überraschte mich von Anfang an. Es fährt immer geradeaus bis es irgendwo anstößt. Dann dreht es sich auf der Stelle in eine zufällige andere Richtung und fährt wieder weiter gerade­aus bis zum nächsten Hindernis. Es teilt mir auch sprachlich z. B. mit, wenn der Akku fast leer ist, es jetzt eine Pause machen wird und zur Ladestation fährt. Andermal sagte es, dass sein Haupt­speicher (sprich Staubsammeltüte) voll ist und ich ihn leeren solle. Das übernimmt natürlich meine Frau, denn bei einem solchen mechanischen Eingriff hat sie kein Vertrauen zu mir.

Der Gipfel war aber, als der Roboter mir bei der gestrigen Arbeit mitteilte, dass er fertig sei und im Zimmer der gesamten Fußboden gereinigt ist.

Das ist doch unmöglich, woher will er das wissen. Und "der gesamte Fußboden" geht schon gar­nicht, denn in die Ecken kommt er ja sowieso nicht. Ich war immer der Meinung, dass er erst fertig zu sein hat, wenn er abgeschaltet wird. Aber Nein sagte meine Frau, das macht er immer so, ich soll­te ihn, wie vereinbart war, nur hin und wieder selbst einsetzen und beobachten (beobachtet wird das Gerät bisweil immer nur von unserer Hauskatze).

Ich wurde neugierig und las entgegen meinen Gewöhnheiten auch die Betriebsanleitung. In der Tat stand darin, dass der Roboter anhält, wenn er mehr als 95 % der Fußbodenfläche gesaugt hat.

Ich dachte, nun gut, 95 % ist okay, aber, wie will er das wissen. Da muss schon eine ziemlich clevere Software im Roboter stecken. Prinzipiell kann man zwar die besaugte Fläche bei Kenntnis der Fahr­geschwindigkeit, der Anstoßpunkte und der neuen Fahrtrichtungen berechnen. Die Flächenseg­mente, die durch das Zufallsprinzip bereits besaugt worden ware, sind entsprechend zu berücksich­tigen (abzuziehen). Alles mathematisch recht kom­pliziert.

Der Gedanke, es statt einer mathematischen Berechnung durch eine informathematische Simulation abzuschätzen, legt auf der Hand. Dies soll daher die Aufgabenstellung sein.

Ausgehend von einer beliebigen Startposition auf einer leeren, rechteckigen Fläche F = LX x LY, mit LX und LY als Seitenlängen des Rechtecks, einer vorgegebenen Fahrgeschwindigkeit V und einer zufälli­gen Richtungsände­rung bei Zusammenstoß mit einem Hindernis (Wand) ist der Anteil besaugter Fläche bzgl. der Gesamtfußbodenfläche wie folgt anzuschätzen:

Auf einem zu Beginn 100 % mit "Staubpixeln" belegten Rechteck wird ein Staubsaug-Roboter mit dem Radius R gesetzt und fährt per Zufall gewählter Richtung staubsaugend geradeaus (Geschwin­digkeit V). Bei Berührung einer Wand ändert er zufällig seine Fahrtrichtung. Während der Fahrt werden alle überfahrenen "Staubpixeln" durch "Reinpixel" ersetzt (siehe Abb. 1). Die Fahrt endet, wenn P_okay = 95 % der Rechtecksfläche mit "Reinpixeln" belegt ist.
#1
2 Kommentare
1x
vote_ok
von daniel59 (4260 Punkte) - 27.11.2017 um 11:27 Uhr
MainWindow.xaml.cs
Quellcode ausblenden C#-Code
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace WpfStaubsaugRoboterSimulation
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        static readonly Random rnd = new Random();
        private DispatcherTimer timer;

        private Ellipse roboter;
        private Point pos;
        private Vector vector;

        private int width, height, radius, v;
        private HashSet<Pixel> cleanPixels;

        public MainWindow()
        {
            InitializeComponent();

        }

        private void buttonSimulate_Click(object sender, RoutedEventArgs e)
        {
            gridWrap.UpdateLayout();
            if (int.TryParse(textboxWidth.Text, out width) & int.TryParse(textboxHeight.Text, out height) && int.TryParse(textboxRadius.Text, out radius) && int.TryParse(textboxVelocity.Text, out v) && width > 0 && width <= gridWrap.ActualWidth && height > 0 && height <= gridWrap.ActualHeight && radius > 0 && v > 0)
            {
                canvasStaub.Width = width;
                canvasStaub.Height = height;
                Simulate();
            }
            else
            {
                MessageBox.Show("Ungültige Eingabe");
            }
        }

        private void Simulate()
        {
            canvasStaub.Children.Clear();
            roboter = new Ellipse() { Width = 2 * radius, Height = 2 * radius, Fill = Brushes.Blue, StrokeThickness = 0 };
            int px = rnd.Next(0, 1 + width - 2 * radius);
            int py = rnd.Next(0, 1 + height - 2 * radius);
            pos = new Point(px, py);
            Canvas.SetLeft(roboter, pos.X - radius);
            Canvas.SetTop(roboter, pos.Y - radius);
            canvasStaub.Children.Add(roboter);

            int vx = rnd.Next(0, 1 + width - 2 * radius) - px;
            int vy = rnd.Next(0, 1 + height - 2 * radius) - py;
            vector = new Vector(vx, vy);
            vector.Normalize();
            vector *= v;

            cleanPixels = new HashSet<Pixel>();
            if (timer != null)
            { timer.Stop(); }
            timer = new DispatcherTimer();
            timer.Interval = new TimeSpan(1);
            timer.Tick += Timer_Tick;
            timer.Start();
        }

        private IEnumerable<Pixel> GetCleanedPixel()
        {
            for (int x = -radius; x <= radius; x++)
            {
                double yP = Math.Sqrt(Math.Pow(radius, 2) - Math.Pow(x, 2));
                double yN = -yP;

                int px = (int)(x + pos.X);
                int pyp = (int)(yP + pos.Y);
                int pyn = (int)(yN + pos.Y);

                for (int y = pyn; y <= pyp; y++)
                {
                    yield return new Pixel(px, y);
                }
            }
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            int c = cleanPixels.Count;

            var cleaned = GetCleanedPixel();
            foreach (var pixel in cleaned)
            { cleanPixels.Add(pixel); }

            double p = ((double)cleanPixels.Count / (width * height)) * 100;
            textBlock.Text = $"{p.ToString("0.0000")} % gereinigt";
            if (p >= 95)
            {
                timer.Stop();
                return;
            }

            pos += vector;
            if (WallHitten())
            {
                double vx = rnd.Next(0, 1 + width - 2 * radius) - pos.X;
                double vy = rnd.Next(0, 1 + height - 2 * radius) - pos.Y;
                vector = new Vector(vx, vy);
                vector.Normalize();
                vector *= v;
            }

            if (c != cleanPixels.Count)
            {
                Ellipse clean = new Ellipse() { Width = 2 * radius, Height = 2 * radius, Fill = Brushes.White, StrokeThickness = 0 };
                Canvas.SetLeft(clean, pos.X);
                Canvas.SetTop(clean, pos.Y);
                canvasStaub.Children.Insert(0, clean);
            }

            Canvas.SetLeft(roboter, pos.X);
            Canvas.SetTop(roboter, pos.Y);

        }

        private bool WallHitten()
        {
            return pos.X <= 0 && vector.X < 0 ||
                   pos.X + 2 * radius >= width && vector.X > 0 ||
                   pos.Y <= 0 && vector.Y < 0 ||
                   pos.Y + 2 * radius >= height && vector.Y > 0;
        }
    }

    class Pixel
    {
        public int X { get; set; }
        public int Y { get; set; }

        public Pixel(int x, int y)
        {
            X = x;
            Y = y;
        }

        public override bool Equals(object obj)
        {
            Pixel p = (Pixel)obj;
            return p.X == X && p.Y == Y;
        }

        public override int GetHashCode()
        {
            int hash = 13;
            hash = (hash * 7) + X.GetHashCode();
            hash = (hash * 7) + Y.GetHashCode();
            return hash;
        }
    }
}


MainWindow.xaml
Quellcode ausblenden XML-Code
<Window x:Class="WpfStaubsaugRoboterSimulation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfStaubsaugRoboterSimulation"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="825" WindowState="Maximized">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>
            <TextBlock Text="Höhe" Margin="10,13,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="0"/>
            <TextBox x:Name="textboxHeight" Margin="44,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="50" TextAlignment="Center" Grid.Column="0"/>

            <TextBlock Text="Breite" Margin="10,13,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="1"/>
            <TextBox x:Name="textboxWidth" Margin="44,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="50" TextAlignment="Center" Grid.Column="1"/>

            <TextBlock Text="Saugradius" Margin="10,13,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="2"/>
            <TextBox x:Name="textboxRadius" Margin="73,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="50" TextAlignment="Center" Grid.Column="2"/>

            <TextBlock Text="Geschwindigkeit" Margin="10,13,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="3"/>
            <TextBox x:Name="textboxVelocity" Margin="102,10,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="50" TextAlignment="Center" Grid.Column="3"/>

            <TextBlock x:Name="textBlock" Grid.Column="4" HorizontalAlignment="Left" Margin="10,13,0,0" TextWrapping="Wrap" Text="0 % gereinigt" VerticalAlignment="Top"/>

            <Button x:Name="buttonSimulate" Content="Simulieren" HorizontalAlignment="Right" VerticalAlignment="Top" Grid.Column="5" Margin="0,10,10,0" Click="buttonSimulate_Click"/>
        </Grid>
        <Grid x:Name="gridWrap" Grid.Row="1" Margin="10">
            <Canvas x:Name="canvasStaub" Background="Gray"/>
        </Grid>
    </Grid>
</Window>

Kommentare:

hollst

Punkte: 13980

761 Aufgaben
132 Lösungen
117 Kommentare

#1
15.12.2017 um 13:44 Uhr
Hallo,

der Gesamtcode macht einen sehr guten Eindruck,
leider wird ein falsche Ergebnis produziert (siehe Bild).

Offensichtlich sind im Bild keine 95 % der Pixel clean.

Das Bild habe ich über folgende Codemodifikation erzeugt:

Quellcode ausblenden C#-Code
            if (WallHitten())
            {
                //double vx = rnd.Next(0, 1 + width - 2 * radius) - pos.X;
                //double vy = rnd.Next(0, 1 + height - 2 * radius) - pos.Y;
                double vx = -vector.X;
                double vy = -vector.Y;

                vector = new Vector(vx, vy);
                vector.Normalize();
                vector *= v;
            }


In Deiner Liste der sauberen Pixel werden sehr viele Pixel mehrfach aufgenommen, der Fehler wäre noch zu bereinigen.

Alles Gute, hollst.


post_arrow
506 0

hollst

Punkte: 13980

761 Aufgaben
132 Lösungen
117 Kommentare

#2
15.12.2017 um 14:02 Uhr
Sorry, alles richtig, ich hatte

Quellcode ausblenden C#-Code
public override int GetHashCode()


ausgeklammert gehabt.

Gruß hollst.
post_arrow
507 0
Bitte melden Sie sich an um eine Kommentar zu schreiben.
Kommentar schreiben