最近沉迷股市,研究期权,导致好久没来写博客了,果然资本最容易让人腐朽。
应好友拜托,帮他在澳洲大学的女友写编程作业,所以正好顺便写写博客。。。
import re
import csv
# 初始命名
EMPTY_TILE = "tile"
START_PIPE = "start"
END_PIPE = "end"
LOCKED_TILE = "locked"
# 起始管道 不可移动
SPECIAL_TILES = {
"S": START_PIPE,
"E": END_PIPE,
"L": LOCKED_TILE
}
# 管道类型
PIPES = {
"ST": "straight", # 横或竖的管子
"CO": "corner", # 弯的管子
"CR": "cross", # 十字管
"JT": "junction-t", # T型管
"DI": "diagonals", # 平行管
"OU": "over-under" # 天桥管
}
管道类
Tile
接下来创建一个管道地类(铺设管道的地面),负责给地面道命名,与赋予是否可选状态
铺设管道的地面 (地面名 与 是否可选)
get_name(self) -> str: 获取名字
get_id(self) -> str: 获取类型名
set_select(self, select: bool): 重新设置可选状态
can_select(self) -> bool: 获取可选状态
class Tile:
"""
>>> locked = Tile('locked', False)
>>> locked.get_name()
'locked'
>>> locked.get_id()
'tile'
>>> locked.can_select()
False
>>> str(locked)
"Tile('locked', False)"
"""
def __init__(self, name, selectable=True):
self.name = name
self.selectable = selectable
def get_name(self) -> str:
return self.name
def get_id(self) -> str:
return self.__class__.__name__.lower()
def set_select(self, select: bool):
self.selectable = select
return None
def can_select(self) -> bool:
return self.selectable
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.name},{self.selectable})"
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name},{self.selectable})"
Pipe 类
管道类,负责管道的名称 与 管道的方向 与 是否可选
get_connected(self, side: str) -> list:
取得输入的连接方,若无返回空
rotate(self, direction: int):
旋转,区间在【0,3】,所以使用 %4
get_orientation(self) -> int:
返回方向
PROP_PIPE = {('start',0):{None: ['N'],'N': ['N'], 'S': ['N'],'E': ['N'], 'W': ['N']},
('start',1):{None: ['E'],'N': ['E'], 'S': ['E'],'E': ['E'], 'W': ['E']},
('start',2):{None: ['S'],'N': ['S'], 'S': ['S'],'E': ['S'], 'W': ['S']},
('start',3):{None: ['W'],'N': ['W'], 'S': ['W'],'E': ['W'], 'W': ['W']},
('end',0):{None: ['S'],'N': ['S'], 'S': ['S'],'E': ['S'], 'W': ['S']},
('end',1):{None: ['W'],'N': ['W'], 'S': ['W'],'E': ['W'], 'W': ['W']},
('end',2):{None: ['N'],'N': ['N'], 'S': ['N'],'E': ['N'], 'W': ['N']},
('end',3):{None: ['E'],'N': ['E'], 'S': ['E'],'E': ['E'], 'W': ['E']},
('straight', 0) : {'N': ['S'], 'S': ['N'],'E': [], 'W': []} ,
('straight', 1) : {'E': ['W'], 'W': ['E'],'N': [], 'S': []} ,
('straight', 2) : {'N': ['S'], 'S': ['N'],'E': [], 'W': []} ,
('straight', 3) : {'E': ['W'], 'W': ['E'],'N': [], 'S': []} ,
('corner', 0) : {'N': ['E'], 'E': ['N'],'S':[],'W':[]} ,
('corner', 1) : {'E': ['S'], 'S': ['E'],'N':[],'W':[]} ,
('corner', 2) : {'W': ['S'], 'S': ['W'],'N':[],'E':[]} ,
('corner', 3) : {'W': ['N'], 'N': ['W'],'S':[],'E':[]} ,
('cross', 0) : {'N': ['S', 'W', 'E'], 'S': ['N', 'W', 'E'], 'W': ['N', 'S', 'E'], 'E': ['N', 'S', 'W']} ,
('cross', 1) : {'N': ['S', 'W', 'E'], 'S': ['N', 'W', 'E'], 'W': ['N', 'S', 'E'], 'E': ['N', 'S', 'W']} ,
('cross', 2) : {'N': ['S', 'W', 'E'], 'S': ['N', 'W', 'E'], 'W': ['N', 'S', 'E'], 'E': ['N', 'S', 'W']} ,
('cross', 3) : {'N': ['S', 'W', 'E'], 'S': ['N', 'W', 'E'], 'W': ['N', 'S', 'E'], 'E': ['N', 'S', 'W']} ,
('junction-t', 0) : {'S': ['W', 'E'], 'W': ['S', 'E'], 'E': ['S', 'W'],'N':[]} ,
('junction-t', 1) : {'N': ['S', 'W'], 'S': ['N', 'W'], 'W': ['N', 'S'],'E':[]} ,
('junction-t', 2) : {'N': ['W', 'E'], 'W': ['N', 'E'], 'E': ['N', 'W'],'S':[]} ,
('junction-t', 3) : {'N': ['S', 'E'], 'S': ['N', 'E'], 'E': ['N', 'S'],'W':[]} ,
('diagonals',0) :{'N': ['E'], 'E': ['N'], 'S': ['W'], 'W': ['S']},
('diagonals',1) :{'N': ['W'], 'E': ['S'], 'S': ['E'], 'W': ['N']},
('diagonals',2) :{'N': ['E'], 'E': ['N'], 'S': ['W'], 'W': ['S']},
('diagonals',3) :{'N': ['W'], 'E': ['S'], 'S': ['E'], 'W': ['N']},
('over-under',0) :{'N': ['S'], 'E': ['W'], 'S': ['N'], 'W': ['E']},
('over-under',1) :{'N': ['S'], 'E': ['W'], 'S': ['N'], 'W': ['E']},
('over-under',2) :{'N': ['S'], 'E': ['W'], 'S': ['N'], 'W': ['E']},
('over-under',3) :{'N': ['S'], 'E': ['W'], 'S': ['N'], 'W': ['E']}}
class Pipe(Tile):
"""
>>> new_pipe = Pipe("straight", 1)
>>> new_pipe.get_connected("E")
['W']
>>> new_pipe.get_id()
'pipe'
"""
def __init__(self, name, orientation=0, selectable=True):
self.name = name
self.orientation = orientation
self.selectable = selectable
def get_connected(self, side:str=None) -> list:
return PROP_PIPE[(self.name,self.orientation)][side]
def rotate(self, direction: int):
self.orientation = (self.orientation+direction)%4
return None
def get_orientation(self) -> int:
return self.orientation
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.name}, {self.orientation})"
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name}, {self.orientation})"
Special_Pipe
起始、结束等 特殊固定管道
特殊管道 固定 selectable = False
class Special_Pipe(Pipe):
def __init__(self,name='special_pipe', orientation=0, selectable=False):
self.name = name
self.orientation = orientation
self.selectable = selectable
def get_id(self) -> str:
return 'special_pipe'
def set_select(self, select: bool):
return None
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.orientation})"
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.orientation})"
class StartPipe(Special_Pipe):
"""
>>> start = StartPipe()
>>> str(start)
"StartPipe(0)"
>>> start.get_id()
'special_pipe'
>>> repr(start)
"StartPipe(0)"
>>> start.get_orientation()
0
>>> start.get_connected()
['N']
"""
def __init__(self,orientation=0):
self.name = 'start'
self.orientation = orientation
self.selectable = False
class EndPipe(Special_Pipe):
"""
>>> end = EndPipe()
>>> str(end)
"EndPipe(0)"
>>> repr(end)
"EndPipe(0)"
>>> end.get_id()
'special_pipe'
>>> end.get_connected()
['S']
"""
def __init__(self,orientation=0):
self.name = 'end'
self.orientation = orientation
self.selectable = False
PipeGame 创建游戏
初始化 __init__
(game_file) 桌面保存的文件名
self.board_layout
游戏桌面
self.playable_pipes
可移动的棋子
self.start
开始位置
self.end
结束位置
self.max_row
, self.max_col
最大边长
读取 桌面 load_file
提供的是 csv 文件, 所以 要在 文首 import csv
文件格式如图:
因此 读取时 要把符号转换成 类,用函数 convert
转换 string -> class
因为读取的 信息 有字母与数字 运用 正则表达 提取字母与数字
最后一排 为可移动棋子数, 所以把它另外提取出来,然后给各个棋子具体的数量
get_board_layout(self)
,get_playable_pipes(self)
为读取桌面 与棋子的函数
change_playable_amount
:更改可移动的数量 (增加与减少number
)
get_pipe
: 获取 位置position
的信息
set_pipe
: 放置棋子在桌面上 并移除一个 它的可移动数量
pipe_in_position
: 检测 该位置 的管道
valid_position
: 是否在可棋盘内
remove_pipe
: 在桌面上移除一个棋子 并增加一个 可移动数量
获取对应方向位置的信息
position_in_direction
: 若在位置(1,0)
选择方向('E'
) 那它对应的为 'W',(1,1)
检测 是否获胜
check_win
class PipeGame:
"""
A game of Pipes.
"""
def __init__(self, game_file='game_1.csv'):
"""
Construct a game of Pipes from a file name.
Parameters:
game_file (str): name of the game file.
"""
self.start, self.end = None, None
self.board_layout,self.playable_pipes = self.load_file(game_file)
self.start = self.get_starting_position()
self.end = self.get_ending_position()
self.max_row = len(self.board_layout)
self.max_col = len(self.board_layout[0])
def load_file(self, filename: str):
with open(filename,'r') as csvfile:
reader = csv.reader(csvfile)
board= [row for row in reader]
pipes = board.pop()
playable_pipes = dict()
board_layout = []
for i,name in enumerate(['straight', 'corner', 'cross', 'junction-t', 'diagonals', 'over-under']):
playable_pipes.update({name:int(pipes[i])})
for row in board:
rows = []
for col in row:
rows.append(self.convert(col))
board_layout.append(rows)
return board_layout, playable_pipes
def convert(self, tile):
if tile == '#':
return Tile('tile', True)
orientation = int(re.findall("\d+",tile)[0]) if re.findall("\d+",tile)!=[] else 0
name = re.findall("[A-Z]+", tile)[0]
if name == 'S':
return StartPipe(orientation)
elif name == 'E':
return EndPipe(orientation)
elif name == 'L':
return Tile('locked', False)
elif name in PIPES:
return Pipe(PIPES[name],orientation,False)
def get_board_layout(self):
return self.board_layout
def get_playable_pipes(self):
return self.playable_pipes
def change_playable_amount(self, pipe_name: str, number: int):
add_num = self.playable_pipes[pipe_name]+number
self.playable_pipes.update({pipe_name:add_num})
return None
def get_pipe(self, position):
return self.board_layout[position[0]][position[1]]
def set_pipe(self, pipe: Pipe, position):
self.change_playable_amount(pipe.name,-1)
self.board_layout[position[0]][position[1]] = pipe
return None
def pipe_in_position(self, position) -> Pipe:
if position == None:
return None
elif self.valid_position(position):
pipe = self.board_layout[position[0]][position[1]]
if (pipe.get_id() == 'pipe') or (pipe.get_id() == 'special_pipe'):
return pipe
else:
return None
def remove_pipe(self, position):
self.change_playable_amount(self.pipe_in_position(position).name,1)
self.board_layout[position[0]][position[1]] = Tile('tile', True)
return None
def valid_position(self,position):
if position[0]=self.max_row or position[1]=self.max_col:
return False
else:
return True
def position_in_direction(self, direction, position):
tile = self.board_layout[position[0]][position[1]]
if tile == None:
return None
else:
if direction == 'N' and self.valid_position((position[0]-1,position[1])):
return ('S',(position[0]-1,position[1]))
elif direction == 'S'and self.valid_position((position[0]+1,position[1])):
return ('N',(position[0]+1,position[1]))
elif direction == 'E'and self.valid_position((position[0],position[1]+1)):
return ('W',(position[0],position[1]+1))
elif direction == 'W'and self.valid_position((position[0],position[1]-1)):
return ('E',(position[0],position[1]-1))
return None
def get_starting_position(self):
if self.start != None:
return self.start
for row_num, row in enumerate(self.board_layout):
for col_num,col in enumerate(row):
if col.__class__.__name__=='StartPipe':
return (row_num,col_num)
def get_ending_position(self):
if self.end != None:
return self.end
for row_num, row in enumerate(self.board_layout):
for col_num,col in enumerate(row):
if col.__class__.__name__=='EndPipe':
return (row_num,col_num)
def check_win(self):
"""
(bool) Returns True if the player has won the game False otherwise.
"""
position = self.get_starting_position()
pipe = self.pipe_in_position(position)
queue = [(pipe, None, position)]
discovered = [(pipe, None)]
while queue:
pipe, direction, position = queue.pop()
for direction in pipe.get_connected(direction):
if self.position_in_direction(direction, position) is None:
new_direction = None
new_position = None
else:
new_direction, new_position = self.position_in_direction(direction, position)
if new_position == self.get_ending_position() and direction == self.pipe_in_position(new_position).get_connected()[0]:
return True
pipe = self.pipe_in_position(new_position)
if pipe is None or (pipe, new_direction) in discovered:
continue
discovered.append((pipe, new_direction))
queue.append((pipe, new_direction, new_position))
return False
用 tkinter 创建游戏
以下代码为老师提供
import tkinter as tk
from tkinter import messagebox
from tkinter import simpledialog
左边的选择框 与可选棋子
class SelectionPanel(tk.Canvas):
"""
Sidebar display of the selectable pipes.
Shows all the types of pipes that can be placed within the game and how many are left to be placed.
"""
def __init__(self, master, playable_pipes, panel_selection=None, selected=None, *args, **kwargs):
"""
Construct a new selection panel canvas.
Parameters:
master (tk.Widget): Widget within which to place the selection panel.
playable_pipes (dict): Mapping of types of pipes to amount of pipes remaining.
panel_selection (callable): Function or method to call when a pipe is selected.
selected (str): Type of pipe that is currently selected.
"""
super().__init__(master, *args, **kwargs)
self._master = master
self._selected = selected
self._playable = playable_pipes
self._panel_selection = panel_selection
# map pipe types to their pipe display and remaining count
self._pipes = {}
self.draw_pipes()
self.redraw()
def draw_pipes(self):
"""Draw all the pipes in the selection panel"""
for pipe_type in self._playable:
image = get_image(f"images/{pipe_type}0")
pipe_frame = tk.Frame(self, highlightthickness=2)
selection = tk.Label(pipe_frame, image=image)
selection.image = image
selection.pack(side=tk.TOP)
selection.bind("", lambda e, pipe=pipe_type: self._handle_click(pipe))
number = tk.Label(pipe_frame, text=f"{self._playable[pipe_type]}")
number.pack(side=tk.TOP)
pipe_frame.pack(side=tk.TOP)
pipe_frame.bind("", lambda e, pipe=pipe_type: self._handle_click(pipe))
self._pipes[pipe_type] = (pipe_frame, number)
def redraw(self, selected=None):
"""Update the pipes with current information
- Sets the outline of selected pipes to red
- Updates the amount of remaining pipes
"""
for pipe, (frame, number) in self._pipes.items():
if pipe == selected and self._playable[selected] > 0:
border = "red"
else:
border = "white"
frame.config(highlightbackground=border)
number.config(text=f"{self._playable[pipe]}")
def _handle_click(self, pipe):
"""Called when a pipe is clicked, handling calling the callback panel_selection method"""
if self._panel_selection is not None:
self._panel_selection(pipe)
主桌面 试图
class BoardView(tk.Canvas):
"""View of the Pipe game board"""
def __init__(self, master, board_layout, place_pipe=None, remove_pipe=None, *args, **kwargs):
"""Construct a board view from a board_layout.
Parameters:
master (tk.Widget): Widget within which the board is placed.
board_layout (list<list>): 2D array of tiles in a board.
place_pipe (callable): Callable to call when a pipe is being placed.
remove_pipe (callable): Callable to call when a pipe is being removed.
"""
super().__init__(master, *args, **kwargs)
self._master = master
self._board_layout = board_layout
self.place_pipe = place_pipe
self.remove_pipe = remove_pipe
self._board = self.load_board()
def load_board(self):
"""(list<list>) Create a 2D array of labels representing the board to display."""
labels = []
for y, row in enumerate(self._board_layout):
board_row = []
for x, tile in enumerate(row):
placement = tk.Label(self, text="T")
placement.grid(column=x, row=y, ipady=4, ipadx=4)
self.bind_clicks(placement, tile, (y, x))
board_row.append(placement)
labels.append(board_row)
return labels
def redraw(self):
"""Redraw the game board by updating the images displayed in each grid"""
for y, row in enumerate(self._board_layout):
for x, tile in enumerate(row):
position = (y, x)
image = self._load_tile_image(tile)
placement = self._board[y][x]
placement.config(image=image)
placement.image = image
self.bind_clicks(placement, tile, position)
def bind_clicks(self, label, tile, position):
"""Bind clicks on a label to the left and right click handlers.
Parameters:
label (tk.Widget): Label which clicks should bound to.
tile (Tile): Tile to pass as a parameter to the handlers.
position (tuple): Position to pass as a parameter to the handlers.
"""
# bind left click
label.bind("", lambda e, tile=tile, position=position: self._handle_left_click(tile, position))
# bind right click
# right click can be either Button-2 or Button-3 depending on operating system
for i in range(2, 4):
label.bind(f"",
lambda e, tile=tile, position=position: self._handle_right_click(tile, position))
def _handle_left_click(self, pipe, position):
"""Handle left clicking on a tile to place a pipe.
Calls the provided place_pipe method if available and pipe is selectable.
"""
if self.place_pipe is not None and pipe.can_select():
self.place_pipe(position)
def _handle_right_click(self, pipe, position):
"""Handle right clicking on a tile"""
if self.remove_pipe is not None and pipe.get_id() == "pipe" and pipe.can_select():
if pipe.get_name() in PIPES.values():
self.remove_pipe(position)
def _load_tile_image(self, tile):
"""Load the PhotoImage to use for a given tile.
If the tile class has not been fully implemented defaults to images/tile
"""
try:
if tile.get_id() != "tile":
image = get_image(f"images/{tile.get_name()}{tile.get_orientation()}")
else:
image = get_image(f"images/{tile.get_name()}")
except AttributeError:
print("get_name(), get_orientation() and get_id() methods need to be implemented correctly.",
"\n",
"Make sure all class attributes are defined correctly.",
"\n")
image = get_image("images/tile")
return image
游戏主代码
class GameApp:
"""Game application that manages communication between the selection panel, board view and game model."""
def __init__(self, master):
"""Create a new game app within a master widget"""
self._master = master
self._level = ""
self._game = PipeGame()
self._selected = None
# initialise GUI variables that are assigned in the draw method
self._selection, self._board_view, self._button_frame = None, None, None
self.draw()
def select_pipe(self, pipe):
"""Select a pipe to be placed from the selection panel.
Parameters:
pipe (Pipe): The selected pipe.
"""
# ignore selection if not enough pipes available
if self._game.get_playable_pipes()[pipe] <= 0:
return
# unselect if pipe is clicked twice
if self._selected == pipe:
pipe = None
self._selected = pipe
self._selection.redraw(selected=self._selected)
def place_pipe(self, position):
"""Place the selected pipe on the game board.
Parameters:
position (tuple): The position to place the pipe within the board.
"""
selected = self._selected
# tile at the placing position
tile = self._game.get_pipe(position)
if tile.can_select() and selected is not None and tile.get_id() == "tile":
self._game.set_pipe(Pipe(selected), position)
# rotate already placed pipes
if tile.get_id() == "pipe":
tile.rotate(1)
# unselect when placed
self._selected = None
self._selection.redraw()
self._board_view.redraw()
self.check_game_over()
def remove_pipe(self, position):
"""Remove the pipe at the given position
Parameters:
position (tuple): The position to remove the pipe from.
"""
self._game.remove_pipe(position)
self._board_view.redraw()
self._selection.redraw()
def check_game_over(self):
"""Check if the game is over and exit if so"""
if self._game.check_win():
messagebox.showinfo("Game Over", "You won! :D")
self._master.destroy()
def new_game(self):
"""Start a new level from the user inputted selection."""
self._level = simpledialog.askstring("Input", "What level would you like to play?",
parent=self._master)
if self._level is not None:
self.reset_game()
def reset_game(self):
"""Restart the game on the current level."""
if self._level == "":
self._game = PipeGame()
else:
self._game = PipeGame(self._level)
self.redraw()
def redraw(self):
"""Redraw the whole game window."""
self._selection.destroy()
self._board_view.destroy()
self._button_frame.destroy()
self.draw()
def draw(self):
"""Draw the game to the master widget."""
try:
self._selection = SelectionPanel(self._master, self._game.get_playable_pipes(), self.select_pipe)
self._selection.pack(side=tk.LEFT)
except AttributeError:
print("get_playable_pipes() method needs to be implemented correctly.",
"\n")
try:
self._board_view = BoardView(self._master, self._game.get_board_layout(), self.place_pipe, self.remove_pipe)
self._board_view.redraw()
self._board_view.pack(side=tk.LEFT)
except AttributeError:
print("get_board_layout() method needs to be implemented correctly.",
"\n")
self._button_frame = tk.Frame(self._master)
restart_button = tk.Button(self._button_frame, text="Restart", command=self.reset_game)
restart_button.pack(side=tk.TOP)
new_button = tk.Button(self._button_frame, text="New Game", command=self.new_game)
new_button.pack(side=tk.TOP)
self._button_frame.pack(side=tk.LEFT)
def get_image(image_name):
"""(tk.PhotoImage) Get a image file based on capability.
If a .png doesn't work, default to the .gif image.
"""
try:
image = tk.PhotoImage(file=image_name + ".png")
except tk.TclError:
image = tk.PhotoImage(file=image_name + ".gif")
return image
def main():
root = tk.Tk()
root.title("Game")
GameApp(root)
root.update()
root.mainloop()
最后 就可以开心的玩游戏啦 !!!
main()