Skip to content

Commit

Permalink
Feat: Added Timed input game
Browse files Browse the repository at this point in the history
  • Loading branch information
tommyah committed May 23, 2026
1 parent 1607c94 commit c31d0ec
Showing 1 changed file with 168 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.games;

import edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement;
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameGimmick;
import edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesActions;
import java.util.Random;
import java.util.function.IntConsumer;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;

/**
* Timed clicks game found in the
* {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.MiniGamesView}.
*
* <p>Extends {@link edu.ntnu.idi.idatt2003.g40.mappe.view.ViewElement}</p>
*
* <p>Implements {@link GameGimmick}</p>
* */
public final class TimeInputsGame
extends ViewElement<StackPane, MiniGamesActions>
implements GameGimmick {

/**
* Canvas for the game.
* */
private Canvas canvas;

/**
* Random generator for angle.
* */
private Random random;

/**
* Angle of the indicator (the mark you use to "hit" the green area).
* */
private double indicatorAngle;

/**
* Start point of the success zone (green area).
* */
private double successZoneStart;

/**
* How "far" the success zone stretches across the circle.
* */
private final double successZoneExtent = 30;

/**
* Consumer given by {@link edu.ntnu.idi.idatt2003.g40.mappe.view.widgets.minigames.GameEngineController}
* That determines how to calculate point gains/losses.
* */
private IntConsumer scoreModifier;

/**
* Constructor.
* */
public TimeInputsGame() {
super(new StackPane(), MiniGamesActions.class);
}

/**
* Evaluates a hit (when pressing space/enter).
*
* <p>Calculates the normalized angle of the current hit section (white part).
* Then, checks if that angle is within the success zone defined by a start
* and length. Then adds or subtracts points based on result,
* and generates a new success zone.</p>
* */
private void evaluateHit() {
// Due to indicator angle being decreased instead of increased,
// We need to increase it by 360 to normalize it.
double normalizedAngle = (indicatorAngle + 360) % 360;

// -10 due to some visual latency causing invalid hits to appear valid.
if (normalizedAngle >= successZoneStart - 10 && normalizedAngle <= (successZoneStart + successZoneExtent)) {
scoreModifier.accept(6);
} else {
scoreModifier.accept(-2);
}

// Changes target area.
this.successZoneStart = generateRandomAngle();

// Resets indicator angle.
indicatorAngle = 90;
}

/**
* Generates a new random number based on the area of a success zone.
*/
private double generateRandomAngle() {
return random.nextDouble() * (360.0 - successZoneExtent);
}

/**
* Renders the canvas, updating the current indicator and success zone.
* */
private void renderCanvas() {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, 300, 300);

double centerX = 150;
double centerY = 150;
double radius = 100;

// Draws circle.
gc.setStroke(Color.BLACK);
gc.setLineWidth(6);
gc.strokeOval(centerX - radius, centerY - radius, radius * 2, radius * 2);

// Draws an arc representing the success zone.
gc.setStroke(Color.CHARTREUSE);
gc.setLineWidth(12);
gc.strokeArc(centerX - radius, centerY - radius, radius * 2, radius * 2, successZoneStart, successZoneExtent, javafx.scene.shape.ArcType.OPEN);

// Calculates and draws the indicator line based on radius and angle.
double rad = Math.toRadians(-indicatorAngle);
double startX = centerX + (radius - 15) * Math.cos(rad);
double startY = centerY + (radius - 15) * Math.sin(rad);
double endX = centerX + (radius + 15) * Math.cos(rad);
double endY = centerY + (radius + 15) * Math.sin(rad);

gc.setStroke(Color.WHITE);
gc.setLineWidth(4);
gc.strokeLine(startX, startY, endX, endY);
}

@Override
protected void initLayout() {
canvas = new Canvas(300,300);
random = new Random();
successZoneStart = generateRandomAngle();
getRootPane().getChildren().add(canvas);
}

@Override
protected void initStyling() {
getRootPane().getStyleClass().add("time-inputs-minigame-root");
}

@Override
public Node getCanvasNode() {
return getRootPane();
}

@Override
public void initialize(final IntConsumer scoreModifier) {
this.scoreModifier = scoreModifier;

getRootPane().setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.SPACE || e.getCode() == KeyCode.ENTER) {
evaluateHit();
}
});
getRootPane().requestFocus();
renderCanvas();
}

@Override
public void updateTick() {
indicatorAngle = (indicatorAngle - 8) % 360;
renderCanvas();
}
}

0 comments on commit c31d0ec

Please sign in to comment.