/* * The MIT License (MIT) * * FXGL - JavaFX Game Library * * Copyright (c) 2015-2017 AlmasB (almaslvl@gmail.com) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.almasb.fxglgames.tictactoe; import com.almasb.fxgl.app.ApplicationMode; import com.almasb.fxgl.app.GameApplication; import com.almasb.fxgl.ecs.Entity; import com.almasb.fxgl.settings.GameSettings; import com.almasb.fxglgames.tictactoe.control.enemy.MinimaxControl; import com.almasb.fxglgames.tictactoe.event.AIEvent; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.scene.paint.Color; import javafx.scene.shape.Line; import javafx.util.Duration; import java.util.ArrayList; import java.util.List; /** * An example of a UI based game. * A classic game of Tic-tac-toe. * Comes with 2 types of AI: rule based and minimax based. * * @author Almas Baimagambetov (almaslvl@gmail.com) */ public class TicTacToeApp extends GameApplication { @Override protected void initSettings(GameSettings settings) { settings.setTitle("TicTacToe"); settings.setVersion("0.3"); settings.setWidth(600); settings.setHeight(600); settings.setIntroEnabled(false); settings.setMenuEnabled(false); settings.setProfilingEnabled(false); settings.setCloseConfirmation(false); settings.setApplicationMode(ApplicationMode.DEVELOPER); } @Override protected void preInit() { getEventBus().addEventHandler(AIEvent.MOVED, event -> checkGameFinished()); } private TileEntity[][] board = new TileEntity[3][3]; private List<TileCombo> combos = new ArrayList<>(); private boolean playerStarts = true; public TileEntity[][] getBoard() { return board; } public List<TileCombo> getCombos() { return combos; } @Override protected void initGame() { for (int y = 0; y < 3; y++) { for (int x = 0; x < 3; x++) { TileEntity tile = new TileEntity(x * getWidth() / 3, y * getHeight() / 3); board[x][y] = tile; getGameWorld().addEntity(tile); } } Entity enemy = new Entity(); // this controls the AI behavior enemy.addControl(new MinimaxControl()); getGameWorld().addEntity(enemy); combos.clear(); // horizontal for (int y = 0; y < 3; y++) { combos.add(new TileCombo(board[0][y], board[1][y], board[2][y])); } // vertical for (int x = 0; x < 3; x++) { combos.add(new TileCombo(board[x][0], board[x][1], board[x][2])); } // diagonals combos.add(new TileCombo(board[0][0], board[1][1], board[2][2])); combos.add(new TileCombo(board[2][0], board[1][1], board[0][2])); if (playerStarts) { playerStarts = false; } else { aiMove(); playerStarts = true; } } @Override protected void initPhysics() {} @Override protected void initUI() { Line line1 = new Line(getWidth() / 3, 0, getWidth() / 3, 0); Line line2 = new Line(getWidth() / 3 * 2, 0, getWidth() / 3 * 2, 0); Line line3 = new Line(0, getHeight() / 3, 0, getHeight() / 3); Line line4 = new Line(0, getHeight() / 3 * 2, 0, getHeight() / 3 * 2); getGameScene().addUINodes(line1, line2, line3, line4); // animation KeyFrame frame1 = new KeyFrame(Duration.seconds(0.5), new KeyValue(line1.endYProperty(), getHeight())); KeyFrame frame2 = new KeyFrame(Duration.seconds(1), new KeyValue(line2.endYProperty(), getHeight())); KeyFrame frame3 = new KeyFrame(Duration.seconds(0.5), new KeyValue(line3.endXProperty(), getWidth())); KeyFrame frame4 = new KeyFrame(Duration.seconds(1), new KeyValue(line4.endXProperty(), getWidth())); Timeline timeline = new Timeline(frame1, frame2, frame3, frame4); timeline.play(); } private boolean checkGameFinished() { for (TileCombo combo : combos) { if (combo.isComplete()) { playWinAnimation(combo); return true; } } for (int y = 0; y < 3; y++) { for (int x = 0; x < 3; x++) { TileEntity tile = board[x][y]; if (tile.getValue() == TileValue.NONE) { // at least 1 tile is empty return false; } } } gameOver("DRAW"); return true; } private void playWinAnimation(TileCombo combo) { Line line = new Line(); line.setStartX(combo.getTile1().getCenter().getX()); line.setStartY(combo.getTile1().getCenter().getY()); line.setEndX(combo.getTile1().getCenter().getX()); line.setEndY(combo.getTile1().getCenter().getY()); line.setStroke(Color.YELLOW); line.setStrokeWidth(3); getGameScene().addUINode(line); Timeline timeline = new Timeline(); timeline.getKeyFrames().add(new KeyFrame(Duration.seconds(1), new KeyValue(line.endXProperty(), combo.getTile3().getCenter().getX()), new KeyValue(line.endYProperty(), combo.getTile3().getCenter().getY()))); timeline.setOnFinished(e -> gameOver(combo.getWinSymbol())); timeline.play(); } private void gameOver(String winner) { getDisplay().showConfirmationBox("Winner: " + winner + "\nContinue?", yes -> { if (yes) startNewGame(); else exit(); }); } public void onUserMove(TileEntity tile) { boolean ok = tile.getControl().mark(TileValue.X); if (ok) { boolean over = checkGameFinished(); if (!over) { aiMove(); } } } private void aiMove() { getEventBus().fireEvent(new AIEvent(AIEvent.WAITING)); } public static void main(String[] args) { launch(args); } }