1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
#
# Shoes Minesweeper by que/varyform
#
LEVELS = { :beginner => [9, 9, 10], :intermediate => [16, 16, 40], :expert => [30, 16, 99] }
class Field
CELL_SIZE = 20
COLORS = %w(#00A #0A0 #A00 #004 #040 #400 #000)
class Cell
attr_accessor :flag
def initialize(aflag = false)
@flag = aflag
end
end
class Bomb < Cell
attr_accessor :exploded
def initialize(exploded = false)
@exploded = exploded
end
end
class OpenCell < Cell
attr_accessor :number
def initialize(bombs_around = 0)
@number = bombs_around
end
end
class EmptyCell < Cell; end
attr_reader :cell_size, :offset
def initialize(app, level = :beginner)
@app = app
@field = []
@w, @h, @bombs = LEVELS[level][0], LEVELS[level][1], LEVELS[level][2]
@h.times { @field << Array.new(@w) { EmptyCell.new } }
@game_over = false
@width, @height, @cell_size = @w * CELL_SIZE, @h * CELL_SIZE, CELL_SIZE
@offset = [(@app.width - @width.to_i) / 2, (@app.height - @height.to_i) / 2]
plant_bombs
@start_time = Time.now
end
def total_time
@latest_time = Time.now - @start_time unless game_over? || all_found?
@latest_time
end
def click!(x, y)
return unless cell_exists?(x, y)
return if has_flag?(x, y)
return die!(x, y) if bomb?(x, y)
open(x, y)
discover(x, y) if bombs_around(x, y) == 0
end
def flag!(x, y)
return unless cell_exists?(x, y)
self[x, y].flag = !self[x, y].flag unless self[x, y].is_a?(OpenCell)
end
def game_over?
@game_over
end
def render_cell(x, y, color = "#AAA", stroke = true)
@app.stroke "#666" if stroke
@app.fill color
@app.rect x*cell_size, y*cell_size, cell_size-1, cell_size-1
@app.stroke "#BBB" if stroke
@app.line x*cell_size+1, y*cell_size+1, x*cell_size+cell_size-1, y*cell_size
@app.line x*cell_size+1, y*cell_size+1, x*cell_size, y*cell_size+cell_size-1
end
def render_flag(x, y)
@app.stroke "#000"
@app.line(x*cell_size+cell_size / 4 + 1, y*cell_size + cell_size / 5, x*cell_size+cell_size / 4 + 1, y*cell_size+cell_size / 5 * 4)
@app.fill "#A00"
@app.rect(x*cell_size+cell_size / 4+2, y*cell_size + cell_size / 5,
cell_size / 3, cell_size / 4)
end
def render_bomb(x, y)
render_cell(x, y)
if (game_over? or all_found?) then # draw bomb
if self[x, y].exploded then
render_cell(x, y, @app.rgb(0xFF, 0, 0, 0.5))
end
@app.nostroke
@app.fill @app.rgb(0, 0, 0, 0.8)
@app.oval(x*cell_size+3, y*cell_size+3, 13)
@app.fill "#333"
@app.oval(x*cell_size+5, y*cell_size+5, 7)
@app.fill "#AAA"
@app.oval(x*cell_size+6, y*cell_size+6, 3)
@app.fill @app.rgb(0, 0, 0, 0.8)
@app.stroke "#222"
@app.strokewidth 2
@app.oval(x*cell_size + cell_size / 2 + 2, y*cell_size + cell_size / 4 - 2, 2)
@app.oval(x*cell_size + cell_size / 2 + 4, y*cell_size + cell_size / 4 - 2, 1)
@app.strokewidth 1
end
end
def render_number(x, y)
render_cell(x, y, "#999", false)
if self[x, y].number != 0 then
@app.nostroke
@app.para self[x, y].number.to_s, :left => x*cell_size + 3, :top => y*cell_size - 2,
:font => '13px', :stroke => COLORS[self[x, y].number - 1]
end
end
def paint
0.upto @h-1 do |y|
0.upto @w-1 do |x|
@app.nostroke
case self[x, y]
when EmptyCell then render_cell(x, y)
when Bomb then render_bomb(x, y)
when OpenCell then render_number(x, y)
end
render_flag(x, y) if has_flag?(x, y) && !(game_over? && bomb?(x, y))
end
end
end
def bombs_left
@bombs - @field.flatten.compact.reject {|e| !e.flag }.size
end
def all_found?
@field.flatten.compact.reject {|e| !e.is_a?(OpenCell) }.size + @bombs == @w*@h
end
def reveal!(x, y)
return unless cell_exists?(x, y)
return unless self[x, y].is_a?(Field::OpenCell)
if flags_around(x, y) >= self[x, y].number then
(-1..1).each do |v|
(-1..1).each { |h| click!(x+h, y+v) unless (v==0 && h==0) or has_flag?(x+h, y+v) }
end
end
end
private
def cell_exists?(x, y)
((0...@w).include? x) && ((0...@h).include? y)
end
def has_flag?(x, y)
return false unless cell_exists?(x, y)
return self[x, y].flag
end
def bomb?(x, y)
cell_exists?(x, y) && (self[x, y].is_a? Bomb)
end
def can_be_discovered?(x, y)
return false unless cell_exists?(x, y)
return false if self[x, y].flag
cell_exists?(x, y) && (self[x, y].is_a? EmptyCell) && !bomb?(x, y) && (bombs_around(x, y) == 0)
end
def open(x, y)
self[x, y] = OpenCell.new(bombs_around(x, y)) unless (self[x, y].is_a? OpenCell) or has_flag?(x, y)
end
def neighbors
(-1..1).each do |col|
(-1..1).each { |row| yield row, col unless col==0 && row == 0 }
end
end
def discover(x, y)
open(x, y)
neighbors do |col, row|
cx, cy = x+row, y+col
next unless cell_exists?(cx, cy)
discover(cx, cy) if can_be_discovered?(cx, cy)
open(cx, cy)
end
end
def count_neighbors
return 0 unless block_given?
count = 0
neighbors { |h, v| count += 1 if yield(h, v) }
count
end
def bombs_around(x, y)
count_neighbors { |v, h| bomb?(x+h, y+v) }
end
def flags_around(x, y)
count_neighbors { |v, h| has_flag?(x+h, y+v) }
end
def die!(x, y)
self[x, y].exploded = true
@game_over = true
end
def plant_bomb(x, y)
self[x, y].is_a?(EmptyCell) ? self[x, y] = Bomb.new : false
end
def plant_bombs
@bombs.times { redo unless plant_bomb(rand(@w), rand(@h)) }
end
def [](*args)
x, y = args
raise "Cell #{x}:#{y} does not exists!" unless cell_exists?(x, y)
@field[y][x]
end
def []=(*args)
x, y, v = args
cell_exists?(x, y) ? @field[y][x] = v : false
end
end
Shoes.app :width => 730, :height => 450, :title => 'Minesweeper' do
def render_field
clear do
background rgb(50, 50, 90, 0.7)
flow :margin => 4 do
button("Beginner") { new_game :beginner }
button("Intermediate") { new_game :intermediate }
button("Expert") { new_game :expert }
end
stack do @status = para :stroke => white end
@field.paint
para "Left click - open cell, right click - put flag, middle click - reveal empty cells", :top => 420, :left => 0, :stroke => white, :font => "11px"
end
end
def new_game level
@field = Field.new self, level
translate -@old_offset.first, -@old_offset.last unless @old_offset.nil?
translate @field.offset.first, @field.offset.last
@old_offset = @field.offset
render_field
end
new_game :beginner
animate(5) { @status.replace "Time: #{@field.total_time.to_i} Bombs left: #{@field.bombs_left}" }
click do |button, x, y|
next if @field.game_over? || @field.all_found?
fx, fy = ((x-@field.offset.first) / @field.cell_size).to_i, ((y-@field.offset.last) / @field.cell_size).to_i
@field.click!(fx, fy) if button == 1
@field.flag!(fx, fy) if button == 2
@field.reveal!(fx, fy) if button == 3
render_field
alert("Winner!\nTotal time: #{@field.total_time}") if @field.all_found?
alert("Bang!\nYou loose.") if @field.game_over?
end
end
|