1 ;;; bubbles.el --- Puzzle game for Emacs.
3 ;; Copyright (C) 2007 Free Software Foundation, Inc.
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; URL: http://ulf.epplejasper.de/
7 ;; Created: 5. Feb. 2007
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 3, or (at your option)
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.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
29 ;; Bubbles is a puzzle game. Its goal is to remove as many bubbles as
30 ;; possible in as few moves as possible.
32 ;; Bubbles is an implementation of the "Same Game", similar to "Same
33 ;; GNOME" and many others, see <http://en.wikipedia.org/wiki/SameGame>.
38 ;; Add the following lines to your Emacs startup file (`~/.emacs').
39 ;; (add-to-list 'load-path "/path/to/bubbles/")
40 ;; (autoload 'bubbles "bubbles" "Play Bubbles" t)
42 ;; ======================================================================
47 ;; - Allow for undoing last move.
48 ;; - Bonus for removing all bubbles.
49 ;; - Speed improvements.
50 ;; - Animation enhancements.
51 ;; - Added `bubbles-mode-hook'.
52 ;; - Fixes: Don't move point.
56 ;; - Renamed shift modes and thus names of score files. All
57 ;; highscores are lost, unless you rename the score files from
58 ;; bubbles-shift-... to bubbles-...!
59 ;; - Bugfixes: Check for successful image creation.
60 ;; Disable menus and counter when game is over.
61 ;; Tested with GNU Emacs 22.0.93
64 ;; - Introduced game themes.
65 ;; - Introduced graphics themes (changeable while playing).
67 ;; - Customization: grid size, colors, chars, shift mode.
68 ;; - More keybindings.
69 ;; - Changed shift direction from to-right to to-left.
70 ;; - Bugfixes: Don't remove single-bubble regions;
71 ;; Animation glitches fixed.
72 ;; Tested with GNU Emacs 22.0.93 and 21.4.1.
75 ;; Initial release. Tested with GNU Emacs 22.0.93 and 21.4.1.
77 ;; ======================================================================
81 (defconst bubbles-version
"0.4" "Version number of bubbles.el.")
88 ;; Careful with that axe, Eugene! Order does matter in the custom
91 (defcustom bubbles-game-theme
94 The overall game theme specifies a grid size, a set of colors,
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
))
103 (defun bubbles-set-game-easy ()
104 "Set game theme to 'easy'."
106 (setq bubbles-game-theme
'easy
)
109 (defun bubbles-set-game-medium ()
110 "Set game theme to 'medium'."
112 (setq bubbles-game-theme
'medium
)
115 (defun bubbles-set-game-difficult ()
116 "Set game theme to 'difficult'."
118 (setq bubbles-game-theme
'difficult
)
121 (defun bubbles-set-game-hard ()
122 "Set game theme to 'hard'."
124 (setq bubbles-game-theme
'hard
)
127 (defun bubbles-set-game-userdefined ()
128 "Set game theme to 'user-defined'."
130 (setq bubbles-game-theme
'user-defined
)
133 (defgroup bubbles nil
134 "Bubbles, a puzzle game."
137 (defcustom bubbles-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
))
150 (defconst bubbles--grid-small
'(10 .
10)
151 "Predefined small bubbles grid.")
153 (defconst bubbles--grid-medium
'(15 .
10)
154 "Predefined medium bubbles grid.")
156 (defconst bubbles--grid-large
'(20 .
15)
157 "Predefined large bubbles grid.")
159 (defconst bubbles--grid-huge
'(30 .
20)
160 "Predefined huge bubbles grid.")
162 (defcustom bubbles-grid-size
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")))
174 (defconst bubbles--colors-2
'("orange" "violet")
175 "Predefined bubbles color list with two colors.")
177 (defconst bubbles--colors-3
'("lightblue" "palegreen" "pink")
178 "Predefined bubbles color list with three colors.")
180 (defconst bubbles--colors-4
'("firebrick" "sea green" "steel blue" "chocolate")
181 "Predefined bubbles color list with four colors.")
183 (defconst bubbles--colors-5
'("firebrick" "sea green" "steel blue"
184 "sandy brown" "bisque3")
185 "Predefined bubbles color list with five colors.")
187 (defcustom bubbles-colors
189 "List of bubble colors.
190 The length of this list determines how many different bubble
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"
197 (repeat :tag
"User defined" color
))
200 (defcustom bubbles-chars
201 '(?
+ ?O ?
# ?X ?. ?
* ?
& ?ยง
)
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
)
208 (defcustom bubbles-shift-mode
211 Available modes are `shift-default' and`shift-always'."
212 :type
'(radio (const :tag
"Default" default
)
213 (const :tag
"Shifter" always
)
214 ;;(const :tag "Mega Shifter" 'mega)
218 (defcustom bubbles-mode-hook nil
219 "Hook run by Bubbles mode."
223 (defun bubbles-customize ()
224 "Open customization buffer for bubbles."
226 (customize-group 'bubbles
))
228 ;; ======================================================================
229 ;; internal variables
231 (defvar bubbles--score
0
232 "Current Bubbles score.")
234 (defvar bubbles--neighbourhood-score
0
235 "Score of active bubbles neighbourhood.")
237 (defvar bubbles--faces nil
238 "List of currently used faces.")
240 (defvar bubbles--playing nil
241 "Play status indicator.")
243 (defvar bubbles--empty-image nil
244 "Image used for removed bubbles (empty grid cells).")
246 (defvar bubbles--images nil
247 "List of images for bubbles.")
249 (defvar bubbles--images-ok nil
250 "Indicate whether images have been created successfully.")
252 (defvar bubbles--col-offset
0
253 "Horizontal offset for centering the bubbles grid.")
255 (defvar bubbles--row-offset
0
256 "Vertical offset for centering the bubbles grid.")
258 (defvar bubbles--save-data nil
259 "List containing bubbles save data (SCORE BUFFERCONTENTS).")
261 (defconst bubbles--image-template-circle
263 static char * dot_xpm[] = {
269 \" .............. \",
270 \" ................ \",
271 \" ................ \",
272 \" .................. \",
273 \" .................. \",
274 \"....................\",
275 \"....................\",
276 \"....................\",
277 \"....................\",
278 \"....................\",
279 \"....................\",
280 \" .................. \",
281 \" .................. \",
282 \" ................ \",
283 \" ................ \",
284 \" .............. \",
288 (defconst bubbles--image-template-square
290 static char * dot_xpm[] = {
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\"};")
315 (defconst bubbles--image-template-diamond
317 static char * dot_xpm[] = {
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\"};")
342 (defconst bubbles--image-template-emacs
344 static char * emacs_24_xpm[] = {
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. \"};")
500 (defconst bubbles--image-template-ball
502 static char * dot3d_xpm[] = {
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.. \"};")
715 ;; ======================================================================
718 (defsubst bubbles--grid-width
()
719 "Return the grid width for the current game theme."
720 (car (case bubbles-game-theme
724 bubbles--grid-medium
)
730 bubbles-grid-size
))))
732 (defsubst bubbles--grid-height
()
733 "Return the grid height for the current game theme."
734 (cdr (case bubbles-game-theme
738 bubbles--grid-medium
)
744 bubbles-grid-size
))))
746 (defsubst bubbles--colors
()
747 "Return the color list for the current game theme."
748 (case bubbles-game-theme
760 (defsubst bubbles--shift-mode
()
761 "Return the shift mode for the current game theme."
762 (case bubbles-game-theme
772 bubbles-shift-mode
)))
774 (defun bubbles-save-settings ()
775 "Save current customization settings."
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))
782 (defsubst bubbles--empty-char
()
783 "The character used for removed bubbles (empty grid cells)."
786 (defun bubbles-set-graphics-theme-ascii ()
787 "Set graphics theme to `ascii'."
789 (setq bubbles-graphics-theme
'ascii
)
790 (bubbles--update-faces-or-images))
792 (defun bubbles-set-graphics-theme-circles ()
793 "Set graphics theme to `circles'."
795 (setq bubbles-graphics-theme
'circles
)
796 (bubbles--initialize-images)
797 (bubbles--update-faces-or-images))
799 (defun bubbles-set-graphics-theme-squares ()
800 "Set graphics theme to `squares'."
802 (setq bubbles-graphics-theme
'squares
)
803 (bubbles--initialize-images)
804 (bubbles--update-faces-or-images))
806 (defun bubbles-set-graphics-theme-diamonds ()
807 "Set graphics theme to `diamonds'."
809 (setq bubbles-graphics-theme
'diamonds
)
810 (bubbles--initialize-images)
811 (bubbles--update-faces-or-images))
813 (defun bubbles-set-graphics-theme-balls ()
814 "Set graphics theme to `balls'."
816 (setq bubbles-graphics-theme
'balls
)
817 (bubbles--initialize-images)
818 (bubbles--update-faces-or-images))
820 (defun bubbles-set-graphics-theme-emacs ()
821 "Set graphics theme to `emacs'."
823 (setq bubbles-graphics-theme
'emacs
)
824 (bubbles--initialize-images)
825 (bubbles--update-faces-or-images))
828 (defvar bubbles-mode-map
829 (make-keymap 'bubbles-mode-map
))
830 (define-key bubbles-mode-map
"q" 'bubbles-quit
)
831 (define-key bubbles-mode-map
"\n" 'bubbles-plop
)
832 (define-key bubbles-mode-map
" " 'bubbles-plop
)
833 (define-key bubbles-mode-map
[double-down-mouse-1
] 'bubbles-plop
)
834 (define-key bubbles-mode-map
[mouse-2
] 'bubbles-plop
)
835 (define-key bubbles-mode-map
"\C-m" 'bubbles-plop
)
836 (define-key bubbles-mode-map
"u" 'bubbles-undo
)
837 (define-key bubbles-mode-map
"p" 'previous-line
)
838 (define-key bubbles-mode-map
"n" 'next-line
)
839 (define-key bubbles-mode-map
"f" 'forward-char
)
840 (define-key bubbles-mode-map
"b" 'backward-char
)
844 (defvar bubbles-game-theme-menu
(make-sparse-keymap "Game Theme"))
845 (define-key bubbles-game-theme-menu
[bubbles-set-game-userdefined
]
846 (list 'menu-item
"User defined" 'bubbles-set-game-userdefined
847 :button
'(:radio .
(eq bubbles-game-theme
'user-defined
))))
848 (define-key bubbles-game-theme-menu
[bubbles-set-game-hard
]
849 (list 'menu-item
"Hard" 'bubbles-set-game-hard
850 :button
'(:radio .
(eq bubbles-game-theme
'hard
))))
851 (define-key bubbles-game-theme-menu
[bubbles-set-game-difficult
]
852 (list 'menu-item
"Difficult" 'bubbles-set-game-difficult
853 :button
'(:radio .
(eq bubbles-game-theme
'difficult
))))
854 (define-key bubbles-game-theme-menu
[bubbles-set-game-medium
]
855 (list 'menu-item
"Medium" 'bubbles-set-game-medium
856 :button
'(:radio .
(eq bubbles-game-theme
'medium
))))
857 (define-key bubbles-game-theme-menu
[bubbles-set-game-easy
]
858 (list 'menu-item
"Easy" 'bubbles-set-game-easy
859 :button
'(:radio .
(eq bubbles-game-theme
'easy
))))
861 ;; graphics theme menu
862 (defvar bubbles-graphics-theme-menu
(make-sparse-keymap "Graphics Theme"))
863 (define-key bubbles-graphics-theme-menu
[bubbles-set-graphics-theme-ascii
]
864 (list 'menu-item
"ASCII" 'bubbles-set-graphics-theme-ascii
865 :button
'(:radio .
(eq bubbles-graphics-theme
'ascii
))))
866 (define-key bubbles-graphics-theme-menu
[bubbles-set-graphics-theme-emacs
]
867 (list 'menu-item
"Emacs" 'bubbles-set-graphics-theme-emacs
868 :button
'(:radio .
(eq bubbles-graphics-theme
'emacs
))))
869 (define-key bubbles-graphics-theme-menu
[bubbles-set-graphics-theme-balls
]
870 (list 'menu-item
"Balls" 'bubbles-set-graphics-theme-balls
871 :button
'(:radio .
(eq bubbles-graphics-theme
'balls
))))
872 (define-key bubbles-graphics-theme-menu
[bubbles-set-graphics-theme-diamonds
]
873 (list 'menu-item
"Diamonds" 'bubbles-set-graphics-theme-diamonds
874 :button
'(:radio .
(eq bubbles-graphics-theme
'diamonds
))))
875 (define-key bubbles-graphics-theme-menu
[bubbles-set-graphics-theme-squares
]
876 (list 'menu-item
"Squares" 'bubbles-set-graphics-theme-squares
877 :button
'(:radio .
(eq bubbles-graphics-theme
'squares
))))
878 (define-key bubbles-graphics-theme-menu
[bubbles-set-graphics-theme-circles
]
879 (list 'menu-item
"Circles" 'bubbles-set-graphics-theme-circles
880 :button
'(:radio .
(eq bubbles-graphics-theme
'circles
))))
883 (defvar bubbles-menu
(make-sparse-keymap "Bubbles"))
884 (define-key bubbles-menu
[bubbles-quit
]
885 (list 'menu-item
"Quit" 'bubbles-quit
))
886 (define-key bubbles-menu
[bubbles]
887 (list 'menu-item "New game" 'bubbles))
888 (define-key bubbles-menu [bubbles-separator-1]
890 (define-key bubbles-menu [bubbles-save-settings]
891 (list 'menu-item "Save all settings" 'bubbles-save-settings))
892 (define-key bubbles-menu [bubbles-customize]
893 (list 'menu-item "Edit all settings" 'bubbles-customize))
894 (define-key bubbles-menu [bubbles-game-theme-menu]
895 (list 'menu-item "Game Theme" bubbles-game-theme-menu))
896 (define-key bubbles-menu [bubbles-graphics-theme-menu]
897 (list 'menu-item "Graphics Theme" bubbles-graphics-theme-menu
898 :enable 'bubbles--playing))
899 (define-key bubbles-menu [bubbles-separator-2]
901 (define-key bubbles-menu [bubbles-undo]
902 (list 'menu-item "Undo last move" 'bubbles-undo
903 :enable '(and bubbles--playing bubbles--save-data)))
905 ;; bind menu to mouse
906 (define-key bubbles-mode-map [down-mouse-3] bubbles-menu)
907 ;; Put menu in menu-bar
908 (define-key bubbles-mode-map [menu-bar Bubbles]
909 (cons "Bubbles" bubbles-menu))
911 (defun bubbles-mode ()
912 "Major mode for playing bubbles.
913 \\{bubbles-mode-map}"
914 (kill-all-local-variables)
915 (use-local-map bubbles-mode-map)
916 (setq major-mode 'bubbles-mode)
917 (setq mode-name "Bubbles")
918 (setq buffer-read-only t)
920 (add-hook 'post-command-hook 'bubbles--mark-neighbourhood t t)
921 (run-hooks 'bubbles-mode-hook))
927 (switch-to-buffer (get-buffer-create "*bubbles*"))
928 (when (or (not bubbles--playing)
929 (y-or-n-p "Start new game? "))
930 (setq bubbles--save-data nil)
931 (setq bubbles--playing t)
932 (bubbles--initialize)))
934 (defun bubbles-quit ()
937 (message "bubbles-quit")
940 (defun bubbles--compute-offsets ()
941 "Update horizontal and vertical offsets for centering the bubbles grid.
942 Set `bubbles--col-offset' and `bubbles--row-offset'."
943 (cond ((and (display-images-p)
945 (not (eq bubbles-graphics-theme 'ascii))
946 (fboundp 'window-inside-pixel-edges))
947 ;; compute offset in units of pixels
948 (let ((bubbles--image-size
949 (car (image-size (car bubbles--images) t))))
950 (setq bubbles--col-offset
952 (max 0 (/ (- (nth 2 (window-inside-pixel-edges))
953 (nth 0 (window-inside-pixel-edges))
954 (* ( + bubbles--image-size 2) ;; margin
955 (bubbles--grid-width))) 2))))
956 (setq bubbles--row-offset
958 (max 0 (/ (- (nth 3 (window-inside-pixel-edges))
959 (nth 1 (window-inside-pixel-edges))
960 (* (+ bubbles--image-size 1) ;; margin
961 (bubbles--grid-height))) 2))))))
963 ;; compute offset in units of chars
964 (setq bubbles--col-offset
965 (max 0 (/ (- (window-width)
966 (bubbles--grid-width)) 2)))
967 (setq bubbles--row-offset
968 (max 0 (/ (- (window-height)
969 (bubbles--grid-height) 2) 2))))))
971 (defun bubbles--remove-overlays ()
972 "Remove all overlays."
973 (if (fboundp 'remove-overlays)
976 (defun bubbles--initialize ()
977 "Initialize Bubbles game."
978 (bubbles--initialize-faces)
979 (bubbles--initialize-images)
980 (bubbles--remove-overlays)
982 (switch-to-buffer (get-buffer-create "*bubbles*"))
983 (bubbles--compute-offsets)
984 (let ((inhibit-read-only t))
985 (set-buffer-modified-p nil)
989 (point-min) (point) (list 'intangible t 'display
991 (list :height bubbles--row-offset))))
993 (let ((max-char (length (bubbles--colors))))
994 (dotimes (i (bubbles--grid-height))
998 p (point) (list 'intangible t
999 'display (cons 'space
1001 bubbles--col-offset)))))
1002 (dotimes (j (bubbles--grid-width))
1003 (let* ((index (random max-char))
1004 (char (nth index bubbles-chars)))
1006 (add-text-properties (1- (point)) (point) (list 'index index))))
1009 (add-text-properties
1010 (1- (point)) (point) (list 'intangible t 'display
1012 (list :width bubbles--col-offset)))))
1013 (put-text-property (point-min) (point-max) 'pointer 'arrow))
1015 (bubbles--reset-score)
1016 (bubbles--update-faces-or-images)
1017 (bubbles--goto 0 0))
1019 (defun bubbles--initialize-faces ()
1020 "Prepare faces for playing `bubbles'."
1021 (copy-face 'default 'bubbles--highlight-face)
1022 (set-face-background 'bubbles--highlight-face "#8080f4")
1023 (when (display-color-p)
1024 (setq bubbles--faces
1025 (mapcar (lambda (color)
1026 (let ((fname (intern (format "bubbles--face-%s" color))))
1027 (unless (facep fname)
1028 (copy-face 'default fname)
1029 (set-face-foreground fname color))
1031 (bubbles--colors)))))
1033 (defsubst bubbles--row (pos)
1034 "Return row of point POS."
1038 (1- (count-lines (point-min) (point)))))
1040 (defsubst bubbles--col (pos)
1041 "Return column of point POS."
1044 (1- (current-column))))
1046 (defun bubbles--goto (row col)
1047 "Move point to bubble at coordinates ROW and COL."
1050 (>= row (bubbles--grid-height))
1051 (>= col (bubbles--grid-width)))
1052 ;; Error! return nil
1055 (goto-char (point-min))
1056 (forward-line (1+ row))
1057 (forward-char (1+ col))
1060 (defun bubbles--char-at (row col)
1061 "Return character at bubble ROW and COL."
1063 (if (bubbles--goto row col)
1064 (char-after (point))
1067 (defun bubbles--mark-direct-neighbours (row col char)
1068 "Mark direct neighbours of bubble at ROW COL with same CHAR."
1071 (when (and (bubbles--goto row col)
1072 (eq char (char-after (point)))
1073 (not (get-text-property (point) 'active)))
1074 (add-text-properties (point) (1+ (point))
1075 '(active t face 'bubbles--highlight-face))
1077 (bubbles--mark-direct-neighbours row (1+ col) char)
1078 (bubbles--mark-direct-neighbours row (1- col) char)
1079 (bubbles--mark-direct-neighbours (1+ row) col char)
1080 (bubbles--mark-direct-neighbours (1- row) col char))))
1083 (defun bubbles--mark-neighbourhood (&optional pos)
1084 "Mark neighbourhood of point.
1085 Use optional parameter POS instead of point if given."
1086 (when bubbles--playing
1087 (unless pos (setq pos (point)))
1089 (let ((char (char-after pos))
1090 (inhibit-read-only t)
1091 (row (bubbles--row (point)))
1092 (col (bubbles--col (point))))
1093 (add-text-properties (point-min) (point-max)
1094 '(face default active nil))
1096 (when (and row col (not (eq char (bubbles--empty-char))))
1097 (setq count (bubbles--mark-direct-neighbours row col char))
1099 (add-text-properties (point-min) (point-max)
1100 '(face default active nil))
1102 (bubbles--update-neighbourhood-score count))
1103 (put-text-property (point-min) (point-max) 'pointer 'arrow)
1104 (bubbles--update-faces-or-images)
1106 (error (message "Bubbles: Internal error %s" err)))))
1108 (defun bubbles--neighbourhood-available ()
1109 "Return t if another valid neighbourhood is available."
1112 (dotimes (i (bubbles--grid-height))
1113 (dotimes (j (bubbles--grid-width))
1114 (let ((c (bubbles--char-at i j)))
1115 (if (and (not (eq c (bubbles--empty-char)))
1116 (or (eq c (bubbles--char-at (1+ i) j))
1117 (eq c (bubbles--char-at i (1+ j)))))
1118 (throw 'found t)))))
1121 (defun bubbles--count ()
1122 "Count remaining bubbles."
1125 (dotimes (i (bubbles--grid-height))
1126 (dotimes (j (bubbles--grid-width))
1127 (let ((c (bubbles--char-at i j)))
1128 (if (not (eq c (bubbles--empty-char)))
1129 (setq count (1+ count)))))))
1132 (defun bubbles--reset-score ()
1133 "Reset bubbles score."
1134 (setq bubbles--neighbourhood-score 0
1136 (bubbles--update-score))
1138 (defun bubbles--update-score ()
1139 "Calculate and display new bubble score."
1140 (setq bubbles--score (+ bubbles--score bubbles--neighbourhood-score))
1141 (bubbles--show-scores))
1143 (defun bubbles--update-neighbourhood-score (size)
1144 "Calculate and display score of active neighbourhood from its SIZE."
1146 (setq bubbles--neighbourhood-score (expt (- size 1) 2))
1147 (setq bubbles--neighbourhood-score 0))
1148 (bubbles--show-scores))
1150 (defun bubbles--show-scores ()
1151 "Display current scores."
1153 (goto-char (or (next-single-property-change (point-min) 'status)
1155 (let ((inhibit-read-only t)
1157 (delete-region (point) (point-max))
1158 (insert (format "Selected: %4d\n" bubbles--neighbourhood-score))
1160 (add-text-properties (1- (point)) (point)
1161 (list 'intangible t 'display
1163 (list :width bubbles--col-offset))))
1164 (insert (format "Score: %4d" bubbles--score))
1165 (put-text-property pos (point) 'status t))))
1167 (defun bubbles--game-over ()
1168 "Finish bubbles game."
1169 (bubbles--update-faces-or-images)
1170 (setq bubbles--playing nil
1171 bubbles--save-data nil)
1172 ;; add bonus if all bubbles were removed
1173 (when (= 0 (bubbles--count))
1174 (setq bubbles--score (+ bubbles--score (* (bubbles--grid-height)
1175 (bubbles--grid-width))))
1176 (bubbles--show-scores))
1177 ;; Game over message
1178 (goto-char (point-max))
1179 (let* ((inhibit-read-only t))
1181 (add-text-properties (1- (point)) (point)
1182 (list 'intangible t 'display
1184 (list :width bubbles--col-offset))))
1185 (insert "Game Over!"))
1187 (gamegrid-add-score (format "bubbles-%s-%d-%d-%d-scores"
1188 (symbol-name (bubbles--shift-mode))
1189 (length (bubbles--colors))
1190 (bubbles--grid-width) (bubbles--grid-height))
1193 (defun bubbles-plop ()
1194 "Remove active bubbles region."
1196 (when (and bubbles--playing
1197 (> bubbles--neighbourhood-score 0))
1198 (setq bubbles--save-data (list bubbles--score (buffer-string)))
1199 (setq buffer-undo-list '(apply bubbles-undo . nil))
1200 (let ((inhibit-read-only t))
1201 ;; blank out current neighbourhood
1202 (let ((row (bubbles--row (point)))
1203 (col (bubbles--col (point))))
1204 (goto-char (point-max))
1207 (while (get-text-property (point) 'active)
1209 (insert (bubbles--empty-char))
1210 (add-text-properties (1- (point)) (point) (list 'removed t
1212 (bubbles--goto row col))
1214 (bubbles--update-score)
1215 ;; update display and wait
1216 (bubbles--update-faces-or-images)
1221 (let ((something-dropped nil))
1223 (dotimes (i (bubbles--grid-height))
1224 (dotimes (j (bubbles--grid-width))
1226 (while (get-text-property (point) 'removed)
1227 (setq something-dropped (or (bubbles--shift 'top i j)
1228 something-dropped))))))
1229 ;; update display and wait
1230 (bubbles--update-faces-or-images)
1231 (when something-dropped
1235 (put-text-property (point-min) (point-max) 'removed nil)
1237 (goto-char (point-min))
1238 (let ((removed-string (format "%c" (bubbles--empty-char))))
1239 (while (search-forward removed-string nil t)
1240 (put-text-property (1- (point)) (point) 'removed t))))
1241 (let ((shifted nil))
1242 (cond ((eq (bubbles--shift-mode) 'always)
1244 (dotimes (i (bubbles--grid-height))
1245 (dotimes (j (bubbles--grid-width))
1247 (while (get-text-property (point) 'removed)
1248 (setq shifted (or (bubbles--shift 'right i j) shifted))))))
1249 (bubbles--update-faces-or-images)
1251 (t ;; default shift-mode
1253 (dotimes (j (bubbles--grid-width))
1254 (bubbles--goto (1- (bubbles--grid-height)) j)
1255 (let ((shifted-cols 0))
1256 (while (get-text-property (point) 'removed)
1257 (setq shifted-cols (1+ shifted-cols))
1258 (bubbles--shift 'right (1- (bubbles--grid-height)) j))
1259 (dotimes (k shifted-cols)
1260 (let ((i (- (bubbles--grid-height) 2)))
1262 (setq shifted (or (bubbles--shift 'right i j) shifted))
1263 (setq i (1- i))))))))))
1266 (bubbles--update-faces-or-images)
1268 (put-text-property (point-min) (point-max) 'removed nil)
1269 (unless (bubbles--neighbourhood-available)
1270 (bubbles--game-over)))))
1272 (defun bubbles-undo ()
1275 (when bubbles--save-data
1276 (let ((inhibit-read-only t)
1279 (insert (cadr bubbles--save-data))
1280 (bubbles--update-faces-or-images)
1281 (setq bubbles--score (car bubbles--save-data))
1284 (defun bubbles--shift (from row col)
1285 "Move bubbles FROM one side to position ROW COL.
1286 Return t if new char is non-empty."
1288 (when (bubbles--goto row col)
1289 (let ((char-org (char-after (point)))
1290 (char-new (bubbles--empty-char))
1295 (cond ((eq from 'top)
1296 (setq trow (1- row)))
1298 (setq tcol (1- col)))
1300 (setq tcol (1+ col))))
1302 (when (bubbles--goto trow tcol)
1303 (setq char-new (char-after (point)))
1304 (setq removed (get-text-property (point) 'removed))
1305 (setq index (get-text-property (point) 'index))
1306 (bubbles--shift from trow tcol)))
1309 (add-text-properties (1- (point)) (point) (list 'index index
1311 (not (eq char-new (bubbles--empty-char)))))))
1313 (defun bubbles--initialize-images ()
1314 "Prepare images for playing `bubbles'."
1315 (when (and (display-images-p)
1316 (not (eq bubbles-graphics-theme 'ascii)))
1317 (let ((template (case bubbles-graphics-theme
1318 ('circles bubbles--image-template-circle)
1319 ('balls bubbles--image-template-ball)
1320 ('squares bubbles--image-template-square)
1321 ('diamonds bubbles--image-template-diamond)
1322 ('emacs bubbles--image-template-emacs))))
1323 (setq bubbles--empty-image
1324 (create-image (replace-regexp-in-string
1325 "^\"\\(.*\\)\t.*c .*\",$"
1326 "\"\\1\tc #FFFFFF\"," template)
1330 (setq bubbles--images
1331 (mapcar (lambda (color)
1332 (let* ((rgb (color-values color))
1338 (goto-char (point-min))
1340 "^\"[0-9]+ [0-9]+ \\(.*?\\) .*\",$" nil t)
1341 (goto-char (point-min))
1342 (while (re-search-forward
1343 "^\"\\(.*\\)\t.*c \\(#.*\\)\",$" nil t)
1344 (let* ((crgb (color-values (match-string 2)))
1348 (brightness (/ (+ r g b) 3.0 256 256))
1349 (val (sin (* brightness (/ pi 2))))
1353 ;;(rr (/ (+ red r) 2))
1354 ;;(gg (/ (+ green g) 2))
1355 ;;(bb (/ (+ blue b) 2))
1356 (color (format "#%02x%02x%02x"
1357 (/ rr 256) (/ gg 256)
1359 (replace-match (format "\"\\1\tc %s\","
1361 (create-image (buffer-string) 'xpm t
1365 (bubbles--colors))))
1367 (setq bubbles--images-ok bubbles--empty-image)
1369 (setq bubbles--images-ok (and bubbles--images-ok elt)))
1372 (defun bubbles--update-faces-or-images ()
1373 "Update faces and/or images, depending on graphics mode."
1374 (bubbles--set-faces)
1375 (bubbles--show-images))
1377 (defun bubbles--set-faces ()
1378 "Update faces in the bubbles buffer."
1379 (unless (and (display-images-p)
1381 (not (eq bubbles-graphics-theme 'ascii)))
1382 (when (display-color-p)
1384 (let ((inhibit-read-only t))
1385 (dotimes (i (bubbles--grid-height))
1386 (dotimes (j (bubbles--grid-width))
1388 (let* ((index (get-text-property (point) 'index))
1389 (face (nth index bubbles--faces))
1390 (fg-col (face-foreground face)))
1391 (when (get-text-property (point) 'active)
1392 (set-face-foreground 'bubbles--highlight-face "#ff0000")
1393 (setq face 'bubbles--highlight-face))
1394 (put-text-property (point) (1+ (point))
1397 (defun bubbles--show-images ()
1398 "Update images in the bubbles buffer."
1399 (bubbles--remove-overlays)
1400 (if (and (display-images-p)
1402 (not (eq bubbles-graphics-theme 'ascii)))
1404 (goto-char (point-min))
1406 (let ((inhibit-read-only t)
1408 (dotimes (i (bubbles--grid-height))
1409 (dotimes (j (bubbles--grid-width))
1411 (let ((index (get-text-property (point) 'index)))
1412 (let ((img bubbles--empty-image))
1414 (setq img (nth index bubbles--images)))
1415 (put-text-property (point) (1+ (point))
1416 'display (cons img nil)))))
1419 (let ((inhibit-read-only t))
1420 (goto-char (point-min))
1422 (let ((disp-prop (get-text-property (point) 'display)))
1423 (if (and (listp disp-prop)
1424 (listp (car disp-prop))
1425 (eq (caar disp-prop) 'image))
1426 (put-text-property (point) (1+ (point)) 'display nil))
1428 (put-text-property (point-min) (point-max) 'pointer 'arrow)))))
1432 ;; arch-tag: 2cd7237a-b0ad-400d-a7fd-75f676dceb70
1433 ;;; bubbles.el ends here