diff --git a/.travis.yml b/.travis.yml
index 9bcf999..42fe87f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,3 @@
language: java
jdk:
- - oraclejdk8
+ - oraclejdk14
diff --git a/README.md b/README.md
index c2c0ff8..7fd057b 100644
--- a/README.md
+++ b/README.md
@@ -12,12 +12,26 @@

+
+
+
+
## How to build it ?
You will need a Java JDK 8+ and maven 3+.
Execute **mvn clean package assembly:single** to build the release package.
+# How to run it ?
+```
+mvn clean
+
+mvn package
+
+mvn exec:java
+
+```
+
## How to play ?
- SPACE - Start a new game
@@ -34,6 +48,8 @@ Execute **mvn clean package assembly:single** to build the release package.
- P - Pause the current game
+- H - Show the high scores
+
- M - Mute sound
- PAGE UP - Increase the volume
diff --git a/img/screenshot_name_input.png b/img/screenshot_name_input.png
new file mode 100644
index 0000000..f33502f
Binary files /dev/null and b/img/screenshot_name_input.png differ
diff --git a/img/screenshot_score_table.png b/img/screenshot_score_table.png
new file mode 100644
index 0000000..6a0fdd2
Binary files /dev/null and b/img/screenshot_score_table.png differ
diff --git a/pom.xml b/pom.xml
index c8fd6d0..5224cb3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -111,6 +111,14 @@
@{project.version}
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.2.1
+
+ spypunk.tetris.Main
+
+
@@ -146,5 +154,11 @@
commons-lang3
3.6
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.4.2
+
+
diff --git a/score_table.txt b/score_table.txt
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/java/spypunk/tetris/service/TetrisService.java b/src/main/java/spypunk/tetris/service/TetrisService.java
index 24aa953..621699b 100644
--- a/src/main/java/spypunk/tetris/service/TetrisService.java
+++ b/src/main/java/spypunk/tetris/service/TetrisService.java
@@ -23,4 +23,9 @@ public interface TetrisService {
void pause();
void mute();
+
+ void showScores();
+
+ void takeName();
+
}
diff --git a/src/main/java/spypunk/tetris/service/TetrisServiceImpl.java b/src/main/java/spypunk/tetris/service/TetrisServiceImpl.java
index 7b14c0e..faf4db3 100644
--- a/src/main/java/spypunk/tetris/service/TetrisServiceImpl.java
+++ b/src/main/java/spypunk/tetris/service/TetrisServiceImpl.java
@@ -33,6 +33,8 @@
import spypunk.tetris.model.ShapeType;
import spypunk.tetris.model.Tetris;
import spypunk.tetris.model.Tetris.State;
+import spypunk.tetris.ui.view.TetrisNameInputView;
+import spypunk.tetris.ui.view.TetrisScoresView;
import spypunk.tetris.model.TetrisEvent;
import spypunk.tetris.model.TetrisInstance;
@@ -79,7 +81,7 @@ public void update() {
public void pause() {
tetris.setState(tetris.getState().onPause());
}
-
+
@Override
public void move(final Movement movement) {
if (isMovementAllowed()) {
@@ -332,4 +334,33 @@ private int getLevelSpeed(final int level) {
private boolean isMovementAllowed() {
return isTetrisRunning() && !isCurrentShapeLocked();
}
+
+ TetrisScoresView tetrisScoresView=new TetrisScoresView();
+ @Override
+ public void showScores() {
+ if(tetris.getState().equals(State.RUNNING)){
+ tetris.setState(tetris.getState().onPause());
+ }
+ if(tetrisNameInputView.isAdded==false && !tetrisNameInputView.getName().equals("")){
+ String name=tetrisNameInputView.getName();
+ Integer score=tetris.getScore();
+ tetrisScoresView.putScoreAndName(name, score);
+ tetrisNameInputView.isAdded=true;
+ }
+ tetrisScoresView.show();
+ }
+
+ TetrisNameInputView tetrisNameInputView=new TetrisNameInputView();
+ @Override
+ public void takeName() {
+ if(tetrisScoresView.isFull()){
+ if(tetris.getScore()>tetrisScoresView.getMinScore()){
+ tetrisNameInputView.show(tetris.getScore());
+ }
+ }
+ else{
+ tetrisNameInputView.show(tetris.getScore());
+ }
+
+ }
}
diff --git a/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCache.java b/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCache.java
index b9c5027..4951daa 100644
--- a/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCache.java
+++ b/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCache.java
@@ -26,7 +26,8 @@ public enum TetrisControllerCommandType {
OPEN_PROJECT_URL,
SHAPE_LOCKED,
GAME_OVER,
- ROWS_COMPLETED
+ ROWS_COMPLETED,
+ SHOW_SCORES
}
TetrisControllerCommand getTetrisControllerCommand(TetrisControllerCommandType tetrisControllerCommandType);
diff --git a/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCacheImpl.java b/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCacheImpl.java
index 16a5019..4a50558 100644
--- a/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCacheImpl.java
+++ b/src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCacheImpl.java
@@ -64,6 +64,8 @@ public TetrisControllerCommandCacheImpl(final TetrisService tetrisService,
tetrisControllerCommands.put(TetrisControllerCommandType.SHAPE_LOCKED, createShapeLockedCommand());
tetrisControllerCommands.put(TetrisControllerCommandType.GAME_OVER, createGameOverCommand());
tetrisControllerCommands.put(TetrisControllerCommandType.ROWS_COMPLETED, createRowsCompletedCommand());
+ tetrisControllerCommands.put(TetrisControllerCommandType.SHOW_SCORES, createShowScoresCommand());
+
}
@Override
@@ -93,6 +95,19 @@ private TetrisControllerCommand createPauseCommand() {
}
};
}
+ private TetrisControllerCommand createShowScoresCommand() {
+ return () -> {
+ tetrisService.showScores();
+
+ final State state = tetris.getState();
+
+ if (State.PAUSED.equals(state)) {
+ soundService.pauseMusic();
+ } else if (State.RUNNING.equals(state)) {
+ soundService.resumeMusic();
+ }
+ };
+ }
private TetrisControllerCommand createMoveCommand(final Movement movement) {
return () -> tetrisService.move(movement);
@@ -114,7 +129,10 @@ private TetrisControllerCommand createMuteCommand() {
}
private TetrisControllerCommand createGameOverCommand() {
- return () -> soundService.playMusic(Sound.GAME_OVER);
+ return () -> {
+ soundService.playMusic(Sound.GAME_OVER);
+ tetrisService.takeName();
+ };
}
private TetrisControllerCommand createRowsCompletedCommand() {
diff --git a/src/main/java/spypunk/tetris/ui/controller/input/TetrisControllerInputHandlerImpl.java b/src/main/java/spypunk/tetris/ui/controller/input/TetrisControllerInputHandlerImpl.java
index 37caee4..191fd53 100644
--- a/src/main/java/spypunk/tetris/ui/controller/input/TetrisControllerInputHandlerImpl.java
+++ b/src/main/java/spypunk/tetris/ui/controller/input/TetrisControllerInputHandlerImpl.java
@@ -48,6 +48,8 @@ public TetrisControllerInputHandlerImpl(final TetrisControllerCommandCache tetri
releasedKeyEventCommandTypes.put(KeyEvent.VK_PAGE_UP, TetrisControllerCommandType.INCREASE_VOLUME);
releasedKeyEventCommandTypes.put(KeyEvent.VK_PAGE_DOWN, TetrisControllerCommandType.DECREASE_VOLUME);
releasedKeyEventCommandTypes.put(KeyEvent.VK_CONTROL, TetrisControllerCommandType.HARD_DROP);
+ releasedKeyEventCommandTypes.put(KeyEvent.VK_H, TetrisControllerCommandType.SHOW_SCORES);
+
}
@Override
diff --git a/src/main/java/spypunk/tetris/ui/view/TetrisMainViewImpl.java b/src/main/java/spypunk/tetris/ui/view/TetrisMainViewImpl.java
index 245c98e..7e86f52 100644
--- a/src/main/java/spypunk/tetris/ui/view/TetrisMainViewImpl.java
+++ b/src/main/java/spypunk/tetris/ui/view/TetrisMainViewImpl.java
@@ -163,7 +163,6 @@ public TetrisMainViewImpl(final TetrisController tetrisController,
frame.add(centerPanel, BorderLayout.CENTER);
frame.add(bottomPanel, BorderLayout.SOUTH);
frame.pack();
-
frame.setLocationRelativeTo(null);
}
diff --git a/src/main/java/spypunk/tetris/ui/view/TetrisNameInputView.java b/src/main/java/spypunk/tetris/ui/view/TetrisNameInputView.java
new file mode 100644
index 0000000..bf0f792
--- /dev/null
+++ b/src/main/java/spypunk/tetris/ui/view/TetrisNameInputView.java
@@ -0,0 +1,101 @@
+package spypunk.tetris.ui.view;
+
+import javax.swing.JFrame;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JButton;
+import javax.swing.JTextField;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.BorderLayout;
+import java.awt.Font;
+
+import static spypunk.tetris.ui.constants.TetrisUIConstants.DEFAULT_FONT_COLOR;
+import spypunk.tetris.ui.font.cache.FontCacheImpl;
+
+public class TetrisNameInputView{
+
+ private final JFrame frame;
+ private final Container contentPane;
+ private final JPanel firstPanel;
+ private final JButton submitButton;
+ private final JTextField textField;
+ private final JTextArea textArea;
+
+ private final FontCacheImpl fontCache;
+ private final Font font;
+
+ private String name="";
+ public boolean isAdded=false;;
+
+ public TetrisNameInputView(){
+
+ frame=new JFrame();
+ contentPane=frame.getContentPane();
+ contentPane.setBackground(Color.BLACK);
+ frame.setPreferredSize(new Dimension(400, 200));
+ frame.setLayout(new BorderLayout());
+ frame.setResizable(false);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+
+ firstPanel=new JPanel();
+ textField = new JTextField(10);
+ submitButton = new JButton("SUBMIT");
+ textArea=new JTextArea();
+
+ fontCache=new FontCacheImpl();
+ font=fontCache.getDefaultFont();
+ }
+
+ public void show(int score){
+ print(score);
+ frame.setVisible(true);
+ }
+
+ private void print(int score){
+
+ submitButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ name=textField.getText();
+ if(!name.equals("")){
+ if(isAdded){
+ isAdded=false;
+ }
+ frame.setVisible(false);
+ }
+ }
+ });
+
+ submitButton.setBackground(Color.BLACK);
+ submitButton.setForeground(DEFAULT_FONT_COLOR);
+ submitButton.setFont(font);
+
+ textField.setBackground(Color.BLACK);
+ textField.setForeground(DEFAULT_FONT_COLOR);
+ textField.setFont(font);
+ textField.setText("");
+
+ textArea.setBackground(Color.BLACK);
+ textArea.setForeground(DEFAULT_FONT_COLOR);
+ textArea.setFont(font);
+
+ textArea.setText("\n YOU ARE IN TOP 5 !\n\n SCORE : "+score+"\n\n PLEASE ENTER YOUR NAME\n");
+ textArea.setEditable(false);
+
+ firstPanel.setBackground(Color.BLACK);
+ firstPanel.add(textField);
+ firstPanel.add(submitButton);
+
+ frame.add(firstPanel,BorderLayout.CENTER);
+ frame.add(textArea,BorderLayout.NORTH);
+ }
+ public String getName(){
+ return name;
+ }
+
+}
diff --git a/src/main/java/spypunk/tetris/ui/view/TetrisScoresTest.java b/src/main/java/spypunk/tetris/ui/view/TetrisScoresTest.java
new file mode 100644
index 0000000..706b4c1
--- /dev/null
+++ b/src/main/java/spypunk/tetris/ui/view/TetrisScoresTest.java
@@ -0,0 +1,64 @@
+package spypunk.tetris.ui.view;
+
+import static org.junit.jupiter.api.Assertions.*;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+
+public class TetrisScoresTest {
+
+ private TetrisScoresView tsv;
+ @BeforeEach
+ public void setUp(){
+ tsv=new TetrisScoresView();
+ }
+ @Test
+ public void testGetMin(){
+ tsv.hashMap.put("A",2);
+ tsv.hashMap.put("B",1);
+ tsv.hashMap.put("C",3);
+ assertEquals(1,tsv.getMinScore());
+ }
+ @Test
+ public void testGetMinWithEmptyList(){
+ //If list is empty then minimum score is 0.
+ assertEquals(0,tsv.getMinScore());
+ }
+ @Test
+ public void testPutScoreAndName(){
+ //When the number of elements in the list is less than 5.
+ tsv.putScoreAndName("A",5);
+ assertTrue(tsv.hashMap.containsKey("A"));
+ }
+ @Test
+ public void testPutScoreAndNameWithFullList(){
+ //We expect that when list is full, if F is added then the element
+ // has minimum score is removed.
+ tsv.hashMap.put("A",1);
+ tsv.hashMap.put("B",2);
+ tsv.hashMap.put("C",3);
+ tsv.hashMap.put("D",4);
+ tsv.hashMap.put("E",5);
+ tsv.putScoreAndName("F",6);
+ assertTrue(tsv.hashMap.containsKey("F"));
+ assertFalse(tsv.hashMap.containsKey("A"));
+ }
+ @Test
+ public void testIsFull(){
+ //When the number of elements in the list is equal to 5.
+ tsv.hashMap.put("A",1);
+ tsv.hashMap.put("B",2);
+ tsv.hashMap.put("C",3);
+ tsv.hashMap.put("D",4);
+ tsv.hashMap.put("E",5);
+ assertTrue(tsv.isFull());
+ }
+ @Test
+ public void testRemoveMinScoreElement(){
+ tsv.hashMap.put("A",1);
+ tsv.hashMap.put("B",2);
+ tsv.hashMap.put("C",3);
+ tsv.removeMinScoreElement();
+ assertFalse(tsv.hashMap.containsKey("A"));
+ }
+
+}
diff --git a/src/main/java/spypunk/tetris/ui/view/TetrisScoresView.java b/src/main/java/spypunk/tetris/ui/view/TetrisScoresView.java
new file mode 100644
index 0000000..8ba5265
--- /dev/null
+++ b/src/main/java/spypunk/tetris/ui/view/TetrisScoresView.java
@@ -0,0 +1,179 @@
+package spypunk.tetris.ui.view;
+
+import javax.swing.JFrame;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.Map.Entry;
+import javax.swing.JTextArea;
+
+import static spypunk.tetris.ui.constants.TetrisUIConstants.DEFAULT_FONT_COLOR;
+import spypunk.tetris.ui.font.cache.FontCacheImpl;
+
+public class TetrisScoresView{
+
+ private final JFrame frame;
+ public HashMap hashMap;
+ private final Container contentPane;
+ private final JTextArea textArea;
+ private final FontCacheImpl fontCache;
+ private final Font font ;
+ private final int SIZE=5;
+
+ public TetrisScoresView(){
+ //Initilizations.
+ frame=new JFrame();
+ hashMap=new HashMap<>();
+
+ contentPane=frame.getContentPane();
+ contentPane.setBackground(Color.BLACK);
+
+ frame.setPreferredSize(new Dimension(400, 300));
+ frame.setResizable(false);
+ frame.pack();
+ frame.setLocationRelativeTo(null);
+
+ textArea = new JTextArea();
+ fontCache=new FontCacheImpl();
+ font=fontCache.getDefaultFont();
+
+ readFile();
+ }
+
+ public void show(){
+ //Set visibility true.
+ print();
+ frame.setVisible(true);
+ }
+
+ private void print(){
+ hashMap=sort(hashMap);
+ int counter=1;
+ String s="\n HIGH SCORES\n";
+ for ( String key : hashMap.keySet() ) {
+ s+="\n "+counter+") "+key+" "+hashMap.get(key);
+ counter++;
+ }
+ textArea.setText(s);
+ textArea.setBackground(Color.BLACK);
+ textArea.setForeground(DEFAULT_FONT_COLOR);
+ textArea.setEditable(false);
+
+ float size=font.getSize()+10.0f;
+ textArea.setFont(font.deriveFont(size));
+ frame.add(textArea);
+ }
+
+ private void update(){
+ //When new element is added, sort the list and update the textArea.
+ hashMap=sort(hashMap);
+ int counter=1;
+ String s=" HIGH SCORES\n";
+ for ( String key : hashMap.keySet() ) {
+ s+="\n "+counter+") "+key+" "+hashMap.get(key);
+ counter++;
+ }
+ textArea.setText(s);
+ }
+
+ private void readFile(){
+ //Reads from file, separates name and score and add to list.
+ try {
+ File file = new File("score_table.txt");
+ Scanner fileScanner= new Scanner(file);
+ while (fileScanner.hasNextLine()) {
+ String data = fileScanner.nextLine();
+ String name=data.substring(0,data.indexOf(" "));
+ String score_s=data.substring(data.indexOf(" ")+1);
+ Integer score=Integer.parseInt(score_s);
+ hashMap.put(name, score);
+ }
+ fileScanner.close();
+ } catch (FileNotFoundException e) {
+ System.out.println("An error occurred.");
+ e.printStackTrace();
+ }
+ }
+ private void writeFile(){
+ //Writes to file.
+ try {
+ FileWriter fileWriter = new FileWriter("score_table.txt");
+ String str="";
+ for ( String key : hashMap.keySet() ) {
+ str+=key+" "+hashMap.get(key)+"\n";
+ }
+ fileWriter.write(str);
+ fileWriter.close();
+ } catch (IOException e) {
+ System.out.println("An error occurred.");
+ e.printStackTrace();
+ }
+ }
+
+ private HashMap sort(HashMap hashMap){
+ //Sorts the list by using a new comparator.
+ List>list=new LinkedList>(hashMap.entrySet());
+ Collections.sort(list,new Comparator>(){
+
+ @Override
+ public int compare(Map.Entry o1, Map.Entry o2) {
+ return o2.getValue().compareTo(o1.getValue());
+ }
+ });
+ HashMap sorted=new LinkedHashMap<>();
+ for(Map.Entry e:list){
+ sorted.put(e.getKey(),e.getValue());
+ }
+ return sorted;
+ }
+
+ public Integer getMinScore(){
+ //If list is empty then return 0.
+ try {
+ return Collections.min(hashMap.values());
+ } catch (Exception e) {
+ return 0;
+ }
+
+ }
+
+ public void putScoreAndName(String name,Integer score){
+ //If list is full then remove element has minimum score.
+ if(hashMap.size()+1>SIZE)
+ removeMinScoreElement();
+ hashMap.put(name, score);
+ update();
+ writeFile();
+ }
+
+ public void removeMinScoreElement(){
+ //Since the list is sorted when this is called, it removes the last element of the list.
+ try {
+ Iterator> itr = hashMap.entrySet().iterator();
+ while(itr.hasNext())
+ itr.next();
+ itr.remove();
+ } catch (Exception e) {
+ System.err.println("Empty hashmap error");
+ }
+
+ }
+
+ public boolean isFull(){
+ return hashMap.size()==SIZE;
+ }
+}