JavaFX Tetris Game

Assignment

Arcade

Please read the entirety of this file before beginning your project.

Project Description

Your goal is to implement a single GUI application in Java using JavaFX 8 that provides an arcade with your own Java+JavaFX implementations of at least two playable games. Here is an example of what your program might look like when it first launches:

Your team must choose one game from each of the following groups:

You have a lot of flexibility with regard to the visuals of your games. As long as the functional requirements are met and the game mechanics are easily recognizable, you are free to make each game look and feel however you want. The general functional requirements for each group are provided later in this document

Part of software development is being given a goal but not necessarily being given instruction on all of the details needed to accomplish that goal. For example, even though working with things like keyoard and mouse-related events haven’t been explicitly covered in class, you are going to need to lookup how to do these things in order to complete this project.

This project is also designed to help you better understand the usefulness of good class design. While you can technically write your entire JavaFX-based GUI application entirely in the start method, this will make your code messy, hard to read, possibly redundant, and likely more prone to errors. Before you write any code, you should plan out your application’s scene graph (i.e., the containment hierarchy), and design custom components as needed. If you find that you are writing a lot of code related to a specific component (e.g., setting styling, adding event handlers, etc.), then it’s probably a good idea to make a custom version of that component in order to reduce clutter. You are strongly encouraged to consider swapping out multiple scenes for this project.

Functional Requirements

  • Main Application Requirements: The main part of your application needs to fulfill the following functional requirements:
    • Game List: Your application should present the user with a visual list of available games. Starting a game should either swap the scene in the current stage or create an application modal stage on which to display the game’s scene graph.
    • Multiple Games per Execution: Your application should allow users to exit one game (without exiting the entire application) and start the same game again (with its state reset) or start the other game.
  • Group 1 Game Requirements: Your Group 1 game implementation needs to fulfill the following functional requirements:
    • UI, Mechanics, and Scoring: The user interface and game mechanics must be easily recognizable and consistent with traditional implementations of the game you chose (see the Wikipedia link for more information). Your game must provide a consistent scoring mechanism and display the score or scores to the user somewhere in the user interface. Furthermore, a game in this category must support multiple levels of difficulty that a user will encounter as they play the game. The first level should be designed so that it is easily attainable by the grader. The current level of difficulty should always be visible to the user somewhere in the user interface.
    • Controls: You are required to provide keyboard controls for a game in this group. If anything is not intuitive, then you need to let the user know before the game starts.
  • Group 2 Game Requirements: Your Group 2 game implementation needs to fulfill the following functional requirements:
    • UI, Mechanics, and Scoring: The user interface and game mechanics must be easily recognizable and consistent with traditional implementations of the game you chose (see the Wikipedia link for more information). Your game must provide a consistent scoring mechanism and display the score or scores to the users somewhere in the user interface.
    • Controls: You are required to provide intuitive mouse controls for a game in this group. If anything is not intuitive, then you need to let the user know before the game starts.
    • Note: You are only required to provide a human player vs. human player mode for a game in this category. Feel free to add support for computer players, however, such support will not contribute to your grade.
  • Extra Credit 1: Add some kind of animated intro to your application. One potential way to accomplish this is by using a separate scene for your intro. This intro should include, in addition to some kind of animated element, the application title (i.e., cs1302-arcade), your team name, and the name of each team member. If you want the grader to check for this requirement, then please make sure it is included in your last update to REFLECTION.md.
  • Extra Credit 2: Add a high score table to your application, accessible via your application’s menu (menu bar or otherwise). The table needs to actually keep track of the high scores for each game and include player initials. This may involve modifications in other areas of your application to accomodate this. In order to receive full credit for this extra credit functional requirement, the high score table must persist over time and over separate executions of your application. If you want the grader to check for this requirement, then please make sure it is included in your last update to REFLECTION.md.

Non-Functional Requirements

Points indicated for non-functional requirements are not added to the grade total if satisfied but are subtracted from the grade total if not satisfied.

  • User-Friendly: Except for reasonable delays resulting from X forwarding, your application should not hang/freeze or crash during execution.
  • Attribution: Proper attribution should be given for all assets (e.g., art, sound, music, etc.) that is not authored by members of your project team. You may consider making an ATTRIBUTIONS.md file that contains this information.
  • Javadoc Documentation: Each method and class needs to be documented using Javadoc comments. If a method overrides an inheritted method that is already documented, then that method only needs a Javadoc comment if the implementation differs from the existing documentation. In such cases, the use of @inheritDoc is encouraged.
  • In-line Documentation: Code blocks should be adequately documented using in-line comments. This is especially necessary when a block of code is not immediately understood by a reader (e.g., the grader).
  • Reflection Updates: Before each deadline mentioned towards the beginning of this document (except the application deadline), you will need to update your project’s REFLECTION.md file to include a new section describing: i) what work has been done; ii) what work do you plan to complete before the next deadline; and what problems, if any, you have encountered related to this project. These changes must be committed and pushed to your team repository before each deadline for them to count.
  • Team Application Agreement Adherance: You must adhere to the agreement in the Pair Programming Team Application you submitted for this project. Deviations will very likely result in this non-functional requirement being unsatisfied.

Setting up Your Local Repository

Each team member will need to perform the following instructions on their Nike account. You will need the following information to get started:

On Nike, execute the following commands to create an empty, local repository for your poject and connect it with the two remote repositories mentioned above. These instructions will place the files into sub-directory called cs1302-arcade within your present working directory:

  1. Create a new local repository:
  1. $ mkdir cs1302-arcade
  2. $ cd cs1302-arcade
  3. $ git init
  1. Add the remote repositories (replace SKELETON_REPO_URL and TEAM_REPO_URL appropriately):
  1. $ git remote add skeleton SKELETON_REPO_URL
  2. $ git remote add team TEAM_REPO_URL
  1. Explicitly pull skeleton code from skeleton/master:
  1. $ git pull skeleton master
  1. NOTE: To set the upstream for your local master branch to team/master so that push, fetch, and pull connect with your team repository by default, make sure that you use -u the first time you call the command (after grabbing the skeleton code). For example:
  1. $ git push -u team master
  1. At this point, you should be good to go! Changes won’t appear on your team’s GitHub repository website.

Getting Updates from Skeleton Project

If any updates to the skeleton project are announced by your instructor, you can fetch those changes into your local repository by changing into your project directory on Nike and issuing the following command:

$ git fetch skeleton master

Once you’re ready to merge the fetched skeleton project changes into your local master branch, you might issue the following commands:

$ git checkout master

$ git merge skeleton/master

Remember, you can comine fetch and merge using pull, if desired:

$ git checkout master

$ git pull skeleton master

package cs1302.arcade;

import java.util.Random;

import javafx.application.Application;

import javafx.application.Platform;

import javafx.scene.Group;

import javafx.scene.Scene;

import javafx.scene.input.KeyCode;

import javafx.scene.shape.Rectangle;

import javafx.stage.Stage;

public class ArcadeApp extends Application {

Random rng = new Random();

@Override

public void start(Stage stage) {

/* You are allowed to rewrite this start method, add other methods,

* files, classes, etc., as needed. This currently contains some

* simple sample code for mouse and keyboard interactions with a node

* (rectangle) in a group.

*/

Group group = new Group();           // main container

Rectangle r = new Rectangle(20, 20); // some rectangle

r.setX(50);                          // 50px in the x direction (right)

r.setY(50);                          // 50ps in the y direction (down)

group.getChildren().add(r);          // add to main container

// when the user clicks on the rectangle, send to random position

r.setOnMouseClicked(event -> {

System.out.println(event);

r.setX(rng.nextDouble() * (640 – r.getWidth()));

r.setY(rng.nextDouble() * (480 – r.getHeight()));

});

// when the user presses left and right, move the rectangle

group.setOnKeyPressed(event -> {

System.out.println(event);

if (event.getCode() == KeyCode.LEFT)  r.setX(r.getX() – 10.0);

if (event.getCode() == KeyCode.RIGHT) r.setX(r.getX() + 10.0);

// TODO bounds checking

});

Scene scene = new Scene(group, 640, 480);

stage.setTitle(“cs1302-arcade!”);

stage.setScene(scene);

stage.sizeToScene();

stage.show();

// the group must request input focus to receive key events

// @see https://docs.oracle.com/javase/8/javafx/api/javafx/scene/Node.html#requestFocus–

group.requestFocus();

} // start

public static void main(String[] args) {

try {

Application.launch(args);

} catch (UnsupportedOperationException e) {

System.out.println(e);

System.err.println(“If this is a DISPLAY problem, then your X server connection”);

System.err.println(“has likely timed out. This can generally be fixed by logging”);

System.err.println(“out and logging back in.”);

System.exit(1);

} // try

} // main

} // ArcadeApp

Solution 

ModelBoard 

Board 

Board.java 

package Model.ModelBoard.Board;

import Model.ModelBoard.Direction;

import Model.ModelBoard.Observers.GravityListener;

import Model.ModelBoard.Pieces.GravityDeomon;

import Model.ModelBoard.Pieces.Identificator;

import Model.ModelBoard.Pieces.Piece;

import Model.ModelBoard.Position.Position;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.FileWriter;

import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Paths;

import java.text.CharacterIterator;

import java.text.StringCharacterIterator;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.concurrent.*;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReadWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

import java.util.stream.Collectors;

public class Board {

private final Map<Position, Piece> collisions;

private ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(2);

private Map<Piece, ScheduledFuture<?>> futures = new HashMap<>();

private Map<Piece, Thread> daemons = new HashMap<>();

private int height;

private int width;

private ReadWriteLock lock = new ReentrantReadWriteLock();

private Lock read =  lock.readLock();

private Lock write =  lock.writeLock();

private GravityListener listener;

private ArrayList<Piece> debug = new ArrayList<>();

public Board(int height, int width) {

this.height = height;

this.width = width;

collisions = new ConcurrentHashMap<>();

}

public void addListener(GravityListener listener){

this.listener = listener;

}

public  Board(Board board) {

collisions = new HashMap<>();

synchronized (collisions){

collisions.putAll(board.collisions);

}

this.height = board.height;

this.width = board.width;

}

public void addPiece(Piece piece){

write.lock();

executor.setRemoveOnCancelPolicy(true);

piece.getPositions().forEach(

position -> collisions.put(position, piece)

);

debug.add(piece);

write.unlock();

}

public void addDaeomon(Piece piece, GravityListener listener){

Thread t = new Thread(new GravityDeomon(this, piece, listener));

t.setDaemon(true);

daemons.put(piece, t);

futures.put(piece, executor.scheduleAtFixedRate(t, 0, 30, TimeUnit.MILLISECONDS));

}

public boolean checkMovement(Direction direction, Piece piece){

read.lock();

if(Thread.currentThread().isInterrupted()) {

read.unlock();

return false;

}

if(!collisions.containsValue(piece)) {

return false;

}

List<Position> toCheck = piece.getPositions().stream()

.map(direction::getNewPosition).collect(Collectors.toList());

boolean ok = true;

for(Position pos : toCheck){

if(checkCollide(pos, piece)) {

ok = false;

break;

}

}

read.unlock();

return ok;

}

private boolean checkCollide(Position pos, Piece piece) {

return !(pos.getX() >= 0 && pos.getY() >= 0 && pos.getX() < height && pos.getY() < width)

|| collisions.containsKey(pos) && !collisions.get(pos).equals(piece);

}

public void movePiece(Direction direction, Piece piece){

write.lock();

if(Thread.currentThread().isInterrupted()){

write.unlock();

return;

}

try{

if(collisions.containsValue(piece)){

/*         if(direction == Direction.DOWN)

{

while(checkMovement(direction, piece)){

piece.getPositions().forEach(collisions::remove);

piece.move(direction);

piece.getPositions().forEach(position -> collisions.put(position, piece));

}

}

else*/ if(checkMovement(direction, piece))

{

piece.getPositions().forEach(collisions::remove);

piece.move(direction);

piece.getPositions().forEach(position -> collisions.put(position, piece));

}

} else {

if (futures.get(piece) != null) {

futures.get(piece).cancel(true);

}

}

} catch (Exception e){

e.printStackTrace();

}

finally {

write.unlock();

}

}

public boolean contains(Piece p){

return collisions.containsValue(p);

}

public boolean isEmptyRow(int i){

read.lock();

for (int j = 0; j < width; j++) {

Position toCheck = new Position(i, j);

if(collisions.containsKey(toCheck)){

return false;

}

}

read.unlock();

return true;

}

private boolean isFullRow(int i){

for (int j = 0; j < width; j++) {

Position toCheck = new Position(i, j);

if(!collisions.containsKey(toCheck)){

return false;

}

}

return true;

}

public int sweep(){

write.lock();

try{

int count = 0;

for (int i = height-1; i >= 0; i–) {

if(isFullRow(i)){

count++;

for (int j = 0; j < width; j++) {

Position toRemove = new Position(i, j);

Piece p = collisions.get(toRemove);

if(futures.get(p) != null) {

futures.remove(p).cancel(true);

}

if( daemons.get(p) != null) {

daemons.remove(p).interrupt();

}

p.removePosition(toRemove);

collisions.remove(toRemove);

}

}

}

if(count > 0){

collisions.values().stream()

.filter(Piece::hasBeenChanged)

.forEach(piece -> {

this.resolveHoles(piece);

if(futures.get(piece) != null)

futures.remove(piece).cancel(true);

if (daemons.get(piece)!= null) {

daemons.remove(piece).interrupt();

}

listener.update(piece);

});

}

futures.entrySet().stream()

.map(Map.Entry::getKey)

.filter(Piece::onlyFalse)

.forEach(piece -> {

futures.get(piece).cancel(true);

executor.remove(daemons.get(piece));

collisions.entrySet().removeIf(entry -> entry.getValue().equals(piece));

listener.onCleanUp(piece);

});

futures.entrySet()

.removeIf(entry -> entry.getKey().onlyFalse());

collisions.entrySet()

.stream()

.map(Map.Entry::getValue)

.forEach(piece -> {

Thread t = new Thread(new GravityDeomon(this, piece, listener));

t.setDaemon(true);

//daemons.put(piece, t);

if(futures.get(piece) == null){

futures.put(piece, executor.scheduleAtFixedRate(t, 1000, 30, TimeUnit.MILLISECONDS));

if(daemons.get(piece) == null)

daemons.put(piece, t);

else

daemons.replace(piece, t);

}

});

write.unlock();

return  count;

} catch (Exception e){

e.printStackTrace();

}

return 0;

}

public void resolveHoles(Piece p){

write.lock();

try{

p.getPositions().forEach(collisions::remove);

p.resolveHoles();

p.getPositions().forEach(pos -> collisions.put(pos, p));

} catch (Exception e){

e.printStackTrace();

} finally {

write.unlock();

}

}

private boolean checkRotation(Piece piece){

read.lock();

List<Position> toCheck = piece.getRotations();

boolean ok = true;

for(Position pos : toCheck){

if(checkCollide(pos, piece)) {

ok = false;

break;

}

}

read.unlock();

return ok;

}

public void rotateClockWise(Piece piece){

write.lock();

try {

if(checkRotation(piece)){

piece.getPositions().forEach(collisions::remove);

piece.rotateClockWise();

piece.getPositions().forEach( position -> collisions.put(position, piece));

}

} catch (Exception e ){

e.printStackTrace();

}

finally {

write.unlock();

}

}

public void onQuit(){

executor.shutdown();

}

public int rowsToSweep() {

int rows = 0;

for (int i = 0; i < height; i++) {

if(isFullRow(i)){

rows++;

}

}

return rows;

}

public boolean isInPiece(Piece p, Position pos){

return collisions.getOrDefault(pos, new Piece(0,0)).equals(p);

}

public boolean isEmpty(Position pos) {

return pos.getX() >= 0 && pos.getY() >= 0 && pos.getX() < height && pos.getY() < width && !collisions.containsKey(pos);

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

sb.append(“{“);

sb.append(“\”height\”: \””);

sb.append(height);

sb.append(“\”, \”width\”: \””);

sb.append(width);

sb.append(“\”, \”collisions\”:[“);

collisions.forEach((key, value) -> {

sb.append(“{“);

sb.append(“\”key\”:”);

sb.append(key.toString());

sb.append(“, \”piece\”:”);

sb.append(value.toString());

sb.append(“}”);

if (collisions.entrySet().iterator().hasNext()) {

sb.append(“,”);

}

});

sb.deleteCharAt(sb.lastIndexOf(“,”));

sb.append(“]”);

sb.append(“}”);

return sb.toString().replaceAll(“\\s+”, “”);

}

private static Board fromJson(String json) {

CharacterIterator iterator = new StringCharacterIterator(json);

//String = height”:….

char c;

//We remove {“height”:

while(iterator.next() != ‘:’);

iterator.next();

StringBuilder sb = new StringBuilder();

while((c = iterator.next()) != ‘\”‘)

sb.append(c);

//sb = height value

int height = Integer.parseInt(sb.toString());

//Remvoe ,”width”:

while(iterator.next() != ‘:’);

iterator.next();

sb = new StringBuilder();

while((c = iterator.next()) != ‘\”‘)

sb.append(c);

//sb = width value

int width = Integer.parseInt(sb.toString());

//We remove “,”collisions”:

while (iterator.next() != ‘[‘);

// iterator.next();

c = iterator.current();

int countSquare = 0;

HashMap<Position, Piece> hash = new HashMap<>();

while( (countSquare != 0 || c != ‘]’) ){

if(c == ‘[‘)

countSquare++;

if(c == ‘]’)

countSquare–;

if(c == ‘]’ && countSquare == 0)

break;

//Removing {“key”:

while(iterator.next() != ‘:’){

//break;

}

// iterator.next();

StringBuilder positionJson = new StringBuilder();

//We extract the json position

while(( c =iterator.next()) != ‘}’){

positionJson.append(c);

}

positionJson.append(c);

Position position = Position.fromJson(positionJson.toString());

//Removing ,”piece”:

while(iterator.next() != ‘:’);

iterator.next();

//Extracting the json piece

int countBrackets = 0;

sb = new StringBuilder();

c= iterator.current();

while(countBrackets != 0 || c != ‘}’){

if(c == ‘{‘)

countBrackets++;

if(c == ‘}’)

countBrackets–;

if(c == ‘}’ &&  countBrackets == 0)

break;

sb.append(c);

c = iterator.next();

}

sb.append(c);

Piece piece = Piece.fromJson(sb.toString());

hash.put(position, piece);

iterator.next();

c = iterator.next();

}

Board board = new Board(height, width);

board.collisions.putAll(hash);

return board;

}

public static Board fromFile(String filename){

try {

BufferedReader br = Files.newBufferedReader(Paths.get(filename));

String jsonBoard = br.readLine();

br.close();

return fromJson(jsonBoard);

} catch (IOException e){

e.printStackTrace();

}

throw new NullPointerException();

}

public void toFile(String filename){

try{

BufferedWriter out = new BufferedWriter(new FileWriter(filename));

out.write(this.toString());

out.close();

} catch (IOException e){

e.printStackTrace();

}

}

public Piece getPieceAt(Position pos){

return collisions.get(pos);

}

public List<Piece> getPieces(){

Identificator identificator = new Identificator();

return collisions.entrySet().stream()

.map(Map.Entry::getValue)

.filter(identificator::add)

.collect(Collectors.toList());

}

public void stop() {

futures.forEach((key, value) -> {

value.cancel(true);

});

executor.shutdown();

}

}

 Observers

 GravityListener.java 

package Model.ModelBoard.Observers;

import Model.ModelBoard.Pieces.Piece;

public interface GravityListener {

void onMovement();

void onChangedNext();

void onSweep();

void onQuit();

void onCleanUp(Piece p);

void update(Piece p);

} 

Pieces 

GravityDeomon.java 

package Model.ModelBoard.Pieces;

import Model.ModelBoard.Board.Board;

import Model.ModelBoard.Direction;

import Model.ModelBoard.Observers.GravityListener;

public class GravityDeomon implements Runnable {

private Board board;

private Piece piece;

private GravityListener listener;

public GravityDeomon(Board board, Piece piece, GravityListener listener) {

this.board = board;

this.piece = piece;

this.listener = listener;

}

@Override

public void run() {

try{

Thread.currentThread().setName(piece.toString());

if(piece.onlyFalse())

{

return;

}

board.movePiece(Direction.DOWN, piece);

listener.update(piece);

//listener.onSweep();

} catch (Exception e){

e.printStackTrace();

}

}

}

Identificator.java 

package Model.ModelBoard.Pieces;

import java.util.ArrayList;

public class Identificator {

ArrayList<Piece> pieces = new ArrayList<>();

public boolean add(Piece p){

if(pieces.stream().filter(p::equals).count() == 0) {

pieces.add(p);

return true;

}

return false;

}

} 

Piece.java 

package Model.ModelBoard.Pieces;

import Model.ModelBoard.Direction;

import Model.ModelBoard.Orientation;

import Model.ModelBoard.Position.Position;

import java.text.CharacterIterator;

import java.text.StringCharacterIterator;

import java.util.ArrayList;

import java.util.List;

import java.util.Objects;

public class Piece{

private Position position;

private int height, width;

private boolean positions[][];

private boolean hasBeenChanged = false;

private Orientation orientation;

public Piece(int height, int width) {

position = new Position(0, 0);

this.height = height;

this.width = width;

positions = new boolean[height][width];

for (int i = 0; i < height; i++) {

for (int j = 0; j < width; j++) {

positions[i][j] = false;

}

}

}

public Piece(String[][] scheme, Position startingPosition, Orientation orientation){

this(scheme.length, scheme[0].length);

for (int i = 0; i < height; i++) {

for (int j = 0; j < width; j++) {

positions[i][j] = scheme[i][j].equals(“1”);

}

}

this.orientation = orientation;

position = startingPosition;

}

public Piece(Piece b){

this.position = b.position;

height = b.height;

width = b.width;

positions = new boolean[height][width];

for (int i = 0; i < height; i++) {

System.arraycopy(b.positions[i], 0, positions[i], 0, width);

}

this.orientation = b.getOrientation();

}    public void removePosition(Position pos){

positions[pos.getX() – position.getX()][pos.getY() – position.getY()] = false;

if(!onlyFalse())

setHasChanged(true);

}

private boolean[][] rotate(){

int M = positions.length;

int N = positions[0].length;

boolean rotated[][] = new boolean[N][M];

for (int i = 0; i < M; i++) {

for (int j = 0; j < N; j++) {

rotated[j][M-1-i] = positions[i][j];

}

}

return rotated;

}

public void rotateClockWise(){

positions = rotate();

height = positions.length;

width = positions[0].length;

}

public void move(Direction d){

position = d.getNewPosition(position);

}

public List<Position> getPositions(){

List<Position> positionsList = new ArrayList<>();

for (int i = 0; i < height; i++) {

for (int j = 0; j < width; j++) {

if(positions[i][j])

positionsList.add(new Position(position.getX()+i, position.getY()+j));

}

}

return positionsList;

}

public List<Position> getRotations(){

List<Position> rotationList = new ArrayList<>();

boolean[][] rotated = rotate();

for (int i = 0; i < rotated.length; i++) {

for (int j = 0; j < rotated[0].length; j++) {

if(rotated[i][j])

rotationList.add(new Position(position.getX()+i, position.getY()+j));

}

}

return rotationList;

}

public void resolveHoles() {

setHasChanged(false);

for (int i = 0; i < height; i++) {

applyGravity();

}

}

private void applyGravity(){

for (int r = height-2; r >= 0; r–) {

for (int c = 0; c < width; c++) {

if(positions

[r]

[c]

&& !positions

[r+1]

[c]

){

positions

[r]

[c]

= false;

positions

[r+1]

[c]

= true;

}

}

}

}

public boolean onlyFalse(){

for (int i = 0; i < height; i++) {

for (int j = 0; j < width; j++) {

if(positions[i][j])

return false;

}

}

return true;

}

public void setPosition(Position position){

this.position = position;

}

public boolean hasBeenChanged(){

return hasBeenChanged;

}

private void setHasChanged(boolean changed){

hasBeenChanged = changed;

}

public Orientation getOrientation(){

return orientation;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

sb.append(“{“);

sb.append(“\””);

sb.append(“position\”:”);

sb.append(position.toString());

sb.append(“, “);

sb.append(“\”height\”: \””);

sb.append(height);

sb.append(“\”, “);

sb.append(“\”width\”: \””);

sb.append(width);

sb.append(“\”, “);

sb.append(“\”positions\”: [“);

for (int i = 0; i < height; i++) {

sb.append(“[“);

for (int j = 0; j < width; j++) {

sb.append(“{“);

sb.append(“\”position\”:\””);

sb.append(positions[i][j]);

sb.append(“\”}”);

if(j + 1 < width)

sb.append(“,”);

}

sb.append(“]”);

if(i + 1 < height)

sb.append(“, “);

}

sb.append(“]”);

sb.append(“,\”orientation\”: \””);

sb.append(orientation);

sb.append(“\””);

sb.append(“}”);

return sb.toString().replaceAll(“\\s+”, “”);

}

@Override

public boolean equals(Object obj) {

if(obj == this) {

return true;

}

if(obj == null)

return this == null;

if(this == null)

return false;

if(obj instanceof Piece){

Piece other = (Piece) obj;

if (this.height == other.height) {

if(this.width == other.width){

boolean ok = true;

for (int i = 0; i < height; i++) {

for (int j = 0; j < width; j++) {

if (positions[i][j] != positions[i][j])

ok = false;

}

}

return (super.equals(obj) || (ok && position.equals(other.position) && orientation.equals(other.orientation)));

}

}

}

return super.equals(obj);

}

/*@Override

public int hashCode() {

int hash = position.hashCode();

hash += Integer.hashCode(height);

hash += Integer.hashCode(width);

hash += orientation.hashCode();

return  Objects.hash();

// return Integer.hashCode(hash);

}*/

public static Piece fromJson(String json){

CharacterIterator iterator = new StringCharacterIterator(json);

char c;

//Removing {“position”

while(iterator.next() != ‘:’);

StringBuilder positionJson = new StringBuilder();

//We extract the json position

while(( c =iterator.next()) != ‘}’){

positionJson.append(c);

}

positionJson.append(c);

Position position = Position.fromJson(positionJson.toString());

//Removing ,”height”

while(iterator.next() != ‘:’);

StringBuilder sb = new StringBuilder();

//Removing :

iterator.next();

while ( (c = iterator.next()) != ‘\”‘){

sb.append(c);

}

int height = Integer.parseInt(sb.toString());

//Removing “,”width”

while(iterator.next() != ‘:’);

iterator.next();

sb = new StringBuilder();

while ( (c = iterator.next()) != ‘\”‘){

sb.append(c);

}

int width = Integer.parseInt(sb.toString());

//Removing “,”position”:

while(iterator.next() != ‘[‘);

//Removing [

iterator.next();

boolean[][] positions = new boolean[height][width];

//TO REPEAT

for (int i = 0; i < height; i++) {

//Removing [

//iterator.next();

for (int j = 0; j < width; j++) {

while(iterator.next() != ‘:’);

iterator.next();

sb = new StringBuilder();

while((c = iterator.next() )!= ‘\”‘){

sb.append(c);

}

switch (sb.toString()){

case “true”:

positions[i][j] = true;

break;

case “false”:

positions[i][j] = false;

break;

default:

break;

}

}

}

while(iterator.next() != ‘:’);

iterator.next();

sb = new StringBuilder();

while((c = iterator.next()) != ‘\”‘){

sb.append(c);

}

Orientation orientation;

switch (sb.toString()){

case “HORIZONTAL”:

orientation= Orientation.HORIZONTAL;

break;

case “VERTICAL”:

orientation = Orientation.VERTICAL;

break;

default:

orientation = Orientation.HORIZONTAL;

break;

}

Piece piece = new Piece(height, width);

piece.positions = positions;

piece.position = position;

piece.orientation = orientation;

return piece;

}

}

 Position

 Position.java

 package Model.ModelBoard.Position;

import java.io.Serializable;

import java.text.CharacterIterator;

import java.text.StringCharacterIterator;

public class Position implements Serializable{

private int x;

private int y;

public Position(int x, int y) {

setX(x);

setY(y);

}

public int getX() {

return x;

}

private void setX(int x) {

this.x = x;

}

public int getY() {

return y;

}

private void setY(int y) {

this.y = y;

}

@Override

public int hashCode() {

int result = x;

result = 31 * result + y;

return result;

}

@Override

public boolean equals(Object obj) {

return obj != null && (obj == this || obj instanceof Position && this.equals((Position) obj));

}

@Override

public String toString() {

String position = “{ \”x\”: \”” + x +”\”, \”y\”: \”” + y +”\”}”;

return position.replaceAll(“\\s+”, “”);

}

public static Position fromJson(String json){

CharacterIterator iterator = new StringCharacterIterator(json);

iterator.next();

char c;

//we remove {“x”

while(iterator.next() != ‘:’);

iterator.next();

c = iterator.next();

//c = x value

StringBuilder sb = new StringBuilder();

while(c != ‘\”‘){

sb.append(c);

c = iterator.next();

}

//sb contain integer value of x

int x = Integer.parseInt(sb.toString());

//We remove “,”y”

while(iterator.next() != ‘:’);

iterator.next();

c = iterator.next();

//c = y value

sb = new StringBuilder();

while(c != ‘\”‘){

sb.append(c);

c = iterator.next();

}

int y = Integer.parseInt(sb.toString());

return new Position(x, y);

}

private boolean equals(Position pos) {

return (pos.x == this.x && pos.y == this.y);

}

}

 Direction.java

 package Model.ModelBoard;

import Model.ModelBoard.Position.Position;

public enum Direction {

/**

* Direction will help indicate a futur position

* They each refer to a certain position on an orthonormal grid.

* We will set the origin (0, 0) on the top left corner.

*/

UP {

@Override

public Position getNewPosition(Position pos) {

return new Position(pos.getX() – 1, pos.getY());

}

}, /** refer to minus one in the X axes */

LEFT {

@Override

public Position getNewPosition(Position pos) {

return new Position(pos.getX(), pos.getY() – 1);

}

}, /** refer to minus one in the Y axes */

DOWN {

@Override

public Position getNewPosition(Position pos) {

return new Position(pos.getX() + 1, pos.getY());

}

}, /** refer to plus one ine the X axes */

RIGHT {

@Override

public Position getNewPosition(Position pos) {

return new Position(pos.getX(), pos.getY() + 1);

}

}; /** refer to plus one in the Y axes*/

public abstract Position getNewPosition(Position pos);

}

 Orientation.java 

package Model.ModelBoard;

public enum  Orientation {

HORIZONTAL,

VERTICAL

}

 ModelTetris

  Tetris.java

 package Model.ModelTetris;

import Model.ModelBoard.Board.Board;

import Model.ModelBoard.Direction;

import Model.ModelBoard.Observers.GravityListener;

import Model.ModelBoard.Pieces.Piece;

import Model.ModelBoard.Position.Position;

import javafx.scene.control.Alert;

import javafx.scene.control.ButtonType;

import javafx.scene.control.Alert.AlertType;

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

import java.util.Optional;

import java.util.Random;

import java.util.concurrent.Executor;

public class Tetris {

private Board board;

private Piece current;

private Piece next;

public static int height = 18;

public static int width = 10;

private boolean finished;

private int score;

private List<Integer> pieces;

private static Position position = new Position(0, 4);

private List<GravityListener> movementListeners;

public Tetris() {

board = new Board(height, width);

pieces = new ArrayList<>(7);

movementListeners = new ArrayList<>();

current = randomBlock();

current.setPosition(position);

board.addPiece(current);

next = randomBlock();

//BlockFactory.get(TetrisBlocks.Straight);

// board.addPiece(current);

}

public Tetris(Tetris t){

this.board = new Board(t.board);

this.pieces = new ArrayList<>(7);

pieces.addAll(t.pieces);

this.current = new Piece(t.current);

this.next = new Piece(t.next);

this.finished = t.finished;

this.score = t.score;

movementListeners = new ArrayList<>();

this.board.addPiece(current);

}

public void move(Direction d){

board.movePiece(d, current);

movementListeners.forEach(GravityListener::onMovement);

}

@Override

public String toString() {

return board.toString();

}

public void applyGravity(){

if(board.checkMovement(Direction.DOWN, current)){

move(Direction.DOWN);

return;

}

movementListeners.forEach(GravityListener::onMovement);

score(board.sweep());

movementListeners.forEach(GravityListener::onSweep);

if(!this.isFinished()){

swapCurrent();

} else {

quit();

movementListeners.forEach(GravityListener::onQuit);

}

}

private void score(int i) {

//score += (Math.exp(i)*5);

score += i;

}

private void addToBoard(){

board.addPiece(current);

}

public int getScore(){

return score;

}

public Piece getNext(){

return next;

}

public void quit(){

stop();

board.onQuit();

}

private Piece randomBlock(){

if(pieces.size() == 0){

for (int i = 0; i < 7; i++) {

pieces.add(i);

}

Collections.shuffle(pieces);

}

int value = pieces.get(0);

pieces.remove(0);

return TetrisPieceFactory.get(TetrisBlocks.values()[value]);

}

public void rotate(){

board.rotateClockWise(current);

movementListeners.forEach(GravityListener::onMovement);

}

private void randomRotate(Piece piece){

Random rd = new Random();

int numberOfRotation = rd.nextInt(4);

for (int i = 0; i < numberOfRotation; i++) {

board.rotateClockWise(piece);

}

}

private boolean isFinished() {

return (!board.isEmptyRow(0) || !board.isEmptyRow(1));

}

public Piece getCurrent(){

return current;

}

public int sumHeight() {

int max = 0;

for (int i = 0; i < width; i++) {

max += height(i);

}

return max;

}

public int rowsToSweep(){

return board.rowsToSweep();

}

public int holes() {

int holes = 0;

boolean atLeastOne;

for (int i = 0; i < height; i++) {

atLeastOne = false;

for (int j = 0; j < width; j++) {

Position tmp = new Position(i, j);

if(!board.isEmpty(tmp)) {

atLeastOne = true;

} else if (board.isEmpty(tmp) && atLeastOne){

holes++;

}

}

}

return holes;

}

private int height(int j){

int heightC = 0;

for (int i = height-1; i >= 0; i–) {

Position tmp = new Position(i, j);

if(board.isInPiece(current, tmp)){

if(!board.isEmpty(tmp))

heightC = height – i; //We count the height only if this is not the current block

//Height – i is to calculate the height as 0 is on top and height is on the bottom

}

}

return heightC;

}

public int bumpiness() {

int bumpiness = 0;

for (int i = 0; i < width – 1; i++) {

bumpiness += Math.abs(height(i) – height(i+1));

}

return bumpiness;

}

public void addGravityListener(GravityListener listener){

movementListeners.add(listener);

board.addListener(listener);

}

private void swapCurrent(){

board.addDaeomon(current, movementListeners.get(0));

movementListeners.forEach(gravityListener -> gravityListener.update(getCurrent()));

current = next;

current.setPosition(position);

next = randomBlock();

addToBoard();

randomRotate(current);

movementListeners.forEach(GravityListener::onChangedNext);

move(Direction.DOWN);

move(Direction.DOWN);

}

public void stop() {

board.stop();

}

}

 TetrisBlocks.java

 package Model.ModelTetris;

/**

* Created by Irindul on 10/02/2017.

* List of every available tetris pieces

*/

public enum TetrisBlocks {

Straight,

RightL,

LeftL,

TwoByTwo,

RightZ,

LeftZ,

ThreeOne

}

TetrisPieceFactory.java

 package Model.ModelTetris;

import Model.ModelBoard.Orientation;

import Model.ModelBoard.Pieces.Piece;

import Model.ModelBoard.Position.Position;

class TetrisPieceFactory {

private static Position startingPosition = new Position(0, 0);

private static Orientation orientation = Orientation.HORIZONTAL;

static Piece get(TetrisBlocks t){

switch (t){

case Straight:

return getStraight();

case RightL:

return getRightL();

case LeftL:

return getLeftL();

case TwoByTwo:

return getTwoByTwo();

case RightZ:

return getRightZ();

case LeftZ:

return getLeftZ();

case ThreeOne:

return getThreeOne();

default:

return null;

}

}

private static Piece getStraight(){

String[][] scheme = {

{“1”},

{“1”},

{“1”},

{“1”}

};

return new Piece(scheme, startingPosition, orientation);

}

private static Piece getLeftL(){

String[][] scheme = {

{“0”, “1”},

{“0”, “1”},

{“1”, “1”},

};

return new Piece(scheme, startingPosition, orientation);

}

private static Piece getRightL(){

String[][] scheme = {

{“1”, “0”},

{“1”, “0”},

{“1”, “1”},

};

return new Piece(scheme, startingPosition, orientation);

}

private static Piece getTwoByTwo(){

String[][] scheme = {

{“1”, “1”},

{“1”, “1”}

};

return new Piece(scheme, startingPosition, orientation);

}

private static Piece getRightZ(){

String[][] scheme = {

{“1”, “0”},

{“1”, “1”},

{“0”, “1”}

};

//blockAggregate.setOrigin(new Origin(block2, new Position(0, 0)));

return new Piece(scheme, startingPosition, orientation);

}

private static Piece getLeftZ(){

String[][] scheme = {

{“0”, “1”},

{“1”, “1”},

{“1”, “0”}

};

//blockAggregate.setOrigin(new Origin(block2, new Position(0, 0)));

return new Piece(scheme, startingPosition, orientation);

}

private static Piece getThreeOne(){

String[][] scheme = {

{“0”, “1”, “0”},

{“1”, “1”, “1”},

};

//blockAggregate.setOrigin(new Origin(block2, new Position(0, 0)));

 

return new Piece(scheme, startingPosition, orientation);

}

}  

Player

   ArtificialIntelligence.java

 package Model.ModelTetris.Player;

import Model.ModelBoard.Direction;

import Model.ModelBoard.Observers.GravityListener;

import Model.ModelBoard.Pieces.Piece;

import Model.ModelTetris.Tetris;

/**

* Created by Irindul on 18/02/2017.

* Contains the artificial intelligence for the Tetris game

*/

public class ArtificialIntelligence implements GravityListener, Runnable{

private Tetris tetris;

private Evaluator evaluator;

private int score;

private boolean hasChanged;

private boolean go = true;

public ArtificialIntelligence(Tetris tetris, Evaluator evaluator) {

this.tetris = tetris;

tetris.addGravityListener(this);

this.evaluator = evaluator;

score = 0;

hasChanged = true;

}

public void executeNextMove(){

if(hasChanged){

hasChanged = false;

Pair<Pair<Integer, Integer>, Double> moves = computeEveryMove();

int rotation = moves.getFirst().getFirst();

for (int i = 0; i < rotation; i++) {

tetris.rotate();

}

for (int i = 0; i < Tetris.width; i++) {

tetris.move(Direction.LEFT);

}

int rights = moves.getFirst().getSecond();

for (int i = 0; i < rights; i++) {

tetris.move(Direction.RIGHT);

}

}

// tetris.move(Direction.DOWN);

}

public void setHasChanged() {

this.hasChanged = true;

}

private Pair<Pair<Integer, Integer>, Double> computeEveryMove(){

Tetris startingGrid = new Tetris(tetris); // We copy the grid so we don’t affect it

Pair<Pair<Integer, Integer>, Double> best = new Pair<>();

//THe first integer will sotre the number of rotation needed to be on the position.

//The second integer will store the number of RIGHT movement needed to achieve the posssible grid position;

double bestScore = 0;

for (int k = 0; k < 4; k++) {

Tetris rotation = new Tetris(startingGrid);

for (int i = 0; i < k; i++) {

rotation.rotate();

}

for (int i = 0; i < Tetris.width; i++) {

rotation.move(Direction.LEFT);

}

int iterations = Tetris.width; //- current.getMaximumY();

for (int i = 0; i < iterations; i++) {

Tetris possible = new Tetris(rotation);

for (int j = 0; j < i; j++) { //We shift one more at every possibility

possible.move(Direction.RIGHT);

}

//We place the piece on the bottom

for (int j = 0; j < Tetris.height; j++) {

possible.move(Direction.DOWN);

}

// possible.addToBoard();

double score;

score = evaluator.evaluate(possible);

if(score > bestScore || bestScore == 0){

bestScore = score;

best.setSecond(score);

Pair<Integer, Integer> move = new Pair<>();

move.setFirst(k);

move.setSecond(i);

best.setFirst(move);

}

}

}

return best;

}

int getScore() {

return score;

}

@Override

public void run() {

while(go && tetris.getScore() < 2001 && !Thread.currentThread().isInterrupted()){

executeNextMove();

tetris.applyGravity();

}

tetris.stop();

score = tetris.getScore();

}

void reset(){

tetris = new Tetris();

tetris.addGravityListener(this);

}

Evaluator getEvaluator() {

return evaluator;

}

@Override

public void onMovement() {

}

@Override

public void onChangedNext() {

hasChanged = true;

}

@Override

public void onSweep() {

}

@Override

public void onQuit() {

go = false;

}

@Override

public void onCleanUp(Piece p) {

}

@Override

public void update(Piece p) {

}

}

  Evaluator.java

 package Model.ModelTetris.Player;

import Model.ModelTetris.Tetris;

import java.util.Random;

/**

* Created by Irindul on 20/02/2017.

* Evaluates a given tetris grid

*/

public class Evaluator {

private double a;

private double b;

private double c;

private double d;

private double score;

public Evaluator(double a, double b, double c, double d) {

this.a = a;

this.b = b;

this.c = c;

this.d = d;

score = 0;

}

private Evaluator(Evaluator other){

this(other.a, other.b, other.c, other.d);

}

double evaluate(Tetris t){

return a*t.sumHeight() + b* t.rowsToSweep() + c*t.holes() + d*t.bumpiness();

}

static Evaluator getRandomEvaluator(){

double min = -1;

double max = 1;

Random rd = new Random();

double a = min + (rd.nextDouble() * (max – min));

double b = min + (rd.nextDouble() * (max – min));

double c = min + (rd.nextDouble() * (max – min));

double d = min + (rd.nextDouble() * (max – min));

return new Evaluator(a, b, c, d);

}

private void operatorTime(double score){

a *= score;

b *= score;

c *= score;

d *= score;

}

private void operatorAdd(Evaluator other){

this.a += other.a;

this.b += other.b;

this.c += other.c;

this.d += other.d;

}

void normalization(){

double norme = norme();

a /= norme;

b /= norme;

c /= norme;

d /= norme;

}

private double norme(){

return Math.sqrt( a*a  + b*b + c*c + d*d );

}

void mutation(){

Random rd = new Random();

int component = rd.nextInt(4);

double adj = -0.2 + (rd.nextDouble() * (0.4));

switch (component){

case 0:

a += adj;

break;

case 1:

b += adj;

break;

case 2:

c += adj;

break;

case 3:

d += adj;

break;

default:

break;

}

}

void display(){

System.out.println(a +”, ” + b + “, ” + c + “, ” + d);

}

static Evaluator crossover(Evaluator p1, Evaluator p2){

Evaluator child = new Evaluator(p1);

Evaluator child2 = new Evaluator(p2);

child.operatorTime(p1.score + 1);

child2.operatorTime(p2.score + 1);

child.operatorAdd(child2);

//child.operatorTime(1/(p1.score + p2.score));

return child;

}

}

GeneticAlgorithm.java

 package Model.ModelTetris.Player;

import Model.ModelTetris.Tetris;

import java.util.ArrayList;

import java.util.Comparator;

import java.util.List;

import java.util.Random;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

* Created by Irindul on 21/02/2017.

* Genetic algorithm to find a proper evaluator

*/

public class GeneticAlgorithm implements Runnable{

private List<ArtificialIntelligence> specimens;

public GeneticAlgorithm() {

specimens = new ArrayList<>(100);

for (int i = 0; i < 100; i++) {

specimens.add(new ArtificialIntelligence(new Tetris(), Evaluator.getRandomEvaluator()));

}

}

private Evaluator run(int success){

List<ArtificialIntelligence> offsprings;

int nbGen = 0;

while(!overSuccess(success)){

specimens.forEach(ArtificialIntelligence::reset);

System.out.println(“Generation : ” + nbGen++);

ExecutorService execute = Executors.newFixedThreadPool(100);

specimens.forEach(execute::execute);

execute.shutdown();

while(!execute.isTerminated()){

//Waiting for all threads to terminate

}

System.out.println(“Threads ternimated”);

offsprings = selectionCrossover();

offsprings.forEach(player -> {

player.getEvaluator().normalization();

Random rd = new Random();

int mutate = rd.nextInt(100);

if(mutate < getPourcentageRate()){

player.getEvaluator().mutation();

}

});

specimens.sort(Comparator.comparingDouble(ArtificialIntelligence::getScore));

// specimens.sort((o1, o2) -> o1.getScore() > o2.getScore() ? 1 : -1);

int numberDeletion = offsprings.size();

for (int i = 0; i < numberDeletion; i++) {

specimens.remove(i);

}

specimens.addAll(offsprings);

}

specimens.sort((o1, o2) -> o1.getScore() < o2.getScore() ? 1 : -1);

return specimens.get(0).getEvaluator();

}

private double  getPourcentageRate(){

/*double square = score * score;

double limit = 0.05;

return ((1/(square + 1)) + limit) * 100;*/

return 5;

}

private List<ArtificialIntelligence> selectionCrossover() {

List<ArtificialIntelligence> offsprings = new ArrayList<>();

List<ArtificialIntelligence> selected = new ArrayList<>();

int numberOfCrossover = (int) ((double) 30/100 * specimens.size());

int numberSelection = (int) ( (double) 10/100 * specimens.size());

Random rd = new Random();

while (offsprings.size() < numberOfCrossover){

while(selected.size() < numberSelection){

int ind = rd.nextInt(specimens.size());

selected.add(specimens.get(ind));

}

selected.sort((o1, o2) -> o1.getScore() < o2.getScore() ? 1 : -1);

ArtificialIntelligence parent = selected.get(0);

ArtificialIntelligence parent2 = selected.get(1);

Evaluator evChild = Evaluator.crossover(parent.getEvaluator(), parent2.getEvaluator());

offsprings.add(new ArtificialIntelligence(new Tetris(), evChild));

}

return offsprings;

}

private boolean overSuccess(int success) {

int max = specimens.stream()

.max((o1, o2) -> o1.getScore() > o2.getScore() ? 1 : -1)

.get()

.getScore();

return max >= success;

}

@Override

public void run() {

this.run(2000).display();

}

} 

Pair.java 

package Model.ModelTetris.Player;

/**

* Created by Irindul on 22/02/2017.

* Custom pair class with generics

*/

class Pair<T, K> {

private T first;

private K second;

void setFirst(T first){

this.first = first;

}

void setSecond(K second) {

this.second = second;

}

T getFirst() {

return first;

}

K getSecond() {

return second;

}

} 

View 

IMenu.java

package View;

import javafx.scene.Scene;

public interface IMenu {

void reset(Scene s);

void launchAI(Scene s);

void goBackToMenu();

} 

Menu.java 

package View;

import javafx.application.Application;

import javafx.application.Platform;

import javafx.geometry.Rectangle2D;

import javafx.scene.Parent;

import javafx.scene.Scene;

import javafx.scene.control.Button;

import javafx.scene.control.Label;

import javafx.scene.input.KeyCode;

import javafx.scene.layout.Pane;

import javafx.scene.text.Text;

import javafx.stage.Screen;

import javafx.stage.Stage;

import javafx.stage.WindowEvent;

public class Menu extends Application implements IMenu {

private final static int WIDTH = 300;

private final static int HEIGHT = 300;

private Stage stage;

@Override

public void start(Stage primaryStage) throws Exception {

Scene scene = new Scene(createContent());

//createHandlers(scene);

primaryStage.setOnCloseRequest(t -> {

Platform.exit();

System.exit(0);

});

scene.getStylesheets().addAll(this.getClass().getResource(“menu.css”).toExternalForm());

primaryStage.setTitle(“Tetris Game”);

primaryStage.setResizable(false);

primaryStage.setScene(scene);

primaryStage.show();

stage = primaryStage;

createHandlers();

}

private void createHandlers(){

stage.getScene().setOnKeyPressed(event -> {

if(event.getCode() == KeyCode.ESCAPE){

stage.fireEvent(

new WindowEvent(

stage,

WindowEvent.WINDOW_CLOSE_REQUEST

)

);

}

});

}

private Parent createContent() {

Pane root = new Pane();

root.setPrefSize(WIDTH, HEIGHT);

root.getStyleClass().add(“menu”);

Button button = new Button(“Start”);

button.relocate(75, 10);

button.setOnAction(event -> launchTetris());

Text controlT = new Text(“Move Right: Right Arrow\n Move Left: Left Arrow\n Rotate : Up Arrow\n Drop : Down Arrow”);

controlT.relocate(15, 140);

root.getChildren().addAll(button , controlT);

root.setStyle(“-fx-font-weight: bold;-fx-font-size: 24px;”);

controlT.getStyleClass().add(“Controls”);

return root;

}

private void center() {

Rectangle2D screenBounds = Screen.getPrimary().getVisualBounds();

stage.setX((screenBounds.getWidth() – stage.getWidth()) / 2);

stage.setY((screenBounds.getHeight() – stage.getHeight()) / 2);

}

private void launchTetris() {

TetrisGame tetrisGame = new TetrisGame(this);

this.stage.setScene(tetrisGame.start());

center();

}

@Override

public void reset(Scene s) {

stage.setScene(s);

}

@Override

public void launchAI(Scene s) {

stage.setScene(s);

}

@Override

public void goBackToMenu() {

this.stage.setScene(new Scene(createContent()));

createHandlers();

stage.getScene().getStylesheets().add(“style/menu.css”);

}

} 

TetrisGame.java 

package View;

import Model.ModelBoard.Direction;

import Model.ModelBoard.Observers.GravityListener;

import Model.ModelBoard.Pieces.Piece;

import Model.ModelBoard.Position.Position;

import Model.ModelTetris.Player.ArtificialIntelligence;

import Model.ModelTetris.Player.Evaluator;

import Model.ModelTetris.Tetris;

import View.ViewBoard.BoardView;

import View.ViewBoard.PieceView;

import javafx.animation.AnimationTimer;

import javafx.application.Platform;

import javafx.scene.Group;

import javafx.scene.Parent;

import javafx.scene.Scene;

import javafx.scene.canvas.GraphicsContext;

import javafx.scene.control.Alert;

import javafx.scene.control.Alert.AlertType;

import javafx.scene.control.Button;

import javafx.scene.control.ButtonType;

import javafx.scene.control.Label;

import javafx.scene.input.KeyCode;

import javafx.scene.layout.Pane;

import javafx.scene.paint.Color;

import javafx.stage.Stage;

import java.util.Comparator;

import java.util.Optional;

import java.util.Random;

public class TetrisGame implements GravityListener{

private static final int TILE_SIZE = 40;

private static final int WIDTH = 10 * TILE_SIZE;

private static final int HEIGHT = 16 * TILE_SIZE;

private static final int SCORE_WIDTH = 10 * TILE_SIZE;

private static final int NEXT_WIDTH = 5 * TILE_SIZE;

private static final int NEXT_HEIGHT = 6 * TILE_SIZE;

private boolean go;

private Tetris tetris;

private Group nextGroup;

private BoardView boardView;

private PieceView next;

private Scene scene;

private double time;

private  AnimationTimer timer;

private Label score;

private  boolean artificialPlayer;

private boolean pause;

private ArtificialIntelligence artificialIntelligence;

private Button Easy,Medium, Difficult;

private static double timerSpeed = 0.017;

private IMenu menu;

TetrisGame(IMenu menu) {

this.menu = menu;

}

//  @Override

Scene start() {

Scene scene = new Scene(createContent());

scene.getStylesheets().addAll(this.getClass().getResource(“tetris.css”).toExternalForm());

createHandlers(scene);

this.scene = scene;

return scene;

}

private void createHandlers(Scene scene){

scene.setOnKeyPressed(e -> {

if(e.getCode() == KeyCode.ESCAPE){

stopGame();

menu.goBackToMenu();

}

if(e.getCode() == KeyCode.ENTER){

// e.consume();

pause();

}

if(e.getCode() == KeyCode.LEFT){

tetris.move(Direction.LEFT);

}

if(e.getCode() == KeyCode.RIGHT){

tetris.move(Direction.RIGHT);

}

if(e.getCode() == KeyCode.DOWN){

tetris.move(Direction.DOWN);

}

if(e.getCode() == KeyCode.UP){

tetris.rotate();

}

// render();

});

}

private void pause() {

if(pause){

timer.start();

pause = false;

} else {

timer.stop();

pause = true;

}

}

private Parent createContent() {

//Creating the different panes

Pane root = new Pane();

root.setPrefSize((WIDTH + SCORE_WIDTH), (HEIGHT));

Pane  game = new Pane();

game.setPrefSize(WIDTH, HEIGHT);

// game.getStyleClass().add(“gamePane”);

game.getStyleClass().add(“dark”);

Pane border = new Pane();

border.setPrefSize(WIDTH, HEIGHT);

border.getStyleClass().add(“gamePane”);

Pane menu = new Pane();

menu.setPrefSize(SCORE_WIDTH, HEIGHT);

menu.relocate(WIDTH, 0);

menu.getStyleClass().add(“dark”);

Pane nextPiece = new Pane();

nextPiece.setPrefSize(NEXT_WIDTH, NEXT_HEIGHT – 40);

nextPiece.relocate((SCORE_WIDTH – NEXT_WIDTH)/2 , 5);

nextPiece.getStyleClass().add(“nextPiecePane”);

nextGroup = new Group();

//Creating buttons

Easy = new Button(“Easy”);

Easy.setMinHeight(100);

Easy.setMinWidth(NEXT_WIDTH + 30);

Easy.setTranslateX((SCORE_WIDTH – NEXT_WIDTH) / 2 + 30);

Easy.setTranslateY(230);

Medium = new Button(“Medium”);

Medium.setMinHeight(100);

Medium.setMinWidth(NEXT_WIDTH + 30);

Medium.setTranslateX((SCORE_WIDTH – NEXT_WIDTH) / 2+ 30);

Medium.setTranslateY(290);

Difficult = new Button(“Difficult”);

Difficult.setMinHeight(100);

Difficult.setMinWidth(NEXT_WIDTH);

Difficult.setTranslateX((SCORE_WIDTH – NEXT_WIDTH) / 2+ 30);

Difficult.setTranslateY(350);

score = new Label();

score.relocate(SCORE_WIDTH / 2 – 35, 450);

boardView = new BoardView(“block”);

Group board = boardView.getGroup();

//  board.getStyleClass().add(“gamePane”);

//board.maxHeight(HEIGHT-15);

//Adding every node to its root

nextPiece.getChildren().add(nextGroup);

game.getChildren().addAll(board, border);

menu.getChildren().addAll(Easy,Medium, Difficult, score);

menu.getChildren().add(nextPiece);

root.getChildren().add(game);

root.getChildren().add(menu);

/*tetrominos.addAll(

tetris.getBlocks().stream() //List to stream

//.Association to a new tetromino

.map(blockAggregate -> new Tetromino(getRandomColor(), blockAggregate))

//Returning a list

.collect(Collectors.toList())

); */

go = true;

tetris = new Tetris();

boardView.addPiece(tetris.getCurrent(), getRandomColor(), TILE_SIZE, 2);

tetris.addGravityListener(this);

next = new PieceView(getRandomColor(), tetris.getNext(), TILE_SIZE, 0);

next.getSquare().forEach(rectangle -> rectangle.getStyleClass().add(“block”));

drawNext();

timer = new AnimationTimer() {

@Override

public void handle(long now) {

time += timerSpeed;

if(time >= 0.5 && go){

if(artificialPlayer){

artificialIntelligence.executeNextMove();

}

update();

time = 0;

}

}

};

Easy.setOnAction(event -> setLevelEasy());

Medium.setOnAction(event -> setLevelMedium());

Difficult.setOnAction(event -> setLevelDifficult());

pause = false;

timer.start();

return root;

}

private void setLevelEasy() {

timerSpeed = 0.017;

resetGame();

}

private void setLevelMedium() {

timerSpeed = 0.037;

resetGame();

}

private void setLevelDifficult() {

timerSpeed = 0.067;

resetGame();

}

private void resetGame(){

tetris.quit();

timer.stop();

boardView.clear();

scene = new Scene(createContent());

scene.getStylesheets().addAll(this.getClass().getResource(“tetris.css”).toExternalForm());

createHandlers(scene);

int level = (int) (timerSpeed * 1000);

switch(level)

{

case 17:

Easy.getStyleClass().add(“selected”);

Medium.getStyleClass().remove(“selected”);

Difficult.getStyleClass().remove(“selected”);

break;

case 37:

Medium.getStyleClass().add(“selected”);

Easy.getStyleClass().remove(“selected”);

Difficult.getStyleClass().remove(“selected”);

break;

case 67:

Difficult.getStyleClass().add(“selected”);

Medium.getStyleClass().remove(“selected”);

Easy.getStyleClass().remove(“selected”);

break;

}

time = 0;

menu.reset(scene);

}

private void update() {

tetris.applyGravity();

}

private void stopGame() {

timer.stop();

tetris.stop();

}

private Color getRandomColor(){

Random rd = new Random();

int color;

color = rd.nextInt(7);

switch (color){

case 0:

return  Color.rgb(144, 198, 149);

case 1:

return  Color.rgb(104, 195, 163);

case 2:

return  Color.rgb(3, 201, 169);

case 3:

return Color.rgb(248, 148, 6);

case 4:

return Color.rgb(219, 10, 91);

case 5:

return Color.rgb(102, 51, 153);

case 6:

return Color.rgb(65, 131, 215);

default:

return Color.BLACK;

}

//

//

}

@Override

public void onMovement() {

boardView.updatePiece(tetris.getCurrent());

}

@Override

public void onChangedNext() {

boardView.addPiece(tetris.getCurrent(), next.getColor(), TILE_SIZE, 2);

next = new PieceView(getRandomColor(), tetris.getNext(), TILE_SIZE, 0);

next.getSquare().forEach(rectangle -> rectangle.getStyleClass().add(“block”));

drawNext();

if (artificialPlayer){

artificialIntelligence.setHasChanged();

}

}

private void drawNext() {

nextGroup.getChildren().clear();

int maxPieceY = next.getPiece().getPositions().stream().

max(Comparator.comparingInt(Position::getY)).get().getY();

int maxPieceX = next.getPiece().getPositions().stream().

max(Comparator.comparingInt(Position::getX)).get().getX();

int offsetY = (5 – maxPieceY)/2;

int offsetX = (5 – maxPieceX)/2;

next.getSquare().forEach(rectangle -> rectangle.relocate(rectangle.getLayoutX() + TILE_SIZE*offsetY, rectangle.getLayoutY() + TILE_SIZE*offsetX));

next.getSquare().forEach(rectangle -> rectangle.setStroke(Color.BLACK));

nextGroup.getChildren().addAll(next.getSquare());

}

@Override

public synchronized void onSweep() {

//boardView.updateAll();

Platform.runLater(() -> boardView.updateAll());

Platform.runLater(() -> score.setText(Integer.toString(tetris.getScore())));

}

@Override

public void onQuit() {

stopGame();

}

@Override

public void onCleanUp(Piece p) {

boardView.clean(p);

}

@Override

public void update(Piece p) {

Platform.runLater(() -> boardView.updatePiece(p));

}

} 

Clocking 

Clock.java 

package View.Clocking;

public class Clock {

private CounterArray clocks;

public Clock(){

int[] steps = {1, 1, 1};

int[] infs = {0, 0, 0};

int[] sups = {60, 60, 12};

clocks = new CounterArray(3, steps, infs, sups);

}

public int getSeconds(){

return  clocks.getCounterI(0).getValue();

}

int getMinutes(){

return  clocks.getCounterI(1).getValue();

}

int getHour(){

return  clocks.getCounterI(2).getValue();

}

public void reset(){

clocks.flush();

}

private void display(){

System.out.println(this.getHour() + “h” + this.getMinutes() + “m” + this.getSeconds() + “s”);

}

public void increment(){

clocks.increment();

}

public void clock(){

while(true){  //Infinite loop the clock is always running till it runs out of energy. Not handle here.

this.display(); //We call the display function.

this.sleep(1000);

this.increment();

}

}

public static void sleep(int n){

try {

Thread.sleep(n);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

@Override

public String toString() {

return String.valueOf(getHour()) +

“h” +

getMinutes() +

“m” +

getSeconds() +

“s”;

}

public int toSeconds() {

return getHour() * 3600 + getMinutes() * 60 + getSeconds();

}

} 

Counter.java 

package View.Clocking;

public class Counter {

private int step;

private int inf;

private int sup;

private int value;

Counter(int step, int inf, int sup) {

this.step = step;

this.inf = inf;

this.sup = sup;

this.value = this.inf;

}

void increment(){ /*When we add something in our counter, we call this fonction*/

if(this.value + this.step < this.sup) /*If the value + the step does not go over the sup bound*/

this.value += this.step; /*We increment*/

else this.flush(); /*Otherwise we loop over*/

}

public void decrement(){ /*When we delete something in our counter, we call this function*/

if(this.value – this.step > this.inf) /*If the value – the step does not go under the inf bound*/

this.value -= this.step; /*We increment*/

else this.flush(); /*Otherwise we stay at the minimum*/

}

void flush(){

this.value = this.inf; /*Reset the counter to the minimum*/

}

public int getStep() {

return step;

}

public int getInf() {

return inf;

}

int getSup() {

return sup;

}

public int getValue() {

return value;

}

} 

CounterArray.java

 package View.Clocking;

public class CounterArray {

private Counter[] counters;

public CounterArray(int size, int[] steps, int[] infs, int[] sups) {

counters = new Counter[size];

for(int i = 0;i < size; i++){

counters[i] = new Counter(steps[i], infs[i], sups[i]);

}

}

public void increment(){ // Wrapper so that the user just have to call increment().

this.increment(0);

}

private void increment(int n){  //Recursive function.

if( n == counters.length-1){ //Stop case : If the  n is the last element of the array.

if(counters[n].getValue() < counters[n].getSup()-1){ //If we can still increment the value

counters[n].increment();

} else {

this.flush(); //If we are at the maximum value, we flush everything.

}

} else {

if(counters[n].getValue() < counters[n].getSup()-1){ //Same with the other element of the array.

counters[n].increment();

} else {

counters[n].increment();

this.increment(n+1); //We call again the function with the next element of the array.

}

}

}

public void flush(){

for (Counter counter : counters) {

counter.flush(); //Reset every element of the array to the minimum

}

}

public Counter getCounterI(int i){

return counters[i];

}

public int getLength(){

return counters.length;

}

} 

 ViewBoard 

BoardView.java 

package View.ViewBoard;

import Model.ModelBoard.Pieces.Piece;

import javafx.scene.Group;

import javafx.scene.paint.Color;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

import java.util.stream.Collectors;

/**

* Created by Irindul on 21/03/2017.

* Board view library. Contains a group with different pieces on it

*/

public class BoardView {

private Group group;

private HashMap<Piece, PieceView> pieces;

private String style;

public BoardView() {

group = new Group();

pieces = new HashMap<>();

style = null;

}

public BoardView(String style){

this();

this.style = style;

}

public void addPiece(Piece piece, Color color, double tilesize, int offset){

PieceView view = new PieceView(color, piece, tilesize, offset);

if (style != null) {

view.getSquare().forEach(rectangle -> rectangle.getStyleClass().add(style));

}

addPiece(piece, view);

}

public void addPiece(Piece piece, PieceView pieceView){

group.getChildren().add(pieceView);

pieces.put(piece, pieceView);

}

public List<PieceView> getPieceViews(){

return pieces.entrySet().stream()

.map(Map.Entry::getValue)

.collect(Collectors.toList());

}

public void clear(){

group.getChildren().clear();

pieces.clear();

}

public void updatePiece(Piece piece) {

//pieces.get(piece).getChildren().clear();

if (pieces.get(piece) != null) {

pieces.get(piece).update();

}

}

public void updateAll(){

pieces.entrySet().stream()

.map(Map.Entry::getKey)

.forEach(this::updatePiece);

}

public Group getGroup(){

return group;

}

public void clean(Piece p) {

//pieces.clear();

//pieces.get(p).getChildren().clear();

pieces.get(p).getSquare().forEach(

rectangle -> group.getChildren().remove(rectangle));

}

}

PieceView.java 

package View.ViewBoard;

import Model.ModelBoard.Pieces.Piece;

import javafx.collections.ObservableList;

import javafx.scene.Node;

import javafx.scene.Parent;

import javafx.scene.paint.Color;

import javafx.scene.shape.Rectangle;

import java.util.ArrayList;

import java.util.List;

/**

* Created by Irindul on 16/02/2017.

* Piece library. Custom Node component to be added on a another Node

*/

public class PieceView extends Parent{

private Color color;

private Color stroke;

private Piece piece;

private double tilesize;

private int offset;

public PieceView(Color color, Piece piece, double tilesize, int offset) {

this(color, piece, tilesize, offset, Color.BLACK);

}

private PieceView(Color color, Piece piece, double tilesize, int offset, Color stroke){

super();

this.color = color;

this.piece = piece;

this.tilesize = tilesize;

this.offset = offset;

//square = new ArrayList<>();

piece.getPositions().stream()

.map(position -> {

Rectangle rect = new Rectangle(this.tilesize, this.tilesize, this.color);

rect.relocate(position.getY()*this.tilesize, position.getX()*this.tilesize);

return rect;

})

.forEach(rectangle -> this.getChildren().add(rectangle));

this.stroke = stroke;

}

public synchronized void update(){

this.getChildren().clear();

piece.getPositions().stream()

.map(position -> {

Rectangle rect = new Rectangle(tilesize, tilesize, color);

rect.relocate(position.getY()*tilesize, (position.getX()-offset)*tilesize);

rect.setStroke(stroke);

return rect;

})

.forEach(rectangle -> getChildren().add(rectangle));

}

public List<Rectangle> getSquare() {

List<Rectangle> square = new ArrayList<>();

this.getChildren().forEach(rectangle -> square.add((Rectangle) rectangle));

return square;

}

public Piece getPiece() {

return piece;

}

public Color getColor(){

return color;

}

public void setStroke(Color stroke) {

this.stroke = stroke;

}

@Override

public ObservableList<Node> getChildren() {

return super.getChildren();

}

}