How to consume an iterator from end to start?

Hello everyone,

First of all thank you for being a part of this forum. The rust community has been very welcoming to me and I’m grateful :slight_smile:

I’m implementing the Tetris game in rust as an exercise. It goes pretty well, I’m having a lot of fun.
To represent the game’s state I chose an array of 210 bytes:

pub struct Game<R, W: Write> {
    stdout: W,
    stdin: R,
    // other content
    board: [u8; 210],
}

The width of the game is 10, the indexes of the board’s cells look like this:

 200 201 202 203 204 205 206 207 208 209
 190 191 192 193 194 195 196 197 198 199
 // ------
  20  21  22  23  24  25  26  27  28  29
  10  11  12  13  14  15  16  17  18  19
   0   1   2   3   4   5   6   7   8   9

I’m delighted about this data structure, it makes moving the pieces around a breeze.

Every byte of the array get assigned a value. 0 for an empty cell, 1 for a T block, etc. To display it I use Termion by doing:

impl<R: Read, W: Write> Game<R, W> {

  fn display_the board(&mut self) {
    write!(self.stdout, "{}{}", clear::All, cursor::Goto(1, 1)).unwrap();

    // verbose way of iterating because I'm a learner
    let mut line_iterator = self.board.chunks(10);
    while let Some(line) = line_iterator.next() {
      for &cell in line.iter() {
        let symbol = match cell {
            0 => b" ",
            1 => b"T",
            2 => b"I",
            3 => b"S",
            4 => b"Z",
            5 => b"O",
            6 => b"L",
            7 => b"J",
            _ => b"X",
        };
        self.stdout.write(symbol).unwrap();
      }
    }
    self.stdout.flush().unwrap();
  }
}

Wwhich works perfectly well but draws my board upside-down :

   0   1   2   3   4   5   6   7   8   9
  20  21  22  23  24  25  26  27  28  29
  10  11  12  13  14  15  16  17  18  19
  // ------
 190 191 192 193 194 195 196 197 198 199
 200 201 202 203 204 205 206 207 208 209

In order to iterate over the line_iterator (made of array chunks) from end to start, I want to replace the next(&mut self) iterator method with last(self).
Because of the different method signature, I remove the mut keyword when creating line_iterator. But rustc tells me:

let line_iterator = self.board.chunks(10);
 // ------------- move occurs because `line_iterator` has type
 // `std::slice::Chunks<'_, u8>`, which does not implement the `Copy` trait

while let Some(line) = line_iterator.last() {
                    // ^^^^^^^^^^^^^ value moved here, in previous iteration of loop

Which is too bad. I want to consume the iterator, it seems to be the right thing to do.
Do I have to implement the Copy trait for this Chunk type, like rustc suggests ? And how do I do that ?
Or am I entirely wrong and there is a much easier way of dealing with this ?

Thanks a lot in advance.

The trick here is to iterate over the lines in a reverse manner. Since the iterator over the lines is self.board.chunks(10), all you need to do is use the .rev iterator adapter on it:

// verbose way of iterating because I'm a learner
let mut line_iterator = self.board.chunks(10).rev();
while let Some(line) = line_iterator.next() {

and, by the way, to save some typing, the pattern

let mut iterator = <iterator expression>;
while let Some(<pattern>) = iterator.next() {

can be written as

for <pattern> in <iterator expression> {

So in your case it results in:

for line in self.board.chunks(10).rev() {
3 Likes

Some tips that you may or may not want to follow up on:

Instead of using u8 for your cell content, you could use an enum (with or without repr(u8)). This will compile to the same code, but it will make it easier to make your code correct and allows you to impl methods on it.

Also, if you move the board to its own struct, you can implement the Index and IndexMut traits, so you can access cells like this: board[(10, 5)]. I don’t know if you need it, but it’s an option.

3 Likes

Yep, here is a playground to get you started, and also using ? instead of .unwrap() on the display_... method, so that the caller can still “catch” and handle an error on write.

2 Likes

You guys are gold.
I’ve refactored my code two times already so I may follow up on your advice to use an enum for cell content (and the rest of them) but for now I’ll work on the run(&mut self) that calls all the others, get things to flow.

@Yandros, thank you so much for your playground code! Don’t waste so much of your good time on me! I’ll definitely take inspiration from it!

My code only gets bigger and more complicated, so I started to follow on your advice.
My board is now an array of the Cell enum, that simplified things a bit, and I think of implementing those Index and IndexMut traits on the board (and make it a struct).

But I got some questions first.

  • @ThomasdenH : What does #[repr(u8)] mean ? The Rustonomicon speaks of it but I’m overwhelmed.
  • Will this code you gave me, @Yandros, work with Termion ?
impl fmt::Display for Cell {
    fn fmt (
        self: &'_ Self,
        stream: &'_ mut fmt::Formatter<'_>,
    ) -> fmt::Result
    {
        use self::Cell::*;
        write!(stream, "{}", match *self {
            | Blank => " ",
            | T     => "T",
            | I     => "I",
            | S     => "S",
            | Z     => "Z",
            | O     => "O",
            | L     => "L",
            | J     => "J",
            | X     => "X",
        })
    }
}

The #[repr] attribute changes how a type is stored in memory, allowing the programmer to choose a more efficient storage type or an (ABI? I’m not sure about the correct wording here) ffi-compatible type where its size is the same as another.
For example, in the case of #[repr(u8)], it means that the enum that it is being applied to, if it is a c-style enum, will have its variants differentiated (Meaning, the internal value will be represented by) a u8 instead of whatever the compiler would’ve defaulted to.
Or, in another case, when you want to std::mem::transmute one type into another, a #[repr(transparent)] tag would be useful.

In this case, what the #[repr(u8)] attribute is meant to do is optimize storage used.

1 Like

It should, but I haven’t tested it