闲来蛋疼用python不调用第三方库写了一个贪吃蛇玩玩。
输出通过print()和os.system('cls')清屏实现。用threading库分了两个线程,一个线程负责处理按键输入和处理一些与游戏非直接交互的按键输入,一个线程负责主游戏线程。画面通过维护一个二维列表实现,蛇通过维护一个队列实现,队列的头为蛇头。通过输出ANSI转义的\033实现画面现实。
2024.10.27:修复了一个bug,在input_thread()里增加了输入限制。
import threading
import time
import msvcrt
from os import system
from collections import deque
import random
# 用于存储输入的字符
input_char = 'd'
input_lock = threading.Lock()
should_exit = False # 用于指示程序是否应该结束
map_length=10 #游戏地图长度
map_width=10 #游戏地图宽度
#地图,0是空区域,1是食物,2是蛇头,3是蛇身
map_list=[[0 for _ in range(map_width)] for _ in range(map_length)]
snake=deque([[0, 0], [0, 0]]) #右侧第一个为蛇头
apple = [random.randint(1, map_length-1), random.randint(1, map_width-1)] # 初始化apple的位置
map_list[apple[0]][apple[1]]=1 #初始化apple
map_colors={0:'\033[34;107m \033[0m',
1:'\033[31;107mѼ \033[0m', #Ѽ一个奇怪的字符,占位只占一格,但显示的时候显示两个,后面跟个其他字符,字符会出现再后半个Ѽ中
2:'\033[34;107m֎ \033[0m',
3:'\033[34;107m()\033[0m',}
move={'w':[-1,0], #移动
's':[1,0],
'a':[0,-1],
'd':[0,1],}
speed=20 #用int类型处理float类型出现的浮点误差,用到时再/10
lenth=4
is_paused=False
# 输入线程
def input_thread():
global input_char, should_exit, speed,is_paused
while not should_exit:
char = msvcrt.getch()
if char not in [b'w',b's',b'a',b'd',b'q',b'e',b' ',b'\x1b']: #过滤输入
pass
else:
char=char.decode('utf-8')
with input_lock:
input_char = char
if char == '\x1b': # 检查是否按下 ESC 键
should_exit = True # 设置退出标志
break # 退出循环
elif char == 'q':
speed+=2
elif char == 'e' and speed >2:
speed-=2
elif char == ' ': # 检查空格暂停
is_paused = not is_paused
# 输出线程/主游戏线程
def output_thread():
global input_char, should_exit, speed,is_paused,lenth
last_char = 'd' #初始化移动方向
while not should_exit: # 检查退出标志
if is_paused: # 检查是否处于暂停状态
system('cls')
print('移动方向: ' + last_char)
for i in map_list:
for k in i:
print(map_colors[k], end='')
print()
print('游戏已暂停. 按空格键继续...')
print('ESC:结束 WASD:移动 q:加速 e:减速 space:暂停/开始')
print('长度' + str(lenth) + ' 速度:' + str(speed/10) + '格/秒')
print('http://8.130.137.28')
time.sleep(10/speed)
continue
with input_lock:
if input_char is not None and input_char in ['a','w','s','d','\x1b',]: #限制输入
if not ((input_char == 'w' and last_char == 's') or (input_char == 's' and last_char == 'w') or
(input_char == 'a' and last_char == 'd') or (input_char == 'd' and last_char == 'a')): #不让蛇掉头自杀
last_char = input_char
if last_char in ['a','w','s','d']: #控制移动
new_postion=[(snake[-1][0]+move[last_char][0])%map_length,(snake[-1][1]+move[last_char][1])%map_width] #计算新位置
#print(new_postion)
snake.append(new_postion) #更新蛇
if map_list[new_postion[0]][new_postion[1]]!=1: #吃到apple
lost_body=snake.popleft()
map_list[lost_body[0]][lost_body[1]]=0
#刷新apple
else:
if lenth == map_width * map_length - 1: # 填满屏幕胜利
should_exit = True
print("You Win")
break
apple_list=[] #随机更新apple,遍历出所有为0的点,在抽一个为苹果
for i in range(len(map_list)):
for j in range(len(map_list[i])):
if map_list[i][j] == 0:
apple_list.append((i, j))
apple=random.choice(apple_list)
while map_list[apple[0]][apple[1]] == 0:
map_list[apple[0]][apple[1]] = 1
''' #另一个方案,直接随机苹果位置,直到那个苹果的位置为0
apple = [random.randint(0, 9), random.randint(0, 9)]
while map_list[apple[0]][apple[1]]==0:
map_list[apple[0]][apple[1]]=1
'''
#print(snake)
if map_list[new_postion[0]][new_postion[1]]==3: #判定撞到蛇身失败
should_exit=True
print("Game Over")
#print('多按几次回车结束')
break
map_list[snake[-1][0]][snake[-1][1]] = 2
map_list[snake[-2][0]][snake[-2][1]] = 3
lenth=len(snake) #更新长度
system('cls') #清楚画面,重画
# print('\n'*15)
print('移动方向: '+last_char)
for i in map_list:
for k in i:
print(map_colors[k], end='')
print()
print()
print('ESC:结束 WASD:移动 q:加速 e:减速 space:暂停/开始')
print('长度'+str(lenth)+' 速度:'+str(speed/10)+'格/秒')
print('https://blog.yakumoran.top')
time.sleep(10/speed)
# 创建线程
thread1 = threading.Thread(target=input_thread, daemon=True)
thread2 = threading.Thread(target=output_thread, daemon=True)
# 启动线程
thread1.start()
thread2.start()
# 主线程等待子线程完成
thread1.join()
thread2.join()
for i in range(5):
x=input('多按几次回车结束')