Created
April 16, 2014 17:39
-
-
Save liamoc/10911986 to your computer and use it in GitHub Desktop.
Graphical enhancement to 2048-game.el.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; 2048.el --- play 2048 in Emacs (updated graphical version) | |
;; A mild extension for graphical improvements on the 2048-game.el | |
;; by Liam O'Connor to the game 2048 implemented in emacs by | |
;; Zachary Kafner | |
;; Copyright 2014 Zachary Kanfer | |
;; Author: Zachary Kanfer <[email protected]> | |
;; Version: 2014.03.27 | |
;; URL: https://bitbucket.org/zck/2048.el | |
;; This file is not part of GNU Emacs | |
;; This program 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. | |
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | |
;;; Code: | |
(define-derived-mode 2048-mode special-mode "2048-mode" | |
(define-key 2048-mode-map (kbd "p") '2048-up) | |
(define-key 2048-mode-map (kbd "C-p") '2048-up) | |
(define-key 2048-mode-map (kbd "<up>") '2048-up) | |
(define-key 2048-mode-map (kbd "n") '2048-down) | |
(define-key 2048-mode-map (kbd "C-n") '2048-down) | |
(define-key 2048-mode-map (kbd "<down>") '2048-down) | |
(define-key 2048-mode-map (kbd "b") '2048-left) | |
(define-key 2048-mode-map (kbd "C-b") '2048-left) | |
(define-key 2048-mode-map (kbd "<left>") '2048-left) | |
(define-key 2048-mode-map (kbd "f") '2048-right) | |
(define-key 2048-mode-map (kbd "C-f") '2048-right) | |
(define-key 2048-mode-map (kbd "<right>") '2048-right)) | |
(defun 2048-game () "Start playing 2048" | |
(interactive) | |
(switch-to-buffer "2048") | |
(2048-mode) | |
(2048-init)) | |
(defvar *2048-board* nil | |
"The board itself. If a number is in the square, the number is stored. Otherwise, 0 is stored. | |
You should access this with 2048-get-cell.") | |
(defvar *2048-combines-this-move* nil | |
"This stores, for each cell in the board, whether the number in it was generated this turn by two numbers combining.") | |
(defvar *2048-columns* 4 | |
"The width of the board. It could be customized, if you wanted to make the game very very hard, or very very easy.") | |
(defvar *2048-rows* 4 | |
"The height of the board. It could be customized, if you wanted to make the game very very tall, or very very short.") | |
(defvar *2048-random-4-threshold* 90 | |
"When a new number is inserted into the board, insert a 4 if (>= (random 100) *2048-random-4-threshold*). Otherwise, 2.") | |
(defvar *2048-victory-value* 2048 | |
"When this number is reached, the user wins! Yay!") | |
(defvar *2048-debug* nil | |
"when 't, print debugging information.") | |
(defun 2048-init () | |
"Begin a game of 2048." | |
(setq *2048-board* (make-vector (* *2048-columns* *2048-rows*) | |
0)) | |
(setq *2048-combines-this-move* (make-vector (* *2048-columns* *2048-rows*) | |
nil)) | |
(2048-insert-random-cell) | |
(2048-insert-random-cell) | |
(2048-print-board)) | |
(defun 2048-get-cell (row col) | |
"Gets the value in (row, col)." | |
(elt *2048-board* | |
(+ (* row *2048-columns*) | |
col))) | |
(defun 2048-set-cell (row column val) | |
"Sets the value in (row, column)." | |
(aset *2048-board* | |
(+ (* row *2048-columns*) | |
column) | |
val)) | |
(defun 2048-num-to-printable (num) | |
"If you pass in 0, returns an empty string. Otherwise, returns the number as a string." | |
(if (eq num 0) | |
"" | |
(format "%d" num))) | |
(defun 2048-was-combined-this-turn (row column) | |
"Returns whether the number in it was generated this turn by two numbers combining." | |
(elt *2048-combines-this-move* | |
(+ (* row *2048-columns*) | |
column))) | |
(defun 2048-set-was-combined-this-turn (row column) | |
"Returns whether the number in it was generated this turn by two numbers combining." | |
(2048-debug (format "setting (%d, %d) as combined this turn." row column)) | |
(aset *2048-combines-this-move* | |
(+ (* row *2048-columns*) | |
column) | |
t)) | |
(defun 2048-insert-random-cell () | |
"Picks a number randomly, and inserts it into a random cell." | |
(let ((number-to-insert (if (>= (random 100) | |
*2048-random-4-threshold*) | |
4 | |
2)) | |
(row (random *2048-rows*)) | |
(column (random *2048-columns*))) | |
(while (not (eq (2048-get-cell row column) | |
0)) | |
(setq row (random *2048-rows*)) | |
(setq column (random *2048-columns*))) | |
(2048-set-cell row column number-to-insert))) | |
(defun 2048-check-game-end () | |
"Checks whether the game has either been won or lost. If so, it handles notifying and restarting." | |
(cond ((2048-game-was-won) | |
(message "yay!")) | |
((2048-game-was-lost) | |
(message "boo!")))) | |
(defun 2048-game-was-won () | |
"Returns t if the game was won, nil otherwise." | |
(let ((game-was-won nil)) | |
(2048-for row 0 (1- *2048-rows*) | |
(2048-for column 0 (1- *2048-columns*) | |
(when (eq (2048-get-cell row column) | |
*2048-victory-value*) | |
(setq game-was-won t)))) | |
game-was-won)) | |
(defun 2048-game-was-lost () | |
"Returns t if the game was lost, nil otherwise." | |
(let ((game-was-lost t)) | |
(2048-for row 0 (1- *2048-rows*) | |
(2048-for column 0 (1- *2048-columns*) | |
(when (eq (2048-get-cell row column) | |
0) | |
(setq game-was-lost nil)))) | |
game-was-lost)) | |
(defun 2048-print-board () | |
"Wipes the entire field, and prints the board." | |
(let ((inhibit-read-only t)) | |
(erase-buffer) | |
(dotimes (row *2048-rows*) | |
;;print the separator lineon top of, and between cells | |
(dotimes (col *2048-columns*) | |
(insert (propertize " " 'font-lock-face '(:height 40)))) | |
(insert (propertize "\n" 'font-lock-face '(:height 40))) | |
;;print the numbers | |
(dotimes (col *2048-columns*) | |
(let ((current-value (2048-get-cell row col))) | |
(insert (propertize " " 'font-lock-face '(:height 120))) | |
(insert (propertize (format "%5s " (2048-num-to-printable current-value)) 'font-lock-face | |
(cond ((eq current-value 2) '(:foreground "#776e65" :weight extra-bold :height 240 :background "#eee4da" :box (:line-width -2 :color "#fff5eb" :style released-button))) | |
((eq current-value 4) '(:foreground "#776e65" :weight extra-bold :height 240 :background "#ede0c8" :box (:line-width -2 :color "#fef1d9" :style released-button))) | |
((eq current-value 8) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#f2b179" :box (:line-width -2 :color "#f3c28a" :style released-button))) | |
((eq current-value 16) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#f59563" :box (:line-width -2 :color "#f6a674" :style released-button))) | |
((eq current-value 32) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#f67c5f" :box (:line-width -2 :color "#f78d6f" :style released-button))) | |
((eq current-value 64) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#f65e3b" :box (:line-width -2 :color "#f76f4c" :style released-button))) | |
((eq current-value 128) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#edcf72" :box (:line-width -2 :color "#fedf83" :style released-button))) | |
((eq current-value 256) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#edcc61" :box (:line-width -2 :color "#fedd72" :style released-button))) | |
((eq current-value 512) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#edc850" :box (:line-width -2 :color "#fed961" :style released-button))) | |
((eq current-value 1024) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#edc53f" :box (:line-width -2 :color "#fed64f" :style released-button))) | |
((eq current-value 2048) '(:foreground "#f9f6f2" :weight extra-bold :height 240 :background "#edc22e" :box (:line-width -2 :color "#fed33f" :style released-button))) | |
((eq current-value 0) '(:foreground "#bbada0" :weight extra-bold :height 240 :background "#bbada0" :box (:line-width -2 :color "#bbada0" :style pressed-button))) | |
(t '(:foreground "#f9f6f2" :background "#3c3a32" :weight extra-bold :height 240 :box (:line-width -2 :color "#fed33f" :style released-button))) | |
) )))) | |
(insert (propertize " " 'font-lock-face '(:height 720 ))) | |
(insert "\n") | |
))) | |
(defun 2048-move (from-row from-column delta-row delta-column) | |
"Tries to move the number in (from-row, from-column) to (to-row, to-column). | |
This succeeds when (to-row, to-column) either is 0, | |
or is the same value as (from-row, from-column). | |
If (to-row, to-column) is zero, cascade and try to move further. | |
Returns t if we were able to move; otherwise nil." | |
(let ((to-row (+ from-row delta-row)) | |
(to-column (+ from-column delta-column))) | |
(when (in-bounds to-row to-column) | |
(2048-debug (format "moving the cell (%d, %d) by (%d, %d) to (%d, %d)" from-row from-column delta-row delta-column to-row to-column)) | |
(let ((from-val (2048-get-cell from-row from-column)) | |
(to-val (2048-get-cell to-row to-column))) | |
(cond ((eq from-val to-val) | |
(unless (or (eq from-val 0) | |
(2048-was-combined-this-turn to-row to-column)) | |
(2048-debug (format "combining (%d, %d) into (%d, %d)" from-row from-column to-row to-column)) | |
(2048-set-cell to-row to-column (* from-val 2)) | |
(2048-set-cell from-row from-column 0) | |
(2048-set-was-combined-this-turn to-row to-column))) | |
((eq to-val 0) | |
(2048-set-cell to-row to-column from-val) | |
(2048-set-cell from-row from-column 0) | |
(2048-move to-row to-column delta-row delta-column) | |
t) | |
(t nil)))))) ;;ugh, need to pass out whether something was combined, and pass that to the _next_ call to 2048-move. We see bugs on rows like 4 0 4 0. | |
(defun in-bounds (row column) | |
(and (>= row 0) | |
(>= column 0) | |
(< row *2048-rows*) | |
(< column *2048-columns*))) | |
(defun 2048-up () | |
"Shifts the board up" | |
(interactive) | |
(2048-game-move | |
(setq *2048-combines-this-move* (make-vector (* *2048-columns* *2048-rows*) | |
nil)) | |
(let ((has-moved nil)) | |
(2048-for col 0 (1- *2048-columns*) | |
(2048-for row 1 (1- *2048-rows*) | |
(setq has-moved (or (2048-move row col -1 0) | |
has-moved)))) | |
(when has-moved | |
(2048-insert-random-cell))))) | |
(defun 2048-down () | |
"Shifts the board down" | |
(interactive) | |
(2048-game-move | |
(setq *2048-combines-this-move* (make-vector (* *2048-columns* *2048-rows*) | |
nil)) | |
(let ((has-moved nil)) | |
(2048-for col 0 (1- *2048-columns*) | |
(2048-for-down row (- *2048-rows* 2) 0 | |
(setq has-moved (or (2048-move row col 1 0) | |
has-moved)))) | |
(when has-moved | |
(2048-insert-random-cell))))) | |
(defun 2048-left () | |
"Shifts the board left." | |
(interactive) | |
(2048-game-move | |
(let ((has-moved nil)) | |
(2048-for row 0 (1- *2048-rows*) | |
(2048-for col 1 (1- *2048-columns*) | |
(setq has-moved (or (2048-move row col 0 -1) | |
has-moved)))) | |
(when has-moved | |
(2048-insert-random-cell))))) | |
(defun 2048-right () | |
"Shifts the board right." | |
(interactive) | |
(2048-game-move | |
(let ((has-moved nil)) | |
(2048-for row 0 (1- *2048-rows*) | |
(2048-for-down col (- *2048-columns* 2) 0 | |
(setq has-moved (or (2048-move row col 0 1) | |
has-moved)))) | |
(when has-moved | |
(2048-insert-random-cell))))) | |
(defmacro 2048-for (var init end &rest body) | |
"Helper function. executes 'body repeatedly, with 'var assigned values starting at 'init, and ending at 'end, increasing by one each iteration." | |
`(let ((,var ,init) | |
(end-val ,end)) | |
(while (<= ,var end-val) | |
,@body | |
(setq ,var (1+ ,var))))) | |
(defmacro 2048-for-down (var init end &rest body) | |
"Helper function, executes 'body repeatedly, with 'var assigned values starting at 'init, and ending at 'end, decreasing by one each iteration." | |
`(let ((,var ,init) | |
(end-val ,end)) | |
(while (>= ,var end-val) | |
,@body | |
(setq ,var (1- ,var))))) | |
(defmacro 2048-game-move (&rest body) | |
`(progn (setq *2048-combines-this-move* (make-vector (* *2048-columns* *2048-rows*) | |
nil)) | |
,@body | |
(2048-print-board) | |
(2048-check-game-end))) | |
(defmacro 2048-debug (&rest body) | |
"If *2048-debug* is 't, log ,@body as a string to the buffer named '2048-debug'" | |
`(when *2048-debug* | |
(print (concat ,@body) | |
(get-buffer-create "2048-debug")))) | |
(provide '2048-game) | |
;;; 2048.el ends here |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment