Skip to content

lujun9972/emoji-recall.el

Repository files navigation

README

Emoji Recall

献给脸盲症的你~~~~~~~~~~~~~

Emoji Recall 考验你的记忆力,它会每轮依次显示几个 Emoji 表情,然后让你回忆刚刚出现的 Emoji 表情顺序,必须全对才可以。 ./emoji-recall.gif

;; 
(defgroup emoji-recall nil
  "Emoji recall game"
  :prefix "emoji-recall-"
  :group 'game)

设计

游戏界面应该包含两个buffer,一个buffer用于列出所有的emoji供玩家选择:

(defcustom emoji-recall-option-buffer "*emoji-recall-option*"
  "buffer name for the options of emoji-recall"
  :group 'emoji-recall
  :type 'string)

还有一个buffer用于显示游戏场次,依次显示Emoji表情,显示玩家选择的Emoji表情顺序

(defcustom emoji-recall-answer-buffer "*emoji-recall-answer*"
  "buffer name for the answers of emoji-recall"
  :group 'emoji-recall
  :type 'string)

Emoji的图片放在一个目录下,以png格式存储:

(defcustom emoji-recall-pics-dir (concat (if load-file-name
                                             (file-name-directory load-file-name)
                                           default-directory)
                                         "emoji-cheat-sheet/")
  "Directory storing emoji pictures which should be png file"
  :group 'emoji-recall
  :type 'file)

answer-buffer

在answer-buffer中,我们先显示Round N: emoji1 emoji2 … emojiN. 然后在最后一行留空用于存放玩家的答案.类似下面这样:

./answer-buffer.png

  1. 我们需要一个变量存放目前时第几轮的游戏:
    (defvar emoji-recall-round 1
      "Round of the game")
        
  2. 每个emoji在显示一段时间后要变成 * 号:
    • 我们先定义这个时间
      (defcustom emoji-recall-display-interval 3
        "Emoji becomes an asterisk after the number of seconds"
        :group 'emoji-recall
        :type 'number)
              
    • 然后定义一个函数,该函数先在answer-buffer的合适位置处放置一个emoji,然后等待一段时间后将其变为 *
      (defun emoji-recall-create-emoji-image (emoji)
        "Create an image displaying EMOJI"
        (let ((emoji-file (concat emoji-recall-pics-dir emoji ".png")))
          (create-image emoji-file nil nil)))
      (defun emoji-recall-insert-emoji (emoji)
        "Insert an emoji and turn it into an asterisk after certain seconds"
        (with-current-buffer (get-buffer-create emoji-recall-answer-buffer)
          (goto-char (point-max))
          (search-backward-regexp "^Round [0-9]+: \\(.*\\)$")
          (move-end-of-line nil)
          (let* ((emoji-image (emoji-recall-create-emoji-image emoji))
                 (start (point))
                 end)
            (insert (propertize emoji 'display emoji-image))
            (setq end (point))
            (insert " ")
            (run-at-time emoji-recall-display-interval nil (lambda ()
                                                             (put-text-property start end
                                                                                'display "*" (get-buffer-create emoji-recall-answer-buffer)))))))
              
  3. 接下来再定义个函数画出第N轮的问题
    (defun emoji-recall-list-all-emojis (emoji-dir)
      "List all emojis stored in EMOJI-DIR"
      (mapcar #'file-name-base (directory-files emoji-dir nil "\\.png$")))
    
    (defun emoji-recall-random-emoji ()
      "Return random emoji stored in `emoji-recall-pics-dir"
      (let* ((emojis (emoji-recall-list-all-emojis emoji-recall-pics-dir))
             (len (length emojis))
             (idx (random len)))
        (nth idx emojis)))
    
    (defun emoji-recall-insert-random-emojis (N)
      "Insert N random emojis"
      (when (> N 0)
        (emoji-recall-insert-emoji (emoji-recall-random-emoji))
        (run-at-time emoji-recall-display-interval nil (lambda ()
                                                         (emoji-recall-insert-random-emojis (- N 1))))))
    
    (defun emoji-recall-draw-question (N)
      "Draw round N question"
      (with-current-buffer (get-buffer-create emoji-recall-answer-buffer)
        (goto-char (point-max))
        (move-beginning-of-line nil)
        (delete-region (point) (point-max))
        (insert (format "Round %d: " N))
        (newline)
        (insert "> ")
        (emoji-recall-insert-random-emojis N)))
        

option-buffer

在opton-buffer中需要列出所有可能的emoji供玩家选择,玩家点击一个emoji则在 answer-buffer 中存放答案的区域添加一个emoji,当然为了防止玩家输错,需要允许玩家点击答案区域的emoji撤回该emoji.

考虑到这些emoji都需要对点击事件做出响应,我们考虑把这些emoji做成button. 而且将 answer-buffer 中答案位置的emoji做成button还有个好处: 由于Elisp中的button其实就是带有一堆text/overlay属性的文本,这样在核对玩家给出的答案是否正确时,只需要将答案区域的文本与问题处的文本对比一下内容是否一致就行了.

最后,我们还需要一个提交按钮,用于提交答案.

answer-button

  1. 我们先定义答案区域emoji button的类型(暂时命名为answer-button吧),该类button要在被点击的时候将自己从答案区域删除掉.
    (define-button-type 'emoji-recall-answer-button
      'action (lambda (b)
                (delete-region (button-start b)
                               (+ 1 (button-end b)))) ;这里+1是因为每个emoji后面都带个空格
      'follow-link t)
        

option-button

  1. 然后定义option区域中emoji button的类型(暂时命名为option-button吧),该类型的button要在点击的时候,在答案区域插入一个answer-button. 当然,其插入的answer-button的显示与label应该与option-button一致.
    (defun emoji-recall-insert-answer-button (b)
      (let ((label (button-label b))
            (display (button-get b 'display))
            (help-echo (button-get b 'help-echo)))
        (with-current-buffer (get-buffer-create emoji-recall-answer-buffer)
          (goto-char (point-max))
          (insert-text-button label
                              'display display
                              'help-echo help-echo
                              :type 'emoji-recall-answer-button)
          (insert " "))))                   ;这里必须带个空格时因为当相同的emoji靠在一起时,由于display属性相同,Emacs只显示一个emoji image
    
    (define-button-type 'emoji-recall-option-button
      'action #'emoji-recall-insert-answer-button
      'follow-link t)
        
  2. 定义函数用于在option-buffer中插入option button
    (defun emoji-recall-insert-option-button (emoji)
      "Insert an option-button with label EMOJI"
      (let ((emoji-image (emoji-recall-create-emoji-image emoji)))
        (with-current-buffer (get-buffer-create emoji-recall-option-buffer)
          (goto-char (point-max))
          (insert-text-button emoji
                              'display emoji-image
                              'help-echo emoji
                              :type 'emoji-recall-option-button)
          (insert " "))))
    
    
    (defun emoji-recall-draw-options ()
      "Draw all the options"
      (with-current-buffer (get-buffer-create emoji-recall-option-buffer)
        (erase-buffer)
        (mapc #'emoji-recall-insert-option-button
              (emoji-recall-list-all-emojis emoji-recall-pics-dir))))
        

submit-button

  1. 定义一个函数来检查玩家的回答是否正确

    获取回答的答案

    (defun emoji-recall-get-user-answer ()
      "Geth the user answer"
      (with-current-buffer emoji-recall-answer-buffer
        (goto-char (point-min))
        (search-forward "> ")
        (buffer-substring-no-properties (point) (point-max))))
        

    获取正确的答案

    (defun emoji-recall-get-correct-answer ()
      "Geth the user answer"
      (with-current-buffer emoji-recall-answer-buffer
        (goto-char (point-max))
        (search-backward-regexp "^Round [0-9]+: \\(.+\\)$")
        (match-string-no-properties 1))) ;remove the last newline
    
        

    检查玩家输入的答案是否正确

    (defun emoji-recall-verify-user-answer ()
      (string= (emoji-recall-get-user-answer)
               (emoji-recall-get-correct-answer)))
        
  2. 定义submit-button的类型. 该类型的button要在点击的时候,要检查玩家输入的答案是否正确,若正确则进入下一关,否则游戏结束.
    (define-button-type 'emoji-recall-submit-button
      'action (lambda (b)
                (if (emoji-recall-verify-user-answer)
                    (emoji-recall-next-level)
                  (emoji-recall-game-over)))
      'follow-link t)
        
  3. 定义函数用于在option-buffer中插入option button
    (defun emoji-recall-draw-submit-button ()
      "Draw the submit-button"
      (with-current-buffer (get-buffer-create emoji-recall-option-buffer)
        (goto-char (point-max))
        (insert-text-button "Submit"
                            :type 'emoji-recall-submit-button)))
        

其他

游戏开始时

  1. 保存window configuration
  2. 画出游戏界面
(defvar emoji-recall-orign-window-configuration nil
  "orign widndow configuration")

;;;###autoload
(defun emoji-recall-game-start ()
  (interactive)
  (setq emoji-recall-round 1)
  (setq emoji-recall-orign-window-configuration (current-window-configuration))
  (switch-to-buffer (get-buffer-create emoji-recall-answer-buffer))
  (erase-buffer)
  (delete-other-windows)
  (split-window-below)
  (windmove-down)
  (switch-to-buffer (get-buffer-create emoji-recall-option-buffer))
  (emoji-recall-draw-question emoji-recall-round)
  (emoji-recall-draw-options)
  (emoji-recall-draw-submit-button))

下一关

  1. round加一
  2. 插入新的问题
  3. 清空原答案
(defun emoji-recall-next-level ()
  (setq emoji-recall-round (+ 1 emoji-recall-round))
  (emoji-recall-draw-question emoji-recall-round)
  (with-current-buffer emoji-recall-answer-buffer
    (goto-char (point-min))
    (search-forward "> ")
    (delete-region (point) (point-max))))

游戏结束时

  1. 清空answer-buffer的内容
  2. 显示游戏结束画面
(defun emoji-recall-game-over ()
  "Game over and show achievements"
  (interactive)
  (switch-to-buffer emoji-recall-answer-buffer)
  (erase-buffer)
  (insert (propertize (format "Game Over! You Got Round %d\nPress any key to quit !" emoji-recall-round) 'display '(height 2)))
  (while (not (input-pending-p))
    (sit-for 0.01))
  (emoji-recall-game-quit))

游戏退出

  1. 删除answer-buffer
  2. 删除option-buffer
  3. 还原window configuration
(defun emoji-recall-game-quit ()
  "Quit game"
  (interactive)
  (kill-buffer emoji-recall-answer-buffer)
  (kill-buffer emoji-recall-option-buffer)
  (when emoji-recall-orign-window-configuration
    (set-window-configuration emoji-recall-orign-window-configuration)
    (setq emoji-recall-orign-window-configuration nil)))

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors