├── $AI五子棋 ├── GobangAI.py ├── graphics.py └── references │ ├── SearchingForSolutions.pdf │ └── wagnervirag_2001.pdf ├── $滑雪游戏 ├── Game4.py ├── font │ └── simkai.ttf ├── images │ ├── flag.png │ ├── skier_fall.png │ ├── skier_forward.png │ ├── skier_left1.png │ ├── skier_left2.png │ ├── skier_right1.png │ ├── skier_right2.png │ └── tree.png └── music │ └── bg_music.mp3 ├── $贪吃蛇 ├── AI │ ├── AI_snake.py │ └── simkai.ttf └── Normal │ ├── AI_snake.py │ ├── Normal_snake.py │ └── simkai.ttf ├── .gitattributes ├── .gitignore └── README.md /$AI五子棋/GobangAI.py: -------------------------------------------------------------------------------- 1 | # AI五子棋 2 | # 作者: Charles 3 | # 公众号:Charles的皮卡丘 4 | from graphics import * 5 | 6 | 7 | GRID_WIDTH = 40 8 | COLUMN = 15 9 | ROW = 15 10 | ai_list = [] 11 | me_list = [] 12 | aime_list = [] 13 | all_list = [] 14 | next_point = [0, 0] 15 | ratio = 1 16 | DEPTH = 3 17 | cut_count = 0 18 | search_count = 0 19 | 20 | 21 | # 棋型评分表 22 | scoreModel = [(50, (0, 1, 1, 0, 0)), 23 | (50, (0, 0, 1, 1, 0)), 24 | (200, (1, 1, 0, 1, 0)), 25 | (500, (0, 0, 1, 1, 1)), 26 | (500, (1, 1, 1, 0, 0)), 27 | (5000, (0, 1, 1, 1, 0)), 28 | (5000, (0, 1, 0, 1, 1, 0)), 29 | (5000, (0, 1, 1, 0, 1, 0)), 30 | (5000, (1, 1, 1, 0, 1)), 31 | (5000, (1, 1, 0, 1, 1)), 32 | (5000, (1, 0, 1, 1, 1)), 33 | (5000, (1, 1, 1, 1, 0)), 34 | (5000, (0, 1, 1, 1, 1)), 35 | (50000, (0, 1, 1, 1, 1, 0)), 36 | (99999999, (1, 1, 1, 1, 1))] 37 | 38 | 39 | # 判断游戏是否结束 40 | # 四种情况 41 | def is_GameOver(list_now): 42 | for c in range(COLUMN): 43 | for r in range(ROW): 44 | if r < ROW - 4 and (r, c) in list_now and (r+1, c) in list_now and (r+2, c) in list_now and (r+3, c) in list_now and (r+4, c) in list_now: 45 | return True 46 | elif c < COLUMN - 4 and (r, c) in list_now and (r, c+1) in list_now and (r, c+2) in list_now and (r, c+3) in list_now and (r, c+4) in list_now: 47 | return True 48 | elif r < ROW - 4 and c < COLUMN - 4 and (r, c) in list_now and (r+1, c+1) in list_now and (r+2, c+2) in list_now and (r+3, c+3) in list_now and (r+4, c+4) in list_now: 49 | return True 50 | elif r > 3 and c < COLUMN - 4 and (r, c) in list_now and (r-1, c+1) in list_now and (r-2, c+2) in list_now and (r-3, c+3) in list_now and (r-4, c+4) in list_now: 51 | return True 52 | return False 53 | 54 | 55 | # 计算每个方向上的分值 56 | # list1是下子方 57 | # scores_all用于避免重复计算和奖励棋型相交 58 | def calc_score(r, c, x_direction, y_direction, list1, list2, scores_all): 59 | add_score = 0 60 | max_score = (0, None) 61 | # 避免重复计算 62 | for score_all in scores_all: 63 | for ps in score_all[1]: 64 | if r == ps[0] and c == ps[1] and x_direction == score_all[2][0] and y_direction == score_all[2][1]: 65 | return 0, scores_all 66 | # 获得棋型 67 | for noffset in range(-5, 1): 68 | position = [] 69 | for poffset in range(0, 6): 70 | x, y = r + (poffset + noffset) * x_direction, c + (poffset + noffset) * y_direction 71 | if (x, y) in list2: 72 | position.append(2) 73 | elif (x, y) in list1: 74 | position.append(1) 75 | else: 76 | position.append(0) 77 | temp_shape5 = tuple([i for i in position[0: -1]]) 78 | temp_shape6 = tuple(position) 79 | for score, shape in scoreModel: 80 | if temp_shape5 == shape or temp_shape6 == shape: 81 | if score > max_score[0]: 82 | max_score = (score, ((r + (0 + noffset) * x_direction, c + (0 + noffset) * y_direction), 83 | (r + (1 + noffset) * x_direction, c + (1 + noffset) * y_direction), 84 | (r + (2 + noffset) * x_direction, c + (2 + noffset) * y_direction), 85 | (r + (3 + noffset) * x_direction, c + (3 + noffset) * y_direction), 86 | (r + (4 + noffset) * x_direction, c + (4 + noffset) * y_direction)), (x_direction, y_direction)) 87 | # 如果棋型相交,则得分增加 88 | if max_score[1] is not None: 89 | for score_all in scores_all: 90 | for ps1 in score_all[1]: 91 | for ps2 in max_score[1]: 92 | if ps1 == ps2 and max_score[0] > 10 and score_all[0] > 10: 93 | add_score += max_score[0] + score_all[0] 94 | scores_all.append(max_score) 95 | return add_score + max_score[0], scores_all 96 | 97 | 98 | # 评估函数 99 | def evaluation(is_ai): 100 | total_score = 0 101 | if is_ai: 102 | list1 = ai_list 103 | list2 = me_list 104 | else: 105 | list1 = me_list 106 | list2 = ai_list 107 | # 评估主动方(下子方) 108 | Active_scores_all = [] 109 | Active_score = 0 110 | for l1 in list1: 111 | r, c = l1[0], l1[1] 112 | temp_score, Active_scores_all = calc_score(r, c, 0, 1, list1, list2, Active_scores_all) 113 | Active_score += temp_score 114 | temp_score, Active_scores_all = calc_score(r, c, 1, 0, list1, list2, Active_scores_all) 115 | Active_score += temp_score 116 | temp_score, Active_scores_all = calc_score(r, c, 1, 1, list1, list2, Active_scores_all) 117 | Active_score += temp_score 118 | temp_score, Active_scores_all = calc_score(r, c, -1, 1, list1, list2, Active_scores_all) 119 | Active_score += temp_score 120 | # 评估被动方(非下子方) 121 | Passive_scores_all = [] 122 | Passive_score = 0 123 | for l2 in list2: 124 | r, c = l2[0], l2[1] 125 | temp_score, Passive_scores_all = calc_score(r, c, 0, 1, list2, list1, Passive_scores_all) 126 | Passive_score += temp_score 127 | temp_score, Passive_scores_all = calc_score(r, c, 1, 0, list2, list1, Passive_scores_all) 128 | Passive_score += temp_score 129 | temp_score, Passive_scores_all = calc_score(r, c, 1, 1, list2, list1, Passive_scores_all) 130 | Passive_score += temp_score 131 | temp_score, Passive_scores_all = calc_score(r, c, -1, 1, list2, list1, Passive_scores_all) 132 | Passive_score += temp_score 133 | # 总评 134 | total_score = Active_score - Passive_score * ratio * 0.1 135 | return total_score 136 | 137 | 138 | # 重新排列未落子的位置列表 139 | # 假设离最后落子的邻居位置最有可能是最优点 140 | def Rearrange(blank_list): 141 | last_step = aime_list[-1] 142 | for bl in blank_list: 143 | for i in range(-1, 2): 144 | for j in range(-1, 2): 145 | if i == 0 and j == 0: 146 | continue 147 | next_step = (last_step[0]+i, last_step[1]+j) 148 | if next_step in blank_list: 149 | blank_list.remove(next_step) 150 | blank_list.insert(0, next_step) 151 | return blank_list 152 | 153 | 154 | # 判断下一步位置是否存在相邻的子 155 | def has_neighbor(next_step): 156 | for i in range(-1, 2): 157 | for j in range(-1, 2): 158 | if i == 0 and j == 0: 159 | continue 160 | if (next_step[0]+i, next_step[1]+j) in aime_list: 161 | return True 162 | return False 163 | 164 | 165 | # 负极大值搜索 alpha+beta剪枝 166 | # is_ai: AI方下还是我方下 167 | def negativeMax(is_ai, depth, alpha, beta): 168 | if is_GameOver(ai_list) or is_GameOver(me_list) or depth == 0: 169 | return evaluation(is_ai) 170 | # 未落子的位置 171 | blank_list = list(set(all_list).difference(set(aime_list))) 172 | blank_list = Rearrange(blank_list) 173 | for next_step in blank_list: 174 | global search_count 175 | search_count += 1 176 | if not has_neighbor(next_step): 177 | continue 178 | if is_ai: 179 | ai_list.append(next_step) 180 | else: 181 | me_list.append(next_step) 182 | aime_list.append(next_step) 183 | value = -negativeMax(not is_ai, depth-1, -beta, -alpha) 184 | if is_ai: 185 | ai_list.remove(next_step) 186 | else: 187 | me_list.remove(next_step) 188 | aime_list.remove(next_step) 189 | if value > alpha: 190 | if depth == DEPTH: 191 | next_point[0], next_point[1] = next_step[0], next_step[1] 192 | if value >= beta: 193 | global cut_count 194 | cut_count += 1 195 | return beta 196 | alpha = value 197 | return alpha 198 | 199 | 200 | # AI下棋 201 | def AI(): 202 | global cut_count 203 | global search_count 204 | # 剪枝次数 205 | cut_count = 0 206 | # 搜索次数 207 | search_count = 0 208 | negativeMax(True, DEPTH, -99999999, 99999999) 209 | print('[Cut_Count]: %d, [Search_Count]: %d' % (cut_count, search_count)) 210 | return next_point[0], next_point[1] 211 | 212 | 213 | # 画棋盘 214 | def GobangWin(): 215 | gw = GraphWin('AI Gobang', GRID_WIDTH*COLUMN, GRID_WIDTH*ROW) 216 | gw.setBackground('gray') 217 | for j in range(0, GRID_WIDTH*COLUMN+1, GRID_WIDTH): 218 | l = Line(Point(j, 0), Point(j, GRID_WIDTH*COLUMN)) 219 | l.draw(gw) 220 | for i in range(0, GRID_WIDTH*ROW+1, GRID_WIDTH): 221 | l = Line(Point(0, i), Point(GRID_WIDTH*ROW, i)) 222 | l.draw(gw) 223 | return gw 224 | 225 | 226 | # 主程序 227 | def run(): 228 | # 初始化 229 | gw = GobangWin() 230 | for j in range(COLUMN+1): 231 | for i in range(ROW+1): 232 | all_list.append((i, j)) 233 | # 游戏是否结束flag 234 | is_game = True 235 | # 统计步数,用于判断现在轮到谁落子,奇数为AI方,偶数为我方 236 | step_count = 0 237 | while is_game: 238 | if step_count % 2: 239 | p_ai = AI() 240 | if p_ai in aime_list: 241 | message = Text(Point(300, 300), 'AI gets a wrong next step.') 242 | message.draw(gw) 243 | is_game = False 244 | ai_list.append(p_ai) 245 | aime_list.append(p_ai) 246 | piece = Circle(Point(GRID_WIDTH * p_ai[0], GRID_WIDTH * p_ai[1]), 16) 247 | piece.setFill('white') 248 | piece.draw(gw) 249 | if is_GameOver(ai_list): 250 | message = Text(Point(100, 100), 'AI white win.') 251 | message.draw(gw) 252 | is_game = False 253 | step_count += 1 254 | else: 255 | p_me = gw.getMouse() 256 | x = round((p_me.getX()) / GRID_WIDTH) 257 | y = round((p_me.getY()) / GRID_WIDTH) 258 | if not ((x, y) in aime_list): 259 | me_list.append((x, y)) 260 | aime_list.append((x, y)) 261 | piece = Circle(Point(GRID_WIDTH * x, GRID_WIDTH * y), 16) 262 | piece.setFill('black') 263 | piece.draw(gw) 264 | if is_GameOver(me_list): 265 | message = Text(Point(100, 100), 'You black win.') 266 | message.draw(gw) 267 | is_game = False 268 | step_count += 1 269 | # 游戏结束后的处理 270 | message = Text(Point(300, 300), 'Click anywhere to quit.') 271 | message.draw(gw) 272 | gw.getMouse() 273 | gw.close() 274 | 275 | 276 | 277 | 278 | 279 | if __name__ == '__main__': 280 | run() -------------------------------------------------------------------------------- /$AI五子棋/graphics.py: -------------------------------------------------------------------------------- 1 | # graphics.py 2 | """Simple object oriented graphics library 3 | 4 | The library is designed to make it very easy for novice programmers to 5 | experiment with computer graphics in an object oriented fashion. It is 6 | written by John Zelle for use with the book "Python Programming: An 7 | Introduction to Computer Science" (Franklin, Beedle & Associates). 8 | 9 | LICENSE: This is open-source software released under the terms of the 10 | GPL (http://www.gnu.org/licenses/gpl.html). 11 | 12 | PLATFORMS: The package is a wrapper around Tkinter and should run on 13 | any platform where Tkinter is available. 14 | 15 | INSTALLATION: Put this file somewhere where Python can see it. 16 | 17 | OVERVIEW: There are two kinds of objects in the library. The GraphWin 18 | class implements a window where drawing can be done and various 19 | GraphicsObjects are provided that can be drawn into a GraphWin. As a 20 | simple example, here is a complete program to draw a circle of radius 21 | 10 centered in a 100x100 window: 22 | 23 | -------------------------------------------------------------------- 24 | from graphics import * 25 | 26 | def main(): 27 | win = GraphWin("My Circle", 100, 100) 28 | c = Circle(Point(50,50), 10) 29 | c.draw(win) 30 | win.getMouse() # Pause to view result 31 | win.close() # Close window when done 32 | 33 | main() 34 | -------------------------------------------------------------------- 35 | GraphWin objects support coordinate transformation through the 36 | setCoords method and mouse and keyboard interaction methods. 37 | 38 | The library provides the following graphical objects: 39 | Point 40 | Line 41 | Circle 42 | Oval 43 | Rectangle 44 | Polygon 45 | Text 46 | Entry (for text-based input) 47 | Image 48 | 49 | Various attributes of graphical objects can be set such as 50 | outline-color, fill-color and line-width. Graphical objects also 51 | support moving and hiding for animation effects. 52 | 53 | The library also provides a very simple class for pixel-based image 54 | manipulation, Pixmap. A pixmap can be loaded from a file and displayed 55 | using an Image object. Both getPixel and setPixel methods are provided 56 | for manipulating the image. 57 | 58 | DOCUMENTATION: For complete documentation, see Chapter 4 of "Python 59 | Programming: An Introduction to Computer Science" by John Zelle, 60 | published by Franklin, Beedle & Associates. Also see 61 | http://mcsp.wartburg.edu/zelle/python for a quick reference""" 62 | 63 | __version__ = "5.0" 64 | 65 | # Version 5 8/26/2016 66 | # * update at bottom to fix MacOS issue causing askopenfile() to hang 67 | # * update takes an optional parameter specifying update rate 68 | # * Entry objects get focus when drawn 69 | # * __repr_ for all objects 70 | # * fixed offset problem in window, made canvas borderless 71 | 72 | # Version 4.3 4/25/2014 73 | # * Fixed Image getPixel to work with Python 3.4, TK 8.6 (tuple type handling) 74 | # * Added interactive keyboard input (getKey and checkKey) to GraphWin 75 | # * Modified setCoords to cause redraw of current objects, thus 76 | # changing the view. This supports scrolling around via setCoords. 77 | # 78 | # Version 4.2 5/26/2011 79 | # * Modified Image to allow multiple undraws like other GraphicsObjects 80 | # Version 4.1 12/29/2009 81 | # * Merged Pixmap and Image class. Old Pixmap removed, use Image. 82 | # Version 4.0.1 10/08/2009 83 | # * Modified the autoflush on GraphWin to default to True 84 | # * Autoflush check on close, setBackground 85 | # * Fixed getMouse to flush pending clicks at entry 86 | # Version 4.0 08/2009 87 | # * Reverted to non-threaded version. The advantages (robustness, 88 | # efficiency, ability to use with other Tk code, etc.) outweigh 89 | # the disadvantage that interactive use with IDLE is slightly more 90 | # cumbersome. 91 | # * Modified to run in either Python 2.x or 3.x (same file). 92 | # * Added Image.getPixmap() 93 | # * Added update() -- stand alone function to cause any pending 94 | # graphics changes to display. 95 | # 96 | # Version 3.4 10/16/07 97 | # Fixed GraphicsError to avoid "exploded" error messages. 98 | # Version 3.3 8/8/06 99 | # Added checkMouse method to GraphWin 100 | # Version 3.2.3 101 | # Fixed error in Polygon init spotted by Andrew Harrington 102 | # Fixed improper threading in Image constructor 103 | # Version 3.2.2 5/30/05 104 | # Cleaned up handling of exceptions in Tk thread. The graphics package 105 | # now raises an exception if attempt is made to communicate with 106 | # a dead Tk thread. 107 | # Version 3.2.1 5/22/05 108 | # Added shutdown function for tk thread to eliminate race-condition 109 | # error "chatter" when main thread terminates 110 | # Renamed various private globals with _ 111 | # Version 3.2 5/4/05 112 | # Added Pixmap object for simple image manipulation. 113 | # Version 3.1 4/13/05 114 | # Improved the Tk thread communication so that most Tk calls 115 | # do not have to wait for synchonization with the Tk thread. 116 | # (see _tkCall and _tkExec) 117 | # Version 3.0 12/30/04 118 | # Implemented Tk event loop in separate thread. Should now work 119 | # interactively with IDLE. Undocumented autoflush feature is 120 | # no longer necessary. Its default is now False (off). It may 121 | # be removed in a future version. 122 | # Better handling of errors regarding operations on windows that 123 | # have been closed. 124 | # Addition of an isClosed method to GraphWindow class. 125 | 126 | # Version 2.2 8/26/04 127 | # Fixed cloning bug reported by Joseph Oldham. 128 | # Now implements deep copy of config info. 129 | # Version 2.1 1/15/04 130 | # Added autoflush option to GraphWin. When True (default) updates on 131 | # the window are done after each action. This makes some graphics 132 | # intensive programs sluggish. Turning off autoflush causes updates 133 | # to happen during idle periods or when flush is called. 134 | # Version 2.0 135 | # Updated Documentation 136 | # Made Polygon accept a list of Points in constructor 137 | # Made all drawing functions call TK update for easier animations 138 | # and to make the overall package work better with 139 | # Python 2.3 and IDLE 1.0 under Windows (still some issues). 140 | # Removed vestigial turtle graphics. 141 | # Added ability to configure font for Entry objects (analogous to Text) 142 | # Added setTextColor for Text as an alias of setFill 143 | # Changed to class-style exceptions 144 | # Fixed cloning of Text objects 145 | 146 | # Version 1.6 147 | # Fixed Entry so StringVar uses _root as master, solves weird 148 | # interaction with shell in Idle 149 | # Fixed bug in setCoords. X and Y coordinates can increase in 150 | # "non-intuitive" direction. 151 | # Tweaked wm_protocol so window is not resizable and kill box closes. 152 | 153 | # Version 1.5 154 | # Fixed bug in Entry. Can now define entry before creating a 155 | # GraphWin. All GraphWins are now toplevel windows and share 156 | # a fixed root (called _root). 157 | 158 | # Version 1.4 159 | # Fixed Garbage collection of Tkinter images bug. 160 | # Added ability to set text atttributes. 161 | # Added Entry boxes. 162 | 163 | import time, os, sys 164 | 165 | try: # import as appropriate for 2.x vs. 3.x 166 | import tkinter as tk 167 | except: 168 | import Tkinter as tk 169 | 170 | 171 | ########################################################################## 172 | # Module Exceptions 173 | 174 | class GraphicsError(Exception): 175 | """Generic error class for graphics module exceptions.""" 176 | pass 177 | 178 | OBJ_ALREADY_DRAWN = "Object currently drawn" 179 | UNSUPPORTED_METHOD = "Object doesn't support operation" 180 | BAD_OPTION = "Illegal option value" 181 | 182 | ########################################################################## 183 | # global variables and funtions 184 | 185 | _root = tk.Tk() 186 | _root.withdraw() 187 | 188 | _update_lasttime = time.time() 189 | 190 | def update(rate=None): 191 | global _update_lasttime 192 | if rate: 193 | now = time.time() 194 | pauseLength = 1/rate-(now-_update_lasttime) 195 | if pauseLength > 0: 196 | time.sleep(pauseLength) 197 | _update_lasttime = now + pauseLength 198 | else: 199 | _update_lasttime = now 200 | 201 | _root.update() 202 | 203 | ############################################################################ 204 | # Graphics classes start here 205 | 206 | class GraphWin(tk.Canvas): 207 | 208 | """A GraphWin is a toplevel window for displaying graphics.""" 209 | 210 | def __init__(self, title="Graphics Window", 211 | width=200, height=200, autoflush=True): 212 | assert type(title) == type(""), "Title must be a string" 213 | master = tk.Toplevel(_root) 214 | master.protocol("WM_DELETE_WINDOW", self.close) 215 | tk.Canvas.__init__(self, master, width=width, height=height, 216 | highlightthickness=0, bd=0) 217 | self.master.title(title) 218 | self.pack() 219 | master.resizable(0,0) 220 | self.foreground = "black" 221 | self.items = [] 222 | self.mouseX = None 223 | self.mouseY = None 224 | self.bind("", self._onClick) 225 | self.bind_all("", self._onKey) 226 | self.height = int(height) 227 | self.width = int(width) 228 | self.autoflush = autoflush 229 | self._mouseCallback = None 230 | self.trans = None 231 | self.closed = False 232 | master.lift() 233 | self.lastKey = "" 234 | if autoflush: _root.update() 235 | 236 | def __repr__(self): 237 | if self.isClosed(): 238 | return "" 239 | else: 240 | return "GraphWin('{}', {}, {})".format(self.master.title(), 241 | self.getWidth(), 242 | self.getHeight()) 243 | 244 | def __str__(self): 245 | return repr(self) 246 | 247 | def __checkOpen(self): 248 | if self.closed: 249 | raise GraphicsError("window is closed") 250 | 251 | def _onKey(self, evnt): 252 | self.lastKey = evnt.keysym 253 | 254 | 255 | def setBackground(self, color): 256 | """Set background color of the window""" 257 | self.__checkOpen() 258 | self.config(bg=color) 259 | self.__autoflush() 260 | 261 | def setCoords(self, x1, y1, x2, y2): 262 | """Set coordinates of window to run from (x1,y1) in the 263 | lower-left corner to (x2,y2) in the upper-right corner.""" 264 | self.trans = Transform(self.width, self.height, x1, y1, x2, y2) 265 | self.redraw() 266 | 267 | def close(self): 268 | """Close the window""" 269 | 270 | if self.closed: return 271 | self.closed = True 272 | self.master.destroy() 273 | self.__autoflush() 274 | 275 | 276 | def isClosed(self): 277 | return self.closed 278 | 279 | 280 | def isOpen(self): 281 | return not self.closed 282 | 283 | 284 | def __autoflush(self): 285 | if self.autoflush: 286 | _root.update() 287 | 288 | 289 | def plot(self, x, y, color="black"): 290 | """Set pixel (x,y) to the given color""" 291 | self.__checkOpen() 292 | xs,ys = self.toScreen(x,y) 293 | self.create_line(xs,ys,xs+1,ys, fill=color) 294 | self.__autoflush() 295 | 296 | def plotPixel(self, x, y, color="black"): 297 | """Set pixel raw (independent of window coordinates) pixel 298 | (x,y) to color""" 299 | self.__checkOpen() 300 | self.create_line(x,y,x+1,y, fill=color) 301 | self.__autoflush() 302 | 303 | def flush(self): 304 | """Update drawing to the window""" 305 | self.__checkOpen() 306 | self.update_idletasks() 307 | 308 | def getMouse(self): 309 | """Wait for mouse click and return Point object representing 310 | the click""" 311 | self.update() # flush any prior clicks 312 | self.mouseX = None 313 | self.mouseY = None 314 | while self.mouseX == None or self.mouseY == None: 315 | self.update() 316 | if self.isClosed(): raise GraphicsError("getMouse in closed window") 317 | time.sleep(.1) # give up thread 318 | x,y = self.toWorld(self.mouseX, self.mouseY) 319 | self.mouseX = None 320 | self.mouseY = None 321 | return Point(x,y) 322 | 323 | def checkMouse(self): 324 | """Return last mouse click or None if mouse has 325 | not been clicked since last call""" 326 | if self.isClosed(): 327 | raise GraphicsError("checkMouse in closed window") 328 | self.update() 329 | if self.mouseX != None and self.mouseY != None: 330 | x,y = self.toWorld(self.mouseX, self.mouseY) 331 | self.mouseX = None 332 | self.mouseY = None 333 | return Point(x,y) 334 | else: 335 | return None 336 | 337 | def getKey(self): 338 | """Wait for user to press a key and return it as a string.""" 339 | self.lastKey = "" 340 | while self.lastKey == "": 341 | self.update() 342 | if self.isClosed(): raise GraphicsError("getKey in closed window") 343 | time.sleep(.1) # give up thread 344 | 345 | key = self.lastKey 346 | self.lastKey = "" 347 | return key 348 | 349 | def checkKey(self): 350 | """Return last key pressed or None if no key pressed since last call""" 351 | if self.isClosed(): 352 | raise GraphicsError("checkKey in closed window") 353 | self.update() 354 | key = self.lastKey 355 | self.lastKey = "" 356 | return key 357 | 358 | def getHeight(self): 359 | """Return the height of the window""" 360 | return self.height 361 | 362 | def getWidth(self): 363 | """Return the width of the window""" 364 | return self.width 365 | 366 | def toScreen(self, x, y): 367 | trans = self.trans 368 | if trans: 369 | return self.trans.screen(x,y) 370 | else: 371 | return x,y 372 | 373 | def toWorld(self, x, y): 374 | trans = self.trans 375 | if trans: 376 | return self.trans.world(x,y) 377 | else: 378 | return x,y 379 | 380 | def setMouseHandler(self, func): 381 | self._mouseCallback = func 382 | 383 | def _onClick(self, e): 384 | self.mouseX = e.x 385 | self.mouseY = e.y 386 | if self._mouseCallback: 387 | self._mouseCallback(Point(e.x, e.y)) 388 | 389 | def addItem(self, item): 390 | self.items.append(item) 391 | 392 | def delItem(self, item): 393 | self.items.remove(item) 394 | 395 | def redraw(self): 396 | for item in self.items[:]: 397 | item.undraw() 398 | item.draw(self) 399 | self.update() 400 | 401 | 402 | class Transform: 403 | 404 | """Internal class for 2-D coordinate transformations""" 405 | 406 | def __init__(self, w, h, xlow, ylow, xhigh, yhigh): 407 | # w, h are width and height of window 408 | # (xlow,ylow) coordinates of lower-left [raw (0,h-1)] 409 | # (xhigh,yhigh) coordinates of upper-right [raw (w-1,0)] 410 | xspan = (xhigh-xlow) 411 | yspan = (yhigh-ylow) 412 | self.xbase = xlow 413 | self.ybase = yhigh 414 | self.xscale = xspan/float(w-1) 415 | self.yscale = yspan/float(h-1) 416 | 417 | def screen(self,x,y): 418 | # Returns x,y in screen (actually window) coordinates 419 | xs = (x-self.xbase) / self.xscale 420 | ys = (self.ybase-y) / self.yscale 421 | return int(xs+0.5),int(ys+0.5) 422 | 423 | def world(self,xs,ys): 424 | # Returns xs,ys in world coordinates 425 | x = xs*self.xscale + self.xbase 426 | y = self.ybase - ys*self.yscale 427 | return x,y 428 | 429 | 430 | # Default values for various item configuration options. Only a subset of 431 | # keys may be present in the configuration dictionary for a given item 432 | DEFAULT_CONFIG = {"fill":"", 433 | "outline":"black", 434 | "width":"1", 435 | "arrow":"none", 436 | "text":"", 437 | "justify":"center", 438 | "font": ("helvetica", 12, "normal")} 439 | 440 | class GraphicsObject: 441 | 442 | """Generic base class for all of the drawable objects""" 443 | # A subclass of GraphicsObject should override _draw and 444 | # and _move methods. 445 | 446 | def __init__(self, options): 447 | # options is a list of strings indicating which options are 448 | # legal for this object. 449 | 450 | # When an object is drawn, canvas is set to the GraphWin(canvas) 451 | # object where it is drawn and id is the TK identifier of the 452 | # drawn shape. 453 | self.canvas = None 454 | self.id = None 455 | 456 | # config is the dictionary of configuration options for the widget. 457 | config = {} 458 | for option in options: 459 | config[option] = DEFAULT_CONFIG[option] 460 | self.config = config 461 | 462 | def setFill(self, color): 463 | """Set interior color to color""" 464 | self._reconfig("fill", color) 465 | 466 | def setOutline(self, color): 467 | """Set outline color to color""" 468 | self._reconfig("outline", color) 469 | 470 | def setWidth(self, width): 471 | """Set line weight to width""" 472 | self._reconfig("width", width) 473 | 474 | def draw(self, graphwin): 475 | 476 | """Draw the object in graphwin, which should be a GraphWin 477 | object. A GraphicsObject may only be drawn into one 478 | window. Raises an error if attempt made to draw an object that 479 | is already visible.""" 480 | 481 | if self.canvas and not self.canvas.isClosed(): raise GraphicsError(OBJ_ALREADY_DRAWN) 482 | if graphwin.isClosed(): raise GraphicsError("Can't draw to closed window") 483 | self.canvas = graphwin 484 | self.id = self._draw(graphwin, self.config) 485 | graphwin.addItem(self) 486 | if graphwin.autoflush: 487 | _root.update() 488 | return self 489 | 490 | 491 | def undraw(self): 492 | 493 | """Undraw the object (i.e. hide it). Returns silently if the 494 | object is not currently drawn.""" 495 | 496 | if not self.canvas: return 497 | if not self.canvas.isClosed(): 498 | self.canvas.delete(self.id) 499 | self.canvas.delItem(self) 500 | if self.canvas.autoflush: 501 | _root.update() 502 | self.canvas = None 503 | self.id = None 504 | 505 | 506 | def move(self, dx, dy): 507 | 508 | """move object dx units in x direction and dy units in y 509 | direction""" 510 | 511 | self._move(dx,dy) 512 | canvas = self.canvas 513 | if canvas and not canvas.isClosed(): 514 | trans = canvas.trans 515 | if trans: 516 | x = dx/ trans.xscale 517 | y = -dy / trans.yscale 518 | else: 519 | x = dx 520 | y = dy 521 | self.canvas.move(self.id, x, y) 522 | if canvas.autoflush: 523 | _root.update() 524 | 525 | def _reconfig(self, option, setting): 526 | # Internal method for changing configuration of the object 527 | # Raises an error if the option does not exist in the config 528 | # dictionary for this object 529 | if option not in self.config: 530 | raise GraphicsError(UNSUPPORTED_METHOD) 531 | options = self.config 532 | options[option] = setting 533 | if self.canvas and not self.canvas.isClosed(): 534 | self.canvas.itemconfig(self.id, options) 535 | if self.canvas.autoflush: 536 | _root.update() 537 | 538 | 539 | def _draw(self, canvas, options): 540 | """draws appropriate figure on canvas with options provided 541 | Returns Tk id of item drawn""" 542 | pass # must override in subclass 543 | 544 | 545 | def _move(self, dx, dy): 546 | """updates internal state of object to move it dx,dy units""" 547 | pass # must override in subclass 548 | 549 | 550 | class Point(GraphicsObject): 551 | def __init__(self, x, y): 552 | GraphicsObject.__init__(self, ["outline", "fill"]) 553 | self.setFill = self.setOutline 554 | self.x = float(x) 555 | self.y = float(y) 556 | 557 | def __repr__(self): 558 | return "Point({}, {})".format(self.x, self.y) 559 | 560 | def _draw(self, canvas, options): 561 | x,y = canvas.toScreen(self.x,self.y) 562 | return canvas.create_rectangle(x,y,x+1,y+1,options) 563 | 564 | def _move(self, dx, dy): 565 | self.x = self.x + dx 566 | self.y = self.y + dy 567 | 568 | def clone(self): 569 | other = Point(self.x,self.y) 570 | other.config = self.config.copy() 571 | return other 572 | 573 | def getX(self): return self.x 574 | def getY(self): return self.y 575 | 576 | class _BBox(GraphicsObject): 577 | # Internal base class for objects represented by bounding box 578 | # (opposite corners) Line segment is a degenerate case. 579 | 580 | def __init__(self, p1, p2, options=["outline","width","fill"]): 581 | GraphicsObject.__init__(self, options) 582 | self.p1 = p1.clone() 583 | self.p2 = p2.clone() 584 | 585 | def _move(self, dx, dy): 586 | self.p1.x = self.p1.x + dx 587 | self.p1.y = self.p1.y + dy 588 | self.p2.x = self.p2.x + dx 589 | self.p2.y = self.p2.y + dy 590 | 591 | def getP1(self): return self.p1.clone() 592 | 593 | def getP2(self): return self.p2.clone() 594 | 595 | def getCenter(self): 596 | p1 = self.p1 597 | p2 = self.p2 598 | return Point((p1.x+p2.x)/2.0, (p1.y+p2.y)/2.0) 599 | 600 | 601 | class Rectangle(_BBox): 602 | 603 | def __init__(self, p1, p2): 604 | _BBox.__init__(self, p1, p2) 605 | 606 | def __repr__(self): 607 | return "Rectangle({}, {})".format(str(self.p1), str(self.p2)) 608 | 609 | def _draw(self, canvas, options): 610 | p1 = self.p1 611 | p2 = self.p2 612 | x1,y1 = canvas.toScreen(p1.x,p1.y) 613 | x2,y2 = canvas.toScreen(p2.x,p2.y) 614 | return canvas.create_rectangle(x1,y1,x2,y2,options) 615 | 616 | def clone(self): 617 | other = Rectangle(self.p1, self.p2) 618 | other.config = self.config.copy() 619 | return other 620 | 621 | 622 | class Oval(_BBox): 623 | 624 | def __init__(self, p1, p2): 625 | _BBox.__init__(self, p1, p2) 626 | 627 | def __repr__(self): 628 | return "Oval({}, {})".format(str(self.p1), str(self.p2)) 629 | 630 | 631 | def clone(self): 632 | other = Oval(self.p1, self.p2) 633 | other.config = self.config.copy() 634 | return other 635 | 636 | def _draw(self, canvas, options): 637 | p1 = self.p1 638 | p2 = self.p2 639 | x1,y1 = canvas.toScreen(p1.x,p1.y) 640 | x2,y2 = canvas.toScreen(p2.x,p2.y) 641 | return canvas.create_oval(x1,y1,x2,y2,options) 642 | 643 | class Circle(Oval): 644 | 645 | def __init__(self, center, radius): 646 | p1 = Point(center.x-radius, center.y-radius) 647 | p2 = Point(center.x+radius, center.y+radius) 648 | Oval.__init__(self, p1, p2) 649 | self.radius = radius 650 | 651 | def __repr__(self): 652 | return "Circle({}, {})".format(str(self.getCenter()), str(self.radius)) 653 | 654 | def clone(self): 655 | other = Circle(self.getCenter(), self.radius) 656 | other.config = self.config.copy() 657 | return other 658 | 659 | def getRadius(self): 660 | return self.radius 661 | 662 | 663 | class Line(_BBox): 664 | 665 | def __init__(self, p1, p2): 666 | _BBox.__init__(self, p1, p2, ["arrow","fill","width"]) 667 | self.setFill(DEFAULT_CONFIG['outline']) 668 | self.setOutline = self.setFill 669 | 670 | def __repr__(self): 671 | return "Line({}, {})".format(str(self.p1), str(self.p2)) 672 | 673 | def clone(self): 674 | other = Line(self.p1, self.p2) 675 | other.config = self.config.copy() 676 | return other 677 | 678 | def _draw(self, canvas, options): 679 | p1 = self.p1 680 | p2 = self.p2 681 | x1,y1 = canvas.toScreen(p1.x,p1.y) 682 | x2,y2 = canvas.toScreen(p2.x,p2.y) 683 | return canvas.create_line(x1,y1,x2,y2,options) 684 | 685 | def setArrow(self, option): 686 | if not option in ["first","last","both","none"]: 687 | raise GraphicsError(BAD_OPTION) 688 | self._reconfig("arrow", option) 689 | 690 | 691 | class Polygon(GraphicsObject): 692 | 693 | def __init__(self, *points): 694 | # if points passed as a list, extract it 695 | if len(points) == 1 and type(points[0]) == type([]): 696 | points = points[0] 697 | self.points = list(map(Point.clone, points)) 698 | GraphicsObject.__init__(self, ["outline", "width", "fill"]) 699 | 700 | def __repr__(self): 701 | return "Polygon"+str(tuple(p for p in self.points)) 702 | 703 | def clone(self): 704 | other = Polygon(*self.points) 705 | other.config = self.config.copy() 706 | return other 707 | 708 | def getPoints(self): 709 | return list(map(Point.clone, self.points)) 710 | 711 | def _move(self, dx, dy): 712 | for p in self.points: 713 | p.move(dx,dy) 714 | 715 | def _draw(self, canvas, options): 716 | args = [canvas] 717 | for p in self.points: 718 | x,y = canvas.toScreen(p.x,p.y) 719 | args.append(x) 720 | args.append(y) 721 | args.append(options) 722 | return GraphWin.create_polygon(*args) 723 | 724 | class Text(GraphicsObject): 725 | 726 | def __init__(self, p, text): 727 | GraphicsObject.__init__(self, ["justify","fill","text","font"]) 728 | self.setText(text) 729 | self.anchor = p.clone() 730 | self.setFill(DEFAULT_CONFIG['outline']) 731 | self.setOutline = self.setFill 732 | 733 | def __repr__(self): 734 | return "Text({}, '{}')".format(self.anchor, self.getText()) 735 | 736 | def _draw(self, canvas, options): 737 | p = self.anchor 738 | x,y = canvas.toScreen(p.x,p.y) 739 | return canvas.create_text(x,y,options) 740 | 741 | def _move(self, dx, dy): 742 | self.anchor.move(dx,dy) 743 | 744 | def clone(self): 745 | other = Text(self.anchor, self.config['text']) 746 | other.config = self.config.copy() 747 | return other 748 | 749 | def setText(self,text): 750 | self._reconfig("text", text) 751 | 752 | def getText(self): 753 | return self.config["text"] 754 | 755 | def getAnchor(self): 756 | return self.anchor.clone() 757 | 758 | def setFace(self, face): 759 | if face in ['helvetica','arial','courier','times roman']: 760 | f,s,b = self.config['font'] 761 | self._reconfig("font",(face,s,b)) 762 | else: 763 | raise GraphicsError(BAD_OPTION) 764 | 765 | def setSize(self, size): 766 | if 5 <= size <= 36: 767 | f,s,b = self.config['font'] 768 | self._reconfig("font", (f,size,b)) 769 | else: 770 | raise GraphicsError(BAD_OPTION) 771 | 772 | def setStyle(self, style): 773 | if style in ['bold','normal','italic', 'bold italic']: 774 | f,s,b = self.config['font'] 775 | self._reconfig("font", (f,s,style)) 776 | else: 777 | raise GraphicsError(BAD_OPTION) 778 | 779 | def setTextColor(self, color): 780 | self.setFill(color) 781 | 782 | 783 | class Entry(GraphicsObject): 784 | 785 | def __init__(self, p, width): 786 | GraphicsObject.__init__(self, []) 787 | self.anchor = p.clone() 788 | #print self.anchor 789 | self.width = width 790 | self.text = tk.StringVar(_root) 791 | self.text.set("") 792 | self.fill = "gray" 793 | self.color = "black" 794 | self.font = DEFAULT_CONFIG['font'] 795 | self.entry = None 796 | 797 | def __repr__(self): 798 | return "Entry({}, {})".format(self.anchor, self.width) 799 | 800 | def _draw(self, canvas, options): 801 | p = self.anchor 802 | x,y = canvas.toScreen(p.x,p.y) 803 | frm = tk.Frame(canvas.master) 804 | self.entry = tk.Entry(frm, 805 | width=self.width, 806 | textvariable=self.text, 807 | bg = self.fill, 808 | fg = self.color, 809 | font=self.font) 810 | self.entry.pack() 811 | #self.setFill(self.fill) 812 | self.entry.focus_set() 813 | return canvas.create_window(x,y,window=frm) 814 | 815 | def getText(self): 816 | return self.text.get() 817 | 818 | def _move(self, dx, dy): 819 | self.anchor.move(dx,dy) 820 | 821 | def getAnchor(self): 822 | return self.anchor.clone() 823 | 824 | def clone(self): 825 | other = Entry(self.anchor, self.width) 826 | other.config = self.config.copy() 827 | other.text = tk.StringVar() 828 | other.text.set(self.text.get()) 829 | other.fill = self.fill 830 | return other 831 | 832 | def setText(self, t): 833 | self.text.set(t) 834 | 835 | 836 | def setFill(self, color): 837 | self.fill = color 838 | if self.entry: 839 | self.entry.config(bg=color) 840 | 841 | 842 | def _setFontComponent(self, which, value): 843 | font = list(self.font) 844 | font[which] = value 845 | self.font = tuple(font) 846 | if self.entry: 847 | self.entry.config(font=self.font) 848 | 849 | 850 | def setFace(self, face): 851 | if face in ['helvetica','arial','courier','times roman']: 852 | self._setFontComponent(0, face) 853 | else: 854 | raise GraphicsError(BAD_OPTION) 855 | 856 | def setSize(self, size): 857 | if 5 <= size <= 36: 858 | self._setFontComponent(1,size) 859 | else: 860 | raise GraphicsError(BAD_OPTION) 861 | 862 | def setStyle(self, style): 863 | if style in ['bold','normal','italic', 'bold italic']: 864 | self._setFontComponent(2,style) 865 | else: 866 | raise GraphicsError(BAD_OPTION) 867 | 868 | def setTextColor(self, color): 869 | self.color=color 870 | if self.entry: 871 | self.entry.config(fg=color) 872 | 873 | 874 | class Image(GraphicsObject): 875 | 876 | idCount = 0 877 | imageCache = {} # tk photoimages go here to avoid GC while drawn 878 | 879 | def __init__(self, p, *pixmap): 880 | GraphicsObject.__init__(self, []) 881 | self.anchor = p.clone() 882 | self.imageId = Image.idCount 883 | Image.idCount = Image.idCount + 1 884 | if len(pixmap) == 1: # file name provided 885 | self.img = tk.PhotoImage(file=pixmap[0], master=_root) 886 | else: # width and height provided 887 | width, height = pixmap 888 | self.img = tk.PhotoImage(master=_root, width=width, height=height) 889 | 890 | def __repr__(self): 891 | return "Image({}, {}, {})".format(self.anchor, self.getWidth(), self.getHeight()) 892 | 893 | def _draw(self, canvas, options): 894 | p = self.anchor 895 | x,y = canvas.toScreen(p.x,p.y) 896 | self.imageCache[self.imageId] = self.img # save a reference 897 | return canvas.create_image(x,y,image=self.img) 898 | 899 | def _move(self, dx, dy): 900 | self.anchor.move(dx,dy) 901 | 902 | def undraw(self): 903 | try: 904 | del self.imageCache[self.imageId] # allow gc of tk photoimage 905 | except KeyError: 906 | pass 907 | GraphicsObject.undraw(self) 908 | 909 | def getAnchor(self): 910 | return self.anchor.clone() 911 | 912 | def clone(self): 913 | other = Image(Point(0,0), 0, 0) 914 | other.img = self.img.copy() 915 | other.anchor = self.anchor.clone() 916 | other.config = self.config.copy() 917 | return other 918 | 919 | def getWidth(self): 920 | """Returns the width of the image in pixels""" 921 | return self.img.width() 922 | 923 | def getHeight(self): 924 | """Returns the height of the image in pixels""" 925 | return self.img.height() 926 | 927 | def getPixel(self, x, y): 928 | """Returns a list [r,g,b] with the RGB color values for pixel (x,y) 929 | r,g,b are in range(256) 930 | 931 | """ 932 | 933 | value = self.img.get(x,y) 934 | if type(value) == type(0): 935 | return [value, value, value] 936 | elif type(value) == type((0,0,0)): 937 | return list(value) 938 | else: 939 | return list(map(int, value.split())) 940 | 941 | def setPixel(self, x, y, color): 942 | """Sets pixel (x,y) to the given color 943 | 944 | """ 945 | self.img.put("{" + color +"}", (x, y)) 946 | 947 | 948 | def save(self, filename): 949 | """Saves the pixmap image to filename. 950 | The format for the save image is determined from the filname extension. 951 | 952 | """ 953 | 954 | path, name = os.path.split(filename) 955 | ext = name.split(".")[-1] 956 | self.img.write( filename, format=ext) 957 | 958 | 959 | def color_rgb(r,g,b): 960 | """r,g,b are intensities of red, green, and blue in range(256) 961 | Returns color specifier string for the resulting color""" 962 | return "#%02x%02x%02x" % (r,g,b) 963 | 964 | def test(): 965 | win = GraphWin() 966 | win.setCoords(0,0,10,10) 967 | t = Text(Point(5,5), "Centered Text") 968 | t.draw(win) 969 | p = Polygon(Point(1,1), Point(5,3), Point(2,7)) 970 | p.draw(win) 971 | e = Entry(Point(5,6), 10) 972 | e.draw(win) 973 | win.getMouse() 974 | p.setFill("red") 975 | p.setOutline("blue") 976 | p.setWidth(2) 977 | s = "" 978 | for pt in p.getPoints(): 979 | s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY()) 980 | t.setText(e.getText()) 981 | e.setFill("green") 982 | e.setText("Spam!") 983 | e.move(2,0) 984 | win.getMouse() 985 | p.move(2,3) 986 | s = "" 987 | for pt in p.getPoints(): 988 | s = s + "(%0.1f,%0.1f) " % (pt.getX(), pt.getY()) 989 | t.setText(s) 990 | win.getMouse() 991 | p.undraw() 992 | e.undraw() 993 | t.setStyle("bold") 994 | win.getMouse() 995 | t.setStyle("normal") 996 | win.getMouse() 997 | t.setStyle("italic") 998 | win.getMouse() 999 | t.setStyle("bold italic") 1000 | win.getMouse() 1001 | t.setSize(14) 1002 | win.getMouse() 1003 | t.setFace("arial") 1004 | t.setSize(20) 1005 | win.getMouse() 1006 | win.close() 1007 | 1008 | #MacOS fix 2 1009 | #tk.Toplevel(_root).destroy() 1010 | 1011 | # MacOS fix 1 1012 | update() 1013 | 1014 | if __name__ == "__main__": 1015 | test() 1016 | -------------------------------------------------------------------------------- /$AI五子棋/references/SearchingForSolutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$AI五子棋/references/SearchingForSolutions.pdf -------------------------------------------------------------------------------- /$AI五子棋/references/wagnervirag_2001.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$AI五子棋/references/wagnervirag_2001.pdf -------------------------------------------------------------------------------- /$滑雪游戏/Game4.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | # 公众号: Charles的皮卡丘 3 | # 作者: Charles 4 | # 滑雪小游戏 5 | import sys 6 | import pygame 7 | import random 8 | from pygame.locals import * 9 | 10 | 11 | # 滑雪者类 12 | class SkierClass(pygame.sprite.Sprite): 13 | def __init__(self): 14 | pygame.sprite.Sprite.__init__(self) 15 | # 滑雪者的朝向(-2到2) 16 | self.direction = 0 17 | self.imgs = ["./images/skier_forward.png", "./images/skier_right1.png", "./images/skier_right2.png", "./images/skier_left2.png", "./images/skier_left1.png"] 18 | self.person = pygame.image.load(self.imgs[self.direction]) 19 | self.rect = self.person.get_rect() 20 | self.rect.center = [320, 100] 21 | self.speed = [self.direction, 6-abs(self.direction)*2] 22 | # 改变滑雪者的朝向 23 | # 负数为向左,正数为向右,0为向前 24 | def turn(self, num): 25 | self.direction += num 26 | self.direction = max(-2, self.direction) 27 | self.direction = min(2, self.direction) 28 | center = self.rect.center 29 | self.person = pygame.image.load(self.imgs[self.direction]) 30 | self.rect = self.person.get_rect() 31 | self.rect.center = center 32 | self.speed = [self.direction, 6-abs(self.direction)*2] 33 | return self.speed 34 | # 移动滑雪者 35 | def move(self): 36 | self.rect.centerx += self.speed[0] 37 | self.rect.centerx = max(20, self.rect.centerx) 38 | self.rect.centerx = min(620, self.rect.centerx) 39 | 40 | 41 | # 障碍物类 42 | # Input: 43 | # -img_path: 障碍物图片路径 44 | # -location: 障碍物位置 45 | # -attribute: 障碍物类别属性 46 | class ObstacleClass(pygame.sprite.Sprite): 47 | def __init__(self, img_path, location, attribute): 48 | pygame.sprite.Sprite.__init__(self) 49 | self.img_path = img_path 50 | self.image = pygame.image.load(self.img_path) 51 | self.location = location 52 | self.rect = self.image.get_rect() 53 | self.rect.center = self.location 54 | self.attribute = attribute 55 | self.passed = False 56 | # 移动 57 | def move(self, num): 58 | self.rect.centery = self.location[1] - num 59 | 60 | 61 | # 创建障碍物 62 | def create_obstacles(s, e, num=10): 63 | obstacles = pygame.sprite.Group() 64 | locations = [] 65 | for i in range(num): 66 | row = random.randint(s, e) 67 | col = random.randint(0, 9) 68 | location = [col*64+20, row*64+20] 69 | if location not in locations: 70 | locations.append(location) 71 | attribute = random.choice(["tree", "flag"]) 72 | img_path = './images/tree.png' if attribute=="tree" else './images/flag.png' 73 | obstacle = ObstacleClass(img_path, location, attribute) 74 | obstacles.add(obstacle) 75 | return obstacles 76 | 77 | 78 | # 合并障碍物 79 | def AddObstacles(obstacles0, obstacles1): 80 | obstacles = pygame.sprite.Group() 81 | for obstacle in obstacles0: 82 | obstacles.add(obstacle) 83 | for obstacle in obstacles1: 84 | obstacles.add(obstacle) 85 | return obstacles 86 | 87 | 88 | # 显示游戏开始界面 89 | def Show_Start_Interface(Demo, width, height): 90 | Demo.fill((255, 255, 255)) 91 | tfont = pygame.font.Font('./font/simkai.ttf', width//4) 92 | cfont = pygame.font.Font('./font/simkai.ttf', width//20) 93 | title = tfont.render(u'滑雪游戏', True, (255, 0, 0)) 94 | content = cfont.render(u'按任意键开始游戏', True, (0, 0, 255)) 95 | trect = title.get_rect() 96 | trect.midtop = (width/2, height/10) 97 | crect = content.get_rect() 98 | crect.midtop = (width/2, height/2.2) 99 | Demo.blit(title, trect) 100 | Demo.blit(content, crect) 101 | pygame.display.update() 102 | while True: 103 | for event in pygame.event.get(): 104 | if event.type == QUIT: 105 | sys.exit() 106 | elif event.type == pygame.KEYDOWN: 107 | return 108 | 109 | 110 | # 主程序 111 | def main(): 112 | ''' 113 | 初始化 114 | ''' 115 | pygame.init() 116 | # 声音 117 | pygame.mixer.init() 118 | pygame.mixer.music.load("./music/bg_music.mp3") 119 | pygame.mixer.music.set_volume(0.4) 120 | pygame.mixer.music.play(-1) 121 | # 屏幕 122 | screen = pygame.display.set_mode([640, 640]) 123 | pygame.display.set_caption('滑雪游戏-公众号:Charles的皮卡丘') 124 | # 主频 125 | clock = pygame.time.Clock() 126 | # 滑雪者 127 | skier = SkierClass() 128 | # 记录滑雪的距离 129 | distance = 0 130 | # 创建障碍物 131 | obstacles0 = create_obstacles(20, 29) 132 | obstacles1 = create_obstacles(10, 19) 133 | obstaclesflag = 0 134 | obstacles = AddObstacles(obstacles0, obstacles1) 135 | # 分数 136 | font = pygame.font.Font(None, 50) 137 | score = 0 138 | score_text = font.render("Score: "+str(score), 1, (0, 0, 0)) 139 | # 速度 140 | speed = [0, 6] 141 | Show_Start_Interface(screen, 640, 640) 142 | ''' 143 | 主循环 144 | ''' 145 | # 更新屏幕 146 | def update(): 147 | screen.fill([255, 255, 255]) 148 | pygame.display.update(obstacles.draw(screen)) 149 | screen.blit(skier.person, skier.rect) 150 | screen.blit(score_text, [10, 10]) 151 | pygame.display.flip() 152 | while True: 153 | # 左右键控制人物方向 154 | for event in pygame.event.get(): 155 | if event.type == pygame.QUIT: 156 | sys.exit() 157 | if event.type == pygame.KEYDOWN: 158 | if event.key == pygame.K_LEFT or event.key == pygame.K_a: 159 | speed = skier.turn(-1) 160 | elif event.key == pygame.K_RIGHT or event.key == pygame.K_d: 161 | speed = skier.turn(1) 162 | skier.move() 163 | distance += speed[1] 164 | if distance >= 640 and obstaclesflag == 0: 165 | obstaclesflag = 1 166 | obstacles0 = create_obstacles(20, 29) 167 | obstacles = AddObstacles(obstacles0, obstacles1) 168 | if distance >= 1280 and obstaclesflag == 1: 169 | obstaclesflag = 0 170 | distance -= 1280 171 | for obstacle in obstacles0: 172 | obstacle.location[1] = obstacle.location[1] - 1280 173 | obstacles1 = create_obstacles(10, 19) 174 | obstacles = AddObstacles(obstacles0, obstacles1) 175 | # 用于碰撞检测 176 | for obstacle in obstacles: 177 | obstacle.move(distance) 178 | # 碰撞检测 179 | is_hit = pygame.sprite.spritecollide(skier, obstacles, False) 180 | if is_hit: 181 | if is_hit[0].attribute == "tree" and not is_hit[0].passed: 182 | score -= 50 183 | skier.person = pygame.image.load("./images/skier_fall.png") 184 | update() 185 | # 摔倒后暂停一会再站起来 186 | pygame.time.delay(1000) 187 | skier.person = pygame.image.load("./images/skier_forward.png") 188 | skier.direction = 0 189 | speed = [0, 6] 190 | is_hit[0].passed = True 191 | elif is_hit[0].attribute == "flag" and not is_hit[0].passed: 192 | score += 10 193 | obstacles.remove(is_hit[0]) 194 | score_text = font.render("Score: "+str(score), 1, (0, 0, 0)) 195 | update() 196 | clock.tick(40) 197 | 198 | 199 | if __name__ == '__main__': 200 | main() -------------------------------------------------------------------------------- /$滑雪游戏/font/simkai.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/font/simkai.ttf -------------------------------------------------------------------------------- /$滑雪游戏/images/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/flag.png -------------------------------------------------------------------------------- /$滑雪游戏/images/skier_fall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/skier_fall.png -------------------------------------------------------------------------------- /$滑雪游戏/images/skier_forward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/skier_forward.png -------------------------------------------------------------------------------- /$滑雪游戏/images/skier_left1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/skier_left1.png -------------------------------------------------------------------------------- /$滑雪游戏/images/skier_left2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/skier_left2.png -------------------------------------------------------------------------------- /$滑雪游戏/images/skier_right1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/skier_right1.png -------------------------------------------------------------------------------- /$滑雪游戏/images/skier_right2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/skier_right2.png -------------------------------------------------------------------------------- /$滑雪游戏/images/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/images/tree.png -------------------------------------------------------------------------------- /$滑雪游戏/music/bg_music.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$滑雪游戏/music/bg_music.mp3 -------------------------------------------------------------------------------- /$贪吃蛇/AI/AI_snake.py: -------------------------------------------------------------------------------- 1 | import random 2 | import pygame 3 | import sys 4 | from pygame.locals import * 5 | 6 | 7 | # 错误码 8 | ERR = -404 9 | # 屏幕大小 10 | Window_Width = 800 11 | Window_Height = 500 12 | # 刷新频率 13 | Display_Clock = 17 14 | # 一块蛇身大小 15 | Cell_Size = 20 16 | assert Window_Width % Cell_Size == 0 17 | assert Window_Height % Cell_Size == 0 18 | # 等价的运动区域大小 19 | Cell_W = int(Window_Width/Cell_Size) 20 | Cell_H = int(Window_Height/Cell_Size) 21 | FIELD_SIZE = Cell_W * Cell_H 22 | # 背景颜色 23 | Background_Color = (0, 0, 0) 24 | # 蛇头索引 25 | Head_index = 0 26 | # 运动方向 27 | best_move = ERR 28 | # 不同东西在矩阵里用不同的数字表示 29 | FOOD = 0 30 | FREE_PLACE = (Cell_W+1) * (Cell_H+1) 31 | SNAKE_PLACE = 2 * FREE_PLACE 32 | # 运动方向字典 33 | move_directions = { 34 | 'left': -1, 35 | 'right': 1, 36 | 'up': -Cell_W, 37 | 'down': Cell_W 38 | } 39 | 40 | 41 | # 关闭游戏界面 42 | def close_game(): 43 | pygame.quit() 44 | sys.exit() 45 | 46 | 47 | # 检测玩家的按键 48 | def Check_PressKey(): 49 | if len(pygame.event.get(QUIT)) > 0: 50 | close_game() 51 | KeyUp_Events = pygame.event.get(KEYUP) 52 | if len(KeyUp_Events) == 0: 53 | return None 54 | elif KeyUp_Events[0].key == K_ESCAPE: 55 | close_game() 56 | return KeyUp_Events[0].key 57 | 58 | 59 | # 显示当前得分 60 | def Show_Score(score): 61 | score_Content = Main_Font.render('得分:%s' % (score), True, (255, 255, 255)) 62 | score_Rect = score_Content.get_rect() 63 | score_Rect.topleft = (Window_Width-120, 10) 64 | Main_Display.blit(score_Content, score_Rect) 65 | 66 | 67 | # 获得果实位置 68 | def Get_Apple_Location(snake_Coords): 69 | flag = True 70 | while flag: 71 | apple_location = {'x': random.randint(0, Cell_W-1), 'y': random.randint(0, Cell_H-1)} 72 | if apple_location not in snake_Coords: 73 | flag = False 74 | return apple_location 75 | 76 | 77 | # 显示果实 78 | def Show_Apple(coord): 79 | x = coord['x'] * Cell_Size 80 | y = coord['y'] * Cell_Size 81 | apple_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 82 | pygame.draw.rect(Main_Display, (255, 0, 0), apple_Rect) 83 | 84 | 85 | # 显示蛇 86 | def Show_Snake(coords): 87 | x = coords[0]['x'] * Cell_Size 88 | y = coords[0]['y'] * Cell_Size 89 | Snake_head_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 90 | pygame.draw.rect(Main_Display, (0, 80, 255), Snake_head_Rect) 91 | Snake_head_Inner_Rect = pygame.Rect(x+4, y+4, Cell_Size-8, Cell_Size-8) 92 | pygame.draw.rect(Main_Display, (0, 80, 255), Snake_head_Inner_Rect) 93 | for coord in coords[1:]: 94 | x = coord['x'] * Cell_Size 95 | y = coord['y'] * Cell_Size 96 | Snake_part_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 97 | pygame.draw.rect(Main_Display, (0, 155, 0), Snake_part_Rect) 98 | Snake_part_Inner_Rect = pygame.Rect(x+4, y+4, Cell_Size-8, Cell_Size-8) 99 | pygame.draw.rect(Main_Display, (0, 255, 0), Snake_part_Inner_Rect) 100 | 101 | 102 | # 画网格 103 | def draw_Grid(): 104 | # 垂直方向 105 | for x in range(0, Window_Width, Cell_Size): 106 | pygame.draw.line(Main_Display, (40, 40, 40), (x, 0), (x, Window_Height)) 107 | # 水平方向 108 | for y in range(0, Window_Height, Cell_Size): 109 | pygame.draw.line(Main_Display, (40, 40, 40), (0, y), (Window_Width, y)) 110 | 111 | 112 | # 显示开始界面 113 | def Show_Start_Interface(): 114 | title_Font = pygame.font.Font('simkai.ttf', 100) 115 | title_content = title_Font.render('贪吃蛇', True, (255, 255, 255), (0, 0, 160)) 116 | angle = 0 117 | while True: 118 | Main_Display.fill(Background_Color) 119 | rotated_title = pygame.transform.rotate(title_content, angle) 120 | rotated_title_Rect = rotated_title.get_rect() 121 | rotated_title_Rect.center = (Window_Width/2, Window_Height/2) 122 | Main_Display.blit(rotated_title, rotated_title_Rect) 123 | pressKey_content = Main_Font.render('按任意键开始游戏!', True, (255, 255, 255)) 124 | pressKey_Rect = pressKey_content.get_rect() 125 | pressKey_Rect.topleft = (Window_Width-200, Window_Height-30) 126 | Main_Display.blit(pressKey_content, pressKey_Rect) 127 | if Check_PressKey(): 128 | # 清除事件队列 129 | pygame.event.get() 130 | return 131 | pygame.display.update() 132 | Snake_Clock.tick(Display_Clock) 133 | angle -= 5 134 | 135 | 136 | # 显示结束界面 137 | def Show_End_Interface(): 138 | title_Font = pygame.font.Font('simkai.ttf', 100) 139 | title_game = title_Font.render('Game', True, (233, 150, 122)) 140 | title_over = title_Font.render('Over', True, (233, 150, 122)) 141 | game_Rect = title_game.get_rect() 142 | over_Rect = title_over.get_rect() 143 | game_Rect.midtop = (Window_Width/2, 70) 144 | over_Rect.midtop = (Window_Width/2, game_Rect.height+70+25) 145 | Main_Display.blit(title_game, game_Rect) 146 | Main_Display.blit(title_over, over_Rect) 147 | pygame.display.update() 148 | pygame.time.wait(500) 149 | while True: 150 | for event in pygame.event.get(): 151 | if event.type == QUIT: 152 | close_game() 153 | elif event.type == KEYDOWN: 154 | if event.key == K_ESCAPE: 155 | close_game() 156 | 157 | 158 | # 判断该位置是否为空 159 | def Is_Cell_Free(idx, psnake): 160 | location_x = idx % Cell_W 161 | location_y = idx // Cell_W 162 | idx = {'x': location_x, 'y': location_y} 163 | return (idx not in psnake) 164 | 165 | 166 | # 重置board 167 | def board_reset(psnake, pboard, pfood): 168 | temp_board = pboard[:] 169 | pfood_idx = pfood['x'] + pfood['y'] * Cell_W 170 | for i in range(FIELD_SIZE): 171 | if i == pfood_idx: 172 | temp_board[i] = FOOD 173 | elif Is_Cell_Free(i, psnake): 174 | temp_board[i] = FREE_PLACE 175 | else: 176 | temp_board[i] = SNAKE_PLACE 177 | return temp_board 178 | 179 | 180 | # 检查位置idx是否可以向当前move方向运动 181 | def is_move_possible(idx, move_direction): 182 | flag = False 183 | if move_direction == 'left': 184 | if idx%Cell_W > 0: 185 | flag = True 186 | else: 187 | flag = False 188 | elif move_direction == 'right': 189 | if idx%Cell_W < Cell_W-1: 190 | flag = True 191 | else: 192 | flag = False 193 | elif move_direction == 'up': 194 | if idx > Cell_W-1: 195 | flag = True 196 | else: 197 | flag = False 198 | elif move_direction == 'down': 199 | if idx < FIELD_SIZE - Cell_W: 200 | flag = True 201 | else: 202 | flag = False 203 | return flag 204 | 205 | 206 | # 广度优先搜索遍历整个board 207 | # 计算出board中每个非SNAKE_PLACE元素到达食物的路径长度 208 | def board_refresh(psnake, pfood, pboard): 209 | temp_board = pboard[:] 210 | pfood_idx = pfood['x'] + pfood['y'] * Cell_W 211 | queue = [] 212 | queue.append(pfood_idx) 213 | inqueue = [0] * FIELD_SIZE 214 | found = False 215 | while len(queue) != 0: 216 | idx = queue.pop(0) 217 | if inqueue[idx] == 1: 218 | continue 219 | inqueue[idx] = 1 220 | for move_direction in ['left', 'right', 'up', 'down']: 221 | if is_move_possible(idx, move_direction): 222 | if (idx+move_directions[move_direction]) == (psnake[Head_index]['x'] + psnake[Head_index]['y']*Cell_W): 223 | found = True 224 | # 该点不是蛇身(食物是0才可以这样子写) 225 | if temp_board[idx+move_directions[move_direction]] < SNAKE_PLACE: 226 | if temp_board[idx+move_directions[move_direction]] > temp_board[idx]+1: 227 | temp_board[idx+move_directions[move_direction]] = temp_board[idx] + 1 228 | if inqueue[idx+move_directions[move_direction]] == 0: 229 | queue.append(idx+move_directions[move_direction]) 230 | return (found, temp_board) 231 | 232 | 233 | # 根据board中元素值 234 | # 从蛇头周围4个领域点中选择最短路径 235 | def choose_shortest_safe_move(psnake, pboard): 236 | best_move = ERR 237 | min_distance = SNAKE_PLACE 238 | for move_direction in ['left', 'right', 'up', 'down']: 239 | idx = psnake[Head_index]['x'] + psnake[Head_index]['y']*Cell_W 240 | if is_move_possible(idx, move_direction) and (pboard[idx+move_directions[move_direction]]3): 311 | result = False 312 | return result 313 | 314 | 315 | # 根据board中元素值 316 | # 从蛇头周围4个领域点中选择最远路径 317 | def choose_longest_safe_move(psnake, pboard): 318 | best_move = ERR 319 | max_distance = -1 320 | for move_direction in ['left', 'right', 'up', 'down']: 321 | idx = psnake[Head_index]['x'] + psnake[Head_index]['y']*Cell_W 322 | if is_move_possible(idx, move_direction) and (pboard[idx+move_directions[move_direction]]>max_distance) and (pboard[idx+move_directions[move_direction]] 0: 50 | close_game() 51 | KeyUp_Events = pygame.event.get(KEYUP) 52 | if len(KeyUp_Events) == 0: 53 | return None 54 | elif KeyUp_Events[0].key == K_ESCAPE: 55 | close_game() 56 | return KeyUp_Events[0].key 57 | 58 | 59 | # 显示当前得分 60 | def Show_Score(score): 61 | score_Content = Main_Font.render('得分:%s' % (score), True, (255, 255, 255)) 62 | score_Rect = score_Content.get_rect() 63 | score_Rect.topleft = (Window_Width-120, 10) 64 | Main_Display.blit(score_Content, score_Rect) 65 | 66 | 67 | # 获得果实位置 68 | def Get_Apple_Location(snake_Coords): 69 | flag = True 70 | while flag: 71 | apple_location = {'x': random.randint(0, Cell_W-1), 'y': random.randint(0, Cell_H-1)} 72 | if apple_location not in snake_Coords: 73 | flag = False 74 | return apple_location 75 | 76 | 77 | # 显示果实 78 | def Show_Apple(coord): 79 | x = coord['x'] * Cell_Size 80 | y = coord['y'] * Cell_Size 81 | apple_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 82 | pygame.draw.rect(Main_Display, (255, 0, 0), apple_Rect) 83 | 84 | 85 | # 显示蛇 86 | def Show_Snake(coords): 87 | x = coords[0]['x'] * Cell_Size 88 | y = coords[0]['y'] * Cell_Size 89 | Snake_head_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 90 | pygame.draw.rect(Main_Display, (0, 80, 255), Snake_head_Rect) 91 | Snake_head_Inner_Rect = pygame.Rect(x+4, y+4, Cell_Size-8, Cell_Size-8) 92 | pygame.draw.rect(Main_Display, (0, 80, 255), Snake_head_Inner_Rect) 93 | for coord in coords[1:]: 94 | x = coord['x'] * Cell_Size 95 | y = coord['y'] * Cell_Size 96 | Snake_part_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 97 | pygame.draw.rect(Main_Display, (0, 155, 0), Snake_part_Rect) 98 | Snake_part_Inner_Rect = pygame.Rect(x+4, y+4, Cell_Size-8, Cell_Size-8) 99 | pygame.draw.rect(Main_Display, (0, 255, 0), Snake_part_Inner_Rect) 100 | 101 | 102 | # 画网格 103 | def draw_Grid(): 104 | # 垂直方向 105 | for x in range(0, Window_Width, Cell_Size): 106 | pygame.draw.line(Main_Display, (40, 40, 40), (x, 0), (x, Window_Height)) 107 | # 水平方向 108 | for y in range(0, Window_Height, Cell_Size): 109 | pygame.draw.line(Main_Display, (40, 40, 40), (0, y), (Window_Width, y)) 110 | 111 | 112 | # 显示开始界面 113 | def Show_Start_Interface(): 114 | title_Font = pygame.font.Font('simkai.ttf', 100) 115 | title_content = title_Font.render('贪吃蛇', True, (255, 255, 255), (0, 0, 160)) 116 | angle = 0 117 | while True: 118 | Main_Display.fill(Background_Color) 119 | rotated_title = pygame.transform.rotate(title_content, angle) 120 | rotated_title_Rect = rotated_title.get_rect() 121 | rotated_title_Rect.center = (Window_Width/2, Window_Height/2) 122 | Main_Display.blit(rotated_title, rotated_title_Rect) 123 | pressKey_content = Main_Font.render('按任意键开始游戏!', True, (255, 255, 255)) 124 | pressKey_Rect = pressKey_content.get_rect() 125 | pressKey_Rect.topleft = (Window_Width-200, Window_Height-30) 126 | Main_Display.blit(pressKey_content, pressKey_Rect) 127 | if Check_PressKey(): 128 | # 清除事件队列 129 | pygame.event.get() 130 | return 131 | pygame.display.update() 132 | Snake_Clock.tick(Display_Clock) 133 | angle -= 5 134 | 135 | 136 | # 显示结束界面 137 | def Show_End_Interface(): 138 | title_Font = pygame.font.Font('simkai.ttf', 100) 139 | title_game = title_Font.render('Game', True, (233, 150, 122)) 140 | title_over = title_Font.render('Over', True, (233, 150, 122)) 141 | game_Rect = title_game.get_rect() 142 | over_Rect = title_over.get_rect() 143 | game_Rect.midtop = (Window_Width/2, 70) 144 | over_Rect.midtop = (Window_Width/2, game_Rect.height+70+25) 145 | Main_Display.blit(title_game, game_Rect) 146 | Main_Display.blit(title_over, over_Rect) 147 | pygame.display.update() 148 | pygame.time.wait(500) 149 | while True: 150 | for event in pygame.event.get(): 151 | if event.type == QUIT: 152 | close_game() 153 | elif event.type == KEYDOWN: 154 | if event.key == K_ESCAPE: 155 | close_game() 156 | 157 | 158 | # 判断该位置是否为空 159 | def Is_Cell_Free(idx, psnake): 160 | location_x = idx % Cell_W 161 | location_y = idx // Cell_W 162 | idx = {'x': location_x, 'y': location_y} 163 | return (idx not in psnake) 164 | 165 | 166 | # 重置board 167 | def board_reset(psnake, pboard, pfood): 168 | temp_board = pboard[:] 169 | pfood_idx = pfood['x'] + pfood['y'] * Cell_W 170 | for i in range(FIELD_SIZE): 171 | if i == pfood_idx: 172 | temp_board[i] = FOOD 173 | elif Is_Cell_Free(i, psnake): 174 | temp_board[i] = FREE_PLACE 175 | else: 176 | temp_board[i] = SNAKE_PLACE 177 | return temp_board 178 | 179 | 180 | # 检查位置idx是否可以向当前move方向运动 181 | def is_move_possible(idx, move_direction): 182 | flag = False 183 | if move_direction == 'left': 184 | if idx%Cell_W > 0: 185 | flag = True 186 | else: 187 | flag = False 188 | elif move_direction == 'right': 189 | if idx%Cell_W < Cell_W-1: 190 | flag = True 191 | else: 192 | flag = False 193 | elif move_direction == 'up': 194 | if idx > Cell_W-1: 195 | flag = True 196 | else: 197 | flag = False 198 | elif move_direction == 'down': 199 | if idx < FIELD_SIZE - Cell_W: 200 | flag = True 201 | else: 202 | flag = False 203 | return flag 204 | 205 | 206 | # 广度优先搜索遍历整个board 207 | # 计算出board中每个非SNAKE_PLACE元素到达食物的路径长度 208 | def board_refresh(psnake, pfood, pboard): 209 | temp_board = pboard[:] 210 | pfood_idx = pfood['x'] + pfood['y'] * Cell_W 211 | queue = [] 212 | queue.append(pfood_idx) 213 | inqueue = [0] * FIELD_SIZE 214 | found = False 215 | while len(queue) != 0: 216 | idx = queue.pop(0) 217 | if inqueue[idx] == 1: 218 | continue 219 | inqueue[idx] = 1 220 | for move_direction in ['left', 'right', 'up', 'down']: 221 | if is_move_possible(idx, move_direction): 222 | if (idx+move_directions[move_direction]) == (psnake[Head_index]['x'] + psnake[Head_index]['y']*Cell_W): 223 | found = True 224 | # 该点不是蛇身(食物是0才可以这样子写) 225 | if temp_board[idx+move_directions[move_direction]] < SNAKE_PLACE: 226 | if temp_board[idx+move_directions[move_direction]] > temp_board[idx]+1: 227 | temp_board[idx+move_directions[move_direction]] = temp_board[idx] + 1 228 | if inqueue[idx+move_directions[move_direction]] == 0: 229 | queue.append(idx+move_directions[move_direction]) 230 | return (found, temp_board) 231 | 232 | 233 | # 根据board中元素值 234 | # 从蛇头周围4个领域点中选择最短路径 235 | def choose_shortest_safe_move(psnake, pboard): 236 | best_move = ERR 237 | min_distance = SNAKE_PLACE 238 | for move_direction in ['left', 'right', 'up', 'down']: 239 | idx = psnake[Head_index]['x'] + psnake[Head_index]['y']*Cell_W 240 | if is_move_possible(idx, move_direction) and (pboard[idx+move_directions[move_direction]]3): 311 | result = False 312 | return result 313 | 314 | 315 | # 根据board中元素值 316 | # 从蛇头周围4个领域点中选择最远路径 317 | def choose_longest_safe_move(psnake, pboard): 318 | best_move = ERR 319 | max_distance = -1 320 | for move_direction in ['left', 'right', 'up', 'down']: 321 | idx = psnake[Head_index]['x'] + psnake[Head_index]['y']*Cell_W 322 | if is_move_possible(idx, move_direction) and (pboard[idx+move_directions[move_direction]]>max_distance) and (pboard[idx+move_directions[move_direction]] 0: 33 | close_game() 34 | KeyUp_Events = pygame.event.get(KEYUP) 35 | if len(KeyUp_Events) == 0: 36 | return None 37 | elif KeyUp_Events[0].key == K_ESCAPE: 38 | close_game() 39 | return KeyUp_Events[0].key 40 | 41 | 42 | # 显示当前得分 43 | def Show_Score(score): 44 | score_Content = Main_Font.render('得分:%s' % (score), True, (255, 255, 255)) 45 | score_Rect = score_Content.get_rect() 46 | score_Rect.topleft = (Window_Width-120, 10) 47 | Main_Display.blit(score_Content, score_Rect) 48 | 49 | 50 | # 获得果实位置 51 | def Get_Apple_Location(snake_Coords): 52 | flag = True 53 | while flag: 54 | apple_location = {'x': random.randint(0, Cell_W-1), 'y': random.randint(0, Cell_H-1)} 55 | if apple_location not in snake_Coords: 56 | flag = False 57 | return apple_location 58 | 59 | 60 | # 显示果实 61 | def Show_Apple(coord): 62 | x = coord['x'] * Cell_Size 63 | y = coord['y'] * Cell_Size 64 | apple_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 65 | pygame.draw.rect(Main_Display, (255, 0, 0), apple_Rect) 66 | 67 | 68 | # 显示蛇 69 | def Show_Snake(coords): 70 | x = coords[0]['x'] * Cell_Size 71 | y = coords[0]['y'] * Cell_Size 72 | Snake_head_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 73 | pygame.draw.rect(Main_Display, (0, 80, 255), Snake_head_Rect) 74 | Snake_head_Inner_Rect = pygame.Rect(x+4, y+4, Cell_Size-8, Cell_Size-8) 75 | pygame.draw.rect(Main_Display, (0, 80, 255), Snake_head_Inner_Rect) 76 | for coord in coords[1:]: 77 | x = coord['x'] * Cell_Size 78 | y = coord['y'] * Cell_Size 79 | Snake_part_Rect = pygame.Rect(x, y, Cell_Size, Cell_Size) 80 | pygame.draw.rect(Main_Display, (0, 155, 0), Snake_part_Rect) 81 | Snake_part_Inner_Rect = pygame.Rect(x+4, y+4, Cell_Size-8, Cell_Size-8) 82 | pygame.draw.rect(Main_Display, (0, 255, 0), Snake_part_Inner_Rect) 83 | 84 | 85 | # 画网格 86 | def draw_Grid(): 87 | # 垂直方向 88 | for x in range(0, Window_Width, Cell_Size): 89 | pygame.draw.line(Main_Display, (40, 40, 40), (x, 0), (x, Window_Height)) 90 | # 水平方向 91 | for y in range(0, Window_Height, Cell_Size): 92 | pygame.draw.line(Main_Display, (40, 40, 40), (0, y), (Window_Width, y)) 93 | 94 | 95 | # 显示开始界面 96 | def Show_Start_Interface(): 97 | title_Font = pygame.font.Font('simkai.ttf', 100) 98 | title_content = title_Font.render('贪吃蛇', True, (255, 255, 255), (0, 0, 160)) 99 | angle = 0 100 | while True: 101 | Main_Display.fill(Background_Color) 102 | rotated_title = pygame.transform.rotate(title_content, angle) 103 | rotated_title_Rect = rotated_title.get_rect() 104 | rotated_title_Rect.center = (Window_Width/2, Window_Height/2) 105 | Main_Display.blit(rotated_title, rotated_title_Rect) 106 | pressKey_content = Main_Font.render('按任意键开始游戏!', True, (255, 255, 255)) 107 | pressKey_Rect = pressKey_content.get_rect() 108 | pressKey_Rect.topleft = (Window_Width-200, Window_Height-30) 109 | Main_Display.blit(pressKey_content, pressKey_Rect) 110 | if Check_PressKey(): 111 | # 清除事件队列 112 | pygame.event.get() 113 | return 114 | pygame.display.update() 115 | Snake_Clock.tick(Display_Clock) 116 | angle -= 5 117 | 118 | 119 | # 显示结束界面 120 | def Show_End_Interface(): 121 | title_Font = pygame.font.Font('simkai.ttf', 100) 122 | title_game = title_Font.render('Game', True, (233, 150, 122)) 123 | title_over = title_Font.render('Over', True, (233, 150, 122)) 124 | game_Rect = title_game.get_rect() 125 | over_Rect = title_over.get_rect() 126 | game_Rect.midtop = (Window_Width/2, 70) 127 | over_Rect.midtop = (Window_Width/2, game_Rect.height+70+25) 128 | Main_Display.blit(title_game, game_Rect) 129 | Main_Display.blit(title_over, over_Rect) 130 | pressKey_content = Main_Font.render('按任意键开始游戏!', True, (255, 255, 255)) 131 | pressKey_Rect = pressKey_content.get_rect() 132 | pressKey_Rect.topleft = (Window_Width-200, Window_Height-30) 133 | Main_Display.blit(pressKey_content, pressKey_Rect) 134 | pygame.display.update() 135 | pygame.time.wait(500) 136 | # 清除事件队列 137 | Check_PressKey() 138 | while True: 139 | if Check_PressKey(): 140 | pygame.event.get() 141 | return 142 | 143 | 144 | # 运行游戏 145 | def Run_Game(): 146 | # 蛇出生地 147 | start_x = random.randint(5, Cell_W-6) 148 | start_y = random.randint(5, Cell_H-6) 149 | snake_Coords = [{'x': start_x, 'y': start_y}, 150 | {'x': start_x-1, 'y': start_y}, 151 | {'x': start_x-2, 'y': start_y}] 152 | direction = 'right' 153 | apple_location = Get_Apple_Location(snake_Coords) 154 | while True: 155 | for event in pygame.event.get(): 156 | if event.type == QUIT: 157 | close_game() 158 | elif event.type == KEYDOWN: 159 | if (event.key == K_LEFT) and (direction != 'right'): 160 | direction = 'left' 161 | elif (event.key == K_RIGHT) and (direction != 'left'): 162 | direction = 'right' 163 | elif (event.key == K_UP) and (direction != 'down'): 164 | direction = 'up' 165 | elif (event.key == K_DOWN) and (direction != 'up'): 166 | direction = 'down' 167 | elif event.key == K_ESCAPE: 168 | close_game() 169 | # 碰到墙壁或者自己则游戏结束 170 | if (snake_Coords[Head_index]['x'] == -1) or (snake_Coords[Head_index]['x'] == Cell_W) or \ 171 | (snake_Coords[Head_index]['y'] == -1) or (snake_Coords[Head_index]['y'] == Cell_H): 172 | return 173 | if snake_Coords[Head_index] in snake_Coords[1:]: 174 | return 175 | if (snake_Coords[Head_index]['x'] == apple_location['x']) and (snake_Coords[Head_index]['y'] == apple_location['y']): 176 | apple_location = Get_Apple_Location(snake_Coords) 177 | else: 178 | del snake_Coords[-1] 179 | if direction == 'up': 180 | newHead = {'x': snake_Coords[Head_index]['x'], 181 | 'y': snake_Coords[Head_index]['y']-1} 182 | elif direction == 'down': 183 | newHead = {'x': snake_Coords[Head_index]['x'], 184 | 'y': snake_Coords[Head_index]['y']+1} 185 | elif direction == 'left': 186 | newHead = {'x': snake_Coords[Head_index]['x']-1, 187 | 'y': snake_Coords[Head_index]['y']} 188 | elif direction == 'right': 189 | newHead = {'x': snake_Coords[Head_index]['x']+1, 190 | 'y': snake_Coords[Head_index]['y']} 191 | snake_Coords.insert(0, newHead) 192 | Main_Display.fill(Background_Color) 193 | draw_Grid() 194 | Show_Snake(snake_Coords) 195 | Show_Apple(apple_location) 196 | Show_Score(len(snake_Coords)-3) 197 | pygame.display.update() 198 | Snake_Clock.tick(Display_Clock) 199 | 200 | 201 | # 主函数 202 | def main(): 203 | global Main_Display, Main_Font, Snake_Clock 204 | pygame.init() 205 | Snake_Clock = pygame.time.Clock() 206 | Main_Display = pygame.display.set_mode((Window_Width, Window_Height)) 207 | Main_Font = pygame.font.Font('simkai.ttf', 18) 208 | pygame.display.set_caption('Normal_snake') 209 | Show_Start_Interface() 210 | while True: 211 | Run_Game() 212 | Show_End_Interface() 213 | 214 | 215 | 216 | if __name__ == '__main__': 217 | main() -------------------------------------------------------------------------------- /$贪吃蛇/Normal/simkai.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lucifer-ww/PythonGame/aa34159e629bed378139baa466ff1f217e2acf56/$贪吃蛇/Normal/simkai.ttf -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :octocat:PythonGame仓库 2 | 3 | 学了几天Python,学了GUI库、Pygame库,又做一些游戏了,有贪吃蛇:snake:、滑雪:snowflake:和AI对下五子棋:black_square_button: 4 | 5 | GIthub仓库: 6 | 7 | ## :dog:客官,点个赞? 8 | 9 | :star:如果觉得对您有帮助的话,点个 star ,再走? 10 | 11 | 12 | 13 | ## :cat:详细解释 14 | 15 | 首先,需要几个库,打开cmd,输入如下命令(如果已经有了,那么就不用了) 16 | 17 | ```powershell 18 | pip install pygame 19 | ``` 20 | 21 | 安装pygame,如果出错,可以试试 22 | 23 | ```powershell 24 | pip3 install pygame 25 | ``` 26 | 27 | ### :bug:1$贪吃蛇 28 | 29 | 我做了一个手动版本,和一个AI版本,AI就是自动寻径原理,自动的,知道框满了不会碰边,但是由于算法不够精良(BFS广搜),所以蛇越长速度越慢……:turtle: 30 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503125240496.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvb2w5OTc4MQ==,size_16,color_FFFFFF,t_70) 31 | **:calendar:主要思路** 32 | :hourglass_flowing_sand:(1)蛇每走一步,就使用BFS计算游戏界面中每个位置(蛇身除外)到达食物的最短路径长; 33 | 34 | :hourglass_flowing_sand:(2)将蛇的安全定义为蛇是否可以跟着蛇尾运动,即蛇头和蛇尾间是否存在路径; 35 | 36 | :hourglass_flowing_sand:(3)蛇每次行动前先利用虚拟的蛇进行探路,若虚拟的蛇吃完食物后是安全的,真蛇才行动; 37 | 38 | :hourglass_flowing_sand:(4)若蛇和食物之间不存在路径或者吃完食物后并不安全,就跟着蛇尾走; 39 | 40 | :hourglass_flowing_sand:(5)若蛇和食物之间、蛇和蛇尾之间均不存在路径,就随便挑一步可行的来走; 41 | 42 | (6)保证目标是食物时蛇走最短路径,目标是蛇尾时蛇走最长路径。 43 | **:calendar:不足之处** 44 | 45 | 由于食物是随机出现的,若虚拟的蛇跑一遍发现去吃食物是不安全的,真蛇就不会去吃食物,而是选择追着蛇尾跑,若一直如此,就陷入了死循环,蛇一直追着蛇尾跑跑跑。。。 46 | 47 | 直到你终止游戏为止。。。 48 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503125434765.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvb2w5OTc4MQ==,size_16,color_FFFFFF,t_70) 49 | 仓库中还有一个简单版,Normal的,可以手动操作,但是一般走几步就废了,控制不住:sweat: 50 | 51 | ### :snowman:2$滑雪小游戏 52 | **:calendar:TOOL** 53 | 开发工具:Python版本:3.6.4 54 | 55 | 相关模块:pygame模块;以及一些Python自带的模块。 56 | 57 | 环境搭建:安装Python并添加到环境变量,pip安装需要的相关模块即可。 58 | 59 | **:calendar:游戏规则:** 60 | 61 | 玩家通过“AD”键或者“←→”操控前进中的滑雪者,努力避开路上的树,尽量捡到路上的小旗。 62 | 63 | 如果碰到树,则得分减50,如果捡到小旗子,则得分加10。 64 | 65 | **:calendar:逐步实现:** 66 | 67 | :hourglass_flowing_sand:Step1:定义精灵类 68 | 69 | 由于游戏涉及到碰撞检测(滑雪者与树和小旗之间的碰撞),因此我们定义两个精灵类,分别用于代表滑雪者和障碍物(即树和小旗): 70 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503130652715.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvb2w5OTc4MQ==,size_16,color_FFFFFF,t_70) 71 | 其中,滑雪者在前进过程中应当拥有向左,向右偏移的能力,并且在偏移时滑雪者向前的速度应当减慢才更加合乎常理,这样才能供玩家操作。同时,滑雪者应当拥有不同的姿态来表现自己滑行时的状态: 72 | 73 | 直线: 74 | 75 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc3NTg5NzA5NTMucG5n?x-oss-process=image/format,png) 76 | 77 | 左偏一点: 78 | 79 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc3NjQyNjUyODYucG5n?x-oss-process=image/format,png) 80 | 81 | 左偏很多: 82 | 83 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MTAyNDc0NzQucG5n?x-oss-process=image/format,png) 84 | 85 | 右偏一点: 86 | 87 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MTQ1ODU3OTUucG5n?x-oss-process=image/format,png) 88 | 89 | 右偏很多: 90 | 91 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MTg1NDA5MzgucG5n?x-oss-process=image/format,png) 92 | 93 | 另外,尽管滑雪者的左右移动通过移动滑雪者本身实现,但是滑雪者的向前移动是通过移动障碍物实现的。 94 | 95 | :hourglass_flowing_sand:Step2:随机创建障碍物 96 | 97 | 现在我们需要定义一个随机创建障碍物的函数,以便在游戏主循环中调用: 98 | 99 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MjIzMDM3NTkuanBn?x-oss-process=image/format,png) 100 | 101 | :hourglass_flowing_sand:Step3:游戏主循环 102 | 103 | 首先我们初始化一些参数: 104 | 105 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MjUzMjQ4MzUuanBn?x-oss-process=image/format,png) 106 | 107 | 其中障碍物创建两次的目的是便于画面衔接。 108 | 109 | 然后我们就可以定义主循环了: 110 | 111 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MzI2NDgxOTIuanBn?x-oss-process=image/format,png) 112 | 113 | 主循环的内容包括: 114 | 115 | 事件监听、障碍物的更新、碰撞检测以及分数的展示等内容,总之还是很容易实现的。 116 | 117 | :hourglass_flowing_sand:Step4:其他 118 | 119 | 开始、结束界面这些,就靠大家自己发挥了,我就写了一个简单的开始界面: 120 | 121 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODA5LzE1MzM4MDc4MzYxMjk5OTcuanBn?x-oss-process=image/format,png) 122 | ### :fries:2$AI五子棋 123 | 比较愚蠢的AI五子棋。 124 | 125 | T_T当然你好好和它下,它还是比较机智的。 126 | 127 | 让我们愉快地开始吧~~~ 128 | 129 | **:calendar:原理简介** 130 | 131 | 对于五子棋这样的博弈类AI,很自然的想法就是让计算机把当前所有可能的情况都尝试一遍,找到最优的落子点。这里有两个问题: 132 | 133 | :hourglass_flowing_sand:(1)如何把所有可能的情况都尝试一遍; 134 | 135 | :hourglass_flowing_sand:(2)如何定量判断某落子点的优劣。 136 | 137 | 对于第一个问题,其实就是所谓的博弈树搜索,对于第二个问题,其实就是所谓的选择评估函数。评估函数的选取直接决定了AI算法的优劣,其形式也千变万化。可以说,每个评估函数就是一个选手,对不同的棋型每个选手自然有不同的看法和应对措施,当然他们的棋力也就因此各不相同了。 138 | 139 | 但博弈树搜索就比较固定了,其核心思想无非是让计算机考虑当前局势下之后N步所有可能的情况,其中奇数步(因为现在轮到AI下)要让AI方的得分最大,偶数步要让AI方的得分最小(因为对手也就是人类,也可以选择最优策略)。 140 | 141 | 当然这样的搜索其计算量是极大的,这时候就需要剪枝来减少计算量。例如下图: 142 | 143 | ![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly93d3cudzNjc2Nob29sLmNuL2F0dGFjaG1lbnRzL2ltYWdlLzIwMTgwODEzLzE1MzQxNDgwMDY0OTk4NzEuanBn?x-oss-process=image/format,png) 144 | 145 | 其中A代表AI方,P代表人类方。AI方搜索最大值,人类方搜索最小值。因此Layer3的A1向下搜索的最终结果为4,Layer3的A2向下搜索,先搜索Layer4的P3,获得的分值为6,考虑到Layer2的P1向下搜索时取Layer3的A1和A2中的较小值,而Layer3的A2搜索完Layer4的P3时,其值就已经必大于Layer3的A1了,就没有搜索下去的必要了,因此Layer3到Layer4的路径3就可以剪掉了。 146 | 147 | 上述搜索策略其实质就是: 148 | 149 | minimax算法+alpha-beta剪枝算法。 150 | 151 | 了解了上述原理之后,就可以自己写代码实现了。当然实际实现过程中,我做了一些简化,但万变不离其宗,其核心思想都是一样的。 152 | 153 | 具体实现过程详见相关文件中的源代码。 154 | 155 | **:calendar:缺点** 156 | 157 | 速度比较慢,棋子越多,反应越慢,所以说很愚蠢,下了30个的时候电脑烫的不行 158 | 159 | **:calendar:使用演示** 160 | 161 | 在cmd窗口运行GobangAI.py文件即可。 162 | 163 | 下面的视频是我和AI的一局对弈,我执黑先行,所以赢的比较轻松T_T。毕竟五子棋先手者优势巨大,或者说在某些情况/规则下是必胜的。至于原因,在相关文件中提供了两篇论文,感兴趣的可以看看。 164 | ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503131714831.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Nvb2w5OTc4MQ==,size_16,color_FFFFFF,t_70) 165 | 166 | 167 | 168 | ## 联系方式 169 | 170 | + :mailbox_closed:QQ:3392446642 171 | + :e-mail:邮箱: 172 | 173 | 174 | --------------------------------------------------------------------------------