大蟒蛇飞行棋编写总结

最终我完成了这个作业,而且应该是班中完成较好的一份作业。虽然美观上是比不上一些其他同学的作品,但功能上是非常完善的。通过上一次聊天室程序的编写,在编写这个程序时少走了许多弯路。在攻克了数据结构和界面操作难点后,功能的编写就顺利了很多。

本飞行棋游戏实现以下功能:

  • 色子掷出6才能从机场出发
  • 色子掷出6奖励额外的掷色子机会一次
  • 实现了同色位置跳棋功能
  • 实现了飞机在18号位置的飞行功能
  • 实现了吃掉同位子其他颜色棋子的功能
  • 自动判断玩家选择的棋子是否可走

1.数据结构

在经历这次程序编写后,深刻的体会到一个良好的数据结构的重要性。客户端处,我为每种颜色的棋子都设计了一个棋盘,并且进行位置标号,这样在进行位子判断时可以省去很多写很多判断语句的麻烦,因为每一色棋子所可能走过的位置标号都时相同的。

蓝色棋子棋盘

 

红色棋子棋盘

同时我写了一个chess类,成员是蓝、绿、黄、红四色棋子字典,用于保存每色4个棋子的位置。同时写了SetPosition和GetPosition两个方法,用于设定新位置和获取棋子位置。此外,我还使用了4个全局的字典,用于保存每色棋子每一个位子的坐标。

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
#!/usr/bin/env python
#encoding:utf-8
 
class FlyChess():
    """棋子类,存有四方的所有十六枚棋子的状态:颜色和当前位置"""
    def __init__(self):
        """初始化变量"""
        self.blue = {1:None,2:None,3:None,4:None}
        self.green = {1:None,2:None,3:None,4:None}
        self.yellow = {1:None,2:None,3:None,4:None}
        self.red = {1:None,2:None,3:None,4:None}
 
    def GetPosition(self,color,num):
        """供外部调用得到棋子当前位置"""
        if color == 'blue':
            return self.blue[num]
        elif color == 'green':
            return self.green[num]
        elif color == 'yellow':
            return self.yellow[num]
        elif color == 'red':
            return self.red[num]
        else:
            return False
 
    def SetPosition(self,color,num,pos):
        if color == 'blue':
            self.blue[num] = pos
        elif color == 'green':
            self.green[num] = pos
        elif color == 'yellow':
            self.yellow[num] = pos
        elif color == 'red':
            self.red[num] = pos

服务端处,这个程序面令的难点是如何表示4人一组的游戏。我使用的方法是在每一个transport中建立一个partner字典,当4人一组的游戏建立时更新partner字典内存,把维持4个客户端连接的transport映射到入字典。

2.客户端界面操作的实现

学过的东西总还是会有用的时候,虽然自己不知道何时会用到。上学期学习的MFC部分界面实现方法,在这次的程序编写中派上了用处。在开始写程序起的一周内,我都为如何实现鼠标点击获取所选棋子的功能烦恼,曾经考虑过ButtonImage,但效果不理想。最终我想到了MFC中的GDI,通过一些查阅和ZetCode的一些代码示例,这个功能实现了。

我把棋盘和棋子的绘制全部改成了GDI的DrawBitmap实现。获取鼠标所选棋子,则通过获取鼠标点击时的位置,与玩家所控制的4个棋子的位置进行比较判断(通过设定一个反映面积,实现只要点击在棋子上都可以作出响应)。

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
#此处仅贴出棋盘和蓝色棋子的绘制
    def OnPaint(self,event):
        """wx.EVT_PAINT event function"""
        dc = wx.PaintDC(self)
        # 载入图片
        blue_chess = wx.Image(r'resource/blue.gif',wx.BITMAP_TYPE_ANY)
        blue_chess_half = blue_chess.Scale(blue_chess.GetWidth()/2,blue_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        green_chess = wx.Image(r'resource/green.gif',wx.BITMAP_TYPE_ANY)
        green_chess_half = green_chess.Scale(green_chess.GetWidth()/2,green_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        yellow_chess = wx.Image(r'resource/yellow.gif',wx.BITMAP_TYPE_ANY)
        yellow_chess_half = yellow_chess.Scale(yellow_chess.GetWidth()/2,yellow_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        red_chess = wx.Image(r'resource/red.gif',wx.BITMAP_TYPE_ANY)
        red_chess_half = red_chess.Scale(red_chess.GetWidth()/2,red_chess.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
        chess_board = wx.Image(r'resource/flying_chess.jpg',wx.BITMAP_TYPE_ANY)
        chess_board = chess_board.Scale(chess_board.GetWidth()/2,chess_board.GetHeight()/2,wx.IMAGE_QUALITY_HIGH)
 
        # Draw chess board
        dc.DrawBitmap(wx.BitmapFromImage(chess_board),0,0,True)
 
        # Draw blue chess
        if self.chess.blue[1] in (90,91,92,93): # 判断棋子所在位置,决定使用图片的大小
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',1)][0],blue_step[self.chess.GetPosition('blue',1)][1],True)
        if self.chess.blue[2] in (90,91,92,93):
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',2)][0],blue_step[self.chess.GetPosition('blue',2)][1],True)
        if self.chess.blue[3] in (90,91,92,93):
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',3)][0],blue_step[self.chess.GetPosition('blue',3)][1],True)
        if self.chess.blue[4] in (90,91,92,93):
            blue_draw = blue_chess
        else:
            blue_draw = blue_chess_half
        dc.DrawBitmap(wx.BitmapFromImage(blue_draw),blue_step[self.chess.GetPosition('blue',4)][0],blue_step[self.chess.GetPosition('blue',4)][1],True)

3.通信协议格式

在上一次聊天室的程序编写中,由于自己对通信协议格式制订的不完善,导致了各种麻烦的产生。这次学乖了,打上开始和结束标签,即使几个指令在一个包中传输过来也不怕了:

xx+数据内容+_99

当然这次也不是一帆风顺的。由于位置代码都是数字,曾经不加_的情况下引起了数据截断错误的出现。这个还是考虑不周。顺带还是要指出一个错误,这个在答辩时由老师指出的:数据长度没指定。万一数据长度超出包长度,socket会自动截断数据分成多个包发送,接收端没处理好会造成错误的产生。

4.同色位置跳棋功能及18号位置飞行功能的实现

其实这2个功能实现起来很简单。因为数据结构的关系,棋盘上对于每种颜色棋子的同色位置都是相同的,所以程序可以使用同一段代码实现此功能,而不用为每种颜色都单独写出代码来实现。在把肯定不能跳棋的位子进行排除后,根据我所标的位置特点,对于一种颜色的棋子在其自己的棋盘上,只有在位置除4余2的时,该位置和棋子同色。18号位置飞行功能就更为方便了,只要判断是否位子为18号即可。

1
2
3
4
5
6
7
8
9
10
        if not new in (90,91,92,93,0,50,51,52,53,54,55,56,18) and new%4 == 2: # 检查是否同色能跳到下一位置
            new = new +4
            self.chess.SetPosition(self.color,current,new)
            self.SendNewPosition(current,new)
            self.OnPaint(self)
        elif new == 18: # 检查是否能飞到30.18-->30
            new = 30
            self.chess.SetPosition(self.color,current,new)
            self.SendNewPosition(current,new)
            self.OnPaint(self)

5.吃棋功能实现

我设计的数据结构在这个功能的实现上是麻烦了,因为要进行位置转换,位置必须转换为同一张棋盘的位置才性。如果数据结构设计成使用一张棋盘,这个功能实现起来就很方便了。

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
    def HitCheck(self,pos):
        """检查是否发生吃其他玩家的情况发生,发生则棋子被吃发送被吃棋子的新位置(返回机场)"""
        pos_tmp = None
        # 检查蓝色棋子
        if self.color != 'blue':
            if self.color == 'green': # 位置转换成蓝色棋子位置表
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            elif self.color == 'red':
                if pos in range(1,25):
                    pos_tmp = pos+26
                elif pos in range(27,51):
                    pos_tmp = pos-26
            elif self.color == 'yellow':
                if pos in range(1,12):
                    pos_tmp = pos+39
                elif pos in range(14,51):
                    pos_tmp = pos-13
            if pos_tmp:
                if self.chess.blue[1] == pos_tmp:
                    self.socket.send('06blue_iso1_1_iso2_90_99') # 发送被吃棋子新位置。06代表棋子被吃更新信息
                if self.chess.blue[2] == pos_tmp:
                    self.socket.send('06blue_iso1_2_iso2_91_99')
                if self.chess.blue[3] == pos_tmp:
                    self.socket.send('06blue_iso1_3_iso2_92_99')
                if self.chess.blue[4] == pos_tmp:
                    self.socket.send('06blue_iso1_4_iso2_93_99')
        # 检查绿色棋子
        if self.color != 'green':
            if self.color == 'blue':
                if pos in range(1,12):
                    pos_tmp = pos + 39
                elif pos in range(14,51):
                    pos_tmp = pos - 13
            elif self.color == 'red':
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            elif self.color == 'yellow':
                if pos in range(1,25):
                    pos_tmp = pos + 26
                elif pos in range(27,51):
                    pos_tmp = pos - 26
            if pos_tmp:
                if self.chess.green[1] == pos_tmp:
                    self.socket.send('06green_iso1_1_iso2_90_99')
                if self.chess.green[2] == pos_tmp:
                    self.socket.send('06green_iso1_2_iso2_91_99')
                if self.chess.green[3] == pos_tmp:
                    self.socket.send('06green_iso1_3_iso2_92_99')
                if self.chess.green[4] == pos_tmp:
                    self.socket.send('06green_iso1_4_iso2_93_99')
        # 检查黄色棋子
        if self.color != 'yellow':
            if self.color == 'blue':
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            elif self.color == 'green':
                if pos in range(1,25):
                    pos_tmp = pos + 26
                elif pos in range(27,51):
                    pos_tmp = pos - 26
            elif self.color == 'red':
                if pos in range(1,12):
                    pos_tmp = pos + 39
                elif pos in range(14,51):
                    pos_tmp = pos - 13
            if pos_tmp:
                if self.chess.yellow[1] == pos_tmp:
                    self.socket.send('06yellow_iso1_1_iso2_90_99')
                if self.chess.yellow[2] == pos_tmp:
                    self.socket.send('06yellow_iso1_2_iso2_91_99')
                if self.chess.yellow[3] == pos_tmp:
                    self.socket.send('06yellow_iso1_3_iso2_92_99')
                if self.chess.yellow[4] == pos_tmp:
                    self.socket.send('06yellow_iso1_4_iso2_93_99')
        # 检查红色棋子
        if self.color != 'red':
            if self.color == 'blue':
                if pos in range(1,25):
                    pos_tmp = pos + 26
                elif pos in range(27,51):
                    pos_tmp = pos - 26
            elif self.color == 'green':
                if pos in range(1,12):
                    pos_tmp = pos + 39
                elif pos in range(14,51):
                    pos_tmp = pos - 13
            elif self.color == 'yellow':
                if pos in range(1,38):
                    pos_tmp = pos + 13
                elif pos in range(40,51):
                    pos_tmp = pos - 39
            if pos_tmp:
                if self.chess.red[1] == pos_tmp:
                    self.socket.send('06red_iso1_1_iso2_90_99')
                if self.chess.red[2] == pos_tmp:
                    self.socket.send('06red_iso1_2_iso2_91_99')
                if self.chess.red[3] == pos_tmp:
                    self.socket.send('06red_iso1_3_iso2_92_99')
                if self.chess.red[4] == pos_tmp:
                    self.socket.send('06red_iso1_4_iso2_93_99')

6.服务端游戏匹配功能实现

当用户开始游戏,服务端就会为用户寻找可用的游戏组。通过查找其他transport的state变量值,当state为__waiting__状态,该用户就会加入这个transport所在的游戏组,若游戏组中成员达到4人则开始游戏,否则就等待。若无可用的游戏组,则自动开启一个新的游戏组等待其他客户端加入。

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
    def GetReady(self,data):
        """对用户加入游戏请求的处理函数"""
        if data[:2] == '02': # 判断数据内容是否是加入游戏请求。02代表加入游戏请求
            self.state = '__Ready__'
            has_game = False # 标记是否已经找到游戏编号
            group = 0 # 记录同一游戏编号内的排队玩家数
            group_in_game = [] # 记录已经使用的游戏编号
            for name,protocol in self.users.iteritems():
                if name != self.name:
                    if protocol.state == '__Ready__':
                        has_game = True # 找到游戏编号
                        if self.game is None: # 加入游戏编号
                            self.game = protocol.game
                        if self.game == protocol.game: # protocol的游戏编号相同,加入同组游戏
                            group = group + 1
                            self.partner[protocol.color] = protocol # 记录同编号游戏玩家
                            if group == 3: # 检查是否到4人可以开始游戏。可以开始游戏,则所有人都进入'__InGame__'状态
                                self.state = '__InGame__'
                                for color,partner in self.partner.iteritems():
                                    partner.state = '__InGame__'
                    elif protocol.state == '__InGame__': # 记录已使用的游戏编号
                        group_in_game.append(protocol.game)
            if not has_game: # 如果没有找到可用的游戏编号,则启用一个新的游戏编号
                new_game = 1
                while self.game == None:
                    if not new_game in group_in_game:
                        self.game = new_game
                    else:
                        new_game = new_game + 1
            if not has_game: # 给玩家分配颜色
                self.color = 'blue'
                self.partner['blue'] = self
                self.transport.write('30blue_99')
            elif not 'green' in self.partner:
                self.color = 'green'
                self.partner['green'] = self
                self.transport.write('30green_99')
            elif not 'yellow' in self.partner:
                self.color = 'yellow'
                self.partner['yellow'] = self
                self.transport.write('30yellow_99')
            else:
                self.color = 'red'
                self.partner['red'] = self
                self.transport.write('30red_99')
            for color,partner in self.partner.iteritems(): # 更新同游戏编号玩家的partner数据
                if color != self.color:
                    partner.partner[self.color] = self
            if group < 3:
                self.transport.write('31_99') # 缺少玩家,发送等待信息
            else:
                # 玩家到齐,发送游戏开始信息
                for color,partner in self.partner.iteritems():
                    partner.transport.write('32_99') # 游戏开始
                    if color == 'blue':
                        partner.transport.write('34_99') # 34代表该玩家走棋
                    else:
                        partner.transport.write('33蓝方_99') # 33代表该玩家等待其他玩家走棋

 

其他功能就不叙述了,都是一些很简单的判断和循环语句就可以实现的功能。游戏测试中还是发现了BUG:吃棋功能在特殊情况下会出现不应出现吃棋还是有棋子被吃。到目前为止还不清楚是在什么情况下会出现,光是看代码也还看不出个所以然来。然后就是,作业教完了,于是我就懒得修补了。(话说这些内容在应聘的时候被雇主看到怎么办啊?)

最后献上源代码(这次就不搞什么license装逼了。第一次使用git,有些棋盘位置设计图片也被上传上去了):https://github.com/Tynox/FlyingChess

游戏不是人人都能做的

(本人写作成绩为D级)

Humble Indie Bundle 7中除了游戏还包括一部关于独立游戏制作人的独立纪录片’Indie Game: The Movie‘,昨天考完试后就看了它,然后得出一个结论:游戏不是人人都能做的,除了要拥有高超的编程技术,还需要有一个爱幻想的头脑,以及对游戏无比的热情。相比于游戏公司制作游戏,独立游戏的制作都是一人,或者两人,再多也是10人以下的小组来完成,几乎所有的工作都要自己去做,其中的艰辛恐怕只有这些独立游戏制作人才能体会到。影片追踪了’Super Meat Boy’游戏的发行及’Fez’的第一次公众展示,展现了游戏发售前的制作人的彷徨,还有各种生活中可能导致游戏不能完成的变故。不论外界如何评论,无论生活的压力是如何之大,他们依然追逐着自己的游戏梦。

什么是独立游戏?独立游戏是一种很私人化的作品,与其说它是给其他人玩的,不如说它是制作人给自己制作的礼物。这不是光有超高编程能力就可以做出来的,更多的是需要制作人对想象力的追求。所以你看,EA,AB等公司的一些大作在玩家中口碑是如此之差。

我想我这辈子都可能写不出一个游戏,我缺乏天马行空的想象力,我更没有高超的编程能力。看了这纪录片,我更加深刻的认识到这点,虽然我也有一个做游戏的梦想(但做什么我都没想好)。游戏不是人人都能做的,但生活中还是应该保持他们这种对游戏的追求:

I don’t like those games. They’re shits. I just want my game and I’m working on it.