rustで2048の実装をこころみたが、いまいち……　とりあえずメモ

# はじめに

2048楽しいですよね！（数年遅れ）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/6984cc68-de43-fdb4-7a1b-8cd1012339d1.png)

こういう画面で、上下左右を押すとそっち方向にブロックが飛んでいく。で、同じ数字同士がぶつかると、合成される（例えば、2+2=4）。

rust入門の一環で、これを雑に移植してみよう！

（rust素人が悩みつつ、うーんうーんってやってるので、どうかコメントはお手柔らかに……。）

多分、この後もうちょっと勉強して、もう一回作り直したら、もっと良くなると思いつつ。
それでも現状の実力確認として記録に残しておく。

# 実装

## 実行画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89434/fbc80acf-0c43-28e6-f523-af720c0fdbd0.png)


## 参照crate

キー入力受付のための`termion`と、乱数発生のための`rand`を用いる

```
[dependencies]
termion = "*"
rand = "0.8.0"
```

## 今回はまったこと

### 判定結果を引数に書き・・・戻せない？

最初何気なーく、こう書いたんですよ。

```rust
pub fn fn1 ( _is_test: &mut bool){
    // 様々な処理
    _is_test = true;
}
```

で、怒られると…

```
> Executing task: cargo build <

   Compiling booltest v0.1.0 (/home/kmtr/work/rust/booltest)
error[E0308]: mismatched types
 --> src/main.rs:3:16
  |
3 |     _is_test = true;
  |                ^^^^ expected `&mut bool`, found `bool`
  |
help: consider dereferencing here to assign to the mutable borrowed piece of memory
  |
3 |     *_is_test = true;
  |     ^^^^^^^^^

error[E0277]: can't compare `&mut bool` with `bool`
  --> src/main.rs:15:17
   |
15 |     if _is_test == true {
   |                 ^^ no implementation for `&mut bool == bool`
   |
   = help: the trait `PartialEq<bool>` is not implemented for `&mut bool`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `booltest`
```

これなんだろー、と悩んだ結果、そもそも引数に渡す必要ないなこりゃと気が付いた。

```
pub fn fn2 () -> bool {
    // 様々な処理
    true
}
```

ということに気が付かず、うーんうーんどうやろうかなーと悩んで時間を使ったなあ・・・
このあたりまだ経験足らんなーと思いつつ。

```rust
pub fn fn1 ( _is_test: &mut bool){
    // 様々な処理
//    _is_test = true;
}

pub fn fn2 () -> bool {
    // 様々な処理
    true
}

fn main() {
    // let mut _is_test;
    // fn1(_is_test);

    // if _is_test == true {
    //     println!("Hello, World!");
    // }

    if fn2() {
        println!("Hello, World!");
    }
}

```

## 残件

- `iy*4+ix` のところはマクロにすればよかったかなあ
- これクラスっぽいもの導入して、もう少し整理もしたかったなあ（start()が存在するのがその名残)
- いろんな色モード指定サポートもしたかったかなあ
- リプレイ機能とか作りたかったなあ　など等。。。
- 傾けるー＞隣接判定ー＞傾ける、の傾ける処理とかもっときれいにかけたよなあ
    - まあこのあたりは頑張らないで。
- 傾ける処理ってスライスってのを使えばもっときれいにかけたかな
- 回転処理はもうちょっと効率的にかければよかったかな。まあ、4x4ならばこれでもいいけどさ…


## 感想

- コンパイルできたら（多分）動く、できなかったら（確実に）動かない、というのは、ちょっとやりやすいかも。
    - 操作そのもの（ロジック）の問題は見つけられないけど、データなどの扱い方については事前検証できるのは良い。
- エラーメッセージでは悩まされる
    - これは経験不足かな？もうちょっと色々やれば、また別っぽい気がする。
- ドキュメントにも悩まされる
    - ドキュメント、これ読むのつらいなあー
    - 結局crateのサンプルコードを見てなんとかしのいでいるけど、、、

## ソースコード

テスト部分は途中まで作ってたけど、もう終わらせること最優先でばっさり削りました。

```rust
extern crate termion;

use std::io::{stdin, stdout, Write};
use termion::color;
use termion::event::Key;
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use rand::Rng;

pub enum Event {
  TiltUp,
  TiltDown,
  TiltLeft,
  TiltRight,
  Skip,
  End,
}

/**
 * Main for Controller
 */
fn main() {
  start();
}

fn start() {
  let mut score: u32 = 0;
  let mut cells: [u32; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

  // Refered from http://ticki.github.io/blog/making-terminal-applications-in-rust-with-termion/
  let stdin = stdin();
  let mut stdout = stdout().into_raw_mode().unwrap();

  init_board(&mut cells);
  put_number(&mut cells);
  put_number(&mut cells);
  show_board(&mut cells, score);

  for c in stdin.keys() {

    let ev = match c.unwrap() {
      Key::Char('q') => Event::End,
      Key::Up     => Event::TiltUp,
      Key::Left   => Event::TiltLeft,
      Key::Down   => Event::TiltDown,
      Key::Right  => Event::TiltRight, 
      _ => Event::Skip,
    };

    match ev {
      Event::Skip => continue,
      Event::End => break,
      _ => { // Up/Left/Down/Right
        if tilt_board(&mut cells, ev, &mut score) == true {
          if put_number(&mut cells) == false {
            // もうどこにも数字が置けなくなった
            break;
          }
        }
      }

    }

    show_board(&mut cells, score);
  }

  // Show the cursor again before we exit.
  write!(stdout, "{}", termion::cursor::Show).unwrap();
}

pub fn init_board(cells: &mut [u32; 16]) {
  for it in cells {
    *it = 0;
  }
}

pub fn show_board(cells: &[u32; 16], score: u32) {
  let mut stdout = stdout().into_raw_mode().unwrap();
  print!("{}", termion::clear::All);
  let mut cnt = 0;
  for _it in cells {
    print!(
      "{}",
      termion::cursor::Goto((cnt % 4) * 8 + 1, (cnt / 4) + 1)
    );
    match _it {
      0 => print!("{}", color::Fg(color::Rgb(0, 0, 0))),
      2 => print!("{}", color::Fg(color::Rgb(255, 0, 0))),
      4 => print!("{}", color::Fg(color::Rgb(0, 255, 0))),
      8 => print!("{}", color::Fg(color::Rgb(255, 255, 0))),
      16 => print!("{}", color::Fg(color::Rgb(0, 0, 255))),
      32 => print!("{}", color::Fg(color::Rgb(255, 0, 255))),
      64 => print!("{}", color::Fg(color::Rgb(0, 255, 255))),
      128 => print!("{}", color::Fg(color::Rgb(255, 255, 255))),
      32768 => print!("{}", color::Fg(color::Rgb(192, 192, 192))),
      _ => print!("{}", color::Fg(color::Rgb(0, 0, 0))),
    };
    match _it {
      256 => print!("{}", color::Bg(color::Rgb(255, 0, 0))),
      512 => print!("{}", color::Bg(color::Rgb(0, 255, 0))),
      1024 => print!("{}", color::Bg(color::Rgb(255, 255, 0))),
      2048 => print!("{}", color::Bg(color::Rgb(0, 0, 255))),
      4096 => print!("{}", color::Bg(color::Rgb(255, 0, 255))),
      8192 => print!("{}", color::Bg(color::Rgb(255, 255, 0))),
      16384 => print!("{}", color::Bg(color::Rgb(255, 255, 255))),
      32768 => print!("{}", color::Bg(color::Rgb(64, 64, 64))),
      _ => print!("{}", color::Bg(color::Reset)),
    };

    print!(
      "{}{}{}",
      _it,
      color::Fg(color::Reset),
      color::Bg(color::Reset)
    );
    cnt = cnt + 1;
  }

  println!("");
  println!("{} ↑,←,↓,→ / q", termion::cursor::Goto(1, 6));
  println!("{} Score: {} ", termion::cursor::Goto(1, 7), score);

  stdout.flush().unwrap();
}

pub fn tilt_board(cells: &mut [u32; 16], ev: Event, score: &mut u32) -> bool {
  let mut _is_moved = false;
  let tiltup = |cells: &mut [u32; 16], score: &mut u32 | -> bool{
    let mut _is_moved = false;
    for iy in 0..4 {
      for ix in 0..3 {
        if cells[iy * 4 + ix ] == 0 {
          for iz in ix+1..4 {
            if cells[iy * 4 + iz ] !=  0 {
              cells[iy * 4 + ix] = cells[iy * 4 + iz];
              cells[iy * 4 + iz] = 0;
              _is_moved = true;
              break;
            }
          }
        }
      }
      for ix in 0..3 {
        if cells[iy * 4 + ix ] == cells[iy * 4 + ix + 1 ] {
          *score = *score + cells[iy * 4 + ix] as u32;
          cells[iy * 4 + ix] = cells[iy * 4 + ix] * 2;
          cells[iy * 4 + ix+1] = 0;
        }
      }
      for ix in 0..3 {
        if cells[iy * 4 + ix ] == 0 {
          for iz in ix+1..4 {
            if cells[iy * 4 + iz ] !=  0 {
              cells[iy * 4 + ix] = cells[iy * 4 + iz];
              cells[iy * 4 + iz] = 0;
              _is_moved = true;
              break;
            }
          }
        }
      }
    }
    _is_moved
  };

  // 反時計回り
  let rotate_board = |cells: &mut [u32; 16]| {
    let copy = cells.clone();

    for iy in 0..4 {
      for ix in 0..4 {
        cells[iy * 4 + ix] = copy[ix * 4 + (3 - iy)];
      }
    }
  };

  let pre_count = match ev {
    Event::TiltLeft => 0,
    Event::TiltUp => 1,
    Event::TiltRight => 2,
    Event::TiltDown => 3,
    Event::End => 0,
    Event::Skip => 0,
  };

  for _ in 0..pre_count {
    rotate_board(cells);
  }
  _is_moved = tiltup(cells, score);
  for _ in 0..(4 - pre_count) {
    rotate_board(cells);
  }

  _is_moved
}

pub fn put_number( cells : &mut [u32; 16] ) -> bool{
  let mut rng = rand::thread_rng();

  let mut has_blank = false;
  for iy in 0..4{
    for ix in 0..4 {
      if cells[iy *4 + ix ] == 0 {
        has_blank = true;
      }
    }
  }

  if has_blank == false {
    return false;
  }

  loop {
    let ix: usize = rng.gen_range(0..4);
    let iy: usize = rng.gen_range(0..4);

    if cells[iy *4 + ix ] == 0 {
      cells[iy *4 + ix ] = 2;
      break;
    }
  }

  true
}

```