├── .gitignore ├── .travis.yml ├── AUTHORS ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── COPYING ├── CREDITS ├── README.md ├── img ├── screenshot.png ├── screenshot_gameover.png ├── screenshot_pause.png └── screenshot_start.png ├── pom.xml └── src ├── assembly └── assembly.xml └── main ├── java └── spypunk │ └── tetris │ ├── Main.java │ ├── constants │ └── TetrisConstants.java │ ├── exception │ └── TetrisException.java │ ├── factory │ ├── ShapeFactory.java │ └── ShapeFactoryImpl.java │ ├── guice │ └── TetrisModule.java │ ├── model │ ├── Movement.java │ ├── Shape.java │ ├── ShapeType.java │ ├── Tetris.java │ ├── TetrisEvent.java │ └── TetrisInstance.java │ ├── service │ ├── TetrisService.java │ └── TetrisServiceImpl.java │ ├── sound │ ├── Sound.java │ ├── SoundClip.java │ ├── SoundClipImpl.java │ ├── cache │ │ ├── SoundClipCache.java │ │ └── SoundClipCacheImpl.java │ └── service │ │ ├── SoundService.java │ │ └── SoundServiceImpl.java │ └── ui │ ├── cache │ ├── ImageCache.java │ └── ImageCacheImpl.java │ ├── constants │ └── TetrisUIConstants.java │ ├── controller │ ├── TetrisController.java │ ├── TetrisControllerImpl.java │ ├── command │ │ ├── TetrisControllerCommand.java │ │ └── cache │ │ │ ├── TetrisControllerCommandCache.java │ │ │ └── TetrisControllerCommandCacheImpl.java │ ├── event │ │ ├── TetrisControllerTetrisEventHandler.java │ │ └── TetrisControllerTetrisEventHandlerImpl.java │ ├── gameloop │ │ ├── TetrisControllerGameLoop.java │ │ └── TetrisControllerGameLoopImpl.java │ └── input │ │ ├── TetrisControllerInputHandler.java │ │ └── TetrisControllerInputHandlerImpl.java │ ├── font │ ├── Font.java │ ├── FontType.java │ └── cache │ │ ├── FontCache.java │ │ └── FontCacheImpl.java │ ├── icon │ └── Icon.java │ ├── util │ └── SwingUtils.java │ └── view │ ├── AbstractTetrisView.java │ ├── AbstractView.java │ ├── TetrisGridView.java │ ├── TetrisInfoView.java │ ├── TetrisMainView.java │ ├── TetrisMainViewImpl.java │ └── TetrisStatisticsView.java ├── resources-filtered └── tetris.properties ├── resources ├── font │ ├── neutronium.ttf │ └── russo_one.ttf ├── img │ ├── blocks │ │ ├── I.png │ │ ├── J.png │ │ ├── L.png │ │ ├── O.png │ │ ├── S.png │ │ ├── T.png │ │ └── Z.png │ ├── icons │ │ ├── icon.png │ │ ├── mute.png │ │ └── unmute.png │ └── shapes │ │ ├── I.png │ │ ├── J.png │ │ ├── L.png │ │ ├── O.png │ │ ├── S.png │ │ ├── T.png │ │ └── Z.png ├── log4j.properties └── sound │ ├── background.mp3 │ ├── game_over.mp3 │ ├── rows_completed.mp3 │ └── shape_locked.mp3 └── scripts ├── tetris └── tetris.bat /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | /target/ 14 | /.classpath 15 | /.project 16 | /.settings/ 17 | /tetris.log 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | spypunk 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [1.13.0] - 2017-08-11 8 | 9 | - Rendering optimizations and refactoring 10 | 11 | ## [1.12.0] - 2017-08-09 12 | 13 | - Optimizations and more refactoring 14 | 15 | ## [1.11.0] - 2017-08-04 16 | 17 | - Optimizations and more refactoring 18 | 19 | ## [1.10.0] - 2017-08-02 20 | 21 | - Optimizations and more refactoring 22 | 23 | ## [1.9.0] - 2017-07-30 24 | 25 | - Optimizations and more refactoring 26 | 27 | ## [1.8.0] - 2017-07-28 28 | 29 | - Optimizations and a lot of refactoring 30 | 31 | ## [1.7.0] - 2016-09-19 32 | 33 | - New application icon added 34 | 35 | ## [1.6.1] - 2016-09-13 36 | 37 | - Null pointer when instance is not started and keys are pressed 38 | 39 | ## [1.6.0] - 2016-09-10 40 | 41 | - Error dialog added 42 | 43 | ## [1.5.0] - 2016-09-08 44 | 45 | - "Hard drop" feature 46 | 47 | ## [1.4.0] - 2016-09-07 48 | 49 | - Game loop enhanced, memory consumption improved 50 | 51 | ## [1.3.0] - 2016-09-05 52 | 53 | - Code optimizations 54 | 55 | ## [1.2.0] - 2016-09-02 56 | 57 | - Mute/unmute icon 58 | 59 | ## [1.1.0] - 2016-08-31 60 | 61 | - Increase/decrease volume feature 62 | - Application icon 63 | 64 | ## [1.0.0] - 2016-08-20 65 | 66 | First stable release 67 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at spypunk@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | Thanks to [BOLDtype] for the Neutronium font (anavarrete_@hotmail.com) 2 | Thanks to [TinyWorlds] for the blocks pictures (http://opengameart.org/content/colored-blocks-sketchy) 3 | Thanks to [Jovanny Lemonad] for the Russo One font (jovanny.lemonad@mail.ru) 4 | Thanks to [ejfortin] for the sound effects (https://freesound.org/people/ejfortin) 5 | Thanks to [Timbre] and Freesound.org for the background music (https://www.freesound.org/people/Timbre) 6 | Thanks to [Boogle] for the sounds icons (http://opengameart.org/content/sound-onoff-images) 7 | Thanks to [Ikon] for the application icon (http://kon.deviantart.com) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tetris - Just another Tetris™ clone ![alt tag](src/main/resources/img/icons/icon.png) 2 | 3 | [![Release](https://img.shields.io/badge/latest%20release-1.13.0-brightgreen.svg)](https://github.com/spypunk/tetris/releases/tag/1.13.0) [![Build Status](https://travis-ci.org/spypunk/tetris.svg?branch=master)](https://travis-ci.org/spypunk/tetris) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/4d05e805b7ad406a82f10e7900fb497a)](https://www.codacy.com/app/spypunk/tetris?utm_source=github.com&utm_medium=referral&utm_content=spypunk/tetris&utm_campaign=Badge_Grade) [![Download tetris](https://img.shields.io/sourceforge/dt/spypunk-tetris.svg)](https://sourceforge.net/projects/spypunk-tetris/files/latest/download) [![License](http://www.wtfpl.net/wp-content/uploads/2012/12/wtfpl-badge-4.png)](http://www.wtfpl.net/) 4 | 5 | ## How it looks ? 6 | 7 | ![alt tag](img/screenshot_start.png) 8 | 9 | ![alt tag](img/screenshot.png) 10 | 11 | ![alt tag](img/screenshot_pause.png) 12 | 13 | ![alt tag](img/screenshot_gameover.png) 14 | 15 | ## How to build it ? 16 | 17 | You will need a Java JDK 8+ and maven 3+. 18 | 19 | Execute **mvn clean package assembly:single** to build the release package. 20 | 21 | ## How to play ? 22 | 23 | - SPACE - Start a new game 24 | 25 | - LEFT - Move the current shape to the left 26 | 27 | - RIGHT - Move the current shape to the right 28 | 29 | - DOWN - Move the current shape down 30 | 31 | - UP - Rotate the current shape clockwise 32 | 33 | - CTRL - "Hard drop" current shape 34 | 35 | - P - Pause the current game 36 | 37 | - M - Mute sound 38 | 39 | - PAGE UP - Increase the volume 40 | 41 | - PAGE DOWN - Decrease the volume 42 | 43 | ## What about license ? 44 | 45 | This project is licensed under the WTFPL (Do What The Fuck You Want To Public License, Version 2) 46 | 47 | [![WTFPL](http://www.wtfpl.net/wp-content/uploads/2012/12/logo-220x1601.png)](http://www.wtfpl.net/) 48 | 49 | Copyright © 2016-2017 spypunk [spypunk@gmail.com](mailto:spypunk@gmail.com) 50 | 51 | This work is free. You can redistribute it and/or modify it under the terms of the Do What The Fuck You Want To Public License, Version 2, as published by Sam Hocevar. See the COPYING file for more details. 52 | -------------------------------------------------------------------------------- /img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/img/screenshot.png -------------------------------------------------------------------------------- /img/screenshot_gameover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/img/screenshot_gameover.png -------------------------------------------------------------------------------- /img/screenshot_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/img/screenshot_pause.png -------------------------------------------------------------------------------- /img/screenshot_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/img/screenshot_start.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 4.0.0 8 | spypunk 9 | tetris 10 | 1.14.0-SNAPSHOT 11 | Tetris 12 | Just another Tetris™ clone 13 | https://github.com/spypunk/tetris 14 | 15 | 16 | 17 | WTFPL 18 | http://www.wtfpl.net/about/ 19 | 20 | 21 | 22 | 23 | 24 | spypunk 25 | https://github.com/spypunk 26 | 27 | 28 | 29 | 30 | git@github.com:spypunk/tetris.git 31 | scm:git:https://github.com/spypunk/tetris.git 32 | scm:git:git@github.com:spypunk/tetris.git 33 | HEAD 34 | 35 | 36 | 37 | 3.0 38 | 39 | 40 | 41 | ${project.artifactId} 42 | 43 | 44 | src/main/resources 45 | 46 | 47 | src/main/resources-filtered 48 | true 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-compiler-plugin 55 | 3.6.2 56 | 57 | 1.8 58 | 1.8 59 | UTF-8 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-jar-plugin 65 | 3.0.2 66 | 67 | 68 | 69 | true 70 | lib/ 71 | spypunk.tetris.Main 72 | true 73 | 74 | 75 | 76 | 77 | false 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-assembly-plugin 84 | 3.0.0 85 | 86 | ${project.artifactId}-${project.version} 87 | false 88 | 89 | src/assembly/assembly.xml 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-resources-plugin 96 | 3.0.2 97 | 98 | UTF-8 99 | 100 | 101 | 102 | org.codehaus.mojo 103 | versions-maven-plugin 104 | 2.4 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-release-plugin 109 | 2.5.3 110 | 111 | @{project.version} 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | com.google.inject 120 | guice 121 | 4.1.0 122 | 123 | 124 | org.slf4j 125 | slf4j-log4j12 126 | 1.7.21 127 | 128 | 129 | com.googlecode.soundlibs 130 | mp3spi 131 | 1.9.5.4 132 | 133 | 134 | junit 135 | junit 136 | 137 | 138 | 139 | 140 | org.apache.commons 141 | commons-collections4 142 | 4.1 143 | 144 | 145 | org.apache.commons 146 | commons-lang3 147 | 3.6 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/assembly/assembly.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | bin 8 | 9 | zip 10 | 11 | 12 | 13 | false 14 | lib 15 | false 16 | 17 | 18 | 19 | 20 | 21 | ${project.build.scriptSourceDirectory} 22 | 23 | 755 24 | 25 | * 26 | 27 | 28 | 29 | ${project.build.directory} 30 | 31 | 32 | *.jar 33 | 34 | 35 | 36 | ${project.basedir} 37 | 38 | 39 | AUTHORS 40 | COPYING 41 | CREDITS 42 | README.md 43 | CHANGELOG.md 44 | CODE_OF_CONDUCT.md 45 | 46 | 47 | 48 | ${project.basedir}/src/main/resources/img/icons 49 | 50 | 51 | icon.png 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/Main.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris; 10 | 11 | import java.io.File; 12 | 13 | import javax.swing.JOptionPane; 14 | 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import com.google.inject.ConfigurationException; 19 | import com.google.inject.CreationException; 20 | import com.google.inject.Guice; 21 | import com.google.inject.Injector; 22 | import com.google.inject.ProvisionException; 23 | 24 | import spypunk.tetris.guice.TetrisModule; 25 | import spypunk.tetris.ui.controller.TetrisController; 26 | import spypunk.tetris.ui.util.SwingUtils; 27 | 28 | public final class Main { 29 | 30 | private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); 31 | 32 | private static final String USER_HOME = System.getProperty("user.home"); 33 | 34 | private static final String ERROR_TITLE = "Error"; 35 | 36 | private static final String ERROR_MESSAGE_TEMPLATE = "An error occurred, check the log file %s%s.spypunk-tetris%stetris.log for more information"; 37 | 38 | private static final String ERROR_MESSAGE = String.format(ERROR_MESSAGE_TEMPLATE, USER_HOME, File.separator, 39 | File.separator); 40 | 41 | private Main() { 42 | throw new IllegalAccessError(); 43 | } 44 | 45 | public static void main(final String[] args) { 46 | try { 47 | final Injector injector = Guice.createInjector(new TetrisModule()); 48 | injector.getInstance(TetrisController.class).start(); 49 | } catch (CreationException | ConfigurationException | ProvisionException e) { 50 | LOGGER.error(e.getMessage(), e); 51 | SwingUtils.doInAWTThread(Main::showErrorDialog); 52 | } 53 | } 54 | 55 | private static void showErrorDialog() { 56 | JOptionPane.showMessageDialog(null, 57 | ERROR_MESSAGE, 58 | ERROR_TITLE, 59 | JOptionPane.ERROR_MESSAGE); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/constants/TetrisConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.constants; 10 | 11 | import java.awt.Point; 12 | 13 | public final class TetrisConstants { 14 | 15 | public static final int WIDTH = 10; 16 | 17 | public static final int HEIGHT = 20; 18 | 19 | public static final Point LOCATION_0_0 = new Point(0, 0); 20 | 21 | public static final Point LOCATION_0_1 = new Point(0, 1); 22 | 23 | public static final Point LOCATION_0_2 = new Point(0, 2); 24 | 25 | public static final Point LOCATION_1_M1 = new Point(1, -1); 26 | 27 | public static final Point LOCATION_1_0 = new Point(1, 0); 28 | 29 | public static final Point LOCATION_1_1 = new Point(1, 1); 30 | 31 | public static final Point LOCATION_1_2 = new Point(1, 2); 32 | 33 | public static final Point LOCATION_1_3 = new Point(1, 3); 34 | 35 | public static final Point LOCATION_2_M1 = new Point(2, -1); 36 | 37 | public static final Point LOCATION_2_0 = new Point(2, 0); 38 | 39 | public static final Point LOCATION_2_1 = new Point(2, 1); 40 | 41 | public static final Point LOCATION_2_2 = new Point(2, 2); 42 | 43 | public static final Point LOCATION_2_3 = new Point(2, 3); 44 | 45 | public static final Point LOCATION_3_0 = new Point(3, 0); 46 | 47 | public static final Point LOCATION_3_1 = new Point(3, 1); 48 | 49 | public static final Point LOCATION_3_2 = new Point(3, 2); 50 | 51 | private TetrisConstants() { 52 | throw new IllegalAccessError(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/exception/TetrisException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.exception; 10 | 11 | public class TetrisException extends RuntimeException { 12 | 13 | private static final long serialVersionUID = -2863969090656932325L; 14 | 15 | public TetrisException(final Throwable cause) { 16 | super(cause); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/factory/ShapeFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.factory; 10 | 11 | import spypunk.tetris.model.Shape; 12 | 13 | @FunctionalInterface 14 | public interface ShapeFactory { 15 | 16 | Shape createRandomShape(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/factory/ShapeFactoryImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.factory; 10 | 11 | import java.awt.Point; 12 | import java.awt.Rectangle; 13 | import java.util.Random; 14 | 15 | import javax.inject.Singleton; 16 | 17 | import spypunk.tetris.model.Shape; 18 | import spypunk.tetris.model.ShapeType; 19 | 20 | @Singleton 21 | public class ShapeFactoryImpl implements ShapeFactory { 22 | 23 | private static final int INITIAL_ROTATION = 0; 24 | 25 | private static final int MAX_START_X = 7; 26 | 27 | private final Random random = new Random(); 28 | 29 | private final ShapeType[] shapeTypes = ShapeType.values(); 30 | 31 | @Override 32 | public Shape createRandomShape() { 33 | final ShapeType shapeType = getRandomShapeType(); 34 | 35 | final int dx = random.nextInt(MAX_START_X); 36 | 37 | final Rectangle boundingBox = new Rectangle(shapeType.getBoundingBox()); 38 | 39 | boundingBox.translate(dx, 0); 40 | 41 | final Shape shape = new Shape(shapeType, boundingBox, INITIAL_ROTATION); 42 | 43 | shapeType.getRotations().get(INITIAL_ROTATION) 44 | .forEach(location -> shape.new Block(new Point(location.x + dx, location.y))); 45 | 46 | return shape; 47 | } 48 | 49 | private ShapeType getRandomShapeType() { 50 | return shapeTypes[random.nextInt(shapeTypes.length)]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/guice/TetrisModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.guice; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.lang.annotation.ElementType; 14 | import java.lang.annotation.Target; 15 | import java.net.URI; 16 | import java.util.Properties; 17 | 18 | import javax.inject.Inject; 19 | 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import com.google.inject.AbstractModule; 24 | import com.google.inject.BindingAnnotation; 25 | import com.google.inject.Provides; 26 | 27 | import spypunk.tetris.Main; 28 | import spypunk.tetris.exception.TetrisException; 29 | import spypunk.tetris.factory.ShapeFactory; 30 | import spypunk.tetris.factory.ShapeFactoryImpl; 31 | import spypunk.tetris.model.Tetris; 32 | import spypunk.tetris.service.TetrisService; 33 | import spypunk.tetris.service.TetrisServiceImpl; 34 | import spypunk.tetris.sound.cache.SoundClipCache; 35 | import spypunk.tetris.sound.cache.SoundClipCacheImpl; 36 | import spypunk.tetris.sound.service.SoundService; 37 | import spypunk.tetris.sound.service.SoundServiceImpl; 38 | import spypunk.tetris.ui.cache.ImageCache; 39 | import spypunk.tetris.ui.cache.ImageCacheImpl; 40 | import spypunk.tetris.ui.controller.TetrisController; 41 | import spypunk.tetris.ui.controller.TetrisControllerImpl; 42 | import spypunk.tetris.ui.controller.command.cache.TetrisControllerCommandCache; 43 | import spypunk.tetris.ui.controller.command.cache.TetrisControllerCommandCacheImpl; 44 | import spypunk.tetris.ui.controller.event.TetrisControllerTetrisEventHandler; 45 | import spypunk.tetris.ui.controller.event.TetrisControllerTetrisEventHandlerImpl; 46 | import spypunk.tetris.ui.controller.gameloop.TetrisControllerGameLoop; 47 | import spypunk.tetris.ui.controller.gameloop.TetrisControllerGameLoopImpl; 48 | import spypunk.tetris.ui.controller.input.TetrisControllerInputHandler; 49 | import spypunk.tetris.ui.controller.input.TetrisControllerInputHandlerImpl; 50 | import spypunk.tetris.ui.font.cache.FontCache; 51 | import spypunk.tetris.ui.font.cache.FontCacheImpl; 52 | import spypunk.tetris.ui.view.TetrisMainView; 53 | import spypunk.tetris.ui.view.TetrisMainViewImpl; 54 | 55 | public class TetrisModule extends AbstractModule { 56 | 57 | private static final Logger LOGGER = LoggerFactory.getLogger(Main.class); 58 | 59 | private static final String NAME_KEY = "name"; 60 | 61 | private static final String VERSION_KEY = "version"; 62 | 63 | private static final String URL_KEY = "url"; 64 | 65 | private static final String TETRIS_PROPERTIES = "/tetris.properties"; 66 | 67 | private final Tetris tetris; 68 | 69 | public TetrisModule() { 70 | String name; 71 | String version; 72 | URI uri; 73 | 74 | try (InputStream inputStream = TetrisModule.class.getResource(TETRIS_PROPERTIES).openStream()) { 75 | final Properties properties = new Properties(); 76 | 77 | properties.load(inputStream); 78 | 79 | name = properties.getProperty(NAME_KEY); 80 | version = properties.getProperty(VERSION_KEY); 81 | uri = URI.create(properties.getProperty(URL_KEY)); 82 | } catch (final IOException e) { 83 | LOGGER.error(e.getMessage(), e); 84 | throw new TetrisException(e); 85 | } 86 | 87 | tetris = new Tetris(name, version, uri); 88 | } 89 | 90 | @Override 91 | protected void configure() { 92 | bind(TetrisService.class).to(TetrisServiceImpl.class); 93 | bind(ShapeFactory.class).to(ShapeFactoryImpl.class); 94 | bind(TetrisController.class).to(TetrisControllerImpl.class); 95 | bind(ImageCache.class).to(ImageCacheImpl.class); 96 | bind(FontCache.class).to(FontCacheImpl.class); 97 | bind(TetrisControllerCommandCache.class).to(TetrisControllerCommandCacheImpl.class); 98 | bind(SoundService.class).to(SoundServiceImpl.class); 99 | bind(SoundClipCache.class).to(SoundClipCacheImpl.class); 100 | bind(TetrisControllerInputHandler.class).to(TetrisControllerInputHandlerImpl.class); 101 | bind(TetrisControllerTetrisEventHandler.class).to(TetrisControllerTetrisEventHandlerImpl.class); 102 | bind(TetrisControllerGameLoop.class).to(TetrisControllerGameLoopImpl.class); 103 | bind(TetrisMainView.class).to(TetrisMainViewImpl.class); 104 | } 105 | 106 | @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD }) 107 | @BindingAnnotation 108 | public @interface TetrisProvider { 109 | } 110 | 111 | @Provides 112 | @TetrisProvider 113 | @Inject 114 | public Tetris getTetris() { 115 | return tetris; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/model/Movement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.model; 10 | 11 | import java.awt.Point; 12 | import java.awt.Rectangle; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | import spypunk.tetris.model.Shape.Block; 17 | 18 | public enum Movement { 19 | 20 | LEFT { 21 | @Override 22 | public Point apply(final Point location) { 23 | return new Point(location.x - 1, location.y); 24 | } 25 | }, 26 | RIGHT { 27 | @Override 28 | public Point apply(final Point location) { 29 | return new Point(location.x + 1, location.y); 30 | } 31 | }, 32 | DOWN { 33 | @Override 34 | public Point apply(final Point location) { 35 | return new Point(location.x, location.y + 1); 36 | } 37 | }, 38 | ROTATE { 39 | @Override 40 | public Shape apply(final Shape shape) { 41 | final Rectangle boundingBox = shape.getBoundingBox(); 42 | final Rectangle newBoundingBox = new Rectangle(boundingBox); 43 | 44 | int nextRotationIndex = shape.getCurrentRotation() + 1; 45 | 46 | final List> rotations = shape.getShapeType().getRotations(); 47 | 48 | if (rotations.size() == nextRotationIndex) { 49 | nextRotationIndex = 0; 50 | } 51 | 52 | final Shape newShape = new Shape(shape.getShapeType(), newBoundingBox, nextRotationIndex); 53 | 54 | final Set rotation = rotations.get(nextRotationIndex); 55 | 56 | rotation.forEach(location -> newShape.new Block( 57 | new Point(location.x + newBoundingBox.x, location.y + newBoundingBox.y))); 58 | 59 | return newShape; 60 | } 61 | 62 | @Override 63 | public Point apply(final Point location) { 64 | throw new UnsupportedOperationException(); 65 | } 66 | }; 67 | 68 | public abstract Point apply(Point location); 69 | 70 | public Shape apply(final Shape shape) { 71 | final Rectangle boundingBox = shape.getBoundingBox(); 72 | 73 | final Rectangle newBoundingBox = new Rectangle(boundingBox); 74 | 75 | newBoundingBox.setLocation(apply(boundingBox.getLocation())); 76 | 77 | final Shape newShape = new Shape(shape.getShapeType(), newBoundingBox, shape.getCurrentRotation()); 78 | 79 | shape.getBlocks().forEach(block -> apply(block, newShape)); 80 | 81 | return newShape; 82 | } 83 | 84 | protected void apply(final Block block, final Shape newShape) { 85 | newShape.new Block(apply(block.getLocation())); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/model/Shape.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.model; 10 | 11 | import java.awt.Point; 12 | import java.awt.Rectangle; 13 | import java.util.List; 14 | 15 | import com.google.common.collect.Lists; 16 | 17 | public class Shape { 18 | 19 | private final ShapeType shapeType; 20 | 21 | private final Rectangle boundingBox; 22 | 23 | private final int currentRotation; 24 | 25 | private final List blocks = Lists.newArrayList(); 26 | 27 | public class Block { 28 | 29 | private Point location; 30 | 31 | public Block(final Point location) { 32 | this.location = location; 33 | 34 | getShape().blocks.add(this); 35 | } 36 | 37 | public Shape getShape() { 38 | return Shape.this; 39 | } 40 | 41 | public Point getLocation() { 42 | return location; 43 | } 44 | 45 | public void setLocation(final Point location) { 46 | this.location = location; 47 | } 48 | } 49 | 50 | public Shape(final ShapeType shapeType, final Rectangle boundingBox, final int currentRotation) { 51 | this.shapeType = shapeType; 52 | this.boundingBox = boundingBox; 53 | this.currentRotation = currentRotation; 54 | } 55 | 56 | public ShapeType getShapeType() { 57 | return shapeType; 58 | } 59 | 60 | public List getBlocks() { 61 | return blocks; 62 | } 63 | 64 | public Rectangle getBoundingBox() { 65 | return boundingBox; 66 | } 67 | 68 | public int getCurrentRotation() { 69 | return currentRotation; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/model/ShapeType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.model; 10 | 11 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_0_0; 12 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_0_1; 13 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_0_2; 14 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_1_0; 15 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_1_1; 16 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_1_2; 17 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_1_M1; 18 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_2_0; 19 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_2_1; 20 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_2_2; 21 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_2_M1; 22 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_3_0; 23 | import static spypunk.tetris.constants.TetrisConstants.LOCATION_3_1; 24 | 25 | import java.awt.Point; 26 | import java.awt.Rectangle; 27 | import java.util.Arrays; 28 | import java.util.List; 29 | import java.util.Set; 30 | 31 | import com.google.common.collect.Sets; 32 | 33 | public enum ShapeType { 34 | 35 | O( 36 | Arrays.asList(Sets.newHashSet(LOCATION_0_0, LOCATION_1_0, LOCATION_0_1, LOCATION_1_1)), 37 | new Rectangle(0, 0, 2, 2)), 38 | I( 39 | Arrays.asList(Sets.newHashSet(LOCATION_0_0, LOCATION_1_0, LOCATION_2_0, LOCATION_3_0), 40 | Sets.newHashSet(LOCATION_2_M1, LOCATION_2_0, LOCATION_2_1, LOCATION_2_2), 41 | Sets.newHashSet(LOCATION_0_1, LOCATION_1_1, LOCATION_2_1, LOCATION_3_1), 42 | Sets.newHashSet(LOCATION_1_M1, LOCATION_1_0, LOCATION_1_1, LOCATION_1_2)), 43 | new Rectangle(0, 0, 4, 4)), 44 | J( 45 | Arrays.asList(Sets.newHashSet(LOCATION_0_0, LOCATION_0_1, LOCATION_1_1, LOCATION_2_1), 46 | Sets.newHashSet(LOCATION_1_0, LOCATION_2_0, LOCATION_1_1, LOCATION_1_2), 47 | Sets.newHashSet(LOCATION_0_1, LOCATION_1_1, LOCATION_2_1, LOCATION_2_2), 48 | Sets.newHashSet(LOCATION_1_0, LOCATION_1_1, LOCATION_0_2, LOCATION_1_2)), 49 | new Rectangle(0, 0, 3, 3)), 50 | L( 51 | Arrays.asList(Sets.newHashSet(LOCATION_0_1, LOCATION_1_1, LOCATION_2_1, LOCATION_2_0), 52 | Sets.newHashSet(LOCATION_1_0, LOCATION_1_1, LOCATION_1_2, LOCATION_2_2), 53 | Sets.newHashSet(LOCATION_0_1, LOCATION_1_1, LOCATION_2_1, LOCATION_0_2), 54 | Sets.newHashSet(LOCATION_0_0, LOCATION_1_0, LOCATION_1_1, LOCATION_1_2)), 55 | new Rectangle(0, 0, 3, 3)), 56 | S( 57 | Arrays.asList(Sets.newHashSet(LOCATION_1_0, LOCATION_2_0, LOCATION_0_1, LOCATION_1_1), 58 | Sets.newHashSet(LOCATION_1_0, LOCATION_1_1, LOCATION_2_1, LOCATION_2_2), 59 | Sets.newHashSet(LOCATION_1_1, LOCATION_2_1, LOCATION_0_2, LOCATION_1_2), 60 | Sets.newHashSet(LOCATION_0_0, LOCATION_0_1, LOCATION_1_1, LOCATION_1_2)), 61 | new Rectangle(0, 0, 3, 3)), 62 | T( 63 | Arrays.asList(Sets.newHashSet(LOCATION_1_0, LOCATION_0_1, LOCATION_1_1, LOCATION_2_1), 64 | Sets.newHashSet(LOCATION_1_0, LOCATION_1_1, LOCATION_2_1, LOCATION_1_2), 65 | Sets.newHashSet(LOCATION_0_1, LOCATION_1_1, LOCATION_2_1, LOCATION_1_2), 66 | Sets.newHashSet(LOCATION_1_0, LOCATION_0_1, LOCATION_1_1, LOCATION_1_2)), 67 | new Rectangle(0, 0, 3, 3)), 68 | Z( 69 | Arrays.asList(Sets.newHashSet(LOCATION_0_0, LOCATION_1_0, LOCATION_1_1, LOCATION_2_1), 70 | Sets.newHashSet(LOCATION_2_0, LOCATION_1_1, LOCATION_2_1, LOCATION_1_2), 71 | Sets.newHashSet(LOCATION_0_1, LOCATION_1_1, LOCATION_1_2, LOCATION_2_2), 72 | Sets.newHashSet(LOCATION_1_0, LOCATION_0_1, LOCATION_1_1, LOCATION_0_2)), 73 | new Rectangle(0, 0, 3, 3)); 74 | 75 | private final List> rotations; 76 | 77 | private final Rectangle boundingBox; 78 | 79 | ShapeType(final List> rotations, final Rectangle boundingBox) { 80 | this.rotations = rotations; 81 | this.boundingBox = boundingBox; 82 | } 83 | 84 | public List> getRotations() { 85 | return rotations; 86 | } 87 | 88 | public Rectangle getBoundingBox() { 89 | return boundingBox; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/model/Tetris.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.model; 10 | 11 | import java.awt.Point; 12 | import java.net.URI; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | 17 | import spypunk.tetris.model.Shape.Block; 18 | 19 | public class Tetris { 20 | 21 | public enum State { 22 | STOPPED, 23 | RUNNING { 24 | @Override 25 | public State onPause() { 26 | return PAUSED; 27 | } 28 | }, 29 | PAUSED { 30 | @Override 31 | public State onPause() { 32 | return RUNNING; 33 | } 34 | }, 35 | GAME_OVER; 36 | 37 | public State onPause() { 38 | return this; 39 | } 40 | } 41 | 42 | private final String name; 43 | 44 | private final String version; 45 | 46 | private final URI projectURI; 47 | 48 | private TetrisInstance tetrisInstance; 49 | 50 | private boolean muted; 51 | 52 | public Tetris(final String name, final String version, final URI projectURI) { 53 | this.name = name; 54 | this.version = version; 55 | this.projectURI = projectURI; 56 | 57 | tetrisInstance = new TetrisInstance(); 58 | } 59 | 60 | public String getName() { 61 | return name; 62 | } 63 | 64 | public String getVersion() { 65 | return version; 66 | } 67 | 68 | public URI getProjectURI() { 69 | return projectURI; 70 | } 71 | 72 | public void setTetrisInstance(final TetrisInstance tetrisInstance) { 73 | this.tetrisInstance = tetrisInstance; 74 | } 75 | 76 | public boolean isMuted() { 77 | return muted; 78 | } 79 | 80 | public void setMuted(final boolean muted) { 81 | this.muted = muted; 82 | } 83 | 84 | public Map getBlocks() { 85 | return tetrisInstance.getBlocks(); 86 | } 87 | 88 | public Shape getCurrentShape() { 89 | return tetrisInstance.getCurrentShape(); 90 | } 91 | 92 | public void setCurrentShape(final Shape currentShape) { 93 | tetrisInstance.setCurrentShape(currentShape); 94 | } 95 | 96 | public Shape getNextShape() { 97 | return tetrisInstance.getNextShape(); 98 | } 99 | 100 | public void setNextShape(final Shape nextShape) { 101 | tetrisInstance.setNextShape(nextShape); 102 | } 103 | 104 | public Optional getMovement() { 105 | return tetrisInstance.getMovement(); 106 | } 107 | 108 | public void setMovement(final Optional movement) { 109 | tetrisInstance.setMovement(movement); 110 | } 111 | 112 | public Map getStatistics() { 113 | return tetrisInstance.getStatistics(); 114 | } 115 | 116 | public int getLevel() { 117 | return tetrisInstance.getLevel(); 118 | } 119 | 120 | public void setLevel(final int level) { 121 | tetrisInstance.setLevel(level); 122 | } 123 | 124 | public int getScore() { 125 | return tetrisInstance.getScore(); 126 | } 127 | 128 | public void setScore(final int score) { 129 | tetrisInstance.setScore(score); 130 | } 131 | 132 | public int getCompletedRows() { 133 | return tetrisInstance.getCompletedRows(); 134 | } 135 | 136 | public void setCompletedRows(final int completedRows) { 137 | tetrisInstance.setCompletedRows(completedRows); 138 | } 139 | 140 | public int getSpeed() { 141 | return tetrisInstance.getSpeed(); 142 | } 143 | 144 | public void setSpeed(final int speed) { 145 | tetrisInstance.setSpeed(speed); 146 | } 147 | 148 | public int getCurrentGravityFrame() { 149 | return tetrisInstance.getCurrentGravityFrame(); 150 | } 151 | 152 | public void setCurrentGravityFrame(final int currentGravityFrame) { 153 | tetrisInstance.setCurrentGravityFrame(currentGravityFrame); 154 | } 155 | 156 | public int getCurrentMovementScore() { 157 | return tetrisInstance.getCurrentMovementScore(); 158 | } 159 | 160 | public void setCurrentMovementScore(final int currentMovementScore) { 161 | tetrisInstance.setCurrentMovementScore(currentMovementScore); 162 | } 163 | 164 | public boolean isCurrentShapeLocked() { 165 | return tetrisInstance.isCurrentShapeLocked(); 166 | } 167 | 168 | public void setCurrentShapeLocked(final boolean currentShapeLocked) { 169 | tetrisInstance.setCurrentShapeLocked(currentShapeLocked); 170 | } 171 | 172 | public boolean isHardDropEnabled() { 173 | return tetrisInstance.isHardDropEnabled(); 174 | } 175 | 176 | public void setHardDropEnabled(final boolean hardDropEnabled) { 177 | tetrisInstance.setHardDropEnabled(hardDropEnabled); 178 | } 179 | 180 | public State getState() { 181 | return tetrisInstance.getState(); 182 | } 183 | 184 | public void setState(final State state) { 185 | tetrisInstance.setState(state); 186 | } 187 | 188 | public List getTetrisEvents() { 189 | return tetrisInstance.getTetrisEvents(); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/model/TetrisEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.model; 10 | 11 | public enum TetrisEvent { 12 | SHAPE_LOCKED, 13 | ROWS_COMPLETED, 14 | GAME_OVER 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/model/TetrisInstance.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.model; 10 | 11 | import java.awt.Point; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Optional; 16 | import java.util.function.Function; 17 | import java.util.stream.Collectors; 18 | 19 | import com.google.common.collect.Lists; 20 | import com.google.common.collect.Maps; 21 | 22 | import spypunk.tetris.model.Shape.Block; 23 | import spypunk.tetris.model.Tetris.State; 24 | 25 | public class TetrisInstance { 26 | 27 | private final Map blocks = Maps.newHashMap(); 28 | 29 | private final Map statistics; 30 | 31 | private final List tetrisEvents = Lists.newArrayList(); 32 | 33 | private State state = State.STOPPED; 34 | 35 | private Shape currentShape; 36 | 37 | private Shape nextShape; 38 | 39 | private Optional movement = Optional.empty(); 40 | 41 | private int level; 42 | 43 | private int score; 44 | 45 | private int completedRows; 46 | 47 | private int speed; 48 | 49 | private int currentGravityFrame; 50 | 51 | private int currentMovementScore; 52 | 53 | private boolean currentShapeLocked; 54 | 55 | private boolean hardDropEnabled; 56 | 57 | public TetrisInstance() { 58 | statistics = Arrays.asList(ShapeType.values()).stream() 59 | .collect(Collectors.toMap(Function.identity(), shapeType -> 0)); 60 | } 61 | 62 | public Map getBlocks() { 63 | return blocks; 64 | } 65 | 66 | public Shape getCurrentShape() { 67 | return currentShape; 68 | } 69 | 70 | public void setCurrentShape(final Shape currentShape) { 71 | this.currentShape = currentShape; 72 | } 73 | 74 | public Shape getNextShape() { 75 | return nextShape; 76 | } 77 | 78 | public void setNextShape(final Shape nextShape) { 79 | this.nextShape = nextShape; 80 | } 81 | 82 | public Optional getMovement() { 83 | return movement; 84 | } 85 | 86 | public void setMovement(final Optional movement) { 87 | this.movement = movement; 88 | } 89 | 90 | public Map getStatistics() { 91 | return statistics; 92 | } 93 | 94 | public int getLevel() { 95 | return level; 96 | } 97 | 98 | public void setLevel(final int level) { 99 | this.level = level; 100 | } 101 | 102 | public int getScore() { 103 | return score; 104 | } 105 | 106 | public void setScore(final int score) { 107 | this.score = score; 108 | } 109 | 110 | public int getCompletedRows() { 111 | return completedRows; 112 | } 113 | 114 | public void setCompletedRows(final int completedRows) { 115 | this.completedRows = completedRows; 116 | } 117 | 118 | public int getSpeed() { 119 | return speed; 120 | } 121 | 122 | public void setSpeed(final int speed) { 123 | this.speed = speed; 124 | } 125 | 126 | public int getCurrentGravityFrame() { 127 | return currentGravityFrame; 128 | } 129 | 130 | public void setCurrentGravityFrame(final int currentGravityFrame) { 131 | this.currentGravityFrame = currentGravityFrame; 132 | } 133 | 134 | public int getCurrentMovementScore() { 135 | return currentMovementScore; 136 | } 137 | 138 | public void setCurrentMovementScore(final int currentMovementScore) { 139 | this.currentMovementScore = currentMovementScore; 140 | } 141 | 142 | public boolean isCurrentShapeLocked() { 143 | return currentShapeLocked; 144 | } 145 | 146 | public void setCurrentShapeLocked(final boolean currentShapeLocked) { 147 | this.currentShapeLocked = currentShapeLocked; 148 | } 149 | 150 | public boolean isHardDropEnabled() { 151 | return hardDropEnabled; 152 | } 153 | 154 | public void setHardDropEnabled(final boolean hardDropEnabled) { 155 | this.hardDropEnabled = hardDropEnabled; 156 | } 157 | 158 | public State getState() { 159 | return state; 160 | } 161 | 162 | public void setState(final State state) { 163 | this.state = state; 164 | } 165 | 166 | public List getTetrisEvents() { 167 | return tetrisEvents; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/service/TetrisService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.service; 10 | 11 | import spypunk.tetris.model.Movement; 12 | 13 | public interface TetrisService { 14 | 15 | void start(); 16 | 17 | void update(); 18 | 19 | void move(Movement movement); 20 | 21 | void hardDrop(); 22 | 23 | void pause(); 24 | 25 | void mute(); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/service/TetrisServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.service; 10 | 11 | import static spypunk.tetris.constants.TetrisConstants.HEIGHT; 12 | import static spypunk.tetris.constants.TetrisConstants.WIDTH; 13 | 14 | import java.awt.Point; 15 | import java.awt.Rectangle; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.Optional; 19 | import java.util.stream.Collectors; 20 | import java.util.stream.IntStream; 21 | 22 | import javax.inject.Inject; 23 | import javax.inject.Singleton; 24 | 25 | import com.google.common.collect.ImmutableMap; 26 | import com.google.common.collect.Maps; 27 | 28 | import spypunk.tetris.factory.ShapeFactory; 29 | import spypunk.tetris.guice.TetrisModule.TetrisProvider; 30 | import spypunk.tetris.model.Movement; 31 | import spypunk.tetris.model.Shape; 32 | import spypunk.tetris.model.Shape.Block; 33 | import spypunk.tetris.model.ShapeType; 34 | import spypunk.tetris.model.Tetris; 35 | import spypunk.tetris.model.Tetris.State; 36 | import spypunk.tetris.model.TetrisEvent; 37 | import spypunk.tetris.model.TetrisInstance; 38 | 39 | @Singleton 40 | public class TetrisServiceImpl implements TetrisService { 41 | 42 | private static final int ROWS_PER_LEVEL = 10; 43 | 44 | private final ShapeFactory shapeFactory; 45 | 46 | private final Map scorePerRows = ImmutableMap.of(1, 40, 2, 100, 3, 300, 4, 1200); 47 | 48 | private final Map levelSpeeds = createLevelSpeeds(); 49 | 50 | private final Rectangle gridRectangle = new Rectangle(0, 0, WIDTH, HEIGHT); 51 | 52 | private final Tetris tetris; 53 | 54 | @Inject 55 | public TetrisServiceImpl(final ShapeFactory shapeFactory, @TetrisProvider final Tetris tetris) { 56 | this.shapeFactory = shapeFactory; 57 | this.tetris = tetris; 58 | } 59 | 60 | @Override 61 | public void start() { 62 | final int speed = getLevelSpeed(0); 63 | 64 | tetris.setTetrisInstance(new TetrisInstance()); 65 | tetris.setSpeed(speed); 66 | tetris.setNextShape(shapeFactory.createRandomShape()); 67 | tetris.setState(State.RUNNING); 68 | 69 | generateNextShape(); 70 | } 71 | 72 | @Override 73 | public void update() { 74 | applyGravity(); 75 | applyMovement(); 76 | } 77 | 78 | @Override 79 | public void pause() { 80 | tetris.setState(tetris.getState().onPause()); 81 | } 82 | 83 | @Override 84 | public void move(final Movement movement) { 85 | if (isMovementAllowed()) { 86 | tetris.setMovement(Optional.of(movement)); 87 | } 88 | } 89 | 90 | @Override 91 | public void hardDrop() { 92 | if (isMovementAllowed()) { 93 | tetris.setHardDropEnabled(true); 94 | } 95 | } 96 | 97 | @Override 98 | public void mute() { 99 | tetris.setMuted(!tetris.isMuted()); 100 | } 101 | 102 | private void applyGravity() { 103 | if (!isTetrisRunning()) { 104 | return; 105 | } 106 | 107 | if (!isTimeToApplyGravity()) { 108 | incrementGravityFrame(); 109 | return; 110 | } 111 | 112 | if (isCurrentShapeLocked()) { 113 | clearCompleteRows(); 114 | generateNextShape(); 115 | checkShapeLocked(); 116 | } else { 117 | moveShapeDown(); 118 | } 119 | 120 | resetCurrentGravityFrame(); 121 | } 122 | 123 | private boolean isCurrentShapeLocked() { 124 | return tetris.isCurrentShapeLocked(); 125 | } 126 | 127 | private void incrementGravityFrame() { 128 | tetris.setCurrentGravityFrame(tetris.getCurrentGravityFrame() + 1); 129 | } 130 | 131 | private void applyMovement() { 132 | if (!isMovementAllowed()) { 133 | return; 134 | } 135 | 136 | final Optional optionalMovement = tetris.isHardDropEnabled() 137 | ? Optional.of(Movement.DOWN) 138 | : tetris.getMovement(); 139 | 140 | if (optionalMovement.isPresent()) { 141 | final Movement movement = optionalMovement.get(); 142 | 143 | tetris.setMovement(Optional.empty()); 144 | 145 | final boolean isDownMovement = Movement.DOWN.equals(movement); 146 | 147 | if (isDownMovement || canShapeMove(movement)) { 148 | moveShape(movement); 149 | 150 | if (isDownMovement) { 151 | updateScoreWithCompletedMovement(); 152 | } 153 | 154 | if (isCurrentShapeLocked()) { 155 | resetCurrentGravityFrame(); 156 | } 157 | } 158 | } 159 | } 160 | 161 | private void checkShapeLocked() { 162 | if (canShapeMove(Movement.DOWN)) { 163 | return; 164 | } 165 | 166 | tetris.getCurrentShape().getBlocks() 167 | .forEach(block -> tetris.getBlocks().put(block.getLocation(), block)); 168 | 169 | if (isGameOver()) { 170 | tetris.setState(State.GAME_OVER); 171 | tetris.getTetrisEvents().add(TetrisEvent.GAME_OVER); 172 | } else { 173 | tetris.getTetrisEvents().add(TetrisEvent.SHAPE_LOCKED); 174 | tetris.setCurrentShapeLocked(true); 175 | } 176 | 177 | tetris.setHardDropEnabled(false); 178 | } 179 | 180 | private void generateNextShape() { 181 | final Shape currentShape = tetris.getNextShape(); 182 | 183 | tetris.setCurrentShape(currentShape); 184 | tetris.setCurrentShapeLocked(false); 185 | tetris.setNextShape(shapeFactory.createRandomShape()); 186 | 187 | updateStatistics(); 188 | } 189 | 190 | private void updateStatistics() { 191 | final ShapeType shapeType = tetris.getCurrentShape().getShapeType(); 192 | final Map statistics = tetris.getStatistics(); 193 | final Integer count = statistics.get(shapeType); 194 | 195 | statistics.put(shapeType, count + 1); 196 | } 197 | 198 | private boolean isGameOver() { 199 | return tetris.getBlocks().values().stream() 200 | .anyMatch(block -> block.getLocation().y == 0); 201 | } 202 | 203 | private boolean isTimeToApplyGravity() { 204 | return tetris.getCurrentGravityFrame() == tetris.getSpeed(); 205 | } 206 | 207 | private void clearCompleteRows() { 208 | final List completeRows = IntStream.range(0, HEIGHT) 209 | .filter(this::isRowComplete).boxed().collect(Collectors.toList()); 210 | 211 | final int completedRows = completeRows.size(); 212 | 213 | if (completedRows == 0) { 214 | return; 215 | } 216 | 217 | completeRows.forEach(this::clearCompleteRow); 218 | 219 | tetris.setCompletedRows(tetris.getCompletedRows() + completedRows); 220 | 221 | updateScoreWithCompletedRows(completedRows); 222 | updateLevel(); 223 | 224 | tetris.getTetrisEvents().add(TetrisEvent.ROWS_COMPLETED); 225 | } 226 | 227 | private void updateLevel() { 228 | final int completedRows = tetris.getCompletedRows(); 229 | final int nextLevel = tetris.getLevel() + 1; 230 | 231 | if (completedRows >= ROWS_PER_LEVEL * nextLevel) { 232 | tetris.setLevel(nextLevel); 233 | tetris.setSpeed(getLevelSpeed(nextLevel)); 234 | } 235 | } 236 | 237 | private void updateScoreWithCompletedMovement() { 238 | tetris.setScore(tetris.getScore() + 1); 239 | } 240 | 241 | private void updateScoreWithCompletedRows(final int completedRows) { 242 | final Integer rowsScore = scorePerRows.get(completedRows); 243 | final int score = tetris.getScore(); 244 | 245 | tetris.setScore(score + rowsScore * (tetris.getLevel() + 1)); 246 | } 247 | 248 | private void clearCompleteRow(final int row) { 249 | final Map blocks = tetris.getBlocks(); 250 | 251 | final List blocksToMoveDown = blocks.values().stream() 252 | .filter(block -> block.getLocation().y < row) 253 | .collect(Collectors.toList()); 254 | 255 | IntStream.range(0, WIDTH) 256 | .forEach(column -> clearBlockAt(new Point(column, row))); 257 | 258 | blocksToMoveDown.forEach(block -> clearBlockAt(block.getLocation())); 259 | blocksToMoveDown.forEach(this::moveBlockDown); 260 | } 261 | 262 | private void clearBlockAt(final Point location) { 263 | tetris.getBlocks().remove(location); 264 | } 265 | 266 | private boolean isRowComplete(final int row) { 267 | return IntStream.range(0, WIDTH) 268 | .allMatch(column -> tetris.getBlocks().containsKey(new Point(column, row))); 269 | } 270 | 271 | private void moveShape(final Movement movement) { 272 | final Shape currentShape = tetris.getCurrentShape(); 273 | final Shape newShape = movement.apply(currentShape); 274 | 275 | tetris.setCurrentShape(newShape); 276 | 277 | checkShapeLocked(); 278 | } 279 | 280 | private void moveShapeDown() { 281 | moveShape(Movement.DOWN); 282 | } 283 | 284 | private void moveBlockDown(final Block block) { 285 | final Point location = block.getLocation(); 286 | final Point newLocation = Movement.DOWN.apply(location); 287 | 288 | block.setLocation(newLocation); 289 | 290 | tetris.getBlocks().put(block.getLocation(), block); 291 | } 292 | 293 | private boolean canShapeMove(final Movement movement) { 294 | final Shape currentShape = tetris.getCurrentShape(); 295 | final Shape newShape = movement.apply(currentShape); 296 | 297 | return newShape.getBlocks().stream().map(Block::getLocation).allMatch(this::canBlockMove); 298 | } 299 | 300 | private boolean canBlockMove(final Point location) { 301 | return gridRectangle.contains(location) && !tetris.getBlocks().containsKey(location); 302 | } 303 | 304 | private boolean isTetrisRunning() { 305 | return tetris.getState().equals(State.RUNNING); 306 | } 307 | 308 | private void resetCurrentGravityFrame() { 309 | tetris.setCurrentGravityFrame(0); 310 | } 311 | 312 | private static Map createLevelSpeeds() { 313 | final int initialSpeed = 48; 314 | final Map levelSpeeds = Maps.newHashMap(); 315 | 316 | levelSpeeds.put(0, initialSpeed); 317 | levelSpeeds.put(9, 6); 318 | 319 | IntStream.range(1, 9).forEach(level -> levelSpeeds.put(level, initialSpeed - 5 * level)); 320 | IntStream.range(10, 13).forEach(level -> levelSpeeds.put(level, 5)); 321 | IntStream.range(13, 16).forEach(level -> levelSpeeds.put(level, 4)); 322 | IntStream.range(16, 19).forEach(level -> levelSpeeds.put(level, 3)); 323 | IntStream.range(19, 29).forEach(level -> levelSpeeds.put(level, 2)); 324 | 325 | return levelSpeeds; 326 | } 327 | 328 | private int getLevelSpeed(final int level) { 329 | return level < 29 ? levelSpeeds.get(level) : 1; 330 | } 331 | 332 | private boolean isMovementAllowed() { 333 | return isTetrisRunning() && !isCurrentShapeLocked(); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/Sound.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound; 10 | 11 | import javax.sound.sampled.spi.AudioFileReader; 12 | 13 | import javazoom.spi.mpeg.sampled.file.MpegAudioFileReader; 14 | 15 | public enum Sound { 16 | 17 | SHAPE_LOCKED(Format.MP3, false), 18 | GAME_OVER(Format.MP3, true), 19 | BACKGROUND(Format.MP3, true), 20 | ROWS_COMPLETED(Format.MP3, false); 21 | 22 | public enum Format { 23 | MP3 { 24 | @Override 25 | public AudioFileReader getFileReader() { 26 | return new MpegAudioFileReader(); 27 | } 28 | }; 29 | 30 | public abstract AudioFileReader getFileReader(); 31 | } 32 | 33 | private final String fileName; 34 | 35 | private final Format format; 36 | 37 | private final boolean loop; 38 | 39 | Sound(final Format format, final boolean loop) { 40 | fileName = name().toLowerCase() + "." + format.name().toLowerCase(); 41 | this.format = format; 42 | this.loop = loop; 43 | } 44 | 45 | public String getFileName() { 46 | return fileName; 47 | } 48 | 49 | public Format getFormat() { 50 | return format; 51 | } 52 | 53 | public boolean isLoop() { 54 | return loop; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/SoundClip.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound; 10 | 11 | public interface SoundClip { 12 | 13 | void play(); 14 | 15 | void pause(); 16 | 17 | void stop(); 18 | 19 | void setMuted(boolean muted); 20 | 21 | void increaseVolume(); 22 | 23 | void decreaseVolume(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/SoundClipImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound; 10 | 11 | import java.io.IOException; 12 | 13 | import javax.sound.sampled.AudioFormat; 14 | import javax.sound.sampled.AudioFormat.Encoding; 15 | import javax.sound.sampled.AudioInputStream; 16 | import javax.sound.sampled.AudioSystem; 17 | import javax.sound.sampled.Clip; 18 | import javax.sound.sampled.FloatControl; 19 | import javax.sound.sampled.LineUnavailableException; 20 | 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import spypunk.tetris.exception.TetrisException; 25 | 26 | public class SoundClipImpl implements SoundClip { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(SoundClipImpl.class); 29 | 30 | private static final float VOLUME_DELTA = 5F; 31 | 32 | private final Clip clip; 33 | 34 | private final boolean loop; 35 | 36 | private final FloatControl volumeControl; 37 | 38 | private long currentFramePosition; 39 | 40 | private float minimumVolume; 41 | 42 | private float maximumVolume; 43 | 44 | private float currentVolume; 45 | 46 | public SoundClipImpl(final AudioInputStream inputStream, final boolean loop) { 47 | this.loop = loop; 48 | 49 | try { 50 | clip = AudioSystem.getClip(); 51 | 52 | final AudioFormat audioFormat = getOutFormat(inputStream.getFormat()); 53 | final AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(audioFormat, 54 | inputStream); 55 | 56 | clip.open(audioInputStream); 57 | 58 | volumeControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); 59 | maximumVolume = volumeControl.getValue(); 60 | minimumVolume = volumeControl.getMinimum(); 61 | currentVolume = maximumVolume; 62 | } catch (LineUnavailableException | IOException e) { 63 | LOGGER.error(e.getMessage(), e); 64 | throw new TetrisException(e); 65 | } 66 | } 67 | 68 | @Override 69 | public void play() { 70 | clip.setMicrosecondPosition(currentFramePosition); 71 | 72 | if (loop) { 73 | clip.loop(Clip.LOOP_CONTINUOUSLY); 74 | } else { 75 | clip.start(); 76 | } 77 | } 78 | 79 | @Override 80 | public void pause() { 81 | currentFramePosition = clip.getMicrosecondPosition(); 82 | 83 | clip.stop(); 84 | } 85 | 86 | @Override 87 | public void stop() { 88 | clip.stop(); 89 | currentFramePosition = 0; 90 | } 91 | 92 | @Override 93 | public void setMuted(final boolean muted) { 94 | volumeControl.setValue(muted ? minimumVolume : currentVolume); 95 | } 96 | 97 | @Override 98 | public void increaseVolume() { 99 | updateVolume(VOLUME_DELTA); 100 | } 101 | 102 | @Override 103 | public void decreaseVolume() { 104 | updateVolume(-VOLUME_DELTA); 105 | } 106 | 107 | private void updateVolume(final float delta) { 108 | currentVolume += delta; 109 | 110 | if (currentVolume < minimumVolume) { 111 | currentVolume = minimumVolume; 112 | } else if (currentVolume > maximumVolume) { 113 | currentVolume = maximumVolume; 114 | } 115 | 116 | volumeControl.setValue(currentVolume); 117 | } 118 | 119 | private AudioFormat getOutFormat(final AudioFormat inFormat) { 120 | final int ch = inFormat.getChannels(); 121 | final float rate = inFormat.getSampleRate(); 122 | return new AudioFormat(Encoding.PCM_SIGNED, rate, 16, ch, ch * 2, rate, false); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/cache/SoundClipCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound.cache; 10 | 11 | import java.util.Collection; 12 | 13 | import spypunk.tetris.sound.Sound; 14 | import spypunk.tetris.sound.SoundClip; 15 | 16 | public interface SoundClipCache { 17 | 18 | SoundClip getSoundClip(Sound sound); 19 | 20 | Collection getAllSoundClips(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/cache/SoundClipCacheImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound.cache; 10 | 11 | import java.io.BufferedInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.util.Arrays; 15 | import java.util.Collection; 16 | import java.util.Map; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | import javax.inject.Singleton; 21 | import javax.sound.sampled.AudioInputStream; 22 | import javax.sound.sampled.AudioSystem; 23 | import javax.sound.sampled.UnsupportedAudioFileException; 24 | 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import spypunk.tetris.exception.TetrisException; 29 | import spypunk.tetris.sound.Sound; 30 | import spypunk.tetris.sound.SoundClip; 31 | import spypunk.tetris.sound.SoundClipImpl; 32 | import spypunk.tetris.sound.service.SoundServiceImpl; 33 | 34 | @Singleton 35 | public class SoundClipCacheImpl implements SoundClipCache { 36 | 37 | private static final Logger LOGGER = LoggerFactory.getLogger(SoundClipCacheImpl.class); 38 | 39 | private static final String SOUNDS_FOLDER = "/sound/"; 40 | 41 | private final Map soundClips = createSoundClips(); 42 | 43 | @Override 44 | public SoundClip getSoundClip(final Sound sound) { 45 | return soundClips.get(sound); 46 | } 47 | 48 | @Override 49 | public Collection getAllSoundClips() { 50 | return soundClips.values(); 51 | } 52 | 53 | private static SoundClip createSoundClip(final Sound sound) { 54 | try (InputStream inputStream = SoundServiceImpl.class 55 | .getResourceAsStream(SOUNDS_FOLDER + sound.getFileName()); 56 | AudioInputStream audioInputStream = AudioSystem 57 | .getAudioInputStream(new BufferedInputStream(inputStream))) { 58 | 59 | return new SoundClipImpl(audioInputStream, sound.isLoop()); 60 | } catch (UnsupportedAudioFileException | IOException e) { 61 | LOGGER.error(e.getMessage(), e); 62 | throw new TetrisException(e); 63 | } 64 | } 65 | 66 | private static Map createSoundClips() { 67 | return Arrays.asList(Sound.values()).stream() 68 | .collect(Collectors.toMap(Function.identity(), SoundClipCacheImpl::createSoundClip)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/service/SoundService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound.service; 10 | 11 | import spypunk.tetris.sound.Sound; 12 | 13 | public interface SoundService { 14 | 15 | void playSound(Sound sound); 16 | 17 | void playMusic(Sound sound); 18 | 19 | void pauseMusic(); 20 | 21 | void resumeMusic(); 22 | 23 | void stopMusic(); 24 | 25 | void setMuted(boolean muted); 26 | 27 | void increaseVolume(); 28 | 29 | void decreaseVolume(); 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/sound/service/SoundServiceImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.sound.service; 10 | 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | import spypunk.tetris.sound.Sound; 15 | import spypunk.tetris.sound.SoundClip; 16 | import spypunk.tetris.sound.cache.SoundClipCache; 17 | 18 | @Singleton 19 | public class SoundServiceImpl implements SoundService { 20 | 21 | private final SoundClipCache soundClipCache; 22 | 23 | private SoundClip currentMusicSoundClip; 24 | 25 | @Inject 26 | public SoundServiceImpl(final SoundClipCache soundClipCache) { 27 | this.soundClipCache = soundClipCache; 28 | } 29 | 30 | @Override 31 | public void playMusic(final Sound sound) { 32 | stopMusic(); 33 | 34 | currentMusicSoundClip = soundClipCache.getSoundClip(sound); 35 | 36 | currentMusicSoundClip.play(); 37 | } 38 | 39 | @Override 40 | public void pauseMusic() { 41 | if (currentMusicSoundClip != null) { 42 | currentMusicSoundClip.pause(); 43 | } 44 | } 45 | 46 | @Override 47 | public void resumeMusic() { 48 | if (currentMusicSoundClip != null) { 49 | currentMusicSoundClip.play(); 50 | } 51 | } 52 | 53 | @Override 54 | public void stopMusic() { 55 | if (currentMusicSoundClip != null) { 56 | currentMusicSoundClip.stop(); 57 | currentMusicSoundClip = null; 58 | } 59 | } 60 | 61 | @Override 62 | public void playSound(final Sound sound) { 63 | final SoundClip clip = soundClipCache.getSoundClip(sound); 64 | 65 | clip.stop(); 66 | clip.play(); 67 | } 68 | 69 | @Override 70 | public void setMuted(final boolean muted) { 71 | soundClipCache.getAllSoundClips().forEach(soundClip -> soundClip.setMuted(muted)); 72 | } 73 | 74 | @Override 75 | public void increaseVolume() { 76 | soundClipCache.getAllSoundClips().forEach(SoundClip::increaseVolume); 77 | } 78 | 79 | @Override 80 | public void decreaseVolume() { 81 | soundClipCache.getAllSoundClips().forEach(SoundClip::decreaseVolume); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/cache/ImageCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.cache; 10 | 11 | import java.awt.Image; 12 | 13 | import spypunk.tetris.model.ShapeType; 14 | import spypunk.tetris.ui.icon.Icon; 15 | 16 | public interface ImageCache { 17 | 18 | Image getIcon(Icon icon); 19 | 20 | Image getBlockImage(ShapeType shapeType); 21 | 22 | Image getShapeImage(ShapeType shapeType); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/cache/ImageCacheImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.cache; 10 | 11 | import java.awt.Image; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.util.Arrays; 15 | import java.util.Map; 16 | import java.util.function.Function; 17 | import java.util.stream.Collectors; 18 | 19 | import javax.imageio.ImageIO; 20 | import javax.inject.Singleton; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import spypunk.tetris.exception.TetrisException; 26 | import spypunk.tetris.model.ShapeType; 27 | import spypunk.tetris.ui.icon.Icon; 28 | 29 | @Singleton 30 | public class ImageCacheImpl implements ImageCache { 31 | 32 | private static final Logger LOGGER = LoggerFactory.getLogger(ImageCacheImpl.class); 33 | 34 | private static final String IMAGE_FILE_PATTERN = "%s%s.png"; 35 | 36 | private static final String ICONS_FOLDER = "/img/icons/"; 37 | 38 | private static final String BLOCKS_FOLDER = "/img/blocks/"; 39 | 40 | private static final String SHAPES_FOLDER = "/img/shapes/"; 41 | 42 | private final Map icons = createIcons(); 43 | 44 | private final Map blockImages = createBlockImages(); 45 | 46 | private final Map shapeImages = createShapeImages(); 47 | 48 | @Override 49 | public Image getIcon(final Icon icon) { 50 | return icons.get(icon); 51 | } 52 | 53 | @Override 54 | public Image getBlockImage(final ShapeType shapeType) { 55 | return blockImages.get(shapeType); 56 | } 57 | 58 | @Override 59 | public Image getShapeImage(final ShapeType shapeType) { 60 | return shapeImages.get(shapeType); 61 | } 62 | 63 | private static Image createImage(final String imageFolder, final ShapeType shapeType) { 64 | return createImage(imageFolder, shapeType.name()); 65 | } 66 | 67 | private static Image createIcon(final Icon icon) { 68 | return createImage(ICONS_FOLDER, icon.name().toLowerCase()); 69 | } 70 | 71 | private static Image createImage(final String imageFolder, final String fileName) { 72 | final String resourceName = String.format(IMAGE_FILE_PATTERN, imageFolder, fileName); 73 | 74 | try (InputStream inputStream = ImageCacheImpl.class.getResourceAsStream(resourceName)) { 75 | return ImageIO.read(inputStream); 76 | } catch (final IOException e) { 77 | LOGGER.error(e.getMessage(), e); 78 | throw new TetrisException(e); 79 | } 80 | } 81 | 82 | private static Map createIcons() { 83 | return Arrays.asList(Icon.values()).stream().collect(Collectors.toMap(Function.identity(), 84 | ImageCacheImpl::createIcon)); 85 | } 86 | 87 | private static Map createShapeImages() { 88 | return Arrays.asList(ShapeType.values()).stream().collect(Collectors.toMap(Function.identity(), 89 | shapeType -> createImage(SHAPES_FOLDER, shapeType))); 90 | } 91 | 92 | private static Map createBlockImages() { 93 | return Arrays.asList(ShapeType.values()).stream().collect(Collectors.toMap(Function.identity(), 94 | shapeType -> createImage(BLOCKS_FOLDER, shapeType))); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/constants/TetrisUIConstants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.constants; 10 | 11 | import java.awt.Color; 12 | 13 | public final class TetrisUIConstants { 14 | 15 | public static final int BLOCK_SIZE = 32; 16 | 17 | public static final Color DEFAULT_FONT_COLOR = Color.LIGHT_GRAY; 18 | 19 | public static final Color DEFAULT_BORDER_COLOR = Color.GRAY; 20 | 21 | private TetrisUIConstants() { 22 | throw new IllegalAccessError(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/TetrisController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller; 10 | 11 | public interface TetrisController { 12 | 13 | void start(); 14 | 15 | void onWindowClosed(); 16 | 17 | void onProjectURLClicked(); 18 | 19 | void onKeyPressed(int keyCode); 20 | 21 | void onKeyReleased(int keyCode); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/TetrisControllerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller; 10 | 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | import spypunk.tetris.ui.controller.gameloop.TetrisControllerGameLoop; 15 | import spypunk.tetris.ui.controller.input.TetrisControllerInputHandler; 16 | 17 | @Singleton 18 | public class TetrisControllerImpl implements TetrisController { 19 | 20 | private final TetrisControllerGameLoop tetrisControllerGameLoop; 21 | 22 | private final TetrisControllerInputHandler tetrisControllerInputHandler; 23 | 24 | @Inject 25 | public TetrisControllerImpl(final TetrisControllerGameLoop tetrisControllerGameLoop, 26 | final TetrisControllerInputHandler tetrisControllerInputHandler) { 27 | 28 | this.tetrisControllerGameLoop = tetrisControllerGameLoop; 29 | this.tetrisControllerInputHandler = tetrisControllerInputHandler; 30 | } 31 | 32 | @Override 33 | public void start() { 34 | tetrisControllerGameLoop.start(); 35 | } 36 | 37 | @Override 38 | public void onWindowClosed() { 39 | tetrisControllerGameLoop.stop(); 40 | } 41 | 42 | @Override 43 | public void onProjectURLClicked() { 44 | tetrisControllerInputHandler.onProjectURLClicked(); 45 | } 46 | 47 | @Override 48 | public void onKeyPressed(final int keyCode) { 49 | tetrisControllerInputHandler.onKeyPressed(keyCode); 50 | } 51 | 52 | @Override 53 | public void onKeyReleased(final int keyCode) { 54 | tetrisControllerInputHandler.onKeyReleased(keyCode); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/command/TetrisControllerCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.command; 10 | 11 | @FunctionalInterface 12 | public interface TetrisControllerCommand { 13 | 14 | void execute(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.command.cache; 10 | 11 | import spypunk.tetris.ui.controller.command.TetrisControllerCommand; 12 | 13 | public interface TetrisControllerCommandCache { 14 | 15 | public enum TetrisControllerCommandType { 16 | LEFT, 17 | RIGHT, 18 | DOWN, 19 | ROTATE, 20 | NEW_GAME, 21 | PAUSE, 22 | MUTE, 23 | DECREASE_VOLUME, 24 | INCREASE_VOLUME, 25 | HARD_DROP, 26 | OPEN_PROJECT_URL, 27 | SHAPE_LOCKED, 28 | GAME_OVER, 29 | ROWS_COMPLETED 30 | } 31 | 32 | TetrisControllerCommand getTetrisControllerCommand(TetrisControllerCommandType tetrisControllerCommandType); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/command/cache/TetrisControllerCommandCacheImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.command.cache; 10 | 11 | import java.util.Map; 12 | 13 | import javax.inject.Inject; 14 | import javax.inject.Singleton; 15 | 16 | import com.google.common.collect.Maps; 17 | 18 | import spypunk.tetris.guice.TetrisModule.TetrisProvider; 19 | import spypunk.tetris.model.Movement; 20 | import spypunk.tetris.model.Tetris; 21 | import spypunk.tetris.model.Tetris.State; 22 | import spypunk.tetris.service.TetrisService; 23 | import spypunk.tetris.sound.Sound; 24 | import spypunk.tetris.sound.service.SoundService; 25 | import spypunk.tetris.ui.controller.command.TetrisControllerCommand; 26 | import spypunk.tetris.ui.util.SwingUtils; 27 | import spypunk.tetris.ui.view.TetrisMainView; 28 | 29 | @Singleton 30 | public class TetrisControllerCommandCacheImpl implements TetrisControllerCommandCache { 31 | 32 | private final TetrisService tetrisService; 33 | 34 | private final SoundService soundService; 35 | 36 | private final Tetris tetris; 37 | 38 | private final TetrisMainView tetrisMainView; 39 | 40 | private final Map tetrisControllerCommands = Maps 41 | .newHashMap(); 42 | 43 | @Inject 44 | public TetrisControllerCommandCacheImpl(final TetrisService tetrisService, 45 | final SoundService soundService, 46 | @TetrisProvider final Tetris tetris, 47 | final TetrisMainView tetrisMainView) { 48 | this.tetrisService = tetrisService; 49 | this.soundService = soundService; 50 | this.tetris = tetris; 51 | this.tetrisMainView = tetrisMainView; 52 | 53 | tetrisControllerCommands.put(TetrisControllerCommandType.DOWN, createMoveCommand(Movement.DOWN)); 54 | tetrisControllerCommands.put(TetrisControllerCommandType.LEFT, createMoveCommand(Movement.LEFT)); 55 | tetrisControllerCommands.put(TetrisControllerCommandType.RIGHT, createMoveCommand(Movement.RIGHT)); 56 | tetrisControllerCommands.put(TetrisControllerCommandType.ROTATE, createMoveCommand(Movement.ROTATE)); 57 | tetrisControllerCommands.put(TetrisControllerCommandType.HARD_DROP, createHardDropCommand()); 58 | tetrisControllerCommands.put(TetrisControllerCommandType.DECREASE_VOLUME, createDecreaseVolumeCommand()); 59 | tetrisControllerCommands.put(TetrisControllerCommandType.INCREASE_VOLUME, createIncreaseVolumeCommand()); 60 | tetrisControllerCommands.put(TetrisControllerCommandType.MUTE, createMuteCommand()); 61 | tetrisControllerCommands.put(TetrisControllerCommandType.NEW_GAME, createNewGameCommand()); 62 | tetrisControllerCommands.put(TetrisControllerCommandType.OPEN_PROJECT_URL, createOpenProjectURLCommand()); 63 | tetrisControllerCommands.put(TetrisControllerCommandType.PAUSE, createPauseCommand()); 64 | tetrisControllerCommands.put(TetrisControllerCommandType.SHAPE_LOCKED, createShapeLockedCommand()); 65 | tetrisControllerCommands.put(TetrisControllerCommandType.GAME_OVER, createGameOverCommand()); 66 | tetrisControllerCommands.put(TetrisControllerCommandType.ROWS_COMPLETED, createRowsCompletedCommand()); 67 | } 68 | 69 | @Override 70 | public TetrisControllerCommand getTetrisControllerCommand( 71 | final TetrisControllerCommandType tetrisControllerCommandType) { 72 | 73 | return tetrisControllerCommands.get(tetrisControllerCommandType); 74 | } 75 | 76 | private TetrisControllerCommand createNewGameCommand() { 77 | return () -> { 78 | tetrisService.start(); 79 | soundService.playMusic(Sound.BACKGROUND); 80 | }; 81 | } 82 | 83 | private TetrisControllerCommand createPauseCommand() { 84 | return () -> { 85 | tetrisService.pause(); 86 | 87 | final State state = tetris.getState(); 88 | 89 | if (State.PAUSED.equals(state)) { 90 | soundService.pauseMusic(); 91 | } else if (State.RUNNING.equals(state)) { 92 | soundService.resumeMusic(); 93 | } 94 | }; 95 | } 96 | 97 | private TetrisControllerCommand createMoveCommand(final Movement movement) { 98 | return () -> tetrisService.move(movement); 99 | } 100 | 101 | private TetrisControllerCommand createShapeLockedCommand() { 102 | return () -> soundService.playSound(Sound.SHAPE_LOCKED); 103 | } 104 | 105 | private TetrisControllerCommand createMuteCommand() { 106 | return () -> { 107 | tetrisService.mute(); 108 | 109 | final boolean muted = tetris.isMuted(); 110 | 111 | tetrisMainView.setMuted(muted); 112 | soundService.setMuted(muted); 113 | }; 114 | } 115 | 116 | private TetrisControllerCommand createGameOverCommand() { 117 | return () -> soundService.playMusic(Sound.GAME_OVER); 118 | } 119 | 120 | private TetrisControllerCommand createRowsCompletedCommand() { 121 | return () -> soundService.playSound(Sound.ROWS_COMPLETED); 122 | } 123 | 124 | private TetrisControllerCommand createIncreaseVolumeCommand() { 125 | return soundService::increaseVolume; 126 | } 127 | 128 | private TetrisControllerCommand createDecreaseVolumeCommand() { 129 | return soundService::decreaseVolume; 130 | } 131 | 132 | private TetrisControllerCommand createHardDropCommand() { 133 | return tetrisService::hardDrop; 134 | } 135 | 136 | private TetrisControllerCommand createOpenProjectURLCommand() { 137 | return () -> SwingUtils.openURI(tetris.getProjectURI()); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/event/TetrisControllerTetrisEventHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.event; 10 | 11 | @FunctionalInterface 12 | public interface TetrisControllerTetrisEventHandler { 13 | 14 | void handleEvents(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/event/TetrisControllerTetrisEventHandlerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.event; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | import javax.inject.Inject; 15 | import javax.inject.Singleton; 16 | 17 | import com.google.common.collect.Maps; 18 | 19 | import spypunk.tetris.guice.TetrisModule.TetrisProvider; 20 | import spypunk.tetris.model.Tetris; 21 | import spypunk.tetris.model.TetrisEvent; 22 | import spypunk.tetris.ui.controller.command.TetrisControllerCommand; 23 | import spypunk.tetris.ui.controller.command.cache.TetrisControllerCommandCache; 24 | import spypunk.tetris.ui.controller.command.cache.TetrisControllerCommandCache.TetrisControllerCommandType; 25 | 26 | @Singleton 27 | public class TetrisControllerTetrisEventHandlerImpl implements TetrisControllerTetrisEventHandler { 28 | 29 | private final Map tetrisControllerCommandTypes = Maps 30 | .newHashMap(); 31 | 32 | private final Tetris tetris; 33 | 34 | private final TetrisControllerCommandCache tetrisControllerCommandCache; 35 | 36 | @Inject 37 | public TetrisControllerTetrisEventHandlerImpl(final TetrisControllerCommandCache tetrisControllerCommandCache, 38 | @TetrisProvider final Tetris tetris) { 39 | 40 | this.tetris = tetris; 41 | this.tetrisControllerCommandCache = tetrisControllerCommandCache; 42 | 43 | tetrisControllerCommandTypes.put(TetrisEvent.SHAPE_LOCKED, TetrisControllerCommandType.SHAPE_LOCKED); 44 | tetrisControllerCommandTypes.put(TetrisEvent.GAME_OVER, TetrisControllerCommandType.GAME_OVER); 45 | tetrisControllerCommandTypes.put(TetrisEvent.ROWS_COMPLETED, TetrisControllerCommandType.ROWS_COMPLETED); 46 | } 47 | 48 | @Override 49 | public void handleEvents() { 50 | final List tetrisEvents = tetris.getTetrisEvents(); 51 | 52 | if (tetrisEvents.isEmpty()) { 53 | return; 54 | } 55 | 56 | tetrisEvents.stream() 57 | .map(tetrisControllerCommandTypes::get) 58 | .map(tetrisControllerCommandCache::getTetrisControllerCommand) 59 | .forEach(TetrisControllerCommand::execute); 60 | 61 | tetrisEvents.clear(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/gameloop/TetrisControllerGameLoop.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.gameloop; 10 | 11 | public interface TetrisControllerGameLoop { 12 | 13 | void start(); 14 | 15 | void stop(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/gameloop/TetrisControllerGameLoopImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.gameloop; 10 | 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | 14 | import javax.inject.Inject; 15 | import javax.inject.Singleton; 16 | 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import spypunk.tetris.service.TetrisService; 21 | import spypunk.tetris.ui.controller.event.TetrisControllerTetrisEventHandler; 22 | import spypunk.tetris.ui.controller.input.TetrisControllerInputHandler; 23 | import spypunk.tetris.ui.view.TetrisMainView; 24 | 25 | @Singleton 26 | public final class TetrisControllerGameLoopImpl implements TetrisControllerGameLoop, Runnable { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(TetrisControllerGameLoopImpl.class); 29 | 30 | private static final int TICKS_PER_SECOND = 60; 31 | 32 | private static final int SKIP_TICKS = 1000 / TICKS_PER_SECOND; 33 | 34 | private final ExecutorService executorService; 35 | 36 | private final TetrisControllerInputHandler tetrisControllerInputHandler; 37 | 38 | private final TetrisControllerTetrisEventHandler tetrisControllerTetrisEventHandler; 39 | 40 | private final TetrisService tetrisService; 41 | 42 | private final TetrisMainView tetrisMainView; 43 | 44 | private volatile boolean running; 45 | 46 | @Inject 47 | public TetrisControllerGameLoopImpl(final TetrisService tetrisService, 48 | final TetrisControllerInputHandler tetrisControllerInputHandler, 49 | final TetrisControllerTetrisEventHandler tetrisControllerTetrisEventHandler, 50 | final TetrisMainView tetrisMainView) { 51 | 52 | this.tetrisService = tetrisService; 53 | this.tetrisControllerInputHandler = tetrisControllerInputHandler; 54 | this.tetrisControllerTetrisEventHandler = tetrisControllerTetrisEventHandler; 55 | this.tetrisMainView = tetrisMainView; 56 | 57 | executorService = Executors 58 | .newSingleThreadExecutor(runnable -> new Thread(runnable, "TetrisControllerGameLoop")); 59 | } 60 | 61 | @Override 62 | public void start() { 63 | running = true; 64 | executorService.execute(this); 65 | } 66 | 67 | @Override 68 | public void stop() { 69 | running = false; 70 | executorService.shutdown(); 71 | } 72 | 73 | @Override 74 | public void run() { 75 | tetrisMainView.show(); 76 | 77 | while (running) { 78 | long currentTick = System.currentTimeMillis(); 79 | 80 | update(); 81 | 82 | for (final long nextTick = currentTick + SKIP_TICKS; currentTick < nextTick; currentTick = System 83 | .currentTimeMillis()) { 84 | waitMore(); 85 | } 86 | } 87 | 88 | tetrisMainView.hide(); 89 | } 90 | 91 | private void update() { 92 | tetrisControllerInputHandler.handleInputs(); 93 | 94 | tetrisService.update(); 95 | 96 | tetrisControllerTetrisEventHandler.handleEvents(); 97 | 98 | tetrisMainView.update(); 99 | } 100 | 101 | private void waitMore() { 102 | try { 103 | Thread.sleep(1); 104 | } catch (final InterruptedException e) { 105 | LOGGER.error(e.getMessage(), e); 106 | Thread.currentThread().interrupt(); 107 | stop(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/input/TetrisControllerInputHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.input; 10 | 11 | public interface TetrisControllerInputHandler { 12 | 13 | void onKeyPressed(int keyCode); 14 | 15 | void onKeyReleased(int keyCode); 16 | 17 | void onProjectURLClicked(); 18 | 19 | void handleInputs(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/controller/input/TetrisControllerInputHandlerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.controller.input; 10 | 11 | import java.awt.event.KeyEvent; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | import javax.inject.Inject; 16 | import javax.inject.Singleton; 17 | 18 | import com.google.common.collect.Maps; 19 | import com.google.common.collect.Sets; 20 | 21 | import spypunk.tetris.ui.controller.command.TetrisControllerCommand; 22 | import spypunk.tetris.ui.controller.command.cache.TetrisControllerCommandCache; 23 | import spypunk.tetris.ui.controller.command.cache.TetrisControllerCommandCache.TetrisControllerCommandType; 24 | 25 | @Singleton 26 | public class TetrisControllerInputHandlerImpl implements TetrisControllerInputHandler { 27 | 28 | private final Set triggeredCommands = Sets.newConcurrentHashSet(); 29 | 30 | private final Map pressedKeyEventCommandTypes = Maps.newHashMap(); 31 | 32 | private final Map releasedKeyEventCommandTypes = Maps.newHashMap(); 33 | 34 | private final TetrisControllerCommandCache tetrisControllerCommandCache; 35 | 36 | @Inject 37 | public TetrisControllerInputHandlerImpl(final TetrisControllerCommandCache tetrisControllerCommandCache) { 38 | this.tetrisControllerCommandCache = tetrisControllerCommandCache; 39 | 40 | pressedKeyEventCommandTypes.put(KeyEvent.VK_LEFT, TetrisControllerCommandType.LEFT); 41 | pressedKeyEventCommandTypes.put(KeyEvent.VK_RIGHT, TetrisControllerCommandType.RIGHT); 42 | pressedKeyEventCommandTypes.put(KeyEvent.VK_DOWN, TetrisControllerCommandType.DOWN); 43 | 44 | releasedKeyEventCommandTypes.put(KeyEvent.VK_UP, TetrisControllerCommandType.ROTATE); 45 | releasedKeyEventCommandTypes.put(KeyEvent.VK_SPACE, TetrisControllerCommandType.NEW_GAME); 46 | releasedKeyEventCommandTypes.put(KeyEvent.VK_P, TetrisControllerCommandType.PAUSE); 47 | releasedKeyEventCommandTypes.put(KeyEvent.VK_M, TetrisControllerCommandType.MUTE); 48 | releasedKeyEventCommandTypes.put(KeyEvent.VK_PAGE_UP, TetrisControllerCommandType.INCREASE_VOLUME); 49 | releasedKeyEventCommandTypes.put(KeyEvent.VK_PAGE_DOWN, TetrisControllerCommandType.DECREASE_VOLUME); 50 | releasedKeyEventCommandTypes.put(KeyEvent.VK_CONTROL, TetrisControllerCommandType.HARD_DROP); 51 | } 52 | 53 | @Override 54 | public void onKeyPressed(final int keyCode) { 55 | onKey(keyCode, pressedKeyEventCommandTypes); 56 | } 57 | 58 | @Override 59 | public void onKeyReleased(final int keyCode) { 60 | onKey(keyCode, releasedKeyEventCommandTypes); 61 | } 62 | 63 | @Override 64 | public void onProjectURLClicked() { 65 | triggeredCommands.add(TetrisControllerCommandType.OPEN_PROJECT_URL); 66 | } 67 | 68 | @Override 69 | public void handleInputs() { 70 | if (triggeredCommands.isEmpty()) { 71 | return; 72 | } 73 | 74 | triggeredCommands.stream() 75 | .map(tetrisControllerCommandCache::getTetrisControllerCommand) 76 | .forEach(TetrisControllerCommand::execute); 77 | 78 | triggeredCommands.clear(); 79 | } 80 | 81 | private void onKey(final int keyCode, final Map keyEventCommandTypes) { 82 | if (keyEventCommandTypes.containsKey(keyCode)) { 83 | final TetrisControllerCommandType commandType = keyEventCommandTypes.get(keyCode); 84 | 85 | triggeredCommands.add(commandType); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/font/Font.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.font; 10 | 11 | public enum Font { 12 | 13 | NEUTRONIUM, 14 | RUSSO_ONE; 15 | 16 | private static final String TTF_FORMAT = ".ttf"; 17 | 18 | private final String fileName; 19 | 20 | Font() { 21 | fileName = name().toLowerCase() + TTF_FORMAT; 22 | } 23 | 24 | public String getFileName() { 25 | return fileName; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/font/FontType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.font; 10 | 11 | public enum FontType { 12 | 13 | DEFAULT(30F, Font.NEUTRONIUM), 14 | URL(10F, Font.RUSSO_ONE), 15 | BIGGER(42F, Font.NEUTRONIUM); 16 | 17 | private final float size; 18 | 19 | private final Font font; 20 | 21 | FontType(final float size, final Font font) { 22 | this.size = size; 23 | this.font = font; 24 | } 25 | 26 | public float getSize() { 27 | return size; 28 | } 29 | 30 | public String getFileName() { 31 | return font.getFileName(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/font/cache/FontCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.font.cache; 10 | 11 | import java.awt.Font; 12 | 13 | public interface FontCache { 14 | 15 | Font getDefaultFont(); 16 | 17 | Font getBiggerFont(); 18 | 19 | Font getURLFont(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/font/cache/FontCacheImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.font.cache; 10 | 11 | import java.awt.Font; 12 | import java.awt.FontFormatException; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | import java.util.Arrays; 16 | import java.util.Map; 17 | import java.util.function.Function; 18 | import java.util.stream.Collectors; 19 | 20 | import javax.inject.Singleton; 21 | 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import spypunk.tetris.exception.TetrisException; 26 | import spypunk.tetris.ui.font.FontType; 27 | 28 | @Singleton 29 | public class FontCacheImpl implements FontCache { 30 | 31 | private static final Logger LOGGER = LoggerFactory.getLogger(FontCacheImpl.class); 32 | 33 | private static final String FONTS_FOLDER = "/font/"; 34 | 35 | private final Map fonts = createFonts(); 36 | 37 | private static Font createFont(final FontType fontType) { 38 | final String resourceName = String.format("%s%s", FONTS_FOLDER, fontType.getFileName()); 39 | 40 | try (InputStream inputStream = FontCacheImpl.class.getResourceAsStream(resourceName)) { 41 | return Font.createFont(Font.TRUETYPE_FONT, inputStream).deriveFont(fontType.getSize()); 42 | } catch (FontFormatException | IOException e) { 43 | LOGGER.error(e.getMessage(), e); 44 | throw new TetrisException(e); 45 | } 46 | } 47 | 48 | private static Map createFonts() { 49 | return Arrays.asList(FontType.values()).stream() 50 | .collect(Collectors.toMap(Function.identity(), FontCacheImpl::createFont)); 51 | } 52 | 53 | @Override 54 | public Font getDefaultFont() { 55 | return getFont(FontType.DEFAULT); 56 | } 57 | 58 | @Override 59 | public Font getBiggerFont() { 60 | return getFont(FontType.BIGGER); 61 | } 62 | 63 | @Override 64 | public Font getURLFont() { 65 | return getFont(FontType.URL); 66 | } 67 | 68 | private Font getFont(final FontType fontType) { 69 | return fonts.get(fontType); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/icon/Icon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.icon; 10 | 11 | public enum Icon { 12 | 13 | ICON, 14 | MUTE, 15 | UNMUTE 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/util/SwingUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.util; 10 | 11 | import static spypunk.tetris.ui.constants.TetrisUIConstants.BLOCK_SIZE; 12 | import static spypunk.tetris.ui.constants.TetrisUIConstants.DEFAULT_BORDER_COLOR; 13 | import static spypunk.tetris.ui.constants.TetrisUIConstants.DEFAULT_FONT_COLOR; 14 | 15 | import java.awt.Color; 16 | import java.awt.Desktop; 17 | import java.awt.Font; 18 | import java.awt.Graphics2D; 19 | import java.awt.Image; 20 | import java.awt.Rectangle; 21 | import java.awt.font.FontRenderContext; 22 | import java.awt.font.GlyphVector; 23 | import java.awt.geom.Rectangle2D; 24 | import java.io.IOException; 25 | import java.lang.reflect.InvocationTargetException; 26 | import java.net.URI; 27 | 28 | import javax.swing.SwingUtilities; 29 | 30 | import org.slf4j.Logger; 31 | import org.slf4j.LoggerFactory; 32 | 33 | import spypunk.tetris.exception.TetrisException; 34 | 35 | public final class SwingUtils { 36 | 37 | private static final Logger LOGGER = LoggerFactory.getLogger(SwingUtils.class); 38 | 39 | private static final Desktop DESKTOP = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; 40 | 41 | public static class Text { 42 | 43 | private final String value; 44 | 45 | private final Font font; 46 | 47 | private final Color fontColor; 48 | 49 | public Text(final String value, final Font font) { 50 | this.value = value; 51 | this.font = font; 52 | fontColor = DEFAULT_FONT_COLOR; 53 | } 54 | } 55 | 56 | private SwingUtils() { 57 | throw new IllegalAccessError(); 58 | } 59 | 60 | public static void doInAWTThread(final Runnable runnable) { 61 | doInAWTThread(runnable, false); 62 | } 63 | 64 | public static void doInAWTThread(final Runnable runnable, final boolean wait) { 65 | if (wait) { 66 | try { 67 | SwingUtilities.invokeAndWait(runnable); 68 | } catch (final InvocationTargetException e) { 69 | LOGGER.error(e.getMessage(), e); 70 | throw new TetrisException(e); 71 | } catch (final InterruptedException e) { 72 | Thread.currentThread().interrupt(); 73 | LOGGER.error(e.getMessage(), e); 74 | throw new TetrisException(e); 75 | } 76 | } else { 77 | SwingUtilities.invokeLater(runnable); 78 | } 79 | } 80 | 81 | public static Rectangle getCenteredImageRectangle(final Image image, final Rectangle rectangle, 82 | final double ratio) { 83 | final int imageWidth = image.getWidth(null); 84 | final int imageHeight = image.getHeight(null); 85 | 86 | final int targetWidth = (int) (imageWidth * ratio); 87 | final int targetHeight = (int) (imageHeight * ratio); 88 | 89 | final int x1 = rectangle.x + (rectangle.width - targetWidth) / 2; 90 | final int y1 = rectangle.y + (rectangle.height - targetHeight) / 2; 91 | 92 | return new Rectangle(x1, y1, targetWidth, targetHeight); 93 | } 94 | 95 | public static Rectangle getCenteredImageRectangle(final Image image, final Rectangle rectangle) { 96 | return getCenteredImageRectangle(image, rectangle, 1); 97 | } 98 | 99 | public static void drawImage(final Graphics2D graphics, final Image image, 100 | final Rectangle rectangle) { 101 | final int imageWidth = image.getWidth(null); 102 | final int imageHeight = image.getHeight(null); 103 | 104 | graphics.drawImage(image, rectangle.x, rectangle.y, rectangle.x + rectangle.width, 105 | rectangle.y + rectangle.height, 0, 0, imageWidth, imageHeight, 106 | null); 107 | } 108 | 109 | public static void openURI(final URI uri) { 110 | if (DESKTOP != null && DESKTOP.isSupported(Desktop.Action.BROWSE)) { 111 | try { 112 | DESKTOP.browse(uri); 113 | } catch (final IOException e) { 114 | final String message = String.format("Cannot open following URL : %s", uri); 115 | LOGGER.warn(message, e); 116 | } 117 | } else { 118 | final String message = String 119 | .format("Your system does not support URL browsing, cannot open following URL : %s", uri); 120 | LOGGER.warn(message); 121 | } 122 | } 123 | 124 | public static void renderCenteredText(final Graphics2D graphics, final Rectangle rectangle, final Text text) { 125 | graphics.setFont(text.font); 126 | graphics.setColor(text.fontColor); 127 | 128 | final Rectangle textRectangle = SwingUtils.getCenteredTextRectangle(graphics, rectangle, text); 129 | 130 | graphics.drawString(text.value, textRectangle.x, textRectangle.y); 131 | } 132 | 133 | public static void drawRectangleWithTitle(final Graphics2D graphics, final Rectangle rectangle, final Text text) { 134 | final Rectangle titleRectangle = new Rectangle(0, rectangle.y - BLOCK_SIZE, rectangle.width, 135 | BLOCK_SIZE); 136 | 137 | SwingUtils.renderCenteredText(graphics, titleRectangle, text); 138 | 139 | graphics.setColor(DEFAULT_BORDER_COLOR); 140 | graphics.drawRect(rectangle.x, rectangle.y, rectangle.width, rectangle.height); 141 | } 142 | 143 | private static Rectangle getCenteredTextRectangle(final Graphics2D graphics, final Rectangle rectangle, 144 | final Text text) { 145 | final Rectangle2D textBounds = getTextBounds(graphics, text); 146 | 147 | final int x1 = (int) (rectangle.x + (rectangle.width - textBounds.getWidth()) / 2); 148 | final int y1 = (int) (rectangle.y + (rectangle.height + textBounds.getHeight()) / 2); 149 | 150 | return new Rectangle(x1, y1, (int) textBounds.getWidth(), (int) textBounds.getHeight()); 151 | } 152 | 153 | private static Rectangle2D getTextBounds(final Graphics2D graphics, final Text text) { 154 | final FontRenderContext frc = graphics.getFontRenderContext(); 155 | final GlyphVector gv = text.font.createGlyphVector(frc, text.value); 156 | 157 | return gv.getVisualBounds(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/AbstractTetrisView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | import java.awt.Color; 12 | import java.awt.Component; 13 | import java.awt.Dimension; 14 | import java.awt.Graphics; 15 | import java.awt.Graphics2D; 16 | import java.awt.RenderingHints; 17 | 18 | import javax.swing.BorderFactory; 19 | import javax.swing.JLabel; 20 | 21 | import spypunk.tetris.model.Tetris; 22 | import spypunk.tetris.ui.cache.ImageCache; 23 | import spypunk.tetris.ui.font.cache.FontCache; 24 | 25 | public abstract class AbstractTetrisView extends AbstractView { 26 | 27 | private final TetrisViewComponent tetrisViewComponent = new TetrisViewComponent(); 28 | 29 | protected final class TetrisViewComponent extends JLabel { 30 | 31 | private static final long serialVersionUID = 7092310215094716291L; 32 | 33 | @Override 34 | protected void paintComponent(final Graphics graphics) { 35 | super.paintComponent(graphics); 36 | 37 | final Graphics2D graphics2d = (Graphics2D) graphics; 38 | 39 | graphics2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 40 | graphics2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, 41 | RenderingHints.VALUE_TEXT_ANTIALIAS_ON); 42 | 43 | doPaint(graphics2d); 44 | } 45 | } 46 | 47 | protected AbstractTetrisView(final FontCache fontCache, final ImageCache imageCache, final Tetris tetris) { 48 | super(fontCache, imageCache, tetris); 49 | } 50 | 51 | protected void initializeComponent(final int width, final int height) { 52 | tetrisViewComponent.setPreferredSize(new Dimension(width, height)); 53 | } 54 | 55 | protected void initializeComponentWithBorders(final int width, final int height) { 56 | initializeComponent(width, height); 57 | 58 | tetrisViewComponent.setBorder(BorderFactory.createLineBorder(Color.GRAY)); 59 | } 60 | 61 | public Component getComponent() { 62 | return tetrisViewComponent; 63 | } 64 | 65 | protected abstract void doPaint(final Graphics2D graphics); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/AbstractView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | import spypunk.tetris.model.Tetris; 12 | import spypunk.tetris.ui.cache.ImageCache; 13 | import spypunk.tetris.ui.font.cache.FontCache; 14 | 15 | public abstract class AbstractView { 16 | 17 | protected final FontCache fontCache; 18 | 19 | protected final ImageCache imageCache; 20 | 21 | protected final Tetris tetris; 22 | 23 | protected AbstractView(final FontCache fontCache, final ImageCache imageCache, final Tetris tetris) { 24 | this.fontCache = fontCache; 25 | this.imageCache = imageCache; 26 | this.tetris = tetris; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/TetrisGridView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | import static spypunk.tetris.ui.constants.TetrisUIConstants.BLOCK_SIZE; 12 | 13 | import java.awt.Color; 14 | import java.awt.Graphics2D; 15 | import java.awt.Image; 16 | import java.awt.Point; 17 | import java.awt.Rectangle; 18 | 19 | import spypunk.tetris.constants.TetrisConstants; 20 | import spypunk.tetris.model.Shape.Block; 21 | import spypunk.tetris.model.ShapeType; 22 | import spypunk.tetris.model.Tetris; 23 | import spypunk.tetris.model.Tetris.State; 24 | import spypunk.tetris.ui.cache.ImageCache; 25 | import spypunk.tetris.ui.font.cache.FontCache; 26 | import spypunk.tetris.ui.util.SwingUtils; 27 | import spypunk.tetris.ui.util.SwingUtils.Text; 28 | 29 | public class TetrisGridView extends AbstractTetrisView { 30 | 31 | private static final String PAUSE = "PAUSE"; 32 | 33 | private static final Color NOT_RUNNING_FG_COLOR = new Color(30, 30, 30, 200); 34 | 35 | private static final String GAME_OVER = "GAME OVER"; 36 | 37 | private static final String PRESS_SPACE = "PRESS SPACE"; 38 | 39 | private final Rectangle gridRectangle; 40 | 41 | private final Text tetrisStoppedText; 42 | 43 | private final Text tetrisGameOverText; 44 | 45 | private final Text tetrisPausedText; 46 | 47 | public TetrisGridView(final FontCache fontCache, 48 | final ImageCache imageCache, 49 | final Tetris tetris) { 50 | super(fontCache, imageCache, tetris); 51 | 52 | gridRectangle = new Rectangle(0, 0, TetrisConstants.WIDTH * BLOCK_SIZE, 53 | TetrisConstants.HEIGHT * BLOCK_SIZE); 54 | 55 | tetrisStoppedText = new Text(PRESS_SPACE, fontCache.getBiggerFont()); 56 | tetrisGameOverText = new Text(GAME_OVER, fontCache.getBiggerFont()); 57 | tetrisPausedText = new Text(PAUSE, fontCache.getBiggerFont()); 58 | 59 | initializeComponentWithBorders(gridRectangle.width, gridRectangle.height); 60 | } 61 | 62 | @Override 63 | protected void doPaint(final Graphics2D graphics) { 64 | final State tetrisState = tetris.getState(); 65 | 66 | if (tetrisState.equals(State.STOPPED)) { 67 | SwingUtils.renderCenteredText(graphics, gridRectangle, tetrisStoppedText); 68 | return; 69 | } 70 | 71 | tetris.getBlocks().values().forEach(block -> renderBlock(graphics, block)); 72 | tetris.getCurrentShape().getBlocks().forEach(block -> renderBlock(graphics, block)); 73 | 74 | if (!State.RUNNING.equals(tetrisState)) { 75 | renderTetrisNotRunning(graphics, tetrisState); 76 | } 77 | } 78 | 79 | private void renderBlock(final Graphics2D graphics, final Block block) { 80 | final ShapeType shapeType = block.getShape().getShapeType(); 81 | final Image blockImage = imageCache.getBlockImage(shapeType); 82 | final Point location = block.getLocation(); 83 | final Rectangle rectangle = new Rectangle(location.x * BLOCK_SIZE, location.y * BLOCK_SIZE, BLOCK_SIZE, 84 | BLOCK_SIZE); 85 | 86 | SwingUtils.drawImage(graphics, blockImage, rectangle); 87 | } 88 | 89 | private void renderTetrisNotRunning(final Graphics2D graphics, final State state) { 90 | graphics.setColor(NOT_RUNNING_FG_COLOR); 91 | graphics.fillRect(gridRectangle.x, gridRectangle.y, gridRectangle.width, 92 | gridRectangle.height); 93 | 94 | SwingUtils.renderCenteredText(graphics, gridRectangle, 95 | State.GAME_OVER.equals(state) ? tetrisGameOverText : tetrisPausedText); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/TetrisInfoView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | import static spypunk.tetris.ui.constants.TetrisUIConstants.BLOCK_SIZE; 12 | 13 | import java.awt.Graphics2D; 14 | import java.awt.Image; 15 | import java.awt.Rectangle; 16 | import java.util.Arrays; 17 | import java.util.Map; 18 | import java.util.function.Function; 19 | import java.util.stream.Collectors; 20 | 21 | import org.apache.commons.lang3.tuple.Pair; 22 | 23 | import spypunk.tetris.model.ShapeType; 24 | import spypunk.tetris.model.Tetris; 25 | import spypunk.tetris.model.Tetris.State; 26 | import spypunk.tetris.ui.cache.ImageCache; 27 | import spypunk.tetris.ui.font.cache.FontCache; 28 | import spypunk.tetris.ui.util.SwingUtils; 29 | import spypunk.tetris.ui.util.SwingUtils.Text; 30 | 31 | public class TetrisInfoView extends AbstractTetrisView { 32 | 33 | private static final int VIEW_HEIGHT = 1 + BLOCK_SIZE * 16; 34 | 35 | private static final int VIEW_WIDTH = 1 + BLOCK_SIZE * 6; 36 | 37 | private static final String SCORE = "SCORE"; 38 | 39 | private static final String LEVEL = "LEVEL"; 40 | 41 | private static final String NEXT_SHAPE = "NEXT"; 42 | 43 | private static final String ROWS = "ROWS"; 44 | 45 | private final ValueTetrisInfo rowsTetrisInfo; 46 | 47 | private final ValueTetrisInfo scoreTetrisInfo; 48 | 49 | private final ValueTetrisInfo levelTetrisInfo; 50 | 51 | private final NextShapeTetrisInfo nextShapeTetrisInfo; 52 | 53 | private abstract class TetrisInfo { 54 | 55 | protected final Rectangle rectangle; 56 | 57 | private final Text titleText; 58 | 59 | TetrisInfo(final Rectangle rectangle, final String title) { 60 | this.rectangle = rectangle; 61 | 62 | titleText = new Text(title, fontCache.getDefaultFont()); 63 | } 64 | 65 | public void render(final Graphics2D graphics) { 66 | SwingUtils.drawRectangleWithTitle(graphics, rectangle, titleText); 67 | } 68 | } 69 | 70 | private class ValueTetrisInfo extends TetrisInfo { 71 | 72 | ValueTetrisInfo(final Rectangle rectangle, final String title) { 73 | super(rectangle, title); 74 | } 75 | 76 | public void render(final Graphics2D graphics, final int value) { 77 | super.render(graphics); 78 | 79 | final Text valueText = new Text(String.valueOf(value), fontCache.getDefaultFont()); 80 | 81 | SwingUtils.renderCenteredText(graphics, rectangle, valueText); 82 | } 83 | } 84 | 85 | private class NextShapeTetrisInfo extends TetrisInfo { 86 | 87 | private final Map> shapeTypeImageRectangles; 88 | 89 | NextShapeTetrisInfo() { 90 | super(new Rectangle(0, BLOCK_SIZE * 12, BLOCK_SIZE * 6, BLOCK_SIZE * 6), NEXT_SHAPE); 91 | 92 | shapeTypeImageRectangles = Arrays.asList(ShapeType.values()) 93 | .stream() 94 | .collect(Collectors.toMap(Function.identity(), this::createShapeTypeImageRectangle)); 95 | } 96 | 97 | @Override 98 | public void render(final Graphics2D graphics) { 99 | super.render(graphics); 100 | 101 | final State tetrisState = tetris.getState(); 102 | 103 | if (tetrisState.equals(State.STOPPED)) { 104 | return; 105 | } 106 | 107 | final ShapeType shapeType = tetris.getNextShape().getShapeType(); 108 | final Pair shapeTypeImageRectangle = shapeTypeImageRectangles.get(shapeType); 109 | 110 | SwingUtils.drawImage(graphics, shapeTypeImageRectangle.getLeft(), shapeTypeImageRectangle.getRight()); 111 | } 112 | 113 | private Pair createShapeTypeImageRectangle(final ShapeType shapeType) { 114 | final Image shapeTypeImage = imageCache.getShapeImage(shapeType); 115 | 116 | return Pair.of(shapeTypeImage, SwingUtils.getCenteredImageRectangle(shapeTypeImage, rectangle)); 117 | } 118 | } 119 | 120 | public TetrisInfoView(final FontCache fontCache, 121 | final ImageCache imageCache, final Tetris tetris) { 122 | super(fontCache, imageCache, tetris); 123 | 124 | final Rectangle levelRectangle = new Rectangle(0, BLOCK_SIZE * 3, BLOCK_SIZE * 6, BLOCK_SIZE); 125 | final Rectangle scoreRectangle = new Rectangle(0, BLOCK_SIZE * 6, BLOCK_SIZE * 6, BLOCK_SIZE); 126 | final Rectangle rowsRectangle = new Rectangle(0, BLOCK_SIZE * 9, BLOCK_SIZE * 6, BLOCK_SIZE); 127 | 128 | rowsTetrisInfo = new ValueTetrisInfo(rowsRectangle, ROWS); 129 | scoreTetrisInfo = new ValueTetrisInfo(scoreRectangle, SCORE); 130 | levelTetrisInfo = new ValueTetrisInfo(levelRectangle, LEVEL); 131 | nextShapeTetrisInfo = new NextShapeTetrisInfo(); 132 | 133 | initializeComponent(VIEW_WIDTH, VIEW_HEIGHT); 134 | } 135 | 136 | @Override 137 | protected void doPaint(final Graphics2D graphics) { 138 | levelTetrisInfo.render(graphics, tetris.getLevel()); 139 | scoreTetrisInfo.render(graphics, tetris.getScore()); 140 | rowsTetrisInfo.render(graphics, tetris.getCompletedRows()); 141 | nextShapeTetrisInfo.render(graphics); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/TetrisMainView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | public interface TetrisMainView { 12 | 13 | void show(); 14 | 15 | void hide(); 16 | 17 | void update(); 18 | 19 | void setMuted(boolean muted); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/TetrisMainViewImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | import static spypunk.tetris.ui.constants.TetrisUIConstants.BLOCK_SIZE; 12 | import static spypunk.tetris.ui.constants.TetrisUIConstants.DEFAULT_FONT_COLOR; 13 | 14 | import java.awt.BorderLayout; 15 | import java.awt.Color; 16 | import java.awt.event.KeyAdapter; 17 | import java.awt.event.KeyEvent; 18 | import java.awt.event.MouseAdapter; 19 | import java.awt.event.MouseEvent; 20 | import java.awt.event.WindowAdapter; 21 | import java.awt.event.WindowEvent; 22 | import java.net.URI; 23 | 24 | import javax.inject.Inject; 25 | import javax.inject.Singleton; 26 | import javax.swing.BorderFactory; 27 | import javax.swing.ImageIcon; 28 | import javax.swing.JFrame; 29 | import javax.swing.JLabel; 30 | import javax.swing.JPanel; 31 | import javax.swing.WindowConstants; 32 | 33 | import spypunk.tetris.guice.TetrisModule.TetrisProvider; 34 | import spypunk.tetris.model.Tetris; 35 | import spypunk.tetris.ui.cache.ImageCache; 36 | import spypunk.tetris.ui.controller.TetrisController; 37 | import spypunk.tetris.ui.font.cache.FontCache; 38 | import spypunk.tetris.ui.icon.Icon; 39 | import spypunk.tetris.ui.util.SwingUtils; 40 | 41 | @Singleton 42 | public class TetrisMainViewImpl extends AbstractView implements TetrisMainView { 43 | 44 | private final JFrame frame; 45 | 46 | private final JLabel muteLabel; 47 | 48 | private final ImageIcon muteImageIcon; 49 | 50 | private final ImageIcon unmuteImageIcon; 51 | 52 | private final class TetrisViewWindowListener extends WindowAdapter { 53 | 54 | private final TetrisController tetrisController; 55 | 56 | TetrisViewWindowListener(final TetrisController tetrisController) { 57 | this.tetrisController = tetrisController; 58 | } 59 | 60 | @Override 61 | public void windowClosed(final WindowEvent e) { 62 | tetrisController.onWindowClosed(); 63 | } 64 | } 65 | 66 | private static final class TetrisViewKeyAdapter extends KeyAdapter { 67 | 68 | private final TetrisController tetrisController; 69 | 70 | TetrisViewKeyAdapter(final TetrisController tetrisController) { 71 | this.tetrisController = tetrisController; 72 | } 73 | 74 | @Override 75 | public void keyPressed(final KeyEvent e) { 76 | tetrisController.onKeyPressed(e.getKeyCode()); 77 | } 78 | 79 | @Override 80 | public void keyReleased(final KeyEvent e) { 81 | tetrisController.onKeyReleased(e.getKeyCode()); 82 | } 83 | } 84 | 85 | private static final class URLLabelMouseAdapter extends MouseAdapter { 86 | 87 | private final TetrisController tetrisController; 88 | 89 | private final JLabel urlLabel; 90 | 91 | URLLabelMouseAdapter(final TetrisController tetrisController, final JLabel urlLabel) { 92 | this.tetrisController = tetrisController; 93 | this.urlLabel = urlLabel; 94 | } 95 | 96 | @Override 97 | public void mouseClicked(final MouseEvent e) { 98 | tetrisController.onProjectURLClicked(); 99 | } 100 | 101 | @Override 102 | public void mouseEntered(final MouseEvent e) { 103 | urlLabel.setForeground(Color.CYAN); 104 | } 105 | 106 | @Override 107 | public void mouseExited(final MouseEvent e) { 108 | urlLabel.setForeground(DEFAULT_FONT_COLOR); 109 | } 110 | } 111 | 112 | @Inject 113 | public TetrisMainViewImpl(final TetrisController tetrisController, 114 | final FontCache fontCache, 115 | final ImageCache imageCache, 116 | final @TetrisProvider Tetris tetris) { 117 | super(fontCache, imageCache, tetris); 118 | 119 | final TetrisStatisticsView tetrisStatisticsView = new TetrisStatisticsView(fontCache, imageCache, tetris); 120 | final TetrisInfoView tetrisInfoView = new TetrisInfoView(fontCache, imageCache, tetris); 121 | final TetrisGridView tetrisGridView = new TetrisGridView(fontCache, imageCache, tetris); 122 | 123 | muteImageIcon = new ImageIcon(imageCache.getIcon(Icon.MUTE)); 124 | unmuteImageIcon = new ImageIcon(imageCache.getIcon(Icon.UNMUTE)); 125 | 126 | final URI projectURI = tetris.getProjectURI(); 127 | 128 | muteLabel = new JLabel(unmuteImageIcon); 129 | 130 | final JLabel urlLabel = new JLabel(projectURI.getHost() + projectURI.getPath()); 131 | 132 | urlLabel.setFont(fontCache.getURLFont()); 133 | urlLabel.setForeground(DEFAULT_FONT_COLOR); 134 | urlLabel.addMouseListener(new URLLabelMouseAdapter(tetrisController, urlLabel)); 135 | 136 | final JPanel bottomPanel = new JPanel(new BorderLayout()); 137 | 138 | bottomPanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); 139 | bottomPanel.setBackground(Color.BLACK); 140 | 141 | bottomPanel.add(muteLabel, BorderLayout.WEST); 142 | bottomPanel.add(urlLabel, BorderLayout.EAST); 143 | 144 | final JPanel centerPanel = new JPanel(new BorderLayout(BLOCK_SIZE, 0)); 145 | 146 | centerPanel.setBackground(Color.BLACK); 147 | centerPanel.setBorder(BorderFactory.createEmptyBorder(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)); 148 | 149 | centerPanel.add(tetrisGridView.getComponent(), BorderLayout.CENTER); 150 | centerPanel.add(tetrisStatisticsView.getComponent(), BorderLayout.WEST); 151 | centerPanel.add(tetrisInfoView.getComponent(), BorderLayout.EAST); 152 | 153 | frame = new JFrame(tetris.getName() + " " + tetris.getVersion()); 154 | 155 | frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); 156 | frame.setLayout(new BorderLayout(0, 0)); 157 | frame.setResizable(false); 158 | frame.addWindowListener(new TetrisViewWindowListener(tetrisController)); 159 | frame.addKeyListener(new TetrisViewKeyAdapter(tetrisController)); 160 | frame.setIconImage(imageCache.getIcon(Icon.ICON)); 161 | frame.setIgnoreRepaint(true); 162 | 163 | frame.add(centerPanel, BorderLayout.CENTER); 164 | frame.add(bottomPanel, BorderLayout.SOUTH); 165 | frame.pack(); 166 | 167 | frame.setLocationRelativeTo(null); 168 | } 169 | 170 | @Override 171 | public void show() { 172 | setVisible(true); 173 | } 174 | 175 | @Override 176 | public void hide() { 177 | setVisible(false); 178 | } 179 | 180 | @Override 181 | public void update() { 182 | frame.repaint(); 183 | } 184 | 185 | @Override 186 | public void setMuted(final boolean muted) { 187 | SwingUtils.doInAWTThread(() -> muteLabel.setIcon(muted ? muteImageIcon : unmuteImageIcon)); 188 | } 189 | 190 | private void setVisible(final boolean visible) { 191 | SwingUtils.doInAWTThread(() -> frame.setVisible(visible)); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/spypunk/tetris/ui/view/TetrisStatisticsView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright © 2016-2017 spypunk 3 | * 4 | * This work is free. You can redistribute it and/or modify it under the 5 | * terms of the Do What The Fuck You Want To Public License, Version 2, 6 | * as published by Sam Hocevar. See the COPYING file for more details. 7 | */ 8 | 9 | package spypunk.tetris.ui.view; 10 | 11 | import static spypunk.tetris.ui.constants.TetrisUIConstants.BLOCK_SIZE; 12 | 13 | import java.awt.Graphics2D; 14 | import java.awt.Image; 15 | import java.awt.Rectangle; 16 | import java.util.Arrays; 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | import spypunk.tetris.model.ShapeType; 21 | import spypunk.tetris.model.Tetris; 22 | import spypunk.tetris.ui.cache.ImageCache; 23 | import spypunk.tetris.ui.font.cache.FontCache; 24 | import spypunk.tetris.ui.util.SwingUtils; 25 | import spypunk.tetris.ui.util.SwingUtils.Text; 26 | 27 | public class TetrisStatisticsView extends AbstractTetrisView { 28 | 29 | private static final String STATISTICS = "STATISTICS"; 30 | 31 | private final Rectangle statisticsRectangle; 32 | 33 | private final List tetrisStatistics; 34 | 35 | private final Text statisticsTitleText; 36 | 37 | private class TetrisStatistic { 38 | 39 | private final Image image; 40 | 41 | private final Rectangle imageRectangle; 42 | 43 | private final Rectangle textRectangle; 44 | 45 | private final ShapeType shapeType; 46 | 47 | TetrisStatistic(final Image image, final Rectangle imageRectangle, 48 | final Rectangle textRectangle, 49 | final ShapeType shapeType) { 50 | this.image = image; 51 | this.imageRectangle = imageRectangle; 52 | this.textRectangle = textRectangle; 53 | this.shapeType = shapeType; 54 | } 55 | 56 | public void render(final Graphics2D graphics) { 57 | final String value = String.valueOf(tetris.getStatistics().get(shapeType)); 58 | 59 | SwingUtils.drawImage(graphics, image, imageRectangle); 60 | 61 | final Text statisticText = new Text(value, fontCache.getDefaultFont()); 62 | 63 | SwingUtils.renderCenteredText(graphics, textRectangle, statisticText); 64 | } 65 | } 66 | 67 | public TetrisStatisticsView(final FontCache fontCache, 68 | final ImageCache imageCache, final Tetris tetris) { 69 | super(fontCache, imageCache, tetris); 70 | 71 | statisticsRectangle = new Rectangle(0, 3 * BLOCK_SIZE, BLOCK_SIZE * 6, BLOCK_SIZE * 15); 72 | 73 | tetrisStatistics = Arrays.asList(ShapeType.values()) 74 | .stream() 75 | .map(shapeType -> createTetrisStatistic(imageCache, shapeType)) 76 | .collect(Collectors.toList()); 77 | 78 | statisticsTitleText = new Text(STATISTICS, fontCache.getDefaultFont()); 79 | 80 | initializeComponent(statisticsRectangle.width + 1, statisticsRectangle.height + BLOCK_SIZE + 1); 81 | } 82 | 83 | private TetrisStatistic createTetrisStatistic(final ImageCache imageCache, final ShapeType shapeType) { 84 | final Image shapeImage = imageCache.getShapeImage(shapeType); 85 | 86 | final Rectangle imageContainerRectangle = new Rectangle(statisticsRectangle.x, 87 | statisticsRectangle.y + shapeType.ordinal() * 2 * BLOCK_SIZE + BLOCK_SIZE, 88 | statisticsRectangle.width / 2, BLOCK_SIZE); 89 | 90 | final Rectangle imageRectangle = SwingUtils.getCenteredImageRectangle(shapeImage, imageContainerRectangle, 0.5); 91 | 92 | final Rectangle textRectangle = new Rectangle( 93 | statisticsRectangle.x + imageContainerRectangle.width, 94 | imageContainerRectangle.y, imageContainerRectangle.width, imageContainerRectangle.height); 95 | 96 | return new TetrisStatistic(shapeImage, imageRectangle, 97 | textRectangle, shapeType); 98 | } 99 | 100 | @Override 101 | protected void doPaint(final Graphics2D graphics) { 102 | SwingUtils.drawRectangleWithTitle(graphics, statisticsRectangle, statisticsTitleText); 103 | 104 | tetrisStatistics.forEach(tetrisStatistic -> tetrisStatistic.render(graphics)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/resources-filtered/tetris.properties: -------------------------------------------------------------------------------- 1 | name=${project.artifactId} 2 | version=${project.version} 3 | url=${project.url} 4 | -------------------------------------------------------------------------------- /src/main/resources/font/neutronium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/font/neutronium.ttf -------------------------------------------------------------------------------- /src/main/resources/font/russo_one.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/font/russo_one.ttf -------------------------------------------------------------------------------- /src/main/resources/img/blocks/I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/I.png -------------------------------------------------------------------------------- /src/main/resources/img/blocks/J.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/J.png -------------------------------------------------------------------------------- /src/main/resources/img/blocks/L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/L.png -------------------------------------------------------------------------------- /src/main/resources/img/blocks/O.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/O.png -------------------------------------------------------------------------------- /src/main/resources/img/blocks/S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/S.png -------------------------------------------------------------------------------- /src/main/resources/img/blocks/T.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/T.png -------------------------------------------------------------------------------- /src/main/resources/img/blocks/Z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/blocks/Z.png -------------------------------------------------------------------------------- /src/main/resources/img/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/icons/icon.png -------------------------------------------------------------------------------- /src/main/resources/img/icons/mute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/icons/mute.png -------------------------------------------------------------------------------- /src/main/resources/img/icons/unmute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/icons/unmute.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/I.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/I.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/J.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/J.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/L.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/O.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/O.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/S.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/S.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/T.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/T.png -------------------------------------------------------------------------------- /src/main/resources/img/shapes/Z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/img/shapes/Z.png -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger = DEBUG, FILE 2 | log4j.appender.FILE=org.apache.log4j.FileAppender 3 | log4j.appender.FILE.File=${user.home}/.spypunk-tetris/tetris.log 4 | log4j.appender.FILE.ImmediateFlush=true 5 | log4j.appender.FILE.Threshold=debug 6 | log4j.appender.FILE.Append=true 7 | log4j.appender.FILE.layout=org.apache.log4j.PatternLayout 8 | log4j.appender.FILE.layout.conversionPattern=%d{ISO8601} %-5p (%t) [%c{1}(%M:%L)] %m%n 9 | -------------------------------------------------------------------------------- /src/main/resources/sound/background.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/sound/background.mp3 -------------------------------------------------------------------------------- /src/main/resources/sound/game_over.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/sound/game_over.mp3 -------------------------------------------------------------------------------- /src/main/resources/sound/rows_completed.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/sound/rows_completed.mp3 -------------------------------------------------------------------------------- /src/main/resources/sound/shape_locked.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spypunk/tetris/24bbf557fd401365f3c2d226b0e3cddf4f3f819c/src/main/resources/sound/shape_locked.mp3 -------------------------------------------------------------------------------- /src/main/scripts/tetris: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | java -jar tetris.jar 3 | -------------------------------------------------------------------------------- /src/main/scripts/tetris.bat: -------------------------------------------------------------------------------- 1 | start javaw -jar tetris.jar 2 | --------------------------------------------------------------------------------