美柑の部屋

涙は見せないと誓った。

Loading…

Octopress插件设计:mahjong

我接触日本麻将(简称日麻),是从《天才麻将少女》这部动漫开始的,从那之后便跳进日麻的坑中无法自拔,到现在为止已经将近一年了。在这一年中,我对日本麻将也有了一些自己的理解与感悟,我也很希望将这些东西在我的博客上与大家分享。

那么问题来了:当我想说明某个关于日麻的问题时,只凭文字总是显得不那么直观。然而如果用图片的话,对于每张麻将牌都去插入一个<img/>标签又非常不方便。究竟该如何解决呢?我的解决方法是开发一个Octopress插件,在之后写博客时,只需利用此插件简单地键入几个字符,Octopress就可以自动在生成的网页中插入对应的图片。
比如,日麻中的“一万”通常会简称为1m;“五饼”则通常会简称为5p。在我想插入一张“一万”的图片的时候,我只需要使用我开发的插件,写出如下代码{% mahjong 1m %},Octopress就会自动添加该图片。是不是比起自己写<img/>标签来要方便很多?
那么就开始开发自己需要的插件吧!

一、基本功能

具体来说,我的需求分为两部分:首先是在一行文本内部加入单张麻将牌的图片,我计划开发一个名为mahjong的插件来实现;然后是可以另起一行显示一组麻将牌(通常是13张,表示某位玩家的手牌)的图片,我计划开发一个名为mahjonglist的插件来实现。这篇博文讲的是第一个插件的实现方式。
然后我们可以开始定义本插件的语法了。我们一般通过{% plugin_name parameters %}的方式调用插件,所以我将本插件的语法定义为{% mahjong cardName %}
下一步定义cardName的语法。首先我们遵照日本麻将玩家的习惯,“万”简写为m,“饼”简写为p,“索”简写为s,这样就可以表示所有非字牌了。对于字牌,我采用后缀j来表示,1j7j对应东南西北白发中。
另外,日麻中有红宝牌(赤ドラ)的概念,通常是红五万、红五索、红五饼。我采用在数字后加一个!的方法将红宝牌与普通牌区分。所以“五索”为5s,“红五索”则为5!s
还有,由于日麻中在吃、碰、杠后会将其中一张牌横置,表示这张牌来自于哪个玩家。所以我采用在数字后加一个$的方法表示这张牌是横置的。比如要表示一个横置的北风,即为4$j。后缀符$!的先后顺序无关。
最后,日麻中暗杠的表示方法是中间两张牌正放,旁边两张牌倒放,所以我们还需要表示倒放的牌(暗牌)。我采用数字0加上m, p, s, j中的任何一个标签表示暗牌。

二、插件开发

首先我们有一组麻将牌图片的资源,我们需要做的是写一个rb文件,解析cardName的语法,并且生成对应的<img/>标签。我们在plugins/目录下新建一个文件名为mahjong.rb。笔者之前没有任何Ruby开发的经验,这次开发插件,完全是参照已有插件的写法开发的。
我们发现,大部分插件都是自己定义了一个类,继承自Liquid::Tag或者Liquid::Block。在文件的最后,我们需要利用Liquid::Template.register_tag()对插件进行注册。
其中,继承自Liquid::Block的插件属于语句块类型的,即用一个开始标识符和一个结束标识符将要显示的内容包裹起来,比如blockquote.rb;而继承自Liquid::Tag的插件属于标签类型的,不需要用两个标识符包裹,比如image_tag.rb。考虑到我们的功能,我们需要选用的是后一种类型,即继承自Liquid::Tag
我们还发现,我们的插件需要写的函数包括initialize(tag_name, markup, tokens)函数,我们的cardName就是通过markup参数传递进来的。在该函数中,我们需要对传进来的参数进行解析。
我们还需要写一个render(context)函数,这个函数执行的结果就是对应的html标签。
注意到这些点之后,我写出的插件代码如下:

mahjong.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# Title: Simple Mahjong tag for Jekyll
# Author: smwlover
# Syntax: {% mahjong cardName %}
#
# Examples:
# {% mahjong 1m %} 表示一万
# {% mahjong 5!s %} 表示红五索(日麻中的红宝牌)
# {% mahjong 3$p %} 表示横置的三饼(日麻中吃、碰、杠的横置)
# 字牌用数字1-7加j表示。其中1-4分别对应东、南、西、北,5-7则对应白、发、中。
# 暗牌用数字0加m、s、p或j中的任何一个都可以表示。

module Jekyll
  class MahjongTag < Liquid::Tag
    def initialize(tag_name, markup, tokens)
        @curFlag = -1 #m, s, p或j对应0, 1, 2, 3
        @cardNumber = -1 #牌的编号
        @isAkaDora = false #是否是红宝牌
        @isRotated = false #是否是横置的牌
        @successful = true #解析是否成功
        @path = "" #存储解析的结果

        str = "#{markup}".strip
        strLen = str.size
        strReversed = str.reverse
        count = 0
        while count < strLen do
            curChar = strReversed[count]
            if count == 0
                if curChar == "m"
                    @curFlag = 0
                elsif curChar == "s"
                    @curFlag = 1
                elsif curChar == "p"
                    @curFlag = 2
                elsif curChar == "j"
                    @curFlag = 3
                else
                    @path = "缺少m、p、s、j标签"
                    @successful = false
                    break
                end
            else
                if @cardNumber >= 0 #如果重复声明了牌的编号
                    @path = "重复声明了牌的编号"
                    @successful = false
                    break
                end
                if curChar == "$"
                    @isRotated = true
                elsif curChar == "!"
                    @isAkaDora = true
                else
                    @cardNumber = curChar.getbyte(0) - 48
                    if @curFlag == 3 && (@cardNumber < 0 || @cardNumber > 7)
                        @path = "j标签对应的编号只能为0到7"
                        @successful = false
                        break
                    elsif @cardNumber < 0 || @cardNumber > 9
                        @path = "m、p、s标签对应的编号只能为0到9"
                        @successful = false
                        break
                    elsif @isAkaDora && (@cardNumber != 5 || @curFlag == 3) #红宝牌必须是五索、五万、五饼。
                        @path = "红宝牌必须是五万、五饼、五索"
                        @successful = false
                        break
                    elsif @isRotated && @cardNumber == 0 #暗牌不能横置
                        @path = "暗牌不能横置"
                        @successful = false
                        break
                    end
                end
            end
            count = count + 1
        end
        if @cardNumber == -1 || @curFlag == -1
            @successful = false
        end
        if @successful
            getPath()
        end
        super
    end

    def render(context)
        if @successful
            "<img class=\"mahjong\" src=\"#{@path}\"/>"
        else
            "<strong>错误:#{@path}</strong>"
        end
    end

    def getPath()
        direction = "tate"
        name = ""
        if @isRotated
            direction = "yoko"
        end
        if @isAkaDora
            if @curFlag == 0
                name = "aka5m"
            elsif @curFlag == 1
                name = "aka5s"
            elsif @curFlag == 2
                name = "aka5p"
            end
        elsif @cardNumber == 0
            name = "back"
        else
            name = "#{@cardNumber + 9 * @curFlag}"
        end
        @path = "/images/mahjong/#{direction}/#{name}.png"
    end
  end
end

Liquid::Template.register_tag('mahjong', Jekyll::MahjongTag)

关于Ruby语法需要说明的有两点:一是Ruby中变量的命名有一套规范,代码中以@开头的变量,代表类的成员变量;二是Ruby中的字符串可以通过#{expression}的方式直接引用一个变量或表达式的值。
需要注意的是,在上面的代码中,我生成html时为每张图片增加了一个属性class="mahjong"。为什么要这样呢?因为Octopress自己的主题中,显示的图片有默认的样式,如果我们不加这一句,显示时就会按照默认的样式显示。而Octopress默认的样式有很粗的白色边框,还有阴影效果,不适合显示麻将牌。下图是Octopress默认的显示效果:

为了覆盖这种默认的显示效果,我为每张图片增加了一个属性class="mahjong",并且在sass/custom/_styles.scss文件中添加了分类器:

_styles.scss
1
2
3
4
5
6
.mahjong{
    border: 0px;
    border-radius: 0px;
    box-shadow: none;
    vertical-align: middle;
}

这样,我们就可以看到没有边框与阴影的麻将牌了。

三、效果测试

类别一:普通麻将牌

  • {% mahjong 3s %}会显示出
  • {% mahjong 6p %}会显示出
  • {% mahjong 8m %}会显示出
  • {% mahjong 3j %}会显示出;

    类别二:红宝牌与暗牌

  • {% mahjong 5!p %}会显示出;
  • {% mahjong 5!m %}会显示出;
  • {% mahjong 5!s %}会显示出;
  • {% mahjong 0j %}会显示出;
  • {% mahjong 0p %}会显示出;

    类别三:横置的牌与红宝牌综合测试

  • {% mahjong 5$j %}会显示出;
  • {% mahjong 5!$s %}会显示出;
  • {% mahjong 5$!p %}会显示出;

    类别四:错误情况测试

  • 本插件定义的错误情况包括:最右端标签不是m, p, s, j;对于标签j,数字范围不在0-7内或对于标签m, p, s,数字范围不在0-9内;或者红宝牌的数字不是5;或者企图将暗牌横置(日麻中暗牌不可能横置)等。
  • {% mahjong 4!s %}会显示出错误:红宝牌必须是五万、五饼、五索;
  • {% mahjong 5!j %}会显示出错误:红宝牌必须是五万、五饼、五索;
  • {% mahjong 0$j %}会显示出错误:暗牌不能横置;
  • {% mahjong 4a %}会显示出错误:缺少m、p、s、j标签;
  • {% mahjong bs %}会显示出错误:m、p、s标签对应的编号只能为0到9;
  • {% mahjong 8j %}会显示出错误:j标签对应的编号只能为0到7;

四、下一步工作:

接下来要做的就是最开始提到的第二部分,即利用mahjonglist插件实现显示一行麻将牌的功能。当这一部分功能完成之后,我就会开始在我的博客中更新一些关于日麻的内容了~

评论