コンソールで動作するゲームを Rust で実装しました。
(世の中はゼルダ新作でにぎわっているというのに)
コード
(コードの解説はしません。各自ハックしてみてください)
/* cargo add getch_rs rand; cargo run */ const FW:usize =80;const FH:usize=25
;const FZ:usize=100;const FS:usize=FW*FH;const FR: usize=10;const CS:&
str="\x1b[";const C1:&str ="255;000;000";const C2:& str="000;000;255";
const C3:&str="000;255;000" ;const C4:&str="000;250;154" ;const C5:&str=
"144;238;144";const C6:&str ="034;139;034";const C7:&str ="000;100;000";
const C8:&str="047;079;079" ;const C9:&str="112;128;144" ;const C10:&str=
"128;128;128";const C11: &str="192;192;192";const C12 :&str =
"245;245;245";const C13: &str="255;255;255";const C14 :&str=
"255;255;000";const C15 :&str="255;000;000";const M1 :&str=
"Perfect!";const M2:&str ="Bye.";use getch_rs::{Getch as G,Key
::{Left as KL,Right as KR,Up as KU,Down as KD ,Esc
as KE,Char as KC}};use rand::Rng;use std::
sync::{Arc,Mutex};use std::{thread,time };fn main
()->std::io::Result< ()>{cls();let mut c=C::new();c.g()
;c.d();let c=Arc::new (Mutex::new(c)); {let c=Arc::clone
(&c);let _=thread:: spawn(move||{loop {thread::sleep(time
::Duration:: from_millis( 1000));let mut c=
c.lock().unwrap( ) ;let mut nb :Vec<B>=vec
![];if c.bb.len ()>0 {for b in &c.bb{if
b.t<4{nb.push( B::new (b.x,b.y,b. t+1));
}}c.bb=nb;c.u ();}c. d();c.cb(); }});}
let mut bp= false; let g=G ::new()
;loop{match g.getch ()?{KL=>{let mut c=c.lock
().unwrap( );c.mw();bp=c.bp;}KR=>{ let mut c=c.lock
().unwrap ();c.me ();bp=c.bp;}KU=>{ let mut c=c.lock()
.unwrap( ); c.mn();bp=c.bp;}KD=>{let mut c=c.lock().
unwrap( );c.ms();bp=c.bp;}KC(' ' )=>{let mut c=c.lock
(). unwrap();c.ad();}KE|KC( 'q')=>{ break
;}_=> (),}if bp{break;}}Ok(q(
bp)) } struct C{fd:[usize;FS]
,p: P,bb:Vec <B>,bp:bool,}impl C
{fn new()->Self{ C{fd:[0;FS],p:P
:: new(),bb:vec! [],bp:false,
}}fn ad(&mut self){self. bb.push(B::new(
self.p.x-1,self .p.y-1,0));} fn g(&mut self){let mut r=rand
::thread_rng();let mut ks:Vec<K >=vec![];for _ in 0..r.gen_range(5..
FS/20){let x=r. gen_range (1..FW);let y=r.gen_range(1..FH);let z=r.gen_range
(1..FZ);let s=r. gen_range(1..FW);ks.push( K::new(x,y,z,s));}let mut max=0;let mut
cnt=0;let mut lcnt =100 ;while(max<10||cnt>=FS/2)&&lcnt >0{max=0;cnt
=0;for p in 0..FS{for k in&ks{self.fd[p]+=k.val (p%FW,p/
FW);}if self.fd[p]>FZ {self.fd[p]=FZ;}if self.fd
[p]>max{max=self.fd[ p];}if self.fd [ p]<10{ cnt+=1;
}}lcnt-=1;}for x in 0..FW{self. fd[x]=0;self.fd[ FS-FW+
x]=0;}for y in 0..FH{self.fd[y*FW]= 0;self.fd[(y+1) *FW-1]
=0;}}fn u (&mut self){for p in 0.. FS{for b in&self .bb{if
b.ib(p%FW ,p/FW)&&self.fd[p]>=10{ self.fd[p]-=5; }}}for
x in 0..FW{ self.fd[x]=0;self.fd[FS -FW+x]=0;}for y in 0..FH
{self.fd[y*FW ] =0;self.fd[(y+1)*FW-1]= 0;}}fn d(& self){
print!("{}H",CS);for (p,v )in self.fd.iter () .
enumerate(){let mut bp= false;for b in &self.
bb{if b.ib(p%FW,p/FW ){bp= true;break; }}print
!( "{}48;2;{}m {}m" ,CS,if bp{C1}else if*v<10
{C2}else if*v<15{C3 }else if*v<20{ C4 }else
if*v<30{ C5}else if*v< 40{C6}else if*v <50 {C7}else
if*v<60{ C8}else if*v <70{C9} else if*v< 80{C10
}else if *v<90{ C11} else if*v <100 {C12}else {C13},
CS);if p %FW== FW- 1{println! () ;}}print !(
"{}{};{}H" , CS , self.p.y,self . p.x);print !(
"{}48;2;{}m" , CS ,C14);print !("{}38;2;{}m+" ,CS,C15
);println !( "{}m" ,CS);}fn me( &mut self){if self.p
.x<FW-1 {self .p.x+=1;}self .d();}fn mw (&mut
self){if self .p.x>2{self.p. x-=1;}
self.d( );} fn mn(&mut self) {if
self.p. y>2{ self.p.y-=1;}self .d ();}fn
ms(&mut self) { if self. p.y <FH-1{ self.p
.y+=1;} self. d();}fn cb(&mut self){if self.bp
{return}for (p,v)in self. fd.iter (). enumerate
(){let x=p% FW;let y =p/FW ;if x ==0||x== FW-1||
y==0||y==FH-1{ continue ;}if*v >=10{ self.bp
=false;return}} self.bp= true}} struct P{x:
usize,y:usize ,}impl P{fn new()-> Self {P{x:FW
/2+1,y:FH/ 2+1,}}}struct B{x :usize,y :usize
,t: usize,r:f64,}impl B{fn new(x:usize ,y:usize
,t: usize)->Self{let mut r= rand:: thread_rng
(); B{x:x,y:y,t:t,r:r.gen_range (2..=FR)as f64,}}
fn ib (&self,x:usize,y:usize)->bool {let r=self .r*f64
::sin( (self.t as f64)*std::f64::consts ::PI/4.0);let x=(x as
f64)-( self.x as f64);let y=(y as f64)-( self.y as f64 );x*x+y
*y<=r*r}}fn cls(){print!("{}2J",CS);print!("{}H" ,CS);print!( "{}?25l"
,CS);}fn q(bp:bool){cls();println!("{}?25h",CS); println!("{}",if bp{M1}else
{M2})}struct K{x:usize,y:usize,z:usize,n:N,}impl K {fn new(x:usize,y :usize,z:
usize,s:usize)->Self{let n=N::new(0.0,s as f64);K{ x,y,z,n}}fn val(& self,x:usize
,y:usize)->usize{let dx=(self.x as f64)-(x as f64);let dy=(self.y as f64)-(y as f64
);let d=(dx*dx+dy*dy).sqrt();(self.n.pdf(d)*(self.z as f64))as usize}} #[derive(Clone
)]struct N{m:f64,s:f64,}impl N{fn new(m:f64,s:f64)->Self{N{m,s}}fn pdf(&self,x:f64)->
f64 {let x2=x-self.m; let s2=self.s*self.s*2.0;(-x2*x2/s2).exp() / (s2*2.0).sqrt()}}
"rust" = 「錆」ということで。
Tシャツにするとこんな具合になりそうですが、まだまだ修行が足りませんね。
動作確認環境
Windows11 + Rust1.66.1
インストール&起動手順
- cargo new bomb を実行
- bomb/src/main.rs に上のコードをコピペ
- cd bomb して cargo add getch_rs rand を実行
- コンソールのプロパティで横80文字・縦25文字よりも大きくしておく
- cargo run で起動
あそびかた
起動直後の画面
マップは毎回ランダムになります。
青は海、地面は黄緑から海抜が高くなるごとに濃くなりますが一番高いところは白になります。
カーソルの移動
中心の黄色いカーソルは矢印キーで上下左右に移動できます。
爆発
スペースキーを押すとカーソルの位置に「発破」が仕掛けられ、爆発します。
赤い爆炎が広がっていき、やがて小さくなります。
爆発後
爆発により地面は吹き飛びます。
地面は削られ、だんだん地面の高度は下がります。
最後は海抜0となると青くなります。
すべての地面を爆発で吹き飛ばすと完了です。
途中で終了させたい場合は ESC キーか q キーで終了できます。
キー操作まとめ
キー | 操作 |
---|---|
↑ | カーソルを上に移動 |
→ | カーソルを右に移動 |
↓ | カーソルを下に移動 |
← | カーソルを左に移動 |
スペース | 発破のセット |
ESC もしくは q | 終了 |
以上。