当前访客身份:游客 [ 登录 | 加入 OSCHINA ]

代码分享

当前位置:
代码分享 » Python  » 游戏开发
Kinegratii

Python扫雷小游戏

Kinegratii 发布于 2014年04月30日 13时, 0评/10035阅
分享到: 
收藏 +0
2
使用python内置的tkinter开发的一个扫雷游戏。
现已支持右键提示,时间提示等功能。
项目地址
http://git.oschina.net/kinegratii/minesweeper
标签: <无>

代码片段(3) [全屏查看所有代码]

1. [代码]核心模型和算法     跳至 [1] [全屏预览]

#coding=utf8
"""
@author:kinegratii(kinegratii@yeah.net)
"""
import random
import Queue


class MapCreateError(Exception):
    """ Exception when creating map with invalid params.
    """
    INVALID_HEIGHT_OR_WIDTH = 'invalid height or width' 
    MINE_INFO_MISSING = 'mine info missing '
    MINE_INDEX_INVALID = 'invalid mine index'
    MINE_INVALID_POS = 'invalid mine position'
    MINE_INVALID_NUMBER = 'invalid mine number'
    
    def __init__(self, message):
        super(MapCreateError, self).__init__(message)


class Map(object):
    """A map consist of some mine's position data.It is read-only so that you cannot motify
     its attributes after initing.
     Attributes:
        height: A integer,the height of the map.
        width:A integer,the width of the map.
        map_size:A integer,the amount of cells. map_size = height * width
        mine_list:A list,every element is a two-tuple like (x,y) stand for a mine's position.
        mine_number:A integer less or equal than map_size,the amount of mine
        distribute_map:A 2-d list,the distribute_map[x][y] is the amount of mines in near 8 cells
            if (x, y) is not a mine,otherwise will be -1
    """
    #the mine flag in distribute map
    MINE_FLAG = -1
    
    def __init__(self, height, width, mine_number=None, mine_index_list=None,mine_pos_list=None):
        """Create a map with mines.You can privode mine positions in following three ways.
        (Order by priority)
        mine_pos_list:
        mine_index_list:
        mine_number:
        """
        if type(height) != type(1) or type(width) != type(1) or height <= 0 or width <= 0:
            raise MapCreateError(MapCreateError.INVALID_HEIGHT_OR_WIDTH)
        self._height = height
        self._width = width
        self._mine_number = 0
        self._mine_list = []
        if not any([mine_number, mine_index_list,mine_pos_list]):
            raise MapCreateError(MapCreateError.MINE_INFO_MISSING)
        if mine_pos_list:
            self._init_mine_list(mine_pos_list)
        elif mine_index_list:
            self._create_mine_list(mine_index_list)
        else:
            self._create_rand_mine_list(mine_number)
        self._generate_distribute_map()
        
    
    @property
    def height(self):
        return self._height
    
    @property
    def width(self):
        return self._width
    
    @property
    def map_size(self):
        return self._height * self._width
    
    @property
    def mine_list(self):
        return self._mine_list
    
    @property
    def mine_number(self):
        return self._mine_number
    
    @property
    def distribute_map(self):
        return self._distribute_map
            
    def _create_mine_list(self, mine_index_list):
        """Init mine info with a mine index list.
        """
        index_set = set(mine_index_list)
        self._mine_list = []
        for index in index_set:
            if index in xrange(0, self.map_size):
                self._mine_list.append((index/self._width, index%self._width))
            else:
                raise MapCreateError(MapCreateError.MINE_INDEX_INVALID)
        self._mine_number = len(index_set)
            
    def _init_mine_list(self, mine_pos_list):
        """Init mine info with a mine position list.
        """
        pos_set = set(mine_pos_list)
        for pos in pos_set:
            if not self.is_in_map(pos):
                raise MapCreateError(MapCreateError.MINE_INVALID_POS)
        self._mine_list = list(pos_set)
        self._mine_number = len(pos_set)
        
    def _create_rand_mine_list(self,mine_number):
        """Init mine info with a mine number,this will specify the mine position randomly.
        """
        if mine_number not in xrange(0, self.map_size + 1):
            raise MapCreateError(MapCreateError.MINE_INVALID_NUMBER)
        mine_index_list = random.sample(xrange(0, self.map_size), mine_number)
        self._create_mine_list(mine_index_list)
        
    #Some base functions.Use self.height instead of self._height etc.
    
    def _generate_distribute_map(self):
        """Generate the distribute map. 
        """
        self._distribute_map = [[0 for i in xrange(0,self.width)] for i in xrange(0,self.height)]
        offset_step = [(-1,-1),(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1)]
        for t_x, t_y in self.mine_list:
            self._distribute_map[t_x][t_y] = Map.MINE_FLAG
            for o_x, o_y in offset_step:
                d_x, d_y = t_x + o_x, t_y + o_y
                if self.is_in_map((d_x, d_y)):
                    if self._distribute_map[d_x][d_y] != Map.MINE_FLAG:
                        self._distribute_map[d_x][d_y] += 1
    
    def is_in_map(self, pos, offset=None):
        """return the given postion is in the map.
        """
        if offset:
            x, y = pos[0] + offset[0], pos[1] + offset[1]
        else:
            x, y = pos
        return x in xrange(0, self.height) and y in xrange(0, self.width)
    
    def is_mine(self, pos):
        return pos in self.mine_list
    
    def get_near_mine_number(self, pos):
        """Return the mine number near the given position,if the position is mine return -1
        """
        x, y = pos
        return self._distribute_map[x][y]
    
    def create_new_map(self):
        return Map(self.height, self.width, mine_number=self.mine_number)

class Game(object):
    """A state machine for playing minesweeper.There are some attributes and actions as usual machine.
    Const attributes(will not change when running):
        mine_map:The map object the game is playing on.As convience,the game extends map attributes as
            its own attibutes.For examle, Game.height is for short to Game.mine_map.height
    Runtime attributes:
        cur_step:The steps that has already played.
        click_trace:The clicked position in playing. The expression len(click_trace) = cur_step is true.
        state:The state for this game.This game contains a no-end state namely playing, and two end state:
            success and fail.
        (Note:the above two attributes is for users' input)
        invisual_number:The number of invisual cells.
        visual_state_map: A 2-d list with bool flag which show the cell is visual or not.
    Actions:
        move:the core action.run to next state when accept a click position.
        play:the wrapper for move action.
        reset:reset to initial state.
    """
    STATE_PLAY = 1
    STATE_SUCCESS = 2
    STATE_FAIL = 3
    
    def __init__(self, mine_map):
        self._mine_map = mine_map
        self._init_game()
    
    def _init_game(self):
        self._visual_state_map = [[False for i in xrange(0, self._mine_map.width)] for i in xrange(0, self._mine_map.height)]
        self._invisual_number = self._mine_map.map_size
        self._cur_step = 0
        self._click_trace = []
        self._state = Game.STATE_PLAY
    
    def reset(self):
        self._init_game()

    
    @property
    def cur_step(self):
        return self._cur_step
    
    @property
    def click_trace(self):
        return self._click_trace
    
    @property
    def state(self):
        return self._state
    
    @property
    def invisual_number(self):
        return self._invisual_number
    
    @property
    def visual_state_map(self):
        return self._visual_state_map
    
    @property
    def height(self):
        return self._mine_map.height
    
    @property
    def width(self):
        return self._mine_map.width
    @property
    def mine_number(self):
        return self._mine_map.mine_number
    
    @property
    def mine_map(self):
        return self._mine_map
    
    def move(self, click_pos):
        if self._state == Game.STATE_SUCCESS or self._state == Game.STATE_FAIL:
            # success or fail is the end state of game.
            return self._state
        self._cur_step += 1
        self._click_trace.append(click_pos)
        cx, cy = click_pos
        if self._visual_state_map[cx][cy]:
            #click the position has been clicked,pass
            self._state = Game.STATE_PLAY
            return self._state
        
        near_mine_number = self._mine_map.get_near_mine_number(click_pos)
        
        if near_mine_number == Map.MINE_FLAG:
            #click the mine,game over.
            self._invisual_number -= 1
            self._visual_state_map[cx][cy] = True
            return Game.STATE_FAIL
        elif near_mine_number > 0:
            self._invisual_number -= 1
            self._visual_state_map[cx][cy] = True
            if self._invisual_number == self._mine_map.mine_number:
                self._state = Game.STATE_SUCCESS
            else:
                self._state = Game.STATE_PLAY
            return Game.STATE_PLAY
        else:
            scan_step = [(-1, 0), (0, 1), (1, 0), (0, -1)]
            assert near_mine_number == 0
            q = Queue.Queue()
            q.put(click_pos)
            self._invisual_number -= 1
            self._visual_state_map[cx][cy] = True            
            while not q.empty():
                c_x, c_y = q.get()
                for o_x, o_y in scan_step:
                    d_x, d_y = c_x + o_x, c_y + o_y
                    if self._mine_map.is_in_map((d_x, d_y)) and not self._visual_state_map[d_x][d_y]:
                        near_mine_number = self._mine_map.get_near_mine_number((d_x, d_y))
                        if near_mine_number == Map.MINE_FLAG:
                            pass
                        elif near_mine_number == 0:
                            q.put((d_x, d_y))
                            self._visual_state_map[d_x][d_y] = True
                            self._invisual_number -= 1
                        else:
                            self._visual_state_map[d_x][d_y] = True
                            self._invisual_number -= 1
            assert self._invisual_number >= self._mine_map.mine_number
            if self._invisual_number == self._mine_map.mine_number:
                self._state = Game.STATE_SUCCESS
            else:
                self._state = Game.STATE_PLAY
            return self._state
    
    def play(self, click_pos):
        state = self.move(click_pos)
        if state == Game.STATE_SUCCESS or state == Game.STATE_FAIL:
            self._complete_game()
        return state
    
    def _complete_game(self):
        self._visual_state_map = [[True for i in xrange(0, self.width)] for i in xrange(0, self.height)]
        self._invisual_number = self.mine_map.map_size - self.mine_map.mine_number
    

def main():
    pass

if __name__ == '__main__':
    main()

2. [图片] a.png    

3. [图片] b.png    



开源中国-程序员在线工具:Git代码托管 API文档大全(120+) JS在线编辑演示 二维码 更多»

开源从代码分享开始 分享代码
Kinegratii的其它代码 全部(10)...