Help with falling letter cubes and word detection in Processing

final int MENU_WIDTH = 420;
final int MENU_HEIGHT = 380;
final int ACCOUNT_BUTTON_WIDTH = 120;
final int ACCOUNT_BUTTON_HEIGHT = 42;
final int ACCOUNT_BUTTON_MARGIN = 25;
final int ACCOUNT_BUTTON_GAP = 15;
final color BACKGROUND_COLOR = #121623;
final color BORDER_COLOR = #5A648C;
final color TITLE_COLOR = #FFD250;
final color TEXT_COLOR = #EBEBEB;
final color SELECTED_COLOR = #FFD250;
final color INSTRUCTION_COLOR = #C8C8C8;
final color BUTTON_COLOR = #262C41;
final color BUTTON_HOVER_COLOR = #5A648C;
final color BUTTON_BORDER_COLOR = #FFD250;
final String[] MENU_OPTIONS = { "New Game", "Options", "Exit" };
final String MENU = "menu";
final String NAME_ENTRY = "name_entry";
final String GUEST = "guest";
PFont titleFont;
PFont optionFont;
PFont accountButtonFont;
PFont instructionFont;
PFont arrowFont;
int menuX, menuY;
int selectedOption = 0;
String currentScreen = MENU;
String playerName = "";
void settings() {
  
  size(900, 650);
}
void setup() {
  frameRate(60);
  textAlign(CENTER, CENTER);
  titleFont = loadGameFont("AromaVintage-Regular.otf", 56);
  optionFont = loadGameFont("Mooligat-Demo-BF689966f7263ea.otf", 32);
  accountButtonFont = loadGameFont("Mooligat-Demo-BF689966f7263ea.otf", 22);
  instructionFont = createFont("Serif", 18);
  arrowFont = createFont("Consolas Bold", 42);
  menuX = (width - MENU_WIDTH) / 2;
  menuY = (height - MENU_HEIGHT) / 2;
}
void draw() {
  background(BACKGROUND_COLOR);
  if (currentScreen.equals(MENU)) {
    drawMenu();
    drawAccountButton("Login", loginButtonX(), ACCOUNT_BUTTON_MARGIN);
    drawAccountButton("Register", registerButtonX(), ACCOUNT_BUTTON_MARGIN);
  } else if (currentScreen.equals(NAME_ENTRY)) {
    drawNameEntry();
  } else if (currentScreen.equals(GUEST)) {
    drawGuestPage();
  }
}
PFont loadGameFont(String filename, float fontSize) {
  String[] candidates = {
    sketchPath("data/assets/fonts/" + filename),
    sketchPath("assets/fonts/" + filename)
  };
  for (String path : candidates) {
    if (new File(path).isFile()) {
      return createFont(path, fontSize);
    }
  }
  return createFont("Serif", fontSize);
}
void drawCenteredText(PFont font, String message, color textColor, float x, float y) {
  textFont(font);
  fill(textColor);
  textAlign(CENTER, CENTER);
  text(message, x, y);
}
void drawMenu() {
  drawMenuBorder();
  drawCenteredText(titleFont, "Crazy Words", TITLE_COLOR, width / 2, menuY + 65);
  int firstOptionY = menuY + 160;
  for (int index = 0; index < MENU_OPTIONS.length; index++) {
    float optionY = firstOptionY + index * 65;
    color optionColor = (index == selectedOption) ? SELECTED_COLOR : TEXT_COLOR;
    drawCenteredText(optionFont, MENU_OPTIONS[index], optionColor, width / 2, optionY);
    if (index == selectedOption) {
      textFont(arrowFont);
      float halfTextWidth = textWidth(MENU_OPTIONS[index]) / 2.0;
      drawCenteredText(arrowFont, ">", SELECTED_COLOR, width / 2 - halfTextWidth - 40, optionY);
      drawCenteredText(arrowFont, "<", SELECTED_COLOR, width / 2 + halfTextWidth + 40, optionY);
    }
  }
}
void drawMenuBorder() {
  noFill();
  stroke(BORDER_COLOR);
  strokeWeight(3);
  rect(menuX, menuY, MENU_WIDTH, MENU_HEIGHT, 20);
  strokeWeight(1);
}
void drawAccountButton(String label, int x, int y) {
  boolean hovered = pointInRect(mouseX, mouseY, x, y, ACCOUNT_BUTTON_WIDTH, ACCOUNT_BUTTON_HEIGHT);
  fill(hovered ? BUTTON_HOVER_COLOR : BUTTON_COLOR);
  stroke(BUTTON_BORDER_COLOR);
  strokeWeight(2);
  rect(x, y, ACCOUNT_BUTTON_WIDTH, ACCOUNT_BUTTON_HEIGHT, 10);
  strokeWeight(1);
  drawCenteredText(accountButtonFont, label, TEXT_COLOR,
    x + ACCOUNT_BUTTON_WIDTH / 2.0, y + ACCOUNT_BUTTON_HEIGHT / 2.0);
}
void drawNameEntry() {
  drawMenuBorder();
  drawCenteredText(optionFont, "Enter your name", TITLE_COLOR, width / 2, menuY + 80);
  int inputWidth = 320;
  int inputHeight = 60;
  int inputX = width / 2 - inputWidth / 2;
  int inputY = menuY + 165 - inputHeight / 2;
  noFill();
  stroke(BORDER_COLOR);
  strokeWeight(2);
  rect(inputX, inputY, inputWidth, inputHeight, 8);
  strokeWeight(1);
  String cursor = ((millis() / 500) % 2 == 0) ? "_" : "";
  String displayedName = playerName.length() > 0 ? playerName + cursor : "Guest name" + cursor;
  color displayedColor = playerName.length() > 0 ? TEXT_COLOR : INSTRUCTION_COLOR;
  drawCenteredText(optionFont, displayedName, displayedColor, width / 2, menuY + 165);
  drawCenteredText(instructionFont, "Press Enter to continue", INSTRUCTION_COLOR, width / 2, menuY + 245);
  drawCenteredText(instructionFont, "Press Esc to return to the menu", INSTRUCTION_COLOR, width / 2, menuY + 280);
}
void drawGuestPage() {
  drawCenteredText(titleFont, "Welcome, " + playerName + "!", TITLE_COLOR, width / 2, height / 2 - 30);
  drawCenteredText(optionFont, "You are playing as a guest.", TEXT_COLOR, width / 2, height / 2 + 40);
}
void keyPressed() {
  if (currentScreen.equals(MENU)) {
    handleMenuKeyPressed();
  } else if (currentScreen.equals(NAME_ENTRY)) {
    handleNameEntryKeyPressed();
  } else if (currentScreen.equals(GUEST) && key == ESC) {
    key = 0; 
    currentScreen = MENU;
  }
}
void handleMenuKeyPressed() {
  if (key == CODED && keyCode == UP) {
    selectedOption = (selectedOption - 1 + MENU_OPTIONS.length) % MENU_OPTIONS.length;
  } else if (key == CODED && keyCode == DOWN) {
    selectedOption = (selectedOption + 1) % MENU_OPTIONS.length;
  } else if (key == ENTER || key == RETURN) {
    executeMenuAction(selectedOption);
  }
}
void handleNameEntryKeyPressed() {
  if (key == ESC) {
    key = 0; 
    currentScreen = MENU;
  } else if (key == BACKSPACE) {
    if (playerName.length() > 0) {
      playerName = playerName.substring(0, playerName.length() - 1);
    }
  } else if (key == ENTER || key == RETURN) {
    String trimmedName = trim(playerName);
    if (trimmedName.length() > 0) {
      playerName = trimmedName;
      currentScreen = GUEST;
    }
  } else if (key != CODED && !Character.isISOControl(key) && playerName.length() < 16) {
    playerName += key;
  }
}
void mouseMoved() {
  if (!currentScreen.equals(MENU)) {
    return;
  }
  for (int index = 0; index < MENU_OPTIONS.length; index++) {
    if (pointInOptionRect(mouseX, mouseY, index)) {
      selectedOption = index;
      return;
    }
  }
}
void mousePressed() {
  if (!currentScreen.equals(MENU) || mouseButton != LEFT) {
    return;
  }
  if (pointInRect(mouseX, mouseY, loginButtonX(), ACCOUNT_BUTTON_MARGIN,
      ACCOUNT_BUTTON_WIDTH, ACCOUNT_BUTTON_HEIGHT)) {
    println("Login button clicked");
  } else if (pointInRect(mouseX, mouseY, registerButtonX(), ACCOUNT_BUTTON_MARGIN,
      ACCOUNT_BUTTON_WIDTH, ACCOUNT_BUTTON_HEIGHT)) {
    println("Register button clicked");
  } else {
    for (int index = 0; index < MENU_OPTIONS.length; index++) {
      if (pointInOptionRect(mouseX, mouseY, index)) {
        selectedOption = index;
        executeMenuAction(selectedOption);
        return;
      }
    }
  }
}
void executeMenuAction(int option) {
  if (option == 0) {
    playerName = "";
    currentScreen = NAME_ENTRY;
  } else if (option == 1) {
    println("Options selected");
  } else if (option == 2) {
    exit();
  }
}
boolean pointInOptionRect(float px, float py, int optionIndex) {
  int optionWidth = 250;
  int optionHeight = 50;
  int optionX = width / 2 - optionWidth / 2;
  int optionY = menuY + 160 + optionIndex * 65 - optionHeight / 2;
  return pointInRect(px, py, optionX, optionY, optionWidth, optionHeight);
}
boolean pointInRect(float px, float py, float x, float y, float w, float h) {
  return px >= x && px <= x + w && py >= y && py <= y + h;
}
int loginButtonX() {
  return width - ACCOUNT_BUTTON_WIDTH - ACCOUNT_BUTTON_MARGIN;
}
int registerButtonX() {
  return loginButtonX() - ACCOUNT_BUTTON_WIDTH - ACCOUNT_BUTTON_GAP;
}

Hello everyone,

I am building a word game in Processing. I have already created the starting menu, and I now want to implement the gameplay.

My idea is:

  • Random cubes fall downward on the Y axis.
  • Each cube contains one letter.
  • The player selects or arranges the falling cubes to form a word.
  • If the formed word matches the required/correct word, the cubes should explode or disappear.
  • After that, the score should increase, or the game should advance to the next level.

I would appreciate guidance on how to structure this. In particular, I need help with:

  1. Creating and storing multiple falling cubes.
  2. Giving each cube a random letter and downward speed.
  3. Detecting when a cube reaches the bottom of the screen.
  4. Letting the player select cubes and build a word from their letters.
  5. Checking whether the created word is correct.
  6. Creating a simple explosion/disappearing animation.
  7. Updating the score and changing levels.

I am still learning Processing, so examples or suggestions for the correct class structure would be very helpful. Should I create a Cube class, a WordManager class, and a Game class?

Thank you in advance,

Best Regards,

Alex