20212205王子權 2021-2022-2 《Python程式設計》實驗四報告
20212205 2021-2022-2 《Python程式設計》實驗四報告
課程:《Python程式設計》
班級: 2122
姓名: 王子權
學號:20212205
實驗教師:王志強
實驗日期:2022年5月28日
必修/選修: 公選課
1.實驗內容
Python綜合應用:爬蟲、資料處理、視覺化、機器學習、神經網路、遊戲、網路安全等。
注:在華為ECS伺服器(OpenOuler系統)和物理機(Windows/Linux系統)上使用VIM、PDB、IDLE、Pycharm等工具程式設計實現。
2. 實驗過程及結果
1.運用python編寫五子棋小遊戲
遊戲規則:雙方分別在棋盤上進行下棋操作,如果有一方達成了五子連珠,遊戲宣佈結束,如果在棋盤上沒有空地的時候雙方仍沒有分出真正的勝負,則算平局
2.遊戲原始碼
#encoding:utf-8
import pygame
import sys
from pygame.locals import *
# 顏色常量
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
# 錯誤碼
G_POS_PLACED = -4
G_RANGE_ERR = -3
G_STAT_ERR = -2
G_ERR = -1
G_OK = 0
G_FINISH = 1
G_WIN = 2
class GoBang:
def __init__(self, map_size=16):
self.map_size = map_size
# map_size * map_size的二維列表,用於表示棋盤
# 0 ~ 無棋子, 1 ~ 黑棋,-1 ~ 白棋
self.map = [[0 for y in range(0, map_size)] for x in range(0, map_size)]
# 走棋的歷史記錄,用於悔棋。它是一個list,它的成員是一個元組(棋子型別,map.x,map.y)
self.move_stack = []
self.status = 0
self.winner = 0
def start_move(self):
self.status = 1
def get_last_move(self):
return self.move_stack[-1]
def get_winner(self):
return self.winner
def get_steps(self):
return len(self.move_stack)
# 判斷輸贏的演算法: 只需要判斷當前落子相關的四條直線(橫、豎、左斜、右斜),是否形成5個連子。
# 將直線上的落子(黑~ 1,白~ -1),依次相加,連續的子絕對值之和達到5,即可判定為勝利
def __check_winner_(self):
tmp = 0
last_step = self.move_stack[-1]
# 豎向直線, x 固定
for y in range(0, self.map_size):
# 必須是連續的
if y > 0 \
and self.map[last_step[1]][y] != self.map[last_step[1]][y - 1]:
tmp = 0
tmp += self.map[last_step[1]][y]
if abs(tmp) >= 5:
return last_step[0]
# 橫向直線, y 固定
tmp = 0
for x in range(0, self.map_size):
# 必須是連續的
if x > 0 \
and self.map[x][last_step[2]] != self.map[x - 1][last_step[2]]:
tmp = 0
tmp += self.map[x][last_step[2]]
if abs(tmp) >= 5:
return last_step[0]
# 右斜直線,計算出左上角頂點的座標。然後x,y都遞增,到達最右下角頂點。
tmp = 0
min_dist = min(last_step[1], last_step[2])
top_point = [last_step[1] - min_dist, last_step[2] - min_dist]
for incr in range(0, self.map_size):
# 不能超出棋盤邊界
if top_point[0] + incr > self.map_size - 1 \
or top_point[1] + incr > self.map_size - 1:
break
# 必須是連續的
if incr > 0 \
and self.map[top_point[0] + incr][top_point[1] + incr] \
!= self.map[top_point[0] + incr - 1][top_point[1] + incr - 1]:
tmp = 0
tmp += self.map[top_point[0] + incr][top_point[1] + incr]
if abs(tmp) >= 5:
return last_step[0]
# 左斜直線,計算出右上角頂點的座標。然後x遞減、y遞增,到達最左下角頂點。
tmp = 0
min_dist = min(self.map_size - 1 - last_step[1], last_step[2])
top_point = [last_step[1] + min_dist, last_step[2] - min_dist]
for incr in range(0, self.map_size):
# 不能超出棋盤邊界
if top_point[0] - incr < 0 \
or top_point[1] + incr > self.map_size - 1:
break
# 必須是連續的
if incr > 0 \
and self.map[top_point[0] - incr][top_point[1] + incr] \
!= self.map[top_point[0] - incr + 1][top_point[1] + incr - 1]:
tmp = 0
tmp += self.map[top_point[0] - incr][top_point[1] + incr]
if abs(tmp) >= 5:
return last_step[0]
return 0
# 判斷本局是否結束
def __check_(self):
# 所有步數已經走完
if len(self.move_stack) >= self.map_size ** 2:
return G_FINISH
# 贏了
winner = self.__check_winner_()
if winner != 0:
self.winner = winner
return G_WIN
# 未結束
return G_OK
# 走一步棋
def move(self, x, y):
if self.status != 1 and self.status != 2:
return G_STAT_ERR
if self.map_size <= x or x < 0 \
or self.map_size <= y or y < 0:
return G_RANGE_ERR
if self.map[x][y] != 0:
return G_POS_PLACED
t = 1 if self.status == 1 else -1
self.map[x][y] = t
self.move_stack.append((t, x, y))
# 判斷是否結束
ret = self.__check_()
if self.is_finish(ret):
if ret == G_WIN:
self.__set_status(3)
else:
self.__set_status(4)
return ret
# 切換狀態
last_step = self.move_stack[-1]
stat = 2 if last_step[0] == 1 else 1
self.__set_status(stat)
return G_OK
def __set_status(self, stat):
self.status = stat
def is_finish(self, err_code):
if err_code == G_FINISH \
or err_code == G_WIN:
return True
return False
# 悔一步棋
def rollback(self):
if len(self.move_stack) == 0:
return G_ERR
step = self.move_stack.pop()
self.map[step[1]][step[2]] = 0
# 重新整理當前狀態
if step[0] == 1: # 如果當前悔的是黑棋,那麼狀態切換為等待黑棋落子
self.status = 1
elif step[0] == -1:
self.status = 2
else:
return G_ERR
return G_OK
# 獲取當前狀態
# 0 ~ 未開局
# 1 ~ 等待黑棋落子
# 2 ~ 等待白棋落子
# 3 ~ 結束(一方獲勝)
# 4 ~ 結束(棋盤走滿)
def get_status(self):
return self.status
def get_move_stack(self):
return self.move_stack
class TigerGoBang(GoBang):
def __init__(self, map_size=16, map_unit=40):
self.SIZE = map_size
self.UNIT = map_unit
self.TITLE = '五子棋遊戲'
self.PANEL_WIDTH = 200 # 右側面板寬度
self.BORDER_WIDTH = 50 # 預留寬度
# 計算棋盤的有效範圍
self.RANGE_X = [self.BORDER_WIDTH, self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT]
self.RANGE_Y = [self.BORDER_WIDTH, self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT]
# 計算狀態面板的有效範圍
self.PANEL_X = [self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT, \
self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT + self.PANEL_WIDTH]
self.PANEL_Y = [self.BORDER_WIDTH, self.BORDER_WIDTH + (self.SIZE - 1) * self.UNIT]
# 計算視窗大小
self.WINDOW_WIDTH = self.BORDER_WIDTH * 2 \
+ self.PANEL_WIDTH \
+ (self.SIZE - 1) * self.UNIT
self.WINDOW_HEIGHT = self.BORDER_WIDTH * 2 \
+ (self.SIZE - 1) * self.UNIT
# 父類初始化
super(TigerGoBang, self).__init__(map_size=map_size)
# 初始化遊戲
self.__game_init_()
# 繪製棋盤
def __draw_map(self):
# 繪製棋盤
POS_START = [self.BORDER_WIDTH, self.BORDER_WIDTH]
s_font = pygame.font.SysFont('arial', 16)
# 繪製行
for item in range(0, self.SIZE):
pygame.draw.line(self.screen, BLACK,
[POS_START[0], POS_START[1] + item * self.UNIT],
[POS_START[0] + (self.SIZE - 1) * self.UNIT, POS_START[1] + item * self.UNIT],
1)
s_surface = s_font.render(f'{item + 1}', True, BLACK)
self.screen.blit(s_surface, [POS_START[0] - 30, POS_START[1] + item * self.UNIT - 10])
# 繪製列
for item in range(0, self.SIZE):
pygame.draw.line(self.screen, BLACK,
[POS_START[0] + item * self.UNIT, POS_START[1]],
[POS_START[0] + item * self.UNIT, POS_START[1] + (self.SIZE - 1) * self.UNIT],
1)
s_surface = s_font.render(chr(ord('A') + item), True, BLACK)
self.screen.blit(s_surface, [POS_START[0] + item * self.UNIT - 5, POS_START[1] - 30])
# 繪製棋子
def __draw_chess(self):
mst = self.get_move_stack()
for item in mst:
x = self.BORDER_WIDTH + item[1] * self.UNIT
y = self.BORDER_WIDTH + item[2] * self.UNIT
t_color = BLACK if item[0] == 1 else WHITE
pygame.draw.circle(self.screen, t_color, [x, y], int(self.UNIT / 2.5))
# 全部重繪
def __redraw_all(self):
# 重刷背景圖
self.screen.blit(pygame.image.load(r"bg.jpg"), (0, 0))
# 繪製棋盤
self.__draw_map()
# 繪製棋子
self.__draw_chess()
# 繪製面板
self.__draw_panel_()
def __game_init_(self):
# 初始化pygame
pygame.init()
# 設定視窗的大小,單位為畫素
self.screen = pygame.display.set_mode((self.WINDOW_WIDTH, self.WINDOW_HEIGHT))
# 設定視窗標題
pygame.display.set_caption(self.TITLE)
# 設定背景顏色
# self.screen.fill(WHITE)
background = pygame.image.load(r"hhh.jpg")
self.screen.blit(background, (0, 0))
# 繪製棋盤
self.__draw_map()
# 繪製右側的狀態面板
self.__draw_panel_()
def __draw_panel_(self):
# panel區域重繪,用白色矩形覆蓋
pygame.draw.rect(self.screen, WHITE,
[self.PANEL_X[0] + 30, 0,
1000, 1000])
self.panel_font = pygame.font.SysFont('simhei', 20)
# 走棋狀態
stat = self.get_status()
if stat == 0:
stat_str = '點選開始按鈕'
elif stat == 1:
stat_str = '等待黑棋落子..'
elif stat == 2:
stat_str = '等待白棋落子..'
elif stat == 4:
stat_str = '遊戲結束!'
elif stat == 3:
winner = self.get_winner()
if winner == 1:
stat_str = '黑棋獲勝!'
else:
stat_str = '白棋獲勝!'
else:
stat_str = ''
self.surface_stat = self.panel_font.render(stat_str, False, BLACK)
self.screen.blit(self.surface_stat, [self.PANEL_X[0] + 50, self.PANEL_Y[0] + 50])
# 步數
steps = self.get_steps()
self.surface_steps = self.panel_font.render(f'步數: {steps}', False, BLACK)
self.screen.blit(self.surface_steps, [self.PANEL_X[0] + 50, self.PANEL_Y[0] + 150])
# 新的一局
offset_x = self.PANEL_X[0] + 50
offset_y = self.PANEL_Y[0] + 400
btn_h = 50
btn_w = 150
btn_gap = 20
btn_text_x = 35
btn_text_y = 15
self.BTN_RANGE_NEW_START_X = [offset_x, offset_x + btn_w]
self.BTN_RANGE_NEW_START_Y = [offset_y, offset_y + btn_h]
pygame.draw.rect(self.screen, BLACK,
[offset_x, offset_y,
btn_w, btn_h])
self.surface_btn = self.panel_font.render(f'新開一局', False, WHITE)
self.screen.blit(self.surface_btn, [offset_x + btn_text_x, offset_y + btn_text_y])
# 退出遊戲
self.BTN_RANGE_EXIT_GAME_X = [offset_x, offset_x + btn_w]
self.BTN_RANGE_EXIT_GAME_Y = [offset_y + btn_h
+ btn_gap,
offset_y + btn_h + btn_gap + btn_h]
pygame.draw.rect(self.screen, BLACK,
[offset_x, offset_y + btn_h + btn_gap,
btn_w, btn_h])
self.surface_btn = self.panel_font.render(f'退出遊戲', False, WHITE)
self.screen.blit(self.surface_btn,
[offset_x + btn_text_x, offset_y + btn_h + btn_gap + btn_text_y])
# 悔棋
self.BTN_RANGE_RB_X = [offset_x, offset_x + btn_w]
self.BTN_RANGE_RB_Y = [offset_y + (btn_h +
btn_gap) * 2,
offset_y + (btn_h + btn_gap) * 2 + btn_h]
pygame.draw.rect(self.screen, BLACK,
[offset_x, offset_y + (btn_h + btn_gap) * 2,
btn_w, btn_h])
self.surface_btn = self.panel_font.render(f'悔一步棋', False, WHITE)
self.screen.blit(self.surface_btn,
[offset_x + btn_text_x, offset_y + (btn_h + btn_gap) * 2 + btn_text_y])
def __do_move_(self, pos):
# 落子在棋盤之外無效
if pos[0] < self.RANGE_X[0] or pos[0] > self.RANGE_X[1] \
or pos[1] < self.RANGE_Y[0] or pos[1] > self.RANGE_Y[1]:
return G_ERR
# 判斷當前落子的位置,需要吸附在最近的落棋點
s_x = round((pos[0] - self.BORDER_WIDTH) /
self.UNIT)
s_y = round((pos[1] - self.BORDER_WIDTH) /
self.UNIT)
x = self.BORDER_WIDTH + self.UNIT * s_x
y = self.BORDER_WIDTH + self.UNIT * s_y
# 先move,再draw
ret = self.move(s_x, s_y)
if ret < 0:
return G_ERR
# draw
last_move = self.get_last_move()
t_color = BLACK if last_move[0] == 1 else WHITE
pygame.draw.circle(self.screen, t_color, [x, y], int(self.UNIT / 2.5))
# pygame.draw.circle(self.screen, BLACK, [x, y],
int(self.UNIT / 2.5), 1)
self.__draw_panel_()
if self.get_status() >= 3:
return G_OK
def __do_rollback_(self):
if self.rollback() == G_OK:
self.__redraw_all()
def __do_new_start(self):
self.__init__()
self.start()
def __do_btn_(self, pos):
# 是否點選了按鈕
if self.BTN_RANGE_NEW_START_X[0] < pos[0] < self.BTN_RANGE_NEW_START_X[1] \
and self.BTN_RANGE_NEW_START_Y[0] < pos[1] < self.BTN_RANGE_NEW_START_Y[1]:
self.__do_new_start()
return G_OK
elif self.BTN_RANGE_EXIT_GAME_X[0] < pos[0] < self.BTN_RANGE_EXIT_GAME_X[1] \
and self.BTN_RANGE_EXIT_GAME_Y[0] < pos[1] < self.BTN_RANGE_EXIT_GAME_Y[1]:
sys.exit()
elif self.BTN_RANGE_RB_X[0] < pos[0] < self.BTN_RANGE_RB_X[1] \
and self.BTN_RANGE_RB_Y[0] < pos[1] < self.BTN_RANGE_RB_Y[1]:
self.__do_rollback_()
return G_OK
else:
return G_ERR
def start(self):
self.start_move()
self.__draw_panel_()
# 程式主迴圈
while True:
# 獲取事件
for event in pygame.event.get():
# 判斷事件是否為退出事件
if event.type == QUIT:
# 退出pygame
pygame.quit()
# 退出系統
sys.exit()
# 落子事件
if event.type == MOUSEBUTTONUP:
if self.__do_btn_(event.pos) < 0:
# 非按鈕事件,則處理走棋
self.__do_move_(event.pos)
# 繪製螢幕內容
pygame.display.update()
if __name__ == '__main__':
inst1 = TigerGoBang(map_unit=40)
inst1.start()
3. 小遊戲執行介面(為了演示更加清楚,在本地進行執行)
如圖所示
這時我們使用滑鼠進行點選操作
經歷一番戰鬥,黑棋獲得最終勝利
4. 用putty在雲伺服器上執行該檔案(遊戲時間!!!!
3. 實驗過程中遇到的問題和解決過程
- 問題1:在進行環境配置的時候,pygame庫經常出現無法下載的情況
- 問題1解決方案:在同學們的幫助之下,這個問題得到了解決
- 問題2:在遊戲執行時,無法顯示遊戲介面
- 問題2解決方案:在本地安裝xming使其可以在桌面上顯示介面
- 問題3:在安裝完成xming之後無法進行介面顯示
- 問題3解決方案:在putty的初始介面上進行對於xming的相容和連線
- 問題4:在本地執行時的文字,在雲端變成了亂碼
- 問題4解決方案:知道由於文字在兩個終端不同的編碼方式,但是由於時間關係以及能力有限沒能解決這個問題,是一個不小的遺憾吧
4. 課程小結
其實,當初選擇計算機這個專業也是因為覺得那些程式設計師大佬在鍵盤上飛速進行程式碼的錄入非常帥氣,對於一行行的程式碼能組成這樣高質量的程式軟體也感到十分驚奇。但是在真正進入這個領域之後我才發現我實在是太天真了,還記得第一次上計導課的時候,那些紛繁複雜的概念搞的我一頭霧水。自從那時候我就發現自己要學的東西還有很多很多。在這股動力的引導下,我報名了志強老師的python課程,希望能在這個課上學習到自己想要的東西。對於一個初次學習如何編寫程式的小白來說同時學習兩門語言其實是一個十分艱難的事情(python和c)。但我依舊覺得我可以迎難而上。在第一天上python課的時候我對於志強老師的講課方法和講課思路十分敬佩,也讓我有幸結識了這門十分方便快捷的語言————python(後來才知道python是以編譯器巨大負荷為代價給程式設計師帶來便捷的一門語言)初期並未覺得它和c的不同。那節面向物件的程式設計方式可以說直接推翻了我對於程式設計的固有認知,可以說是開闢了一條新的道路。至於後面的socket和爬蟲學習就是擺脫了單機的程式設計模式,嘗試著和外界網路的互動和聯通了。後幾次作業難度非常高,但也同時激起了我們在ddl壓迫下自我學習的動力。經歷這幾次作業特別是這最後一次作業之後,我對於python的理解更加深刻。或許這就是志強老師在有限的課時內想讓我們學到更多東西的方式吧。在這裡不得不說一句老師有心了。說實話在我寫結課總結之前,肚子裡面是怨氣滔天,恨不得說老師你的課程設計不合理。但是在我寫下自己的感悟體會的時候,回想起自己從什麼都不會的小白成為一個程式設計師半吊子(或許也算不上)。逐漸能理解老師的良苦用心了,怨氣自然消失之後,剩下的就是對老師的敬佩了吧,總之,這門課激起了我在暑假繼續學習python的興趣。也讓我感激遇見了一個可以循循善誘的老師。如果下次有機會一定會再考慮上老師您的課程。人生苦短,我用python!!!
另外再給個小建議,希望每次課之後的原始碼都能發群裡一份,這樣我們自己理解起來也會更加方便。
參考資料
搜尋
複製