コンソールゲーム　あるいは　カリグラム的プログラム

コンソールで動作するゲームを Rust で実装しました。
（世の中はゼルダ新作でにぎわっているというのに）

# コード

(コードの解説はしません。各自ハックしてみてください)

```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シャツにするとこんな具合になりそうですが、まだまだ修行が足りませんね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960096/e7bdb042-79c3-ecf2-f7d6-eaf08c4228e5.png)


# 動作確認環境

Windows11 + Rust1.66.1

# インストール＆起動手順

1. cargo new bomb を実行
2. bomb/src/main.rs に上のコードをコピペ
3. cd bomb して cargo add getch_rs rand を実行
4. コンソールのプロパティで横８０文字・縦２５文字よりも大きくしておく
5. cargo run で起動

# あそびかた

## 起動直後の画面

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960096/a331c641-1073-1bad-278a-90bf09b0880d.png)

マップは毎回ランダムになります。
青は海、地面は黄緑から海抜が高くなるごとに濃くなりますが一番高いところは白になります。

## カーソルの移動

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960096/74f8cd67-2979-cc9e-68ae-27af84203bf2.png)

中心の黄色いカーソルは矢印キーで上下左右に移動できます。

## 爆発

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960096/9ad42b70-7fbe-bd11-5cef-f107816e8741.png)

スペースキーを押すとカーソルの位置に「発破」が仕掛けられ、爆発します。
赤い爆炎が広がっていき、やがて小さくなります。

## 爆発後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960096/64543542-55e0-8238-6cce-b7220b7834ce.png)

爆発により地面は吹き飛びます。
地面は削られ、だんだん地面の高度は下がります。
最後は海抜０となると青くなります。

すべての地面を爆発で吹き飛ばすと完了です。

途中で終了させたい場合は ESC キーか q キーで終了できます。

## キー操作まとめ

|キー|操作|
|:---|:---|
|↑|カーソルを上に移動|
|→|カーソルを右に移動|
|↓|カーソルを下に移動|
|←|カーソルを左に移動|
|スペース|発破のセット|
|ESC もしくは q|終了|

以上。