The task for the tenth week is to program a simple PC app and connect it to a circuit with I/O devices.
I do not have much experience with PC apps, so, I chose to do a simple game with Processing and Arduino. The idea is I will control a player in the game using buttons connected to Arduino. The commands will be send over serial line and visualization and the game itself will run on PC.
const int ledPin = 2;
const int buttonRight = 3;
const int buttonLeft = 4;
const int buttonDown = 5;
const int buttonUp = 6;
bool gameOver = false;
void setup() {
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
pinMode(buttonRight, INPUT_PULLUP);
pinMode(buttonLeft, INPUT_PULLUP);
pinMode(buttonDown, INPUT_PULLUP);
pinMode(buttonUp, INPUT_PULLUP);
Serial.begin(9600);
}
void loop() {
if (Serial.available()) {
String incoming = Serial.readStringUntil('\n');
incoming.trim();
if (incoming == "GAME_OVER") {
gameOver = true;
digitalWrite(ledPin, HIGH);
}
}
if (!gameOver) {
if (digitalRead(buttonRight) == LOW) {
Serial.println("RIGHT");
delay(200);
}
if (digitalRead(buttonLeft) == LOW) {
Serial.println("LEFT");
delay(200);
}
if (digitalRead(buttonDown) == LOW) {
Serial.println("DOWN");
delay(200);
}
if (digitalRead(buttonUp) == LOW) {
Serial.println("UP");
delay(200);
}
} else {
// If game over and button pressed → send RESET
if (digitalRead(buttonRight) == LOW ||
digitalRead(buttonLeft) == LOW ||
digitalRead(buttonDown) == LOW ||
digitalRead(buttonUp) == LOW) {
Serial.println("RESET");
gameOver = false;
digitalWrite(ledPin, LOW);
delay(500); // prevent multiple resets
}
}
}
import processing.serial.*;
import processing.sound.*;
Serial myPort;
final int gridSize = 10;
final int tileSize = 50;
color[][] gridColors = new color[gridSize][gridSize];
int playerX = 5;
int playerY = 5;
boolean gameOver = false;
boolean welcomeScreen = true; // Flag for the welcome screen
int startTime;
int endTime;
int highestScore = 0;
String bestTime = "";
PImage safeTexture;
PImage warningTexture;
PImage[] lavaTextures;
// Sounds
SoundFile fireSound;
SoundFile deathSound;
SoundFile backgroundMusic;
void settings() {
size(gridSize * tileSize, gridSize * tileSize);
}
void setup() {
printArray(Serial.list());
myPort = new Serial(this, Serial.list()[2], 9600);
myPort.bufferUntil('\n');
safeTexture = createSafeTexture();
warningTexture = createWarningTexture();
lavaTextures = createLavaTextures();
fireSound = new SoundFile(this, "fire.aiff");
deathSound = new SoundFile(this, "death.wav");
backgroundMusic = new SoundFile(this, "drama.mp3");
backgroundMusic.loop();
}
void draw() {
background(30);
if (welcomeScreen) {
showWelcomeScreen(); // Show the welcome screen
} else if (!gameOver) {
updateTiles();
drawGrid();
drawPlayer();
detectDanger();
showTimer();
} else {
showGameOver();
// Blocking delay for 2 seconds
delay(1000); // 2000 milliseconds = 2 seconds
}
}
void updateTiles() {
if (frameCount % 15 == 0) { // faster tile change
int randX = int(random(gridSize));
int randY = int(random(gridSize));
if (gridColors[randX][randY] == color(0, 0, 0)){
gridColors[randX][randY] = color(255, 255, 0); // warning
} else if (gridColors[randX][randY] == color(255, 255, 0)) {
gridColors[randX][randY] = color(255, 0, 0); // lava
} else {
float r = random(1);
if (r>0.9) {
gridColors[randX][randY] = color(0, 0, 0); // back to stone
}
}
}
}
void drawGrid() {
for (int i = 0; i < gridSize; i++) {
for (int j = 0; j < gridSize; j++) {
color tileColor = gridColors[i][j];
if (tileColor == color(255, 255, 0)) {
image(warningTexture, i * tileSize, j * tileSize);
} else if (tileColor == color(255, 0, 0)) {
int frame = int(frameCount/5) % lavaTextures.length;
image(lavaTextures[frame], i * tileSize, j * tileSize);
} else {
image(safeTexture, i * tileSize, j * tileSize);
}
stroke(100);
noFill();
rect(i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
void drawPlayer() {
float px = playerX * tileSize + tileSize/2;
float py = playerY * tileSize + tileSize/2;
// Draw chicken (simple)
pushMatrix();
translate(px, py);
noStroke();
// Legs
stroke(255, 150, 0); // Orange color for legs
strokeWeight(3);
line(-5, 15, -5, 20); // Left leg
line(5, 15, 5, 20); // Right leg
noStroke();
// Body
fill(255, 255, 255);
rectMode(CENTER);
rect(0, 0, tileSize*0.6, tileSize*0.6);
// Head (smaller block)
fill(255, 0, 0);
rect(0, -tileSize*0.35, tileSize*0.4, tileSize*0.1); // Head
rectMode(CORNER);
// Beak
fill(255, 150, 0);
triangle(10, -5, 15, 0, 10, 5);
// Eye
fill(0);
ellipse(5, -8, 5, 5);
popMatrix();
// Check if standing on lava
if (gridColors[playerX][playerY] == color(255, 0, 0)) {
gameOver = true;
endTime = millis();
myPort.write("GAME_OVER\n");
deathSound.play();
backgroundMusic.stop();
}
}
void serialEvent(Serial p) {
String input = p.readStringUntil('\n');
input = trim(input);
if (welcomeScreen) {
// Once any button is pressed, transition to the game
welcomeScreen = false;
resetGame(true);
} else if (!gameOver) {
if (input.equals("RIGHT")) {
playerX = min(playerX + 1, gridSize - 1);
fireSound.play();
}
if (input.equals("LEFT")) {
playerX = max(playerX - 1, 0);
fireSound.play();
}
if (input.equals("DOWN")) {
playerY = min(playerY + 1, gridSize - 1);
fireSound.play();
}
if (input.equals("UP")) {
playerY = max(playerY - 1, 0);
fireSound.play();
}
} else {
// Restart if any button is pressed
resetGame(false);
}
}
void resetGame(boolean onStart) {
playerX = 4;
playerY = 4;
gameOver = false;
startTime = millis();
if (!onStart) {
backgroundMusic.loop();
}
// Clear all tiles
for (int i = 0; i < gridSize; i++) {
for (int j = 0; j < gridSize; j++) {
gridColors[i][j] = color(0);
}
}
myPort.write("RESET\n");
}
void showGameOver() {
background(0);
textAlign(CENTER, CENTER);
fill(255, 50, 50);
textSize(24);
int score = (endTime - startTime);
if (score > highestScore) {
highestScore = score;
bestTime = (score/1000) + "." + ((score%1000)/10);
}
text("🔥 GAME OVER! 🔥\nYou survived " + (score/1000) + "." + ((score%1000)/10) + " seconds.\nBest time " + bestTime + " seconds\nPress any button to restart!", width/2, height/2);
}
void showTimer() {
fill(255);
textAlign(LEFT, TOP);
textSize(16);
int currentTime = millis();
float seconds = (currentTime - startTime) / 1000f;
text("Time: " + nf(seconds, 0, 1) + "s", 10, 10);
}
// --- Textures ---
PImage createSafeTexture() {
PImage img = createImage(tileSize, tileSize, RGB);
img.loadPixels();
for (int i = 0; i < img.pixels.length; i++) {
float noiseVal = noise(i * 0.01f);
img.pixels[i] = color(20 + noiseVal*20, 20 + noiseVal*20, 20 + noiseVal*20);
}
img.updatePixels();
return img;
}
PImage createWarningTexture() {
PImage img = createImage(tileSize, tileSize, RGB);
img.loadPixels();
for (int i = 0; i < img.pixels.length; i++) {
float noiseVal = noise(i * 0.01f);
img.pixels[i] = color(255, 255, 150 + noiseVal*50);
}
img.updatePixels();
return img;
}
PImage[] createLavaTextures() {
PImage[] frames = new PImage[4];
for (int f = 0; f < frames.length; f++) {
PImage img = createImage(tileSize, tileSize, RGB);
img.loadPixels();
for (int i = 0; i < img.pixels.length; i++) {
float noiseVal = noise(i * 0.01f + f*0.1f);
img.pixels[i] = color(255, noiseVal*100, 0);
}
img.updatePixels();
frames[f] = img;
}
return frames;
}
void detectDanger() {
boolean danger = false;
// Check only the vertical and horizontal neighbors (not diagonals)
int[] dx = {-1, 1, 0, 0}; // Left, Right, Up, Down
int[] dy = {0, 0, -1, 1}; // Left, Right, Up, Down
for (int i = 0; i < dx.length; i++) {
int nx = playerX + dx[i];
int ny = playerY + dy[i];
// Make sure the neighbor is within the grid boundaries
if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize) {
// Check if the neighboring tile is a lava tile (red color)
if (gridColors[nx][ny] == color(255, 0, 0)) {
danger = true;
}
}
}
// If danger is true (i.e., the player is next to lava), show danger effect
if (danger) {
fill(255, 0, 0, 50); // Red color with transparency for the danger effect
rect(0, 0, width, height); // Overlay danger effect on screen
}
}
void showWelcomeScreen() {
background(0);
textAlign(CENTER, CENTER);
fill(255, 204, 0);
textSize(40);
text("Escape Lava Chicken", width/2, height/3);
fill(255);
textSize(24);
text("Press any button to start", width/2, height/2);
}