Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow snake to eat #26

Open
nickgnd opened this issue Feb 15, 2020 · 7 comments
Open

Allow snake to eat #26

nickgnd opened this issue Feb 15, 2020 · 7 comments
Assignees

Comments

@nickgnd
Copy link
Member

nickgnd commented Feb 15, 2020

Draft out chapter 6: Allow snake to eat 馃悰 馃崕

@nickgnd nickgnd self-assigned this Feb 15, 2020
@nickgnd
Copy link
Member Author

nickgnd commented Feb 16, 2020

@klappradla @LuisaAPF @ggpasqualino Hey 馃憢

Do you remember that we removed the size information from the state object?
Apparently, it was a necessary information to properly compute the body of the snake when eating the food 馃檲
At first glance, this size information stored in the state looked like a duplicate info since we can always derive it counting how main tiles are in the body list (and that's why we removed it).
But actually, it is a clever trick which allows the snake to grow when eating, let's consider this example:

  • Initial body %{body: [{9, 9}, {10, 9}], size: 2}

Then the snake eats the food, therefore it needs to grow by 1 unit. The move_snake function, which is called every tick, computes the new snake body first, and then it updates the state and as last step, it check if the snake has eaten the food.

defp move_snake(%{snake: snake} = state) do
  %{body: body, size: size, direction: direction} = snake

  # new head
  [head | _] = body
  new_head = move(state, head, direction)

  # truncate body
  new_body = Enum.take([new_head | body], size) #  Compute snake body

  state
  |> put_in([:objects, :snake, :body], new_body) # First, update the state
  |> maybe_eat_pellet(new_head)                  # Then, check if the snake ate the food 
end

def maybe_eat_pellet(state = %{pellet: pellet}, snake_head) when pellet == snake_head do
  state
  |> grow_snake()
end

def grow_snake(state = %{snake: %{size: size}}) do
  put_in(state, [:snake, :size], size + 1)
end

馃憜 the grow_snake function increments the size by 1 in the state, then in the next state update, the body of the snake is recomputed using the size (in the move_snake function):

new_body = Enum.take([new_head | body], size)

Basically, the size info is needed only to allow the snake to grow when it eats the food and it is evaluated always the following state update.

OK.
Having said that, how would you like to proceed? Personally, I can foresee these different ways:

  • Re-introducing the size in the state (馃憟 probably the easiest option, but definitely not the most intuitive one)
  • Analogous approach, instead of using the size, we can be more explicit and setting a flag like append_tile or snake_has_eaten in the state and then, in the following state update, using this flag to increment the body of 1 unit

What do you think? Do you have any idea?

Let's also keep in mind this info:

  • afaik, It is not possible to append a tile at the end of the body since we only know the direction of the snake head, that's the reason why the application basically use the size to preserve the last tile in the body when the snake has eaten.
  • Prepending a new tile at the top of the body is not a feasible solution because it can lead to unexpected scenario and snake dead

@ggpasqualino
Copy link
Contributor

Good catch @nickgnd!

I like your suggestion to have a state snake_has_eaten, I think it's much easier to understand what needs to happen in the next tick.
Then maybe we can do something like the following

defp move_snake(%{snake: snake} = state) do
  %{body: body, direction: direction} = snake

  # new head's position
  [head | _] = body
  new_head = move(state, head, direction)

  # place a new head on the tile that we want to move to
  # and remove the last tile from the snake tail if it has not eaten any pallet
  new_body = [new_head | body]
  new_body = if snake.has_eaten, do: new_body, else: List.delete_at(new_body, -1)

  state
  |> put_in([:snake, :body], new_body)
end

@LuisaAPF
Copy link
Contributor

@nickgnd I also like your suggestion of adding a has_eaten flag and the implementation above by @ggpasqualino.

@nickgnd
Copy link
Member Author

nickgnd commented Feb 17, 2020

Perfect! Thanks for the feedback 馃
I'll open a PR in the next days 馃挭

@klappradla
Copy link
Member

Wow, definitely a good catch 馃憤

I also like @ggpasqualino's approach - a bit more explicit than using the size information. I'm not sure I fully understand why we would want / need to store this in the state though. Don't we need to re-compute this information on every tick anyways?

@ggpasqualino
Copy link
Contributor

ggpasqualino commented Feb 18, 2020

I think it depends whether we update the snake's size immediately or in the next tick, if we do it in the same tick then we don't need to store the state.

@klappradla
Copy link
Member

I think it depends whether we update the snake's size immediately or in the next tick, if we do it in the same tick then we don't need to store the state.

Yep, talked about it today with @nickgnd. With the current approach it's probably most "natural" to have the snake "grow" on the second tick - so storing in the state is perfect 馃憣

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants