package com.almasb.tutorial13; import java.util.HashMap; import javafx.animation.RotateTransition; import javafx.application.Application; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.collections.ObservableList; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.input.KeyEvent; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.scene.shape.Line; import javafx.scene.shape.Rectangle; import javafx.scene.text.Font; import javafx.scene.text.Text; import javafx.scene.transform.Rotate; import javafx.stage.Stage; import javafx.util.Duration; public class HangmanMain extends Application { private static final int APP_W = 900; private static final int APP_H = 500; private static final Font DEFAULT_FONT = new Font("Courier", 36); private static final int POINTS_PER_LETTER = 100; private static final float BONUS_MODIFIER = 0.2f; /** * The word to guess */ private SimpleStringProperty word = new SimpleStringProperty(); /** * How many letters left to guess */ private SimpleIntegerProperty lettersToGuess = new SimpleIntegerProperty(); /** * Current score */ private SimpleIntegerProperty score = new SimpleIntegerProperty(); /** * How many points next correct letter is worth */ private float scoreModifier = 1.0f; /** * Is game playable */ private SimpleBooleanProperty playable = new SimpleBooleanProperty(); /** * List for letters of the word {@link #word} * It is backed up by the HBox children list, * so changes to this list directly affect the GUI */ private ObservableList<Node> letters; /** * K - characters [A..Z] and '-' * V - javafx.scene.Text representation of K */ private HashMap<Character, Text> alphabet = new HashMap<Character, Text>(); private HangmanImage hangman = new HangmanImage(); private WordReader wordReader = new WordReader(); public Parent createContent() { HBox rowLetters = new HBox(); rowLetters.setAlignment(Pos.CENTER); letters = rowLetters.getChildren(); playable.bind(hangman.lives.greaterThan(0).and(lettersToGuess.greaterThan(0))); playable.addListener((obs, old, newValue) -> { if (!newValue.booleanValue()) stopGame(); }); Button btnAgain = new Button("NEW GAME"); btnAgain.disableProperty().bind(playable); btnAgain.setOnAction(event -> startGame()); // layout HBox row1 = new HBox(); HBox row3 = new HBox(); row1.setAlignment(Pos.CENTER); row3.setAlignment(Pos.CENTER); for (int i = 0 ; i < 20; i++) { row1.getChildren().add(new Letter(' ')); row3.getChildren().add(new Letter(' ')); } HBox rowAlphabet = new HBox(5); rowAlphabet.setAlignment(Pos.CENTER); for (char c = 'A'; c <= 'Z'; c++) { Text t = new Text(String.valueOf(c)); t.setFont(DEFAULT_FONT); alphabet.put(c, t); rowAlphabet.getChildren().add(t); } Text hyphen = new Text("-"); hyphen.setFont(DEFAULT_FONT); alphabet.put('-', hyphen); rowAlphabet.getChildren().add(hyphen); Text textScore = new Text(); textScore.textProperty().bind(score.asString().concat(" Points")); HBox rowHangman = new HBox(10, btnAgain, textScore, hangman); rowHangman.setAlignment(Pos.CENTER); VBox vBox = new VBox(10); // vertical layout vBox.getChildren().addAll( row1, rowLetters, row3, rowAlphabet, rowHangman); return vBox; } private void stopGame() { for (Node n : letters) { Letter letter = (Letter) n; letter.show(); } } private void startGame() { for (Text t : alphabet.values()) { t.setStrikethrough(false); t.setFill(Color.BLACK); } hangman.reset(); word.set(wordReader.getRandomWord().toUpperCase()); lettersToGuess.set(word.length().get()); letters.clear(); for (char c : word.get().toCharArray()) { letters.add(new Letter(c)); } } private static class HangmanImage extends Parent { private static final int SPINE_START_X = 100; private static final int SPINE_START_Y = 20; private static final int SPINE_END_X = SPINE_START_X; private static final int SPINE_END_Y = SPINE_START_Y + 50; /** * How many lives left */ private SimpleIntegerProperty lives = new SimpleIntegerProperty(); public HangmanImage() { Circle head = new Circle(20); head.setTranslateX(SPINE_START_X); Line spine = new Line(); spine.setStartX(SPINE_START_X); spine.setStartY(SPINE_START_Y); spine.setEndX(SPINE_END_X); spine.setEndY(SPINE_END_Y); Line leftArm = new Line(); leftArm.setStartX(SPINE_START_X); leftArm.setStartY(SPINE_START_Y); leftArm.setEndX(SPINE_START_X + 40); leftArm.setEndY(SPINE_START_Y + 10); Line rightArm = new Line(); rightArm.setStartX(SPINE_START_X); rightArm.setStartY(SPINE_START_Y); rightArm.setEndX(SPINE_START_X - 40); rightArm.setEndY(SPINE_START_Y + 10); Line leftLeg = new Line(); leftLeg.setStartX(SPINE_END_X); leftLeg.setStartY(SPINE_END_Y); leftLeg.setEndX(SPINE_END_X + 25); leftLeg.setEndY(SPINE_END_Y + 50); Line rightLeg = new Line(); rightLeg.setStartX(SPINE_END_X); rightLeg.setStartY(SPINE_END_Y); rightLeg.setEndX(SPINE_END_X - 25); rightLeg.setEndY(SPINE_END_Y + 50); getChildren().addAll(head, spine, leftArm, rightArm, leftLeg, rightLeg); lives.set(getChildren().size()); } public void reset() { getChildren().forEach(node -> node.setVisible(false)); lives.set(getChildren().size()); } public void takeAwayLife() { for (Node n : getChildren()) { if (!n.isVisible()) { n.setVisible(true); lives.set(lives.get() - 1); break; } } } } private static class Letter extends StackPane { private Rectangle bg = new Rectangle(40, 60); private Text text; public Letter(char letter) { bg.setFill(letter == ' ' ? Color.DARKSEAGREEN : Color.WHITE); bg.setStroke(Color.BLUE); text = new Text(String.valueOf(letter).toUpperCase()); text.setFont(DEFAULT_FONT); text.setVisible(false); setAlignment(Pos.CENTER); getChildren().addAll(bg, text); } public void show() { RotateTransition rt = new RotateTransition(Duration.seconds(1), bg); rt.setAxis(Rotate.Y_AXIS); rt.setToAngle(180); rt.setOnFinished(event -> text.setVisible(true)); rt.play(); } public boolean isEqualTo(char other) { return text.getText().equals(String.valueOf(other).toUpperCase()); } } @Override public void start(Stage primaryStage) throws Exception { Scene scene = new Scene(createContent()); scene.setOnKeyPressed((KeyEvent event) -> { if (event.getText().isEmpty()) return; char pressed = event.getText().toUpperCase().charAt(0); if ((pressed < 'A' || pressed > 'Z') && pressed != '-') return; if (playable.get()) { Text t = alphabet.get(pressed); if (t.isStrikethrough()) return; // mark the letter 'used' t.setFill(Color.BLUE); t.setStrikethrough(true); boolean found = false; for (Node n : letters) { Letter letter = (Letter) n; if (letter.isEqualTo(pressed)) { found = true; score.set(score.get() + (int)(scoreModifier * POINTS_PER_LETTER)); lettersToGuess.set(lettersToGuess.get() - 1); letter.show(); } } if (!found) { hangman.takeAwayLife(); scoreModifier = 1.0f; } else { scoreModifier += BONUS_MODIFIER; } } }); primaryStage.setResizable(false); primaryStage.setWidth(APP_W); primaryStage.setHeight(APP_H); primaryStage.setTitle("Hangman"); primaryStage.setScene(scene); primaryStage.show(); startGame(); } public static void main(String[] args) { launch(args); } }