Before running the game I suggest checking the build version of your libraries
gradle run
move: a/d rotate: q/e/w/s/arrows drop: space
nothing!
A handful of sites I checked during making this are listed below
(it's worth noting I almost copied no code from them whatsoever)
- github issues
- git.1-hub.cnmunity
- wikipedia
- geeksforgeeks
- stackoverflow
- codingame
- dzone
- gameprogrammingpatterns
- docs.oracle
- workplace.stackexchange
- blog.eduonix
- journaldev
- educba
- cs.cmu
- Quota
- Javaworld
- javapathfinder.sourceforge
- tutorials.jenkov
- codota
- tecmint
- medium
- baeldung
- Java2s
- martinfowler
- artima .com/intv
- tutorialspoint
threre was this funny clip I saw on instagram, cut a few parts of it and used it as my game assets
https://www.instagram.com/p/B-6YDMTggjk/?utm_source=ig_web_copy_link
there's only a mute logo and tbh, idk where I found it :))
public void update() {
if (!isPaused) {
updateState();
updateGraphics();
} else {
doWait();
}
}
//generates a "game state" object and stores it
private void updateState() {
state = manager.update(timer.isTickTime());
}
//then passes it to the game grahpics
//we're also using a timer to keep things under control
private void updateGraphics() {
if (!timer.isLocked()) {
timer.queue(gameGraphics::redraw);
timer.flush();
timer.holdOn();
}
gameGraphics.update(state);
}
-
Singleton
public static Game getInstance() { return instance; }
alongside interfaces connecting to game object:
private IMenuCommand parseMenuCommand(final KeyEvent e) { if (e.getKeyCode()==17) //control return Game::changeGameSpeed; switch(e.getKeyChar()) { case 'l': return Game::load; case 'r': return Game::reset; case 'm': return Game::toggleMenu; case 'v': return Game::save; case 'y': return Game::restore; case 't': return Game::quit; case 'p': return Game::togglePause; case 'u': return Game::toggleMute; default: return null; } }
(Class::function is the method reference syntax added in Java 8, a better alternative here would be using Java 13's switch expressions)
-
Dependency Injection
private Game() { final GameSettings settings = new GameSettings("settings.properties"); manager = new GameManager(timer, null); //passing the game manager to the input input = new Input(manager); //passing the settings holder and input listener to the graphics agent gameGraphics.setup(settings, input); new UiUpdater(gameGraphics).start(); }
there are other few not-exactly-pattern things used here like:
-
Composition
public class Tetrimino implements IGameObject, IShape, Drawable, Animate { //responsible for: //the location, and movements private final IGameObject body; //the orientation private final IShape shape; //draws the object private transient CollidableDrawList leonardoDaVinci = null; //flashy stuff u see on screen :p private transient Animator animator = new Animator(); private Tetrimino(final IShape shape, final IGameObject body) { this.shape = shape; this.body = body; update(); } //updates the status of tetrimino private void update() { leonardoDaVinci = createDrawList(); } //the list holding the "drawables" private CollidableDrawList createDrawList() { return new CollidableDrawList(applyShape(body)); } //multiplies a box at the body's location and applies the "template" stored in the shape to it public List<IGameObject> applyShape(final IGameObject body) { return shape.applyShape(body); }
-
Enum constructors
public enum SoundEffect { BAW("baw"), DASH("dash1"), FELL("fell1"), GAMEOVER("gameover"), POOF("poof"), STACK("stack"), EXPLOSION("explosion"); private SoundEffect(String soundFileName) { /* ... */ }
-
Generics, Threads, Consumers
public class CustomListener<T> { private final T comp; protected final Consumer<T> func; private final int waitTime; public CustomListener(T comp, int waitTime, Consumer<T> func) { this.comp = comp; this.func = func; this.waitTime = waitTime; } public void start() { new Thread(this::update).start(); } private synchronized void update() { while (!Thread.currentThread().isInterrupted()) { try { func.accept(comp); wait(waitTime); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } }
-
Lambda Expressions and List Foreach Iterations
private static final List<JButton> btnList = new ArrayList<>(); private static final transient Map <String, ActionListener> btnMap = Map.of( "Sorr(y)", (ActionEvent e) -> Game.getInstance().restore() , "(R)estart", (ActionEvent e) -> Game.getInstance().reset() , "(L)oad", (ActionEvent e) -> Game.getInstance().load() , "Sa(v)e", (ActionEvent e) -> Game.getInstance().save() , "Qui(t)", (ActionEvent e) -> Game.getInstance().quit() ); private Box buttonsBox() { Box box = Box.createHorizontalBox(); //generates the buttons off the btnMap btnMap.forEach( (String text, ActionListener listener) -> btnList.add(makeButton(text, listener)) ); //a little tweak to the buttons btnList.forEach(btn -> btn.setFocusable(false)); //and then adds them to the box btnList.forEach(box::add); return box; } private JButton makeButton(String text, ActionListener listener) { /* ... */ }
-
Functional Interfaces
@FunctionalInterface public interface ICommand { void act(Tetrimino t); } public interface ICommandReceiver { void receiveCommand(ICommand cmd); }
an implementation:
public void receiveCommand(ICommand cmd) { if (inputLock.isUnlocked() && fallLock.isUnlocked()) { fallLock.unlock(); cmd.act(current); //current : Tetrimino } }
-
Nested Classes
public class GameManager implements ICommandReceiver { public static class Lock { /* ... */ } private enum GameEvent { LINE_REMOVE, END_ROUND, SPAWN, GAMEOVER } private class GameEventHandler { private void call(GameEvent event) { get(event).run(); } private Runnable get(GameEvent event) { switch(event) { case GAMEOVER: return this::gameOver; case LINE_REMOVE: return this::lineRemove; case END_ROUND: return this::endRound; case SPAWN: return this::spawn; default: return () -> {}; } } } }
-
Reflection(?)
This is how objects read the game state
gameState.get(this.getClass());
Inside game state:
public Object get(Object receiver) { if (receiver.getClass()==GamePanel.class) { return getGamePanelDrawables(); } if (receiver.getClass()==NextPanel.class) { return getNextPanelDrawables(); } if (receiver.getClass()==ScorePanel.class) { return score; } if (receiver.getClass()== GameManager.class) { return new ReadableGameState(level, current, next, score); } return null; }
-
and builder pattern
public DrawList getGamePanelDrawables() { return new DrawList().add(level).add(current); } public DrawList getNextPanelDrawables() { return new DrawList().add(next); }
-
Haaave you met my interface? :D
public interface IGameObject extends Serializable { void move(int x, int y); /** Returns a clone of game object which has moved to the coordinate c */ default IGameObject updatedCoordinates(Coordinate c) { IGameObject go = copy(); go.move(c.getX(), c.getY()); return go; } default void moveLeft() { move(-1,0); } default void moveRight() { move(1,0); } default void fall() { move(0, 1); } default void ascend() { move(0, -1); } void revert(); void addTo(Map list); boolean collides(Map map); IGameObject copy(); }
public interface IShape extends Serializable { void rotate(int i); default void rotateLeft() { rotate(1); } default void rotateRight() { rotate(-1); } void revert(); List<IGameObject> applyShape(IGameObject body); }
public interface Drawable { void draw(Graphics g); }
public interface Animate { void toggleHidden(); void show(); }
i guess using swing worker threads for this game was just too much.
public class QueueWorker extends SwingWorker<Timer,Runnable>{
Timer timer;
List<Runnable> tasks;
Runnable onDone;
long counter = 0;
public QueueWorker(List<Runnable> tasks, Runnable onDone) {
this.tasks = tasks;
this.onDone = onDone;
}
@Override
protected Timer doInBackground() throws Exception {
timer = new Timer();
for (Runnable task : tasks) {
publish(task);
}
return timer;
}
@Override
protected void process(List<Runnable> chunks) {
for (Runnable task : chunks) {
task.run();
}
}
@Override
protected void done() {
onDone.run();
}
}
Once a wise java object said:
Don't ask me questions, tell me what to do
I tried so much not to use getters and setters and was trying to achieve a true style of oop. Although It's near to impossible omit them completely , I'm actually rather satisfied with the results
public String toString() {
return
"Printing game state" +
"\n\tCurrent: " + ( (current != null) ? current.toString() : null ) +
"\n\tNext: " + ( (next != null) ? next.toString() : null ) +
"\n\tScore: " + score.getScore()
;
}
(in this example the game state prints itself instead of exposing information to other objects)
Even if we really have to do this:
public class ReadableGameState extends GameState {
public ReadableGameState(Level level, Tetrimino current, Tetrimino next, GameScore score) {
super(level, current, next, score);
}
public Level getLevel() {
return this.level;
}
public Tetrimino getCurrent() {
return this.current;
}
public Tetrimino getNext() {
return this.next;
}
public GameScore getScore() {
return this.score;
}
}
Gradle Version: Gradle 6.2.2
JVM: 13.0.2 (Oracle Corporation 13.0.2+8)
Another Tetris Game
README by aeirya