Header Image

Jak vyrobit (téměř) cokoliv

Week 10

Weekly tasks

  • Create an application that interfaces between the user and your input or output device. The web interface doesn't count (you covered that last week).
  • The idea

    My idea was based on so-called Stroop effect. Simply, for our brain it is hard to acknowledge the colour when the word that has this colour means different colour. I decided to combine Arduino with Processing.

    Arduino

    After last week I returned to Arduino (noises of happiness). To the Arduino I connected 5 different buttons with different colours. After button is pushed, the "Serial.println("b");" writes the coresponding colour.

      
                #define BUTTON_RED     3  // r
                #define BUTTON_GREEN   4  // g
                #define BUTTON_BLUE    5  // b
                #define BUTTON_YELLOW  2  // y
                #define BUTTON_WHITE   6  // w
                
                void setup() {
                  Serial.begin(9600);
                  pinMode(BUTTON_RED, INPUT_PULLUP);
                  pinMode(BUTTON_GREEN, INPUT_PULLUP);
                  pinMode(BUTTON_BLUE, INPUT_PULLUP);
                  pinMode(BUTTON_YELLOW, INPUT_PULLUP);
                  pinMode(BUTTON_WHITE, INPUT_PULLUP);
                }
                
                void loop() {
                  if (digitalRead(BUTTON_RED) == LOW) {
                    Serial.println("r");
                    delay(200);
                  }
                  if (digitalRead(BUTTON_GREEN) == LOW) {
                    Serial.println("g");
                    delay(200);
                  }
                  if (digitalRead(BUTTON_BLUE) == LOW) {
                    Serial.println("b");
                    delay(200);
                  }
                  if (digitalRead(BUTTON_YELLOW) == LOW) {
                    Serial.println("y");
                    delay(200);
                  }
                  if (digitalRead(BUTTON_WHITE) == LOW) {
                    Serial.println("w");
                    delay(200);
                  }
                }
                
            

    Processing

    When I was satisfied with my hardware part of this miniproject, the main focus redirected to software part of it. From the lecture I understood that the easiest way would be to do it in "Processing". The main issue was that it is Java... did I work with Java before? No. Fortunately for me, tutorials (and Chatgpt) exist and I was able to produce this script. It is simplified version where you answer with keyboard and have limited time to do it.

      
            String[] colorWords = {"RED", "GREEN", "BLUE"};
            color[] actualColors = {color(255,0,0), color(0,255,0), color(0,0,255)};
            int wordIndex;              
            int displayedColorIndex;    
            int timeLimit = 5000;
            int lastTime;
            boolean waitingForInput = false;
            
            int score = 0;
            
            void setup() {
              size(600, 400);
              textAlign(CENTER, CENTER);
              textSize(64);
              newRound();
            }
            
            void draw() {
              background(255);
            
              // Zobraz slovo s barvou
              fill(actualColors[displayedColorIndex]);
              text(colorWords[wordIndex], width/2, height/2);
            
              // Zobraz skóre
              fill(0);
              textSize(32);
              textAlign(LEFT, TOP);
              text("Score: " + score, 10, 10);
            
              // Obnovit zarovnání pro další text
              textAlign(CENTER, CENTER);
              textSize(64);
            
              int timeLeft = timeLimit - (millis() - lastTime);
              if (waitingForInput && timeLeft <= 0) {
                println("Timeout!");
                newRound();
              }
            }
            
            void keyPressed() {
              if (!waitingForInput) return;
            
              int pressedIndex = -1;
              if (key == 'r') pressedIndex = 0;
              if (key == 'g') pressedIndex = 1;
              if (key == 'b') pressedIndex = 2;
            
              if (pressedIndex == displayedColorIndex) {
                println("Correct!");
                score++;
                timeLimit = max(1000, timeLimit - 200); 
              } else {
                println("Wrong!");
                score = 0;
                timeLimit = 5000;
              }
            
              newRound();
            }
            
            void newRound() {
              wordIndex = int(random(3));
              do {
                displayedColorIndex = int(random(3));
              } while (displayedColorIndex == wordIndex); 
            
              lastTime = millis();
              waitingForInput = true;
            }
            
        

    Final result

    When both parts were finished, it was time to befriend Arduino with Processing. The main role play first three lines of the script in Processing. The Arduino IDE code I did not change - but here was essential for some reason to close "Serial Monitor" (otherwise the Processing code did not work). The idea behind this code: it writes you name of some colour and it is in different colour. Then you have to push some button - the Arduino then sends the letter (each button is somehow labeled) and Processing analyses if you messed up or not. In the end you also get analysis of how fast was your reaction.
      
            import processing.serial.*;
            Serial myPort;  // Create object from Serial class
            String val;     // Data received from the serial port
            ArrayList reactionTimes = new ArrayList();
            int roundStartTime;
            boolean showFinalGraph = false;
            
            
            
            String[] colorWords = {"červená", "zelená", "modrá", "žlutá", "bílá"};
            color[] actualColors = {
              color(255, 0, 0),    // červená
              color(0, 255, 0),    // zelená
              color(0, 0, 255),    // modrá
              color(255, 255, 0),  // žlutá
              color(255, 255, 255) // bílá
            };
            
            int wordIndex;
            int displayedColorIndex;
            int timeLimit = 5000;
            int lastTime;
            boolean waitingForInput = false;
            boolean gameStarted = false;
            
            int score = 0;
            
            void setup() {
              size(600, 400);
              textAlign(CENTER, CENTER);
              textSize(64);
            
              // Initialize serial
              String portName = Serial.list()[0]; 
              myPort = new Serial(this, portName, 9600);
              myPort.clear();
            }
            
            void draw() {
              background(0);
            
              if (showFinalGraph) {
                drawFinalGraph();
                return;
              }
            
              if (!gameStarted) {
                fill(255);
                textSize(36);
                text("Jsi připraven/a se otestovat?", width/2, height/2 - 40);
                textSize(24);
                text("Stiskni ENTER pro start", width/2, height/2 + 20);
                return;
              }
            
              fill(actualColors[displayedColorIndex]);
              textSize(64);
              text(colorWords[wordIndex], width/2, height/2);
            
              fill(255);
              textSize(32);
              textAlign(LEFT, TOP);
              text("Skóre: " + score, 10, 10);
              textAlign(CENTER, CENTER);
              textSize(64);
            }
            
            void drawGraph() {
              stroke(255);
              noFill();
              beginShape();
              for (int i = 0; i < reactionTimes.size(); i++) {
                float x = map(i, 0, max(10, reactionTimes.size()), 0, width);
                float y = map(reactionTimes.get(i), 0, timeLimit, height, height - 100); // invert Y
                vertex(x, y);
              }
              endShape();
              }
            
            void serialEvent(Serial myPort) {
              String inString = myPort.readStringUntil('\n');
              if (inString != null) {
                inString = trim(inString);
                if (inString.length() > 0) {
                  // Simulate keyPressed with character from Arduino
                  println("From Arduino: " + inString);
                  key = inString.charAt(0);
                  keyPressed();  // manually trigger keyPressed()
                }
              }
            }
            
            void keyPressed() {
              if (!gameStarted && key == ENTER) {
                gameStarted = true;
                score = 0;
                reactionTimes.clear();
                newRound();
                return;
              }
            
              if (!waitingForInput) return;
            
              int pressedIndex = -1;
              if (key == 'r') pressedIndex = 0;
              if (key == 'g') pressedIndex = 1;
              if (key == 'b') pressedIndex = 2;
              if (key == 'y') pressedIndex = 3;
              if (key == 'w') pressedIndex = 4;
            
              if (pressedIndex == displayedColorIndex) {
                println("Correct!");
                score++;
                int reactionTime = millis() - roundStartTime;
                reactionTimes.add(reactionTime);
                println("Reaction Time: " + reactionTime + " ms");
                newRound();
              } else {
                println("Wrong! Game Over.");
                gameStarted = false;  // stops the game
                showFinalGraph = true; // shows the graph
              }
            }
            
            
            void newRound() {
              wordIndex = int(random(5));
              do {
                displayedColorIndex = int(random(5));
              } while (displayedColorIndex == wordIndex);
            
              roundStartTime = millis();
              waitingForInput = true;
            }
            void drawFinalGraph() {
              background(0);
              stroke(255);
              fill(255);
              textSize(20);
              textAlign(CENTER, CENTER);
              text("Reakční časy", width/2, 30);
            
              // Axes
              stroke(150);
              line(60, height - 60, width - 40, height - 60); // X-axis
              line(60, height - 60, 60, 60);                  // Y-axis
            
              // Y-axis labels (reaction time in ms)
              textSize(12);
              int[] yTicks = {0, 500, 1000, 1500, 2000, 2500, 3000};
              for (int i = 0; i < yTicks.length; i++) {
                float y = map(yTicks[i], 0, 3000, height - 60, 60);
                fill(200);
                noStroke();
                text(yTicks[i] + " ms", 35, y);
                stroke(50);
                line(60, y, width - 40, y); // optional horizontal grid lines
              }
            
              // X-axis labels (round numbers)
              int maxX = reactionTimes.size();
              for (int i = 0; i < maxX; i++) {
                float x = map(i, 0, max(1, maxX - 1), 60, width - 40);
                fill(200);
                noStroke();
                text(i + 1, x, height - 40);
              }
            
              // Axis units text
              fill(255);
              text("Číslo odpovědi", width / 2, height - 20);
            
              pushMatrix();
              translate(20, height / 2);
              rotate(-HALF_PI);
              text("Reakční čas (ms)", 0, 0);
              popMatrix();
            
              // Draw reaction time graph
              noFill();
              stroke(0, 255, 0);
              beginShape();
              for (int i = 0; i < reactionTimes.size(); i++) {
                float x = map(i, 0, max(1, maxX - 1), 60, width - 40);
                float y = map(reactionTimes.get(i), 0, 3000, height - 60, 60);
                vertex(x, y);
              }
              endShape();
            
              // Draw points
              for (int i = 0; i < reactionTimes.size(); i++) {
                float x = map(i, 0, max(1, maxX - 1), 60, width - 40);
                float y = map(reactionTimes.get(i), 0, 3000, height - 60, 60);
                fill(255, 0, 0);
                noStroke();
                ellipse(x, y, 6, 6);
              }
            }
        
    Fritzing scheme