From 9bd1c688b0c7f8f98bd176bca903aa9948fb79af Mon Sep 17 00:00:00 2001 From: David96 Date: Thu, 31 Dec 2020 12:26:11 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + Bird.java | 28 ++++ Drawable.java | 3 + Main.java | 5 + Manager.java | 96 +++++++++++++ README.md | 20 +++ Rectangle.java | 80 +++++++++++ Turtle.java | 381 +++++++++++++++++++++++++++++++++++++++++++++++++ Welt.java | 78 ++++++++++ 9 files changed, 692 insertions(+) create mode 100644 .gitignore create mode 100644 Bird.java create mode 100644 Drawable.java create mode 100644 Main.java create mode 100644 Manager.java create mode 100644 README.md create mode 100644 Rectangle.java create mode 100644 Turtle.java create mode 100644 Welt.java 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); + } + } + } +}