Anonymous avatar Anonymous committed 84c3925

Added Projectiles

Comments (0)

Files changed (10)

defender/src/main/java/com/fxexperience/games/defender/games/simple/Enemy.java

 import javafx.beans.property.SimpleObjectProperty;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
 import javafx.scene.Parent;
 import javafx.scene.shape.Path;
 import org.slf4j.Logger;
         }
     }
 
+    public Point2D getCenter() {
+        Bounds bounds = getBoundsInParent();
+        return new Point2D(
+                bounds.getMinX() + (bounds.getWidth()/2.0),
+                bounds.getMinY() + (bounds.getHeight()/2.0));
+    }
+
     protected void destinationReached() {
         // done
         log.info("Enemy made it to the destination");

defender/src/main/java/com/fxexperience/games/defender/games/simple/GameControlPanel.java

 package com.fxexperience.games.defender.games.simple;
 
-import com.fxexperience.games.defender.games.simple.towers.BasicTower;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
 import javafx.fxml.FXMLLoader;
-import javafx.scene.Node;
 import javafx.scene.Parent;
 import javafx.scene.control.Button;
-import javafx.scene.control.ToggleButton;
-import javafx.scene.control.ToggleGroup;
-import javafx.scene.layout.GridPane;
 import javafx.scene.layout.StackPane;
 
-import java.util.Arrays;
-import java.util.List;
-
 /**
  */
 public class GameControlPanel extends StackPane {
     public GameControlPanel(final Game game) {
         getStyleClass().add("game-control-panel");
-        List<? extends Tower> towers = Arrays.asList(
-                new BasicTower()
-        );
 
         try {
             Parent content = FXMLLoader.load(getClass().getResource("/styles/games/simple/GameControlPanel.fxml"));
             getChildren().add(content);
 
-            ToggleGroup grp = new ToggleGroup();
-            GridPane towerGrid = (GridPane)content.lookup("#tower-grid");
-            List<Node> towerButtons = towerGrid.getChildren();
-            for (int i=0; i<towerButtons.size(); i++) {
-                ToggleButton btn = (ToggleButton)towerButtons.get(i);
-                btn.setToggleGroup(grp);
-                if (i < towers.size()) {
-                    Tower tower = towers.get(i);
-                    btn.setText(tower.name.get());
-                } else {
-                    // might want to just set disabled instead, and have each button hard-coded to
-                    // be of a certain tower type!
-                    btn.setVisible(false);
-                }
-            }
-
             final Button playButton = (Button)content.lookup("#play-button");
             playButton.setOnAction(new EventHandler<ActionEvent>() {
                 @Override

defender/src/main/java/com/fxexperience/games/defender/games/simple/LevelPanel.java

  */
 package com.fxexperience.games.defender.games.simple;
 
+import javafx.geometry.Bounds;
 import javafx.geometry.Insets;
+import javafx.geometry.Point2D;
 import javafx.scene.Node;
 import javafx.scene.layout.Pane;
 import javafx.scene.layout.Region;
     private Path path;
     private List<Enemy> enemies;
     private List<Tower> towers;
+    private List<Projectile> projectiles;
 
     public LevelPanel(final Path path, Wave... waves) {
 
         enemies = new ArrayList<>();
         enemyLayer = new Pane();
         towers = new ArrayList<>();
+        projectiles = new ArrayList<>();
         towerLayer = new Pane();
         projectileLayer = new Pane();
         getChildren().addAll(background, enemyLayer, towerLayer, projectileLayer);
             }
         }
 
-        // pulse all our enemies
-        for (Enemy enemy : enemies) {
-            enemy.pulse();
-        }
-
-        // pulse all our towers
-        for (Tower tower : towers) {
-            tower.pulse();
+        // pulse our sprites (cache to list to avoid concurrent modification problems - maybe could be done smarter)
+        List<GamePulseListener> pulsers = new ArrayList<>();
+        pulsers.addAll(enemies);
+        pulsers.addAll(towers);
+        pulsers.addAll(projectiles);
+        for (GamePulseListener pulser : pulsers) {
+            pulser.pulse();
         }
     }
 
+    public List<Enemy> findEnemiesInArea(Point2D center, int radius) {
+        List<Enemy> matches = new ArrayList<>();
+        for (Enemy enemy : enemies) {
+            Bounds bounds = enemy.getBoundsInParent();
+            if (center.getX() - radius <= bounds.getMaxX() && center.getX() + radius >= bounds.getMinX()
+                    && center.getY() - radius <= bounds.getMaxY() && center.getY() + radius >= bounds.getMinY()) {
+                matches.add(enemy);
+            }
+        }
+        return matches;
+    }
+
     public void addEnemy(Enemy enemy) {
+        enemy.setLevel(this);
         enemies.add(enemy);
         enemyLayer.getChildren().add(enemy);
     }
 
     public void removeEnemy(Enemy enemy) {
+        enemy.setLevel(null);
         enemies.remove(enemy);
         enemyLayer.getChildren().remove(enemy);
     }
 
     public void addTower(Tower tower) {
+        tower.setLevel(this);
         towers.add(tower);
         towerLayer.getChildren().add(tower);
     }
 
     public void removeTower(Tower tower) {
+        tower.setLevel(null);
         towers.remove(tower);
         towerLayer.getChildren().remove(tower);
     }
 
+    public void addProjectile(Projectile projectile) {
+        projectile.setLevel(this);
+        projectiles.add(projectile);
+        projectileLayer.getChildren().add(projectile);
+    }
+
+    public void removeProjectile(Projectile projectile) {
+        projectile.setLevel(null);
+        projectiles.remove(projectile);
+        projectileLayer.getChildren().remove(projectile);
+    }
+
     @Override
     protected double computeMinWidth(double v) {
         return computePrefWidth(v);

defender/src/main/java/com/fxexperience/games/defender/games/simple/Projectile.java

+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple;
+
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
+import javafx.scene.Parent;
+
+public class Projectile extends Parent implements GamePulseListener {
+
+    protected LevelPanel level;
+
+    public void setLevel(LevelPanel level) {
+        this.level = level;
+    }
+
+    @Override
+    public void pulse() {
+
+    }
+
+    public Point2D getCenter() {
+        Bounds bounds = getBoundsInParent();
+        return new Point2D(
+                bounds.getMinX() + (bounds.getWidth()/2.0),
+                bounds.getMinY() + (bounds.getHeight()/2.0));
+    }
+}

defender/src/main/java/com/fxexperience/games/defender/games/simple/SimpleGameScene.java

 import javafx.scene.Scene;
 import javafx.scene.layout.BorderPane;
 import javafx.scene.layout.StackPane;
+import javafx.util.Duration;
 
 /**
  * The Scene which will be used for the simple version of the game. This is
         FirstLevel level = new FirstLevel();
 
         // hard code in a tower for now
-        Tower tower = new BasicTower();
+        Tower tower = new BasicTower(Duration.seconds(1), 150);
         tower.setLayoutY(200);
         tower.setLayoutX(300);
         level.addTower(tower);

defender/src/main/java/com/fxexperience/games/defender/games/simple/Tower.java

  */
 package com.fxexperience.games.defender.games.simple;
 
-import javafx.beans.property.*;
+import javafx.geometry.Bounds;
+import javafx.geometry.Point2D;
 import javafx.scene.Parent;
 
 /**
  */
 public class Tower extends Parent implements GamePulseListener {
-    public final IntegerProperty level = new SimpleIntegerProperty(this, "level");
-    public final StringProperty name = new SimpleStringProperty(this, "name");
-    public final DoubleProperty range = new SimpleDoubleProperty(this, "range", 200);
-    public final DoubleProperty fireRate = new SimpleDoubleProperty(this, "fireRate", 300); // measured in shots per millisecond
+
+    protected LevelPanel level;
 
     // Want to create an event that happens when the tower bullet hits an enemy, such that
     // different types of towers can do different things depending on what kind of enemy
     // Need a method which is called whenever the tower should shoot, which will give it the list
     // of possible targets so it can choose which target to fire at?
 
+
+    public void setLevel(LevelPanel level) {
+        this.level = level;
+    }
+
     @Override
     public void pulse() {
     }
+
+    public Point2D getCenter() {
+        Bounds bounds = getBoundsInParent();
+        return new Point2D(
+            bounds.getMinX() + (bounds.getWidth()/2.0),
+            bounds.getMinY() + (bounds.getHeight()/2.0));
+    }
 }

defender/src/main/java/com/fxexperience/games/defender/games/simple/projectiles/BasicProjectile.java

+/*
+ * Copyright (c) 2012, FX Experience. All rights  reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice, this list of
+ * conditions and the following disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * Neither the name of the author nor the names of its contributors may be used to endorse or
+ * promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.fxexperience.games.defender.games.simple.projectiles;
+
+import com.fxexperience.games.defender.games.simple.Projectile;
+import javafx.geometry.Point2D;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BasicProjectile extends Projectile {
+
+    private static final Logger log = LoggerFactory.getLogger(BasicProjectile.class);
+
+    private Point2D destination;
+    private double xstep;
+    private double ystep;
+    private long lastStep;
+    private long stepDelay;
+
+    public BasicProjectile(Point2D start, Point2D destination, double speed) {
+        this.destination = destination;
+
+        getChildren().add(new Circle(5, Color.RED));
+
+        setTranslateX(start.getX());
+        setTranslateY(start.getY());
+
+        getStyleClass().add("bullet");
+        setTranslateY(start.getY());
+        setTranslateX(start.getX());
+
+        double xdist = destination.getX() - start.getX();
+        double ydist = destination.getY() - start.getY();
+
+        xstep = xdist / 5;
+        ystep = ydist / 5;
+        stepDelay = 30;
+
+        log.debug("BasicProjectile firing from ({}, {}) to ({}, {}), step size = ({}, {})", new Object[]{
+                start.getX(), start.getY(), destination.getX(), destination.getY(), xstep, ystep
+        });
+    }
+
+    @Override
+    public void pulse() {
+
+        long now = System.currentTimeMillis();
+        if (now - lastStep > stepDelay) {
+            setTranslateX(getTranslateX() + xstep);
+            setTranslateY(getTranslateY() + ystep);
+            if (Math.abs(getTranslateX() - destination.getX()) < 2 && Math.abs(getTranslateY() - destination.getY()) < 2) {
+                destinationReached();
+            }
+            lastStep = now;
+        }
+    }
+
+    private void destinationReached() {
+        log.debug("BasicProjectile reached target destination");
+        level.removeProjectile(this);
+    }
+}

defender/src/main/java/com/fxexperience/games/defender/games/simple/towers/BasicTower.java

  */
 package com.fxexperience.games.defender.games.simple.towers;
 
-import com.fxexperience.games.defender.games.simple.LevelPanel;
+import com.fxexperience.games.defender.games.simple.Enemy;
+import com.fxexperience.games.defender.games.simple.Projectile;
 import com.fxexperience.games.defender.games.simple.Tower;
+import com.fxexperience.games.defender.games.simple.projectiles.BasicProjectile;
 import javafx.scene.layout.Region;
+import javafx.util.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
 
 public class BasicTower extends Tower {
 
-    private LevelPanel level;
+    private static final Logger log = LoggerFactory.getLogger(BasicTower.class);
 
-    public BasicTower() {
-        name.set("Basic Tower");
+    private Duration shotDelay;
+    private long lastShot;
+    private int range;
+
+    public BasicTower(Duration shotDelay, int range) {
+        this.shotDelay = shotDelay;
+        this.range = range;
+
         Region body = new Region();
         body.getStyleClass().add("tower");
         getChildren().add(body);
 
     @Override
     public void pulse() {
-//
-//        long now = System.currentTimeMillis();
-//        if (now - lastSpawn > delay.toMillis()) {
-//        }
-//
-//            // TODO can enable arbitrarily complex target acquisition logic here. For example,
-//        // the tower should calculate what enemy it can damage the most, and destroy before
-//        // passing outside its bounds.
-//        // Check to see if any enemies are within the range of this guy.
+
+        long now = System.currentTimeMillis();
+        if (level != null && now - lastShot > shotDelay.toMillis()) {
+
+            List<Enemy> enemies = level.findEnemiesInArea(getCenter(), range);
+            if (enemies.size() > 0) {
+                // TODO can enable arbitrarily complex target acquisition logic here. For example,
+                // the tower should calculate what enemy it can damage the most, and destroy before
+                // passing outside its bounds.
+                // Check to see if any enemies are within the range of this guy.
+                shootAt(enemies.get(0));
+                lastShot = now;
+            }
+        }
+
 //        Bounds bounds = getBoundsInParent();
 //        Point2D center = new Point2D(
 //                bounds.getMinX() + (bounds.getWidth()/2.0),
 //        delay = Duration.millis(fireRate.get());
 
     }
+
+    protected void shootAt(Enemy enemy) {
+        log.debug("Shooting at: {}", enemy);
+        Projectile projectile = new BasicProjectile(getCenter(), enemy.getCenter(), 5);
+        level.addProjectile(projectile);
+    }
 }

defender/src/main/resources/styles/games/simple/GameControlPanel.fxml

             minWidth="-Infinity" prefHeight="598.0" prefWidth="262.0">
   <children>
     <Label id="title" alignment="CENTER" contentDisplay="LEFT" prefHeight="-1.0" prefWidth="-1.0" text="JavaFX Defender" AnchorPane.leftAnchor="14.0" AnchorPane.rightAnchor="14.0" AnchorPane.topAnchor="14.0" />
-    <GridPane id="tower-grid" layoutY="139.0" prefHeight="342.0" prefWidth="206.0" AnchorPane.leftAnchor="28.0" AnchorPane.rightAnchor="28.0">
-      <children>
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="0" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="1" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="2" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="3" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="4" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="0" GridPane.rowIndex="5" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="0" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="1" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="2" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="3" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="4" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="5" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="2" GridPane.rowIndex="0" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="2" GridPane.rowIndex="1" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="2" GridPane.rowIndex="2" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="2" GridPane.rowIndex="3" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="2" GridPane.rowIndex="4" />
-        <ToggleButton focusTraversable="false" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" styleClass="tower-button" text="" wrapText="true" GridPane.columnIndex="2" GridPane.rowIndex="5" />
-      </children>
-      <columnConstraints>
-        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
-        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
-        <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
-      </columnConstraints>
-      <rowConstraints>
-        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-        <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
-      </rowConstraints>
-    </GridPane>
     <Button id="new-game-button" layoutY="528.0" mnemonicParsing="false" prefWidth="206.0" text="New Game" AnchorPane.leftAnchor="28.0" AnchorPane.rightAnchor="28.0" />
     <Label id="towers-label" layoutX="28.0" layoutY="116.0" text="Towers" />
     <Label id="money-label" layoutX="28.0" layoutY="62.0" text="Money" />

defender/src/main/resources/styles/games/simple/styles.css

 
 
 
-
-
-
-
-
-
-
-
-
-
-
-
 .gameToolBox {
     -fx-padding: 10;
     -fx-background-color: gray, #ffffdd;
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.