1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Rust]Chip8を実装する際に学んだ記法、メソッド、ロジック

Last updated at Posted at 2022-12-20

はじめに

Rustの勉強とエミュレータ作ってみたくて、入門編のChip8を以下のサイト(pdf)を写経して作ったときによく分からなかった記法を備忘録がてら書いておきます。
An Introduction to Chip-8 Emulation using the Rust Programming
Language

ほぼほぼ写経しただけですけど、一応コードはこちらです。動かないゲームとかあるのでバグあるかもしれないです。。

記法、メソッド

copy_from_slice

こんな感じで出てきました。

new_emu.ram[..FONTSET_SIZE].copy_from_slice(&FONTSET);

// ram: [u8; RAM_SIZE]

以下は標準ライブラリをまとめた公式ドキュメントより抜粋です

pub fn copy_from_slice(&mut self, src: &[T])
where
    T: Copy,
// Copies all elements from src into self, using a memcpy.

// The length of src must be the same as self.

// If T does not implement Copy, use clone_from_slice.

// Panics
// This function will panic if the two slices have different lengths.

// Examples
// Copying two elements from a slice into another:

let src = [1, 2, 3, 4];
let mut dst = [0, 0];

// Because the slices have to be the same length,
// we slice the source slice from four elements
// to two. It will panic if we don't do this.
dst.copy_from_slice(&src[2..]);

assert_eq!(src, [1, 2, 3, 4]);
assert_eq!(dst, [3, 4]);

スライスから任意の指定箇所をコピーするメソッドです。

match

match (digit1, digit2, digit3, digit4) {
            (0, 0, 0, 0) => return,
            (0, 0, 0xE, 0) => {
                self.screen = [false; SCREEN_WIDTH * SCREEN_HEIGHT];
            } // {...} ブロックの後は","を省略可能
            (1, _, _, _) => { // "_"はなんでも良いという意味
                self.pc = op & 0x0FFF;
            }
            (5, _, _, _) => {
                let x = digit2 as usize; // indexはusize型にする必要がある
                let y = digit3 as usize;
                if self.v_reg[x] == self.v_reg[y] {
                    self.pc += 2;
                }
            }
            // ...省略

            (_, _, _, _) => unimplemented!("Unimplemented opcode: {:X}", op),
        }
match evt {
    Event::Quit { .. }
    | Event::KeyDown { // | で複数条件を指定できる
        keycode: Some(Keycode::Escape),
        ..
    } => {
        break 'gameloop;
    }
    
    _ => {}
}

wrapping_add, overflowing_add

こちらからいくつか引用させて頂きます。
Rustでの整数オーバーフローまとめ

オーバーフローした時の挙動が違うっぽいです。

// wrapping_add: オーバーフローした桁を無視
// docs: https://doc.rust-lang.org/std/intrinsics/fn.wrapping_add.html
assert_eq!(250u8.wrapping_add(10), 4);
assert_eq!(4u8.wrapping_sub(10), 250);

// overflowing_add: オーバーフローが起こった場合にboolのタプルを返します。計算結果はwrappingと同じ
// docs: https://doc.rust-lang.org/std/primitive.u64.html#method.overflowing_add
assert_eq!(200u8.overflowing_add(50), (250, false));
assert_eq!(200u8.overflowing_add(60), (4, true));

env::args().collect();

cargo run時のコマンドライン引数の読み込み

let args: Vec<_> = env::args().collect();

// Vec<_>
// "_"には型を入れても良いが、こう書くことで型推論が働く(多分)

// コマンドライン引数はstd::env::argsで取れる

// collect()はイテレータをコレクションにする関数

コレクションはVecとか連想配列とかヒープに保存される型で、イテレータはループで使うときにこの型にする必要がある。
生成したVec型をループしたい時はargs.iter().mapみたいな感じに書く必要ありといった感じです。

Option

matchから抜粋したのが以下のコード

match evt {
  Event::KeyDown {
    // Event::KeyDownというstructのkeycodeだけ指定して、".."は他のプロパティは指定しないという意味
    // Option型なので、keycodeにOption型の何か値が入っていたらSomeの中のkeyを取り出して使える
    keycode: Some(key), .. 
  } => {
    // if letの場合、左辺と右辺が同じであればSomeの中の変数kを取り出して使える
    if let Some(k) = key2btn(key) {
       chip8.keypress(k, true);
     }
  }
}

Option型は慣れが必要ですね。

`loop

ループの文頭にアポストロフィをつけた名称でループにラベルをつけることができるっぽいです。

'gameloop: loop {
        for evt in event_pump.poll_iter() {
            match evt {
                Event::Quit { .. }
                | Event::KeyDown {
                    keycode: Some(Keycode::Escape),
                    ..
                } => {
                    break 'gameloop; // ここで'gameloopという名前のループを終了しています。
                }
                ...

ロジック

CPUのクロック処理

CPUがどういう処理をしているかですが、実際にロジックを担ってる箇所が以下です。

'gameloop: loop {
               // 入力に対する(KeyUp等)の処理を行う
        for evt in event_pump.poll_iter() {
            // ...省略
        }

        // TICKS_PER_FRAME = 10
        // 1ループで10回処理を呼んでる
        for _ in 0..TICKS_PER_FRAME {
            chip8.tick(); // ここで様々な処理を行なっている。
        }
        chip8.tick_timers();
        draw_screen(&chip8, &mut canvas); // ディスプレイ表示の処理
    }

// tick()の中身
pub fn tick(&mut self) {
        let op = self.fetch(); // opコードを呼び出して、pcを進める
        self.execute(op);  // opコードを実行する
}

// tick_timers()の中身
pub fn tick_timers(&mut self) {
        if self.dt > 0 {
            self.dt -= 1;
        }
        if self.st > 0 {
            if self.st == 1 {
                // BEEP
            }
            self.st -= 1;
        }
}

ここは完全に理解できてないんですが、ドキュメントを読む限り、ループの速度はシステム依存でスクリーンの描画速度に合わせているっぽいです。
1回スクリーンが切り替わる度に処理が10進むくらいがちょうど良いということで、TICKS_PER_FRAME = 10としているようです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?