1#! /usr/bin/ruby -Ku
2# -*- coding: utf-8 -*-
3
4class Board
5  def clr
6    print "\e[2J"
7  end
8  def pos(x,y)
9    printf "\e[%d;%dH", y+1, x*2+1
10  end
11  def colorstr(id,s)
12    printf "\e[%dm%s\e[0m", id, s
13  end
14  def put(x, y, col, str)
15    pos(x,y); colorstr(43,str)
16    pos(0,@hi); print "残り:",@mc,"/",@total,"   "
17    pos(x,y)
18  end
19  private :clr, :pos, :colorstr, :put
20  CHR=["・","1","2","3","4","5","6","7","8","★","●","@@"]
21  COL=[46,43,45] # default,opened,over
22  def initialize(h,w,m)
23    # ゲーム盤の生成(h:縦,w:横,m:爆弾の数)
24    @hi=h; @wi=w; @m=m
25    reset
26  end
27  def reset
28    # ゲーム盤を(再)初期化する
29    srand()
30    @cx=0; @cy=0; @mc=@m
31    @over=false
32    @data=Array.new(@hi*@wi)
33    @state=Array.new(@hi*@wi)
34    @total=@hi*@wi
35    @total.times {|i| @data[i]=0}
36    @m.times do
37       loop do
38         j=rand(@total-1)
39         if @data[j] == 0 then
40           @data[j]=1
41           break
42         end
43       end
44    end
45    clr; pos(0,0)
46    @hi.times{|y| pos(0,y); colorstr(COL[0],CHR[0]*@wi)}
47    pos(@cx,@cy)
48  end
49  def mark
50    # 現在のカーソル位置にマークをつける
51    if @state[@wi*@cy+@cx] != nil then return end
52    @state[@wi*@cy+@cx] = "MARK"
53    @mc=@mc-1;
54    @total=@total-1;
55    put(@cx, @cy, COL[1], CHR[9])
56  end
57  def open(x=@cx,y=@cy)
58    # 現在のカーソル位置をオープンにする
59    # 爆弾があればゲームオーバー
60    if @state[@wi*y+x] =="OPEN"  then return 0 end
61    if @state[@wi*y+x] == nil then @total=@total-1 end
62    if @state[@wi*y+x] =="MARK" then @mc=@mc+1 end
63    @state[@wi*y+x]="OPEN"
64    if fetch(x,y) == 1 then @over = 1; return end
65    c = count(x,y)
66    put(x, y, COL[1], CHR[c])
67    return 0 if c != 0
68    if x > 0 && y > 0         then open(x-1,y-1) end
69    if y > 0                  then open(x,  y-1) end
70    if x < @wi-1 && y > 0     then open(x+1,y-1) end
71    if x > 0                  then open(x-1,y) end
72    if x < @wi-1              then open(x+1,y) end
73    if x > 0 && y < @hi-1     then open(x-1,y+1) end
74    if y < @hi -1             then open(x,y+1) end
75    if x < @wi-1 && y < @hi-1 then open(x+1,y+1) end
76    pos(@cx,@cy)
77  end
78  def fetch(x,y)
79    # (x,y)の位置の爆弾の数(0 or 1)を返す
80    if x < 0 then 0
81    elsif x >= @wi then 0
82    elsif y < 0 then 0
83    elsif y >= @hi then 0
84    else
85      @data[y*@wi+x]
86    end
87  end
88  def count(x,y)
89    # (x,y)に隣接する爆弾の数を返す
90    fetch(x-1,y-1)+fetch(x,y-1)+fetch(x+1,y-1)+
91    fetch(x-1,y)  +             fetch(x+1,y)+
92    fetch(x-1,y+1)+fetch(x,y+1)+fetch(x+1,y+1)
93  end
94  def over(win)
95    # ゲームの終了
96    quit
97    unless win
98      pos(@cx,@cy); print CHR[11]
99    end
100    pos(0,@hi)
101    if win then print "*** YOU WIN !! ***"
102    else print "*** GAME OVER ***"
103    end
104  end
105  def over?
106    # ゲームの終了チェック
107    # 終了処理も呼び出す
108    remain = (@mc+@total == 0)
109    if @over || remain
110      over(remain)
111      true
112    else
113      false
114    end
115  end
116  def quit
117    # ゲームの中断(または終了)
118    # 盤面を全て見せる
119    @hi.times do|y|
120      pos(0,y)
121      @wi.times do|x|
122	colorstr(if @state[y*@wi+x] == "MARK" then COL[1] else COL[2] end,
123		 if fetch(x,y)==1 then CHR[10] else CHR[count(x,y)] end)
124      end
125    end
126  end
127  def down
128    # カーソルを下に
129    if @cy < @hi-1 then @cy=@cy+1; pos(@cx, @cy) end
130  end
131  def up
132    # カーソルを上に
133    if @cy > 0 then @cy=@cy-1; pos(@cx, @cy) end
134  end
135  def left
136    # カーソルを左に
137    if @cx > 0 then @cx=@cx-1; pos(@cx, @cy) end
138  end
139  def right
140    # カーソルを右に
141    if @cx < @wi-1 then @cx=@cx+1; pos(@cx, @cy) end
142  end
143end
144
145bd=Board.new(10,10,10)
146system("stty raw -echo")
147begin
148  loop do
149    case STDIN.getc
150    when ?n  # new game
151      bd.reset
152    when ?m  # mark
153      bd.mark
154    when ?j
155      bd.down
156    when ?k
157      bd.up
158    when ?h
159      bd.left
160    when ?l
161      bd.right
162    when ?\s
163      bd.open
164    when ?q,?\C-c  # quit game
165      bd.quit
166      break
167    end
168    if bd.over?
169      if STDIN.getc == ?q then break end
170      bd.reset
171    end
172  end
173ensure
174  system("stty -raw echo")
175end
176print "\n"
177