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 @@ ![alt tag](img/screenshot_gameover.png) +![alt tag](img/screenshot_name_input.png) + +![alt tag](img/screenshot_score_table.png) + ## 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; + } +}