Skip to content

Commit 9c10e77

Browse files
authored
Merge pull request #67 from Project516/copilot/add-teavm-for-gh-pages
Replace CheerpJ with TeaVM for GitHub Pages deployment
2 parents 5ec0b0c + 0655a55 commit 9c10e77

9 files changed

Lines changed: 1231 additions & 4 deletions

File tree

.github/workflows/javadoc.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ jobs:
2424
- name: Generate JavaDoc
2525
run: ./gradlew javadoc
2626

27-
- name: Copy Cheerpj
28-
run: ./cheerpj.sh
27+
- name: Build TeaVM (JavaScript compilation)
28+
run: ./teavm.sh
2929

3030
- name: Deploy to GitHub Pages
3131
uses: peaceiris/actions-gh-pages@v4

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,5 @@ NumberGuessingGame-windows.zip
3737
NumberGuessingGame-macos.zip
3838
NumberGuessingGame-linux.tar.gz
3939

40+
# TeaVM generated JavaScript (build artifact)
41+
teavm/classes.js

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ A simple number guessing game where you try to guess a randomly generated number
55
**Features:**
66
- Swing-based GUI (default)
77
- Console mode (use `--console` flag)
8+
- Web browser version (available on GitHub Pages)
89
- High score tracking with usernames
910
- Persistent score storage
1011
- Cross-platform
@@ -61,7 +62,7 @@ Download the `archive.zip` from the [latest release](https://github.com/project5
6162

6263
#### Requirements
6364

64-
- Java 8 or higher (may require Java 17+ in future versions)
65+
- Java 11 or higher
6566

6667
#### How to Run
6768

app/build.gradle

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ plugins {
44

55
//Spotless
66
id 'com.diffplug.spotless' version '8.0.0'
7+
8+
// TeaVM plugin for JavaScript compilation
9+
id 'io.github.zebalu.teavm-gradle-plugin' version '1.0.0'
710
}
811

912
repositories {
@@ -14,6 +17,9 @@ repositories {
1417
dependencies {
1518
// This dependency is used by the application.
1619
implementation libs.guava
20+
21+
// TeaVM JSO APIs for web development (needed at compile time for WebGUI)
22+
compileOnly 'org.teavm:teavm-jso-apis:0.10.2'
1723
}
1824

1925
testing {
@@ -35,7 +41,10 @@ java {
3541
}
3642

3743
tasks.withType(JavaCompile) {
38-
options.release.set(8)
44+
// Changed from 8 to 11 to support TeaVM for web deployment.
45+
// TeaVM 0.10.2+ requires Java 11 as minimum target.
46+
// This change affects the minimum JRE required to run the JAR.
47+
options.release.set(11)
3948
}
4049

4150
application {
@@ -61,3 +70,9 @@ jar {
6170
)
6271
}
6372
}
73+
74+
// TeaVM configuration for compiling Java GUI to JavaScript
75+
teavm {
76+
// Main class to compile
77+
mainClass = 'io.github.project516.NumberGuessingGame.WebGUI'
78+
}
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
package io.github.project516.NumberGuessingGame;
2+
3+
import org.teavm.jso.browser.Window;
4+
import org.teavm.jso.dom.html.*;
5+
6+
/**
7+
* Web-based GUI for the Number Guessing Game using TeaVM. This class provides a browser-native
8+
* interface that reuses the core game logic from the existing classes. Created specifically for
9+
* TeaVM compilation to JavaScript for GitHub Pages deployment.
10+
*/
11+
public class WebGUI {
12+
// Game state - reusing existing game logic classes
13+
private int targetNumber;
14+
private int numberOfGuesses;
15+
private RandomNumber randomGenerator;
16+
private CheckGuess guessChecker;
17+
18+
// DOM elements
19+
private HTMLDocument document;
20+
private HTMLInputElement guessInput;
21+
private HTMLButtonElement submitButton;
22+
private HTMLButtonElement newGameButton;
23+
private HTMLElement feedbackElement;
24+
private HTMLElement guessCountElement;
25+
26+
/**
27+
* Main entry point for the web application. TeaVM will call this method when the page loads.
28+
*
29+
* @param args command line arguments (not used in web context)
30+
*/
31+
public static void main(String[] args) {
32+
new WebGUI().initialize();
33+
}
34+
35+
/** Initializes the web GUI and starts a new game. */
36+
public void initialize() {
37+
// Initialize game objects - reusing existing classes
38+
randomGenerator = new RandomNumber();
39+
guessChecker = new CheckGuess();
40+
41+
// Get the HTML document
42+
document = Window.current().getDocument();
43+
44+
// Create the game UI
45+
createGameUI();
46+
47+
// Start a new game
48+
startNewGame();
49+
}
50+
51+
/** Creates the game UI elements in the HTML document. */
52+
private void createGameUI() {
53+
// Get or create the game container
54+
HTMLElement gameContainer = (HTMLElement) document.getElementById("game-container");
55+
if (gameContainer == null) {
56+
gameContainer = (HTMLElement) document.createElement("div");
57+
gameContainer.setId("game-container");
58+
document.getBody().appendChild(gameContainer);
59+
}
60+
61+
// Clear existing content
62+
gameContainer.setInnerHTML("");
63+
64+
// Create title
65+
HTMLElement title = (HTMLElement) document.createElement("h1");
66+
title.setInnerHTML("Number Guessing Game");
67+
gameContainer.appendChild(title);
68+
69+
// Create instructions
70+
HTMLElement instructions = (HTMLElement) document.createElement("p");
71+
instructions.setInnerHTML("Guess a number between 1 and 100!");
72+
gameContainer.appendChild(instructions);
73+
74+
// Create guess count display
75+
guessCountElement = (HTMLElement) document.createElement("p");
76+
guessCountElement.setId("guess-count");
77+
guessCountElement.setInnerHTML("Number of guesses: 0");
78+
gameContainer.appendChild(guessCountElement);
79+
80+
// Create input field
81+
guessInput = (HTMLInputElement) document.createElement("input");
82+
guessInput.setType("number");
83+
guessInput.setAttribute("min", "1");
84+
guessInput.setAttribute("max", "100");
85+
guessInput.setAttribute("placeholder", "Enter your guess");
86+
guessInput.setId("guess-input");
87+
gameContainer.appendChild(guessInput);
88+
89+
// Create submit button
90+
submitButton = (HTMLButtonElement) document.createElement("button");
91+
submitButton.setInnerHTML("Submit Guess");
92+
submitButton.setId("submit-button");
93+
submitButton.addEventListener("click", evt -> submitGuess());
94+
gameContainer.appendChild(submitButton);
95+
96+
// Allow Enter key to submit
97+
guessInput.addEventListener(
98+
"keypress",
99+
evt -> {
100+
if ("Enter".equals(((HTMLKeyboardEvent) evt).getKey())) {
101+
submitGuess();
102+
}
103+
});
104+
105+
// Create feedback area
106+
feedbackElement = (HTMLElement) document.createElement("p");
107+
feedbackElement.setId("feedback");
108+
feedbackElement.setInnerHTML(" ");
109+
gameContainer.appendChild(feedbackElement);
110+
111+
// Create new game button (initially hidden)
112+
newGameButton = (HTMLButtonElement) document.createElement("button");
113+
newGameButton.setInnerHTML("New Game");
114+
newGameButton.setId("new-game-button");
115+
newGameButton.getStyle().setProperty("display", "none");
116+
newGameButton.addEventListener("click", evt -> startNewGame());
117+
gameContainer.appendChild(newGameButton);
118+
}
119+
120+
/** Starts a new game by resetting the game state. */
121+
private void startNewGame() {
122+
// Generate new target number using existing RandomNumber class
123+
targetNumber = randomGenerator.number(100);
124+
numberOfGuesses = 0;
125+
126+
// Reset UI
127+
guessInput.setValue("");
128+
guessInput.setDisabled(false);
129+
submitButton.setDisabled(false);
130+
newGameButton.getStyle().setProperty("display", "none");
131+
feedbackElement.setInnerHTML(" ");
132+
feedbackElement.setClassName("");
133+
updateGuessCount();
134+
135+
// Focus on input
136+
guessInput.focus();
137+
}
138+
139+
/** Processes the user's guess and updates the UI with feedback. */
140+
private void submitGuess() {
141+
String input = guessInput.getValue().trim();
142+
143+
if (input.isEmpty()) {
144+
showFeedback("Please enter a number!", "error");
145+
return;
146+
}
147+
148+
int guess;
149+
try {
150+
guess = Integer.parseInt(input);
151+
} catch (NumberFormatException e) {
152+
showFeedback("Invalid input! Please enter a valid number.", "error");
153+
guessInput.setValue("");
154+
return;
155+
}
156+
157+
// Validate guess using existing CheckGuess class
158+
try {
159+
guessChecker.check(guess);
160+
} catch (IllegalArgumentException e) {
161+
showFeedback(e.getMessage(), "error");
162+
guessInput.setValue("");
163+
return;
164+
}
165+
166+
numberOfGuesses++;
167+
updateGuessCount();
168+
169+
// Check the guess
170+
if (guess > targetNumber) {
171+
showFeedback("Too high! Try a lower number.", "high");
172+
} else if (guess < targetNumber) {
173+
showFeedback("Too low! Try a higher number.", "low");
174+
} else {
175+
// Correct guess!
176+
showFeedback(
177+
String.format(
178+
"🎉 Congratulations! You guessed it in %d %s!",
179+
numberOfGuesses, numberOfGuesses == 1 ? "guess" : "guesses"),
180+
"success");
181+
guessInput.setDisabled(true);
182+
submitButton.setDisabled(true);
183+
newGameButton.getStyle().setProperty("display", "block");
184+
newGameButton.focus();
185+
}
186+
187+
guessInput.setValue("");
188+
guessInput.focus();
189+
}
190+
191+
/**
192+
* Updates the feedback message and applies the appropriate CSS class.
193+
*
194+
* @param message the feedback message to display
195+
* @param cssClass the CSS class to apply (error, high, low, success)
196+
*/
197+
private void showFeedback(String message, String cssClass) {
198+
feedbackElement.setInnerHTML(message);
199+
feedbackElement.setClassName("feedback-" + cssClass);
200+
}
201+
202+
/** Updates the guess count display. */
203+
private void updateGuessCount() {
204+
guessCountElement.setInnerHTML("Number of guesses: " + numberOfGuesses);
205+
}
206+
207+
/** Keyboard event interface for handling key presses. */
208+
private interface HTMLKeyboardEvent extends HTMLEvent {
209+
String getKey();
210+
}
211+
212+
/** Base HTML event interface. */
213+
private interface HTMLEvent extends org.teavm.jso.JSObject {}
214+
}

teavm.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/sh
2+
# Script to build TeaVM JavaScript output for GitHub Pages deployment
3+
# This replaces the cheerpj.sh script
4+
5+
echo "Building project..."
6+
./gradlew build
7+
8+
echo "Compiling Java to JavaScript with TeaVM..."
9+
# Disable configuration cache due to TeaVM plugin compatibility
10+
./gradlew teavmc --no-configuration-cache
11+
12+
echo "Copying TeaVM output to javadoc directory..."
13+
mkdir -p app/build/docs/javadoc/teavm
14+
cp -f app/build/teavm/classes.js app/build/docs/javadoc/teavm/
15+
cp -f teavm/index.html app/build/docs/javadoc/teavm/
16+
17+
echo "TeaVM build completed successfully!"
18+
echo "Output location: app/build/docs/javadoc/teavm/"

teavm/README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# TeaVM Web Version
2+
3+
This directory contains the web browser version of the Number Guessing Game, compiled from Java to JavaScript using TeaVM.
4+
5+
## What is TeaVM?
6+
7+
TeaVM is an ahead-of-time compiler for Java bytecode that emits JavaScript. It allows running Java applications in web browsers without requiring a Java plugin or JVM.
8+
9+
## Files
10+
11+
- `index.html` - The main HTML page that loads and runs the game
12+
- `classes.js` - The compiled JavaScript (generated during build, not committed to git)
13+
14+
## Building
15+
16+
To build the TeaVM version:
17+
18+
```bash
19+
./teavm.sh
20+
```
21+
22+
This script will:
23+
1. Build the project
24+
2. Compile the WebGUI class to JavaScript using TeaVM
25+
3. Copy the output to `app/build/docs/javadoc/teavm/` for deployment
26+
27+
## Implementation
28+
29+
The web version uses `WebGUI.java` which:
30+
- Implements a browser-native UI using TeaVM's JSO (JavaScript Objects) API
31+
- Reuses the core game logic classes (`RandomNumber`, `CheckGuess`, etc.)
32+
- Creates DOM elements dynamically for the game interface
33+
- Provides the same gameplay experience as the Swing GUI
34+
35+
## Deployment
36+
37+
The TeaVM version is automatically deployed to GitHub Pages via the `javadoc.yml` workflow whenever changes are pushed to the master branch.
38+
39+
## Technical Details
40+
41+
- **TeaVM Version**: 0.10.2
42+
- **Plugin**: io.github.zebalu.teavm-gradle-plugin v1.0.0
43+
- **Main Class**: `io.github.project516.NumberGuessingGame.WebGUI`
44+
- **Target**: JavaScript
45+
- **Java Version Required**: 11+ (for compilation)

0 commit comments

Comments
 (0)