Commit | Line | Data |
---|---|---|
c38e0c97 | 1 | ;;; bubbles.el --- Puzzle game for Emacs -*- coding: utf-8 -*- |
a79b55e5 | 2 | |
ba318903 | 3 | ;; Copyright (C) 2007-2014 Free Software Foundation, Inc. |
a79b55e5 | 4 | |
a79b55e5 | 5 | ;; Author: Ulf Jasper <ulf.jasper@web.de> |
a79b55e5 TTN |
6 | ;; URL: http://ulf.epplejasper.de/ |
7 | ;; Created: 5. Feb. 2007 | |
2f123a54 | 8 | ;; Keywords: games |
a79b55e5 | 9 | |
2f123a54 | 10 | ;; This file is part of GNU Emacs. |
a79b55e5 | 11 | |
b1fc2b50 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify |
a79b55e5 | 13 | ;; it under the terms of the GNU General Public License as published by |
b1fc2b50 GM |
14 | ;; the Free Software Foundation, either version 3 of the License, or |
15 | ;; (at your option) any later version. | |
a79b55e5 | 16 | |
2f123a54 TTN |
17 | ;; GNU Emacs is distributed in the hope that it will be useful, |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
a79b55e5 TTN |
21 | |
22 | ;; You should have received a copy of the GNU General Public License | |
b1fc2b50 | 23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
a79b55e5 TTN |
24 | |
25 | ;;; Commentary: | |
26 | ||
27 | ;; Bubbles is a puzzle game. Its goal is to remove as many bubbles as | |
28 | ;; possible in as few moves as possible. | |
29 | ||
30 | ;; Bubbles is an implementation of the "Same Game", similar to "Same | |
2f123a54 | 31 | ;; GNOME" and many others, see <http://en.wikipedia.org/wiki/SameGame>. |
a79b55e5 TTN |
32 | |
33 | ;; Installation | |
34 | ;; ------------ | |
35 | ||
865fe16f | 36 | ;; Add the following lines to your init file: |
a79b55e5 TTN |
37 | ;; (add-to-list 'load-path "/path/to/bubbles/") |
38 | ;; (autoload 'bubbles "bubbles" "Play Bubbles" t) | |
39 | ||
40 | ;; ====================================================================== | |
41 | ||
42 | ;;; History: | |
43 | ||
a4fcacde TTN |
44 | ;; 0.5 (2007-09-14) |
45 | ;; - Minor bugfixes. | |
46 | ||
a79b55e5 TTN |
47 | ;; 0.4 (2007-08-27) |
48 | ;; - Allow for undoing last move. | |
49 | ;; - Bonus for removing all bubbles. | |
50 | ;; - Speed improvements. | |
51 | ;; - Animation enhancements. | |
52 | ;; - Added `bubbles-mode-hook'. | |
53 | ;; - Fixes: Don't move point. | |
54 | ;; - New URL. | |
55 | ||
56 | ;; 0.3 (2007-03-11) | |
57 | ;; - Renamed shift modes and thus names of score files. All | |
3ed8598c | 58 | ;; high scores are lost, unless you rename the score files from |
a79b55e5 TTN |
59 | ;; bubbles-shift-... to bubbles-...! |
60 | ;; - Bugfixes: Check for successful image creation. | |
61 | ;; Disable menus and counter when game is over. | |
62 | ;; Tested with GNU Emacs 22.0.93 | |
63 | ||
64 | ;; 0.2 (2007-02-24) | |
65 | ;; - Introduced game themes. | |
66 | ;; - Introduced graphics themes (changeable while playing). | |
67 | ;; - Added menu. | |
68 | ;; - Customization: grid size, colors, chars, shift mode. | |
69 | ;; - More keybindings. | |
70 | ;; - Changed shift direction from to-right to to-left. | |
71 | ;; - Bugfixes: Don't remove single-bubble regions; | |
72 | ;; Animation glitches fixed. | |
73 | ;; Tested with GNU Emacs 22.0.93 and 21.4.1. | |
74 | ||
75 | ;; 0.1 (2007-02-11) | |
76 | ;; Initial release. Tested with GNU Emacs 22.0.93 and 21.4.1. | |
77 | ||
78 | ;; ====================================================================== | |
79 | ||
80 | ;;; Code: | |
81 | ||
a4fcacde | 82 | (defconst bubbles-version "0.5" "Version number of bubbles.el.") |
2f123a54 | 83 | |
a79b55e5 | 84 | (require 'gamegrid) |
a79b55e5 TTN |
85 | |
86 | ;; User options | |
87 | ||
88 | ;; Careful with that axe, Eugene! Order does matter in the custom | |
89 | ;; section below. | |
90 | ||
91 | (defcustom bubbles-game-theme | |
92 | 'easy | |
93 | "Overall game theme. | |
94 | The overall game theme specifies a grid size, a set of colors, | |
95 | and a shift mode." | |
96 | :type '(radio (const :tag "Easy" easy) | |
97 | (const :tag "Medium" medium) | |
98 | (const :tag "Difficult" difficult) | |
99 | (const :tag "Hard" hard) | |
100 | (const :tag "User defined" user-defined)) | |
101 | :group 'bubbles) | |
102 | ||
103 | (defun bubbles-set-game-easy () | |
104 | "Set game theme to 'easy'." | |
105 | (interactive) | |
106 | (setq bubbles-game-theme 'easy) | |
107 | (bubbles)) | |
108 | ||
109 | (defun bubbles-set-game-medium () | |
110 | "Set game theme to 'medium'." | |
111 | (interactive) | |
112 | (setq bubbles-game-theme 'medium) | |
113 | (bubbles)) | |
114 | ||
115 | (defun bubbles-set-game-difficult () | |
116 | "Set game theme to 'difficult'." | |
117 | (interactive) | |
118 | (setq bubbles-game-theme 'difficult) | |
119 | (bubbles)) | |
120 | ||
121 | (defun bubbles-set-game-hard () | |
122 | "Set game theme to 'hard'." | |
123 | (interactive) | |
124 | (setq bubbles-game-theme 'hard) | |
125 | (bubbles)) | |
126 | ||
127 | (defun bubbles-set-game-userdefined () | |
128 | "Set game theme to 'user-defined'." | |
129 | (interactive) | |
130 | (setq bubbles-game-theme 'user-defined) | |
131 | (bubbles)) | |
132 | ||
133 | (defgroup bubbles nil | |
134 | "Bubbles, a puzzle game." | |
135 | :group 'games) | |
136 | ||
137 | (defcustom bubbles-graphics-theme | |
138 | 'circles | |
139 | "Graphics theme. | |
140 | It is safe to choose a graphical theme. If Emacs cannot display | |
141 | images the `ascii' theme will be used." | |
142 | :type '(radio (const :tag "Circles" circles) | |
143 | (const :tag "Squares" squares) | |
144 | (const :tag "Diamonds" diamonds) | |
145 | (const :tag "Balls" balls) | |
146 | (const :tag "Emacs" emacs) | |
147 | (const :tag "ASCII (no images)" ascii)) | |
148 | :group 'bubbles) | |
149 | ||
150 | (defconst bubbles--grid-small '(10 . 10) | |
151 | "Predefined small bubbles grid.") | |
152 | ||
153 | (defconst bubbles--grid-medium '(15 . 10) | |
154 | "Predefined medium bubbles grid.") | |
155 | ||
156 | (defconst bubbles--grid-large '(20 . 15) | |
157 | "Predefined large bubbles grid.") | |
158 | ||
159 | (defconst bubbles--grid-huge '(30 . 20) | |
160 | "Predefined huge bubbles grid.") | |
161 | ||
162 | (defcustom bubbles-grid-size | |
163 | bubbles--grid-medium | |
164 | "Size of bubbles grid." | |
165 | :type `(radio (const :tag "Small" ,bubbles--grid-small) | |
166 | (const :tag "Medium" ,bubbles--grid-medium) | |
167 | (const :tag "Large" ,bubbles--grid-large) | |
168 | (const :tag "Huge" ,bubbles--grid-huge) | |
169 | (cons :tag "User defined" | |
170 | (integer :tag "Width") | |
171 | (integer :tag "Height"))) | |
172 | :group 'bubbles) | |
173 | ||
174 | (defconst bubbles--colors-2 '("orange" "violet") | |
175 | "Predefined bubbles color list with two colors.") | |
176 | ||
177 | (defconst bubbles--colors-3 '("lightblue" "palegreen" "pink") | |
178 | "Predefined bubbles color list with three colors.") | |
179 | ||
180 | (defconst bubbles--colors-4 '("firebrick" "sea green" "steel blue" "chocolate") | |
181 | "Predefined bubbles color list with four colors.") | |
182 | ||
183 | (defconst bubbles--colors-5 '("firebrick" "sea green" "steel blue" | |
184 | "sandy brown" "bisque3") | |
185 | "Predefined bubbles color list with five colors.") | |
186 | ||
187 | (defcustom bubbles-colors | |
188 | bubbles--colors-3 | |
189 | "List of bubble colors. | |
190 | The length of this list determines how many different bubble | |
191 | types are present." | |
192 | :type `(radio (const :tag "Red, darkgreen" ,bubbles--colors-2) | |
193 | (const :tag "Red, darkgreen, blue" ,bubbles--colors-3) | |
194 | (const :tag "Red, darkgreen, blue, orange" ,bubbles--colors-4) | |
195 | (const :tag "Red, darkgreen, blue, orange, violet" | |
196 | ,bubbles--colors-5) | |
197 | (repeat :tag "User defined" color)) | |
198 | :group 'bubbles) | |
199 | ||
200 | (defcustom bubbles-chars | |
c38e0c97 | 201 | '(?+ ?O ?# ?X ?. ?* ?& ?ยง) |
a79b55e5 TTN |
202 | "Characters used for bubbles. |
203 | Note that the actual number of different bubbles is determined by | |
204 | the number of colors, see `bubbles-colors'." | |
205 | :type '(repeat character) | |
206 | :group 'bubbles) | |
207 | ||
208 | (defcustom bubbles-shift-mode | |
209 | 'default | |
210 | "Shift mode. | |
e73da129 | 211 | Available modes are `shift-default' and `shift-always'." |
a79b55e5 TTN |
212 | :type '(radio (const :tag "Default" default) |
213 | (const :tag "Shifter" always) | |
9c61f806 | 214 | ;;(const :tag "Mega Shifter" mega) |
a79b55e5 TTN |
215 | ) |
216 | :group 'bubbles) | |
217 | ||
218 | (defcustom bubbles-mode-hook nil | |
219 | "Hook run by Bubbles mode." | |
220 | :group 'bubbles | |
221 | :type 'hook) | |
222 | ||
223 | (defun bubbles-customize () | |
224 | "Open customization buffer for bubbles." | |
225 | (interactive) | |
226 | (customize-group 'bubbles)) | |
227 | ||
228 | ;; ====================================================================== | |
229 | ;; internal variables | |
230 | ||
231 | (defvar bubbles--score 0 | |
232 | "Current Bubbles score.") | |
233 | ||
4fa51741 | 234 | (defvar bubbles--neighborhood-score 0 |
a7c29764 | 235 | "Score of active bubbles neighborhood.") |
a79b55e5 TTN |
236 | |
237 | (defvar bubbles--faces nil | |
238 | "List of currently used faces.") | |
239 | ||
240 | (defvar bubbles--playing nil | |
241 | "Play status indicator.") | |
242 | ||
243 | (defvar bubbles--empty-image nil | |
244 | "Image used for removed bubbles (empty grid cells).") | |
245 | ||
246 | (defvar bubbles--images nil | |
247 | "List of images for bubbles.") | |
248 | ||
249 | (defvar bubbles--images-ok nil | |
250 | "Indicate whether images have been created successfully.") | |
251 | ||
252 | (defvar bubbles--col-offset 0 | |
253 | "Horizontal offset for centering the bubbles grid.") | |
254 | ||
255 | (defvar bubbles--row-offset 0 | |
256 | "Vertical offset for centering the bubbles grid.") | |
257 | ||
258 | (defvar bubbles--save-data nil | |
259 | "List containing bubbles save data (SCORE BUFFERCONTENTS).") | |
260 | ||
261 | (defconst bubbles--image-template-circle | |
262 | "/* XPM */ | |
263 | static char * dot_xpm[] = { | |
264 | \"20 20 2 1\", | |
265 | \" c None\", | |
266 | \". c #FFFFFF\", | |
267 | \" ...... \", | |
268 | \" .......... \", | |
269 | \" .............. \", | |
270 | \" ................ \", | |
271 | \" ................ \", | |
272 | \" .................. \", | |
273 | \" .................. \", | |
274 | \"....................\", | |
275 | \"....................\", | |
276 | \"....................\", | |
277 | \"....................\", | |
278 | \"....................\", | |
279 | \"....................\", | |
280 | \" .................. \", | |
281 | \" .................. \", | |
282 | \" ................ \", | |
283 | \" ................ \", | |
284 | \" .............. \", | |
285 | \" .......... \", | |
286 | \" ...... \"};") | |
287 | ||
288 | (defconst bubbles--image-template-square | |
289 | "/* XPM */ | |
290 | static char * dot_xpm[] = { | |
291 | \"20 20 2 1\", | |
292 | \"0 c None\", | |
293 | \"1 c #FFFFFF\", | |
294 | \"00000000000000000000\", | |
295 | \"01111111111111111110\", | |
296 | \"01111111111111111110\", | |
297 | \"01111111111111111110\", | |
298 | \"01111111111111111110\", | |
299 | \"01111111111111111110\", | |
300 | \"01111111111111111110\", | |
301 | \"01111111111111111110\", | |
302 | \"01111111111111111110\", | |
303 | \"01111111111111111110\", | |
304 | \"01111111111111111110\", | |
305 | \"01111111111111111110\", | |
306 | \"01111111111111111110\", | |
307 | \"01111111111111111110\", | |
308 | \"01111111111111111110\", | |
309 | \"01111111111111111110\", | |
310 | \"01111111111111111110\", | |
311 | \"01111111111111111110\", | |
312 | \"01111111111111111110\", | |
313 | \"00000000000000000000\"};") | |
314 | ||
315 | (defconst bubbles--image-template-diamond | |
316 | "/* XPM */ | |
317 | static char * dot_xpm[] = { | |
318 | \"20 20 2 1\", | |
319 | \"0 c None\", | |
320 | \"1 c #FFFFFF\", | |
321 | \"00000000011000000000\", | |
322 | \"00000000111100000000\", | |
323 | \"00000001111110000000\", | |
324 | \"00000011111111000000\", | |
325 | \"00000111111111100000\", | |
326 | \"00001111111111110000\", | |
327 | \"00011111111111111000\", | |
328 | \"00111111111111111100\", | |
329 | \"01111111111111111110\", | |
330 | \"11111111111111111111\", | |
331 | \"01111111111111111110\", | |
332 | \"00111111111111111100\", | |
333 | \"00011111111111111000\", | |
334 | \"00001111111111110000\", | |
335 | \"00000111111111100000\", | |
336 | \"00000011111111000000\", | |
337 | \"00000001111110000000\", | |
338 | \"00000000111100000000\", | |
339 | \"00000000011000000000\", | |
340 | \"00000000000000000000\"};") | |
341 | ||
342 | (defconst bubbles--image-template-emacs | |
343 | "/* XPM */ | |
344 | static char * emacs_24_xpm[] = { | |
345 | \"24 24 129 2\", | |
346 | \" c None\", | |
347 | \". c #837DA4\", | |
348 | \"+ c #807AA0\", | |
349 | \"@ c #9894B2\", | |
350 | \"# c #CCCAD9\", | |
351 | \"$ c #C2C0D2\", | |
352 | \"% c #B6B3C9\", | |
353 | \"& c #A19DB9\", | |
354 | \"* c #8681A5\", | |
355 | \"= c #7D779B\", | |
356 | \"- c #B6B3C7\", | |
357 | \"; c #ABA7BE\", | |
358 | \"> c #9792AF\", | |
359 | \", c #AAA6BD\", | |
360 | \"' c #CBC9D7\", | |
361 | \") c #AAA7BE\", | |
362 | \"! c #908BAA\", | |
363 | \"~ c #797397\", | |
364 | \"{ c #948FAC\", | |
365 | \"] c #9A95B1\", | |
366 | \"^ c #EBEAEF\", | |
367 | \"/ c #F1F1F5\", | |
368 | \"( c #BCB9CB\", | |
369 | \"_ c #A9A5BD\", | |
370 | \": c #757093\", | |
371 | \"< c #918DA9\", | |
372 | \"[ c #DDDBE4\", | |
373 | \"} c #FFFFFF\", | |
374 | \"| c #EAE9EF\", | |
375 | \"1 c #A7A4BA\", | |
376 | \"2 c #716C8F\", | |
377 | \"3 c #8D89A5\", | |
378 | \"4 c #9C98B1\", | |
379 | \"5 c #DBDAE3\", | |
380 | \"6 c #A4A1B7\", | |
381 | \"7 c #6E698A\", | |
382 | \"8 c #8B87A1\", | |
383 | \"9 c #928EA7\", | |
384 | \"0 c #C5C3D1\", | |
385 | \"a c #F8F8F9\", | |
386 | \"b c #CCCAD6\", | |
387 | \"c c #A29FB4\", | |
388 | \"d c #6A6585\", | |
389 | \"e c #88849D\", | |
390 | \"f c #B5B2C2\", | |
391 | \"g c #F0F0F3\", | |
392 | \"h c #E1E0E6\", | |
393 | \"i c #A5A2B5\", | |
394 | \"j c #A09DB1\", | |
395 | \"k c #676281\", | |
396 | \"l c #85819A\", | |
397 | \"m c #9591A7\", | |
398 | \"n c #E1E0E5\", | |
399 | \"o c #F0EFF2\", | |
400 | \"p c #B3B0C0\", | |
401 | \"q c #9D9AAE\", | |
402 | \"r c #635F7C\", | |
403 | \"s c #827F96\", | |
404 | \"t c #9997AA\", | |
405 | \"u c #F7F7F9\", | |
406 | \"v c #C8C7D1\", | |
407 | \"w c #89869D\", | |
408 | \"x c #9B99AB\", | |
409 | \"y c #5F5B78\", | |
410 | \"z c #7F7C93\", | |
411 | \"A c #CFCDD6\", | |
412 | \"B c #B7B5C2\", | |
413 | \"C c #9996A9\", | |
414 | \"D c #5C5873\", | |
415 | \"E c #7A778D\", | |
416 | \"F c #F5F5F6\", | |
417 | \"G c #8E8C9E\", | |
418 | \"H c #7D798F\", | |
419 | \"I c #58546F\", | |
420 | \"J c #6C6981\", | |
421 | \"K c #D5D4DB\", | |
422 | \"L c #F5F4F6\", | |
423 | \"M c #9794A5\", | |
424 | \"N c #625F78\", | |
425 | \"O c #79768C\", | |
426 | \"P c #55516A\", | |
427 | \"Q c #605C73\", | |
428 | \"R c #CAC9D1\", | |
429 | \"S c #EAE9EC\", | |
430 | \"T c #B4B3BE\", | |
431 | \"U c #777488\", | |
432 | \"V c #514E66\", | |
433 | \"W c #DEDEE2\", | |
434 | \"X c #F4F4F5\", | |
435 | \"Y c #9D9BA9\", | |
436 | \"Z c #747185\", | |
437 | \"` c #4E4B62\", | |
438 | \" . c #DEDDE1\", | |
439 | \".. c #A6A5B0\", | |
440 | \"+. c #716F81\", | |
441 | \"@. c #4A475D\", | |
442 | \"#. c #A4A3AE\", | |
443 | \"$. c #F4F3F5\", | |
444 | \"%. c #777586\", | |
445 | \"&. c #6E6C7D\", | |
446 | \"*. c #464358\", | |
447 | \"=. c #514E62\", | |
448 | \"-. c #B9B8C0\", | |
449 | \";. c #D1D0D5\", | |
450 | \">. c #747282\", | |
451 | \",. c #6B6979\", | |
452 | \"'. c #434054\", | |
453 | \"). c #5A5769\", | |
454 | \"!. c #D0CFD4\", | |
455 | \"~. c #5B5869\", | |
456 | \"{. c #696676\", | |
457 | \"]. c #403D50\", | |
458 | \"^. c #DBDADE\", | |
459 | \"/. c #F3F3F4\", | |
460 | \"(. c #646271\", | |
461 | \"_. c #666473\", | |
462 | \":. c #3D3A4C\", | |
463 | \"<. c #555362\", | |
464 | \"[. c #9E9DA6\", | |
465 | \"}. c #9E9CA5\", | |
466 | \"|. c #646170\", | |
467 | \"1. c #393647\", | |
468 | \"2. c #514E5D\", | |
469 | \"3. c #83818C\", | |
470 | \"4. c #A8A7AE\", | |
471 | \"5. c #E6E6E8\", | |
472 | \"6. c #DAD9DC\", | |
473 | \"7. c #353343\", | |
474 | \"8. c #32303E\", | |
475 | \" . . . . . . . . . . . . . . . . . . \", | |
476 | \" + @ # $ % % % % % % % % % % % % % % & * + + \", | |
477 | \" = - ; > > > > > > > > , ' ) > > > > > > ! = \", | |
478 | \"~ ~ { { { { { { { { { { { ] ^ / ( { { { { _ ~ ~ \", | |
479 | \": : < < < < < < < < < < < < [ } } | < < < 1 : : \", | |
480 | \"2 2 3 3 3 3 3 3 3 3 3 3 4 5 } } } 5 3 3 3 6 2 2 \", | |
481 | \"7 7 8 8 8 8 8 8 8 8 9 0 a } } } b 8 8 8 8 c 7 7 \", | |
482 | \"d d e e e e e e e f g } } } h i e e e e e j d d \", | |
483 | \"k k l l l l l m n } } } o p l l l l l l l q k k \", | |
484 | \"r r s s s s t u } } } v w s s s s s s s s x r r \", | |
485 | \"y y z z z z A } } } B z z z z z z z z z z C y y \", | |
486 | \"D D D D D D E F } } G D D D D D D D D D D H D D \", | |
487 | \"I I I I I I I J K } L M N I I I I I I I I O I I \", | |
488 | \"P P P P P P Q R } } } S T P P P P P P P P U P P \", | |
489 | \"V V V V V V W } } X Y V V V V V V V V V V Z V V \", | |
490 | \"` ` ` ` ` ` .} } ..` ` ` ` ` ` ` ` ` ` ` +.` ` \", | |
491 | \"@.@.@.@.@.@.@.#.$.$.%.@.@.@.@.@.@.@.@.@.@.&.@.@.\", | |
492 | \"*.*.*.*.*.*.*.*.=.-.} ;.>.*.*.*.*.*.*.*.*.,.*.*.\", | |
493 | \"'.'.'.'.'.'.'.'.'.'.).!.} !.~.'.'.'.'.'.'.{.'.'.\", | |
494 | \"].].].].].].].].].].].].^.} /.(.].].].].]._.].].\", | |
495 | \":.:.:.:.:.:.:.:.:.:.<.[./.} } }.:.:.:.:.:.|.:.:.\", | |
496 | \" 1.1.1.1.1.1.1.1.2.3.4.5.6.3.1.1.1.1.1.1.1.1. \", | |
497 | \" 7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7. \", | |
498 | \" 8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8. \"};") | |
499 | ||
500 | (defconst bubbles--image-template-ball | |
501 | "/* XPM */ | |
502 | static char * dot3d_xpm[] = { | |
503 | \"20 20 190 2\", | |
504 | \" c None\", | |
505 | \". c #F9F6F6\", | |
506 | \"+ c #D6D0D0\", | |
507 | \"@ c #BFBBBB\", | |
508 | \"# c #AAA4A4\", | |
509 | \"$ c #ABAAAB\", | |
510 | \"% c #A8A8A8\", | |
511 | \"& c #A29D9D\", | |
512 | \"* c #B5B2B2\", | |
513 | \"= c #CDC9C9\", | |
514 | \"- c #D7D0D0\", | |
515 | \"; c #B3AFAF\", | |
516 | \"> c #B5B5B5\", | |
517 | \", c #B7B7B7\", | |
518 | \"' c #B8B8B8\", | |
519 | \") c #B6B6B6\", | |
520 | \"! c #B3B3B3\", | |
521 | \"~ c #AFAFAF\", | |
522 | \"{ c #A9A9A9\", | |
523 | \"] c #A2A2A2\", | |
524 | \"^ c #9C9A9A\", | |
525 | \"/ c #C9C5C5\", | |
526 | \"( c #FDFBFB\", | |
527 | \"_ c #C3BCBC\", | |
528 | \": c #BBBBBB\", | |
529 | \"< c #C0C0C0\", | |
530 | \"[ c #C3C2C2\", | |
531 | \"} c #C3C3C3\", | |
532 | \"| c #C2C2C2\", | |
533 | \"1 c #BEBEBE\", | |
534 | \"2 c #B9B9B9\", | |
535 | \"3 c #B2B2B2\", | |
536 | \"4 c #ABAAAA\", | |
537 | \"5 c #999999\", | |
538 | \"6 c #ACA7A7\", | |
539 | \"7 c #C2BBBB\", | |
540 | \"8 c #C5C5C5\", | |
541 | \"9 c #CACBCB\", | |
542 | \"0 c #CECECE\", | |
543 | \"a c #CFCFCF\", | |
544 | \"b c #CDCDCD\", | |
545 | \"c c #C8C9C9\", | |
546 | \"d c #9F9F9F\", | |
547 | \"e c #959595\", | |
548 | \"f c #A9A5A5\", | |
549 | \"g c #D5CFCE\", | |
550 | \"h c #BDBDBD\", | |
551 | \"i c #C6C6C6\", | |
552 | \"j c #D5D5D5\", | |
553 | \"k c #D9D9D9\", | |
554 | \"l c #DADADA\", | |
555 | \"m c #D8D8D8\", | |
556 | \"n c #D2D2D2\", | |
557 | \"o c #CBCBCB\", | |
558 | \"p c #A4A4A5\", | |
559 | \"q c #9A9A9A\", | |
560 | \"r c #8F8F8F\", | |
561 | \"s c #C3BFBF\", | |
562 | \"t c #AFACAB\", | |
563 | \"u c #CCCCCC\", | |
564 | \"v c #D6D6D6\", | |
565 | \"w c #DEDEDE\", | |
566 | \"x c #E4E4E4\", | |
567 | \"y c #E5E5E5\", | |
568 | \"z c #E2E2E2\", | |
569 | \"A c #DBDBDB\", | |
570 | \"B c #C9C8C8\", | |
571 | \"C c #A8A9A8\", | |
572 | \"D c #9D9E9D\", | |
573 | \"E c #929292\", | |
574 | \"F c #8A8888\", | |
575 | \"G c #D3CECE\", | |
576 | \"H c #B0B0B0\", | |
577 | \"I c #D1D1D1\", | |
578 | \"J c #DCDCDC\", | |
579 | \"K c #E6E6E6\", | |
580 | \"L c #EEEEEE\", | |
581 | \"M c #F1F1F0\", | |
582 | \"N c #EBEBEB\", | |
583 | \"O c #D7D7D8\", | |
584 | \"P c #ABABAB\", | |
585 | \"Q c #A0A0A0\", | |
586 | \"R c #949494\", | |
587 | \"S c #898989\", | |
588 | \"T c #C0BDBD\", | |
589 | \"U c #B9B6B6\", | |
590 | \"V c #B1B1B1\", | |
591 | \"W c #BCBCBC\", | |
592 | \"X c #C8C8C8\", | |
593 | \"Y c #D3D3D3\", | |
594 | \"Z c #DFDFDE\", | |
595 | \"` c #EAEAEA\", | |
596 | \" . c #F5F5F5\", | |
597 | \".. c #FAFAFA\", | |
598 | \"+. c #F1F1F1\", | |
599 | \"@. c #CECFCF\", | |
600 | \"#. c #ACACAC\", | |
601 | \"$. c #A1A1A1\", | |
602 | \"%. c #8A8A8A\", | |
603 | \"&. c #9B9999\", | |
604 | \"*. c #C7C7C7\", | |
605 | \"=. c #DDDDDD\", | |
606 | \"-. c #E8E8E8\", | |
607 | \";. c #F2F2F2\", | |
608 | \">. c #898A89\", | |
609 | \",. c #7A7878\", | |
610 | \"'. c #AEAEAE\", | |
611 | \"). c #C4C4C4\", | |
612 | \"!. c #CBCBCA\", | |
613 | \"~. c #AAAAAA\", | |
614 | \"{. c #939393\", | |
615 | \"]. c #888888\", | |
616 | \"^. c #7C7C7C\", | |
617 | \"/. c #AAAAAB\", | |
618 | \"(. c #BFBFBF\", | |
619 | \"_. c #C9C9C9\", | |
620 | \":. c #DFDEDF\", | |
621 | \"<. c #A6A6A6\", | |
622 | \"[. c #9B9B9B\", | |
623 | \"}. c #909191\", | |
624 | \"|. c #858586\", | |
625 | \"1. c #797979\", | |
626 | \"2. c #989494\", | |
627 | \"3. c #A5A6A5\", | |
628 | \"4. c #B9B9B8\", | |
629 | \"5. c #C1C1C1\", | |
630 | \"6. c #CFCFCE\", | |
631 | \"7. c #979797\", | |
632 | \"8. c #8D8D8D\", | |
633 | \"9. c #828282\", | |
634 | \"0. c #747171\", | |
635 | \"a. c #ADAAAA\", | |
636 | \"b. c #A9A8A9\", | |
637 | \"c. c #B8B9B9\", | |
638 | \"d. c #A5A5A5\", | |
639 | \"e. c #9C9C9C\", | |
640 | \"f. c #7E7E7D\", | |
641 | \"g. c #929191\", | |
642 | \"h. c #C9C4C4\", | |
643 | \"i. c #989898\", | |
644 | \"j. c #ADADAD\", | |
645 | \"k. c #9D9D9D\", | |
646 | \"l. c #8C8C8C\", | |
647 | \"m. c #787878\", | |
648 | \"n. c #B8B6B6\", | |
649 | \"o. c #939191\", | |
650 | \"p. c #A5A5A6\", | |
651 | \"q. c #ABABAA\", | |
652 | \"r. c #A8A8A9\", | |
653 | \"s. c #A3A3A3\", | |
654 | \"t. c #858585\", | |
655 | \"u. c #757474\", | |
656 | \"v. c #C5C1C1\", | |
657 | \"w. c #969696\", | |
658 | \"x. c #9B9B9C\", | |
659 | \"y. c #A4A4A4\", | |
660 | \"z. c #9E9E9E\", | |
661 | \"A. c #939394\", | |
662 | \"B. c #7D7D7D\", | |
663 | \"C. c #747474\", | |
664 | \"D. c #B7B5B5\", | |
665 | \"E. c #A5A1A1\", | |
666 | \"F. c #919191\", | |
667 | \"G. c #9A9999\", | |
668 | \"H. c #838383\", | |
669 | \"I. c #757575\", | |
670 | \"J. c #939090\", | |
671 | \"K. c #A29E9E\", | |
672 | \"L. c #868686\", | |
673 | \"M. c #8D8D8C\", | |
674 | \"N. c #8E8E8E\", | |
675 | \"O. c #8D8D8E\", | |
676 | \"P. c #8B8C8C\", | |
677 | \"Q. c #848485\", | |
678 | \"R. c #7F7F80\", | |
679 | \"S. c #7A7A7A\", | |
680 | \"T. c #737373\", | |
681 | \"U. c #929090\", | |
682 | \"V. c #828080\", | |
683 | \"W. c #818181\", | |
684 | \"X. c #808080\", | |
685 | \"Y. c #7E7E7E\", | |
686 | \"Z. c #737272\", | |
687 | \"`. c #B7B4B4\", | |
688 | \" + c #BCBABA\", | |
689 | \".+ c #959494\", | |
690 | \"++ c #747172\", | |
691 | \"@+ c #767676\", | |
692 | \"#+ c #6F6D6D\", | |
693 | \"$+ c #8F8E8E\", | |
694 | \" . + @ # $ % & * = . \", | |
695 | \" - ; > , ' ) ! ~ { ] ^ / \", | |
696 | \" ( _ > : < [ } | 1 2 3 4 ] 5 6 ( \", | |
697 | \" 7 ) 1 8 9 0 a b c | : 3 { d e f \", | |
698 | \" g ! h i 0 j k l m n o | 2 ~ p q r s \", | |
699 | \". t ' | u v w x y z A n B 1 ! C D E F . \", | |
700 | \"G H : i I J K L M N z O b | ) P Q R S T \", | |
701 | \"U V W X Y Z ` ...+.y l @.} ' #.$.e %.&.\", | |
702 | \"& H W *.n =.-.;. .L x k 0 [ , #.Q e >.,.\", | |
703 | \"] '.2 ).a k z -.` K w j !.< > ~.d {.].^.\", | |
704 | \"d /.> (._.I k =.:.J v 0 8 : V <.[.}.|.1.\", | |
705 | \"2.3.~ 4.5._.6.n Y I u i 1 > P $.7.8.9.0.\", | |
706 | \"a.d b.V c.(.).*.X i | h ) '.d.e.E ].f.g.\", | |
707 | \"h.i.$.C ~ > 2 W W : ' ! j.d.k.e l.9.m.n.\", | |
708 | \". o.i.d p.q.'.H V H j.r.s.k.e 8.t.^.u.. \", | |
709 | \" v.r w.x.Q s.d.d.y.] z.5 A.8.t.B.C.D. \", | |
710 | \" E.l.F.e i.G.q 5 7.{.r %.H.^.I.J. \", | |
711 | \" ( K.L.%.M.N.N.O.P.S Q.R.S.T.U.( \", | |
712 | \" @ V.W.H.H.9.X.Y.S.I.Z.`. \", | |
713 | \" . +.+++@+C.#+$+D.. \"};") | |
714 | ||
715 | ;; ====================================================================== | |
716 | ;; Functions | |
717 | ||
718 | (defsubst bubbles--grid-width () | |
719 | "Return the grid width for the current game theme." | |
a464a6c7 SM |
720 | (car (pcase bubbles-game-theme |
721 | (`easy | |
a79b55e5 | 722 | bubbles--grid-small) |
a464a6c7 | 723 | (`medium |
a79b55e5 | 724 | bubbles--grid-medium) |
a464a6c7 | 725 | (`difficult |
a79b55e5 | 726 | bubbles--grid-large) |
a464a6c7 | 727 | (`hard |
a79b55e5 | 728 | bubbles--grid-huge) |
a464a6c7 | 729 | (`user-defined |
a79b55e5 TTN |
730 | bubbles-grid-size)))) |
731 | ||
732 | (defsubst bubbles--grid-height () | |
733 | "Return the grid height for the current game theme." | |
a464a6c7 SM |
734 | (cdr (pcase bubbles-game-theme |
735 | (`easy | |
a79b55e5 | 736 | bubbles--grid-small) |
a464a6c7 | 737 | (`medium |
a79b55e5 | 738 | bubbles--grid-medium) |
a464a6c7 | 739 | (`difficult |
a79b55e5 | 740 | bubbles--grid-large) |
a464a6c7 | 741 | (`hard |
a79b55e5 | 742 | bubbles--grid-huge) |
a464a6c7 | 743 | (`user-defined |
a79b55e5 TTN |
744 | bubbles-grid-size)))) |
745 | ||
746 | (defsubst bubbles--colors () | |
747 | "Return the color list for the current game theme." | |
a464a6c7 SM |
748 | (pcase bubbles-game-theme |
749 | (`easy | |
a79b55e5 | 750 | bubbles--colors-2) |
a464a6c7 | 751 | (`medium |
a79b55e5 | 752 | bubbles--colors-3) |
a464a6c7 | 753 | (`difficult |
a79b55e5 | 754 | bubbles--colors-4) |
a464a6c7 | 755 | (`hard |
a79b55e5 | 756 | bubbles--colors-5) |
a464a6c7 | 757 | (`user-defined |
a79b55e5 TTN |
758 | bubbles-colors))) |
759 | ||
760 | (defsubst bubbles--shift-mode () | |
761 | "Return the shift mode for the current game theme." | |
a464a6c7 SM |
762 | (pcase bubbles-game-theme |
763 | (`easy | |
a79b55e5 | 764 | 'default) |
a464a6c7 | 765 | (`medium |
a79b55e5 | 766 | 'default) |
a464a6c7 | 767 | (`difficult |
a79b55e5 | 768 | 'always) |
a464a6c7 | 769 | (`hard |
a79b55e5 | 770 | 'always) |
a464a6c7 | 771 | (`user-defined |
a79b55e5 TTN |
772 | bubbles-shift-mode))) |
773 | ||
774 | (defun bubbles-save-settings () | |
775 | "Save current customization settings." | |
776 | (interactive) | |
777 | (custom-set-variables | |
778 | (list 'bubbles-game-theme `(quote ,bubbles-game-theme) t) | |
779 | (list 'bubbles-graphics-theme `(quote ,bubbles-graphics-theme) t)) | |
780 | (customize-save-customized)) | |
781 | ||
782 | (defsubst bubbles--empty-char () | |
783 | "The character used for removed bubbles (empty grid cells)." | |
e73da129 | 784 | ?\s) |
a79b55e5 TTN |
785 | |
786 | (defun bubbles-set-graphics-theme-ascii () | |
787 | "Set graphics theme to `ascii'." | |
788 | (interactive) | |
789 | (setq bubbles-graphics-theme 'ascii) | |
790 | (bubbles--update-faces-or-images)) | |
791 | ||
792 | (defun bubbles-set-graphics-theme-circles () | |
793 | "Set graphics theme to `circles'." | |
794 | (interactive) | |
795 | (setq bubbles-graphics-theme 'circles) | |
796 | (bubbles--initialize-images) | |
797 | (bubbles--update-faces-or-images)) | |
798 | ||
799 | (defun bubbles-set-graphics-theme-squares () | |
800 | "Set graphics theme to `squares'." | |
801 | (interactive) | |
802 | (setq bubbles-graphics-theme 'squares) | |
803 | (bubbles--initialize-images) | |
804 | (bubbles--update-faces-or-images)) | |
805 | ||
806 | (defun bubbles-set-graphics-theme-diamonds () | |
807 | "Set graphics theme to `diamonds'." | |
808 | (interactive) | |
809 | (setq bubbles-graphics-theme 'diamonds) | |
810 | (bubbles--initialize-images) | |
811 | (bubbles--update-faces-or-images)) | |
812 | ||
813 | (defun bubbles-set-graphics-theme-balls () | |
814 | "Set graphics theme to `balls'." | |
815 | (interactive) | |
816 | (setq bubbles-graphics-theme 'balls) | |
817 | (bubbles--initialize-images) | |
818 | (bubbles--update-faces-or-images)) | |
819 | ||
820 | (defun bubbles-set-graphics-theme-emacs () | |
821 | "Set graphics theme to `emacs'." | |
822 | (interactive) | |
823 | (setq bubbles-graphics-theme 'emacs) | |
824 | (bubbles--initialize-images) | |
825 | (bubbles--update-faces-or-images)) | |
826 | ||
a79b55e5 | 827 | ;; game theme menu |
a4fcacde TTN |
828 | (defvar bubbles-game-theme-menu |
829 | (let ((menu (make-sparse-keymap "Game Theme"))) | |
830 | (define-key menu [bubbles-set-game-userdefined] | |
831 | (list 'menu-item "User defined" 'bubbles-set-game-userdefined | |
832 | :button '(:radio . (eq bubbles-game-theme 'user-defined)))) | |
833 | (define-key menu [bubbles-set-game-hard] | |
834 | (list 'menu-item "Hard" 'bubbles-set-game-hard | |
835 | :button '(:radio . (eq bubbles-game-theme 'hard)))) | |
836 | (define-key menu [bubbles-set-game-difficult] | |
837 | (list 'menu-item "Difficult" 'bubbles-set-game-difficult | |
838 | :button '(:radio . (eq bubbles-game-theme 'difficult)))) | |
839 | (define-key menu [bubbles-set-game-medium] | |
840 | (list 'menu-item "Medium" 'bubbles-set-game-medium | |
841 | :button '(:radio . (eq bubbles-game-theme 'medium)))) | |
842 | (define-key menu [bubbles-set-game-easy] | |
843 | (list 'menu-item "Easy" 'bubbles-set-game-easy | |
844 | :button '(:radio . (eq bubbles-game-theme 'easy)))) | |
845 | menu) | |
846 | "Map for bubbles game theme menu.") | |
a79b55e5 TTN |
847 | |
848 | ;; graphics theme menu | |
a4fcacde TTN |
849 | (defvar bubbles-graphics-theme-menu |
850 | (let ((menu (make-sparse-keymap "Graphics Theme"))) | |
851 | (define-key menu [bubbles-set-graphics-theme-ascii] | |
852 | (list 'menu-item "ASCII" 'bubbles-set-graphics-theme-ascii | |
853 | :button '(:radio . (eq bubbles-graphics-theme 'ascii)))) | |
854 | (define-key menu [bubbles-set-graphics-theme-emacs] | |
855 | (list 'menu-item "Emacs" 'bubbles-set-graphics-theme-emacs | |
856 | :button '(:radio . (eq bubbles-graphics-theme 'emacs)))) | |
857 | (define-key menu [bubbles-set-graphics-theme-balls] | |
858 | (list 'menu-item "Balls" 'bubbles-set-graphics-theme-balls | |
859 | :button '(:radio . (eq bubbles-graphics-theme 'balls)))) | |
860 | (define-key menu [bubbles-set-graphics-theme-diamonds] | |
861 | (list 'menu-item "Diamonds" 'bubbles-set-graphics-theme-diamonds | |
862 | :button '(:radio . (eq bubbles-graphics-theme 'diamonds)))) | |
863 | (define-key menu [bubbles-set-graphics-theme-squares] | |
864 | (list 'menu-item "Squares" 'bubbles-set-graphics-theme-squares | |
865 | :button '(:radio . (eq bubbles-graphics-theme 'squares)))) | |
866 | (define-key menu [bubbles-set-graphics-theme-circles] | |
867 | (list 'menu-item "Circles" 'bubbles-set-graphics-theme-circles | |
868 | :button '(:radio . (eq bubbles-graphics-theme 'circles)))) | |
869 | menu) | |
870 | "Map for bubbles graphics theme menu.") | |
a79b55e5 TTN |
871 | |
872 | ;; menu | |
a4fcacde TTN |
873 | (defvar bubbles-menu |
874 | (let ((menu (make-sparse-keymap "Bubbles"))) | |
875 | (define-key menu [bubbles-quit] | |
876 | (list 'menu-item "Quit" 'bubbles-quit)) | |
877 | (define-key menu [bubbles] | |
878 | (list 'menu-item "New game" 'bubbles)) | |
879 | (define-key menu [bubbles-separator-1] | |
880 | '("--")) | |
881 | (define-key menu [bubbles-save-settings] | |
882 | (list 'menu-item "Save all settings" 'bubbles-save-settings)) | |
883 | (define-key menu [bubbles-customize] | |
884 | (list 'menu-item "Edit all settings" 'bubbles-customize)) | |
885 | (define-key menu [bubbles-game-theme-menu] | |
886 | (list 'menu-item "Game Theme" bubbles-game-theme-menu)) | |
887 | (define-key menu [bubbles-graphics-theme-menu] | |
888 | (list 'menu-item "Graphics Theme" bubbles-graphics-theme-menu | |
889 | :enable 'bubbles--playing)) | |
890 | (define-key menu [bubbles-separator-2] | |
891 | '("--")) | |
892 | (define-key menu [bubbles-undo] | |
893 | (list 'menu-item "Undo last move" 'bubbles-undo | |
894 | :enable '(and bubbles--playing (listp buffer-undo-list)))) | |
895 | menu) | |
896 | "Map for bubbles menu.") | |
897 | ||
898 | ;; bubbles mode map | |
899 | (defvar bubbles-mode-map | |
900 | (let ((map (make-sparse-keymap 'bubbles-mode-map))) | |
901 | ;; (suppress-keymap map t) | |
902 | (define-key map "q" 'bubbles-quit) | |
903 | (define-key map "\n" 'bubbles-plop) | |
904 | (define-key map " " 'bubbles-plop) | |
905 | (define-key map [double-down-mouse-1] 'bubbles-plop) | |
906 | (define-key map [mouse-2] 'bubbles-plop) | |
907 | (define-key map "\C-m" 'bubbles-plop) | |
908 | (define-key map "u" 'bubbles-undo) | |
909 | (define-key map "p" 'previous-line) | |
910 | (define-key map "n" 'next-line) | |
911 | (define-key map "f" 'forward-char) | |
912 | (define-key map "b" 'backward-char) | |
913 | ;; bind menu to mouse | |
914 | (define-key map [down-mouse-3] bubbles-menu) | |
915 | ;; Put menu in menu-bar | |
916 | (define-key map [menu-bar Bubbles] (cons "Bubbles" bubbles-menu)) | |
917 | map) | |
918 | "Mode map for bubbles.") | |
a79b55e5 | 919 | |
e73da129 | 920 | (define-derived-mode bubbles-mode nil "Bubbles" |
a79b55e5 TTN |
921 | "Major mode for playing bubbles. |
922 | \\{bubbles-mode-map}" | |
121656e9 JB |
923 | (setq buffer-read-only t |
924 | show-trailing-whitespace nil) | |
a4fcacde | 925 | (buffer-disable-undo) |
a4fcacde TTN |
926 | (force-mode-line-update) |
927 | (redisplay) | |
4fa51741 | 928 | (add-hook 'post-command-hook 'bubbles--mark-neighborhood t t)) |
a79b55e5 TTN |
929 | |
930 | ;;;###autoload | |
931 | (defun bubbles () | |
b74d9506 CY |
932 | "Play Bubbles game. |
933 | \\<bubbles-mode-map> | |
934 | The goal is to remove all bubbles with as few moves as possible. | |
935 | \\[bubbles-plop] on a bubble removes that bubble and all | |
936 | connected bubbles of the same color. Unsupported bubbles fall | |
937 | down, and columns that do not contain any bubbles suck the | |
938 | columns on its right towards the left. | |
939 | ||
940 | \\[bubbles-set-game-easy] sets the difficulty to easy. | |
941 | \\[bubbles-set-game-medium] sets the difficulty to medium. | |
942 | \\[bubbles-set-game-difficult] sets the difficulty to difficult. | |
943 | \\[bubbles-set-game-hard] sets the difficulty to hard." | |
a79b55e5 TTN |
944 | (interactive) |
945 | (switch-to-buffer (get-buffer-create "*bubbles*")) | |
946 | (when (or (not bubbles--playing) | |
947 | (y-or-n-p "Start new game? ")) | |
948 | (setq bubbles--save-data nil) | |
949 | (setq bubbles--playing t) | |
950 | (bubbles--initialize))) | |
951 | ||
952 | (defun bubbles-quit () | |
953 | "Quit Bubbles." | |
954 | (interactive) | |
955 | (message "bubbles-quit") | |
956 | (bury-buffer)) | |
957 | ||
aa360da1 GM |
958 | (declare-function image-size "image.c" (spec &optional pixels frame)) |
959 | ||
a79b55e5 TTN |
960 | (defun bubbles--compute-offsets () |
961 | "Update horizontal and vertical offsets for centering the bubbles grid. | |
962 | Set `bubbles--col-offset' and `bubbles--row-offset'." | |
963 | (cond ((and (display-images-p) | |
964 | bubbles--images-ok | |
965 | (not (eq bubbles-graphics-theme 'ascii)) | |
966 | (fboundp 'window-inside-pixel-edges)) | |
967 | ;; compute offset in units of pixels | |
968 | (let ((bubbles--image-size | |
969 | (car (image-size (car bubbles--images) t)))) | |
970 | (setq bubbles--col-offset | |
971 | (list | |
972 | (max 0 (/ (- (nth 2 (window-inside-pixel-edges)) | |
973 | (nth 0 (window-inside-pixel-edges)) | |
974 | (* ( + bubbles--image-size 2) ;; margin | |
975 | (bubbles--grid-width))) 2)))) | |
976 | (setq bubbles--row-offset | |
977 | (list | |
978 | (max 0 (/ (- (nth 3 (window-inside-pixel-edges)) | |
979 | (nth 1 (window-inside-pixel-edges)) | |
980 | (* (+ bubbles--image-size 1) ;; margin | |
981 | (bubbles--grid-height))) 2)))))) | |
982 | (t | |
983 | ;; compute offset in units of chars | |
984 | (setq bubbles--col-offset | |
985 | (max 0 (/ (- (window-width) | |
986 | (bubbles--grid-width)) 2))) | |
987 | (setq bubbles--row-offset | |
988 | (max 0 (/ (- (window-height) | |
989 | (bubbles--grid-height) 2) 2)))))) | |
990 | ||
991 | (defun bubbles--remove-overlays () | |
992 | "Remove all overlays." | |
993 | (if (fboundp 'remove-overlays) | |
994 | (remove-overlays))) | |
995 | ||
996 | (defun bubbles--initialize () | |
997 | "Initialize Bubbles game." | |
998 | (bubbles--initialize-faces) | |
999 | (bubbles--initialize-images) | |
1000 | (bubbles--remove-overlays) | |
1001 | ||
1002 | (switch-to-buffer (get-buffer-create "*bubbles*")) | |
1003 | (bubbles--compute-offsets) | |
1004 | (let ((inhibit-read-only t)) | |
1005 | (set-buffer-modified-p nil) | |
1006 | (erase-buffer) | |
1007 | (insert " ") | |
cf10b349 SM |
1008 | (put-text-property (point-min) (point) |
1009 | 'display | |
1010 | (cons 'space (list :height bubbles--row-offset))) | |
a79b55e5 TTN |
1011 | (insert "\n") |
1012 | (let ((max-char (length (bubbles--colors)))) | |
1013 | (dotimes (i (bubbles--grid-height)) | |
1014 | (let ((p (point))) | |
1015 | (insert " ") | |
cf10b349 SM |
1016 | (put-text-property p (point) |
1017 | 'display | |
1018 | (cons 'space (list :width bubbles--col-offset)))) | |
a79b55e5 TTN |
1019 | (dotimes (j (bubbles--grid-width)) |
1020 | (let* ((index (random max-char)) | |
1021 | (char (nth index bubbles-chars))) | |
1022 | (insert char) | |
1023 | (add-text-properties (1- (point)) (point) (list 'index index)))) | |
1024 | (insert "\n")) | |
1025 | (insert "\n ") | |
cf10b349 SM |
1026 | (put-text-property (1- (point)) (point) |
1027 | 'display | |
1028 | (cons 'space (list :width bubbles--col-offset)))) | |
a79b55e5 TTN |
1029 | (put-text-property (point-min) (point-max) 'pointer 'arrow)) |
1030 | (bubbles-mode) | |
1031 | (bubbles--reset-score) | |
1032 | (bubbles--update-faces-or-images) | |
a4fcacde TTN |
1033 | (bubbles--goto 0 0) |
1034 | (setq buffer-undo-list t) | |
1035 | (force-mode-line-update) | |
1036 | (redisplay)) | |
a79b55e5 TTN |
1037 | |
1038 | (defun bubbles--initialize-faces () | |
1039 | "Prepare faces for playing `bubbles'." | |
1040 | (copy-face 'default 'bubbles--highlight-face) | |
1041 | (set-face-background 'bubbles--highlight-face "#8080f4") | |
1042 | (when (display-color-p) | |
1043 | (setq bubbles--faces | |
1044 | (mapcar (lambda (color) | |
1045 | (let ((fname (intern (format "bubbles--face-%s" color)))) | |
1046 | (unless (facep fname) | |
1047 | (copy-face 'default fname) | |
1048 | (set-face-foreground fname color)) | |
1049 | fname)) | |
1050 | (bubbles--colors))))) | |
1051 | ||
1052 | (defsubst bubbles--row (pos) | |
1053 | "Return row of point POS." | |
1054 | (save-excursion | |
1055 | (goto-char pos) | |
1056 | (beginning-of-line) | |
1057 | (1- (count-lines (point-min) (point))))) | |
1058 | ||
1059 | (defsubst bubbles--col (pos) | |
1060 | "Return column of point POS." | |
1061 | (save-excursion | |
1062 | (goto-char pos) | |
1063 | (1- (current-column)))) | |
1064 | ||
1065 | (defun bubbles--goto (row col) | |
1066 | "Move point to bubble at coordinates ROW and COL." | |
1067 | (if (or (< row 0) | |
1068 | (< col 0) | |
1069 | (>= row (bubbles--grid-height)) | |
1070 | (>= col (bubbles--grid-width))) | |
1071 | ;; Error! return nil | |
1072 | nil | |
1073 | ;; go | |
1074 | (goto-char (point-min)) | |
1075 | (forward-line (1+ row)) | |
1076 | (forward-char (1+ col)) | |
1077 | (point))) | |
1078 | ||
1079 | (defun bubbles--char-at (row col) | |
1080 | "Return character at bubble ROW and COL." | |
1081 | (save-excursion | |
1082 | (if (bubbles--goto row col) | |
1083 | (char-after (point)) | |
1084 | nil))) | |
1085 | ||
4fa51741 | 1086 | (defun bubbles--mark-direct-neighbors (row col char) |
a7c29764 | 1087 | "Mark direct neighbors of bubble at ROW COL with same CHAR." |
a79b55e5 TTN |
1088 | (save-excursion |
1089 | (let ((count 0)) | |
1090 | (when (and (bubbles--goto row col) | |
1091 | (eq char (char-after (point))) | |
1092 | (not (get-text-property (point) 'active))) | |
1093 | (add-text-properties (point) (1+ (point)) | |
1094 | '(active t face 'bubbles--highlight-face)) | |
1095 | (setq count (+ 1 | |
4fa51741 PE |
1096 | (bubbles--mark-direct-neighbors row (1+ col) char) |
1097 | (bubbles--mark-direct-neighbors row (1- col) char) | |
1098 | (bubbles--mark-direct-neighbors (1+ row) col char) | |
1099 | (bubbles--mark-direct-neighbors (1- row) col char)))) | |
a79b55e5 TTN |
1100 | count))) |
1101 | ||
4fa51741 | 1102 | (defun bubbles--mark-neighborhood (&optional pos) |
a7c29764 | 1103 | "Mark neighborhood of point. |
a79b55e5 TTN |
1104 | Use optional parameter POS instead of point if given." |
1105 | (when bubbles--playing | |
1106 | (unless pos (setq pos (point))) | |
30213927 GM |
1107 | (with-demoted-errors "Bubbles: Internal error %s" |
1108 | (let ((char (char-after pos)) | |
1109 | (inhibit-read-only t) | |
1110 | (row (bubbles--row (point))) | |
1111 | (col (bubbles--col (point)))) | |
1112 | (add-text-properties (point-min) (point-max) | |
1113 | '(face default active nil)) | |
1114 | (let ((count 0)) | |
1115 | (when (and row col (not (eq char (bubbles--empty-char)))) | |
4fa51741 | 1116 | (setq count (bubbles--mark-direct-neighbors row col char)) |
30213927 GM |
1117 | (unless (> count 1) |
1118 | (add-text-properties (point-min) (point-max) | |
1119 | '(face default active nil)) | |
1120 | (setq count 0))) | |
4fa51741 | 1121 | (bubbles--update-neighborhood-score count)) |
30213927 GM |
1122 | (put-text-property (point-min) (point-max) 'pointer 'arrow) |
1123 | (bubbles--update-faces-or-images) | |
1124 | (sit-for 0))))) | |
a79b55e5 | 1125 | |
4fa51741 | 1126 | (defun bubbles--neighborhood-available () |
a7c29764 | 1127 | "Return t if another valid neighborhood is available." |
a79b55e5 TTN |
1128 | (catch 'found |
1129 | (save-excursion | |
1130 | (dotimes (i (bubbles--grid-height)) | |
1131 | (dotimes (j (bubbles--grid-width)) | |
1132 | (let ((c (bubbles--char-at i j))) | |
1133 | (if (and (not (eq c (bubbles--empty-char))) | |
1134 | (or (eq c (bubbles--char-at (1+ i) j)) | |
1135 | (eq c (bubbles--char-at i (1+ j))))) | |
1136 | (throw 'found t))))) | |
1137 | nil))) | |
1138 | ||
1139 | (defun bubbles--count () | |
1140 | "Count remaining bubbles." | |
1141 | (let ((count 0)) | |
1142 | (save-excursion | |
1143 | (dotimes (i (bubbles--grid-height)) | |
1144 | (dotimes (j (bubbles--grid-width)) | |
1145 | (let ((c (bubbles--char-at i j))) | |
1146 | (if (not (eq c (bubbles--empty-char))) | |
1147 | (setq count (1+ count))))))) | |
1148 | count)) | |
1149 | ||
1150 | (defun bubbles--reset-score () | |
1151 | "Reset bubbles score." | |
4fa51741 | 1152 | (setq bubbles--neighborhood-score 0 |
a79b55e5 TTN |
1153 | bubbles--score 0) |
1154 | (bubbles--update-score)) | |
1155 | ||
1156 | (defun bubbles--update-score () | |
e73da129 | 1157 | "Calculate and display new bubbles score." |
4fa51741 | 1158 | (setq bubbles--score (+ bubbles--score bubbles--neighborhood-score)) |
a79b55e5 TTN |
1159 | (bubbles--show-scores)) |
1160 | ||
4fa51741 | 1161 | (defun bubbles--update-neighborhood-score (size) |
a7c29764 | 1162 | "Calculate and display score of active neighborhood from its SIZE." |
a79b55e5 | 1163 | (if (> size 1) |
4fa51741 PE |
1164 | (setq bubbles--neighborhood-score (expt (- size 1) 2)) |
1165 | (setq bubbles--neighborhood-score 0)) | |
a79b55e5 TTN |
1166 | (bubbles--show-scores)) |
1167 | ||
1168 | (defun bubbles--show-scores () | |
1169 | "Display current scores." | |
1170 | (save-excursion | |
1171 | (goto-char (or (next-single-property-change (point-min) 'status) | |
1172 | (point-max))) | |
1173 | (let ((inhibit-read-only t) | |
1174 | (pos (point))) | |
1175 | (delete-region (point) (point-max)) | |
4fa51741 | 1176 | (insert (format "Selected: %4d\n" bubbles--neighborhood-score)) |
a79b55e5 | 1177 | (insert " ") |
cf10b349 SM |
1178 | (put-text-property (1- (point)) (point) |
1179 | 'display | |
1180 | (cons 'space (list :width bubbles--col-offset))) | |
a79b55e5 TTN |
1181 | (insert (format "Score: %4d" bubbles--score)) |
1182 | (put-text-property pos (point) 'status t)))) | |
1183 | ||
1184 | (defun bubbles--game-over () | |
1185 | "Finish bubbles game." | |
1186 | (bubbles--update-faces-or-images) | |
1187 | (setq bubbles--playing nil | |
1188 | bubbles--save-data nil) | |
1189 | ;; add bonus if all bubbles were removed | |
1190 | (when (= 0 (bubbles--count)) | |
1191 | (setq bubbles--score (+ bubbles--score (* (bubbles--grid-height) | |
1192 | (bubbles--grid-width)))) | |
1193 | (bubbles--show-scores)) | |
1194 | ;; Game over message | |
1195 | (goto-char (point-max)) | |
1196 | (let* ((inhibit-read-only t)) | |
1197 | (insert "\n ") | |
cf10b349 SM |
1198 | (put-text-property (1- (point)) (point) |
1199 | 'display | |
1200 | (cons 'space (list :width bubbles--col-offset))) | |
a79b55e5 TTN |
1201 | (insert "Game Over!")) |
1202 | ;; save score | |
1203 | (gamegrid-add-score (format "bubbles-%s-%d-%d-%d-scores" | |
1204 | (symbol-name (bubbles--shift-mode)) | |
1205 | (length (bubbles--colors)) | |
1206 | (bubbles--grid-width) (bubbles--grid-height)) | |
1207 | bubbles--score)) | |
1208 | ||
1209 | (defun bubbles-plop () | |
1210 | "Remove active bubbles region." | |
1211 | (interactive) | |
1212 | (when (and bubbles--playing | |
4fa51741 | 1213 | (> bubbles--neighborhood-score 0)) |
a79b55e5 | 1214 | (setq bubbles--save-data (list bubbles--score (buffer-string))) |
a79b55e5 | 1215 | (let ((inhibit-read-only t)) |
4fa51741 | 1216 | ;; blank out current neighborhood |
a79b55e5 TTN |
1217 | (let ((row (bubbles--row (point))) |
1218 | (col (bubbles--col (point)))) | |
1219 | (goto-char (point-max)) | |
1220 | (while (not (bobp)) | |
1221 | (backward-char) | |
1222 | (while (get-text-property (point) 'active) | |
1223 | (delete-char 1) | |
1224 | (insert (bubbles--empty-char)) | |
1225 | (add-text-properties (1- (point)) (point) (list 'removed t | |
1226 | 'index -1)))) | |
1227 | (bubbles--goto row col)) | |
1228 | ;; show new score | |
1229 | (bubbles--update-score) | |
1230 | ;; update display and wait | |
1231 | (bubbles--update-faces-or-images) | |
1232 | (sit-for 0) | |
1233 | (sleep-for 0.2) | |
1234 | (discard-input) | |
1235 | ;; drop down | |
1236 | (let ((something-dropped nil)) | |
1237 | (save-excursion | |
1238 | (dotimes (i (bubbles--grid-height)) | |
1239 | (dotimes (j (bubbles--grid-width)) | |
1240 | (bubbles--goto i j) | |
1241 | (while (get-text-property (point) 'removed) | |
1242 | (setq something-dropped (or (bubbles--shift 'top i j) | |
1243 | something-dropped)))))) | |
1244 | ;; update display and wait | |
1245 | (bubbles--update-faces-or-images) | |
1246 | (when something-dropped | |
1247 | (sit-for 0))) | |
1248 | (discard-input) | |
1249 | ;; shift to left | |
1250 | (put-text-property (point-min) (point-max) 'removed nil) | |
1251 | (save-excursion | |
1252 | (goto-char (point-min)) | |
1253 | (let ((removed-string (format "%c" (bubbles--empty-char)))) | |
1254 | (while (search-forward removed-string nil t) | |
1255 | (put-text-property (1- (point)) (point) 'removed t)))) | |
1256 | (let ((shifted nil)) | |
1257 | (cond ((eq (bubbles--shift-mode) 'always) | |
1258 | (save-excursion | |
1259 | (dotimes (i (bubbles--grid-height)) | |
1260 | (dotimes (j (bubbles--grid-width)) | |
1261 | (bubbles--goto i j) | |
1262 | (while (get-text-property (point) 'removed) | |
a4fcacde TTN |
1263 | (setq shifted (or (bubbles--shift 'right i j) |
1264 | shifted)))))) | |
a79b55e5 TTN |
1265 | (bubbles--update-faces-or-images) |
1266 | (sleep-for 0.5)) | |
1267 | (t ;; default shift-mode | |
1268 | (save-excursion | |
1269 | (dotimes (j (bubbles--grid-width)) | |
1270 | (bubbles--goto (1- (bubbles--grid-height)) j) | |
1271 | (let ((shifted-cols 0)) | |
1272 | (while (get-text-property (point) 'removed) | |
1273 | (setq shifted-cols (1+ shifted-cols)) | |
1274 | (bubbles--shift 'right (1- (bubbles--grid-height)) j)) | |
1275 | (dotimes (k shifted-cols) | |
1276 | (let ((i (- (bubbles--grid-height) 2))) | |
1277 | (while (>= i 0) | |
a4fcacde TTN |
1278 | (setq shifted (or (bubbles--shift 'right i j) |
1279 | shifted)) | |
a79b55e5 TTN |
1280 | (setq i (1- i)))))))))) |
1281 | (when shifted | |
1282 | ;;(sleep-for 0.5) | |
1283 | (bubbles--update-faces-or-images) | |
1284 | (sit-for 0))) | |
1285 | (put-text-property (point-min) (point-max) 'removed nil) | |
4fa51741 | 1286 | (unless (bubbles--neighborhood-available) |
a4fcacde TTN |
1287 | (bubbles--game-over))) |
1288 | ;; undo | |
1289 | (setq buffer-undo-list '((apply bubbles-undo . nil))) | |
1290 | (force-mode-line-update) | |
1291 | (redisplay))) | |
a79b55e5 TTN |
1292 | |
1293 | (defun bubbles-undo () | |
1294 | "Undo last move." | |
1295 | (interactive) | |
1296 | (when bubbles--save-data | |
1297 | (let ((inhibit-read-only t) | |
1298 | (pos (point))) | |
1299 | (erase-buffer) | |
1300 | (insert (cadr bubbles--save-data)) | |
1301 | (bubbles--update-faces-or-images) | |
1302 | (setq bubbles--score (car bubbles--save-data)) | |
a4fcacde TTN |
1303 | (goto-char pos)) |
1304 | (setq buffer-undo-list t) | |
1305 | (force-mode-line-update) | |
1306 | (redisplay))) | |
a79b55e5 TTN |
1307 | |
1308 | (defun bubbles--shift (from row col) | |
1309 | "Move bubbles FROM one side to position ROW COL. | |
1310 | Return t if new char is non-empty." | |
1311 | (save-excursion | |
1312 | (when (bubbles--goto row col) | |
121656e9 | 1313 | (let ((char-new (bubbles--empty-char)) |
a79b55e5 TTN |
1314 | (removed nil) |
1315 | (trow row) | |
1316 | (tcol col) | |
1317 | (index -1)) | |
1318 | (cond ((eq from 'top) | |
1319 | (setq trow (1- row))) | |
1320 | ((eq from 'left) | |
1321 | (setq tcol (1- col))) | |
1322 | ((eq from 'right) | |
1323 | (setq tcol (1+ col)))) | |
1324 | (save-excursion | |
1325 | (when (bubbles--goto trow tcol) | |
1326 | (setq char-new (char-after (point))) | |
1327 | (setq removed (get-text-property (point) 'removed)) | |
1328 | (setq index (get-text-property (point) 'index)) | |
1329 | (bubbles--shift from trow tcol))) | |
1330 | (insert char-new) | |
1331 | (delete-char 1) | |
1332 | (add-text-properties (1- (point)) (point) (list 'index index | |
1333 | 'removed removed)) | |
1334 | (not (eq char-new (bubbles--empty-char))))))) | |
1335 | ||
1336 | (defun bubbles--initialize-images () | |
1337 | "Prepare images for playing `bubbles'." | |
1338 | (when (and (display-images-p) | |
1339 | (not (eq bubbles-graphics-theme 'ascii))) | |
a464a6c7 SM |
1340 | (let ((template (pcase bubbles-graphics-theme |
1341 | (`circles bubbles--image-template-circle) | |
1342 | (`balls bubbles--image-template-ball) | |
1343 | (`squares bubbles--image-template-square) | |
1344 | (`diamonds bubbles--image-template-diamond) | |
1345 | (`emacs bubbles--image-template-emacs)))) | |
a79b55e5 TTN |
1346 | (setq bubbles--empty-image |
1347 | (create-image (replace-regexp-in-string | |
1348 | "^\"\\(.*\\)\t.*c .*\",$" | |
45169e8d | 1349 | "\"\\1\tc None\"," template) |
a79b55e5 TTN |
1350 | 'xpm t |
1351 | ;;:mask 'heuristic | |
1352 | :margin '(2 . 1))) | |
1353 | (setq bubbles--images | |
1354 | (mapcar (lambda (color) | |
1355 | (let* ((rgb (color-values color)) | |
1356 | (red (nth 0 rgb)) | |
1357 | (green (nth 1 rgb)) | |
1358 | (blue (nth 2 rgb))) | |
1359 | (with-temp-buffer | |
1360 | (insert template) | |
1361 | (goto-char (point-min)) | |
1362 | (re-search-forward | |
1363 | "^\"[0-9]+ [0-9]+ \\(.*?\\) .*\",$" nil t) | |
1364 | (goto-char (point-min)) | |
1365 | (while (re-search-forward | |
1366 | "^\"\\(.*\\)\t.*c \\(#.*\\)\",$" nil t) | |
1367 | (let* ((crgb (color-values (match-string 2))) | |
1368 | (r (nth 0 crgb)) | |
1369 | (g (nth 1 crgb)) | |
1370 | (b (nth 2 crgb)) | |
1371 | (brightness (/ (+ r g b) 3.0 256 256)) | |
9e0d4f9e | 1372 | (val (sin (* brightness (/ float-pi 2)))) |
a79b55e5 TTN |
1373 | (rr (* red val)) |
1374 | (gg (* green val)) | |
1375 | (bb (* blue val)) | |
1376 | ;;(rr (/ (+ red r) 2)) | |
1377 | ;;(gg (/ (+ green g) 2)) | |
1378 | ;;(bb (/ (+ blue b) 2)) | |
1379 | (color (format "#%02x%02x%02x" | |
1380 | (/ rr 256) (/ gg 256) | |
1381 | (/ bb 256)))) | |
1382 | (replace-match (format "\"\\1\tc %s\"," | |
1383 | (upcase color))))) | |
1384 | (create-image (buffer-string) 'xpm t | |
1385 | :margin '(2 . 1) | |
1386 | ;;:mask 'heuristic | |
1387 | )))) | |
1388 | (bubbles--colors)))) | |
1389 | ;; check images | |
1390 | (setq bubbles--images-ok bubbles--empty-image) | |
1391 | (mapc (lambda (elt) | |
1392 | (setq bubbles--images-ok (and bubbles--images-ok elt))) | |
1393 | bubbles--images))) | |
1394 | ||
1395 | (defun bubbles--update-faces-or-images () | |
1396 | "Update faces and/or images, depending on graphics mode." | |
1397 | (bubbles--set-faces) | |
1398 | (bubbles--show-images)) | |
1399 | ||
1400 | (defun bubbles--set-faces () | |
1401 | "Update faces in the bubbles buffer." | |
1402 | (unless (and (display-images-p) | |
1403 | bubbles--images-ok | |
1404 | (not (eq bubbles-graphics-theme 'ascii))) | |
1405 | (when (display-color-p) | |
1406 | (save-excursion | |
1407 | (let ((inhibit-read-only t)) | |
1408 | (dotimes (i (bubbles--grid-height)) | |
1409 | (dotimes (j (bubbles--grid-width)) | |
1410 | (bubbles--goto i j) | |
121656e9 JB |
1411 | (let ((face (nth (get-text-property (point) 'index) |
1412 | bubbles--faces))) | |
a79b55e5 TTN |
1413 | (when (get-text-property (point) 'active) |
1414 | (set-face-foreground 'bubbles--highlight-face "#ff0000") | |
1415 | (setq face 'bubbles--highlight-face)) | |
1416 | (put-text-property (point) (1+ (point)) | |
1417 | 'face face))))))))) | |
1418 | ||
1419 | (defun bubbles--show-images () | |
1420 | "Update images in the bubbles buffer." | |
1421 | (bubbles--remove-overlays) | |
1422 | (if (and (display-images-p) | |
1423 | bubbles--images-ok | |
1424 | (not (eq bubbles-graphics-theme 'ascii))) | |
1425 | (save-excursion | |
1426 | (goto-char (point-min)) | |
1427 | (forward-line 1) | |
121656e9 | 1428 | (let ((inhibit-read-only t)) |
a79b55e5 TTN |
1429 | (dotimes (i (bubbles--grid-height)) |
1430 | (dotimes (j (bubbles--grid-width)) | |
1431 | (forward-char 1) | |
a4fcacde | 1432 | (let ((index (or (get-text-property (point) 'index) -1))) |
a79b55e5 TTN |
1433 | (let ((img bubbles--empty-image)) |
1434 | (if (>= index 0) | |
1435 | (setq img (nth index bubbles--images))) | |
1436 | (put-text-property (point) (1+ (point)) | |
1437 | 'display (cons img nil))))) | |
1438 | (forward-line 1)))) | |
1439 | (save-excursion | |
1440 | (let ((inhibit-read-only t)) | |
1441 | (goto-char (point-min)) | |
1442 | (while (not (eobp)) | |
1443 | (let ((disp-prop (get-text-property (point) 'display))) | |
1444 | (if (and (listp disp-prop) | |
1445 | (listp (car disp-prop)) | |
1446 | (eq (caar disp-prop) 'image)) | |
1447 | (put-text-property (point) (1+ (point)) 'display nil)) | |
1448 | (forward-char 1))) | |
1449 | (put-text-property (point-min) (point-max) 'pointer 'arrow))))) | |
1450 | ||
1451 | (provide 'bubbles) | |
1452 | ||
1453 | ;;; bubbles.el ends here |