           (****** PL/T による マインスイーパー ******)
(*
     ８×８のマス目に１０個の地雷が仕掛けてあります。この１０個の地雷を
   避けた５４のマスを陣地として無事取ることができればクリアというゲーム
　 です。マスを開くと、そのマスの縦横斜めに何個の地雷があるかを数字で表
   示してくれます。この数字を頼りにマスを開いていくわけですが、地雷に当
   たればそこでゲームオーバーとなります。
*)

const Size    = 10 ;         (* 実際の盤の1辺の大きさ       *)
      N       =  8 ;         (* ゲーム上の盤の大きさ Size-2 *)
      MaxMine = 10 ;         (* 地雷の数                    *)
      (empty,mine,ground) ;  (* 盤の状態                    *)

var   table[Size,Size] ;     (* 盤                          *)
      numTb[Size,Size] ;     (* 数情報盤                    *)
      mineTbl[MaxMine] ;     (* 地雷の座標                  *)
      remainder        ;     (* 残り陣地数                  *)

(****************************)
(*    画面制御ライブラリ    *)
(****************************)

const  ESC     = 27 ;         (* ESCコード                   *)

(* X桁 Y行にカーソルを移動 *)
procedure gotoXY(x,y);
begin
  write(ESC$,'[',y:1,';',x:1,'H')
end;

(* 画面の消去 *)
procedure ClrScr;
begin
  write(ESC$,'*')
end;

(* カーソル以降の画面をクリア   *)
procedure clrUnderCsr ;
begin
  write(ESC$,'Y')
end ;

(* 反転表示 *)
procedure reverse;
begin
  write(ESC$,'[7m')
end ;

(* アトリビュート解除 *)
procedure normal;
begin
  write(ESC$,'[0m')
end ;

(****************************)
(*    乱数発生ライブラリ    *)
(****************************)

var   RD ;                              (* 乱数列 *)

(* k未満の乱数を乗算合同法で発生 *)
function rand(k) ;
begin
  RD := RD * 259   ;
  RD := RD % 32767 ;
  return (RD % k)
end ;

(* 乱数列の初期値入力処理
    入力された数を1〜32767までの奇数に変換する *)
procedure randomize ;
begin
  write('乱数列発生のため任意の値を入れて下さい => ');
  read(RD) ;
  RD := abs(RD) % 32768 ;
  if RD % 2 = 0 then RD := RD + 1       (* PL/Tには odd関数はない *)
end ;

(**************************)
(*     地雷表示処理       *)
(**************************)
procedure printMine ;
  var i,x,y ;
begin
  for i:=0 to MaxMine-1 do
  begin
    x := mineTbl[i] / N + 1 ;           (* 対応するx,y座標計算 *)
    y := mineTbl[i] % N + 1 ;
    gotoXY((y-1)*4+3,x*2+1) ;
    write('★')
  end ;
  gotoXY(1,22)
end ;

(**************************)
(*  盤のオープン表示処理  *)
(**************************)
procedure print(x,y) ;
begin
  if (x=0) + (y=0) + (x=N+1) + (y=N+1) then return ; (* 境界 *)
  if table[x,y] <> empty then return ;  (* 処理済のところはやらない *)
  gotoXY((y-1)*4+3,x*2+1) ;
  reverse ;                             (* 反転表示 *)
  write(numTb[x,y]:2) ;
  normal ;                              (* 通常表示 *)
  table[x,y] := ground ;
  remainder  := remainder - 1 ;

(*  再帰によって０のマスの回りをすべて開けていく *)
  if numTb[x,y] = 0 then
  begin
    print(x-1,y-1) ; print(x-1,y) ; print(x-1,y+1) ;
    print(x  ,y-1) ;(***********)   print(x  ,y+1) ;
    print(x+1,y-1) ; print(x+1,y) ; print(x+1,y+1)
  end
end ;

(**************************)
(*   ゲームプレイ処理     *)
(**************************)
procedure play ;
  var position ;
      x,y      ;
      ok       ;
begin
  repeat
    gotoXY(1,21) ;
    clrUnderCsr  ;
    write('取りたい陣地を番号で入力して下さい ? ') ;
    read(position) ;
    ok := (0<=position) * (position < N*N) ;
    if ok then
    begin
      x := position / N + 1 ;           (* 対応するx,y座標計算 *)
      y := position % N + 1 ;
      if table[x,y] = mine then
      begin
        writeln('地雷に当たりました！') ;
        printMine ;
        halt                            (* プログラム停止 *)
      end ;
      ok := table[x,y] = empty
    end
  until ok ;

  print(x,y)   ;                        (* 該当場所をオープンする *)
  gotoxy(1,19) ;
  writeln('残り陣地数は',remainder:3)
end ;

(**************************)
(*     初期設定処理       *)
(**************************)
procedure initialize ;
  var i,x,y ;
begin
  randomize ;
  writeln('しばらくお待ち下さい') ;

 (* 全エリアを空に初期設定 *)
  for x:=0 to N+1 do
    for y := 0 to N+1 do table[x,y] := empty ;

  (* 地雷を埋め込む *)
  for i := 1 to MaxMine do
  begin
    repeat
      x := rand(N)+1 ; y := rand(N)+1 ; (* 1〜Nまでの乱数発生   *)
    until table[x,y] = empty ;          (* 空でなければ繰り返す *)
    table[x,y] := mine ;                (* 地雷を埋め込む       *)
    mineTbl[i-1] := (x-1)*N+y-1 ;       (* 地雷座標             *)
  end  ;
  remainder := N*N - MaxMine ;          (* 残り地雷数を初期設定 *)

 (* 各マス目に情報を設定する *)
  for x:=1 to N do
    for y:=1 to N do
     numTb[x,y] :=      (* mine = 1 なので 単純に加算すれば良い *)
      table[x-1,y-1] + table[x-1,y] + table[x-1,y+1] +
      table[x  ,y-1] + (**********)   table[x  ,y+1] +
      table[x+1,y-1] + table[x+1,y] + table[x+1,y+1] ;

 (* 盤を表示する *)
  clrScr ;
  writeln(' ＊＊＊　マインスイーパー　＊＊＊') ;
  write('┏') ;                         (* 先頭行 *)
  for y:=1 to N-1 do write('━┳') ;
  writeln('━┓') ;
  for x:=1 to N do
  begin
    for y:=1 to N do write('┃',(x-1)*N+y-1:2) ;
    writeln('┃') ;
    if x <> N then                      (* 中間行 *)
      begin  write('┣') ;
             for y:=1 to N-1 do write('━╋') ;
             writeln('━┫')
      end
    else                               (* 最終行 *)
      begin  write('┗') ;
             for y:=1 to N-1 do write('━┻') ;
             writeln('━┛')
      end
  end ;
  writeln('残り陣地数は',remainder:3)
end ;

(**************************)
(*      メイン処理        *)
(**************************)
begin
  initialize ;
  repeat  play  until remainder = 0 ;
  printMine ;
  writeln('*** クリア！ ***')
end.
