-1

I'm writing a game of Snake and am trying to implement the movement of the snake. Currently, the head of the snake moves perfectly according to arrowpad input, but it moves independently of the rest of the snake. To me, it seems that this is because the instantiation and passage of the KeyFrame object which governs the Snake movement (which is performed in a peek() operation) only occurs once for the head of the snake rather than for each piece.

The snake pieces are extensions of the rectangle class, called Segment.

Here is the code where I create and pass all the KeyFrames to the Segments:

    public class Snake extends Group
    {
        private static final Duration STANDARD_DURATION = Duration.millis(150);
        private final Timeline timeline = new Timeline();

        private final Segment[] snakeFrame =
                {
                        new Segment( new Rectangle(250, 250, 25, 25) ),
                        new Segment( new Rectangle(225, 250, 25, 25) ),
                        new Segment( new Rectangle(200, 250, 25, 25) ),
                        new Segment( new Rectangle(175, 250, 25, 25) ),
                        new Segment( new Rectangle(150, 250, 25, 25) ),
                };



        public Snake()
        {
            timeline.setCycleCount(Animation.INDEFINITE);

            getChildren().addAll(FXCollections.observableList
                    (
                    Arrays.stream(snakeFrame)
                            .peek(Segment ->
                            {
                                Segment.setFill(Color.DARKBLUE);
                                Segment.setStroke(Color.ORANGE);
                                Segment.setFrame(new KeyFrame(STANDARD_DURATION,
                                        event ->
                                        {
                                            getScene().setOnKeyPressed(keyEvent -> Segment.changeDirection(keyEvent.getCode()));
                                            Segment.move();
                                        }));
                                timeline.getKeyFrames().add(Segment.getFrame());
                            })
                            .collect(Collectors.toList())
                    )
            );
        }

      public void play()
      {
          timeline.play();
      }
    }

Why does this only attach a KeyFrame to the head Segment of the list instead of each Segment?

Joe Coon
  • 21
  • 6
  • 1
    Seriously, I have no idea what you are doing. Why would you want to set a new key event on every frame? On top of that, you have a single `Timeline` which you have one `KeyFrame` for each `Segment` object you have. Doing this means that the `Timeline` would move *one* `Segment` every `STANDARD_DURATION` has passed. – Jai Dec 19 '17 at 05:20
  • When I run my program, all 5 segments of the snake move, but only one changes direction. I've been having trouble getting any of the segments to receive direction input so it's kinda been a break for me. – Joe Coon Dec 19 '17 at 06:51
  • I see that you set the listener by calling `getScene().setOnKeyPressed()`. If this follows common Java naming conventions, then for each subsequent segment you will **replace** the listener that has been attached for the previous segment. That would explain why only one segment listens to the key stroke in the end. Instead, you could try to put `getScene().setOnKeyPressed(e -> snakeFrame.forEach(s -> s.changeDirection(e.getCode())))` outside the stream to have one listener for the whole snake. – Malte Hartwig Dec 19 '17 at 09:55
  • Why are you abusing `peek` here? It doesn’t make the code simpler nor does it add to efficiency. You can use `Arrays.asList(snakeFrame)` to get a `List` without any copying overhead. Then, just iterate over the list and perform your action. You can use `forEach` if you want to bring in a lambda at all costs (still no need for the Stream API). The action remains the same and all the extra lines for the braces dedicated to the unnecessary nesting are gone… – Holger Dec 19 '17 at 10:18
  • @MalteHartwig That makes sense now that I read it. There's only on scene with one setOnKeyPressed method. Thanks! – Joe Coon Dec 19 '17 at 20:27
  • @Holger I have recently been learning about streams, so I'm experimenting with them. I realize there is some inefficiency in copying the snake into a stream, but it only occurs on the initial list with 5 elements. With such a small collection, isn't the inefficiency negligible? – Joe Coon Dec 19 '17 at 20:33
  • Sure, but the point is that copying the list and using `peek` to setup the elements, [which it is not intended for](https://stackoverflow.com/q/33635717/2711488), is *both*, less efficient and less readable, compared to a straight-forward two step operation. Actually, there isn’t even a need to declare `snakeFrame ` as array at all, `private final List snakeFrame = Array.asList(new Segment(new Rectangle(250, 250, 25, 25) ), …);`. – Holger Dec 20 '17 at 07:44
  • But that’s only a side note, not related to your actual question. There is a logic error, segments should follow their preceding segment, which is not always moving into the same direction as their head, i.e. the snake should not move sidewards, but *turn*. It’s even possible that all segments move in different directions at a point of time. ˩‌˥ – Holger Dec 20 '17 at 07:57
  • @Holger Thanks! I hadn't read about peek's purpose; I only saw it's functionality as similar to a forEach operation but intermediate instead of terminal. After using Malte Hartwig's implementation above, I came into the situation you described (the snake wasn't turning, but moving as one block). That said, my question about the functionality of peek has been answered. Happy Holidays! – Joe Coon Dec 21 '17 at 05:05

1 Answers1

0

From Malte Hartwig's comment above:

"I see that you set the listener by calling getScene().setOnKeyPressed(). If this follows common Java naming conventions, then for each subsequent segment you will replace the listener that has been attached for the previous segment. That would explain why only one segment listens to the key stroke in the end. Instead, you could try to put getScene().setOnKeyPressed(e -> snakeFrame.forEach(s -> s.changeDirection(e.getCode()))) outside the stream to have one listener for the whole snake."

Once I followed these instructions, each of the segments responded to keyboard input

Joe Coon
  • 21
  • 6