Python權威指南的10個項目(6~10)

6. 項目6:使用CGI進行遠程編輯

  這個項目主要用的是的CGI進行遠程編輯——在另一臺機器上通過Web來編輯 文檔。你在一臺機器上存儲了一個文檔,希望能夠在另一臺機器上通過Web來編輯它。這讓多個用 戶能夠協作編輯一個文檔,且無需使用FTP或類似的文件傳輸技術,也無需操心同步多個副本的 問題。要編輯文件,只要有Web瀏覽器就行。

(1) 問題描述

  • 能夠以普通網頁的方式顯示文檔。
  • 能夠在Web表單的文本區域內顯示文檔
  • 用戶能夠保存表單中的文本
  • 程序應使用密碼對文檔進行保護
  • 程序應易於擴展,以支持對多個文檔進行編輯

    (2) 工作準備

  • 模塊cgi以及用於調試的模塊cgitb

    (3) 初次實現

    這個簡單的程序的邏輯大概如下:
       - 獲取CGI參數text(默認爲數據文件的當前內容)
       - 將text的值保存到數據文件中
       - 打印表單,其中的文本區域包含text的值
      要讓腳本能夠寫入數據文件,必須先創建這樣的文件(如simple_edit.dat)。這個文件可以爲 空,也可包含初始文檔(純文本文件,其中可能包含一些標記,如XML或HTML)。
       運行之前,首先小編先介紹一下如何在Tomcat中運行Python腳本:
    ① 修改Tomcat的配置文件:web.xml

    <servlet>  
        <servlet-name>cgi</servlet-name>  
        <servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>  
         <init-param>  
         <param-name>debug</param-name>  
         <param-value>0</param-value>  
         </init-param>  
        <init-param>  
          <param-name>cgiPathPrefix</param-name>  
          <param-value>WEB-INF/cgi</param-value>  
        </init-param>  
       <init-param>  
          <param-name>executable</param-name>  
          <param-value>C:\Users\Administrator\AppData\Local\Programs\Python\Python36-32\python.exe</param-value>  
        </init-param>  
         <init-param>  
          <param-name>passShellEnvironment</param-name>  
          <param-value>true</param-value>  
        </init-param>  
        <load-on-startup>5</load-on-startup>  
    </servlet>  
    <servlet-mapping>  
        <servlet-name>cgi</servlet-name>  
        <url-pattern>/cgi-bin/*</url-pattern>  
    </servlet-mapping> 
    #passShellEnvironment: 與Python解析器解析CGI腳本有關,但是一定要配置好Python的環境變量;
    #cgiPathPrefix:  配置訪問的腳本目錄
    #executable: (本地python的安裝路徑)配置Python的解析器

    ② 修改context.xml
    在標籤中加入:
    由於上面web.xml是這樣配置的:
    Python權威指南的10個項目(6~10)
    所以在Tomcat的webapps中創建一個cgitest,然後在cgitest中創建一個WEB-INF,然後在WEB-INF中創建一個cgi文件夾,將編寫的Python腳本放入cgi文件夾中:
    腳本:test.py
    最終路徑:webapps/cgitest/WEB-INF/cgi/test.py
    然後訪問時http://localhost:8080/cgitest/cgi-bin/test.py
    注意這裏的cgi-bin是:
    Python權威指南的10個項目(6~10)
    ③ 重啓Tomcat

#腳本:
#C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe
import cgi
form = cgi.FieldStorage()
text = form.getvalue('text', open('simple_edit.dat').read())
f = open('simple_edit.dat', 'w')
f.write(text)
f.close()
print("Content-type: text/html\n\n")
print("""
<html>
 <head>
 <title>A Simple Editor</title>
 </head>
 <body>
 <form action='simple_edit.py' method='POST'>
 <textarea rows='10' cols='20' name='text'>{}</textarea><br />
 <input type='submit' />
 </form>
 </body>
</html>
""".format(text))

效果:
Python權威指南的10個項目(6~10)
當在輸入框中編輯然後提交後,內容會更新到simple_edit.dat中。

###(4) 再次實現
   至此,第一個原型已編寫好,它還缺什麼呢?應讓用戶能夠編輯多個文件,並使用密碼保護 這些文件。相比於第一個原型,再次實現的主要不同在於,你將把它分成兩個CGI腳本,分別對應於系 統支持的兩種操作。新的原型包含如下文件:

  • index.py:一個普通網頁,包含一個供用戶輸入文件名的表單,還包含一個觸發edit.py 的Open按鈕。
  • edit.py:在文本區域中顯示指定文件的腳本。它還包含一個用於輸入密碼的文本框以及 一個觸發save.py的Save按鈕。
  • save.py:將收到的文本保存到指定的文件並顯示一條簡單消息(如The file has been saved) 的腳本。這個腳本還應負責檢查密碼。
    ① index.py
#C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe
print('Content-type: text/html\n\n')
import cgi, sys

form = cgi.FieldStorage()
print('''
<html>
<head>
    <title>File Editor</title>
</head>
<body>
<form action='edit.py' method='POST'>
    <b>File name:</b><br/>
    <input type='text' name='filename'/>
    <input type='submit' value='Open'/>
</form>
</body>
</html>
''')

   文本框名爲filename,這確保其內容將通過CGI參數filename提供給腳本edit.cgi,如果在文本框中輸入文件名,再 單擊Open按鈕,將運行腳本edit.cgi。
② 編寫編輯器腳本
  腳本edit.cgi顯示的頁面應包含一個文本區域和一個文本框,其中前者包含當前編輯的文件的 內容,而後者用於輸入密碼。這個腳本需要的唯一輸入是文件名,它是從index.html中的表單中獲得的。然而,可在不提交index.html中表單的情況下直接運行腳本edit.py。在這種情況下, cgi.FieldStorage的字段將是未設置的。因此,你需要檢查是否獲得了文件名;如果獲得了,就 打開指定目錄中的這個文件。我們將這個目錄命名爲data(當然,你必須創建這個目錄)。
③ 編寫保存腳本
  這個簡單系統的最後一部分是執行保存的腳本。它接收文件名、密碼和一些文本,並檢查密 碼是否正確;如果正確,就將這些文本存儲到指定的文件中。我們將使用模塊sha來處理密碼。

(5) 結果展示

Python權威指南的10個項目(6~10)

7. 項目7:自建公告板

  本項目實現的是基於Web的論壇,雖然其功能與複雜的社交媒體平臺相距甚遠,但提供了評論系統的基本功能。

(1) 問題描述

  在這個項目中,你將創建一個通過Web發佈和回覆消息的簡單系統,它可作爲論壇使用。這 個系統非常簡單,但提供了基本的功能,並能夠處理大量的帖子。
  本章介紹的技術不僅可用於開發獨立論壇,還可用於實現更通用的協作系統、問題跟蹤系統、 帶評論功能的博客等。通過將支持在消息下方以縮放的方式顯示回覆CGI(或類似的技術)和可靠的數據庫(這裏是SQL數據庫)結合 起來使用,可實現非常強大的功能,而且用途非常廣泛。
最終這個項目要實現的功能:

  • 顯示當前所有消息的主題
  • 支持在消息下方以縮放的方式顯示回覆
  • 讓用戶能夠查看已有的消息
  • 讓用戶能夠回覆已有的消息

    (2) 工作準備

  • Gci+Tomcat
  • 單機數據庫(MySQL/SQLite)本項目使用的是MySQL。(import pymysql)
    ① 建表語句:
    -- 創建MySQL數據庫
    CREATE TABLE messages (
    id INT NOT NULL AUTO_INCREMENT,
    subject VARCHAR(100) NOT NULL,
    sender VARCHAR(15) NOT NULL,
    reply_to INT,
    text MEDIUMTEXT NOT NULL, PRIMARY KEY(id)
    );

    字段介紹:

  • id:用於標識消息。每條消息都會自動獲得由數據庫管理器提供的獨一無二的ID,因此 無需在Python代碼中指定這些ID。
  • subject:包含消息主題的字符串。
  • sender:包含發送者姓名、電子郵箱地址或其他類似信息的字符串。
  • reply_to:如果消息是另一條消息的回覆,這個字段將包含那條消息的id,否則爲空。
  • text:包含消息正文的字符串。

    (3) 初步實現

#連接數據庫腳本:
import pymysql

def create_table(cursor,table_name,create_sql):
    # 使用 execute() 方法執行 SQL,如果表存在則刪除
    drop_table = 'drop table if EXISTS '+table_name
    cursor.execute(drop_table)
    # 建表
    cursor.execute(sql)

def insert(cursor,table_name):
    reply_to = input('Reply to: ')
    subject = input('Subject: ')
    sender = input('Sender: ')
    text = input('Text: ')
    sql='insert into {}(reply_to, sender, subject, text) values({},"{}","{}","{}")'\
        .format(table_name,reply_to, sender, subject, text)
    cursor.execute(sql)

if __name__=='__main__':
    server_host="localhost"
    user="xxxx"
    passwd= "xxxx"
    db_name="test"
    # 打開數據庫連接
    db = pymysql.connect(server_host, user,passwd,db_name)
    # 使用 cursor() 方法創建一個遊標對象 cursor
    cursor = db.cursor()
    #建表
    sql = '''
    CREATE TABLE messages (
     id INT NOT NULL AUTO_INCREMENT,
     subject VARCHAR(100) NOT NULL,
     sender VARCHAR(15) NOT NULL,
     reply_to INT,
     text MEDIUMTEXT NOT NULL, PRIMARY KEY(id)
    )
    '''
    create_table(cursor,"messages",sql)
    #插入數據
    insert(cursor,db_name+".messages")
    db.commit()
    # 關閉連接
    cursor.close()
#cgi腳本:
'''
 公告板主頁
'''

print('Content-type: text/html\n')
import cgitb;

cgitb.enable()

import pymysql

server_host = "localhost"
user = "root"
passwd = "123456"
db_name = "test"
# 打開數據庫連接
db = pymysql.connect(server_host, user, passwd, db_name)
curs = db.cursor()

print("""
<html>
 <head>
 <title>The FooBar Bulletin Board</title>
 </head>
 <body>
 <h1>The FooBar Bulletin Board</h1> 
""")
toplevel = []
children = {}
curs.execute('SELECT * FROM test.messages')
rows = curs.fetchall()
for row in rows:
    parent_id = row[3]
    if parent_id is None:
        toplevel.append(row)
    else:
        children.setdefault(parent_id, []).append(row)

def format(row):
    print(row[1]+'</br>')
    try:
        kids = children[row[0]]
    except KeyError:
        pass
    else:
        print('<blockquote>')
        for kid in kids:
            format(kid)
        print('</blockquote>')

print('<p>')
for row in toplevel:
    format(row)
print("""
 </p>
 </body>
 </html>
 """)

(4) 最終版

  初次實現的功能很有限,用戶甚至不能發佈消息。這裏我們將內容豐富一些:

  • main.py:以層次方式顯示所有消息的主題,並將這些主題作爲到消息本身的鏈接。
  • view.py:顯示一條消息,並提供讓用戶能夠回覆的鏈接。
  • edit.py:以可編輯的方式顯示一條消息(就像第25章那樣使用文本框和文本區域),其中 的Submit按鈕鏈接到腳本save.cgi
  • save.py:從edit.cgi那裏接收有關消息的信息,並通過在數據庫表中插入一個新行來保存 這條消息
      腳本的逐個分析:
    ① main.py
      腳本main.cgi很像第一個原型中的腳本simple_main.cgi,主要差別在於加入了鏈接:每個主題 都鏈接到相應消息(到view.cgi的鏈接);同時在頁面底部添加讓用戶能夠發佈新消息的鏈接(到 edit.cgi的鏈接)。
    ② view.py
      腳本view.cgi根據提供給它的CGI參數id從數據庫獲取一條消息,再使用得到的值來生成一個 簡單的HTML頁面。這個頁面包含一個返回到主頁面(main.py)的鏈接,更有趣的是,它還包 含一個到edit.py的鏈接,但這裏將參數reply_to設置爲id的值,以確保新消息是對當前消息的回覆。
    ③ edit.py
      腳本edit.cgi實際上承擔了雙重職責:既用於編輯新消息,也用於編輯回覆。這兩項功能的差 別並不大:如果在CGI請求中提供了reply_to,就將其存儲在編輯表單中一個隱藏的input元素中。 在Web表單中,隱藏的input元素用於臨時存儲信息。它們不像文本區域等元素那樣是用戶能夠 看到的,但它們的值也將傳遞給表單的屬性action指定的CGI腳本,這讓生成表單的腳本能夠向 處理該表單的腳本傳遞信息。另外,默認將主題設置爲"Re: parentsubject"(除非主題已經以Re:打頭,在這種情況下,不用繼續添加Re。
    ④ save.py
      下面來編寫最後一個腳本。腳本save.py從edit.py生成的表單那裏接收有關一條消息的信息, 並將其存儲到數據庫中。

    (5) 結果展示

    Python權威指南的10個項目(6~10)

    8. 項目8:使用XML-RPC 共享文件

      本項目是一個簡單的文件共享應用程序。我們將使用的主要技術是XML-RPC,這是一種遠程調用過程(函數)的協議, 這種調用可能是通過網絡進行的。如果你願意,可使用普通的套接字編程輕鬆地實現這個項目的功能。

    (1) 問題描述

      我們要創建一個P2P(peer-to-peer)文件共享程序。大致而言,文件共享意味着在運行於不 同計算機上的程序之間交換文件。在P2P交互中,任何對等體(peer)都可連接到其他對等體。在 這樣一個由對等體組成的網絡中,不存在中央權威,這讓網絡更健壯,因爲除非你關閉大部分對等體,否則這樣的網絡不可能崩潰。
    項目滿足條件:

  • 每個節點都必須跟蹤一組已知的節點,以便能夠向這些節點尋求幫助。還必須讓節點能 夠向其他節點介紹自己,從而成爲其他節點跟蹤的節點集中的一員。
  • 節點必須能夠通過提供文件名向其他節點請求文件。如果對方有這樣的文件,應將其返 回,否則應轉而向其鄰居請求這個文件(而這些鄰居可能轉而請其鄰居請求該文件)。被 請求的節點如果有這樣的文件,就將其返回。
  • 爲避免循環(A向B請求,B又反過來向A請求),同時避免形成過長的請求鏈(A向B請求, B向C請求等,直到向Z請求),向節點查詢時必須提供歷史記錄。
  • 必須能夠連接到其他節點,並將自己標識爲可信任方。通過這樣做,節點將能夠使用不 可信任方(如P2P網絡中的其他節點)無法使用的功能。這種功能可能包括請求對方通過 查詢從網絡中的其他節點下載文件並存儲。
  • 必須提供這樣的用戶界面:讓用戶能夠作爲可信任方連接到其他節點,並讓對方下載文 件。這種界面應該能夠輕鬆地擴展乃至替換。

(2) 工作準備

  • 需要的模塊:xmlrpc.client和xmlrpc.server
    • 模塊xmlrpc.client的用法非常簡單,你 只需使用服務器的URL創建一個ServerProxy對象,就能夠馬上訪問遠程過程。
    • 模塊xmlrpc.server 使用起來要複雜些
  • 爲實現這個文件共享程序的界面,我們將使用cmd模塊
  • 爲了並行,我們將使用threading
  • 爲了提取URL,我們將使用urllib.parse模塊

    (3) 初次實現

    這個RPC簡單的說就是遠程調用服務端的方法,接下來我們簡單是實現以下,看看效果:

#Server:
'''
對下面案例的解釋:
方法register_instance註冊一個實現了其“遠程方法”的實例,也
可使用方法register_function註冊各個函數。爲運行服務器做好準備(讓它能夠響應來自外部的
請求)後,調用其方法serve_forever ,方法serve_forever解釋器看起來就像“掛起”了一樣,但實際上它是在等待RPC請求。
'''
from xmlrpc.server import SimpleXMLRPCServer
s=SimpleXMLRPCServer(("",16888)) #localhost和端口4242
def twice(x):
    return x*2
s.register_function(twice)  # 給服務器添加功能,也就是客戶端調用的函數
s.serve_forever() # 啓動服務器
#Server:
'''
對下面案例的解釋:
方法register_instance註冊一個實現了其“遠程方法”的實例,也
可使用方法register_function註冊各個函數。爲運行服務器做好準備(讓它能夠響應來自外部的
請求)後,調用其方法serve_forever ,方法serve_forever解釋器看起來就像“掛起”了一樣,但實際上它是在等待RPC請求。
'''
from xmlrpc.server import SimpleXMLRPCServer
s=SimpleXMLRPCServer(("",16888)) #localhost和端口4242
def twice(x):
    return x*2
s.register_function(twice)  # 給服務器添加功能,也就是客戶端調用的函數
s.serve_forever() # 啓動服務器

先運行server在運行client,我們發現:
在server中出現:
Python權威指南的10個項目(6~10)
而client成功調用了sever的方法,並輸出了結果:4。
是不是挺簡單的,好像URL請求一樣,接下來我們就要根據上面的需求實現相應的功能了:
① 定義node
  回顧需求,我們關心的主要有兩點:Node 必須存儲哪些信息(屬性);Node必須能夠執行哪些操作(方法)。
所有node需要有以下屬性:
  - 目錄名:讓Node知道到哪裏去查找文件或將文件存儲到哪裏。
  -密碼:供其他節點用來將自己標識爲可信任方。
  - 一組已知的對等體(URL)。
  - URL:可能加入到查詢歷史記錄中或提供給其他節點

Node能執行什麼操作呢,必須定義一些方法:
  - 用於查詢的方法(query)
  - 獲取和存儲文件的方法(fetch)
  - 向其他節點介紹自己的方法(hello)

② fetch方法解讀
  這個方法必須接受參數query和 secret,其中secret是必不可少的, 可避免節點被其他節點隨便操縱。調用fetch將導致節 點下載一個文件。如果提供的密碼不同於(啓動時指定的)self.secret,fetch將直接返回FAIL; 否則它將調 用query來獲取指定的文件。調用query時,你希望能夠知道查詢是否 成功,並在成功時返回指定文件的內容。因此,我們將query的返回值定義爲元組(code, data), 其中code的可能取值爲OK和FAIL,而data是一個字符串。如果code爲OK,這個字符串將包含找到 的文件的內容;否則爲一個隨意的值,如空字符串。如果查詢成功,並且返回OK,則開始創建一個文件並開始向其中寫入內容。
③ query方法解讀
query的結構:query→self._handle→_broadcast
  首先調用_handle方法,它負責查詢的內容處理(檢查節點是否包含指定的文件,獲取數據 等),它像query一樣返回一個編碼和一些數據。如果code爲OK,則直接返回,如果爲FAIL,就需要其他節點的幫助因此它需要將self.url 添加到history中,然後通過history向方法_broadcast向所有已知的對等體廣播查詢,它迭代self.known的副本,如果當前對等體包含在history中, 就使用continue語句跳到下一個對等體,否則創建一個ServerProxy對象,並對其調用方法query。 如果方法query成功,就將其返回值作爲_broadcast的返回值。

由於代碼比較長這裏就不給出了,可以在小編的git中下載。
最後我們演示一定這個初級版的程序如何運行:

mypeer:python xxx.py  http://localhost:16888 file1 123456 
otherpeer:python xxx.py  http://localhost:16889 file2 123456

#創建mypeer

Python權威指南的10個項目(6~10)
它訪問file2下的文件:
Python權威指南的10個項目(6~10)
然後創建otherpeer:
Python權威指南的10個項目(6~10)
它訪問file1下的文件:
Python權威指南的10個項目(6~10)
把mypeer介紹給otherpeer:
Python權威指南的10個項目(6~10)
然後在訪問file1下的文件:
Python權威指南的10個項目(6~10)
通過otherpeer拉去file1下的文件:
Python權威指南的10個項目(6~10)
發現file2下有test1.txt:
Python權威指南的10個項目(6~10)

  有朋友想問,這個有啥用啊,我們不妨拓展一下思維,如果這裏是一個集羣,我們用client去訪問server中的文件,但是文件分佈在server集羣的某個節點上,想想我們可不可通過這種方法,把文件傳遞到訪問的節點上,然後從訪問節點中拿取,我的天一不小心瞭解分佈式的原理,低調低調。

(4) 最終實現

最終版我們需要在初次實現中修改一些內容:

  • 如果你停止並重啓一個節點,可能出現錯誤消息,指出端口被佔用。
  • 你可能想提供對用戶更友好的界面,而不是在交互式Python解釋器中使用xmlrpc.client。
  • 返回的編碼不方便,一種更自然、更符合Python風格的解決方案是,在找不到文件時引發 自定義異常。
  • 節點沒有檢查它返回的文件是否包含在文件目錄中。

    模塊解讀:

    ① 創建客戶端界面
      客戶端界面是使用模塊cmd中的Cmd類實現,我們在這裏只實現命令fetch(下載文件)和exit(退出程序)。命令fetch調用服務器的方 法fetch,並在文件沒有找到時打印一條錯誤消息。命令exit打印一個空行(這只是出於美觀考 慮)並調用sys.exit。
    ② 引發異常
      不返回表示成功還是失敗的編碼,而是假定肯定會成功,並在失敗時引發異常。這裏我們使用200正常的失敗(請求未得到處理)和500表示 請求被拒絕(拒絕訪問)。

    UNHANDLED = 200
    ACCESS_DENIED = 500
    class UnhandledQuery(Fault):
     """
     表示查詢未得到處理的異常
     """
     def __init__(self, message="Couldn't handle the query"):
        super().__init__(UNHANDLED, message)
    class AccessDenied(Fault):
     """
     用戶試圖訪問未獲得授權的資源時將引發的異常
     """
     def __init__(self, message="Access denied"):
         super().__init__(ACCESS_DENIED, message)

      異常是xmlrpc.client.Fault的子類。在服務器中引發的異常將傳遞到客戶端,並保持 faultCode不變。如果在服務器中引發了普通異常(如IOException),也將創建一個Fault類實例, 因此你不能在服務器中隨意地使用異常。

③ 驗證文件名
  檢查指定的文件是否包含在指定的目錄中。我們這裏採用較爲簡單的方法實現:
根據目錄名和文件名創建絕對路徑(例如,這將把'/foo/bar/../ baz'轉換爲'/foo/baz'),將目錄名與空文件名合併以確保它以文件分隔符(如'/')結尾,再檢 查絕對文件名是否以絕對路徑名打頭。如果是這樣的,就說明指定的文件包含在指定的目錄中。

(5) 結果展示

  這裏在pychram中運行有諸多bug,使用cmd感覺不是那麼好使,或者可以在Linux下運行試試,這裏小編也給出了一個Cmd運行的dome,大概是這樣的:
Python權威指南的10個項目(6~10)
在Miller2>後面輸入,定義的do_xxx的方法中的xxx,它就會執行相應方法中的內容。
最後小編在這裏說一下這個項目的運行規則:
運行:python client.py urls.txt directory http://servername.com:16888
Python權威指南的10個項目(6~10)
執行錯誤操作:
Python權威指南的10個項目(6~10)
首先查詢所有的能關聯的所有的urls列表,沒有的返回Couldn't find the file 123。
輸入exit 或者EOF會退出程序:
Python權威指南的10個項目(6~10)

9. 項目9:使用GUI共享文件

這個項目較小,你將看到給既 有Python程序添加GUI非常容易。

(1) 問題描述

  開發的文件共享系統:添加GUI客戶端,讓它使用起來更 容易。這意味着可能有更多的人選擇使用它,他實現的需求是:

  • 允許用戶輸入文件名,並將其提交給服務器的方法fecth
  • 列出服務器的文件目錄當前包含哪些文件

    (2) 工作準備

      開發這個項目時,最好是將項目8做一遍之後,然後在熟悉熟悉GUI的工具包的使用。

    (3) 初次實現

    由於這裏是結合着項目8的代碼,這裏小編給出運行結果:
    Python權威指南的10個項目(6~10)

    (4) 再次實現

      第一個原型非常簡單,它確實實現了文件共享功能,但對用戶不太友好。如果用戶能夠知道 有哪些文件可用(這些文件可能是程序啓動時就位於文件目錄中,也可能是後來從其他節點那裏 下載的),將大有裨益。再次實現將實現這種列出文件的功能,要獲取節點包含的文件的列表。

    (5) 結果展示

    Python權威指南的10個項目(6~10)

10. 項目10:自制街機遊戲

  到了最後一個項目了,這也是Python權威指南的最後一課了,經過一個多月的學習對Python也有了大致的瞭解,希望以後可以運用到工作中,至少目前看代碼時完全沒有問題。
  最後一個項目,應該說是所有項目中代碼量最多的,而且也比較有趣,這個項目將學習如何使用Pygame,這個擴展讓你能夠使用Python編寫功能齊備的全屏街機遊戲。

(1) 問題描述

  這個遊戲中,我們將讓玩家控制一支香蕉。這支香蕉要躲開從天而 降的16噸鉛錘,盡力在防禦戰中活下來。這個項目的目標是圍繞着遊戲設計展開的。這款遊戲必須像設計的那樣:香蕉能夠移動,16 噸的鉛錘從天而降。另外,與往常一樣,代碼必須是模塊化的,且易於擴展。一個重要的需求是, 設計應包含一些遊戲狀態(如遊戲簡介、關卡和“遊戲結束”狀態),同時可輕鬆地添加新狀態。

(2) 工作準備

  這個項目需要的工具只有一個,那就是pygame。模塊pygame自動導入其他所有的Pygame模塊,因此只要在程序開頭包含語句import pygame, 就能使用其他模塊,如pygame.display和pygame.font。
  模塊pygame包含函數Surface,它返回一個新的Surface對象。Surface對象其實就是一個指定 尺寸的空圖像,可用來繪畫和傳送。傳送(調用Surface對象的方法blit)意味着在Surface之間傳輸內容。
  函數init是Pygame遊戲的核心,必須在遊戲進入主事件循環前調用。這個函數自動初始化其他所有模塊(如font和image)。

下載:pip install pygame

如果要捕獲Pygame特有的錯誤,就需要使用error類。
接下來我們再來了解幾個pygame重要的函數:

  • pygame.locals:包含你可能在自定義模塊的作用域內使用的名稱(變量),如事件類型、 鍵、視頻模式等的名稱。
  • pygame.display:模塊pygame.display包含處理內容顯示的函數,這些內容可顯示在普通窗口中,也可佔據整個屏幕,而在這個模塊我們具體使用的函數有:
    • flip:更新顯示。一般而言,分兩步來修改當前屏幕。首先,對函數get_surface返回 的Surface對象做必要的修改,然後調用pygame.display.flip來更新顯示,反映出所做 的修改。
    • update:只想更新屏幕的一部分時,使用這個函數,而不是flip。調用這個函數時,可只 提供一個參數,即RenderUpdates類的方法draw返回的矩形列表
    • set_mode:設置顯示的尺寸和類型。顯示模式有多種,但這裏只使用全屏模式和默認模式 “在窗口中顯示”
    • set_caption:設置Pygame程序的標題。
    • get_surface:返回一個Surface對象,你可在其中繪製圖形,再調用pygame.display.flip 或pygame.display.blit。
  • pygame.font:模塊pygame.font包含函數Font。
  • pygame.sprite:包含兩個非常重要的類:Sprite和Group,Sprite類是所有可見遊戲對象(在這個項目中,是香蕉和重16噸的鉛錘)的基類。要實現自 定義的遊戲對象,可從Sprite派生出子類,並重寫構造函數以設置其屬性image和rect(這些屬性 決定了Sprite的外觀和位置),同時重寫在Sprite可能需要更新時調用的方法update。
  • Group:及其子類的實例用作Sprite對象的容器。一般而言,使用Group是個不錯的主意。在簡 單的遊戲(如本章的項目)中,只需創建一個名爲sprites或allsprites之類的Group,並將所有 Sprite都添加到其中。這樣,當你調用Group對象的方法update時,將自動調用所有Sprite對象的 方法update。另外,Group對象的方法clear用於清除它包含的所有Sprite對象
  • pygame.mouse:只使用模塊pygame.mouse來做兩件事情:隱藏鼠標以及獲取 鼠標的位置
  • pygame.event:模塊pygame.event跟蹤各種事件,如鼠標單擊、鼠標移動、按下或鬆開鍵等。要獲取最近發 生的事件列表,可使用函數pygame.event.get。
  • pygame.image:模塊pygame.image用於處理圖像,如以GIF、PNG、JPEG和其他幾種文件格式存儲的圖像。
    兩張圖片:
    Python權威指南的10個項目(6~10)Python權威指南的10個項目(6~10)

(3) 初次實現

在初次實現的時候,我們只實現其中一些簡單的功能:

  • 使用pygame.init、pygame.display.set_mode和pygame.mouse.set_visible初始化Pygame。 使用pygame.display.get_surface獲取屏幕表面,使用方法fill以白色填充屏幕表面,再調用 pygame.display.flip顯示所做的修改
  • 加載鉛錘圖像
  • 使用這幅圖像創建自定義類Weight(Sprite的子類)的一個實例。將這個對象添加到Render Updates編組sprites中。(處理多個Sprite對象時,這樣做很有幫助。)
  • 使用pygame.event.get獲取最近發生的所有事件,並依次檢查這些事件。如果發現事件QU IT 或因按下Escape鍵(K_ESCAPE)而觸發的KEYDOWN事件,就退出程序
  • 調用編組sprites的方法clear和update。方法clear使用回調函數來清除所有的Sprite對象 (這裏是鉛錘),而方法update調用Weight實例的方法update
  • 調用sprites.draw並將屏幕表面作爲參數,以便在當前位置繪製鉛錘(每次調用Weight實例 的update方法後,位置都將發生變化
  • 調用pygame.display.update,並將sprites.draw返回的矩形列表作爲參數,只更新需要更 新的部分。
  • 重複第4~7步
# C:\Users\Administrator\AppData\Local\Programs\Python\Python36\python.exe
# -*- coding: utf-8 -*-

'''
    簡單的“鉛錘從天而降”動畫
'''

import sys,pygame
from random import randrange
from pygame.locals import *

class Weight(pygame.sprite.Sprite):
    def __init__(self,speed):
        pygame.sprite.Sprite.__init__(self)
        self.speed=speed
        #繪製Sprite對象時要用到的圖像和矩形:
        self.image=weight_image
        self.rect=self.image.get_rect()
        self.reset()

    def reset(self):
        """
       將鉛錘移到屏幕頂端的一個隨機位置
        """
        self.rect.top=-self.rect.height
        self.rect.centerx=randrange(screen_size[0])

    def update(self):
        """
       更新下一幀中的鉛錘
        """
        if self.rect.top> screen_size[1]:
            self.reset()
        self.rect.top += self.speed
#初始化
pygame.init()
screen_size=1366,768
pygame.display.set_mode(screen_size,FULLSCREEN)
pygame.mouse.set_visible(0)

# 加載鉛錘圖像
image_path="images/jack.jpg"
weight_image=pygame.image.load(image_path)
weight_image=weight_image.convert() # 以便與顯示匹配

# 你可能想設置不同的速度
#speed=5

# 創建一個Sprite對象編組,並在其中添加一個Weight實例
sprites = pygame.sprite.RenderUpdates()
#同時添加三個鉛塊
sprites.add(Weight(speed=2))
sprites.add(Weight(speed=3))
sprites.add(Weight(speed=5))

# 獲取並填充屏幕表面
screen=pygame.display.get_surface()
bg=(255, 255, 255) #白色
#以白色填充屏幕表面
screen.fill(bg)
#顯示所做的修改
pygame.display.flip()

clock=pygame.time.Clock() # 設置時鐘,限制移動速度

# 用於清除Sprite對象:
def clear_back(surf,rect):
    surf.fill(bg, rect)

while True:
    clock.tick(60)  # 然後在while循環中設置多長時間運行一次,每秒執行60次
    # 檢查退出事件:
    for event in pygame.event.get():
        if event.type==QUIT:
            sys.exit()
        if event.type== KEYDOWN and event.key == K_ESCAPE:
            sys.exit()
    # 清除以前的位置:
    sprites.clear(screen,clear_back)
    # 更新所有的Sprite對象:
    sprites.update() #方法update調用Weight實例的方法update
    # 繪製所有的Sprite對象:
    updates=sprites.draw(screen) #調用sprites.draw並將屏幕表面作爲參數,以便在當前位置繪製鉛錘
    # 更新必要的顯示部分:
    pygame.display.update(updates)

(4) 再次實現

最終我們將實現這樣的一個遊戲:

  • 這個遊戲包含5個文件:包含各種配置變量的config.py;包含遊戲對象的實現的objects.py; 包含主遊戲類和各種遊戲狀態類的squish.py;遊戲使用的圖像jack.jpg和banana. jpg
  • 矩形的方法clamp確保一個矩形位於另一個矩形內,並在必要時移動這個矩形。這個方法 用於避免香蕉移到屏幕外。
  • 矩形的方法inflate調整矩形的尺寸——在水平和垂直方向調整指定數量的像素。這個方 法用於收縮香蕉的邊界,從而在香蕉和鉛錘重疊到一定程度後,才認爲香蕉被砸到
  • 這個遊戲本身由一個遊戲對象和各種狀態組成。遊戲對象在特定時間點只有一種狀態, 而狀態負責處理事件並在屏幕上顯示自己。狀態還能讓遊戲切換到另一種狀態。例如, 狀態Level可以讓遊戲切換到GameOver狀態。

這裏我們將實現這樣幾個模塊:
config.py:可根據偏好隨意修改配置變量,如果遊戲的節奏太快或太慢,可嘗試修改與速度相關的變量
objects.py:這個模塊包含遊戲Squish使用的遊戲對象
squish.py:這個模塊包含遊戲Squish的主遊戲邏輯

(5) 最終展示

開始界面:
Python權威指南的10個項目(6~10)
遊戲界面:
Python權威指南的10個項目(6~10)
闖關界面:
Python權威指南的10個項目(6~10)
結束界面:
Python權威指南的10個項目(6~10)

到此所有的項目都已結束!!!
所有的代碼 小編都上傳到gitlab上:

https://gitlab.com/ZZY478086819/actualcombatproject

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章