华容道游戏胜利条件:曹操左上位置的块移动到下方出口(以1,3坐标位置为左上角的四格区域)即可胜利。
五虎将各占两个格子,兵占一个格子,曹操占四个格子。将人物设计成继承自Button类的自定义类Block。游戏过程中,通过点击鼠标移动块。如果移动时,会与别的块有交叉(重叠),则不允许移动,无交叉是才允许移动。
移动格子通过鼠标拖动(点击住并拉拽)实现格子(Block块)的移动。且需要判断鼠标的移动方向(上下左右)。且判断是否允许移动。
1.用四个数值代表四种形状:One表示一个格子(小兵),Twoheng表示横格形状(关羽),Twoshu表示竖状(张赵马黄)、Four表示大块(曹操)
from tkinter import *
from tkinter.messagebox import *
One = 1 # 表示士兵,数字代表方块类型
Twoheng = 2 # 表示横长方形
Twoshu = 3 # 表示竖长方形
Four = 4 # 表示曹操
2.设计点类Point来存储各个方块的坐标(x,y)
class Point: # 点类,存储方块所在的棋盘坐标
def __init__(self, x, y):
self.x = x
self.y = y
3.设计鼠标按下、拖动、松开的处理函数:
def btn_MouseDown(event): # 鼠标按下的事件
global mouseDownPoint, mouseDown # mouseDownPoint为鼠标按下时,位置的坐标
mouseDownPoint = Point(event.x, event.y) # 获取坐标点
mouseDown = True # 鼠标是否按下
def btn_Realse(event): # 释放时鼠标事件
global mouseDownPoint, mouseDown
print(event.x, event.y) # 打印鼠标松开时像素坐标
if not mouseDown: # 鼠标之前未按下,什么都不发生
return
moveheng = event.x - mouseDownPoint.x # 横向的偏移量
moveshu = event.y - mouseDownPoint.y # 竖向偏移量
pass
4.完善设计块类Block。通过每个块左上角的坐标,即可得出其所占的全部坐标。每个块其实是一个按钮。
class Block(Button):
def __init__(self, p, blockType, master, r, bm): # p:左上角棋盘位置,点对象;blockType:方块类型;
# master:父继承;r:方块角色名;
# bm:图片文件
Button.__init__(self, master)
self.Location = p # 方块左上角棋盘位置,一个点Point对象
self.BType = blockType # 方块类型:One、Twoheng、Twoshu、Four
self['text'] = r # 方块角色名,五虎将、兵、曹操
self['image'] = bm # 引用的角色图片
self.bind('<ButtonPress>', btn_MouseDown) # 绑定事件
self.bind('<ButtonRelease>', btn_Realse)
self.place(x=self.Location.x * 80, y=self.Location.y * 80) # 点坐标*80->转为图形像素位置
def GetPoints(self): # 获取块中所有点坐标,返回一个该方块所占据的所有坐标点的列表
pList = []
if self.BType == One: # 占一个方块(兵)
pList.append(self.Location)
elif self.BType == Twoheng: # 多横向一方块(关羽)
pList.append(self.Location)
pList.append(Point(self.Location.X + 1, self.Location.Y))
elif self.BType == Twoshu: # 多竖向一方块(张赵马黄)
pList.append(self.Location)
pList.append(Point(self.Location.X, self.Location.Y + 1))
elif self.BType == Four: # 占四个,多三个方块(曹操)
pList.append(self.Location)
pList.append(Point(self.Location.X + 1, self.Location.Y))
pList.append(Point(self.Location.X, self.Location.Y + 1))
pList.append(Point(self.Location.X + 1, self.Location.Y + 1))
return pList
def Contains(self, point): # 某块中是否包含某个点
pList = self.GetPoints()
for i in range(len(pList)): # 遍历块中所有的点坐标,和参数点坐标比较,如果相同则包含
if pList[i].x == point.x and pList[i].y == point.y:
return True
def Intersects(self, block): # 是否和另一个块交叉
myPoints = self.GetPoints() # 获取自身块的点列表
otherPoints = block.GetPoints() # 获取参数块的点列表
for i in range(len(otherPoints)): # 遍历比较有无相同包含点,若有则说明两者交叉
p = otherPoints[i]
for j in range(len(myPoints)):
if p.x == myPoints[j].x and p.y == myPoints[j].y:
return True
def IsValid(self, width, height): # 块是否在界限内:横向4*竖向5的游戏坐标格子中
points = self.GetPoints()
for i in range(len(points)):
p = points[i]
if p.x < 0 or p.x >= width or p.y < 0 or p.y >= height:
return False
return True
5.设计游戏Game类,包括了游戏格子的大小(4*5),包含所有块的列表,结束点(1,3),初始化方块
class Game:
Width = 4
Height = 5 # 游戏格子区域
WinFlag = False # 判断是否结束
Blocks = [] # 向游戏中添加块
finishPoint = Point(1, 3) # 游戏结束位置
def AddBlock(self, block): # 向区域中添加方块的方法
if block in self.Blocks: # 如果已经存在则添加失败
return False
if not block.IsValid(self.Width, self.Height): # 如果超出区域则添加失败
return False
for i in range(len(self.Blocks)):
if self.Blocks[i].Intersects(block): # 如果区域大小不够(有交叉)则添加失败
return False
self.Blocks.append(block) # 添加方块block到区域中
def GetBlockByPos(self, p): # 获取p位置的方块(由p获取方块)
for i in range(len(self.Blocks)):
if self.Blocks[i].Location.x == p.x and self.Blocks[i].Location.y == p.y:
return self.Blocks[i]
return False
def MoveBlock(self, block, direction): # block为块,direction为移动方向
if block not in self.Blocks:
print("非此游戏中的块")
return
oldx = block.Location.x # 记录原来的位置
oldy = block.Location.y
if direction == 'Up':
block.Location.y -= 1
elif direction == 'Down':
block.Location.y += 1
elif direction == 'Left':
block.Location.x -= 1
elif direction == 'Right':
block.Location.x += 1
moveOK = True # 假设可以移动
if not block.IsValid(self.Width, self.Height): # 如果移动越界
moveOK = False
else:
for i in range(len(self.Blocks)): # 遍历所有方块是否有交叉
if block is not self.Blocks[i] and block.Intersects(self.Blocks[i]): # 如交叉
moveOK = False
break
if not moveOK:
print('不能移动')
print(block.Location.x, block.Location.y)
block.Location = Point(oldx, oldy) # 回到原来的位置
print(block.Location.x, block.Location.y)
if moveOK:
print(block['text'], block.Location.x, block.Location.y)
if block['text'] == '曹操' and block.Location.x == 1 and block.Location.y == 3:
self.WinFlag = True
return moveOK
def GameWin(self): # 胜利否
if self.WinFlag == True:
return True
else:
return False
全部完整代码:
# -*- coding: utf-8 -*-
# @Time : 2020/12/2 下午6:50
# @Author : Zhenghui Lyu
# @File : main.py
# @Software: PyCharm
from tkinter import *
from tkinter.messagebox import *
from tkinter import messagebox
One = 1 # 表示士兵,数字代表方块类型
Twoheng = 2 # 表示横长方形
Twoshu = 3 # 表示竖长方形
Four = 4 # 表示曹操
class Point: # 点类,存储方块所在的棋盘坐标
def __init__(self, x, y):
self.x = x
self.y = y
BlockSize = 80 # 游戏中块的显示大小
mouseDownPoint = Point(0, 0) # 鼠标的按下的位置
mouseDown = False # 鼠标默认未按下
def btn_MouseDown(event): # 鼠标按下的事件
global mouseDownPoint, mouseDown # mouseDownPoint为鼠标按下时,位置的坐标
mouseDownPoint = Point(event.x, event.y) # 获取坐标点
mouseDown = True # 鼠标是否按下
def btn_Realse(event): # 释放时鼠标事件
global mouseDownPoint, mouseDown
print(event.x, event.y) # 打印鼠标松开时像素坐标
if not mouseDown: # 鼠标之前未按下,什么都不发生
return
moveheng = event.x - mouseDownPoint.x # 横向的偏移量
moveshu = event.y - mouseDownPoint.y # 竖向偏移量
x = int(event.widget.place_info()['x']) // 80
y = int(event.widget.place_info()['y']) // 80
block = game.GetBlockByPos(Point(x, y))
if moveheng >= BlockSize * 1 / 3:
game.MoveBlock(block, 'Right')
elif moveheng <= -BlockSize * 1 / 3:
game.MoveBlock(block, 'Left')
elif moveshu >= BlockSize * 1 / 3:
game.MoveBlock(block, 'Down')
elif moveshu <= -BlockSize * 1 / 3:
game.MoveBlock(block, 'Up')
else:
return
event.widget.place(x=block.Location.x * 80, y=block.Location.y * 80)
if game.GameWin():
print('游戏胜利!')
messagebox.showinfo('Info', '游戏胜利')
mouseDown = False
class Block(Button):
def __init__(self, p, blockType, master, r, bm): # p:左上角棋盘位置,点对象;blockType:方块类型;
# master:父继承;r:方块角色名;
# bm:图片文件
Button.__init__(self, master)
self.Location = p # 方块左上角棋盘位置,一个点Point对象
self.BType = blockType # 方块类型:One、Twoheng、Twoshu、Four
self['text'] = r # 方块角色名,五虎将、兵、曹操
self['image'] = bm # 引用的角色图片
self.bind('<ButtonPress>', btn_MouseDown) # 绑定事件
self.bind('<ButtonRelease>', btn_Realse)
self.place(x=self.Location.x * 80, y=self.Location.y * 80) # 点坐标*80->转为图形像素位置
def GetPoints(self): # 获取块中所有点坐标,返回一个该方块所占据的所有坐标点的列表
pList = []
if self.BType == One: # 占一个方块(兵)
pList.append(self.Location)
elif self.BType == Twoheng: # 多横向一方块(关羽)
pList.append(self.Location)
pList.append(Point(self.Location.x + 1, self.Location.y))
elif self.BType == Twoshu: # 多竖向一方块(张赵马黄)
pList.append(self.Location)
pList.append(Point(self.Location.x, self.Location.y + 1))
elif self.BType == Four: # 占四个,多三个方块(曹操)
pList.append(self.Location)
pList.append(Point(self.Location.x + 1, self.Location.y))
pList.append(Point(self.Location.x, self.Location.y + 1))
pList.append(Point(self.Location.x + 1, self.Location.y + 1))
return pList
def Contains(self, point): # 某块中是否包含某个点
pList = self.GetPoints()
for i in range(len(pList)): # 遍历块中所有的点坐标,和参数点坐标比较,如果相同则包含
if pList[i].x == point.x and pList[i].y == point.y:
return True
def Intersects(self, block): # 是否和另一个块交叉
myPoints = self.GetPoints() # 获取自身块的点列表
otherPoints = block.GetPoints() # 获取参数块的点列表
for i in range(len(otherPoints)): # 遍历比较有无相同包含点,若有则说明两者交叉
p = otherPoints[i]
for j in range(len(myPoints)):
if p.x == myPoints[j].x and p.y == myPoints[j].y:
return True
def IsValid(self, width, height): # 块是否在界限内:横向4*竖向5的游戏坐标格子中
points = self.GetPoints()
for i in range(len(points)):
p = points[i]
if p.x < 0 or p.x >= width or p.y < 0 or p.y >= height:
return False
return True
class Game:
Width = 4
Height = 5 # 游戏格子区域
WinFlag = False # 判断是否结束
Blocks = [] # 向游戏中添加块
finishPoint = Point(1, 3) # 游戏结束位置
def AddBlock(self, block): # 向区域中添加方块的方法
if block in self.Blocks: # 如果已经存在则添加失败
return False
if not block.IsValid(self.Width, self.Height): # 如果超出区域则添加失败
return False
for i in range(len(self.Blocks)):
if self.Blocks[i].Intersects(block): # 如果区域大小不够(有交叉)则添加失败
return False
self.Blocks.append(block) # 添加方块block到区域中
def GetBlockByPos(self, p): # 获取p位置的方块(由p获取方块)
for i in range(len(self.Blocks)):
if self.Blocks[i].Location.x == p.x and self.Blocks[i].Location.y == p.y:
return self.Blocks[i]
return False
def MoveBlock(self, block, direction): # block为块,direction为移动方向
if block not in self.Blocks:
print("非此游戏中的块")
return
oldx = block.Location.x # 记录原来的位置
oldy = block.Location.y
if direction == 'Up':
block.Location.y -= 1
elif direction == 'Down':
block.Location.y += 1
elif direction == 'Left':
block.Location.x -= 1
elif direction == 'Right':
block.Location.x += 1
moveOK = True # 假设可以移动
if not block.IsValid(self.Width, self.Height): # 如果移动越界
moveOK = False
else:
for i in range(len(self.Blocks)): # 遍历所有方块是否有交叉
if block is not self.Blocks[i] and block.Intersects(self.Blocks[i]): # 如交叉
moveOK = False
break
if not moveOK:
print('不能移动')
print(block.Location.x, block.Location.y)
block.Location = Point(oldx, oldy) # 回到原来的位置
print(block.Location.x, block.Location.y)
if moveOK:
print(block['text'], block.Location.x, block.Location.y)
if block['text'] == '曹操' and block.Location.x == 1 and block.Location.y == 3:
self.WinFlag = True
return moveOK
def GameWin(self): # 胜利否
if self.WinFlag == True:
return True
else:
return False
win = Tk()
win.title('华容道游戏')
win.geometry('320x400')
game = Game()
bm = [PhotoImage(file='cao.gif'), PhotoImage(file='guan.gif'), PhotoImage(file='huang.gif'),
PhotoImage(file='ma.gif'), PhotoImage(file='zhang.gif'), PhotoImage(file='zhao.gif'),
PhotoImage(file='bing.gif')] # 注意tkinter只支持gif格式
b0 = Block(Point(1, 0), Four, win, '曹操', bm[0])
b1 = Block(Point(1, 2), Twoheng, win, '关羽', bm[1])
b2 = Block(Point(3, 2), Twoshu, win, '黄忠', bm[2])
b3 = Block(Point(0, 0), Twoshu, win, '马超', bm[3])
b4 = Block(Point(0, 2), Twoshu, win, '张飞', bm[4])
b5 = Block(Point(3, 0), Twoshu, win, '赵云', bm[5])
b6 = Block(Point(0, 4), One, win, '兵', bm[6])
b7 = Block(Point(1, 3), One, win, '兵', bm[6])
b8 = Block(Point(2, 3), One, win, '兵', bm[6])
b9 = Block(Point(3, 4), One, win, '兵', bm[6])
game.AddBlock(b0)
game.AddBlock(b1)
game.AddBlock(b2)
game.AddBlock(b3)
game.AddBlock(b4)
game.AddBlock(b5)
game.AddBlock(b6)
game.AddBlock(b7)
game.AddBlock(b8)
game.AddBlock(b9)
win.mainloop()