shoes 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (275) hide show
  1. data/.gitignore +12 -0
  2. data/CHANGELOG +29 -0
  3. data/COPYING +30 -0
  4. data/README.md +76 -0
  5. data/README.old +117 -0
  6. data/Rakefile +130 -0
  7. data/Rakefile.bk +651 -0
  8. data/app.yaml +7 -0
  9. data/bin/main.skel +104 -0
  10. data/bugs/issue-012e9468ddc7b0cb7e1503413a8a11c4f8707b67.yaml +21 -0
  11. data/bugs/issue-0711ff8b67baa63586f11d0096fee9dd4436bf58.yaml +23 -0
  12. data/bugs/issue-07f87998d93eb401e22395c11085676389b935c4.yaml +22 -0
  13. data/bugs/issue-0bff2d80008803cbc7efbcdacdc60ef2163664d2.yaml +22 -0
  14. data/bugs/issue-0c66f223d19efbca7b692f3b91961f407ad7abec.yaml +24 -0
  15. data/bugs/issue-183bc3c7a8f575aff2b24e0bf22aa0dfcc8e4fc2.yaml +29 -0
  16. data/bugs/issue-1bad1e60a04cb3adb6a8f3ded128af2e16b56ffe.yaml +18 -0
  17. data/bugs/issue-1f1d43f76bf4de79d7f94adbae6dd506a9d81633.yaml +41 -0
  18. data/bugs/issue-320924117cadb045cc91f2a6fca922b4d81d4bb5.yaml +20 -0
  19. data/bugs/issue-33e5c8355fbf65252ea9e939714651df8bd8cb3b.yaml +25 -0
  20. data/bugs/issue-36f7c8262a72bcd5e28cfa4ed29328b8237ea79b.yaml +20 -0
  21. data/bugs/issue-39bf04ee50c3bef67b89b8e9beb28657805587ce.yaml +18 -0
  22. data/bugs/issue-3e28ba7754f73d02ef416eef989af707a8c00f01.yaml +23 -0
  23. data/bugs/issue-3f638cc03f41f9c3fdf217355321f1563f8fee45.yaml +22 -0
  24. data/bugs/issue-41e48e14f1ef4f6deb69be68165d7dd3ffd99156.yaml +22 -0
  25. data/bugs/issue-4c9408166a2aa5dd333bbb05520deebf4ded08e0.yaml +22 -0
  26. data/bugs/issue-55c687e108ecd5aeb8e0d0aeb4294984f84acd3e.yaml +18 -0
  27. data/bugs/issue-5811d34692cc292717a58ec1df1f8948cf30d826.yaml +28 -0
  28. data/bugs/issue-5a7c1f59a0526f9cea7fb366867cf1e6ed8ef69d.yaml +22 -0
  29. data/bugs/issue-688a2c2566bef6a03efa57a4ae1f7cfa8fc74444.yaml +21 -0
  30. data/bugs/issue-78b16c7988ade2ef96e716fa8cb9a004bd4ced65.yaml +33 -0
  31. data/bugs/issue-804ee49f9800154eddf0650690c7a9bb5626253f.yaml +27 -0
  32. data/bugs/issue-807dd1c610b2e941a5d454e26b2eac27e89a0ab4.yaml +22 -0
  33. data/bugs/issue-822ee33ec11ebd25f90ea86e8438de7891e63aee.yaml +18 -0
  34. data/bugs/issue-82ff7152cebb8a1cb065b864cb4fe22de2328146.yaml +29 -0
  35. data/bugs/issue-856afbdcdd4970ad54b8ce4a6c017fbaab45f49d.yaml +31 -0
  36. data/bugs/issue-8d49e32fb21522651c67490adb4d80076dbb14e1.yaml +24 -0
  37. data/bugs/issue-91583359653a9d530c1e32be72d874d847a306e7.yaml +21 -0
  38. data/bugs/issue-af41a5229613172764e7d3c98431172db337053d.yaml +20 -0
  39. data/bugs/issue-bd9856b3788dd429c998a5af1b2aae8221cb80b7.yaml +28 -0
  40. data/bugs/issue-be22aef5c564fb7b5e9938343136a9cd6a2edb7b.yaml +22 -0
  41. data/bugs/issue-bf8697523c77c326e7e35fc82cf3dd739ae92310.yaml +27 -0
  42. data/bugs/issue-cdd4b4795f34d6ff46e56ece10f8216a4a5456b2.yaml +25 -0
  43. data/bugs/issue-d9bfb5581d745ef9deb3b9b2e08ef74d1cd43082.yaml +20 -0
  44. data/bugs/issue-e0ce2f687cc096f35715d28b0af88589f4ab6cc6.yaml +18 -0
  45. data/bugs/issue-e4b0aca4a10a574b333aeaecaaf221dbc5ed22c6.yaml +38 -0
  46. data/bugs/issue-f263746594b95ba778455730478ee8df60ee639d.yaml +22 -0
  47. data/bugs/project.yaml +72 -0
  48. data/builddeps.sh +102 -0
  49. data/fonts/Coolvetica.ttf +0 -0
  50. data/fonts/Lacuna.ttf +0 -0
  51. data/gemlib/shoes.rb +1 -0
  52. data/lib/shoes.rb +548 -0
  53. data/lib/shoes/cache.rb +54 -0
  54. data/lib/shoes/chipmunk.rb +35 -0
  55. data/lib/shoes/data.rb +39 -0
  56. data/lib/shoes/help.rb +468 -0
  57. data/lib/shoes/image.rb +25 -0
  58. data/lib/shoes/inspect.rb +128 -0
  59. data/lib/shoes/log.rb +48 -0
  60. data/lib/shoes/minitar.rb +986 -0
  61. data/lib/shoes/override.rb +38 -0
  62. data/lib/shoes/pack.rb +543 -0
  63. data/lib/shoes/search.rb +46 -0
  64. data/lib/shoes/setup.rb +329 -0
  65. data/lib/shoes/shy.rb +131 -0
  66. data/lib/shoes/shybuilder.rb +44 -0
  67. data/lib/shoes/version.rb +3 -0
  68. data/make/darwin/deps.vlc +12 -0
  69. data/make/darwin/dylibs.shoes +22 -0
  70. data/make/darwin/dylibs.video +11 -0
  71. data/make/darwin/env.rb +81 -0
  72. data/make/darwin/tasks.rb +105 -0
  73. data/make/linux/env.rb +65 -0
  74. data/make/linux/tasks.rb +61 -0
  75. data/make/make.rb +85 -0
  76. data/make/mingw/dlls +19 -0
  77. data/make/mingw/env.rb +69 -0
  78. data/make/mingw/tasks.rb +70 -0
  79. data/make/rakefile_common.rb +8 -0
  80. data/manual-snapshots/class-book.png +0 -0
  81. data/manual-snapshots/expert-definr.png +0 -0
  82. data/manual-snapshots/expert-funnies.png +0 -0
  83. data/manual-snapshots/expert-irb.png +0 -0
  84. data/manual-snapshots/expert-minesweeper.png +0 -0
  85. data/manual-snapshots/expert-othello.png +0 -0
  86. data/manual-snapshots/expert-pong.png +0 -0
  87. data/manual-snapshots/expert-tankspank.png +0 -0
  88. data/manual-snapshots/good-arc.png +0 -0
  89. data/manual-snapshots/good-clock.png +0 -0
  90. data/manual-snapshots/good-follow.png +0 -0
  91. data/manual-snapshots/good-reminder.png +0 -0
  92. data/manual-snapshots/good-vjot.png +0 -0
  93. data/manual-snapshots/simple-accordion.png +0 -0
  94. data/manual-snapshots/simple-anim-shapes.png +0 -0
  95. data/manual-snapshots/simple-anim-text.png +0 -0
  96. data/manual-snapshots/simple-arc.png +0 -0
  97. data/manual-snapshots/simple-bounce.png +0 -0
  98. data/manual-snapshots/simple-calc.png +0 -0
  99. data/manual-snapshots/simple-chipmunk.png +0 -0
  100. data/manual-snapshots/simple-control-sizes.png +0 -0
  101. data/manual-snapshots/simple-curve.png +0 -0
  102. data/manual-snapshots/simple-dialogs.png +0 -0
  103. data/manual-snapshots/simple-downloader.png +0 -0
  104. data/manual-snapshots/simple-draw.png +0 -0
  105. data/manual-snapshots/simple-editor.png +0 -0
  106. data/manual-snapshots/simple-form.png +0 -0
  107. data/manual-snapshots/simple-mask.png +0 -0
  108. data/manual-snapshots/simple-menu.png +0 -0
  109. data/manual-snapshots/simple-menu1.png +0 -0
  110. data/manual-snapshots/simple-rubygems.png +0 -0
  111. data/manual-snapshots/simple-slide.png +0 -0
  112. data/manual-snapshots/simple-sphere.png +0 -0
  113. data/manual-snapshots/simple-sqlite3.png +0 -0
  114. data/manual-snapshots/simple-timer.png +0 -0
  115. data/manual-snapshots/simple-video.png +0 -0
  116. data/platform/mac/Info.plist +55 -0
  117. data/platform/mac/build-deps.sh +658 -0
  118. data/platform/mac/command-manual.rb +1 -0
  119. data/platform/mac/deps-osx.patch +159 -0
  120. data/platform/mac/dmg_ds_store +0 -0
  121. data/platform/mac/pangorc +2 -0
  122. data/platform/mac/pkg-dmg +1447 -0
  123. data/platform/mac/shoes +31 -0
  124. data/platform/mac/shoes-launch +7 -0
  125. data/platform/mac/stub.m +178 -0
  126. data/platform/mac/version.plist +14 -0
  127. data/platform/msw/base.nsi +644 -0
  128. data/platform/msw/installer-1.bmp +0 -0
  129. data/platform/msw/installer-2.bmp +0 -0
  130. data/platform/msw/shoes.exe.manifest +17 -0
  131. data/platform/msw/shoes.ico +0 -0
  132. data/platform/msw/stub-inject.c +59 -0
  133. data/platform/msw/stub.c +271 -0
  134. data/platform/msw/stub32.h +14 -0
  135. data/platform/msw/stub32.rc +16 -0
  136. data/platform/nix/INSTALL +56 -0
  137. data/platform/nix/Makefile +144 -0
  138. data/platform/nix/shoes.launch +20 -0
  139. data/platform/skel.rb +27 -0
  140. data/rakefile_darwin.rb +7 -0
  141. data/rakefile_linux.rb +3 -0
  142. data/rakefile_mingw.rb +7 -0
  143. data/samples/class-book.rb +43 -0
  144. data/samples/class-book.yaml +387 -0
  145. data/samples/expert-definr.rb +23 -0
  146. data/samples/expert-funnies.rb +51 -0
  147. data/samples/expert-irb.rb +112 -0
  148. data/samples/expert-minesweeper.rb +267 -0
  149. data/samples/expert-othello.rb +319 -0
  150. data/samples/expert-pong.rb +62 -0
  151. data/samples/expert-tankspank.rb +385 -0
  152. data/samples/good-arc.rb +37 -0
  153. data/samples/good-clock.rb +51 -0
  154. data/samples/good-follow.rb +26 -0
  155. data/samples/good-reminder.rb +174 -0
  156. data/samples/good-vjot.rb +56 -0
  157. data/samples/simple-accordion.rb +75 -0
  158. data/samples/simple-anim-shapes.rb +17 -0
  159. data/samples/simple-anim-text.rb +13 -0
  160. data/samples/simple-arc.rb +23 -0
  161. data/samples/simple-bounce.rb +24 -0
  162. data/samples/simple-calc.rb +70 -0
  163. data/samples/simple-chipmunk.rb +26 -0
  164. data/samples/simple-control-sizes.rb +24 -0
  165. data/samples/simple-curve.rb +26 -0
  166. data/samples/simple-dialogs.rb +29 -0
  167. data/samples/simple-downloader.rb +27 -0
  168. data/samples/simple-draw.rb +13 -0
  169. data/samples/simple-editor.rb +28 -0
  170. data/samples/simple-form.rb +28 -0
  171. data/samples/simple-form.shy +0 -0
  172. data/samples/simple-mask.rb +21 -0
  173. data/samples/simple-menu.rb +31 -0
  174. data/samples/simple-menu1.rb +35 -0
  175. data/samples/simple-rubygems.rb +29 -0
  176. data/samples/simple-slide.rb +45 -0
  177. data/samples/simple-sphere.rb +28 -0
  178. data/samples/simple-sqlite3.rb +13 -0
  179. data/samples/simple-timer.rb +13 -0
  180. data/samples/simple-video.rb +13 -0
  181. data/shoes.gemspec +21 -0
  182. data/shoes/app.c +591 -0
  183. data/shoes/app.h +110 -0
  184. data/shoes/appwin32.h +13 -0
  185. data/shoes/appwin32.rc +28 -0
  186. data/shoes/canvas.c +2202 -0
  187. data/shoes/canvas.h +682 -0
  188. data/shoes/code.h +14 -0
  189. data/shoes/config.h +232 -0
  190. data/shoes/effects.c +243 -0
  191. data/shoes/effects.h +7 -0
  192. data/shoes/http.h +44 -0
  193. data/shoes/http/common.h +86 -0
  194. data/shoes/http/curl.c +259 -0
  195. data/shoes/http/nsurl.m +274 -0
  196. data/shoes/http/windownload.c +114 -0
  197. data/shoes/http/winhttp.c +216 -0
  198. data/shoes/http/winhttp.h +19 -0
  199. data/shoes/image.c +1020 -0
  200. data/shoes/internal.c +46 -0
  201. data/shoes/internal.h +63 -0
  202. data/shoes/native.h +110 -0
  203. data/shoes/native/cocoa.h +105 -0
  204. data/shoes/native/cocoa.m +1557 -0
  205. data/shoes/native/gtk.c +1257 -0
  206. data/shoes/native/windows.c +2392 -0
  207. data/shoes/ruby.c +5221 -0
  208. data/shoes/ruby.h +299 -0
  209. data/shoes/world.c +243 -0
  210. data/shoes/world.h +63 -0
  211. data/static/Shoes.icns +0 -0
  212. data/static/avatar.png +0 -0
  213. data/static/code_highlighter.js +188 -0
  214. data/static/code_highlighter_ruby.js +26 -0
  215. data/static/icon-debug.png +0 -0
  216. data/static/icon-error.png +0 -0
  217. data/static/icon-info.png +0 -0
  218. data/static/icon-warn.png +0 -0
  219. data/static/listbox_button1.png +0 -0
  220. data/static/listbox_button2.png +0 -0
  221. data/static/man-app.png +0 -0
  222. data/static/man-builds.png +0 -0
  223. data/static/man-builds1.png +0 -0
  224. data/static/man-editor-notepad.png +0 -0
  225. data/static/man-editor-osx.png +0 -0
  226. data/static/man-ele-background.png +0 -0
  227. data/static/man-ele-border.png +0 -0
  228. data/static/man-ele-button.png +0 -0
  229. data/static/man-ele-check.png +0 -0
  230. data/static/man-ele-editbox.png +0 -0
  231. data/static/man-ele-editline.png +0 -0
  232. data/static/man-ele-image.png +0 -0
  233. data/static/man-ele-listbox.png +0 -0
  234. data/static/man-ele-progress.png +0 -0
  235. data/static/man-ele-radio.png +0 -0
  236. data/static/man-ele-shape.png +0 -0
  237. data/static/man-ele-textblock.png +0 -0
  238. data/static/man-ele-video.png +0 -0
  239. data/static/man-intro-dmg.png +0 -0
  240. data/static/man-intro-exe.png +0 -0
  241. data/static/man-look-tiger.png +0 -0
  242. data/static/man-look-ubuntu.png +0 -0
  243. data/static/man-look-vista.png +0 -0
  244. data/static/man-run-osx.png +0 -0
  245. data/static/man-run-vista.png +0 -0
  246. data/static/man-run-xp.png +0 -0
  247. data/static/man-shot1.png +0 -0
  248. data/static/manual-en.txt +3531 -0
  249. data/static/manual-ja.txt +2825 -0
  250. data/static/manual.css +167 -0
  251. data/static/menu-corner1.png +0 -0
  252. data/static/menu-corner2.png +0 -0
  253. data/static/menu-gray.png +0 -0
  254. data/static/menu-left.png +0 -0
  255. data/static/menu-right.png +0 -0
  256. data/static/menu-top.png +0 -0
  257. data/static/shoes-dmg.jpg +0 -0
  258. data/static/shoes-icon-blue.png +0 -0
  259. data/static/shoes-icon.png +0 -0
  260. data/static/shoes-manual-apps.gif +0 -0
  261. data/static/shoes_main_window.png +0 -0
  262. data/static/stripe.png +0 -0
  263. data/static/stubs/blank.exe +0 -0
  264. data/static/stubs/blank.hfz +0 -0
  265. data/static/stubs/blank.run +375 -0
  266. data/static/stubs/cocoa-install +0 -0
  267. data/static/stubs/sh-install +49 -0
  268. data/static/stubs/shoes-stub-inject.exe +0 -0
  269. data/static/stubs/shoes-stub.exe +0 -0
  270. data/static/tutor-back.png +0 -0
  271. data/test/shoes_test.rb +8 -0
  272. data/test/test_helper.rb +25 -0
  273. data/use-deps +12 -0
  274. data/use-tmp-dep +8 -0
  275. metadata +509 -0
@@ -0,0 +1,23 @@
1
+ Shoes.app :title => "Dictionary, powered by Definr", :width => 370, :height => 320 do
2
+ stack do
3
+ background red, :height => 60
4
+ flow :margin => 20 do
5
+ caption "Define: ", :stroke => white
6
+ @lookup = edit_line
7
+ button "Go" do
8
+ download "http://definr.com/definr/show/#{@lookup.text}" do |dl|
9
+ doc = dl.response.body.gsub(' ', ' ').
10
+ gsub(%r!(</a>|<br />|<a href.+?>)!, '').
11
+ gsub(%r!\(http://.+?\)!, '').strip
12
+ title, doc = doc.split(/\n+/, 2)
13
+ @deft.replace title
14
+ @defn.replace doc
15
+ end
16
+ end
17
+ end
18
+ stack :margin => 20 do
19
+ @deft = subtitle "", :margin => 10
20
+ @defn = para ""
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ require 'hpricot'
2
+
3
+ class Comic
4
+ attr_reader :rss, :title
5
+
6
+ def initialize(body)
7
+ @rss = Hpricot.XML(body)
8
+ @title = @rss.at("//channel/title").inner_text
9
+ end
10
+
11
+ def items
12
+ @rss.search("//channel/item")
13
+ end
14
+
15
+ def latest_image
16
+ @rss.search("//channel/item").first.inner_html.scan(/src="([^"]+\.\w+)"/).first
17
+ end
18
+ end
19
+
20
+ Shoes.app :width => 800, :height => 600 do
21
+ background "#555"
22
+
23
+ @title = "Web Funnies"
24
+ @feeds = [
25
+ "http://xkcd.com/rss.xml",
26
+ "http://feedproxy.google.com/DilbertDailyStrip?format=xml",
27
+ "http://www.smbc-comics.com/rss.php",
28
+ "http://www.daybydaycartoon.com/index.xml",
29
+ "http://www.questionablecontent.net/QCRSS.xml",
30
+ "http://indexed.blogspot.com/feeds/posts/default?alt=rss"
31
+ ]
32
+
33
+ stack :margin => 10 do
34
+ title strong(@title), :align => "center", :stroke => "#DFA", :margin => 0
35
+ para "(loaded from RSS feeds)", :align => "center", :stroke => "#DFA",
36
+ :margin => 0
37
+
38
+ @feeds.each do |feed|
39
+ download feed do |dl|
40
+ stack :width => "100%", :margin => 10, :border => 1 do
41
+ c = Comic.new dl.response.body
42
+ stack :margin_right => gutter do
43
+ background "#333", :curve => 4
44
+ caption c.title, :stroke => "#CD9", :margin => 4
45
+ end
46
+ image c.latest_image.first, :margin => 8
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,112 @@
1
+ require 'irb/ruby-lex'
2
+ require 'stringio'
3
+
4
+ class MimickIRB < RubyLex
5
+ attr_accessor :started
6
+
7
+ class Continue < StandardError; end
8
+ class Empty < StandardError; end
9
+
10
+ def initialize
11
+ super
12
+ set_input(StringIO.new)
13
+ end
14
+
15
+ def run(str)
16
+ obj = nil
17
+ @io << str
18
+ @io.rewind
19
+ unless l = lex
20
+ raise Empty if @line == ''
21
+ else
22
+ case l.strip
23
+ when "reset"
24
+ @line = ""
25
+ when "time"
26
+ @line = "puts %{You started \#{IRBalike.started.since} ago.}"
27
+ else
28
+ @line << l << "\n"
29
+ if @ltype or @continue or @indent > 0
30
+ raise Continue
31
+ end
32
+ end
33
+ end
34
+ unless @line.empty?
35
+ obj = eval @line, TOPLEVEL_BINDING, "(irb)", @line_no
36
+ end
37
+ @line_no += @line.scan(/\n/).length
38
+ @line = ''
39
+ @exp_line_no = @line_no
40
+
41
+ @indent = 0
42
+ @indent_stack = []
43
+
44
+ $stdout.rewind
45
+ output = $stdout.read
46
+ $stdout.truncate(0)
47
+ $stdout.rewind
48
+ [output, obj]
49
+ rescue Object => e
50
+ case e when Empty, Continue
51
+ else @line = ""
52
+ end
53
+ raise e
54
+ ensure
55
+ set_input(StringIO.new)
56
+ end
57
+
58
+ end
59
+
60
+ CURSOR = ">>"
61
+ IRBalike = MimickIRB.new
62
+ $stdout = StringIO.new
63
+
64
+ Shoes.app do
65
+ @str, @cmd = [CURSOR + " "], ""
66
+ stack :width => 1.0, :height => 1.0 do
67
+ background "#555"
68
+ stack :width => 1.0, :height => 50 do
69
+ para "Interactive Ruby ready.", :fill => white, :stroke => red
70
+ end
71
+ @scroll =
72
+ stack :width => 1.0, :height => -50, :scroll => true do
73
+ background "#555"
74
+ @console = para @str, :font => "Monospace 12px", :stroke => "#dfa"
75
+ @console.cursor = -1
76
+ end
77
+ end
78
+ keypress do |k|
79
+ case k
80
+ when "\n"
81
+ begin
82
+ out, obj = IRBalike.run(@cmd + ';')
83
+ @str += ["#@cmd\n",
84
+ span("#{out}=> #{obj.inspect}\n", :stroke => "#fda"),
85
+ "#{CURSOR} "]
86
+ @cmd = ""
87
+ rescue MimickIRB::Empty
88
+ rescue MimickIRB::Continue
89
+ @str += ["#@cmd\n.. "]
90
+ @cmd = ""
91
+ rescue Object => e
92
+ @str += ["#@cmd\n", span("#{e.class}: #{e.message}\n", :stroke => "#fcf"),
93
+ "#{CURSOR} "]
94
+ @cmd = ""
95
+ end
96
+ when String
97
+ @cmd += k
98
+ when :backspace
99
+ @cmd.slice!(-1)
100
+ when :tab
101
+ @cmd += " "
102
+ when :alt_q
103
+ quit
104
+ when :alt_c
105
+ self.clipboard = @cmd
106
+ when :alt_v
107
+ @cmd += self.clipboard
108
+ end
109
+ @console.replace *(@str + [@cmd])
110
+ @scroll.scroll_top = @scroll.scroll_max
111
+ end
112
+ end
@@ -0,0 +1,267 @@
1
+ #
2
+ # Shoes Minesweeper by que/varyform
3
+ #
4
+ LEVELS = { :beginner => [9, 9, 10], :intermediate => [16, 16, 40], :expert => [30, 16, 99] }
5
+
6
+ class Field
7
+ CELL_SIZE = 20
8
+ COLORS = %w(#00A #0A0 #A00 #004 #040 #400 #000)
9
+
10
+ class Cell
11
+ attr_accessor :flag
12
+ def initialize(aflag = false)
13
+ @flag = aflag
14
+ end
15
+ end
16
+
17
+ class Bomb < Cell
18
+ attr_accessor :exploded
19
+ def initialize(exploded = false)
20
+ @exploded = exploded
21
+ end
22
+ end
23
+
24
+ class OpenCell < Cell
25
+ attr_accessor :number
26
+ def initialize(bombs_around = 0)
27
+ @number = bombs_around
28
+ end
29
+ end
30
+
31
+ class EmptyCell < Cell; end
32
+
33
+ attr_reader :cell_size, :offset
34
+
35
+ def initialize(app, level = :beginner)
36
+ @app = app
37
+ @field = []
38
+ @w, @h, @bombs = LEVELS[level][0], LEVELS[level][1], LEVELS[level][2]
39
+ @h.times { @field << Array.new(@w) { EmptyCell.new } }
40
+ @game_over = false
41
+ @width, @height, @cell_size = @w * CELL_SIZE, @h * CELL_SIZE, CELL_SIZE
42
+ @offset = [(@app.width - @width.to_i) / 2, (@app.height - @height.to_i) / 2]
43
+ plant_bombs
44
+ @start_time = Time.now
45
+ end
46
+
47
+ def total_time
48
+ @latest_time = Time.now - @start_time unless game_over? || all_found?
49
+ @latest_time
50
+ end
51
+
52
+ def click!(x, y)
53
+ return unless cell_exists?(x, y)
54
+ return if has_flag?(x, y)
55
+ return die!(x, y) if bomb?(x, y)
56
+ open(x, y)
57
+ discover(x, y) if bombs_around(x, y) == 0
58
+ end
59
+
60
+ def flag!(x, y)
61
+ return unless cell_exists?(x, y)
62
+ self[x, y].flag = !self[x, y].flag unless self[x, y].is_a?(OpenCell)
63
+ end
64
+
65
+ def game_over?
66
+ @game_over
67
+ end
68
+
69
+ def render_cell(x, y, color = "#AAA", stroke = true)
70
+ @app.stroke "#666" if stroke
71
+ @app.fill color
72
+ @app.rect x*cell_size, y*cell_size, cell_size-1, cell_size-1
73
+ @app.stroke "#BBB" if stroke
74
+ @app.line x*cell_size+1, y*cell_size+1, x*cell_size+cell_size-1, y*cell_size
75
+ @app.line x*cell_size+1, y*cell_size+1, x*cell_size, y*cell_size+cell_size-1
76
+ end
77
+
78
+ def render_flag(x, y)
79
+ @app.stroke "#000"
80
+ @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)
81
+ @app.fill "#A00"
82
+ @app.rect(x*cell_size+cell_size / 4+2, y*cell_size + cell_size / 5,
83
+ cell_size / 3, cell_size / 4)
84
+ end
85
+
86
+ def render_bomb(x, y)
87
+ render_cell(x, y)
88
+ if (game_over? or all_found?) then # draw bomb
89
+ if self[x, y].exploded then
90
+ render_cell(x, y, @app.rgb(0xFF, 0, 0, 0.5))
91
+ end
92
+ @app.nostroke
93
+ @app.fill @app.rgb(0, 0, 0, 0.8)
94
+ @app.oval(x*cell_size+3, y*cell_size+3, 13)
95
+ @app.fill "#333"
96
+ @app.oval(x*cell_size+5, y*cell_size+5, 7)
97
+ @app.fill "#AAA"
98
+ @app.oval(x*cell_size+6, y*cell_size+6, 3)
99
+ @app.fill @app.rgb(0, 0, 0, 0.8)
100
+ @app.stroke "#222"
101
+ @app.strokewidth 2
102
+ @app.oval(x*cell_size + cell_size / 2 + 2, y*cell_size + cell_size / 4 - 2, 2)
103
+ @app.oval(x*cell_size + cell_size / 2 + 4, y*cell_size + cell_size / 4 - 2, 1)
104
+ @app.strokewidth 1
105
+ end
106
+ end
107
+
108
+ def render_number(x, y)
109
+ render_cell(x, y, "#999", false)
110
+ if self[x, y].number != 0 then
111
+ @app.nostroke
112
+ @app.para self[x, y].number.to_s, :left => x*cell_size + 3, :top => y*cell_size - 2,
113
+ :font => '13px', :stroke => COLORS[self[x, y].number - 1]
114
+ end
115
+ end
116
+
117
+ def paint
118
+ 0.upto @h-1 do |y|
119
+ 0.upto @w-1 do |x|
120
+ @app.nostroke
121
+ case self[x, y]
122
+ when EmptyCell then render_cell(x, y)
123
+ when Bomb then render_bomb(x, y)
124
+ when OpenCell then render_number(x, y)
125
+ end
126
+ render_flag(x, y) if has_flag?(x, y) && !(game_over? && bomb?(x, y))
127
+ end
128
+ end
129
+ end
130
+
131
+ def bombs_left
132
+ @bombs - @field.flatten.compact.reject {|e| !e.flag }.size
133
+ end
134
+
135
+ def all_found?
136
+ @field.flatten.compact.reject {|e| !e.is_a?(OpenCell) }.size + @bombs == @w*@h
137
+ end
138
+
139
+ def reveal!(x, y)
140
+ return unless cell_exists?(x, y)
141
+ return unless self[x, y].is_a?(Field::OpenCell)
142
+ if flags_around(x, y) >= self[x, y].number then
143
+ (-1..1).each do |v|
144
+ (-1..1).each { |h| click!(x+h, y+v) unless (v==0 && h==0) or has_flag?(x+h, y+v) }
145
+ end
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ def cell_exists?(x, y)
152
+ ((0...@w).include? x) && ((0...@h).include? y)
153
+ end
154
+
155
+ def has_flag?(x, y)
156
+ return false unless cell_exists?(x, y)
157
+ return self[x, y].flag
158
+ end
159
+
160
+ def bomb?(x, y)
161
+ cell_exists?(x, y) && (self[x, y].is_a? Bomb)
162
+ end
163
+
164
+ def can_be_discovered?(x, y)
165
+ return false unless cell_exists?(x, y)
166
+ return false if self[x, y].flag
167
+ cell_exists?(x, y) && (self[x, y].is_a? EmptyCell) && !bomb?(x, y) && (bombs_around(x, y) == 0)
168
+ end
169
+
170
+ def open(x, y)
171
+ self[x, y] = OpenCell.new(bombs_around(x, y)) unless (self[x, y].is_a? OpenCell) or has_flag?(x, y)
172
+ end
173
+
174
+ def neighbors
175
+ (-1..1).each do |col|
176
+ (-1..1).each { |row| yield row, col unless col==0 && row == 0 }
177
+ end
178
+ end
179
+
180
+ def discover(x, y)
181
+ open(x, y)
182
+ neighbors do |col, row|
183
+ cx, cy = x+row, y+col
184
+ next unless cell_exists?(cx, cy)
185
+ discover(cx, cy) if can_be_discovered?(cx, cy)
186
+ open(cx, cy)
187
+ end
188
+ end
189
+
190
+ def count_neighbors
191
+ return 0 unless block_given?
192
+ count = 0
193
+ neighbors { |h, v| count += 1 if yield(h, v) }
194
+ count
195
+ end
196
+
197
+ def bombs_around(x, y)
198
+ count_neighbors { |v, h| bomb?(x+h, y+v) }
199
+ end
200
+
201
+ def flags_around(x, y)
202
+ count_neighbors { |v, h| has_flag?(x+h, y+v) }
203
+ end
204
+
205
+ def die!(x, y)
206
+ self[x, y].exploded = true
207
+ @game_over = true
208
+ end
209
+
210
+ def plant_bomb(x, y)
211
+ self[x, y].is_a?(EmptyCell) ? self[x, y] = Bomb.new : false
212
+ end
213
+
214
+ def plant_bombs
215
+ @bombs.times { redo unless plant_bomb(rand(@w), rand(@h)) }
216
+ end
217
+
218
+ def [](*args)
219
+ x, y = args
220
+ raise "Cell #{x}:#{y} does not exists!" unless cell_exists?(x, y)
221
+ @field[y][x]
222
+ end
223
+
224
+ def []=(*args)
225
+ x, y, v = args
226
+ cell_exists?(x, y) ? @field[y][x] = v : false
227
+ end
228
+ end
229
+
230
+ Shoes.app :width => 730, :height => 450, :title => 'Minesweeper' do
231
+ def render_field
232
+ clear do
233
+ background rgb(50, 50, 90, 0.7)
234
+ flow :margin => 4 do
235
+ button("Beginner") { new_game :beginner }
236
+ button("Intermediate") { new_game :intermediate }
237
+ button("Expert") { new_game :expert }
238
+ end
239
+ stack do @status = para :stroke => white end
240
+ @field.paint
241
+ para "Left click - open cell, right click - put flag, middle click - reveal empty cells", :top => 420, :left => 0, :stroke => white, :font => "11px"
242
+ end
243
+ end
244
+
245
+ def new_game level
246
+ @field = Field.new self, level
247
+ translate -@old_offset.first, -@old_offset.last unless @old_offset.nil?
248
+ translate @field.offset.first, @field.offset.last
249
+ @old_offset = @field.offset
250
+ render_field
251
+ end
252
+
253
+ new_game :beginner
254
+ animate(5) { @status.replace "Time: #{@field.total_time.to_i} Bombs left: #{@field.bombs_left}" }
255
+
256
+ click do |button, x, y|
257
+ next if @field.game_over? || @field.all_found?
258
+ fx, fy = ((x-@field.offset.first) / @field.cell_size).to_i, ((y-@field.offset.last) / @field.cell_size).to_i
259
+ @field.click!(fx, fy) if button == 1
260
+ @field.flag!(fx, fy) if button == 2
261
+ @field.reveal!(fx, fy) if button == 3
262
+
263
+ render_field
264
+ alert("Winner!\nTotal time: #{@field.total_time}") if @field.all_found?
265
+ alert("Bang!\nYou loose.") if @field.game_over?
266
+ end
267
+ end