首页 后端开发 Python教程 构建非 Todo 应用程序的东西:在线多人贪吃蛇游戏

构建非 Todo 应用程序的东西:在线多人贪吃蛇游戏

Aug 21, 2024 am 06:19 AM

我最近偶然发现了科技影响者“the primeagen”的一句话,我不太记得了,但我记得它是这样的:

“如果你所做的事情没有失败,那么你就没有学习。”

这让我开始思考我的编码之旅。我已经非常熟悉构建后端,甚至可以编写 import express from 'express';已经变成了一件苦差事。

为了构建我的第百万个革命性的待办事项应用程序,我没有经历学习另一个 JavaScript 框架的经典事件(因为显然,世界需要更多这样的应用程序),我决定做点别的事情。我一直在阅读有关 WebSocket 协议的内容,发现它处理服务器和客户端之间双向异步消息的能力非常令人着迷。我想用它构建一些东西,但我需要脱离 JavaScript。

经过一番考虑,我选择了一款简单的多人 2D 游戏。它将涉及计算(碰撞检测)、数据结构(链表、哈希图)和玩家同步。贪吃蛇游戏似乎很完美,有一些简单的规则:

  1. 吃水果可以让你成长,并为你的分数加 1

  2. 撞到其他玩家的身体会让你缩小,随机重置你的位置,并将你的分数归零

  3. 正面碰撞会导致双方玩家缩小,重置位置,并将分数归零

所有这些计算都发生在服务器端,以防止玩家篡改游戏逻辑。我们将使用 Python 3 和 Pygame 进行图形处理,并使用 websockets 库通过 asyncio 处理异步消息。

现在,让我们深入研究您可能会发现它很混乱的代码,因为记住编程的第一条规则:

“如果它有效,请勿触摸它。”

你已经读够了我的咆哮,让我们继续有趣的部分:编码。但如果您想跳过废话并直接投入使用,只需前往 GitHub 存储库即可。
如果您想做出贡献,请随时提出问题或提交拉取请求。欢迎任何改进或错误修复!

首先,我们定义数据结构:

class Object :
    def __init__(self , x : float , y :float ,  width:int , height :int):

        ####################################
        #  init object's size and postion  #           
        ####################################
        self.x = x
        self.y = y 
        self.height = height
        self.width = width

    def render(self , screen , color) :
        pygame.draw.rect(screen ,color ,pygame.Rect(self.x , self.y , self.width , self.height))

class Player(Object) :

    def __init__(self, x: float, y: float, width: int, height: int):
        super().__init__(x, y, width, height)
        self.next = None
        self.prev = None
        self.tail = self
        self.direction = 'LEFT'
        self.length = 1
        self.color = 'red'

    # move the Snake to a certain direction
    # the "changed" will be a way to tell  either to continue in the same direction 
    # or change the direction of the head to the new direction
    # it is used in the game

    def change_direction(self, keys):
        changed = False
        if self.direction in ['LEFT', 'RIGHT']:
            if keys[pygame.K_w] and self.direction != 'DOWN':
                self.direction = 'UP'
                changed = True
            elif keys[pygame.K_s] and self.direction != 'UP':
                self.direction = 'DOWN'
                changed = True
        elif self.direction in ['UP', 'DOWN']:
            if keys[pygame.K_a] and self.direction != 'RIGHT':
                self.direction = 'LEFT'
                changed = True
            elif keys[pygame.K_d] and self.direction != 'LEFT':
                self.direction = 'RIGHT'
                changed = True
        return changed  

    # move the Snake to a certain direction with a certain speed
    def move(self, screen, dt):
        speed = 150 * dt 
        if self.direction == 'UP':
            self.move_all(screen, 0, -speed)
        elif self.direction == 'DOWN':
            self.move_all(screen, 0, speed)
        elif self.direction == 'LEFT':
            self.move_all(screen, -speed, 0)
        elif self.direction == 'RIGHT':
            self.move_all(screen, speed, 0)
    def bound(self , screen) :
        if self.y < 0  :
            self.y = screen.get_height()
        if self.y > screen.get_height() :
            self.y = 0

        if self.x < 0  :
            self.x = screen.get_width()
        if self.x > screen.get_width() :
            self.x = 0

    def get_pos(self) :
        arr = []
        current = self 
        while current :
            arr.append([current.x , current.y])
            current = current.next
        return arr

    # move the snake and its body to some coordinates 
    def move_all(self, screen, dx, dy):
        # Store old positions
        old_positions = []
        current = self
        while current:
            old_positions.append((current.x, current.y))
            current = current.next

        # Move head
        self.x += dx
        self.y += dy
        self.bound(screen)
        # self.render(screen, self.color)

        # Move body
        current = self.next
        i = 0
        while current:
            current.x, current.y = old_positions[i]
            current.bound(screen)
            # current.render(screen, self.color)
            current = current.next
            i += 1           

    def add(self ):
        new = Player(self.tail.x+self.tail.width+10 ,
                     self.tail.y ,self.tail.width ,                    
                      self.tail.height)
        new.prev = self.tail 
        self.tail.next = new
        self.tail = new
        self.length +=1

    def shrink(self , x , y):
        self.next = None 
        self.tail = self 
        self.length = 1
        self.x = x 
        self.y = y

    # used for the and the opponent player when 
    # receiving its coordinates
    def setall(self , arr) :
        self.x = arr[0][0]
        self.y = arr[0][1]
        self.next = None
        self.tail = self
        current = self 
        for i in range(1 , len(arr)) :
            x = arr[i][0]
            y = arr[i][1]
            new = Player(x ,y ,self.width , self.height)
            current.next = new 
            self.tail = new
            current = current.next 

   # render the whole snake on the screen 
   # used for both the current player and the opponent
    def render_all(self, screen, color):
        current = self        
        if self.next : 
            self.render(screen,'white')
            current = self.next
        while current :
            current.render(screen , color) 
            current = current.next

登录后复制

Object 类是游戏对象的基类,而 Player 类则使用蛇特定的功能扩展了它。 Player 类包含用于改变方向、移动、增大、缩小和渲染蛇的方法。

接下来,我们有游戏逻辑:

import pygame 
from objects import Player
import websockets 
import asyncio
import json

uri = 'ws://localhost:8765'

pygame.font.init() 


# Render the text on a transparent surface
font = pygame.font.Font(None, 36)

# playing the main theme (you should hear it)
def play() :
    pygame.mixer.init()
    pygame.mixer.music.load('unknown.mp3')
    pygame.mixer.music.play()

def stop():
    if pygame.mixer.music.get_busy():  # Check if music is playing
        pygame.mixer.music.stop()

# initialize players and the fruit
def init(obj) :
    print(obj)
    player = Player(*obj['my'][0])
    opp = Player(*obj['opp'][0] )
    food = Food(*obj['food'])
    return (player , opp , food)

async def main():

    async with websockets.connect(uri) as ws:
        choice = int(input('1 to create a room \n2 to join a room\n>>>'))
        room_name = input('enter room name: ')
        await ws.send(json.dumps({
            "choice" : choice ,
            "room" : room_name
        }))

        ## waiting for the other player to connecet
        res = {}
        while True:
            res = await ws.recv()
            try:
                res = json.loads(res)

                break 
            except Exception as e:
                pass

        player, opp, food = init(res) 
        pygame.init()
        screen = pygame.display.set_mode((600, 400))
        clock = pygame.time.Clock()
        running = True
        dt = 0
        play()
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

            screen.fill("black")
            my_pos = {
                'pos': player.get_pos(), 
                'len': player.length
            }
            await ws.send(json.dumps(my_pos))
            response = await ws.recv()
            response = json.loads(response)

            # Update food position
            pygame.draw.rect(screen ,'green' ,pygame.Rect(response['food'][0] ,response['food'][1] ,20, 20))
            # Handle actions
            if response['act'] == 'grow':
                player.add()
            elif response['act'] == 'shrinkall':
                player.shrink(*response['my'][0][0:2])
                opp.shrink(*response['opp'][0][0:2])
            elif response['act'] == 'shrink':
                player.shrink(*response['my'][0][0:2])
                # restarting the song each time you bump into the other player
                stop()
                play()
            else:
                opp.setall(response['opp'])

            # Render everything once per frame 
            player.render_all(screen, 'red')
            opp.render_all(screen, 'blue')

            ## score
            ## x | y => x you , y opponent
            text = font.render(f'{response["my_score"]} | {response["other_score"]}', True, (255, 255, 255))

            screen.blit(text, (0, 0)) 
            pygame.display.flip()

            keys = pygame.key.get_pressed()
            changed = player.change_direction(keys)
            # keep moving in the same direction if it is not changed
            if not changed:
                player.move(screen, dt)

            dt = clock.tick(60) / 1000

        pygame.quit()

asyncio.run(main())
登录后复制

这将连接您的服务器并让您创建并加入一个房间,它会将您的位置更新发送到服务器并获取对手的位置和水果位置以及对玩家发出命令的行为要么缩小,要么增长

最后是服务器代码:

import asyncio
import websockets 
import random
import json

def generate_food_position(min_x, max_x, min_y, max_y):
    x = random.randint(min_x, max_x)
    y = random.randint(min_y, max_y)
    return [x, y, 20, 20]

rooms  = {}

def collide(a , b , width):
    return (abs(a[0] - b[0]) < width and
            abs(a[1] - b[1]) < width)

# detecting possible collides : 
def collides(a , b , food ) :
    head_to_head = collide(a[0] , b[0] ,30) ;
    head_to_food = collide(a[0] , food ,25  ) 
    head_to_body = False 
    this_head = a[0]
    for part in b :
        if collide(this_head , part  ,30) :
            head_to_body = True 
            break 

    return (head_to_head , head_to_body , head_to_food)

# return response as (act ,opponents position(s) , my position(s) , the food and the scores)
def formulate_response(id , oid , food , roomName) :
    this= rooms[roomName][id]['pos']
    other= rooms[roomName][oid]['pos']
    hh , hb , hf = collides(this ,other ,food)
    act = 'None'
    if hh :
        act = 'shrink' 
        rooms[roomName][id]['pos'] = initPlayer()
        rooms[roomName][id]['score'] =0 
        # rooms[roomName][oid]['pos'] = initPlayer()
        # rooms[roomName][oid]['respawn'] = True

    elif hb :
        act = 'shrink' 
        rooms[roomName][id]['pos'] = initPlayer()
        rooms[roomName][id]['score'] = 0   
    elif hf :
        act = 'grow'
        rooms[roomName]['food'] =  generate_food_position(20, 580, 20, 380) 
        rooms[roomName][id]['score']+=1
    return  {
        'act' : act , 
        'opp' : rooms[roomName][oid]['pos'] , 
        'my'  : rooms[roomName][id]['pos'] ,
        'food': rooms[roomName]['food'] ,
        'my_score' : rooms[roomName][id]['score'] ,
        'other_score' : rooms[roomName][oid]['score']
    }




def initPlayer():
    return [[random.randint(30 , 600 ) , random.randint(30 , 400 ) , 30 , 30 ]] 


async def handler(websocket) :

    handshake = await websocket.recv()
    handshake = json.loads(handshake)  

    roomName = handshake["room"]
    if handshake['choice'] == 1 :
        rooms[roomName] = {}
        rooms[roomName]['food'] =generate_food_position(30 ,570 ,30 ,370)

    rooms[roomName][websocket.id] = {
            'socket' : websocket , 
            'pos' : initPlayer() , 
            'respawn' : False ,
            'score' :0
    }
    if len(rooms[roomName]) >= 3 :
        await broadcast(rooms[roomName])
    id = websocket.id

    while True :
        room = rooms[roomName]
        this_pos = await websocket.recv()

        ## synchrnisation issue with this 
        ## after couple of times they collide head to head
        ## the cordinates shown on the screen aren't same 
        ## as the server 

        if room[id]['respawn']==True :
            rooms[roomName][id]['respawn'] = False


            # generate response :
            response = {
                'act' :  'shrinkall', 
                'my'  :  room[id]['pos']  , 
                'opp' :  get_other_pos(get_other_id(id ,room),room),
                'food': room['food']
            } 
            await websocket.send(json.dumps(response))

        else :
            # update player position  

            this_pos = json.loads(this_pos)

            rooms[roomName][id]['pos'] = this_pos['pos']
            rooms[roomName][id]['len'] = this_pos['len']

            other_id = get_other_id(id , room)
            food = room['food']
            response = formulate_response(id ,other_id ,food  ,roomName)  


        await websocket.send(json.dumps(response))

def get_other_id(id , room) :
    for thing in room.keys():
        if thing != 'food' and thing != id :
            return thing
def get_other_pos(id , room) : 
    return room[id]['pos']

async def broadcast(room) :
    for thing in room.keys() :
        if thing!= 'food' :
            init = {
                'my' : room[thing]['pos'] , 
                'opp' : room[get_opp(thing, room)]['pos'] ,
                'food': room['food']
            }
            await room[thing]['socket'].send(json.dumps(init))

def get_opp(id  , room) :
     for thing in room.keys() :
        if thing!= 'food' and thing != id:
            return thing


async def main():
    async with websockets.serve(handler , 'localhost' ,8765 ):
        await asyncio.Future()



if __name__ == '__main__' :
    print('listenning  ... ')
    asyncio.run(main())
登录后复制

它处理玩家的移动、碰撞和交互。它生成食物、检测碰撞并同步玩家之间的游戏状态。服务器还管理玩家连接并推送游戏更新

Building Something That
这是一款多人贪吃蛇游戏,它可能会成为游戏世界的下一个重大事件。谁知道呢,它甚至可能会出现在下一次大型科技会议上!
现在,为什么不尝试一下,看看可以添加什么?查看 GitHub 存储库并在贪吃蛇游戏的下一个重大事件中留下您的印记。
快乐编码!

以上是构建非 Todo 应用程序的东西:在线多人贪吃蛇游戏的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
4 周前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆树的耳语 - 如何解锁抓钩
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
Python与C:学习曲线和易用性 Python与C:学习曲线和易用性 Apr 19, 2025 am 12:20 AM

Python更易学且易用,C 则更强大但复杂。1.Python语法简洁,适合初学者,动态类型和自动内存管理使其易用,但可能导致运行时错误。2.C 提供低级控制和高级特性,适合高性能应用,但学习门槛高,需手动管理内存和类型安全。

学习Python:2小时的每日学习是否足够? 学习Python:2小时的每日学习是否足够? Apr 18, 2025 am 12:22 AM

每天学习Python两个小时是否足够?这取决于你的目标和学习方法。1)制定清晰的学习计划,2)选择合适的学习资源和方法,3)动手实践和复习巩固,可以在这段时间内逐步掌握Python的基本知识和高级功能。

Python vs.C:探索性能和效率 Python vs.C:探索性能和效率 Apr 18, 2025 am 12:20 AM

Python在开发效率上优于C ,但C 在执行性能上更高。1.Python的简洁语法和丰富库提高开发效率。2.C 的编译型特性和硬件控制提升执行性能。选择时需根据项目需求权衡开发速度与执行效率。

Python vs. C:了解关键差异 Python vs. C:了解关键差异 Apr 21, 2025 am 12:18 AM

Python和C 各有优势,选择应基于项目需求。1)Python适合快速开发和数据处理,因其简洁语法和动态类型。2)C 适用于高性能和系统编程,因其静态类型和手动内存管理。

Python标准库的哪一部分是:列表或数组? Python标准库的哪一部分是:列表或数组? Apr 27, 2025 am 12:03 AM

pythonlistsarepartofthestAndArdLibrary,herilearRaysarenot.listsarebuilt-In,多功能,和Rused ForStoringCollections,而EasaraySaraySaraySaraysaraySaraySaraysaraySaraysarrayModuleandleandleandlesscommonlyusedDduetolimitedFunctionalityFunctionalityFunctionality。

Python:自动化,脚本和任务管理 Python:自动化,脚本和任务管理 Apr 16, 2025 am 12:14 AM

Python在自动化、脚本编写和任务管理中表现出色。1)自动化:通过标准库如os、shutil实现文件备份。2)脚本编写:使用psutil库监控系统资源。3)任务管理:利用schedule库调度任务。Python的易用性和丰富库支持使其在这些领域中成为首选工具。

科学计算的Python:详细的外观 科学计算的Python:详细的外观 Apr 19, 2025 am 12:15 AM

Python在科学计算中的应用包括数据分析、机器学习、数值模拟和可视化。1.Numpy提供高效的多维数组和数学函数。2.SciPy扩展Numpy功能,提供优化和线性代数工具。3.Pandas用于数据处理和分析。4.Matplotlib用于生成各种图表和可视化结果。

Web开发的Python:关键应用程序 Web开发的Python:关键应用程序 Apr 18, 2025 am 12:20 AM

Python在Web开发中的关键应用包括使用Django和Flask框架、API开发、数据分析与可视化、机器学习与AI、以及性能优化。1.Django和Flask框架:Django适合快速开发复杂应用,Flask适用于小型或高度自定义项目。2.API开发:使用Flask或DjangoRESTFramework构建RESTfulAPI。3.数据分析与可视化:利用Python处理数据并通过Web界面展示。4.机器学习与AI:Python用于构建智能Web应用。5.性能优化:通过异步编程、缓存和代码优

See all articles