commit 9bd1c688b0c7f8f98bd176bca903aa9948fb79af Author: David96 Date: Thu Dec 31 12:26:11 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b468b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.class diff --git a/Bird.java b/Bird.java new file mode 100644 index 0000000..ae3a306 --- /dev/null +++ b/Bird.java @@ -0,0 +1,28 @@ +public class Bird +{ + private static final int GRAVITY = 20; + private static final int JUMP_SPEED = 7; + private final Rectangle _rect; + private double _speed; + public Bird(Manager man) + { + _rect = new Rectangle(20, 20, 20, 20, "cyan", true); + man.addDrawable(_rect); + } + + public void update(double elapsed) + { + _speed += GRAVITY * elapsed * 1e-3d; + _rect.setPos(_rect.getX(), _rect.getY() + _speed); + } + + public void jump() + { + _speed = -JUMP_SPEED; + } + + public Rectangle getRect() + { + return _rect; + } +} diff --git a/Drawable.java b/Drawable.java new file mode 100644 index 0000000..b3dc46d --- /dev/null +++ b/Drawable.java @@ -0,0 +1,3 @@ +interface Drawable { + public abstract void draw(Turtle t, double elapsedTime); +} diff --git a/Main.java b/Main.java new file mode 100644 index 0000000..6b8735b --- /dev/null +++ b/Main.java @@ -0,0 +1,5 @@ +class Main { + public static void main(String[] args) { + new Manager(); + } +} diff --git a/Manager.java b/Manager.java new file mode 100644 index 0000000..14ca34a --- /dev/null +++ b/Manager.java @@ -0,0 +1,96 @@ +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import java.awt.event.KeyListener; +import java.awt.event.KeyEvent; +import javax.swing.JFrame; + +class Manager implements KeyListener +{ + private final String USAGE = + "------------------------------\n" + + "Simple version of Flappy Bird\n" + + "Press SPACE to jump!\n" + + "------------------------------\n"; + private final Bird _bird; + private final List _drawables; + + public Manager() + { + _drawables = new ArrayList(); + Turtle turtle = new Turtle(); + turtle.setzeGeschwindigkeit(10); + + JFrame fieldValue = new JFrame(); + // Modifying Turtle.java is forbidden so use reflection to get the JFrame and add + // a KeyListener lol + try + { + + Field frameField = TurtleWelt.class.getDeclaredField("_frame"); + + frameField.setAccessible(true); + + fieldValue = (JFrame) frameField.get(TurtleWelt.globaleWelt); + fieldValue.addKeyListener(this); + } + catch (Exception ex) + { + System.out.println("Reflection hack failed"); + System.exit(-1); + } + + System.out.print(USAGE); + + _bird = new Bird(this); + Welt welt = new Welt(this); + + long lastNanos, elapsed; + lastNanos = System.nanoTime(); + while (true) + { + elapsed = System.nanoTime() - lastNanos; + lastNanos = System.nanoTime(); + + _bird.update(elapsed * 1e-6d); + welt.update(elapsed * 1e-6d); + + if (welt.checkCollision(_bird.getRect())) + { + welt.reset(); + } + + TurtleWelt.globaleWelt.bildschirmEinfaerben(255, 255, 255); + for (var d : _drawables) + { + d.draw(turtle, elapsed * 1e-6d); + } + + // Try to achieve approximately 60 FPS (1000ms / 60 FPS = 16.666ms) + int missingMs = (int)(16 - (System.nanoTime() - lastNanos) * 1e-6d); + if (missingMs > 0) + { + try + { + Thread.sleep(missingMs); + } catch (Exception e) {} + } + } + } + + public void addDrawable(Drawable d) + { + _drawables.add(d); + } + + public void keyPressed(KeyEvent e) + { + if (e.getKeyChar() == ' ') + { + _bird.jump(); + } + } + + public void keyReleased(KeyEvent e) {} + public void keyTyped(KeyEvent e) {} +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..697e09a --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Flappy bird + +Simple version of Flappy Bird. +Use SPACE to jump! + +## Build & Run + +``` +# Build all java files +javac *.java +# Run Main class +java Main +``` + +# Disclaimer + +Ich weiß, dass das nicht das ist, was gedacht war. Ich hatte über Weihnachten 2 Zugfahrten und hab es mir zur Challenge +gesetzt, in dieser Zeit mit den limitierten Möglichkeiten der Turtle Klasse ein Flappy Bird zu schreiben. +Dementsprechend blieb auch keine Zeit, Kommentare für getter/setter hinzuzufügen (die brauch nun auch eigentlich +wirklich keiner)… diff --git a/Rectangle.java b/Rectangle.java new file mode 100644 index 0000000..7d1a10f --- /dev/null +++ b/Rectangle.java @@ -0,0 +1,80 @@ + +class Rectangle implements Drawable { + private double _x, _y, _width, _height; + private String _color; + private boolean _fill; + + public Rectangle(double x, double y, double width, double height, String c, boolean fill) { + _x = x; + _y = y; + _width = width; + _height = height; + _color = c; + _fill = fill; + } + + public void drawRect(Turtle t) { + t.hinterlasseKeineSpur(); + t.geheZu((double)_x, (double)_y); + t.hinterlasseSpur(); + t.setzeFarbe(_color); + + if (_fill) { + for (int i = 0; i < _height - 1; ++i) { + t.geheVor(_width - 1); + boolean right = t.gibRichtung() == 0; + t.setzeRichtung(t.gibRichtung() + (right ? 90 : -90)); + t.geheVor(1); + t.setzeRichtung(t.gibRichtung() + (right ? 90 : -90)); + } + t.geheVor(_width - 1); + } else { + for (int i = 0; i < 2; ++i) { + t.geheVor(_width - 1); + t.setzeRichtung(t.gibRichtung() + 90); + t.geheVor(_height - 1); + t.setzeRichtung(t.gibRichtung() + 90); + } + } + + t.setzeRichtung(0); + } + + public void draw(Turtle t, double elapsed) { + drawRect(t); + } + + public boolean intersects(Rectangle r) + { + return _x + _width >= r.getX() + &&_x < r.getX() + r.getWidth() + && _y + _height >= r.getY() + && _y < r.getY() + r.getHeight(); + } + + public double getWidth() + { + return _width; + } + + public double getHeight() + { + return _height; + } + + public double getX() + { + return _x; + } + + public double getY() + { + return _y; + } + + public void setPos(double x, double y) + { + _x = x; + _y = y; + } +} diff --git a/Turtle.java b/Turtle.java new file mode 100644 index 0000000..160928a --- /dev/null +++ b/Turtle.java @@ -0,0 +1,381 @@ +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.image.BufferedImage; + +import javax.swing.JFrame; +import javax.swing.JPanel; + +import java.util.HashMap; + +/** + * Diese Klasse definiert Turtles, die mit einfachen + * Operationen "bewegt" werden koennen. Die Bewegungen + * einer Turtle koennen auf einer Zeichenflaeche sichtbar + * gemacht werden, die Turtle hinterlaesst quasi eine + * Spur. Die Turtle selbst ist nicht sichtbar. + *

+ * Warum gerade eine Turtle, also eine Schildkroete? + * Hauptsaechlich aufgrund der Programmiersprache LOGO, mit + * der die Idee der Turtle Graphics bekannt geworden ist. + * Etwas mehr Hintergrund ist hier zu finden: + * http://llk.media.mit.edu/projects/circles/turtles.html + * + * @author Original von Alfred Hermes (14.12.2003) + * @author Simon Gerlach + * @author Axel Schmolitzky + * @author Fredrik Winkler + * @author Clara Marie Lueders + * @version 5. Dezember 2018 + */ +class Turtle +{ + // Position dieser Turtle + private double _x; + private double _y; + + // Richtung dieser Turtle + private double _richtung; + + // Die Farbe der Spur, die diese Turtle hinterlaesst + private Color _farbe; + + // Gibt an, ob diese Turtle eine Spur hinterlaesst oder nicht + private boolean _spurHinterlassen; + + // Reaktionszeit einer Turtle auf Befehle + private int _verzoegerung; + + // Moegliche Farbwerte + private static final HashMap FARBEN; + private static final Color[] FARB_ARRAY; + + static + { + FARBEN = new HashMap(); + FARBEN.put("schwarz", Color.BLACK); + FARBEN.put("blau", Color.BLUE); + FARBEN.put("cyan", Color.CYAN); + FARBEN.put("dunkelgrau", Color.DARK_GRAY); + FARBEN.put("grau", Color.GRAY); + FARBEN.put("gruen", Color.GREEN); + FARBEN.put("hellgrau", Color.LIGHT_GRAY); + FARBEN.put("magenta", Color.MAGENTA); + FARBEN.put("orange", Color.ORANGE); + FARBEN.put("pink", Color.PINK); + FARBEN.put("rot", Color.RED); + FARBEN.put("weiss", Color.WHITE); + FARBEN.put("gelb", Color.YELLOW); + FARB_ARRAY = FARBEN.values().toArray(new Color[FARBEN.size()]); + } + + /** + * Initialisiert eine neue Turtle auf den Mittelpunkt der Welt. + * Die Ausrichtung ist nach rechts (0 Grad), + * und es wird eine schwarze Spur hinterlassen. + */ + public Turtle() + { + this(TurtleWelt.WIDTH / 2, TurtleWelt.HEIGHT / 2); + } + + /** + * Loescht alle Spuren, die die Turtle bisher hinterlassen hat. + */ + public void loescheAlleSpuren() + { + TurtleWelt.globaleWelt.loescheAlleSpuren(); + } + + /** + * Initialisiert eine neue Turtle auf einen gegebenen Startpunkt. + * Die Ausrichtung ist nach rechts (0 Grad), + * und es wird eine schwarze Spur hinterlassen. + * @param x die X-Koordinate + * @param y die Y-Koordinate + */ + public Turtle(double x, double y) + { + _x = x; + _y = y; + _richtung = 0; + _verzoegerung = 1; + _farbe = Color.BLACK; + _spurHinterlassen = true; + } + + /** + * Bewegt die Turtle vorwaerts in Blickrichtung. + * @param schritte Anzahl der Pixel, die die Turtle zuruecklegen + * soll + */ + public void geheVor(double schritte) + { + double radians = Math.toRadians(_richtung); + double nextX = _x + Math.cos(radians) * schritte; + double nextY = _y + Math.sin(radians) * schritte; + geheZu(nextX, nextY); + } + + /** + * Bewegt die Turtle auf einer Linie zu einer neuen Position. + * @param x X-Koordinate der neuen Position + * @param y Y-Koordinate der neuen Position + */ + public void geheZu(double x, double y) + { + if (_spurHinterlassen) + { + TurtleWelt.globaleWelt.zeichneLinie(_x, _y, x, y, _farbe); + } + _x = x; + _y = y; + verzoegern(); + } + + /** + * Setzt die Blickrichtung der Turtle. + * @param winkel 0 = rechts, 90 = unten, 180 = links, 270 = oben + */ + public void setzeRichtung(double winkel) + { + _richtung = winkel; + verzoegern(); + } + + /** + * Dreht die Turtle um eine angegebene Winkeldifferenz. + * @param winkel zu drehende Winkeldifferenz in Grad + */ + public void drehe(double winkel) + { + setzeRichtung(_richtung + winkel); + } + + /** + * Laesst die Turtle auf den angegebenen Punkt (x, y) schauen. + * @param x X-Koordinate + * @param y Y-Koordinate + */ + public void schaueAuf(double x, double y) + { + double deltaX = x - _x; + double deltaY = y - _y; + setzeRichtung(Math.toDegrees(Math.atan2(deltaY, deltaX))); + } + + /** + * Setzt die Farbe der Spur, die diese Turtle hinterlaesst. + * Moegliche Farben sind "schwarz", "blau", "cyan", "dunkelgrau", "grau", "gruen", "hellgrau", "magenta", "orange", "pink", "rot", "weiss", "gelb". + * @param neueFarbe die neue Spur-Farbe der Turtle + */ + public void setzeFarbe(String neueFarbe) + { + if ((neueFarbe == null) || ((_farbe = FARBEN.get(neueFarbe.toLowerCase())) == null)) + { + _farbe = Color.BLACK; + } + } + + /** + * Setzt eine der 13 moeglichen Farben. + * @param farbnummer die Farbummer (0 = schwarz, 12 = gelb) + */ + public void setzeFarbe(int farbnummer) + { + _farbe = FARB_ARRAY[Math.abs(farbnummer % 13)]; + } + + /** + * Bewegungen der Turtle sind ab sofort unsichtbar. + */ + public void hinterlasseKeineSpur() + { + _spurHinterlassen = false; + } + + /** + * Bewegungen der Turtle sind ab sofort sichtbar. + */ + public void hinterlasseSpur() + { + _spurHinterlassen = true; + } + + /** + * Setzt die Geschwindigkeit, mit der die Turtle auf Anweisungen reagiert. + * @param geschwindigkeit von 0 (langsam) bis 10 (schnell) + */ + public void setzeGeschwindigkeit(int geschwindigkeit) + { + if (geschwindigkeit > 10) + { + geschwindigkeit = 10; + } + else if (geschwindigkeit < 0) + { + geschwindigkeit = 0; + } + _verzoegerung = 10 - geschwindigkeit; + } + + /** + * Gibt die X-Position der Turtle zurueck. + * @return die X-Position + */ + public double gibX() + { + return _x; + } + + /** + * Gibt die Y-Position der Turtle zurueck. + * @return die Y-Position + */ + public double gibY() + { + return _y; + } + + /** + * Gibt die Richtung der Turtle zurueck. + * @return die Richtung + */ + public double gibRichtung() + { + return _richtung; + } + + /** + * Abhaengig von der Geschwindigkeit dieser Turtle wird hier kurze Zeit verzoegert. + */ + private void verzoegern() + { + if (_verzoegerung > 0) + { + try + { + Thread.sleep(_verzoegerung); + } + catch (InterruptedException ignore) + { + } + } + } +} + +/** + * Eine Welt, in der sich Turtles bewegen. + */ +class TurtleWelt +{ + public static final int WIDTH = 500; + public static final int HEIGHT = 500; + + public static final TurtleWelt globaleWelt = new TurtleWelt(); + + private final Graphics2D _graphics; + private final JFrame _frame; + + /** + * Initialisiert eine neue TurtleWelt. + */ + public TurtleWelt() + { + BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + + _graphics = image.createGraphics(); + _graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + _graphics.setColor(Color.WHITE); + _graphics.fillRect(0, 0, WIDTH, HEIGHT); + + JPanel panel = new ImagePanel(image); + panel.addMouseListener(new MouseAdapter() + { + public void mousePressed(MouseEvent e) + { + if (e.getButton() != MouseEvent.BUTTON1) + { + loescheAlleSpuren(); + } + } + }); + + _frame = new JFrame("Turtle Graphics - the canvas can be cleared by right-clicking on it"); + _frame.add(panel); + _frame.pack(); + _frame.setResizable(false); + _frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + _frame.setVisible(true); + } + + /** + * Loescht alle Spuren, die Turtles bisher hinterlassen haben. + */ + public void loescheAlleSpuren() + { + _graphics.setColor(new Color(16, 68, 116)); + _graphics.fillRect(0, 0, WIDTH, HEIGHT); + _frame.repaint(); + } + + /** + * Faerbt den Frame in einer bestimmten Farbe mit RGB wert + * + * @param r der Rotanteil + * @param g der Gruenanteil + * @param b der Blauanteil + */ + public void bildschirmEinfaerben(int r, int g, int b) + { + //Absicherung gegen unzulaessige Werte + r = r % 256; + g = g % 256; + b = b % 256; + + _graphics.setColor(new Color(r, g, b)); + _graphics.fillRect(0, 0, WIDTH, HEIGHT); + _frame.repaint(); + } + + /** + * Zeichnet eine farbige Linie von (x1/y1) nach (x2/y2). + */ + public void zeichneLinie(double x1, double y1, double x2, double y2, Color farbe) + { + _graphics.setColor(farbe); + _graphics.drawLine((int)(x1 + 0.5), (int)(y1 + 0.5), (int)(x2 + 0.5), (int)(y2 + 0.5)); + _frame.repaint(); + } +} + +/** + * Ein Panel, das lediglich aus einem Bild besteht. + */ +class ImagePanel extends JPanel +{ + private final BufferedImage _image; + + /** + * Initialisiert ein neues ImagePanel mit dem angegebenen Bild. + */ + public ImagePanel(BufferedImage image) + { + super(null); + _image = image; + setPreferredSize(new Dimension(_image.getWidth(), _image.getHeight())); + } + + /** + * Zeichnet das ImagePanel. + */ + public void paintComponent(Graphics g) + { + g.drawImage(_image, 0, 0, null); + } +} diff --git a/Welt.java b/Welt.java new file mode 100644 index 0000000..33c7b29 --- /dev/null +++ b/Welt.java @@ -0,0 +1,78 @@ +import java.util.ArrayList; +import java.util.Random; + +class Welt implements Drawable +{ + private static final int DISTANCE = 250; + private final ArrayList _obstacles; + private double _speed = 150; + private double _acc = 0.5; + private final Random _random; + + public Welt(Manager manager) + { + manager.addDrawable(this); + _random = new Random(); + _obstacles = new ArrayList(); + reset(); + } + + public void reset() + { + _obstacles.clear(); + createObstacle(TurtleWelt.WIDTH); + } + + private void createObstacle(int x) + { + int gap = _random.nextInt(TurtleWelt.HEIGHT - 200) + 50; + _obstacles.add(new Rectangle(x, 0, 30, gap, "black", false)); + _obstacles.add(new Rectangle(x, gap + 100, 30, TurtleWelt.HEIGHT, "black", false)); + } + + public void update(double elapsed) + { + ArrayList remove = new ArrayList(); + for (Rectangle r : _obstacles) + { + r.setPos(r.getX() - (elapsed * 1e-3 * _speed), r.getY()); + if (r.getX() < -r.getWidth()) + { + remove.add(r); + } + } + for (Rectangle r : remove) + { + _obstacles.remove(r); + } + Rectangle lastObstacle = _obstacles.get(_obstacles.size() - 1); + if (lastObstacle.getX() + lastObstacle.getWidth() < TurtleWelt.WIDTH - DISTANCE) + { + createObstacle(TurtleWelt.WIDTH); + } + _speed += _acc * elapsed * 1e-3; + } + + public boolean checkCollision(Rectangle r) + { + for (Rectangle o : _obstacles) + { + if (r.intersects(o)) + { + return true; + } + } + return false; + } + + public void draw(Turtle t, double elapsed) + { + for (Rectangle r : _obstacles) + { + if (r.getX() < TurtleWelt.WIDTH) + { + r.draw(t, elapsed); + } + } + } +}