In diesem neuen Beitrag möchte ich dir gerne ein weiteres Spiel vorstellen, welches du recht einfach am ESP32 Development Board mit 2,8″ TFT-Display nachprogrammieren kannst. Es ist “Vier gewinnt!”, das Spiel gibt es in diversen offline Varianten und wirst du bestimmt bereits kennen und gespielt haben.
Inhaltsverzeichnis
- Ziel des Spieles “Vier gewinnt!”
- Benötigte Ressourcen für die Programmierung am ESP32 Development Board
- Schritt-für-Schritt-Anleitung zum Programmieren von Vier gewinnt am ESP32 mit TFT-Display
- Das fertige Spiel – Vier gewinnt für das ESP32 mit TFT-Display
Ziel des Spieles “Vier gewinnt!”
Das Ziel des Spiels “Vier gewinnt!” ist es, als erster Spieler vier seiner Spielsteine in einer horizontalen, vertikalen oder diagonalen Linie zu platzieren. Die Spieler wechseln sich ab, indem sie abwechselnd einen Spielstein in eine der vertikalen Spalten des Spielbretts fallen lassen. Der Spieler, der zuerst eine ununterbrochene Linie aus vier seiner Spielsteine bildet, gewinnt das Spiel. Es erfordert strategisches Denken, um die Spielsteine so zu platzieren, dass man entweder eine eigene Linie vervollständigt oder gleichzeitig die des Gegners blockiert.
Benötigte Ressourcen für die Programmierung am ESP32 Development Board
Wenn du dieses kleine Spiel nachprogrammieren möchtest, dann benötigst du:
- ein ESP32 Development Board (LVGL, ESP32-2432S028)*,
- ein Datenkabel, Eingabestift,
- die Arduino IDE
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
Schritt-für-Schritt-Anleitung zum Programmieren von Vier gewinnt am ESP32 mit TFT-Display
Nachfolgend nun die Schritt-für-Schritt-Anleitung zum programmieren von Vier gewinnt am ESP32 Development Board.
Wenn du dich jedoch nur für das fertige Spiel interessierst, dann kannst du dir dieses auch als ZIP-Datei herunterladen.
Besonderheiten & Vorwort
Bevor wir mit der Programmierung beginnen möchte ich dir zunächst einpaar Hinweise an die Hand geben.
Zum einen habe ich hier einen ansatz gewählt welcher deutlich weniger Speicher benötigt. Das musste ich zum einen weil die Bibliothek für das TFT-Display schon alleine recht groß ist und zum anderen wenn die Daten des Spielfeldes abgelegt werden diese dann unnötig Platz verbrauchen.
Zusätzlich habe ich hier ChatGPT benutzt um den Quellcode zu erstellen. Mit den richtigen Prompts kann die künstliche Intelligenz eine sehr gute Unterstützung sein.
Schritt 1 – zeichnen der Spielfläche
Zunächst zeichnen wir die Spielfläche. Dazu gehört die Überschrift “Vier gewinnt” sowie die blaue Fläche mit den Pfeilen und schwarzen Kreisen.
Die Pfeile zeichne ich in einer Funktion und speichere die Informationen (Index / Spalte, X & Y Koordinate) in einem Array.
//Feld für die Daten eines Pfeiles struct Arrow { int col; int x; //X Koordinate int y; //Y Koordinate }; //Array für die Pfeile //das Array wird beim zeichnen befüllt Arrow arrowFields[COLS] = {}; //Funktion zum zeichnen der Spielfläche void drawPlayground() { //leeren des Displays lcd.clear(); //Hintergrundfarbe Schwarz lcd.fillScreen(TFT_BLACK); //Schriftgröße 4 lcd.setFont(&fonts::Font4); //Schriftfarbe Gelb, //Hintergrundfarbe Schwarz lcd.setTextColor(TFT_YELLOW, TFT_BLACK); //Text an die Position x & y schreiben lcd.drawString("Vier gewinnt!", 105, 5); //ein blaues rechteck zeichnen lcd.fillRect(40, 35, 255, 205, TFT_BLUE); // For-Schleife zum Zeichnen von sieben Dreiecken for (int i = 0; i < 7; i++) { // Koordinaten für die Eckpunkte des Dreiecks berechnen int x1 = startX + (i * (triangleWidth + spacing)); int y1 = startY; int x2 = x1 + triangleWidth / 2; int y2 = startY + (triangleWidth / 2); int x3 = x1 + triangleWidth; int y3 = startY; // fillTriangle-Funktion aufrufen, um das Dreieck zu zeichnen lcd.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN); Arrow arrowField = {i, x1, y1}; arrowFields[i] = arrowField; } //zeichnen des Arrays drawHoles(); //anzeigen der Daten auf dem Display lcd.display(); }
Die schwarzen Kreise werden später mit den gesetzten Spielsteinen befüllt. Hier speichere ich keine Daten ab denn die Berechnung der X & Y Koordinate für einen Kreis erfolgt anhand eines der Pfeile. (Wenn der Spieler eines der grünen Pfeile betätigt dann wird die Spalte und die X & Y Koordinate ermittelt.)
//Anzahl der Spalten const int COLS = 7; //Anzahl der Zeilen const int ROWS = 5; //Das Array mit dem Spielfeld //initial sind alle Felder leer //bzw. mit einem Strich markiert char playground[ROWS][COLS] = { {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH} }; //Funktion zum zeichnen der Daten des Arrays void drawHoles() { //ein gefülltes, blaues rechteck über die Spielfläche zeichnen //damit werden alle zuvor gezeichneten Daten überschrieben lcd.fillRect(40, 60, 255, 185, TFT_BLUE); //Schleife über das Array for (int col = 0; col < COLS; col++) { Arrow arrowField = arrowFields[col]; for (int row = 0; row < ROWS; row++) { //Berechnen der X & Y Koordinate für das Loch int circleY = (startY + triangleWidth + circleSpacing + (2 * circleRadius + circleYspacing) * row) - 10; // y-Koordinate des Kreises int circleX = arrowField.x + triangleWidth / 2; // x-Koordinate des Kreises lcd.fillCircle(circleX, circleY, circleRadius, TFT_BLACK); //Wenn an der Koordinate im Array kein Strich ist, dann ist dort ein Spielstein abgelegt if (playground[row][col] != DASH) { //den Spielstein zeichnen drawStone(circleX, circleY, playground[row][col] == PLAYER1_CHAR); } } } }
Zusätzlich prüfe ich in der Funktion drawHoles auch ob in dem Array playground ein Spielstein an der Position gesetzt ist. Wenn dieses so ist dann wird an die Position zusätzlich der entsprechende Spielstein gezeichnet.
//Zeichnet ein Spielstein an die Position X & Y //der boolsche Parameter player1 gibt an ob Gelb oder Rot am zug ist void drawStone(int x, int y, bool player1) { //initial ist Gelb gesetzt uint8_t colorRingOuter1 = lcd.color332(137, 121, 5);; uint8_t colorRingOuter2 = lcd.color332(238, 226, 146); uint8_t colorRingInner = lcd.color332(243, 212, 0); //bei Spieler 2 soll ein Stein in der Farbe Rot gezeichnet werden. if (!player1) { colorRingOuter1 = lcd.color332(106, 5, 13); colorRingOuter2 = lcd.color332(239, 109, 119); colorRingInner = lcd.color332(255, 0, 0); } //zeichnen des Spielsteins lcd.fillCircle(x, y, 15, colorRingOuter1); lcd.fillCircle(x, y, 12, colorRingOuter2); lcd.fillCircle(x, y, 8, colorRingInner); }
Das macht die Funktion multifunktional denn wir können mit dieser eine die komplette Spielfläche neu zeichnen und sparen somit Speicherplatz und der Code wird auch reduziert.
Schritt 2 – setzen eines Spielsteines
Das Spiel ist für zwei Spieler ausgelegt und diese setzen nacheinander jeweils einen Spielstein. Zum setzen eines Spielsteins in eine Zeile wird eines der grünen Pfeile betätigt. Es wird dann der letzte freie Platz der Spalte ermittelt und in diese eines der Steine gesetzt.
Bevor wir jedoch einen Spielstein setzen können, müssen wir den klick auf eines der Pfeile erkennen. Dieses machen wir wie beim Spiel Tic-Tac-Toe in der Funktion Loop.
void loop() { //Wenn ein Touchaktion ausgeführt / erkannt wurde, //dann liefert die Funktion getTouch den Wert 1 und //befüllt die übergebenen Parametern mit den Koordinaten if (lcd.getTouch(&x, &y) == 1) { //Wenn die ermittelte X oder Y Positon außerhalb der Range ist, //dann soll die Funktion hier verlassen werden. if (x > MAX_X || y > MAX_Y) { return; } //Wenn das Spiel beendet ist, dann... if (gameOver) { //initialisiern der Spielfläche initPlayground(); drawPlayground(); //Feld wieder auf false setzen gameOver = false; } else { //Wenn das Spiel noch läuft, dann... //Variable zum speichern ob ein Pfeil geklickt wurde bool playerChooseArrow = false; //Variable zum speichern ob ein leeres Feld in der Spalte gefunden wurde bool foundEmptyPlace = false; //Schleife über alle Pfeile for (int arrow = 0; arrow < COLS; arrow++) { Arrow arrowField = arrowFields[arrow]; //prüfen ob der Pfeil an der Position aus dem Array geklickt wurde if (checkCoord(arrowField, x, y)) { //Variable auf true setzen playerChooseArrow = true; for (int row = ROWS - 1; row >= 0; row--) { //prüfen ob das Feld leer / mit einem Strich belegt ist if (playground[row][arrow] == DASH) { //Umkehren des Wertes für den Spieler currentPlayer1 = !currentPlayer1; //bei Spieler 1 soll ein X und bei Spieler 2 ein O an die Stelle im //Array gespeichert werden playground[row][arrow] = currentPlayer1 ? PLAYER1_CHAR : PLAYER2_CHAR; //Es wurde ein leeres Feld gefunden und somit Variable auf true setzen foundEmptyPlace = true; //Schleife abbrechen break; } } } } //Wenn ein leeres Feld gefunden wurde, //und ein Pfeil gewählt wurde, dann... if (foundEmptyPlace && playerChooseArrow) { //zeichnen des Arrays drawHoles(); //prüfen ob der aktuelle Spieler gewonnen hat char player = currentPlayer1 ? PLAYER1_CHAR : PLAYER2_CHAR; if (hasPlayerWon(player)) { String playerColor = currentPlayer1 ? "Gelb" : "Rot"; displayMessage("Der Spieler " + playerColor + " hat gewonnen!", 0); gameOver = true; } else if (isGameDraw()) { displayMessage("Das Spiel ist unentschieden!", 40); gameOver = true; } } //eine kleine Pause von 300 ms. //Damit wird dem Spieler die möglichkeit gegeben //den Stift vom Display zu nehmen. delay(300); } } //eine Pause von 50ms. delay(50); }
Schritt 4 – ermitteln ob ein Spieler gewonnen hat
Wenn ein Spieler einen Spielstein gesetzt hat, dann wird zusätzlich auch geprüft ob dieser Spieler gewonnen hat oder es sogar ein unentschieden gibt.
// Funktion, die prüft, ob ein Spieler gewonnen hat bool hasPlayerWon(char player) { // Überprüfen von horizontalen Linien for (int row = 0; row < ROWS; row++) { for (int col = 0; col <= COLS - 4; col++) { if (playground[row][col] == player && playground[row][col + 1] == player && playground[row][col + 2] == player && playground[row][col + 3] == player) { return true; } } } // Überprüfen von vertikalen Linien for (int col = 0; col < COLS; col++) { for (int row = 0; row <= ROWS - 4; row++) { if (playground[row][col] == player && playground[row + 1][col] == player && playground[row + 2][col] == player && playground[row + 3][col] == player) { return true; } } } // Überprüfen von diagonalen Linien (von links oben nach rechts unten) for (int row = 0; row <= ROWS - 4; row++) { for (int col = 0; col <= COLS - 4; col++) { if (playground[row][col] == player && playground[row + 1][col + 1] == player && playground[row + 2][col + 2] == player && playground[row + 3][col + 3] == player) { return true; } } } // Überprüfen von diagonalen Linien (von rechts oben nach links unten) for (int row = 0; row <= ROWS - 4; row++) { for (int col = 3; col < COLS; col++) { if (playground[row][col] == player && playground[row + 1][col - 1] == player && playground[row + 2][col - 2] == player && playground[row + 3][col - 3] == player) { return true; } } } // Falls kein Spieler gewonnen hat, return false return false; }
Um zu prüfen ob das Spiel untentschieden ist, muss zunächst geprüft werden ob keines der Spielfelder mit einem Strich belegt ist.
bool isGameDraw() { // Überprüfen, ob noch leere Felder vorhanden sind for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (playground[row][col] == '-') { // Es gibt noch mindestens ein leeres Feld, das Spiel ist nicht unentschieden return false; } } } // Das Spielfeld ist vollständig belegt // Überprüfen, ob einer der Spieler gewonnen hat return !hasPlayerWon(PLAYER1_CHAR) && !hasPlayerWon(PLAYER2_CHAR); }
Wenn dieses nicht so ist, dann wird zusätzlich geprüft ob Spieler 1 oder Spieler 2 gewonnen hat. Dieses ist theoretisch obsolete da dieses zuvor ebenso geprüft wurde, dient jedoch der Sicherheit.
Das fertige Spiel – Vier gewinnt für das ESP32 mit TFT-Display
Nachfolgend der Quellcode zum fertigen Spiel mit allen Kommentaren.
/* Einbinden der Bibliothek zum kommunizieren mit dem Display. */ #define LGFX_USE_V1 #include <LovyanGFX.hpp> #include "lgfx_ESP32_2432S028.h" //Größe der Zeichenfläche definieren #define MAX_X 319 #define MAX_Y 239 //Felder für die ermittelte Position //bei einem klick uint16_t x = 0, y = 0; //Instanz des Displays static LGFX lcd; //Feld für die Daten eines Pfeiles struct Arrow { int col; int x; //X Koordinate int y; //Y Koordinate }; //die beiden Spieler erhalten jeweils die Farbe Gelb&Rot //im Array jedoch werden diese durch die beiden nachfolgenden //Symbole dargestellt. const char PLAYER1_CHAR = 'X'; const char PLAYER2_CHAR = '0'; //Der Strich ist der default Wert const char DASH = '-'; int triangleWidth = 25; // Breite der Dreiecke int startX = 50; // Start-X-Koordinate int startY = 45; // Start-Y-Koordinate int spacing = 10; // Abstand zwischen den Dreiecken int circleRadius = 15; // Radius der Kreise int circleSpacing = 20; // Abstand zwischen den Kreisen (korrigiert) int circleYspacing = 5; // Abstand zwischen den Kreisen untereinander //Anzahl der Spalten const int COLS = 7; //Anzahl der Zeilen const int ROWS = 5; //Array für die Pfeile //das Array wird beim zeichnen befüllt Arrow arrowFields[COLS] = {}; //Das Array mit dem Spielfeld //initial sind alle Felder leer //bzw. mit einem Strich markiert char playground[ROWS][COLS] = { {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH}, {DASH, DASH, DASH, DASH, DASH, DASH, DASH} }; //Feld zum speichern ob das Spiel beendet ist bool gameOver = false; void setup(void) { //beginn der seriellen Kommunikation mit //115200 Baud Serial.begin(115200); //beginn der Kommunikation mit dem Display lcd.init(); //drehen des Displays lcd.setRotation(1); //füllen des Displays mit der Farbe Schwarz lcd.fillScreen(TFT_BLACK); //eine kleine Pause von 100ms. delay(100); //zeichnen der Spielfläche drawPlayground(); } //Feld zum speichern welcher Spieler am zug ist //beginnen tut der Spieler 1 / Gelb bool currentPlayer1 = false; //Funktion zum initialisieren der Spielfläche void initPlayground() { //Schleife über die Spalten und Zeilen for (int col = 0; col < COLS; col++) { for (int row = 0; row < ROWS; row++) { //Zuweisen eines Striches an die Position im Array playground[row][col] = DASH; } } } //Funktion zum zeichnen der Spielfläche void drawPlayground() { //leeren des Displays lcd.clear(); //Hintergrundfarbe Schwarz lcd.fillScreen(TFT_BLACK); //Schriftgröße 4 lcd.setFont(&fonts::Font4); //Schriftfarbe Gelb, //Hintergrundfarbe Schwarz lcd.setTextColor(TFT_YELLOW, TFT_BLACK); //Text an die Position x & y schreiben lcd.drawString("Vier gewinnt!", 105, 5); //ein blaues rechteck zeichnen lcd.fillRect(40, 35, 255, 205, TFT_BLUE); // For-Schleife zum Zeichnen von sieben Dreiecken for (int i = 0; i < 7; i++) { // Koordinaten für die Eckpunkte des Dreiecks berechnen int x1 = startX + (i * (triangleWidth + spacing)); int y1 = startY; int x2 = x1 + triangleWidth / 2; int y2 = startY + (triangleWidth / 2); int x3 = x1 + triangleWidth; int y3 = startY; // fillTriangle-Funktion aufrufen, um das Dreieck zu zeichnen lcd.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREEN); Arrow arrowField = {i, x1, y1}; arrowFields[i] = arrowField; } //zeichnen des Arrays drawHoles(); //anzeigen der Daten auf dem Display lcd.display(); } //Funktion zum zeichnen der Daten des Arrays void drawHoles() { //ein gefülltes, blaues rechteck über die Spielfläche zeichnen //damit werden alle zuvor gezeichneten Daten überschrieben lcd.fillRect(40, 60, 255, 185, TFT_BLUE); //Schleife über das Array for (int col = 0; col < COLS; col++) { Arrow arrowField = arrowFields[col]; for (int row = 0; row < ROWS; row++) { //Berechnen der X & Y Koordinate für das Loch int circleY = (startY + triangleWidth + circleSpacing + (2 * circleRadius + circleYspacing) * row) - 10; // y-Koordinate des Kreises int circleX = arrowField.x + triangleWidth / 2; // x-Koordinate des Kreises lcd.fillCircle(circleX, circleY, circleRadius, TFT_BLACK); //Wenn an der Koordinate im Array kein Strich ist, dann ist dort ein Spielstein abgelegt if (playground[row][col] != DASH) { //den Spielstein zeichnen drawStone(circleX, circleY, playground[row][col] == PLAYER1_CHAR); } } } } //Zeichnet ein Spielstein an die Position X & Y //der boolsche Parameter player1 gibt an ob Gelb oder Rot am zug ist void drawStone(int x, int y, bool player1) { //initial ist Gelb gesetzt uint8_t colorRingOuter1 = lcd.color332(137, 121, 5);; uint8_t colorRingOuter2 = lcd.color332(238, 226, 146); uint8_t colorRingInner = lcd.color332(243, 212, 0); //bei Spieler 2 soll ein Stein in der Farbe Rot gezeichnet werden. if (!player1) { colorRingOuter1 = lcd.color332(106, 5, 13); colorRingOuter2 = lcd.color332(239, 109, 119); colorRingInner = lcd.color332(255, 0, 0); } //zeichnen des Spielsteins lcd.fillCircle(x, y, 15, colorRingOuter1); lcd.fillCircle(x, y, 12, colorRingOuter2); lcd.fillCircle(x, y, 8, colorRingInner); } // Funktion, die prüft, ob ein Spieler gewonnen hat bool hasPlayerWon(char player) { // Überprüfen von horizontalen Linien for (int row = 0; row < ROWS; row++) { for (int col = 0; col <= COLS - 4; col++) { if (playground[row][col] == player && playground[row][col + 1] == player && playground[row][col + 2] == player && playground[row][col + 3] == player) { return true; } } } // Überprüfen von vertikalen Linien for (int col = 0; col < COLS; col++) { for (int row = 0; row <= ROWS - 4; row++) { if (playground[row][col] == player && playground[row + 1][col] == player && playground[row + 2][col] == player && playground[row + 3][col] == player) { return true; } } } // Überprüfen von diagonalen Linien (von links oben nach rechts unten) for (int row = 0; row <= ROWS - 4; row++) { for (int col = 0; col <= COLS - 4; col++) { if (playground[row][col] == player && playground[row + 1][col + 1] == player && playground[row + 2][col + 2] == player && playground[row + 3][col + 3] == player) { return true; } } } // Überprüfen von diagonalen Linien (von rechts oben nach links unten) for (int row = 0; row <= ROWS - 4; row++) { for (int col = 3; col < COLS; col++) { if (playground[row][col] == player && playground[row + 1][col - 1] == player && playground[row + 2][col - 2] == player && playground[row + 3][col - 3] == player) { return true; } } } // Falls kein Spieler gewonnen hat, return false return false; } void loop() { //Wenn ein Touchaktion ausgeführt / erkannt wurde, //dann liefert die Funktion getTouch den Wert 1 und //befüllt die übergebenen Parametern mit den Koordinaten if (lcd.getTouch(&x, &y) == 1) { //Wenn die ermittelte X oder Y Positon außerhalb der Range ist, //dann soll die Funktion hier verlassen werden. if (x > MAX_X || y > MAX_Y) { return; } //Wenn das Spiel beendet ist, dann... if (gameOver) { //initialisiern der Spielfläche initPlayground(); drawPlayground(); //Feld wieder auf false setzen gameOver = false; } else { //Wenn das Spiel noch läuft, dann... //Variable zum speichern ob ein Pfeil geklickt wurde bool playerChooseArrow = false; //Variable zum speichern ob ein leeres Feld in der Spalte gefunden wurde bool foundEmptyPlace = false; //Schleife über alle Pfeile for (int arrow = 0; arrow < COLS; arrow++) { Arrow arrowField = arrowFields[arrow]; //prüfen ob der Pfeil an der Position aus dem Array geklickt wurde if (checkCoord(arrowField, x, y)) { //Variable auf true setzen playerChooseArrow = true; //Umkehren des Wertes für den Spieler currentPlayer1 = !currentPlayer1; for (int row = ROWS - 1; row >= 0; row--) { //prüfen ob das Feld leer / mit einem Strich belegt ist if (playground[row][arrow] == DASH) { //bei Spieler 1 soll ein X und bei Spieler 2 ein O an die Stelle im //Array gespeichert werden playground[row][arrow] = currentPlayer1 ? PLAYER1_CHAR : PLAYER2_CHAR; //Es wurde ein leeres Feld gefunden und somit Variable auf true setzen foundEmptyPlace = true; //Schleife abbrechen break; } } } } //Wenn kein leeres Feld gefunden wurde, dann soll der Spieler wieder //umgekehrt werden, somit ist der gleiche Spieler wieder dran if (!foundEmptyPlace) { currentPlayer1 = !currentPlayer1; } else { //Wenn ein leeres Feld gefunden wurde, dann... //zeichnen des Arrays drawHoles(); //prüfen ob der aktuelle Spieler gewonnen hat char player = currentPlayer1 ? PLAYER1_CHAR : PLAYER2_CHAR; if (playerChooseArrow && hasPlayerWon(player)) { lcd.fillRect(15, 120, 295, 30, TFT_RED); lcd.setCursor(20, 125); lcd.setTextSize(0.85); lcd.setTextColor(TFT_GREEN, TFT_RED); String playerColor = currentPlayer1 ? "Gelb" : "Rot"; lcd.print("Der Spieler " + playerColor + " hat gewonnen!"); gameOver = true; } } //eine kleine Pause von 300 ms. //Damit wird dem Spieler die möglichkeit gegeben //den Stift vom Display zu nehmen. delay(300); } } //eine Pause von 50ms. delay(50); } //Funktion zum prüfen ob die geklickte Koordinate innerhalb des Feldes liegt bool checkCoord(Arrow arrowField, int x, int y) { return (x >= arrowField.x && x <= (arrowField.x + triangleWidth) && y >= arrowField.y && y <= (arrowField.y + triangleWidth)); }