标题有点长
其实标题是:《使用Opencv-Python实现2048小游戏》
但这么写谁看呢?哈哈哈哈
不过当然是涉及到了标题所说的一些问题。
源码位置:https://github.com/RainkLH/2048GameByOpencv_Python/blob/master/opcv2048.py
目录
2 Opencv-Python响应鼠标事件(游戏的滑动操作)
3 Python数组随机取元素(每滑动一次随机产生新的数字块)
本文代码为Python语言,需要如下模块
import cv2
import numpy as np
import random
接着,要实现2048小游戏,需要一些全局变量
# 用于随机产生新元素 为了方便调整概率以列表形式创建
new_nums = [1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3]
new_vals = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4]
# 4x4的游戏数据
data = np.array([[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]])
# 用于记录鼠标滑动的数据: 开始(按下) 和结束(抬起)
m_start = [0, 0]
m_end = [0, 0]
然后是各功能代码
1 Opencv-Python中生成图片
1.1 游戏背景图
在python中,opencv没有“Mat”数据类型,而是直接使用了Numpy中的数组,原本Mat的操作全部变成np数组的操作了。
比如创建一张白色的图像(作为2048小游戏的基础背景图):
# 创建背景图
def create_back():
color = (255, 255, 255)
img = np.array([[color for i in range(450)]for j in range(450)], dtype=np.uint8)
return img
就是一张450x450的白色图像,不展示了
1.2 数字块的图
# 创建色块
def create_block(num):
# 获取数字位数
index = len(str(num))
# 16个格最大数字不会超过2的16次方 也就是65536,也就是5位数,
# 根据数字长度来确定块的颜色、文字大小、粗细、位置
greens = [180, 162, 144, 126, 108]
font_sizes = [2.75, 1.75, 1.25, 1, 0.875]
thickness_s = [6, 5, 4, 3, 2]
color = (54, greens[index - 1], 60)
font_size = font_sizes[index-1]
thickness = thickness_s[index - 1]
pos = (int(24 - (3.3 * index)), int(85 - (6.5 * index)))
# 创建数字块
img = np.array([[color for i in range(100)] for j in range(100)], dtype=np.uint8)
if num > 0:
# 大于0的数字进行绘制
cv2.putText(img, str(num), pos, cv2.FONT_HERSHEY_SIMPLEX, font_size, (255, 255, 255), thickness)
return img
把2、4、8、16……65536一共16个数字的块全部show出来就是这样,随着数字增大,块的颜色加深。
2 Opencv-Python响应鼠标事件(游戏的滑动操作)
Opencv-Python中支持的鼠标事件有以下这些。
事件 | 编号 | 注释 |
---|---|---|
CV_EVENT_MOUSEMOVE | 0 | 滑动 |
EVENT_LBUTTONDOWN | 1 | 左键点击 |
EVENT_RBUTTONDOWN | 2 | 右键点击 |
EVENT_MBUTTONDOWN | 3 | 中间点击 |
EVENT_LBUTTONUP | 4 | 左键释放 |
EVENT_RBUTTONUP | 5 | 右键释放 |
EVENT_MBUTTONUP | 6 | 中间释放 |
EVENT_LBUTTONDBLCLK | 7 | 左键双击 |
EVENT_RBUTTONDBLCLK | 8 | 右键双击 |
EVENT_MBUTTONDBLCLK | 9 | 中间双击 |
这里主要用到 鼠标左键的按下和抬起
第一步,注册鼠标事件函数:
在游戏初始化,启动的时候,定义窗体名称,绑定鼠标事件函数,然后生成最开始的数字块,并显示画面
# 初始化游戏界面
def game_run():
cv2.namedWindow("2048", cv2.WINDOW_AUTOSIZE)
# 绑定鼠标响应函数
cv2.setMouseCallback("2048", mouse_event)
new_elements()
img = game_image()
cv2.imshow("2048", img)
cv2.waitKey(0)
第二步,实现鼠标响应函数:
左键按下记录坐标,左键抬起记录坐标,然后计算前后坐标判断是向哪个方向的滑动,执行相应函数(即移动数字块,相邻且相同的求和),接着再生成新的数字块,跟新界面。
这里有个很不好处理的问题,opencv里,绑定了鼠标事件函数后,只要鼠标再窗口内,就会触发,不论你是移动、双击、点击……函数写不好容易导致“只要鼠标一进入窗口,就会疯狂执行一些操作的问题”。
# 鼠标事件
def mouse_event(event, x, y, flags, param):
global m_start, m_end
if event == cv2.EVENT_LBUTTONDOWN:
m_start = [x, y]
if event == cv2.EVENT_LBUTTONUP:
m_end = [x, y]
dx = m_end[0] - m_start[0]
dy = m_end[1] - m_start[1]
if abs(dx) > abs(dy):
if dx > 0:
to_right()
else:
to_left()
else:
if dy > 0:
to_bottom()
else:
to_top()
img = game_image()
cv2.imshow("2048", img)
cv2.waitKey(100) # 滑动后更新画面,停顿一下再插入新的数字使有动画的感觉
new_elements()
img = game_image()
cv2.imshow("2048", img)
m_start = [0, 0]
m_end = [0, 0]
3 Python数组随机取元素(每滑动一次随机产生新的数字块)
这里主要用到了random模块里面的
random.choice()---->从列表中随机取一个元素
random.sample()------>从列表中随机取指定个数个元素
# 产生新元素
def new_elements():
pos_list_t = np.where(data == 0)
pos_list = [(pos_list_t[0][i], pos_list_t[1][i]) for i in range(len(pos_list_t[0]))]
# 随机一个新元素的个数
nums = random.choice(new_nums)
if nums >= len(pos_list):
nums = 1
# 随机一个新元素的位置
new_poss = random.sample(pos_list, nums)
for pos in new_poss:
# 随机一个新元素的数值
data[pos[0]][pos[1]] = random.choice(new_vals)
4 (上下左右的滑动处理)
前三个就是设计的技术核心,接下来就是算法核心,向一个方向滑动,就是4x4的矩阵中,非零数字向指定方向移动,然后把相邻且相同的数字求和变成一个数字。
以第一行向左滑动一次举个例子,
[1, 0, 1, 2]-->[1, 1, 2, 0]-->[2, 0, 2, 0]-->[2, 2, 0, 0]
代码如下(如果你们有更简单快速的方法,务必留言教教我)
# 向左滑动
def to_left():
for i in range(4):
index = 0
t = [0, 0, 0, 0]
for x in range(3):
if data[i][x] > 0:
t[index] = data[i][x]
data[i][x] = 0
n = x + 1
while data[i][n] == 0 and n < 3:
n = n + 1
if t[index] == data[i][n]:
t[index], data[i][n] = t[index] + data[i][n], 0
x += 1
index += 1
t[index] = data[i][3]
data[i] = t
# 向右
def to_right():
for i in range(4):
index = 3
t = [0, 0, 0, 0]
for x in range(3, 0, -1):
if data[i][x] > 0:
t[index] = data[i][x]
data[i][x] = 0
n = x - 1
while data[i][n] == 0 and n >= 0:
n = n - 1
if t[index] == data[i][n]:
t[index], data[i][n] = t[index] + data[i][n], 0
x -= 1
index -= 1
t[index] = data[i][0]
data[i] = t
# 向上
def to_top():
for i in range(4):
index = 0
t = [0, 0, 0, 0]
for x in range(3):
if data[x][i] > 0:
t[index] = data[x][i]
data[x][i] = 0
n = x + 1
while data[n][i] == 0 and n < 3:
n = n + 1
if t[index] == data[n][i]:
t[index], data[n][i] = t[index] + data[n][i], 0
x += 1
index += 1
t[index] = data[3][i]
data[:, i] = t
# 向下
def to_bottom():
for i in range(4):
index = 3
t = [0, 0, 0, 0]
for x in range(3, 0, -1):
if data[x][i] > 0:
t[index] = data[x][i]
data[x][i] = 0
n = x - 1
while data[n][i] == 0 and n >= 0:
n = n - 1
if t[index] == data[n][i]:
t[index], data[n][i] = t[index] + data[n][i], 0
x -= 1
index -= 1
t[index] = data[0][i]
data[:, i] = t
5 (其他函数和调用)
还需要一个根据当前游戏数data[4x4]来生成游戏界面的函数
# 生成当前画面
def game_image():
img = create_back()
for i in range(4):
for j in range(4):
x_start = 10 + 110 * i
y_start = 10 + 110 * j
x_end = x_start + 100
y_end = y_start + 100
block = create_block(data[i][j])
img[x_start:x_end, y_start:y_end] = block
return img
最后,进行调用启动游戏
if __name__ == "__main__":
game_run()
画面如下
尾巴:当然还有很多Bug,这里只是整理一些技术,玩一玩就好,真要写游戏肯定也不能拿Opencv来搞的啊。