Cellular Automata



  • Hi,
    ich versuche gerade, ein einfaches Cellular Automata zu programmieren, stecke aber fest:

    #include <SFML/Graphics.hpp>
    #include <iostream>
    
    using namespace sf;
    using namespace std;
    
    #define WHITE 0
    #define BLACK 1
    
    #define dirRight 2
    #define dirLeft 3
    
    #define RIGHT 4
    #define LEFT 5
    #define UP 6
    #define DOWN 7
    
    void changeDirection(int dir, int& toChange) {
    	if (dir == dirRight) {
    		if (toChange == UP)
    			toChange = RIGHT;
    		if (toChange == DOWN)
    			toChange = LEFT;
    		if (toChange == RIGHT)
    			toChange = DOWN;
    		if (toChange == LEFT)
    			toChange = UP;
    	} else if (dir == dirLeft) {
    	    if (toChange == UP)
    			toChange = LEFT;
    		if (toChange == DOWN)
    			toChange = RIGHT;
    		if (toChange == RIGHT)
    			toChange = UP;
    		if (toChange == LEFT)
    			toChange = DOWN;
    	}
    }
    
    int main() {
    	RenderWindow window(VideoMode(1000, 1000), "PHYSIK!");
    	RectangleShape shape;
    	shape.setSize(Vector2f(10, 10));
    
    	Clock clock;
    	Vector2i position(50, 50);
    	int direction;
    	direction = UP;
    
    	int map[100][100];
    	memset(map, 0, sizeof(int) * 100 * 100);
    
    	while (window.isOpen()) {
    		float deltaTime = clock.getElapsedTime().asSeconds();
    		clock.restart();
    
    		Event event;
    		while (window.pollEvent(event)) 
    			if (event.type == event.Closed) 
    				window.close();
    		if (Keyboard::isKeyPressed(Keyboard::Escape))
    			window.close();
    
    		if (map[position.y][position.x] == WHITE) {
    			changeDirection(dirRight, direction);
    			map[position.y][position.x] = BLACK;
    		}
    		else if (map[position.y][position.x] == BLACK) {
    			changeDirection(dirLeft, direction);
    			map[position.y][position.x] = WHITE;
    		}
    
    		switch (direction) {
    		case UP:
    			position.y--;
    		case DOWN:
    			position.y++;
    			break;
    		case RIGHT:
    			position.x++;
    			break;
    		case LEFT:
    			position.x--;
    			break;
    		default:
    			break;
    		}
    
    		cout << direction << "\t" << position.x << "\t" << position.y << endl;
    
    		window.clear(Color(0, 100, 255, 1));
    		for (int i = 0; i < 100; ++i) {
    			for (int j = 0; j < 100; ++j) {
    				shape.setPosition(Vector2f(j * 10, i * 10));
    				if (map[i][j] == WHITE)
    					shape.setFillColor(Color::White);
    				else if (map[i][j] == BLACK)
    					shape.setFillColor(Color::Black);
    				window.draw(shape);
    			}
    		}
    		window.display();
    	}
    	return 0;
    }
    

    Bei diesem Code funktioniert der Anfang gut, aber dann bewegt sich der Körper einfach nur nach unten. Hab versucht, zu debuggen und herausgefunden, dass die Richtung nicht nach Links oder Rechts geändert wird, was ja eigentlich die changeDirection-Funktion machen soll. Was habe ich falsch gemacht? Und Tipps zum Verbessern sind auch gerne gesehen 🙂



  • Meine 2 Cent:
    Dein Programierstil ist total C, und nicht C++, und selbst für C sind soweit ich weiß ein paar Sachen daneben. Nur ein kleines Beispiel:

    int map[100][100];
    memset(map, 0, sizeof(int) * 100 * 100);
    // -->
    int map[100][100] {};
    

    Und selbst da gibt es vermutlich besseres als raw arrays.

    Integer Konstanten sollten auch nicht mit Makros definiert werden, sondern const , constexpr , oder als enum (je nachdem).

    Das using namespace ist in kleinen Programmen noch vertretbar, sollte aber grundsätzlich vermieden werden.

    sf::Clock::restart() gibt auch den momentanen Zeitwert zurück, da kann man sich auch eine Zeile sparen.

    changeDirection würde ich über einen Rückgabewert machen.
    Makros sollten per Konvention so oder so immer in Großbuchstaben gehalten werden (und in C++ für solche Zwecke vermieden werden).

    Bei case UP fehlt ein break.

    Der Code ist jetzt noch weit von perfekt, aber ich behaupte mal ein Tick besser, und läuft auch:

    #include <SFML/Graphics.hpp>
    #include <iostream>
    
    using namespace sf;
    using namespace std;
    
    #define WHITE 0
    #define BLACK 1
    
    enum class Direction {
        Up, Down, Left, Right
    };
    
    // man könnte hier auch über den pre-increment operator gehen, aber das muss nicht sein
    Direction rotateRight(Direction dir)
    {
        switch (dir) {
        case Direction::Up: return Direction::Right;
        case Direction::Right: return Direction::Down;
        case Direction::Down: return Direction::Left;
        case Direction::Left: return Direction::Up;
        }
    }
    
    Direction rotateLeft(Direction dir)
    {
        switch (dir) {
        case Direction::Up: return Direction::Left;
        case Direction::Right: return Direction::Up;
        case Direction::Down: return Direction::Right;
        case Direction::Left: return Direction::Down;
        }
    }
    
    int main() {
        constexpr unsigned WIDTH = 100;
        constexpr unsigned HEIGHT = 100;
        constexpr unsigned BLOCKSIZE = 10;
    
        RenderWindow window(VideoMode(1000, 1000), "PHYSIK!");
        RectangleShape shape;
        shape.setSize(Vector2f(BLOCKSIZE, BLOCKSIZE));
    
        Clock clock;
        Vector2i position(50, 50);
        Direction direction = Direction::Up;
    
        int map[HEIGHT][WIDTH] {};
    
        while (window.isOpen()) {
            float deltaTime = clock.restart().asSeconds();
    
            Event event;
            while (window.pollEvent(event))
                if (event.type == event.Closed)
                    window.close();
            if (Keyboard::isKeyPressed(Keyboard::Escape))
                window.close();
    
            if (map[position.y][position.x] == WHITE) {
                direction = rotateRight(direction);
                map[position.y][position.x] = BLACK;
            }
            else if (map[position.y][position.x] == BLACK) {
                direction = rotateLeft(direction);
                map[position.y][position.x] = WHITE;
            }
    
            switch (direction) {
            case Direction::Up:
                position.y--;
                break;
            case Direction::Down:
                position.y++;
                break;
            case Direction::Right:
                position.x++;
                break;
            case Direction::Left:
                position.x--;
                break;
            default:
                cerr << "Invalid Direction\n";
                return -1;
            }
    
            cout << static_cast<int>(direction) << "\t" << position.x << "\t" << position.y << endl;
    
            window.clear(Color(0, 100, 255, 1));
    
            for (int i = 0; i < HEIGHT; ++i) {
                for (int j = 0; j < WIDTH; ++j) {
                    shape.setPosition(Vector2f(j * BLOCKSIZE, i * BLOCKSIZE));
                    // shape.setFillColor(map[i][j] == WHITE ? Color::White : Color::Black);
                    if (map[i][j] == WHITE)
                        shape.setFillColor(Color::White);
                    else if (map[i][j] == BLACK)
                        shape.setFillColor(Color::Black);
                    window.draw(shape);
                }
            }
    
            window.display();
        }
        return 0;
    }
    

    EDIT: Der andere logische Fehler (neben dem fehlenden break) liegt in der changeDirection Funktion. Und Als Beispiel schauen wir uns nur eine Richtung an, denn das lässt sich auf die Andere übertragen.

    Das Hauptproblem ist, dass hier nicht mit if-else, sondern nur mit if gearbeitet wird. Wenn also z.B. die Richtung von UP->RIGHT geändert wird, springt das Programm zu den nächsten ifs und gelangt irgendwann wieder bei if (toChange == RIGHT) und dann wird die Richtung gleich wieder auf RIGHT->DOWN geändert.

    // toChange == UP
        if (dir == dirRight) {
            if (toChange == UP)
                toChange = RIGHT;  // hier wird geändert UP->RIGHT
            if (toChange == DOWN)
                toChange = LEFT;
            if (toChange == RIGHT)  // hier ist toChange schon RIGHT von oben
                toChange = DOWN;   // toChange wird geändert RIGHT->DOWN
            if (toChange == LEFT)
                toChange = UP;
        }
    

    Also: entweder else-if, oder switch.

    Frage: Kann es vorkommen, dass position x/y >= 100 werden und was würde dann passieren?

    P.S.: Cooles Programm, macht Spaß anzuschauen, weiter so!


Anmelden zum Antworten