;;; bubbles.el --- Puzzle game for Emacs. ;; Copyright (C) 2007, 2008 Free Software Foundation, Inc. ;; Author: Ulf Jasper ;; URL: http://ulf.epplejasper.de/ ;; Created: 5. Feb. 2007 ;; Keywords: games ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; Bubbles is a puzzle game. Its goal is to remove as many bubbles as ;; possible in as few moves as possible. ;; Bubbles is an implementation of the "Same Game", similar to "Same ;; GNOME" and many others, see . ;; Installation ;; ------------ ;; Add the following lines to your Emacs startup file (`~/.emacs'). ;; (add-to-list 'load-path "/path/to/bubbles/") ;; (autoload 'bubbles "bubbles" "Play Bubbles" t) ;; ====================================================================== ;;; History: ;; 0.5 (2007-09-14) ;; - Minor bugfixes. ;; 0.4 (2007-08-27) ;; - Allow for undoing last move. ;; - Bonus for removing all bubbles. ;; - Speed improvements. ;; - Animation enhancements. ;; - Added `bubbles-mode-hook'. ;; - Fixes: Don't move point. ;; - New URL. ;; 0.3 (2007-03-11) ;; - Renamed shift modes and thus names of score files. All ;; highscores are lost, unless you rename the score files from ;; bubbles-shift-... to bubbles-...! ;; - Bugfixes: Check for successful image creation. ;; Disable menus and counter when game is over. ;; Tested with GNU Emacs 22.0.93 ;; 0.2 (2007-02-24) ;; - Introduced game themes. ;; - Introduced graphics themes (changeable while playing). ;; - Added menu. ;; - Customization: grid size, colors, chars, shift mode. ;; - More keybindings. ;; - Changed shift direction from to-right to to-left. ;; - Bugfixes: Don't remove single-bubble regions; ;; Animation glitches fixed. ;; Tested with GNU Emacs 22.0.93 and 21.4.1. ;; 0.1 (2007-02-11) ;; Initial release. Tested with GNU Emacs 22.0.93 and 21.4.1. ;; ====================================================================== ;;; Code: (defconst bubbles-version "0.5" "Version number of bubbles.el.") (require 'gamegrid) (require 'cl) ;; User options ;; Careful with that axe, Eugene! Order does matter in the custom ;; section below. (defcustom bubbles-game-theme 'easy "Overall game theme. The overall game theme specifies a grid size, a set of colors, and a shift mode." :type '(radio (const :tag "Easy" easy) (const :tag "Medium" medium) (const :tag "Difficult" difficult) (const :tag "Hard" hard) (const :tag "User defined" user-defined)) :group 'bubbles) (defun bubbles-set-game-easy () "Set game theme to 'easy'." (interactive) (setq bubbles-game-theme 'easy) (bubbles)) (defun bubbles-set-game-medium () "Set game theme to 'medium'." (interactive) (setq bubbles-game-theme 'medium) (bubbles)) (defun bubbles-set-game-difficult () "Set game theme to 'difficult'." (interactive) (setq bubbles-game-theme 'difficult) (bubbles)) (defun bubbles-set-game-hard () "Set game theme to 'hard'." (interactive) (setq bubbles-game-theme 'hard) (bubbles)) (defun bubbles-set-game-userdefined () "Set game theme to 'user-defined'." (interactive) (setq bubbles-game-theme 'user-defined) (bubbles)) (defgroup bubbles nil "Bubbles, a puzzle game." :group 'games) (defcustom bubbles-graphics-theme 'circles "Graphics theme. It is safe to choose a graphical theme. If Emacs cannot display images the `ascii' theme will be used." :type '(radio (const :tag "Circles" circles) (const :tag "Squares" squares) (const :tag "Diamonds" diamonds) (const :tag "Balls" balls) (const :tag "Emacs" emacs) (const :tag "ASCII (no images)" ascii)) :group 'bubbles) (defconst bubbles--grid-small '(10 . 10) "Predefined small bubbles grid.") (defconst bubbles--grid-medium '(15 . 10) "Predefined medium bubbles grid.") (defconst bubbles--grid-large '(20 . 15) "Predefined large bubbles grid.") (defconst bubbles--grid-huge '(30 . 20) "Predefined huge bubbles grid.") (defcustom bubbles-grid-size bubbles--grid-medium "Size of bubbles grid." :type `(radio (const :tag "Small" ,bubbles--grid-small) (const :tag "Medium" ,bubbles--grid-medium) (const :tag "Large" ,bubbles--grid-large) (const :tag "Huge" ,bubbles--grid-huge) (cons :tag "User defined" (integer :tag "Width") (integer :tag "Height"))) :group 'bubbles) (defconst bubbles--colors-2 '("orange" "violet") "Predefined bubbles color list with two colors.") (defconst bubbles--colors-3 '("lightblue" "palegreen" "pink") "Predefined bubbles color list with three colors.") (defconst bubbles--colors-4 '("firebrick" "sea green" "steel blue" "chocolate") "Predefined bubbles color list with four colors.") (defconst bubbles--colors-5 '("firebrick" "sea green" "steel blue" "sandy brown" "bisque3") "Predefined bubbles color list with five colors.") (defcustom bubbles-colors bubbles--colors-3 "List of bubble colors. The length of this list determines how many different bubble types are present." :type `(radio (const :tag "Red, darkgreen" ,bubbles--colors-2) (const :tag "Red, darkgreen, blue" ,bubbles--colors-3) (const :tag "Red, darkgreen, blue, orange" ,bubbles--colors-4) (const :tag "Red, darkgreen, blue, orange, violet" ,bubbles--colors-5) (repeat :tag "User defined" color)) :group 'bubbles) (defcustom bubbles-chars '(?+ ?O ?# ?X ?. ?* ?& ?§) "Characters used for bubbles. Note that the actual number of different bubbles is determined by the number of colors, see `bubbles-colors'." :type '(repeat character) :group 'bubbles) (defcustom bubbles-shift-mode 'default "Shift mode. Available modes are `shift-default' and`shift-always'." :type '(radio (const :tag "Default" default) (const :tag "Shifter" always) ;;(const :tag "Mega Shifter" 'mega) ) :group 'bubbles) (defcustom bubbles-mode-hook nil "Hook run by Bubbles mode." :group 'bubbles :type 'hook) (defun bubbles-customize () "Open customization buffer for bubbles." (interactive) (customize-group 'bubbles)) ;; ====================================================================== ;; internal variables (defvar bubbles--score 0 "Current Bubbles score.") (defvar bubbles--neighbourhood-score 0 "Score of active bubbles neighborhood.") (defvar bubbles--faces nil "List of currently used faces.") (defvar bubbles--playing nil "Play status indicator.") (defvar bubbles--empty-image nil "Image used for removed bubbles (empty grid cells).") (defvar bubbles--images nil "List of images for bubbles.") (defvar bubbles--images-ok nil "Indicate whether images have been created successfully.") (defvar bubbles--col-offset 0 "Horizontal offset for centering the bubbles grid.") (defvar bubbles--row-offset 0 "Vertical offset for centering the bubbles grid.") (defvar bubbles--save-data nil "List containing bubbles save data (SCORE BUFFERCONTENTS).") (defconst bubbles--image-template-circle "/* XPM */ static char * dot_xpm[] = { \"20 20 2 1\", \" c None\", \". c #FFFFFF\", \" ...... \", \" .......... \", \" .............. \", \" ................ \", \" ................ \", \" .................. \", \" .................. \", \"....................\", \"....................\", \"....................\", \"....................\", \"....................\", \"....................\", \" .................. \", \" .................. \", \" ................ \", \" ................ \", \" .............. \", \" .......... \", \" ...... \"};") (defconst bubbles--image-template-square "/* XPM */ static char * dot_xpm[] = { \"20 20 2 1\", \"0 c None\", \"1 c #FFFFFF\", \"00000000000000000000\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"01111111111111111110\", \"00000000000000000000\"};") (defconst bubbles--image-template-diamond "/* XPM */ static char * dot_xpm[] = { \"20 20 2 1\", \"0 c None\", \"1 c #FFFFFF\", \"00000000011000000000\", \"00000000111100000000\", \"00000001111110000000\", \"00000011111111000000\", \"00000111111111100000\", \"00001111111111110000\", \"00011111111111111000\", \"00111111111111111100\", \"01111111111111111110\", \"11111111111111111111\", \"01111111111111111110\", \"00111111111111111100\", \"00011111111111111000\", \"00001111111111110000\", \"00000111111111100000\", \"00000011111111000000\", \"00000001111110000000\", \"00000000111100000000\", \"00000000011000000000\", \"00000000000000000000\"};") (defconst bubbles--image-template-emacs "/* XPM */ static char * emacs_24_xpm[] = { \"24 24 129 2\", \" c None\", \". c #837DA4\", \"+ c #807AA0\", \"@ c #9894B2\", \"# c #CCCAD9\", \"$ c #C2C0D2\", \"% c #B6B3C9\", \"& c #A19DB9\", \"* c #8681A5\", \"= c #7D779B\", \"- c #B6B3C7\", \"; c #ABA7BE\", \"> c #9792AF\", \", c #AAA6BD\", \"' c #CBC9D7\", \") c #AAA7BE\", \"! c #908BAA\", \"~ c #797397\", \"{ c #948FAC\", \"] c #9A95B1\", \"^ c #EBEAEF\", \"/ c #F1F1F5\", \"( c #BCB9CB\", \"_ c #A9A5BD\", \": c #757093\", \"< c #918DA9\", \"[ c #DDDBE4\", \"} c #FFFFFF\", \"| c #EAE9EF\", \"1 c #A7A4BA\", \"2 c #716C8F\", \"3 c #8D89A5\", \"4 c #9C98B1\", \"5 c #DBDAE3\", \"6 c #A4A1B7\", \"7 c #6E698A\", \"8 c #8B87A1\", \"9 c #928EA7\", \"0 c #C5C3D1\", \"a c #F8F8F9\", \"b c #CCCAD6\", \"c c #A29FB4\", \"d c #6A6585\", \"e c #88849D\", \"f c #B5B2C2\", \"g c #F0F0F3\", \"h c #E1E0E6\", \"i c #A5A2B5\", \"j c #A09DB1\", \"k c #676281\", \"l c #85819A\", \"m c #9591A7\", \"n c #E1E0E5\", \"o c #F0EFF2\", \"p c #B3B0C0\", \"q c #9D9AAE\", \"r c #635F7C\", \"s c #827F96\", \"t c #9997AA\", \"u c #F7F7F9\", \"v c #C8C7D1\", \"w c #89869D\", \"x c #9B99AB\", \"y c #5F5B78\", \"z c #7F7C93\", \"A c #CFCDD6\", \"B c #B7B5C2\", \"C c #9996A9\", \"D c #5C5873\", \"E c #7A778D\", \"F c #F5F5F6\", \"G c #8E8C9E\", \"H c #7D798F\", \"I c #58546F\", \"J c #6C6981\", \"K c #D5D4DB\", \"L c #F5F4F6\", \"M c #9794A5\", \"N c #625F78\", \"O c #79768C\", \"P c #55516A\", \"Q c #605C73\", \"R c #CAC9D1\", \"S c #EAE9EC\", \"T c #B4B3BE\", \"U c #777488\", \"V c #514E66\", \"W c #DEDEE2\", \"X c #F4F4F5\", \"Y c #9D9BA9\", \"Z c #747185\", \"` c #4E4B62\", \" . c #DEDDE1\", \".. c #A6A5B0\", \"+. c #716F81\", \"@. c #4A475D\", \"#. c #A4A3AE\", \"$. c #F4F3F5\", \"%. c #777586\", \"&. c #6E6C7D\", \"*. c #464358\", \"=. c #514E62\", \"-. c #B9B8C0\", \";. c #D1D0D5\", \">. c #747282\", \",. c #6B6979\", \"'. c #434054\", \"). c #5A5769\", \"!. c #D0CFD4\", \"~. c #5B5869\", \"{. c #696676\", \"]. c #403D50\", \"^. c #DBDADE\", \"/. c #F3F3F4\", \"(. c #646271\", \"_. c #666473\", \":. c #3D3A4C\", \"<. c #555362\", \"[. c #9E9DA6\", \"}. c #9E9CA5\", \"|. c #646170\", \"1. c #393647\", \"2. c #514E5D\", \"3. c #83818C\", \"4. c #A8A7AE\", \"5. c #E6E6E8\", \"6. c #DAD9DC\", \"7. c #353343\", \"8. c #32303E\", \" . . . . . . . . . . . . . . . . . . \", \" + @ # $ % % % % % % % % % % % % % % & * + + \", \" = - ; > > > > > > > > , ' ) > > > > > > ! = \", \"~ ~ { { { { { { { { { { { ] ^ / ( { { { { _ ~ ~ \", \": : < < < < < < < < < < < < [ } } | < < < 1 : : \", \"2 2 3 3 3 3 3 3 3 3 3 3 4 5 } } } 5 3 3 3 6 2 2 \", \"7 7 8 8 8 8 8 8 8 8 9 0 a } } } b 8 8 8 8 c 7 7 \", \"d d e e e e e e e f g } } } h i e e e e e j d d \", \"k k l l l l l m n } } } o p l l l l l l l q k k \", \"r r s s s s t u } } } v w s s s s s s s s x r r \", \"y y z z z z A } } } B z z z z z z z z z z C y y \", \"D D D D D D E F } } G D D D D D D D D D D H D D \", \"I I I I I I I J K } L M N I I I I I I I I O I I \", \"P P P P P P Q R } } } S T P P P P P P P P U P P \", \"V V V V V V W } } X Y V V V V V V V V V V Z V V \", \"` ` ` ` ` ` .} } ..` ` ` ` ` ` ` ` ` ` ` +.` ` \", \"@.@.@.@.@.@.@.#.$.$.%.@.@.@.@.@.@.@.@.@.@.&.@.@.\", \"*.*.*.*.*.*.*.*.=.-.} ;.>.*.*.*.*.*.*.*.*.,.*.*.\", \"'.'.'.'.'.'.'.'.'.'.).!.} !.~.'.'.'.'.'.'.{.'.'.\", \"].].].].].].].].].].].].^.} /.(.].].].].]._.].].\", \":.:.:.:.:.:.:.:.:.:.<.[./.} } }.:.:.:.:.:.|.:.:.\", \" 1.1.1.1.1.1.1.1.2.3.4.5.6.3.1.1.1.1.1.1.1.1. \", \" 7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7.7. \", \" 8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8.8. \"};") (defconst bubbles--image-template-ball "/* XPM */ static char * dot3d_xpm[] = { \"20 20 190 2\", \" c None\", \". c #F9F6F6\", \"+ c #D6D0D0\", \"@ c #BFBBBB\", \"# c #AAA4A4\", \"$ c #ABAAAB\", \"% c #A8A8A8\", \"& c #A29D9D\", \"* c #B5B2B2\", \"= c #CDC9C9\", \"- c #D7D0D0\", \"; c #B3AFAF\", \"> c #B5B5B5\", \", c #B7B7B7\", \"' c #B8B8B8\", \") c #B6B6B6\", \"! c #B3B3B3\", \"~ c #AFAFAF\", \"{ c #A9A9A9\", \"] c #A2A2A2\", \"^ c #9C9A9A\", \"/ c #C9C5C5\", \"( c #FDFBFB\", \"_ c #C3BCBC\", \": c #BBBBBB\", \"< c #C0C0C0\", \"[ c #C3C2C2\", \"} c #C3C3C3\", \"| c #C2C2C2\", \"1 c #BEBEBE\", \"2 c #B9B9B9\", \"3 c #B2B2B2\", \"4 c #ABAAAA\", \"5 c #999999\", \"6 c #ACA7A7\", \"7 c #C2BBBB\", \"8 c #C5C5C5\", \"9 c #CACBCB\", \"0 c #CECECE\", \"a c #CFCFCF\", \"b c #CDCDCD\", \"c c #C8C9C9\", \"d c #9F9F9F\", \"e c #959595\", \"f c #A9A5A5\", \"g c #D5CFCE\", \"h c #BDBDBD\", \"i c #C6C6C6\", \"j c #D5D5D5\", \"k c #D9D9D9\", \"l c #DADADA\", \"m c #D8D8D8\", \"n c #D2D2D2\", \"o c #CBCBCB\", \"p c #A4A4A5\", \"q c #9A9A9A\", \"r c #8F8F8F\", \"s c #C3BFBF\", \"t c #AFACAB\", \"u c #CCCCCC\", \"v c #D6D6D6\", \"w c #DEDEDE\", \"x c #E4E4E4\", \"y c #E5E5E5\", \"z c #E2E2E2\", \"A c #DBDBDB\", \"B c #C9C8C8\", \"C c #A8A9A8\", \"D c #9D9E9D\", \"E c #929292\", \"F c #8A8888\", \"G c #D3CECE\", \"H c #B0B0B0\", \"I c #D1D1D1\", \"J c #DCDCDC\", \"K c #E6E6E6\", \"L c #EEEEEE\", \"M c #F1F1F0\", \"N c #EBEBEB\", \"O c #D7D7D8\", \"P c #ABABAB\", \"Q c #A0A0A0\", \"R c #949494\", \"S c #898989\", \"T c #C0BDBD\", \"U c #B9B6B6\", \"V c #B1B1B1\", \"W c #BCBCBC\", \"X c #C8C8C8\", \"Y c #D3D3D3\", \"Z c #DFDFDE\", \"` c #EAEAEA\", \" . c #F5F5F5\", \".. c #FAFAFA\", \"+. c #F1F1F1\", \"@. c #CECFCF\", \"#. c #ACACAC\", \"$. c #A1A1A1\", \"%. c #8A8A8A\", \"&. c #9B9999\", \"*. c #C7C7C7\", \"=. c #DDDDDD\", \"-. c #E8E8E8\", \";. c #F2F2F2\", \">. c #898A89\", \",. c #7A7878\", \"'. c #AEAEAE\", \"). c #C4C4C4\", \"!. c #CBCBCA\", \"~. c #AAAAAA\", \"{. c #939393\", \"]. c #888888\", \"^. c #7C7C7C\", \"/. c #AAAAAB\", \"(. c #BFBFBF\", \"_. c #C9C9C9\", \":. c #DFDEDF\", \"<. c #A6A6A6\", \"[. c #9B9B9B\", \"}. c #909191\", \"|. c #858586\", \"1. c #797979\", \"2. c #989494\", \"3. c #A5A6A5\", \"4. c #B9B9B8\", \"5. c #C1C1C1\", \"6. c #CFCFCE\", \"7. c #979797\", \"8. c #8D8D8D\", \"9. c #828282\", \"0. c #747171\", \"a. c #ADAAAA\", \"b. c #A9A8A9\", \"c. c #B8B9B9\", \"d. c #A5A5A5\", \"e. c #9C9C9C\", \"f. c #7E7E7D\", \"g. c #929191\", \"h. c #C9C4C4\", \"i. c #989898\", \"j. c #ADADAD\", \"k. c #9D9D9D\", \"l. c #8C8C8C\", \"m. c #787878\", \"n. c #B8B6B6\", \"o. c #939191\", \"p. c #A5A5A6\", \"q. c #ABABAA\", \"r. c #A8A8A9\", \"s. c #A3A3A3\", \"t. c #858585\", \"u. c #757474\", \"v. c #C5C1C1\", \"w. c #969696\", \"x. c #9B9B9C\", \"y. c #A4A4A4\", \"z. c #9E9E9E\", \"A. c #939394\", \"B. c #7D7D7D\", \"C. c #747474\", \"D. c #B7B5B5\", \"E. c #A5A1A1\", \"F. c #919191\", \"G. c #9A9999\", \"H. c #838383\", \"I. c #757575\", \"J. c #939090\", \"K. c #A29E9E\", \"L. c #868686\", \"M. c #8D8D8C\", \"N. c #8E8E8E\", \"O. c #8D8D8E\", \"P. c #8B8C8C\", \"Q. c #848485\", \"R. c #7F7F80\", \"S. c #7A7A7A\", \"T. c #737373\", \"U. c #929090\", \"V. c #828080\", \"W. c #818181\", \"X. c #808080\", \"Y. c #7E7E7E\", \"Z. c #737272\", \"`. c #B7B4B4\", \" + c #BCBABA\", \".+ c #959494\", \"++ c #747172\", \"@+ c #767676\", \"#+ c #6F6D6D\", \"$+ c #8F8E8E\", \" . + @ # $ % & * = . \", \" - ; > , ' ) ! ~ { ] ^ / \", \" ( _ > : < [ } | 1 2 3 4 ] 5 6 ( \", \" 7 ) 1 8 9 0 a b c | : 3 { d e f \", \" g ! h i 0 j k l m n o | 2 ~ p q r s \", \". t ' | u v w x y z A n B 1 ! C D E F . \", \"G H : i I J K L M N z O b | ) P Q R S T \", \"U V W X Y Z ` ...+.y l @.} ' #.$.e %.&.\", \"& H W *.n =.-.;. .L x k 0 [ , #.Q e >.,.\", \"] '.2 ).a k z -.` K w j !.< > ~.d {.].^.\", \"d /.> (._.I k =.:.J v 0 8 : V <.[.}.|.1.\", \"2.3.~ 4.5._.6.n Y I u i 1 > P $.7.8.9.0.\", \"a.d b.V c.(.).*.X i | h ) '.d.e.E ].f.g.\", \"h.i.$.C ~ > 2 W W : ' ! j.d.k.e l.9.m.n.\", \". o.i.d p.q.'.H V H j.r.s.k.e 8.t.^.u.. \", \" v.r w.x.Q s.d.d.y.] z.5 A.8.t.B.C.D. \", \" E.l.F.e i.G.q 5 7.{.r %.H.^.I.J. \", \" ( K.L.%.M.N.N.O.P.S Q.R.S.T.U.( \", \" @ V.W.H.H.9.X.Y.S.I.Z.`. \", \" . +.+++@+C.#+$+D.. \"};") ;; ====================================================================== ;; Functions (defsubst bubbles--grid-width () "Return the grid width for the current game theme." (car (case bubbles-game-theme ('easy bubbles--grid-small) ('medium bubbles--grid-medium) ('difficult bubbles--grid-large) ('hard bubbles--grid-huge) ('user-defined bubbles-grid-size)))) (defsubst bubbles--grid-height () "Return the grid height for the current game theme." (cdr (case bubbles-game-theme ('easy bubbles--grid-small) ('medium bubbles--grid-medium) ('difficult bubbles--grid-large) ('hard bubbles--grid-huge) ('user-defined bubbles-grid-size)))) (defsubst bubbles--colors () "Return the color list for the current game theme." (case bubbles-game-theme ('easy bubbles--colors-2) ('medium bubbles--colors-3) ('difficult bubbles--colors-4) ('hard bubbles--colors-5) ('user-defined bubbles-colors))) (defsubst bubbles--shift-mode () "Return the shift mode for the current game theme." (case bubbles-game-theme ('easy 'default) ('medium 'default) ('difficult 'always) ('hard 'always) ('user-defined bubbles-shift-mode))) (defun bubbles-save-settings () "Save current customization settings." (interactive) (custom-set-variables (list 'bubbles-game-theme `(quote ,bubbles-game-theme) t) (list 'bubbles-graphics-theme `(quote ,bubbles-graphics-theme) t)) (customize-save-customized)) (defsubst bubbles--empty-char () "The character used for removed bubbles (empty grid cells)." ? ) (defun bubbles-set-graphics-theme-ascii () "Set graphics theme to `ascii'." (interactive) (setq bubbles-graphics-theme 'ascii) (bubbles--update-faces-or-images)) (defun bubbles-set-graphics-theme-circles () "Set graphics theme to `circles'." (interactive) (setq bubbles-graphics-theme 'circles) (bubbles--initialize-images) (bubbles--update-faces-or-images)) (defun bubbles-set-graphics-theme-squares () "Set graphics theme to `squares'." (interactive) (setq bubbles-graphics-theme 'squares) (bubbles--initialize-images) (bubbles--update-faces-or-images)) (defun bubbles-set-graphics-theme-diamonds () "Set graphics theme to `diamonds'." (interactive) (setq bubbles-graphics-theme 'diamonds) (bubbles--initialize-images) (bubbles--update-faces-or-images)) (defun bubbles-set-graphics-theme-balls () "Set graphics theme to `balls'." (interactive) (setq bubbles-graphics-theme 'balls) (bubbles--initialize-images) (bubbles--update-faces-or-images)) (defun bubbles-set-graphics-theme-emacs () "Set graphics theme to `emacs'." (interactive) (setq bubbles-graphics-theme 'emacs) (bubbles--initialize-images) (bubbles--update-faces-or-images)) ;; game theme menu (defvar bubbles-game-theme-menu (let ((menu (make-sparse-keymap "Game Theme"))) (define-key menu [bubbles-set-game-userdefined] (list 'menu-item "User defined" 'bubbles-set-game-userdefined :button '(:radio . (eq bubbles-game-theme 'user-defined)))) (define-key menu [bubbles-set-game-hard] (list 'menu-item "Hard" 'bubbles-set-game-hard :button '(:radio . (eq bubbles-game-theme 'hard)))) (define-key menu [bubbles-set-game-difficult] (list 'menu-item "Difficult" 'bubbles-set-game-difficult :button '(:radio . (eq bubbles-game-theme 'difficult)))) (define-key menu [bubbles-set-game-medium] (list 'menu-item "Medium" 'bubbles-set-game-medium :button '(:radio . (eq bubbles-game-theme 'medium)))) (define-key menu [bubbles-set-game-easy] (list 'menu-item "Easy" 'bubbles-set-game-easy :button '(:radio . (eq bubbles-game-theme 'easy)))) menu) "Map for bubbles game theme menu.") ;; graphics theme menu (defvar bubbles-graphics-theme-menu (let ((menu (make-sparse-keymap "Graphics Theme"))) (define-key menu [bubbles-set-graphics-theme-ascii] (list 'menu-item "ASCII" 'bubbles-set-graphics-theme-ascii :button '(:radio . (eq bubbles-graphics-theme 'ascii)))) (define-key menu [bubbles-set-graphics-theme-emacs] (list 'menu-item "Emacs" 'bubbles-set-graphics-theme-emacs :button '(:radio . (eq bubbles-graphics-theme 'emacs)))) (define-key menu [bubbles-set-graphics-theme-balls] (list 'menu-item "Balls" 'bubbles-set-graphics-theme-balls :button '(:radio . (eq bubbles-graphics-theme 'balls)))) (define-key menu [bubbles-set-graphics-theme-diamonds] (list 'menu-item "Diamonds" 'bubbles-set-graphics-theme-diamonds :button '(:radio . (eq bubbles-graphics-theme 'diamonds)))) (define-key menu [bubbles-set-graphics-theme-squares] (list 'menu-item "Squares" 'bubbles-set-graphics-theme-squares :button '(:radio . (eq bubbles-graphics-theme 'squares)))) (define-key menu [bubbles-set-graphics-theme-circles] (list 'menu-item "Circles" 'bubbles-set-graphics-theme-circles :button '(:radio . (eq bubbles-graphics-theme 'circles)))) menu) "Map for bubbles graphics theme menu.") ;; menu (defvar bubbles-menu (let ((menu (make-sparse-keymap "Bubbles"))) (define-key menu [bubbles-quit] (list 'menu-item "Quit" 'bubbles-quit)) (define-key menu [bubbles] (list 'menu-item "New game" 'bubbles)) (define-key menu [bubbles-separator-1] '("--")) (define-key menu [bubbles-save-settings] (list 'menu-item "Save all settings" 'bubbles-save-settings)) (define-key menu [bubbles-customize] (list 'menu-item "Edit all settings" 'bubbles-customize)) (define-key menu [bubbles-game-theme-menu] (list 'menu-item "Game Theme" bubbles-game-theme-menu)) (define-key menu [bubbles-graphics-theme-menu] (list 'menu-item "Graphics Theme" bubbles-graphics-theme-menu :enable 'bubbles--playing)) (define-key menu [bubbles-separator-2] '("--")) (define-key menu [bubbles-undo] (list 'menu-item "Undo last move" 'bubbles-undo :enable '(and bubbles--playing (listp buffer-undo-list)))) menu) "Map for bubbles menu.") ;; bubbles mode map (defvar bubbles-mode-map (let ((map (make-sparse-keymap 'bubbles-mode-map))) ;; (suppress-keymap map t) (define-key map "q" 'bubbles-quit) (define-key map "\n" 'bubbles-plop) (define-key map " " 'bubbles-plop) (define-key map [double-down-mouse-1] 'bubbles-plop) (define-key map [mouse-2] 'bubbles-plop) (define-key map "\C-m" 'bubbles-plop) (define-key map "u" 'bubbles-undo) (define-key map "p" 'previous-line) (define-key map "n" 'next-line) (define-key map "f" 'forward-char) (define-key map "b" 'backward-char) ;; bind menu to mouse (define-key map [down-mouse-3] bubbles-menu) ;; Put menu in menu-bar (define-key map [menu-bar Bubbles] (cons "Bubbles" bubbles-menu)) map) "Mode map for bubbles.") (defun bubbles-mode () "Major mode for playing bubbles. \\{bubbles-mode-map}" (kill-all-local-variables) (use-local-map bubbles-mode-map) (setq major-mode 'bubbles-mode) (setq mode-name "Bubbles") (setq buffer-read-only t) (buffer-disable-undo) (setq buffer-undo-list t) (force-mode-line-update) (redisplay) (add-hook 'post-command-hook 'bubbles--mark-neighbourhood t t) (run-hooks 'bubbles-mode-hook)) ;;;###autoload (defun bubbles () "Play Bubbles game." (interactive) (switch-to-buffer (get-buffer-create "*bubbles*")) (when (or (not bubbles--playing) (y-or-n-p "Start new game? ")) (setq bubbles--save-data nil) (setq bubbles--playing t) (bubbles--initialize))) (defun bubbles-quit () "Quit Bubbles." (interactive) (message "bubbles-quit") (bury-buffer)) (declare-function image-size "image.c" (spec &optional pixels frame)) (defun bubbles--compute-offsets () "Update horizontal and vertical offsets for centering the bubbles grid. Set `bubbles--col-offset' and `bubbles--row-offset'." (cond ((and (display-images-p) bubbles--images-ok (not (eq bubbles-graphics-theme 'ascii)) (fboundp 'window-inside-pixel-edges)) ;; compute offset in units of pixels (let ((bubbles--image-size (car (image-size (car bubbles--images) t)))) (setq bubbles--col-offset (list (max 0 (/ (- (nth 2 (window-inside-pixel-edges)) (nth 0 (window-inside-pixel-edges)) (* ( + bubbles--image-size 2) ;; margin (bubbles--grid-width))) 2)))) (setq bubbles--row-offset (list (max 0 (/ (- (nth 3 (window-inside-pixel-edges)) (nth 1 (window-inside-pixel-edges)) (* (+ bubbles--image-size 1) ;; margin (bubbles--grid-height))) 2)))))) (t ;; compute offset in units of chars (setq bubbles--col-offset (max 0 (/ (- (window-width) (bubbles--grid-width)) 2))) (setq bubbles--row-offset (max 0 (/ (- (window-height) (bubbles--grid-height) 2) 2)))))) (defun bubbles--remove-overlays () "Remove all overlays." (if (fboundp 'remove-overlays) (remove-overlays))) (defun bubbles--initialize () "Initialize Bubbles game." (bubbles--initialize-faces) (bubbles--initialize-images) (bubbles--remove-overlays) (switch-to-buffer (get-buffer-create "*bubbles*")) (bubbles--compute-offsets) (let ((inhibit-read-only t)) (set-buffer-modified-p nil) (erase-buffer) (insert " ") (add-text-properties (point-min) (point) (list 'intangible t 'display (cons 'space (list :height bubbles--row-offset)))) (insert "\n") (let ((max-char (length (bubbles--colors)))) (dotimes (i (bubbles--grid-height)) (let ((p (point))) (insert " ") (add-text-properties p (point) (list 'intangible t 'display (cons 'space (list :width bubbles--col-offset))))) (dotimes (j (bubbles--grid-width)) (let* ((index (random max-char)) (char (nth index bubbles-chars))) (insert char) (add-text-properties (1- (point)) (point) (list 'index index)))) (insert "\n")) (insert "\n ") (add-text-properties (1- (point)) (point) (list 'intangible t 'display (cons 'space (list :width bubbles--col-offset))))) (put-text-property (point-min) (point-max) 'pointer 'arrow)) (bubbles-mode) (bubbles--reset-score) (bubbles--update-faces-or-images) (bubbles--goto 0 0) (setq buffer-undo-list t) (force-mode-line-update) (redisplay)) (defun bubbles--initialize-faces () "Prepare faces for playing `bubbles'." (copy-face 'default 'bubbles--highlight-face) (set-face-background 'bubbles--highlight-face "#8080f4") (when (display-color-p) (setq bubbles--faces (mapcar (lambda (color) (let ((fname (intern (format "bubbles--face-%s" color)))) (unless (facep fname) (copy-face 'default fname) (set-face-foreground fname color)) fname)) (bubbles--colors))))) (defsubst bubbles--row (pos) "Return row of point POS." (save-excursion (goto-char pos) (beginning-of-line) (1- (count-lines (point-min) (point))))) (defsubst bubbles--col (pos) "Return column of point POS." (save-excursion (goto-char pos) (1- (current-column)))) (defun bubbles--goto (row col) "Move point to bubble at coordinates ROW and COL." (if (or (< row 0) (< col 0) (>= row (bubbles--grid-height)) (>= col (bubbles--grid-width))) ;; Error! return nil nil ;; go (goto-char (point-min)) (forward-line (1+ row)) (forward-char (1+ col)) (point))) (defun bubbles--char-at (row col) "Return character at bubble ROW and COL." (save-excursion (if (bubbles--goto row col) (char-after (point)) nil))) (defun bubbles--mark-direct-neighbours (row col char) "Mark direct neighbors of bubble at ROW COL with same CHAR." (save-excursion (let ((count 0)) (when (and (bubbles--goto row col) (eq char (char-after (point))) (not (get-text-property (point) 'active))) (add-text-properties (point) (1+ (point)) '(active t face 'bubbles--highlight-face)) (setq count (+ 1 (bubbles--mark-direct-neighbours row (1+ col) char) (bubbles--mark-direct-neighbours row (1- col) char) (bubbles--mark-direct-neighbours (1+ row) col char) (bubbles--mark-direct-neighbours (1- row) col char)))) count))) (defun bubbles--mark-neighbourhood (&optional pos) "Mark neighborhood of point. Use optional parameter POS instead of point if given." (when bubbles--playing (unless pos (setq pos (point))) (condition-case err (let ((char (char-after pos)) (inhibit-read-only t) (row (bubbles--row (point))) (col (bubbles--col (point)))) (add-text-properties (point-min) (point-max) '(face default active nil)) (let ((count 0)) (when (and row col (not (eq char (bubbles--empty-char)))) (setq count (bubbles--mark-direct-neighbours row col char)) (unless (> count 1) (add-text-properties (point-min) (point-max) '(face default active nil)) (setq count 0))) (bubbles--update-neighbourhood-score count)) (put-text-property (point-min) (point-max) 'pointer 'arrow) (bubbles--update-faces-or-images) (sit-for 0)) (error (message "Bubbles: Internal error %s" err))))) (defun bubbles--neighbourhood-available () "Return t if another valid neighborhood is available." (catch 'found (save-excursion (dotimes (i (bubbles--grid-height)) (dotimes (j (bubbles--grid-width)) (let ((c (bubbles--char-at i j))) (if (and (not (eq c (bubbles--empty-char))) (or (eq c (bubbles--char-at (1+ i) j)) (eq c (bubbles--char-at i (1+ j))))) (throw 'found t))))) nil))) (defun bubbles--count () "Count remaining bubbles." (let ((count 0)) (save-excursion (dotimes (i (bubbles--grid-height)) (dotimes (j (bubbles--grid-width)) (let ((c (bubbles--char-at i j))) (if (not (eq c (bubbles--empty-char))) (setq count (1+ count))))))) count)) (defun bubbles--reset-score () "Reset bubbles score." (setq bubbles--neighbourhood-score 0 bubbles--score 0) (bubbles--update-score)) (defun bubbles--update-score () "Calculate and display new bubble score." (setq bubbles--score (+ bubbles--score bubbles--neighbourhood-score)) (bubbles--show-scores)) (defun bubbles--update-neighbourhood-score (size) "Calculate and display score of active neighborhood from its SIZE." (if (> size 1) (setq bubbles--neighbourhood-score (expt (- size 1) 2)) (setq bubbles--neighbourhood-score 0)) (bubbles--show-scores)) (defun bubbles--show-scores () "Display current scores." (save-excursion (goto-char (or (next-single-property-change (point-min) 'status) (point-max))) (let ((inhibit-read-only t) (pos (point))) (delete-region (point) (point-max)) (insert (format "Selected: %4d\n" bubbles--neighbourhood-score)) (insert " ") (add-text-properties (1- (point)) (point) (list 'intangible t 'display (cons 'space (list :width bubbles--col-offset)))) (insert (format "Score: %4d" bubbles--score)) (put-text-property pos (point) 'status t)))) (defun bubbles--game-over () "Finish bubbles game." (bubbles--update-faces-or-images) (setq bubbles--playing nil bubbles--save-data nil) ;; add bonus if all bubbles were removed (when (= 0 (bubbles--count)) (setq bubbles--score (+ bubbles--score (* (bubbles--grid-height) (bubbles--grid-width)))) (bubbles--show-scores)) ;; Game over message (goto-char (point-max)) (let* ((inhibit-read-only t)) (insert "\n ") (add-text-properties (1- (point)) (point) (list 'intangible t 'display (cons 'space (list :width bubbles--col-offset)))) (insert "Game Over!")) ;; save score (gamegrid-add-score (format "bubbles-%s-%d-%d-%d-scores" (symbol-name (bubbles--shift-mode)) (length (bubbles--colors)) (bubbles--grid-width) (bubbles--grid-height)) bubbles--score)) (defun bubbles-plop () "Remove active bubbles region." (interactive) (when (and bubbles--playing (> bubbles--neighbourhood-score 0)) (setq bubbles--save-data (list bubbles--score (buffer-string))) (let ((inhibit-read-only t)) ;; blank out current neighbourhood (let ((row (bubbles--row (point))) (col (bubbles--col (point)))) (goto-char (point-max)) (while (not (bobp)) (backward-char) (while (get-text-property (point) 'active) (delete-char 1) (insert (bubbles--empty-char)) (add-text-properties (1- (point)) (point) (list 'removed t 'index -1)))) (bubbles--goto row col)) ;; show new score (bubbles--update-score) ;; update display and wait (bubbles--update-faces-or-images) (sit-for 0) (sleep-for 0.2) (discard-input) ;; drop down (let ((something-dropped nil)) (save-excursion (dotimes (i (bubbles--grid-height)) (dotimes (j (bubbles--grid-width)) (bubbles--goto i j) (while (get-text-property (point) 'removed) (setq something-dropped (or (bubbles--shift 'top i j) something-dropped)))))) ;; update display and wait (bubbles--update-faces-or-images) (when something-dropped (sit-for 0))) (discard-input) ;; shift to left (put-text-property (point-min) (point-max) 'removed nil) (save-excursion (goto-char (point-min)) (let ((removed-string (format "%c" (bubbles--empty-char)))) (while (search-forward removed-string nil t) (put-text-property (1- (point)) (point) 'removed t)))) (let ((shifted nil)) (cond ((eq (bubbles--shift-mode) 'always) (save-excursion (dotimes (i (bubbles--grid-height)) (dotimes (j (bubbles--grid-width)) (bubbles--goto i j) (while (get-text-property (point) 'removed) (setq shifted (or (bubbles--shift 'right i j) shifted)))))) (bubbles--update-faces-or-images) (sleep-for 0.5)) (t ;; default shift-mode (save-excursion (dotimes (j (bubbles--grid-width)) (bubbles--goto (1- (bubbles--grid-height)) j) (let ((shifted-cols 0)) (while (get-text-property (point) 'removed) (setq shifted-cols (1+ shifted-cols)) (bubbles--shift 'right (1- (bubbles--grid-height)) j)) (dotimes (k shifted-cols) (let ((i (- (bubbles--grid-height) 2))) (while (>= i 0) (setq shifted (or (bubbles--shift 'right i j) shifted)) (setq i (1- i)))))))))) (when shifted ;;(sleep-for 0.5) (bubbles--update-faces-or-images) (sit-for 0))) (put-text-property (point-min) (point-max) 'removed nil) (unless (bubbles--neighbourhood-available) (bubbles--game-over))) ;; undo (setq buffer-undo-list '((apply bubbles-undo . nil))) (force-mode-line-update) (redisplay))) (defun bubbles-undo () "Undo last move." (interactive) (when bubbles--save-data (let ((inhibit-read-only t) (pos (point))) (erase-buffer) (insert (cadr bubbles--save-data)) (bubbles--update-faces-or-images) (setq bubbles--score (car bubbles--save-data)) (goto-char pos)) (setq buffer-undo-list t) (force-mode-line-update) (redisplay))) (defun bubbles--shift (from row col) "Move bubbles FROM one side to position ROW COL. Return t if new char is non-empty." (save-excursion (when (bubbles--goto row col) (let ((char-org (char-after (point))) (char-new (bubbles--empty-char)) (removed nil) (trow row) (tcol col) (index -1)) (cond ((eq from 'top) (setq trow (1- row))) ((eq from 'left) (setq tcol (1- col))) ((eq from 'right) (setq tcol (1+ col)))) (save-excursion (when (bubbles--goto trow tcol) (setq char-new (char-after (point))) (setq removed (get-text-property (point) 'removed)) (setq index (get-text-property (point) 'index)) (bubbles--shift from trow tcol))) (insert char-new) (delete-char 1) (add-text-properties (1- (point)) (point) (list 'index index 'removed removed)) (not (eq char-new (bubbles--empty-char))))))) (defun bubbles--initialize-images () "Prepare images for playing `bubbles'." (when (and (display-images-p) (not (eq bubbles-graphics-theme 'ascii))) (let ((template (case bubbles-graphics-theme ('circles bubbles--image-template-circle) ('balls bubbles--image-template-ball) ('squares bubbles--image-template-square) ('diamonds bubbles--image-template-diamond) ('emacs bubbles--image-template-emacs)))) (setq bubbles--empty-image (create-image (replace-regexp-in-string "^\"\\(.*\\)\t.*c .*\",$" "\"\\1\tc None\"," template) 'xpm t ;;:mask 'heuristic :margin '(2 . 1))) (setq bubbles--images (mapcar (lambda (color) (let* ((rgb (color-values color)) (red (nth 0 rgb)) (green (nth 1 rgb)) (blue (nth 2 rgb))) (with-temp-buffer (insert template) (goto-char (point-min)) (re-search-forward "^\"[0-9]+ [0-9]+ \\(.*?\\) .*\",$" nil t) (goto-char (point-min)) (while (re-search-forward "^\"\\(.*\\)\t.*c \\(#.*\\)\",$" nil t) (let* ((crgb (color-values (match-string 2))) (r (nth 0 crgb)) (g (nth 1 crgb)) (b (nth 2 crgb)) (brightness (/ (+ r g b) 3.0 256 256)) (val (sin (* brightness (/ pi 2)))) (rr (* red val)) (gg (* green val)) (bb (* blue val)) ;;(rr (/ (+ red r) 2)) ;;(gg (/ (+ green g) 2)) ;;(bb (/ (+ blue b) 2)) (color (format "#%02x%02x%02x" (/ rr 256) (/ gg 256) (/ bb 256)))) (replace-match (format "\"\\1\tc %s\"," (upcase color))))) (create-image (buffer-string) 'xpm t :margin '(2 . 1) ;;:mask 'heuristic )))) (bubbles--colors)))) ;; check images (setq bubbles--images-ok bubbles--empty-image) (mapc (lambda (elt) (setq bubbles--images-ok (and bubbles--images-ok elt))) bubbles--images))) (defun bubbles--update-faces-or-images () "Update faces and/or images, depending on graphics mode." (bubbles--set-faces) (bubbles--show-images)) (defun bubbles--set-faces () "Update faces in the bubbles buffer." (unless (and (display-images-p) bubbles--images-ok (not (eq bubbles-graphics-theme 'ascii))) (when (display-color-p) (save-excursion (let ((inhibit-read-only t)) (dotimes (i (bubbles--grid-height)) (dotimes (j (bubbles--grid-width)) (bubbles--goto i j) (let* ((index (get-text-property (point) 'index)) (face (nth index bubbles--faces)) (fg-col (face-foreground face))) (when (get-text-property (point) 'active) (set-face-foreground 'bubbles--highlight-face "#ff0000") (setq face 'bubbles--highlight-face)) (put-text-property (point) (1+ (point)) 'face face))))))))) (defun bubbles--show-images () "Update images in the bubbles buffer." (bubbles--remove-overlays) (if (and (display-images-p) bubbles--images-ok (not (eq bubbles-graphics-theme 'ascii))) (save-excursion (goto-char (point-min)) (forward-line 1) (let ((inhibit-read-only t) char) (dotimes (i (bubbles--grid-height)) (dotimes (j (bubbles--grid-width)) (forward-char 1) (let ((index (or (get-text-property (point) 'index) -1))) (let ((img bubbles--empty-image)) (if (>= index 0) (setq img (nth index bubbles--images))) (put-text-property (point) (1+ (point)) 'display (cons img nil))))) (forward-line 1)))) (save-excursion (let ((inhibit-read-only t)) (goto-char (point-min)) (while (not (eobp)) (let ((disp-prop (get-text-property (point) 'display))) (if (and (listp disp-prop) (listp (car disp-prop)) (eq (caar disp-prop) 'image)) (put-text-property (point) (1+ (point)) 'display nil)) (forward-char 1))) (put-text-property (point-min) (point-max) 'pointer 'arrow))))) (provide 'bubbles) ;; arch-tag: 2cd7237a-b0ad-400d-a7fd-75f676dceb70 ;;; bubbles.el ends here