Rust
OpenGL

Rustでテトリス

More than 1 year has passed since last update.


テトリスとは

降り続くいくつかの四角を操作し、その地を満たすように並べていくと

自然と四角は消え、積もる四角は下へと落ちていく

それを繰り返せば、日頃の煩悩は薄れ、さらに続ければ悟りが開かれよう

それすなわちテトリスと言う

...ナンノコッチャ...

というわけで皆さんご存知テトリスです

以前はターミナルで動くテトリスをRubyで作りました

tetris.gif

今回は少しリッチにOpenGLで描写するテトリスをRustでやります

source:github


コード

まずは描写とは分離されたテトリスのロジック本体


lib.rs


lib.rs

extern crate rand;

use rand::distributions;
use rand::distributions::IndependentSample;

use std::f32;



struct Tetris

操作するブロックとブロックを配置するフィールドを状態として持ちます

structは基本let mutされて使うことを前提とします


lib.rs

pub struct Tetris {

pub block: Block,
pub field: [[Color; 10]; 20],
}


struct Block

ブロックはひとかたまり同じ色として扱います

それぞれのブロックのサイズが違うので配列ではなくベクターを使い表現します


lib.rs

pub struct Block {

pub color: Color,
pub blocks: Vec<(i32,i32)>,
}


enum Color

色は黒色をフィールド上ではブロック未配置とします

OptionのNoneをそれとして扱った方がよかったかもね

そのままではOpenGLの色情報として扱えないのであとで変換してあげます


lib.rs

#[derive(PartialEq, Copy, Clone)]

pub enum Color {
Black, Red, Green, Blue, Yellow, Cyan, Magenta, White
}


enum Control

ブロック操作コマンド


lib.rs

#[derive(PartialEq)]

pub enum Control {
Down, Left, Right, Rotate
}


const COLORS, BLOCKS

降ってくるブロック色と形と

ちなみにconstではVec::new()などのアロケーションは使えないようです、当たり前か


lib.rs

const COLORS: &'static [Color] = &[

Color::Red,
Color::Green,
Color::Blue,
Color::Yellow,
Color::Cyan,
Color::Magenta,
];

const BLOCKS: &'static [&'static [(i32,i32)]] = &[
&[(0,0),(0,1),(1,0),(1,1)],
&[(0,0),(0,1),(0,2),(1,1),(2,1)],
&[(0,0),(0,1),(0,2),(0,3)],
&[(0,0),(0,1),(0,2),(0,3),(1,3)],
&[(0,0),(0,1),(0,2),(0,3),(1,0)],
&[(0,0),(0,1),(1,1),(1,2)],
&[(1,0),(1,1),(0,1),(0,2)],
&[(0,1),(0,1),(0,2),(1,1)],
];



impl Block, fn new, rand, rotate, down, left, right

まずはブロックの操作や初期化を実装

回転はもうちょっとなんとかなりそう


lib.rs

impl Block {

pub fn new(c: Color, b: Vec<(i32,i32)>) -> Block {
return Block {
color: c,
blocks: b
};
}

pub fn rand() -> Block {
let mut rng = rand::thread_rng();
let blocks_range = distributions::Range::new(0, BLOCKS.len());
let colors_range = distributions::Range::new(0, COLORS.len());
return Block {
color: COLORS[colors_range.ind_sample(&mut rng)],
blocks: BLOCKS[blocks_range.ind_sample(&mut rng)].to_vec()
};
}

fn down(&mut self) {
for c in self.blocks.iter_mut() {
c.0 += 1;
}
}

fn left(&mut self) {
for c in self.blocks.iter_mut() {
c.1 -= 1;
}
}

fn right(&mut self) {
for c in self.blocks.iter_mut() {
c.1 += 1;
}
}

fn rotate(&mut self) {
let r: f32 = f32::consts::PI / 2.0;
let cy: f32 =
(self.blocks.iter().map(|i| i.0).sum::<i32>() as f32) / (self.blocks.len() as f32);
let cx: f32 =
(self.blocks.iter().map(|i| i.1).sum::<i32>() as f32) / (self.blocks.len() as f32);

for c in self.blocks.iter_mut() {
let (y, x) = *c;
let y = f32::from(y as i16);
let x = f32::from(x as i16);
*c = (
(cy + (x - cx) * r.sin() + (y - cy) * r.cos()).round() as i32,
(cx + (x - cx) * r.cos() - (y - cy) * r.sin()).round() as i32
);
}
}
}



impl Tetris, fn new, control, delete, fall

fn controlがブロックがフィールド上で移動可能どうか、可能なら移動させる

fn deleteは行が満ちていれば消し、フィールド上のブロックを落下させる

fn fallが現在操作中のブロックを1マス下へ落とす

もう落とせなければブロックをフィールドへ書き込み、新しいブロックを設定する


lib.rs

impl Tetris {

pub fn new() -> Tetris {
return Tetris {
block: Block::rand(),
field: [[Color::Black; 10]; 20],
};
}

pub fn control(&mut self, op: Control) {
let pre = self.block.blocks.clone();
match op {
Control::Down => self.block.down(),
Control::Left => self.block.left(),
Control::Right => self.block.right(),
Control::Rotate => self.block.rotate()
}

let ly = self.field.len() as i32;
let lx = self.field[0].len() as i32;
let exists = self.block.blocks.iter().all(|&(y,x)| {
return 0 <= y && y < ly && 0 <= x && x < lx
&& (self.field[y as usize][x as usize] == Color::Black);
});

if !exists {
self.block.blocks = pre;
}
}

pub fn delete(&mut self) {
for y in 0 .. self.field.len() {
if self.field[y].iter().all(|c| *c != Color::Black) {
for x in 0 .. self.field[y].len() {
let mut yy = y;
for yyy in (0 .. y - 1).rev() {
self.field[yy][x] = self.field[yyy][x];
yy -= 1;
}
}
}
}
}

pub fn fall(&mut self) {
let blocks = self.block.blocks.clone();
self.control(Control::Down);

let mut not_moved = true;
let len = self.block.blocks.len();
for i in 0 .. len {
if self.block.blocks[i] != blocks[i] {
not_moved = false;
break;
}
}

if not_moved {
{
let ref bs = self.block.blocks;
for &(y,x) in bs {
self.field[y as usize][x as usize] = self.block.color;
}
}
self.block = Block::rand();
self.delete();
}
}
}



main.rs

続いて、描写部分

gliumクレートを使います


main.rs

#[macro_use]

extern crate glium;
use glium::{DisplayBuild, Surface, Program};
use glium::{glutin, index, vertex};

extern crate tetris;
use tetris::{Tetris, Color};

use std::f32;
use std::thread;
use std::time;
use std::sync::{Arc, Mutex};



struct Vertex

gliumでは自前でVertexなstructを用意しなければなりません

が、マクロがトレイトを実装してくれるので楽チンです


main.rs

#[derive(Copy, Clone)]

struct Vertex {
pos: [f32; 2],
color: [f32; 4],
}
implement_vertex!(Vertex, pos, color);


const VERTEX_SHADER, FRAGMENT_SHADER

シェーダーも必須です

バーテックス経由でテトリスのフィールド情報を渡しています

ここもうちょっと上手いことできないかな


main.rs

const VERTEX_SHADER: &'static str = r#"

#version 400

in vec2 pos;
in vec4 color;
out vec4 v_color;

void main() {
v_color = color;
gl_Position = vec4(pos, 0, 1);
}
"#;

const FRAGMENT_SHADER: &'static str = r#"
#version 400

in vec4 v_color;
out vec4 f_color;

void main() {
f_color = v_color;
}
"#;



fn color_to_rgba

RustのenumをOpenGLの色vec4へ変換


main.rs

pub fn color_to_rgba(c: Color) -> [f32; 4] {

match c {
Color::Black => [0.0, 0.0, 0.0, 0.0],
Color::Red => [0.5, 0.0, 0.0, 0.5],
Color::Green => [0.0, 0.5, 0.0, 0.5],
Color::Blue => [0.0, 0.0, 0.5, 0.5],
Color::Yellow => [0.5, 0.5, 0.0, 0.5],
Color::Cyan => [0.0, 0.5, 0.5, 0.5],
Color::Magenta => [0.5, 0.0, 0.5, 0.5],
Color::White => [0.5, 0.5, 0.5, 0.5]
}
}


fn tetris_to_vertexs

テトリスの状態をバーテックスシェーダーへ渡す情報へ変換

本当はスケールなりトランスレートなりで表示位置変えたいのだけど

シェーダーだけでどうやるのかわからなかった

glScaleとかglTranslateとかをgliumが提供していないから、nalgebraの行列関数使えってことなのかな

デフォルトの-1 .. 1の間でどうにか表現、めんどかった


main.rs

fn tetris_to_vertexs(tetris: &Tetris) -> Vec<Vertex> {

let mut vs = vec!();
let mut y: f32 = 0.80;
let mut iy: usize = 0;

while y >= -0.885 {
let mut x: f32 = -0.375;
let mut ix: usize = 0;

while x <= 0.46 {
if tetris.block.blocks.iter().any(|&(yy,xx)| iy as i32 == yy && ix as i32 == xx) {
vs.push(Vertex {
pos: [x, y],
color: color_to_rgba(tetris.block.color)
});
}
else {
vs.push(Vertex {
pos: [x, y],
color: color_to_rgba(tetris.field[iy][ix])
});
}

x += 0.085;
ix += 1;
}

y -= 0.085;
iy += 1;
}
return vs;
}



fn main

やっとメイン

スレッドでタイマーを回し、メインスレッドでキー入力と表示を捌く

バーテックスとインデックスとシェーダーとユニフォームとパラメーターを用意してドロー!

実は排他制御が甘いせいか、mutのゴリ押しのせいか、表示がたまに崩れます

解決策がわかりませんでした😇


main.rs

fn main() {

let display = glutin::WindowBuilder::new()
.with_dimensions(600, 600).build_glium().unwrap();
println!("{:?}", display. get_framebuffer_dimensions());

let index = index::NoIndices(index::PrimitiveType::Points);
let uniform = uniform!();
let param = glium::DrawParameters {
point_size: Some(26.0),
.. Default::default()
};

let program = match Program::from_source(&display, VERTEX_SHADER, FRAGMENT_SHADER, None) {
Ok(p) => p,
Err(e) => {
println!("{}", e);
return;
}
};

let mutex_main = Arc::new(Mutex::new(Tetris::new()));
let mutex_timer = mutex_main.clone();

thread::spawn(move || {
loop {
thread::sleep(time::Duration::from_millis(1000));
let mut t = mutex_timer.lock().unwrap();
t.fall();
}
});

loop {
let mut t = mutex_main.lock().unwrap();

'event: for e in display.poll_events() {
match e {
glutin::Event::KeyboardInput(
glutin::ElementState::Pressed, _, Some(keycode)
) => {
//println!("{:?}", e);
match keycode {
glutin::VirtualKeyCode::Up => {
t.control(tetris::Control::Rotate);
},

glutin::VirtualKeyCode::Down => {
t.fall();
},

glutin::VirtualKeyCode::Left => {
t.control(tetris::Control::Left);
},

glutin::VirtualKeyCode::Right => {
t.control(tetris::Control::Right);
},

glutin::VirtualKeyCode::Q => {
return
},

_ => {}
}

break 'event;
},

_ => break 'event
}
}

let mut frame = display.draw();

let vertex_buffer = vertex::VertexBuffer::new(
&display, &tetris_to_vertexs(&t)
).unwrap();

frame.clear_color(0.0, 0.0, 0.0 ,0.0);
frame.draw(&vertex_buffer, &index, &program, &uniform, &param).unwrap();
frame.finish().unwrap();
}
}



こいつ! 動くぞ!!

tetris.gif

(あぁ 表示崩れてる)


結び

ようやくRustと仲良く慣れた気がする

RcやRefCell、ArcやMutexなど

まだまだ理解が足りないところが多いが楽しくRust書けるようになってきたので良しとしよう