RecycleView
RecycleGridLayout
RecycleDataViewBehavior
使用示例Popup
的使用示例FocusBehavior
使用示例canvas
使用random.shuffle
打乱列表翻牌游戏
有一个卡牌矩阵,依次翻开2个卡牌,如果2卡牌相同,则消除改2处的卡牌。依次类推,直到所有卡牌都被消除。
可翻转卡牌实现
:
# 背景色来指示卡牌类型
canvas.before:
Color:
rgba:
(1, 0, 0, 1) if self.mode == 0 \
else (0, 1, 0, 1) if self.mode == 1 \
else (0, 0, 1, 1) if self.mode == 2 \
else (1, 0, 1, 1)
Ellipse:
size: self.size
pos: self.pos
# 前景色 选中时透明
canvas.after:
Color:
rgba:
(0, 0, 0, 1) if not self.selectable \
else (1, 1, 1, .1) if self.selected \
else (.1, .8, .5, 1)
Rectangle:
pos: self.pos
size: self.size
font_size: sp(30)
每个卡牌有三种颜色,红、绿、蓝。当被选中时,前景色透明,可显示背景图颜色和文字信息。当无法被选中时,即not self.selectable
时,前景为全黑且不透明。
在python
中SelectableCard
的定义如下
class SelectableCard(RecycleDataViewBehavior, Label):
""" 可翻转的卡牌"""
index = None
mode = NumericProperty(0)
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
"""视图改变"""
self.index = index
return super(SelectableCard, self).refresh_view_attrs(rv, index, data)
def on_touch_down(self, touch):
""" 触摸按下事件"""
if super(SelectableCard, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable: # 按下坐标是否在卡牌坐标内且改卡牌可被选中
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
"""卡牌被选中"""
self.selected = is_selected
卡牌视图列表布局
使用RecycleView
来显示卡牌
:
layout: gridlayout
viewclass: 'SelectableCard' # 用来处理数据的显示
SelectableRecycleGridLayout:
id: gridlayout
default_size: dp(60), dp(60) # 默认大小
default_size_hint: None, None
size_hint_y: None
height: self.minimum_height
cols: 6 # 列表列数
spacing: 10
multiselect: True # 支持多选
touch_multiselect: True
其中布局定义如下:
class SelectableRecycleGridLayout(FocusBehavior, LayoutSelectionBehavior, RecycleGridLayout):
"""卡牌被选中"""
is_checking = False # 是否正在检测卡牌
count = NumericProperty(0) # 翻拍次数
def select_with_touch(self, index, touch):
if self.is_checking:
return
if len(self.selected_nodes) != 0: # 获取选中卡牌数量
pre_index = self.selected_nodes[0]
if pre_index == index: # 两次点击相同卡牌
return
self.count += 1
super(SelectableRecycleGridLayout, self).select_with_touch(index, touch)
if len(self.selected_nodes) > 1: # 当选中卡牌为2个时 检测选中卡牌是否相同
self.is_checking = True
_thread.start_new_thread(self.check_selected_cards, ())
def check_selected_cards(self):
"""检测选中卡牌是否相同"""
pre_index = self.selected_nodes[0]
index = self.selected_nodes[1]
if (self.parent.data[pre_index]['mode'] == self.parent.data[index]['mode']) and (
self.parent.data[pre_index]['text'] == self.parent.data[index]['text']): # 卡牌相同
# 使两张卡牌无效
self.parent.data[index]['mode'] = self.parent.data[pre_index]['mode'] = 0
self.parent.data[index]['selectable'] = self.parent.data[pre_index]['selectable'] = False
# 更新视图
self.parent.refresh_from_data()
time.sleep(.3) # 延时0.3s 清空选择
self.clear_selection()
# 检查是否已全部消除
for card in self.parent.data:
if card['selectable']:
self.is_checking = False
return
# 已全部消除显示弹窗
popup = FinishedPopup(title='Score: {}'.format(self.count), size_hint=(.8, .2))
popup.content = Label(text='Click another location to continue')
popup.open()
self.count = 0
self.parent.new_game()
def select_with_touch(self, index, touch):
用来处理触摸选中事件
当选择卡牌数量为2个时,启动一个线程def check_selected_cards(self):
来检测选中的两卡牌是否相同(颜色和文字都一样),如果相同,则设置两卡牌不可选即:
self.parent.data[index]['mode'] = self.parent.data[pre_index]['mode'] = 0
self.parent.data[index]['selectable'] = self.parent.data[pre_index]['selectable'] = False
当所有卡牌都不可选,则游戏结束
主界面
主界面kv源码如下:
:
orientation: 'vertical'
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: sp(52)
Button: # 重新游戏
text: 'New'
size_hint_x: None
width: sp(52)
on_press:
rv.layout.count = 0
rv.new_game()
Label: # 翻牌次数计数
text: '{}'.format(rv.layout.count)
Button: # 帮助按钮,打开所有牌
size_hint_x: None
width: sp(52)
text: 'Help'
on_press: rv.open_all()
BoxLayout:
orientation: 'horizontal'
Label: # 保持RV居中
RV:
id: rv
size_hint_x: None
width: sp(430)
Label: # 保持RV居中
Label: # 显示使用时间
size_hint_y: None
height: sp(52)
text: '{}:{}'.format((rv.layout.timer // 60), (rv.layout.timer % 60))
新游戏:
def new_game(self):
"""新游戏"""
self.data = [{'text': str(x), 'mode': x % 3, 'selectable': True, 'selected': True} for x in range(24)] * 2
random.shuffle(self.data) # 随机打乱顺序
self.refresh_from_data()
打开所有牌:
def select_all(self):
for x in range(48):
self.layout.select_node(x)
time.sleep(1)
self.layout.clear_selection()
源码地址:https://github.com/babylco0/python3_examples/tree/master/TurnOverGame