Java Language!
The assignment is to create a text-based, turn-based Space Invaders style game.
The game board is 6 wide by 10 high, though really it could be wider but that would make the game harder.
You type ‘f’ into the console to fire. This destroys all enemy ships in the column that the player is currently in.
To move you type ‘m’ followed by the number of the column to move to that column. For example if you want to move to the second column you type in “m 2”.
Each round the space invaders move down toward the ground. For each one that hits the ground you lose a life.
Expert Answer
package com.zetcode; import java.awt.EventQueue; import javax.swing.JFrame; public class SpaceInvaders extends JFrame implements Commons { public SpaceInvaders() { initUI(); } private void initUI() { add(new Board()); setTitle("Space Invaders"); setDefaultCloseOperation(EXIT_ON_CLOSE); setSize(BOARD_WIDTH, BOARD_HEIGHT); setLocationRelativeTo(null); setResizable(false); } public static void main(String[] args) { EventQueue.invokeLater(() -> { SpaceInvaders ex = new SpaceInvaders(); ex.setVisible(true); }); } }
commons.java
package com.zetcode; public interface Commons { public static final int BOARD_WIDTH = 358; public static final int BOARD_HEIGHT = 350; public static final int GROUND = 290; public static final int BOMB_HEIGHT = 5; public static final int ALIEN_HEIGHT = 12; public static final int ALIEN_WIDTH = 12; public static final int BORDER_RIGHT = 30; public static final int BORDER_LEFT = 5; public static final int GO_DOWN = 15; public static final int NUMBER_OF_ALIENS_TO_DESTROY = 24; public static final int CHANCE = 5; public static final int DELAY = 17; public static final int PLAYER_WIDTH = 15; public static final int PLAYER_HEIGHT = 10; }
Alien.java
package com.zetcode; import javax.swing.ImageIcon; public class Alien extends Sprite { private Bomb bomb; private final String alienImg = "src/images/alien.png"; public Alien(int x, int y) { initAlien(x, y); } private void initAlien(int x, int y) { this.x = x; this.y = y; bomb = new Bomb(x, y); ImageIcon ii = new ImageIcon(alienImg); setImage(ii.getImage()); } public void act(int direction) { this.x += direction; } public Bomb getBomb() { return bomb; } public class Bomb extends Sprite { private final String bombImg = "src/images/bomb.png"; private boolean destroyed; public Bomb(int x, int y) { initBomb(x, y); } private void initBomb(int x, int y) { setDestroyed(true); this.x = x; this.y = y; ImageIcon ii = new ImageIcon(bombImg); setImage(ii.getImage()); } public void setDestroyed(boolean destroyed) { this.destroyed = destroyed; } public boolean isDestroyed() { return destroyed; } } }
This is the Alien sprite. Each alien has an inner Bomb class.
public void act(int direction) { this.x += direction; }
The act() method is called from the Board class. It is used to position an alien in horizontal direction.
public Bomb getBomb() { return bomb; }
The getBomb() method is called when the alien is about to drop a bomb.
Player.java
package com.zetcode; import java.awt.event.KeyEvent; import javax.swing.ImageIcon; public class Player extends Sprite implements Commons { private final int START_Y = 280; private final int START_X = 270; private final String playerImg = "src/images/player.png"; private int width; public Player() { initPlayer(); } private void initPlayer() { ImageIcon ii = new ImageIcon(playerImg); width = ii.getImage().getWidth(null); setImage(ii.getImage()); setX(START_X); setY(START_Y); } public void act() { x += dx; if (x <= 2) { x = 2; } if (x >= BOARD_WIDTH - 2 * width) { x = BOARD_WIDTH - 2 * width; } } public void keyPressed(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) { dx = -2; } if (key == KeyEvent.VK_RIGHT) { dx = 2; } } public void keyReleased(KeyEvent e) { int key = e.getKeyCode(); if (key == KeyEvent.VK_LEFT) { dx = 0; } if (key == KeyEvent.VK_RIGHT) { dx = 0; } } }
This is the Player sprite. We control the cannon with the cursor keys.
private final int START_Y = 280; private final int START_X = 270;
These are the initial coordinates of the player sprite.
if (key == KeyEvent.VK_LEFT) { dx = -2; }
If we press the left cursor key, the dx variable is set to -2. Next time the act() method is called, the player moves to the left.
if (key == KeyEvent.VK_LEFT) { dx = 0; } if (key == KeyEvent.VK_RIGHT) { dx = 0; }
If we release the left or the right cursor, the dx variable is set to zero. The player sprite stops moving.
Shot.java
package com.zetcode; import javax.swing.ImageIcon; public class Shot extends Sprite { private final String shotImg = "src/images/shot.png"; private final int H_SPACE = 6; private final int V_SPACE = 1; public Shot() { } public Shot(int x, int y) { initShot(x, y); } private void initShot(int x, int y) { ImageIcon ii = new ImageIcon(shotImg); setImage(ii.getImage()); setX(x + H_SPACE); setY(y - V_SPACE); } }
This is the Shot sprite. The shot is triggered with the Space key. The H_SPACE and the V_SPACEconstants are used to position the missile appropriately.
Sprite.java
package com.zetcode; import java.awt.Image; public class Sprite { private boolean visible; private Image image; protected int x; protected int y; protected boolean dying; protected int dx; public Sprite() { visible = true; } public void die() { visible = false; } public boolean isVisible() { return visible; } protected void setVisible(boolean visible) { this.visible = visible; } public void setImage(Image image) { this.image = image; } public Image getImage() { return image; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public int getY() { return y; } public int getX() { return x; } public void setDying(boolean dying) { this.dying = dying; } public boolean isDying() { return this.dying; } }
This is the basic Sprite class. Other sprites inherit from it. It has some common functionality.
Board.java
package com.zetcode; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Toolkit; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; import javax.swing.ImageIcon; import javax.swing.JPanel; public class Board extends JPanel implements Runnable, Commons { private Dimension d; private ArrayList<Alien> aliens; private Player player; private Shot shot; private final int ALIEN_INIT_X = 150; private final int ALIEN_INIT_Y = 5; private int direction = -1; private int deaths = 0; private boolean ingame = true; private final String explImg = "src/images/explosion.png"; private String message = "Game Over"; private Thread animator; public Board() { initBoard(); } private void initBoard() { addKeyListener(new TAdapter()); setFocusable(true); d = new Dimension(BOARD_WIDTH, BOARD_HEIGHT); setBackground(Color.black); gameInit(); setDoubleBuffered(true); } @Override public void addNotify() { super.addNotify(); gameInit(); } public void gameInit() { aliens = new ArrayList<>(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 6; j++) { Alien alien = new Alien(ALIEN_INIT_X + 18 * j, ALIEN_INIT_Y + 18 * i); aliens.add(alien); } } player = new Player(); shot = new Shot(); if (animator == null || !ingame) { animator = new Thread(this); animator.start(); } } public void drawAliens(Graphics g) { Iterator it = aliens.iterator(); for (Alien alien: aliens) { if (alien.isVisible()) { g.drawImage(alien.getImage(), alien.getX(), alien.getY(), this); } if (alien.isDying()) { alien.die(); } } } public void drawPlayer(Graphics g) { if (player.isVisible()) { g.drawImage(player.getImage(), player.getX(), player.getY(), this); } if (player.isDying()) { player.die(); ingame = false; } } public void drawShot(Graphics g) { if (shot.isVisible()) { g.drawImage(shot.getImage(), shot.getX(), shot.getY(), this); } } public void drawBombing(Graphics g) { for (Alien a : aliens) { Alien.Bomb b = a.getBomb(); if (!b.isDestroyed()) { g.drawImage(b.getImage(), b.getX(), b.getY(), this); } } } @Override public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.fillRect(0, 0, d.width, d.height); g.setColor(Color.green); if (ingame) { g.drawLine(0, GROUND, BOARD_WIDTH, GROUND); drawAliens(g); drawPlayer(g); drawShot(g); drawBombing(g); } Toolkit.getDefaultToolkit().sync(); g.dispose(); } public void gameOver() { Graphics g = this.getGraphics(); g.setColor(Color.black); g.fillRect(0, 0, BOARD_WIDTH, BOARD_HEIGHT); g.setColor(new Color(0, 32, 48)); g.fillRect(50, BOARD_WIDTH / 2 - 30, BOARD_WIDTH - 100, 50); g.setColor(Color.white); g.drawRect(50, BOARD_WIDTH / 2 - 30, BOARD_WIDTH - 100, 50); Font small = new Font("Helvetica", Font.BOLD, 14); FontMetrics metr = this.getFontMetrics(small); g.setColor(Color.white); g.setFont(small); g.drawString(message, (BOARD_WIDTH - metr.stringWidth(message)) / 2, BOARD_WIDTH / 2); } public void animationCycle() { if (deaths == NUMBER_OF_ALIENS_TO_DESTROY) { ingame = false; message = "Game won!"; } // player player.act(); // shot if (shot.isVisible()) { int shotX = shot.getX(); int shotY = shot.getY(); for (Alien alien: aliens) { int alienX = alien.getX(); int alienY = alien.getY(); if (alien.isVisible() && shot.isVisible()) { if (shotX >= (alienX) && shotX <= (alienX + ALIEN_WIDTH) && shotY >= (alienY) && shotY <= (alienY + ALIEN_HEIGHT)) { ImageIcon ii = new ImageIcon(explImg); alien.setImage(ii.getImage()); alien.setDying(true); deaths++; shot.die(); } } } int y = shot.getY(); y -= 4; if (y < 0) { shot.die(); } else { shot.setY(y); } } // aliens for (Alien alien: aliens) { int x = alien.getX(); if (x >= BOARD_WIDTH - BORDER_RIGHT && direction != -1) { direction = -1; Iterator i1 = aliens.iterator(); while (i1.hasNext()) { Alien a2 = (Alien) i1.next(); a2.setY(a2.getY() + GO_DOWN); } } if (x <= BORDER_LEFT && direction != 1) { direction = 1; Iterator i2 = aliens.iterator(); while (i2.hasNext()) { Alien a = (Alien) i2.next(); a.setY(a.getY() + GO_DOWN); } } } Iterator it = aliens.iterator(); while (it.hasNext()) { Alien alien = (Alien) it.next(); if (alien.isVisible()) { int y = alien.getY(); if (y > GROUND - ALIEN_HEIGHT) { ingame = false; message = "Invasion!"; } alien.act(direction); } } // bombs Random generator = new Random(); for (Alien alien: aliens) { int shot = generator.nextInt(15); Alien.Bomb b = alien.getBomb(); if (shot == CHANCE && alien.isVisible() && b.isDestroyed()) { b.setDestroyed(false); b.setX(alien.getX()); b.setY(alien.getY()); } int bombX = b.getX(); int bombY = b.getY(); int playerX = player.getX(); int playerY = player.getY(); if (player.isVisible() && !b.isDestroyed()) { if (bombX >= (playerX) && bombX <= (playerX + PLAYER_WIDTH) && bombY >= (playerY) && bombY <= (playerY + PLAYER_HEIGHT)) { ImageIcon ii = new ImageIcon(explImg); player.setImage(ii.getImage()); player.setDying(true); b.setDestroyed(true); } } if (!b.isDestroyed()) { b.setY(b.getY() + 1); if (b.getY() >= GROUND - BOMB_HEIGHT) { b.setDestroyed(true); } } } } @Override public void run() { long beforeTime, timeDiff, sleep; beforeTime = System.currentTimeMillis(); while (ingame) { repaint(); animationCycle(); timeDiff = System.currentTimeMillis() - beforeTime; sleep = DELAY - timeDiff; if (sleep < 0) { sleep = 2; } try { Thread.sleep(sleep); } catch (InterruptedException e) { System.out.println("interrupted"); } beforeTime = System.currentTimeMillis(); } gameOver(); } private class TAdapter extends KeyAdapter { @Override public void keyReleased(KeyEvent e) { player.keyReleased(e); } @Override public void keyPressed(KeyEvent e) { player.keyPressed(e); int x = player.getX(); int y = player.getY(); int key = e.getKeyCode(); if (key == KeyEvent.VK_SPACE) { if (ingame) { if (!shot.isVisible()) { shot = new Shot(x, y); } } } } } }
The main logic of the game is located in the Board class.
aliens = new ArrayList<>(); for (int i = 0; i < 4; i++) { for (int j = 0; j < 6; j++) { Alien alien = new Alien(ALIEN_INIT_X + 18 * j, ALIEN_INIT_Y + 18 * i); aliens.add(alien); } } player = new Player(); shot = new Shot();
In the gameInit() method we create 24 aliens. The alien image size is 12×12 px. We put 6 px space among the aliens. We also create the player and the shot objects.
public void drawBombing(Graphics g) { for (Alien a : aliens) { Alien.Bomb b = a.getBomb(); if (!b.isDestroyed()) { g.drawImage(b.getImage(), b.getX(), b.getY(), this); } } }
The drawBombing() method draws bombs launched by the aliens.
if (ingame) { g.drawLine(0, GROUND, BOARD_WIDTH, GROUND); drawAliens(g); drawPlayer(g); drawShot(g); drawBombing(g); }
Inside the paintComponent() method, we draw the ground, the aliens, the player, the shot, and the bombs.
Next we will examine the animationCycle() method.
if (deaths == NUMBER_OF_ALIENS_TO_DESTROY) { ingame = false; message = "Game won!"; }
If we destroy all aliens, we win the game.
if (alien.isVisible() && shot.isVisible()) { if (shotX >= (alienX) && shotX <= (alienX + ALIEN_WIDTH) && shotY >= (alienY) && shotY <= (alienY+ALIEN_HEIGHT) ) { ImageIcon ii = new ImageIcon(getClass().getResource(expl)); alien.setImage(ii.getImage()); alien.setDying(true); deaths++; shot.die(); } }
If the shot triggered by the player collides with an alien, the alien ship is destroyed. More precisely, the dying flag is set. We use it to display an explosion. The deaths variable increases and the shot sprite is destroyed.
if (x >= BOARD_WIDTH - BORDER_RIGHT && direction != -1) { direction = -1; Iterator i1 = aliens.iterator(); while (i1.hasNext()) { Alien a2 = (Alien) i1.next(); a2.setY(a2.getY() + GO_DOWN); } }
If the aliens reach the right end of the Board, they move down and change their direction to the left.
Iterator it = aliens.iterator(); while (it.hasNext()) { Alien alien = (Alien) it.next(); if (alien.isVisible()) { int y = alien.getY(); if (y > GROUND - ALIEN_HEIGHT) { ingame = false; message = "Invasion!"; } alien.act(direction); } }
This code moves aliens. If they reach the bottom, the invasion begins.
int shot = generator.nextInt(15); Alien.Bomb b = alien.getBomb(); if (shot == CHANCE && alien.isVisible() && b.isDestroyed()) { b.setDestroyed(false); b.setX(alien.getX()); b.setY(alien.getY()); }
This is the code that determines whether the alien will drop a bomb. The alien must not be destroyed; i.e. he must be visible. The bomb’s destroyed flag must be set. In other words, it is alien’s first bomb dropping or previous dropped bomb already hit the ground. If these two conditions are fulfilled, the bombing is left to the chance.
if (!b.isDestroyed()) { b.setY(b.getY() + 1); if (b.getY() >= GROUND - BOMB_HEIGHT) { b.setDestroyed(true); } }
If the bomb is not destroyed, it goes 1 px to the ground. If it hits the bottom, the destroyed flag is set. The alien is now ready to drop another bomb.
public void keyReleased(KeyEvent e) { player.keyReleased(e); }