打造GUI文件共享系统与自制街机游戏
一、GUI文件共享系统
在文件共享领域,我们可以构建一个带有图形用户界面(GUI)的对等文件共享程序。
1. 运行程序的基本要求
要运行这个程序,需要一个URL文件、一个用于共享文件的目录以及节点的URL。示例运行命令如下:
$ python simple_guiclient.py urlfile.txt files/ http://localhost:8080
注意,
urlfile.txt
文件必须包含其他节点的URL,程序才能正常工作。你可以在同一台机器上以不同端口号启动多个程序进行测试,也可以在不同机器上运行。
2. 第二版实现
第一版原型虽然能实现文件共享功能,但用户体验不佳。第二版将解决文件列表显示的问题。
-
扩展节点类
:为了获取节点的文件列表,我们创建一个
ListableNode
类,它是
Node
的子类,新增了
list
方法:
class ListableNode(Node):
def list(self):
return listdir(self.dirname)
-
客户端更新列表方法
:在客户端添加
updateList方法,用于更新列表框:
def updateList(self):
self.files.Set(self.server.list())
self.files
指的是在
OnInit
方法中添加的列表框。
updateList
方法在列表框创建时调用,并且每次调用
fetchHandler
时也会调用,因为
fetchHandler
可能会改变文件列表。
3. 完整代码示例(guiclient.py)
from xmlrpclib import ServerProxy, Fault
from server import Node, UNHANDLED
from client import randomString
from threading import Thread
from time import sleep
from os import listdir
import sys
import wx
HEAD_START = 0.1 # Seconds
SECRET_LENGTH = 100
class ListableNode(Node):
"""
An extended version of Node, which can list the files
in its file directory.
"""
def list(self):
return listdir(self.dirname)
class Client(wx.App):
"""
The main client class, which takes care of setting up the GUI and
starts a Node for serving files.
"""
def __init__(self, url, dirname, urlfile):
"""
Creates a random secret, instantiates a ListableNode with that secret,
starts a Thread with the ListableNode's _start method (making sure the
Thread is a daemon so it will quit when the application quits),
reads all the URLs from the URL file and introduces the Node to
them. Finally, sets up the GUI.
"""
# Give the server a head start:
sleep(HEAD_START)
self.server = ServerProxy(url)
super(Client, self).__init__()
self.secret = randomString(SECRET_LENGTH)
n = ListableNode(url, dirname, self.secret)
t = Thread(target=n._start)
t.setDaemon(1)
t.start()
for line in open(urlfile):
line = line.strip()
self.server.hello(line)
def updateList(self):
"""
Updates the list box with the names of the files available
from the server Node.
"""
self.files.Set(self.server.list())
def OnInit(self):
"""
Sets up the GUI. Creates a window, a text field, a button, and
a list box, and lays them out. Binds the submit button to
self.fetchHandler.
"""
win = wx.Frame(None, title="File Sharing Client", size=(400, 300))
bkg = wx.Panel(win)
self.input = input = wx.TextCtrl(bkg);
submit = wx.Button(bkg, label="Fetch", size=(80, 25))
submit.Bind(wx.EVT_BUTTON, self.fetchHandler)
hbox = wx.BoxSizer()
hbox.Add(input, proportion=1, flag=wx.ALL | wx.EXPAND, border=10)
hbox.Add(submit, flag=wx.TOP | wx.BOTTOM | wx.RIGHT, border=10)
self.files = files = wx.ListBox(bkg)
self.updateList()
vbox = wx.BoxSizer(wx.VERTICAL)
vbox.Add(hbox, proportion=0, flag=wx.EXPAND)
vbox.Add(files, proportion=1,
flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=10)
bkg.SetSizer(vbox)
win.Show()
return True
def fetchHandler(self, event):
"""
Called when the user clicks the 'Fetch' button. Reads the
query from the text field, and calls the fetch method of the
server Node. After handling the query, updateList is called.
If the query is not handled, an error message is printed.
"""
query = self.input.GetValue()
try:
self.server.fetch(query, self.secret)
self.updateList()
except Fault, f:
if f.faultCode != UNHANDLED: raise
print "Couldn't find the file", query
def main():
urlfile, directory, url = sys.argv[1:]
client = Client(url, directory, urlfile)
client.MainLoop()
if __name__ == '__main__': main()
4. 程序扩展思路
-
添加状态栏
:显示如“正在下载”或“未找到文件
foo.txt”等消息。 - 节点共享“好友” :当一个节点与另一个节点建立连接时,彼此可以介绍自己已知的节点。并且在节点关闭前,告知当前邻居自己所知的所有节点。
- 在GUI中添加已知节点列表 :允许添加新的URL并保存到URL文件中。
二、自制街机游戏
现在,我们将挑战编写自己的街机游戏,使用Python的Pygame扩展库。
1. 游戏设计
以“Self - Defense Against Fresh Fruit”为灵感,设计一个名为“Squish”的游戏,玩家控制香蕉躲避从上方掉落的16吨重物。
2. 具体目标
- 游戏应按设计运行,香蕉可移动,重物从上方掉落。
- 代码应模块化且易于扩展,游戏状态(如游戏介绍、不同关卡、游戏结束状态)应作为设计的一部分,并且易于添加新状态。
3. 有用工具
仅需从Pygame网站(http://pygame.org)下载Pygame。在UNIX系统上使用可能需要安装额外软件,安装说明可在Pygame网站找到。Windows二进制安装程序使用简单,只需执行安装程序并按提示操作。
4. Pygame模块介绍
| 模块 | 功能 |
|---|---|
pygame
|
自动导入其他Pygame模块,包含
Surface
函数用于创建新的表面对象,
init
函数初始化所有模块,
error
类用于捕获特定错误。
|
pygame.locals
| 包含事件类型、键、视频模式等名称,可安全地全部导入。 |
pygame.display
|
处理Pygame显示,包含
flip
、
update
、
set_mode
、
set_caption
、
get_surface
等函数。
|
pygame.font
|
包含
Font
函数,用于创建字体对象。
|
pygame.sprite
|
包含
Sprite
和
Group
类,
Sprite
是所有可见游戏对象的基类,
Group
用于管理
Sprite
对象。
|
pygame.mouse
| 用于隐藏鼠标光标和获取鼠标位置。 |
pygame.event
|
跟踪各种事件,使用
pygame.event.get
获取最新事件列表。
|
pygame.image
|
用于处理图像,使用
load
函数读取图像文件。
|
5. 准备工作
- 确保安装了Pygame,包括图像和字体模块。可以在交互式Python解释器中导入进行验证。
- 准备两张图像,一张是16吨重物的图像,一张是香蕉的图像,尺寸建议在100×100到200×200像素之间,格式可以是GIF、PNG或JPEG。
6. 第一版实现(简单的重物掉落动画)
以下是实现步骤:
graph TD;
A[初始化Pygame] --> B[加载重物图像];
B --> C[创建自定义Weight类实例并添加到组];
C --> D[获取事件并检查退出条件];
D --> E[更新精灵组];
E --> F[绘制精灵];
F --> G[更新显示];
G --> D;
代码示例(weights.py):
import sys, pygame
from pygame.locals import *
from random import randrange
class Weight(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
# image and rect used when drawing sprite:
self.image = weight_image
self.rect = self.image.get_rect()
self.reset()
def reset(self):
"""
Move the weight to a random position at the top of the screen.
"""
self.rect.top = -self.rect.height
self.rect.centerx = randrange(screen_size[0])
def update(self):
"""
Update the weight for display in the next frame.
"""
self.rect.top += 1
if self.rect.top > screen_size[1]:
self.reset()
# Initialize things
pygame.init()
screen_size = 800, 600
pygame.display.set_mode(screen_size, FULLSCREEN)
pygame.mouse.set_visible(0)
# Load the weight image
weight_image = pygame.image.load('weight.png')
weight_image = weight_image.convert() # ...to match the display
# Create a sprite group and add a Weight
sprites = pygame.sprite.RenderUpdates()
sprites.add(Weight())
# Get the screen surface and fill it
screen = pygame.display.get_surface()
white = (255, 255, 255)
screen.fill(white)
pygame.display.flip()
while 1:
# Check for quit events:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN and event.key == K_ESCAPE:
sys.exit()
# Update all sprites:
sprites.update()
# Draw all sprites:
updates = sprites.draw(screen)
# Update the necessary parts of the display:
pygame.display.update(updates)
运行命令:
$ python weights.py
确保
weights.py
和
weight.png
在当前目录下。
7. 代码解释
-
精灵对象属性
:所有精灵对象应有
image和rect属性,分别用于存储图像和矩形对象,通过修改rect属性可以移动精灵。 - Surface对象的convert方法 :用于创建具有不同颜色模型的副本,无参数调用时创建的表面对象更适合当前显示,显示速度更快。
-
颜色表示
:颜色通过RGB三元组表示,如
(255, 255, 255)表示白色。
打造GUI文件共享系统与自制街机游戏
三、深入探索文件共享系统扩展
为了让文件共享系统更加完善和实用,我们可以进一步深入探讨之前提出的扩展思路,并给出更详细的实现方向。
1. 添加状态栏
在GUI中添加状态栏可以实时反馈系统的运行状态,增强用户体验。以下是一个简单的实现步骤:
- 在
OnInit
方法中创建状态栏:
def OnInit(self):
# ...原有代码...
win = wx.Frame(None, title="File Sharing Client", size=(400, 300))
bkg = wx.Panel(win)
# 创建状态栏
status_bar = win.CreateStatusBar()
# ...原有代码...
return True
-
在
fetchHandler方法中更新状态栏信息:
def fetchHandler(self, event):
query = self.input.GetValue()
try:
# 设置状态栏信息为正在下载
self.GetTopLevelParent().SetStatusText("Downloading...")
self.server.fetch(query, self.secret)
self.updateList()
# 设置状态栏信息为下载完成
self.GetTopLevelParent().SetStatusText("Download completed.")
except Fault, f:
if f.faultCode != UNHANDLED: raise
# 设置状态栏信息为未找到文件
self.GetTopLevelParent().SetStatusText(f"Couldn't find the file {query}")
2. 节点共享“好友”
当节点之间建立连接时,彼此可以交换已知节点的信息,从而扩大文件共享的范围。以下是一个简单的实现思路:
- 在
ListableNode
类中添加一个方法,用于接收并存储其他节点的信息:
class ListableNode(Node):
def __init__(self, url, dirname, secret):
super(ListableNode, self).__init__(url, dirname, secret)
self.friends = []
def add_friend(self, friend_url):
if friend_url not in self.friends:
self.friends.append(friend_url)
def share_friends(self, other_node):
for friend in self.friends:
other_node.add_friend(friend)
for friend in other_node.friends:
self.add_friend(friend)
-
在节点建立连接时调用
share_friends方法:
def __init__(self, url, dirname, urlfile):
# ...原有代码...
for line in open(urlfile):
line = line.strip()
self.server.hello(line)
# 假设存在一个方法可以获取对方节点对象
other_node = get_other_node(line)
n.share_friends(other_node)
# ...原有代码...
- 在节点关闭前,将自己的好友信息告知邻居:
import atexit
class ListableNode(Node):
def __init__(self, url, dirname, secret):
# ...原有代码...
atexit.register(self.notify_friends_on_exit)
def notify_friends_on_exit(self):
for friend in self.friends:
try:
friend_node = ServerProxy(friend)
friend_node.receive_friends(self.friends)
except Exception as e:
print(f"Error notifying {friend}: {e}")
3. 在GUI中添加已知节点列表
在GUI中添加一个列表来显示已知节点的URL,并允许用户添加新的URL和保存到文件中。以下是实现步骤:
- 在
OnInit
方法中添加节点列表框和添加按钮:
def OnInit(self):
# ...原有代码...
node_list = wx.ListBox(bkg)
add_button = wx.Button(bkg, label="Add Node")
add_button.Bind(wx.EVT_BUTTON, self.add_node_handler)
save_button = wx.Button(bkg, label="Save Nodes")
save_button.Bind(wx.EVT_BUTTON, self.save_nodes_handler)
# 布局调整
node_box = wx.BoxSizer(wx.HORIZONTAL)
node_box.Add(node_list, proportion=1, flag=wx.ALL | wx.EXPAND, border=10)
button_box = wx.BoxSizer(wx.VERTICAL)
button_box.Add(add_button, flag=wx.ALL, border=5)
button_box.Add(save_button, flag=wx.ALL, border=5)
node_box.Add(button_box, flag=wx.ALL, border=10)
vbox.Add(node_box, proportion=1, flag=wx.EXPAND)
# ...原有代码...
return True
- 实现添加节点和保存节点的处理方法:
def add_node_handler(self, event):
dlg = wx.TextEntryDialog(self.GetTopLevelParent(), "Enter Node URL:")
if dlg.ShowModal() == wx.ID_OK:
node_url = dlg.GetValue()
self.server.hello(node_url)
self.node_list.Append(node_url)
dlg.Destroy()
def save_nodes_handler(self, event):
with open('urlfile.txt', 'w') as f:
for i in range(self.node_list.GetCount()):
f.write(self.node_list.GetString(i) + '\n')
wx.MessageBox("Nodes saved successfully!", "Info", wx.OK | wx.ICON_INFORMATION)
四、街机游戏的进一步开发
在完成简单的重物掉落动画后,我们可以逐步完善“Squish”游戏,添加香蕉角色和碰撞检测等功能。
1. 添加香蕉角色
创建一个
Banana
类,继承自
pygame.sprite.Sprite
,并在游戏中添加香蕉精灵。
class Banana(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('banana.png')
self.image = self.image.convert()
self.rect = self.image.get_rect()
self.rect.bottom = screen_size[1]
self.rect.centerx = screen_size[0] // 2
def update(self):
pos = pygame.mouse.get_pos()
self.rect.centerx = pos[0]
在主程序中添加香蕉精灵:
# ...原有代码...
# 加载香蕉图像
banana_image = pygame.image.load('banana.png')
banana_image = banana_image.convert()
# 创建香蕉精灵并添加到组
banana = Banana()
sprites.add(banana)
# ...原有代码...
2. 碰撞检测
添加碰撞检测功能,当重物与香蕉发生碰撞时,游戏结束。
while 1:
# ...原有代码...
# 检测碰撞
if pygame.sprite.collide_rect(banana, sprites.sprites()[0]):
print("Game Over!")
sys.exit()
# ...原有代码...
3. 游戏状态管理
为了实现游戏的不同状态(如游戏介绍、游戏进行中、游戏结束),我们可以使用状态机来管理。以下是一个简单的状态机实现:
class GameState:
def __init__(self):
self.state = "intro"
def set_state(self, new_state):
self.state = new_state
def get_state(self):
return self.state
game_state = GameState()
while 1:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
if event.type == KEYDOWN and event.key == K_ESCAPE:
sys.exit()
if event.type == KEYDOWN and event.key == K_RETURN:
if game_state.get_state() == "intro":
game_state.set_state("playing")
elif game_state.get_state() == "game_over":
game_state.set_state("intro")
if game_state.get_state() == "intro":
# 显示游戏介绍界面
pass
elif game_state.get_state() == "playing":
# 游戏进行中
sprites.update()
updates = sprites.draw(screen)
pygame.display.update(updates)
if pygame.sprite.collide_rect(banana, sprites.sprites()[0]):
game_state.set_state("game_over")
elif game_state.get_state() == "game_over":
# 显示游戏结束界面
pass
通过以上步骤,我们可以不断扩展和完善文件共享系统和街机游戏,让它们更加实用和有趣。在实际开发中,还可以根据自己的需求添加更多的功能和优化,发挥无限的创意。
超级会员免费看
55

被折叠的 条评论
为什么被折叠?



