With the tiles in the proper ordering, we can continue to merge the tiles that need to be merged.
Iterators work by using a .next
function that pulls the next item off of the iterator. In the past with ranges and .collect
we haven't had to explicitly use .next
because consuming functions like .collect
handle calling all of the .next
's for every item automatically.
When we iterate through the tiles, we'll want to check the next tile as well. To do this we can make the iterator peekable using .peekable
. Peekable
means that we'll be able to take a look at the next item in the iterator without actually pulling it off the iterator. We effectively have access to two functions: .next
and .peek
. .next
takes the item off the iterator, and .peek
leaves it on the iterator while still letting us look at it.
let mut it = tiles
.iter_mut()
.sorted_by(|a, b| {
match Ord::cmp(&a.1.y, &b.1.y) {
Ordering::Equal => {
Ord::cmp(&a.1.x, &b.1.x)
}
ordering => ordering,
}
})
.peekable();
Now we're going to write the main loop that will handle shifting the board. First we're going to set up a column
variable right below our peekable iterator. This is going to represent the current column
value for the current tile. In the left board shift, we'll use this to rewrite the tile position x value. If we have three tiles, we'll end up with tiles in the 0, 1, and 2 x positions regardless of what x positions they start in.
We reset this variable for each new row.
let mut column: u8 = 0;
Then we get into the loop. We'll use a while-let to keep pulling .next
off of the iterator. You can read this as "while it.next() is a Some() variant, let tile be the value inside of the Some()".
That will keep giving us a mutable tile
as long as there is one left in the iterator.
while let Some(mut tile) = it.next() {
tile.1.x = column;
match it.peek() {
None => {}
Some(tile_next) => {
if tile.1.y != tile_next.1.y {
// different rows, don't merge
column = 0;
} else if tile.2.value
!= tile_next.2.value
{
// different values, don't merge
column = column + 1;
} else {
// merge
let real_next_tile = it
.next()
.expect("A peeked tile should always exist when we .next here");
tile.2.value = tile.2.value
+ real_next_tile.2.value;
commands
.entity(real_next_tile.0)
.despawn_recursive();
if let Some(future) = it.peek()
{
if tile.1.y != future.1.y {
column = 0;
} else {
column = column + 1;
}
}
}
}
}
}
Then we immediately set the x value of the current tile to the column
value.
After setting the new x, we check to see if there is another value left in the iterator using `.peek().
If there isn't a next value, we're at the end of our list of tiles so we're done and don't have to do anything else.
match it.peek() {
None => {}
If there is another tile in the iterator we need to check if it's mergable with the current tile. To be mergable it needs to be in the same row (the same y Position
), and have the same Points
value.
First we check the y values of both tiles, if they're in different rows then we can't merge them, so we reset column
to 0 and continue with the next tile's row.
Then we check to see if the Points
values of the two tiles are the same. If they aren't, then we increment column
by 1 and proceed to loop with the next tile in the row on the next iteration.
Some(tile_next) => {
if tile.1.y != tile_next.1.y {
// different rows, don't merge
column = 0;
} else if tile.2.value != tile_next.2.value {
// different values, don't merge
column = column + 1;
} else {
// merge
let real_next_tile = it
.next()
.expect("A peeked tile should always exist when we .next here");
tile.2.value = tile.2.value
+ real_next_tile.2.value;
commands
.entity(real_next_tile.0)
.despawn_recursive();
if let Some(future) = it.peek()
{
if tile.1.y != future.1.y {
column = 0;
} else {
column = column + 1;
}
}
}
}
Finally, if we should merge the tiles we pull the peeked tile off of the iterator using .next
and set the value of the first tile to the value of both tiles combined.
let real_next_tile = it
.next()
.expect("A peeked tile should always exist when we .next here");
tile.2.value = tile.2.value + real_next_tile.2.value;
We use Commands
to fetch the entity of the second tile and despawn not only itself, but all of its children as well: which means the Text2dBundle
components.
commands
.entity(real_next_tile.0)
.despawn_recursive();
You will also have to add the Commands
to the board_shift
system to have access to it.
fn board_shift(
mut commands: Commands,
input: Res<Input<KeyCode>>,
mut tiles: Query<(Entity, &mut Position, &mut Points)>,
)
Finally we peek the third tile in the iterator. If it's in a different row, we reset column
. If it's in the same row, we add 1
to column
and continue the loop.
if let Some(future) = it.peek()
{
if tile.1.y != future.1.y {
column = 0;
} else {
column = column + 1;
}
}
This is all great but it has one big problem: The tile transforms aren't currently being updated!
If we run the game until we start with two tiles in the same row, the two tiles will merge together because one despawns. If tiles are on different rows, nothing will happen to them.