├── .gitignore ├── GluttonousSnake ├── main.py └── 贪吃蛇 - 游戏截图.png ├── Gomoku └── ManAndMachine.py ├── MineSweeping ├── main.py ├── mineblock.py └── resources │ ├── 0.bmp │ ├── 1.bmp │ ├── 2.bmp │ ├── 3.bmp │ ├── 4.bmp │ ├── 5.bmp │ ├── 6.bmp │ ├── 7.bmp │ ├── 8.bmp │ ├── a.TTF │ ├── ask.bmp │ ├── blank.bmp │ ├── blood.bmp │ ├── error.bmp │ ├── face_fail.bmp │ ├── face_normal.bmp │ ├── face_success.bmp │ ├── flag.bmp │ ├── mine.bmp │ └── 扫雷.png ├── README.md └── Tetris ├── blocks.py ├── main.py └── 俄罗斯方块.png /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | -------------------------------------------------------------------------------- /GluttonousSnake/main.py: -------------------------------------------------------------------------------- 1 | """贪吃蛇""" 2 | 3 | import random 4 | import sys 5 | import time 6 | import pygame 7 | from pygame.locals import * 8 | from collections import deque 9 | 10 | SCREEN_WIDTH = 600 # 屏幕宽度 11 | SCREEN_HEIGHT = 480 # 屏幕高度 12 | SIZE = 20 # 小方格大小 13 | LINE_WIDTH = 1 # 网格线宽度 14 | 15 | # 游戏区域的坐标范围 16 | SCOPE_X = (0, SCREEN_WIDTH // SIZE - 1) 17 | SCOPE_Y = (2, SCREEN_HEIGHT // SIZE - 1) 18 | 19 | # 食物的分值及颜色 20 | FOOD_STYLE_LIST = [(10, (255, 100, 100)), (20, (100, 255, 100)), (30, (100, 100, 255))] 21 | 22 | LIGHT = (100, 100, 100) 23 | DARK = (200, 200, 200) # 蛇的颜色 24 | BLACK = (0, 0, 0) # 网格线颜色 25 | RED = (200, 30, 30) # 红色,GAME OVER 的字体颜色 26 | BGCOLOR = (40, 40, 60) # 背景色 27 | 28 | 29 | def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)): 30 | imgText = font.render(text, True, fcolor) 31 | screen.blit(imgText, (x, y)) 32 | 33 | 34 | # 初始化蛇 35 | def init_snake(): 36 | snake = deque() 37 | snake.append((2, SCOPE_Y[0])) 38 | snake.append((1, SCOPE_Y[0])) 39 | snake.append((0, SCOPE_Y[0])) 40 | return snake 41 | 42 | 43 | def create_food(snake): 44 | food_x = random.randint(SCOPE_X[0], SCOPE_X[1]) 45 | food_y = random.randint(SCOPE_Y[0], SCOPE_Y[1]) 46 | while (food_x, food_y) in snake: 47 | # 如果食物出现在蛇身上,则重来 48 | food_x = random.randint(SCOPE_X[0], SCOPE_X[1]) 49 | food_y = random.randint(SCOPE_Y[0], SCOPE_Y[1]) 50 | return food_x, food_y 51 | 52 | 53 | def get_food_style(): 54 | return FOOD_STYLE_LIST[random.randint(0, 2)] 55 | 56 | 57 | def main(): 58 | pygame.init() 59 | screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 60 | pygame.display.set_caption('贪吃蛇') 61 | 62 | font1 = pygame.font.SysFont('SimHei', 24) # 得分的字体 63 | font2 = pygame.font.Font(None, 72) # GAME OVER 的字体 64 | fwidth, fheight = font2.size('GAME OVER') 65 | 66 | # 如果蛇正在向右移动,那么快速点击向下向左,由于程序刷新没那么快,向下事件会被向左覆盖掉,导致蛇后退,直接GAME OVER 67 | # b 变量就是用于防止这种情况的发生 68 | b = True 69 | 70 | # 蛇 71 | snake = init_snake() 72 | # 食物 73 | food = create_food(snake) 74 | food_style = get_food_style() 75 | # 方向 76 | pos = (1, 0) 77 | 78 | game_over = True 79 | start = False # 是否开始,当start = True,game_over = True 时,才显示 GAME OVER 80 | score = 0 # 得分 81 | orispeed = 0.5 # 原始速度 82 | speed = orispeed 83 | last_move_time = None 84 | pause = False # 暂停 85 | 86 | while True: 87 | for event in pygame.event.get(): 88 | if event.type == QUIT: 89 | sys.exit() 90 | elif event.type == KEYDOWN: 91 | if event.key == K_RETURN: 92 | if game_over: 93 | start = True 94 | game_over = False 95 | b = True 96 | snake = init_snake() 97 | food = create_food(snake) 98 | food_style = get_food_style() 99 | pos = (1, 0) 100 | # 得分 101 | score = 0 102 | last_move_time = time.time() 103 | elif event.key == K_SPACE: 104 | if not game_over: 105 | pause = not pause 106 | elif event.key in (K_w, K_UP): 107 | # 这个判断是为了防止蛇向上移时按了向下键,导致直接 GAME OVER 108 | if b and not pos[1]: 109 | pos = (0, -1) 110 | b = False 111 | elif event.key in (K_s, K_DOWN): 112 | if b and not pos[1]: 113 | pos = (0, 1) 114 | b = False 115 | elif event.key in (K_a, K_LEFT): 116 | if b and not pos[0]: 117 | pos = (-1, 0) 118 | b = False 119 | elif event.key in (K_d, K_RIGHT): 120 | if b and not pos[0]: 121 | pos = (1, 0) 122 | b = False 123 | 124 | # 填充背景色 125 | screen.fill(BGCOLOR) 126 | # 画网格线 竖线 127 | for x in range(SIZE, SCREEN_WIDTH, SIZE): 128 | pygame.draw.line(screen, BLACK, (x, SCOPE_Y[0] * SIZE), (x, SCREEN_HEIGHT), LINE_WIDTH) 129 | # 画网格线 横线 130 | for y in range(SCOPE_Y[0] * SIZE, SCREEN_HEIGHT, SIZE): 131 | pygame.draw.line(screen, BLACK, (0, y), (SCREEN_WIDTH, y), LINE_WIDTH) 132 | 133 | if not game_over: 134 | curTime = time.time() 135 | if curTime - last_move_time > speed: 136 | if not pause: 137 | b = True 138 | last_move_time = curTime 139 | next_s = (snake[0][0] + pos[0], snake[0][1] + pos[1]) 140 | if next_s == food: 141 | # 吃到了食物 142 | snake.appendleft(next_s) 143 | score += food_style[0] 144 | speed = orispeed - 0.03 * (score // 100) 145 | food = create_food(snake) 146 | food_style = get_food_style() 147 | else: 148 | if SCOPE_X[0] <= next_s[0] <= SCOPE_X[1] and SCOPE_Y[0] <= next_s[1] <= SCOPE_Y[1] \ 149 | and next_s not in snake: 150 | snake.appendleft(next_s) 151 | snake.pop() 152 | else: 153 | game_over = True 154 | 155 | # 画食物 156 | if not game_over: 157 | # 避免 GAME OVER 的时候把 GAME OVER 的字给遮住了 158 | pygame.draw.rect(screen, food_style[1], (food[0] * SIZE, food[1] * SIZE, SIZE, SIZE), 0) 159 | 160 | # 画蛇 161 | for s in snake: 162 | pygame.draw.rect(screen, DARK, (s[0] * SIZE + LINE_WIDTH, s[1] * SIZE + LINE_WIDTH, 163 | SIZE - LINE_WIDTH * 2, SIZE - LINE_WIDTH * 2), 0) 164 | 165 | print_text(screen, font1, 30, 7, f'速度: {score//100}') 166 | print_text(screen, font1, 450, 7, f'得分: {score}') 167 | 168 | if game_over: 169 | if start: 170 | print_text(screen, font2, (SCREEN_WIDTH - fwidth) // 2, (SCREEN_HEIGHT - fheight) // 2, 'GAME OVER', RED) 171 | 172 | pygame.display.update() 173 | 174 | 175 | if __name__ == '__main__': 176 | main() 177 | -------------------------------------------------------------------------------- /GluttonousSnake/贪吃蛇 - 游戏截图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/GluttonousSnake/贪吃蛇 - 游戏截图.png -------------------------------------------------------------------------------- /Gomoku/ManAndMachine.py: -------------------------------------------------------------------------------- 1 | """五子棋之人机对战""" 2 | 3 | import sys 4 | import random 5 | import pygame 6 | from pygame.locals import * 7 | import pygame.gfxdraw 8 | from collections import namedtuple 9 | 10 | Chessman = namedtuple('Chessman', 'Name Value Color') 11 | Point = namedtuple('Point', 'X Y') 12 | 13 | BLACK_CHESSMAN = Chessman('黑子', 1, (45, 45, 45)) 14 | WHITE_CHESSMAN = Chessman('白子', 2, (219, 219, 219)) 15 | 16 | offset = [(1, 0), (0, 1), (1, 1), (1, -1)] 17 | 18 | 19 | class Checkerboard: 20 | def __init__(self, line_points): 21 | self._line_points = line_points 22 | self._checkerboard = [[0] * line_points for _ in range(line_points)] 23 | 24 | def _get_checkerboard(self): 25 | return self._checkerboard 26 | 27 | checkerboard = property(_get_checkerboard) 28 | 29 | # 判断是否可落子 30 | def can_drop(self, point): 31 | return self._checkerboard[point.Y][point.X] == 0 32 | 33 | def drop(self, chessman, point): 34 | """ 35 | 落子 36 | :param chessman: 37 | :param point:落子位置 38 | :return:若该子落下之后即可获胜,则返回获胜方,否则返回 None 39 | """ 40 | print(f'{chessman.Name} ({point.X}, {point.Y})') 41 | self._checkerboard[point.Y][point.X] = chessman.Value 42 | 43 | if self._win(point): 44 | print(f'{chessman.Name}获胜') 45 | return chessman 46 | 47 | # 判断是否赢了 48 | def _win(self, point): 49 | cur_value = self._checkerboard[point.Y][point.X] 50 | for os in offset: 51 | if self._get_count_on_direction(point, cur_value, os[0], os[1]): 52 | return True 53 | 54 | def _get_count_on_direction(self, point, value, x_offset, y_offset): 55 | count = 1 56 | for step in range(1, 5): 57 | x = point.X + step * x_offset 58 | y = point.Y + step * y_offset 59 | if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value: 60 | count += 1 61 | else: 62 | break 63 | for step in range(1, 5): 64 | x = point.X - step * x_offset 65 | y = point.Y - step * y_offset 66 | if 0 <= x < self._line_points and 0 <= y < self._line_points and self._checkerboard[y][x] == value: 67 | count += 1 68 | else: 69 | break 70 | 71 | return count >= 5 72 | 73 | 74 | SIZE = 30 # 棋盘每个点时间的间隔 75 | Line_Points = 19 # 棋盘每行/每列点数 76 | Outer_Width = 20 # 棋盘外宽度 77 | Border_Width = 4 # 边框宽度 78 | Inside_Width = 4 # 边框跟实际的棋盘之间的间隔 79 | Border_Length = SIZE * (Line_Points - 1) + Inside_Width * 2 + Border_Width # 边框线的长度 80 | Start_X = Start_Y = Outer_Width + int(Border_Width / 2) + Inside_Width # 网格线起点(左上角)坐标 81 | SCREEN_HEIGHT = SIZE * (Line_Points - 1) + Outer_Width * 2 + Border_Width + Inside_Width * 2 # 游戏屏幕的高 82 | SCREEN_WIDTH = SCREEN_HEIGHT + 200 # 游戏屏幕的宽 83 | 84 | Stone_Radius = SIZE // 2 - 3 # 棋子半径 85 | Stone_Radius2 = SIZE // 2 + 3 86 | Checkerboard_Color = (0xE3, 0x92, 0x65) # 棋盘颜色 87 | BLACK_COLOR = (0, 0, 0) 88 | WHITE_COLOR = (255, 255, 255) 89 | RED_COLOR = (200, 30, 30) 90 | BLUE_COLOR = (30, 30, 200) 91 | 92 | RIGHT_INFO_POS_X = SCREEN_HEIGHT + Stone_Radius2 * 2 + 10 93 | 94 | 95 | def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)): 96 | imgText = font.render(text, True, fcolor) 97 | screen.blit(imgText, (x, y)) 98 | 99 | 100 | def main(): 101 | pygame.init() 102 | screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 103 | pygame.display.set_caption('五子棋') 104 | 105 | font1 = pygame.font.SysFont('SimHei', 32) 106 | font2 = pygame.font.SysFont('SimHei', 72) 107 | fwidth, fheight = font2.size('黑方获胜') 108 | 109 | checkerboard = Checkerboard(Line_Points) 110 | cur_runner = BLACK_CHESSMAN 111 | winner = None 112 | computer = AI(Line_Points, WHITE_CHESSMAN) 113 | 114 | black_win_count = 0 115 | white_win_count = 0 116 | 117 | while True: 118 | for event in pygame.event.get(): 119 | if event.type == QUIT: 120 | sys.exit() 121 | elif event.type == KEYDOWN: 122 | if event.key == K_RETURN: 123 | if winner is not None: 124 | winner = None 125 | cur_runner = BLACK_CHESSMAN 126 | checkerboard = Checkerboard(Line_Points) 127 | computer = AI(Line_Points, WHITE_CHESSMAN) 128 | elif event.type == MOUSEBUTTONDOWN: 129 | if winner is None: 130 | pressed_array = pygame.mouse.get_pressed() 131 | if pressed_array[0]: 132 | mouse_pos = pygame.mouse.get_pos() 133 | click_point = _get_clickpoint(mouse_pos) 134 | if click_point is not None: 135 | if checkerboard.can_drop(click_point): 136 | winner = checkerboard.drop(cur_runner, click_point) 137 | if winner is None: 138 | cur_runner = _get_next(cur_runner) 139 | computer.get_opponent_drop(click_point) 140 | AI_point = computer.AI_drop() 141 | winner = checkerboard.drop(cur_runner, AI_point) 142 | if winner is not None: 143 | white_win_count += 1 144 | cur_runner = _get_next(cur_runner) 145 | else: 146 | black_win_count += 1 147 | else: 148 | print('超出棋盘区域') 149 | 150 | # 画棋盘 151 | _draw_checkerboard(screen) 152 | 153 | # 画棋盘上已有的棋子 154 | for i, row in enumerate(checkerboard.checkerboard): 155 | for j, cell in enumerate(row): 156 | if cell == BLACK_CHESSMAN.Value: 157 | _draw_chessman(screen, Point(j, i), BLACK_CHESSMAN.Color) 158 | elif cell == WHITE_CHESSMAN.Value: 159 | _draw_chessman(screen, Point(j, i), WHITE_CHESSMAN.Color) 160 | 161 | _draw_left_info(screen, font1, cur_runner, black_win_count, white_win_count) 162 | 163 | if winner: 164 | print_text(screen, font2, (SCREEN_WIDTH - fwidth)//2, (SCREEN_HEIGHT - fheight)//2, winner.Name + '获胜', RED_COLOR) 165 | 166 | pygame.display.flip() 167 | 168 | 169 | def _get_next(cur_runner): 170 | if cur_runner == BLACK_CHESSMAN: 171 | return WHITE_CHESSMAN 172 | else: 173 | return BLACK_CHESSMAN 174 | 175 | 176 | # 画棋盘 177 | def _draw_checkerboard(screen): 178 | # 填充棋盘背景色 179 | screen.fill(Checkerboard_Color) 180 | # 画棋盘网格线外的边框 181 | pygame.draw.rect(screen, BLACK_COLOR, (Outer_Width, Outer_Width, Border_Length, Border_Length), Border_Width) 182 | # 画网格线 183 | for i in range(Line_Points): 184 | pygame.draw.line(screen, BLACK_COLOR, 185 | (Start_Y, Start_Y + SIZE * i), 186 | (Start_Y + SIZE * (Line_Points - 1), Start_Y + SIZE * i), 187 | 1) 188 | for j in range(Line_Points): 189 | pygame.draw.line(screen, BLACK_COLOR, 190 | (Start_X + SIZE * j, Start_X), 191 | (Start_X + SIZE * j, Start_X + SIZE * (Line_Points - 1)), 192 | 1) 193 | # 画星位和天元 194 | for i in (3, 9, 15): 195 | for j in (3, 9, 15): 196 | if i == j == 9: 197 | radius = 5 198 | else: 199 | radius = 3 200 | # pygame.draw.circle(screen, BLACK, (Start_X + SIZE * i, Start_Y + SIZE * j), radius) 201 | pygame.gfxdraw.aacircle(screen, Start_X + SIZE * i, Start_Y + SIZE * j, radius, BLACK_COLOR) 202 | pygame.gfxdraw.filled_circle(screen, Start_X + SIZE * i, Start_Y + SIZE * j, radius, BLACK_COLOR) 203 | 204 | 205 | # 画棋子 206 | def _draw_chessman(screen, point, stone_color): 207 | # pygame.draw.circle(screen, stone_color, (Start_X + SIZE * point.X, Start_Y + SIZE * point.Y), Stone_Radius) 208 | pygame.gfxdraw.aacircle(screen, Start_X + SIZE * point.X, Start_Y + SIZE * point.Y, Stone_Radius, stone_color) 209 | pygame.gfxdraw.filled_circle(screen, Start_X + SIZE * point.X, Start_Y + SIZE * point.Y, Stone_Radius, stone_color) 210 | 211 | 212 | # 画左侧信息显示 213 | def _draw_left_info(screen, font, cur_runner, black_win_count, white_win_count): 214 | _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, Start_X + Stone_Radius2), BLACK_CHESSMAN.Color) 215 | _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, Start_X + Stone_Radius2 * 4), WHITE_CHESSMAN.Color) 216 | 217 | print_text(screen, font, RIGHT_INFO_POS_X, Start_X + 3, '玩家', BLUE_COLOR) 218 | print_text(screen, font, RIGHT_INFO_POS_X, Start_X + Stone_Radius2 * 3 + 3, '电脑', BLUE_COLOR) 219 | 220 | print_text(screen, font, SCREEN_HEIGHT, SCREEN_HEIGHT - Stone_Radius2 * 8, '战况:', BLUE_COLOR) 221 | _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, SCREEN_HEIGHT - int(Stone_Radius2 * 4.5)), BLACK_CHESSMAN.Color) 222 | _draw_chessman_pos(screen, (SCREEN_HEIGHT + Stone_Radius2, SCREEN_HEIGHT - Stone_Radius2 * 2), WHITE_CHESSMAN.Color) 223 | print_text(screen, font, RIGHT_INFO_POS_X, SCREEN_HEIGHT - int(Stone_Radius2 * 5.5) + 3, f'{black_win_count} 胜', BLUE_COLOR) 224 | print_text(screen, font, RIGHT_INFO_POS_X, SCREEN_HEIGHT - Stone_Radius2 * 3 + 3, f'{white_win_count} 胜', BLUE_COLOR) 225 | 226 | 227 | def _draw_chessman_pos(screen, pos, stone_color): 228 | pygame.gfxdraw.aacircle(screen, pos[0], pos[1], Stone_Radius2, stone_color) 229 | pygame.gfxdraw.filled_circle(screen, pos[0], pos[1], Stone_Radius2, stone_color) 230 | 231 | 232 | # 根据鼠标点击位置,返回游戏区坐标 233 | def _get_clickpoint(click_pos): 234 | pos_x = click_pos[0] - Start_X 235 | pos_y = click_pos[1] - Start_Y 236 | if pos_x < -Inside_Width or pos_y < -Inside_Width: 237 | return None 238 | x = pos_x // SIZE 239 | y = pos_y // SIZE 240 | if pos_x % SIZE > Stone_Radius: 241 | x += 1 242 | if pos_y % SIZE > Stone_Radius: 243 | y += 1 244 | if x >= Line_Points or y >= Line_Points: 245 | return None 246 | 247 | return Point(x, y) 248 | 249 | 250 | class AI: 251 | def __init__(self, line_points, chessman): 252 | self._line_points = line_points 253 | self._my = chessman 254 | self._opponent = BLACK_CHESSMAN if chessman == WHITE_CHESSMAN else WHITE_CHESSMAN 255 | self._checkerboard = [[0] * line_points for _ in range(line_points)] 256 | 257 | def get_opponent_drop(self, point): 258 | self._checkerboard[point.Y][point.X] = self._opponent.Value 259 | 260 | def AI_drop(self): 261 | point = None 262 | score = 0 263 | for i in range(self._line_points): 264 | for j in range(self._line_points): 265 | if self._checkerboard[j][i] == 0: 266 | _score = self._get_point_score(Point(i, j)) 267 | if _score > score: 268 | score = _score 269 | point = Point(i, j) 270 | elif _score == score and _score > 0: 271 | r = random.randint(0, 100) 272 | if r % 2 == 0: 273 | point = Point(i, j) 274 | self._checkerboard[point.Y][point.X] = self._my.Value 275 | return point 276 | 277 | def _get_point_score(self, point): 278 | score = 0 279 | for os in offset: 280 | score += self._get_direction_score(point, os[0], os[1]) 281 | return score 282 | 283 | def _get_direction_score(self, point, x_offset, y_offset): 284 | count = 0 # 落子处我方连续子数 285 | _count = 0 # 落子处对方连续子数 286 | space = None # 我方连续子中有无空格 287 | _space = None # 对方连续子中有无空格 288 | both = 0 # 我方连续子两端有无阻挡 289 | _both = 0 # 对方连续子两端有无阻挡 290 | 291 | # 如果是 1 表示是边上是我方子,2 表示敌方子 292 | flag = self._get_stone_color(point, x_offset, y_offset, True) 293 | if flag != 0: 294 | for step in range(1, 6): 295 | x = point.X + step * x_offset 296 | y = point.Y + step * y_offset 297 | if 0 <= x < self._line_points and 0 <= y < self._line_points: 298 | if flag == 1: 299 | if self._checkerboard[y][x] == self._my.Value: 300 | count += 1 301 | if space is False: 302 | space = True 303 | elif self._checkerboard[y][x] == self._opponent.Value: 304 | _both += 1 305 | break 306 | else: 307 | if space is None: 308 | space = False 309 | else: 310 | break # 遇到第二个空格退出 311 | elif flag == 2: 312 | if self._checkerboard[y][x] == self._my.Value: 313 | _both += 1 314 | break 315 | elif self._checkerboard[y][x] == self._opponent.Value: 316 | _count += 1 317 | if _space is False: 318 | _space = True 319 | else: 320 | if _space is None: 321 | _space = False 322 | else: 323 | break 324 | else: 325 | # 遇到边也就是阻挡 326 | if flag == 1: 327 | both += 1 328 | elif flag == 2: 329 | _both += 1 330 | 331 | if space is False: 332 | space = None 333 | if _space is False: 334 | _space = None 335 | 336 | _flag = self._get_stone_color(point, -x_offset, -y_offset, True) 337 | if _flag != 0: 338 | for step in range(1, 6): 339 | x = point.X - step * x_offset 340 | y = point.Y - step * y_offset 341 | if 0 <= x < self._line_points and 0 <= y < self._line_points: 342 | if _flag == 1: 343 | if self._checkerboard[y][x] == self._my.Value: 344 | count += 1 345 | if space is False: 346 | space = True 347 | elif self._checkerboard[y][x] == self._opponent.Value: 348 | _both += 1 349 | break 350 | else: 351 | if space is None: 352 | space = False 353 | else: 354 | break # 遇到第二个空格退出 355 | elif _flag == 2: 356 | if self._checkerboard[y][x] == self._my.Value: 357 | _both += 1 358 | break 359 | elif self._checkerboard[y][x] == self._opponent.Value: 360 | _count += 1 361 | if _space is False: 362 | _space = True 363 | else: 364 | if _space is None: 365 | _space = False 366 | else: 367 | break 368 | else: 369 | # 遇到边也就是阻挡 370 | if _flag == 1: 371 | both += 1 372 | elif _flag == 2: 373 | _both += 1 374 | 375 | score = 0 376 | if count == 4: 377 | score = 10000 378 | elif _count == 4: 379 | score = 9000 380 | elif count == 3: 381 | if both == 0: 382 | score = 1000 383 | elif both == 1: 384 | score = 100 385 | else: 386 | score = 0 387 | elif _count == 3: 388 | if _both == 0: 389 | score = 900 390 | elif _both == 1: 391 | score = 90 392 | else: 393 | score = 0 394 | elif count == 2: 395 | if both == 0: 396 | score = 100 397 | elif both == 1: 398 | score = 10 399 | else: 400 | score = 0 401 | elif _count == 2: 402 | if _both == 0: 403 | score = 90 404 | elif _both == 1: 405 | score = 9 406 | else: 407 | score = 0 408 | elif count == 1: 409 | score = 10 410 | elif _count == 1: 411 | score = 9 412 | else: 413 | score = 0 414 | 415 | if space or _space: 416 | score /= 2 417 | 418 | return score 419 | 420 | # 判断指定位置处在指定方向上是我方子、对方子、空 421 | def _get_stone_color(self, point, x_offset, y_offset, next): 422 | x = point.X + x_offset 423 | y = point.Y + y_offset 424 | if 0 <= x < self._line_points and 0 <= y < self._line_points: 425 | if self._checkerboard[y][x] == self._my.Value: 426 | return 1 427 | elif self._checkerboard[y][x] == self._opponent.Value: 428 | return 2 429 | else: 430 | if next: 431 | return self._get_stone_color(Point(x, y), x_offset, y_offset, False) 432 | else: 433 | return 0 434 | else: 435 | return 0 436 | 437 | 438 | if __name__ == '__main__': 439 | main() 440 | -------------------------------------------------------------------------------- /MineSweeping/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from enum import Enum 4 | import pygame 5 | from pygame.locals import * 6 | from mineblock import * 7 | 8 | 9 | # 游戏屏幕的宽 10 | SCREEN_WIDTH = BLOCK_WIDTH * SIZE 11 | # 游戏屏幕的高 12 | SCREEN_HEIGHT = (BLOCK_HEIGHT + 2) * SIZE 13 | 14 | 15 | class GameStatus(Enum): 16 | readied = 1, 17 | started = 2, 18 | over = 3, 19 | win = 4 20 | 21 | 22 | def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)): 23 | imgText = font.render(text, True, fcolor) 24 | screen.blit(imgText, (x, y)) 25 | 26 | 27 | def main(): 28 | pygame.init() 29 | screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 30 | pygame.display.set_caption('扫雷') 31 | 32 | font1 = pygame.font.Font('resources/a.TTF', SIZE * 2) # 得分的字体 33 | fwidth, fheight = font1.size('999') 34 | red = (200, 40, 40) 35 | 36 | # 加载资源图片,因为资源文件大小不一,所以做了统一的缩放处理 37 | img0 = pygame.image.load('resources/0.bmp').convert() 38 | img0 = pygame.transform.smoothscale(img0, (SIZE, SIZE)) 39 | img1 = pygame.image.load('resources/1.bmp').convert() 40 | img1 = pygame.transform.smoothscale(img1, (SIZE, SIZE)) 41 | img2 = pygame.image.load('resources/2.bmp').convert() 42 | img2 = pygame.transform.smoothscale(img2, (SIZE, SIZE)) 43 | img3 = pygame.image.load('resources/3.bmp').convert() 44 | img3 = pygame.transform.smoothscale(img3, (SIZE, SIZE)) 45 | img4 = pygame.image.load('resources/4.bmp').convert() 46 | img4 = pygame.transform.smoothscale(img4, (SIZE, SIZE)) 47 | img5 = pygame.image.load('resources/5.bmp').convert() 48 | img5 = pygame.transform.smoothscale(img5, (SIZE, SIZE)) 49 | img6 = pygame.image.load('resources/6.bmp').convert() 50 | img6 = pygame.transform.smoothscale(img6, (SIZE, SIZE)) 51 | img7 = pygame.image.load('resources/7.bmp').convert() 52 | img7 = pygame.transform.smoothscale(img7, (SIZE, SIZE)) 53 | img8 = pygame.image.load('resources/8.bmp').convert() 54 | img8 = pygame.transform.smoothscale(img8, (SIZE, SIZE)) 55 | img_blank = pygame.image.load('resources/blank.bmp').convert() 56 | img_blank = pygame.transform.smoothscale(img_blank, (SIZE, SIZE)) 57 | img_flag = pygame.image.load('resources/flag.bmp').convert() 58 | img_flag = pygame.transform.smoothscale(img_flag, (SIZE, SIZE)) 59 | img_ask = pygame.image.load('resources/ask.bmp').convert() 60 | img_ask = pygame.transform.smoothscale(img_ask, (SIZE, SIZE)) 61 | img_mine = pygame.image.load('resources/mine.bmp').convert() 62 | img_mine = pygame.transform.smoothscale(img_mine, (SIZE, SIZE)) 63 | img_blood = pygame.image.load('resources/blood.bmp').convert() 64 | img_blood = pygame.transform.smoothscale(img_blood, (SIZE, SIZE)) 65 | img_error = pygame.image.load('resources/error.bmp').convert() 66 | img_error = pygame.transform.smoothscale(img_error, (SIZE, SIZE)) 67 | face_size = int(SIZE * 1.25) 68 | img_face_fail = pygame.image.load('resources/face_fail.bmp').convert() 69 | img_face_fail = pygame.transform.smoothscale(img_face_fail, (face_size, face_size)) 70 | img_face_normal = pygame.image.load('resources/face_normal.bmp').convert() 71 | img_face_normal = pygame.transform.smoothscale(img_face_normal, (face_size, face_size)) 72 | img_face_success = pygame.image.load('resources/face_success.bmp').convert() 73 | img_face_success = pygame.transform.smoothscale(img_face_success, (face_size, face_size)) 74 | face_pos_x = (SCREEN_WIDTH - face_size) // 2 75 | face_pos_y = (SIZE * 2 - face_size) // 2 76 | 77 | img_dict = { 78 | 0: img0, 79 | 1: img1, 80 | 2: img2, 81 | 3: img3, 82 | 4: img4, 83 | 5: img5, 84 | 6: img6, 85 | 7: img7, 86 | 8: img8 87 | } 88 | 89 | bgcolor = (225, 225, 225) # 背景色 90 | 91 | block = MineBlock() 92 | game_status = GameStatus.readied 93 | start_time = None # 开始时间 94 | elapsed_time = 0 # 耗时 95 | 96 | while True: 97 | # 填充背景色 98 | screen.fill(bgcolor) 99 | 100 | for event in pygame.event.get(): 101 | if event.type == QUIT: 102 | sys.exit() 103 | elif event.type == MOUSEBUTTONDOWN: 104 | mouse_x, mouse_y = event.pos 105 | x = mouse_x // SIZE 106 | y = mouse_y // SIZE - 2 107 | b1, b2, b3 = pygame.mouse.get_pressed() 108 | if game_status == GameStatus.started: 109 | # 鼠标左右键同时按下,如果已经标记了所有雷,则打开周围一圈 110 | # 如果还未标记完所有雷,则有一个周围一圈被同时按下的效果 111 | if b1 and b3: 112 | mine = block.getmine(x, y) 113 | if mine.status == BlockStatus.opened: 114 | if not block.double_mouse_button_down(x, y): 115 | game_status = GameStatus.over 116 | elif event.type == MOUSEBUTTONUP: 117 | if y < 0: 118 | if face_pos_x <= mouse_x <= face_pos_x + face_size \ 119 | and face_pos_y <= mouse_y <= face_pos_y + face_size: 120 | game_status = GameStatus.readied 121 | block = MineBlock() 122 | start_time = time.time() 123 | elapsed_time = 0 124 | continue 125 | 126 | if game_status == GameStatus.readied: 127 | game_status = GameStatus.started 128 | start_time = time.time() 129 | elapsed_time = 0 130 | 131 | if game_status == GameStatus.started: 132 | mine = block.getmine(x, y) 133 | if b1 and not b3: # 按鼠标左键 134 | if mine.status == BlockStatus.normal: 135 | if not block.open_mine(x, y): 136 | game_status = GameStatus.over 137 | elif not b1 and b3: # 按鼠标右键 138 | if mine.status == BlockStatus.normal: 139 | mine.status = BlockStatus.flag 140 | elif mine.status == BlockStatus.flag: 141 | mine.status = BlockStatus.ask 142 | elif mine.status == BlockStatus.ask: 143 | mine.status = BlockStatus.normal 144 | elif b1 and b3: 145 | if mine.status == BlockStatus.double: 146 | block.double_mouse_button_up(x, y) 147 | 148 | flag_count = 0 149 | opened_count = 0 150 | 151 | for row in block.block: 152 | for mine in row: 153 | pos = (mine.x * SIZE, (mine.y + 2) * SIZE) 154 | if mine.status == BlockStatus.opened: 155 | screen.blit(img_dict[mine.around_mine_count], pos) 156 | opened_count += 1 157 | elif mine.status == BlockStatus.double: 158 | screen.blit(img_dict[mine.around_mine_count], pos) 159 | elif mine.status == BlockStatus.bomb: 160 | screen.blit(img_blood, pos) 161 | elif mine.status == BlockStatus.flag: 162 | screen.blit(img_flag, pos) 163 | flag_count += 1 164 | elif mine.status == BlockStatus.ask: 165 | screen.blit(img_ask, pos) 166 | elif mine.status == BlockStatus.hint: 167 | screen.blit(img0, pos) 168 | elif game_status == GameStatus.over and mine.value: 169 | screen.blit(img_mine, pos) 170 | elif mine.value == 0 and mine.status == BlockStatus.flag: 171 | screen.blit(img_error, pos) 172 | elif mine.status == BlockStatus.normal: 173 | screen.blit(img_blank, pos) 174 | 175 | print_text(screen, font1, 30, (SIZE * 2 - fheight) // 2 - 2, '%02d' % (MINE_COUNT - flag_count), red) 176 | if game_status == GameStatus.started: 177 | elapsed_time = int(time.time() - start_time) 178 | print_text(screen, font1, SCREEN_WIDTH - fwidth - 30, (SIZE * 2 - fheight) // 2 - 2, '%03d' % elapsed_time, red) 179 | 180 | if flag_count + opened_count == BLOCK_WIDTH * BLOCK_HEIGHT: 181 | game_status = GameStatus.win 182 | 183 | if game_status == GameStatus.over: 184 | screen.blit(img_face_fail, (face_pos_x, face_pos_y)) 185 | elif game_status == GameStatus.win: 186 | screen.blit(img_face_success, (face_pos_x, face_pos_y)) 187 | else: 188 | screen.blit(img_face_normal, (face_pos_x, face_pos_y)) 189 | 190 | pygame.display.update() 191 | 192 | 193 | if __name__ == '__main__': 194 | main() 195 | -------------------------------------------------------------------------------- /MineSweeping/mineblock.py: -------------------------------------------------------------------------------- 1 | import random 2 | from enum import Enum 3 | 4 | BLOCK_WIDTH = 30 5 | BLOCK_HEIGHT = 16 6 | SIZE = 20 # 块大小 7 | MINE_COUNT = 99 # 地雷数 8 | 9 | 10 | class BlockStatus(Enum): 11 | normal = 1 # 未点击 12 | opened = 2 # 已点击 13 | mine = 3 # 地雷 14 | flag = 4 # 标记为地雷 15 | ask = 5 # 标记为问号 16 | bomb = 6 # 踩中地雷 17 | hint = 7 # 被双击的周围 18 | double = 8 # 正被鼠标左右键双击 19 | 20 | 21 | class Mine: 22 | def __init__(self, x, y, value=0): 23 | self._x = x 24 | self._y = y 25 | self._value = 0 26 | self._around_mine_count = -1 27 | self._status = BlockStatus.normal 28 | self.set_value(value) 29 | 30 | def __repr__(self): 31 | return str(self._value) 32 | # return f'({self._x},{self._y})={self._value}, status={self.status}' 33 | 34 | def get_x(self): 35 | return self._x 36 | 37 | def set_x(self, x): 38 | self._x = x 39 | 40 | x = property(fget=get_x, fset=set_x) 41 | 42 | def get_y(self): 43 | return self._y 44 | 45 | def set_y(self, y): 46 | self._y = y 47 | 48 | y = property(fget=get_y, fset=set_y) 49 | 50 | def get_value(self): 51 | return self._value 52 | 53 | def set_value(self, value): 54 | if value: 55 | self._value = 1 56 | else: 57 | self._value = 0 58 | 59 | value = property(fget=get_value, fset=set_value, doc='0:非地雷 1:雷') 60 | 61 | def get_around_mine_count(self): 62 | return self._around_mine_count 63 | 64 | def set_around_mine_count(self, around_mine_count): 65 | self._around_mine_count = around_mine_count 66 | 67 | around_mine_count = property(fget=get_around_mine_count, fset=set_around_mine_count, doc='四周地雷数量') 68 | 69 | def get_status(self): 70 | return self._status 71 | 72 | def set_status(self, value): 73 | self._status = value 74 | 75 | status = property(fget=get_status, fset=set_status, doc='BlockStatus') 76 | 77 | 78 | class MineBlock: 79 | def __init__(self): 80 | self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)] 81 | 82 | # 埋雷 83 | for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT): 84 | self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1 85 | 86 | def get_block(self): 87 | return self._block 88 | 89 | block = property(fget=get_block) 90 | 91 | def getmine(self, x, y): 92 | return self._block[y][x] 93 | 94 | def open_mine(self, x, y): 95 | # 踩到雷了 96 | if self._block[y][x].value: 97 | self._block[y][x].status = BlockStatus.bomb 98 | return False 99 | 100 | # 先把状态改为 opened 101 | self._block[y][x].status = BlockStatus.opened 102 | 103 | around = _get_around(x, y) 104 | 105 | _sum = 0 106 | for i, j in around: 107 | if self._block[j][i].value: 108 | _sum += 1 109 | self._block[y][x].around_mine_count = _sum 110 | 111 | # 如果周围没有雷,那么将周围8个未中未点开的递归算一遍 112 | # 这就能实现一点出现一大片打开的效果了 113 | if _sum == 0: 114 | for i, j in around: 115 | if self._block[j][i].around_mine_count == -1: 116 | self.open_mine(i, j) 117 | 118 | return True 119 | 120 | def double_mouse_button_down(self, x, y): 121 | if self._block[y][x].around_mine_count == 0: 122 | return True 123 | 124 | self._block[y][x].status = BlockStatus.double 125 | 126 | around = _get_around(x, y) 127 | 128 | sumflag = 0 # 周围被标记的雷数量 129 | for i, j in _get_around(x, y): 130 | if self._block[j][i].status == BlockStatus.flag: 131 | sumflag += 1 132 | # 周边的雷已经全部被标记 133 | result = True 134 | if sumflag == self._block[y][x].around_mine_count: 135 | for i, j in around: 136 | if self._block[j][i].status == BlockStatus.normal: 137 | if not self.open_mine(i, j): 138 | result = False 139 | else: 140 | for i, j in around: 141 | if self._block[j][i].status == BlockStatus.normal: 142 | self._block[j][i].status = BlockStatus.hint 143 | return result 144 | 145 | def double_mouse_button_up(self, x, y): 146 | self._block[y][x].status = BlockStatus.opened 147 | for i, j in _get_around(x, y): 148 | if self._block[j][i].status == BlockStatus.hint: 149 | self._block[j][i].status = BlockStatus.normal 150 | 151 | 152 | def _get_around(x, y): 153 | """返回(x, y)周围的点的坐标""" 154 | # 这里注意,range 末尾是开区间,所以要加 1 155 | return [(i, j) for i in range(max(0, x - 1), min(BLOCK_WIDTH - 1, x + 1) + 1) 156 | for j in range(max(0, y - 1), min(BLOCK_HEIGHT - 1, y + 1) + 1) if i != x or j != y] 157 | -------------------------------------------------------------------------------- /MineSweeping/resources/0.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/0.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/1.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/1.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/2.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/2.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/3.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/3.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/4.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/4.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/5.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/5.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/6.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/6.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/7.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/7.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/8.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/8.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/a.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/a.TTF -------------------------------------------------------------------------------- /MineSweeping/resources/ask.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/ask.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/blank.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/blank.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/blood.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/blood.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/error.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/error.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/face_fail.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/face_fail.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/face_normal.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/face_normal.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/face_success.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/face_success.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/flag.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/flag.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/mine.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/mine.bmp -------------------------------------------------------------------------------- /MineSweeping/resources/扫雷.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/MineSweeping/resources/扫雷.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pygame 2 | Python 小游戏 3 | -------------------------------------------------------------------------------- /Tetris/blocks.py: -------------------------------------------------------------------------------- 1 | import random 2 | from collections import namedtuple 3 | 4 | Point = namedtuple('Point', 'X Y') 5 | Shape = namedtuple('Shape', 'X Y Width Height') 6 | Block = namedtuple('Block', 'template start_pos end_pos name next') 7 | 8 | # 方块形状的设计,我最初我是做成 4 × 4,因为长宽最长都是4,这样旋转的时候就不考虑怎么转了,就是从一个图形替换成另一个 9 | # 其实要实现这个功能,只需要固定左上角的坐标就可以了 10 | 11 | # S形方块 12 | S_BLOCK = [Block(['.OO', 13 | 'OO.', 14 | '...'], Point(0, 0), Point(2, 1), 'S', 1), 15 | Block(['O..', 16 | 'OO.', 17 | '.O.'], Point(0, 0), Point(1, 2), 'S', 0)] 18 | # Z形方块 19 | Z_BLOCK = [Block(['OO.', 20 | '.OO', 21 | '...'], Point(0, 0), Point(2, 1), 'Z', 1), 22 | Block(['.O.', 23 | 'OO.', 24 | 'O..'], Point(0, 0), Point(1, 2), 'Z', 0)] 25 | # I型方块 26 | I_BLOCK = [Block(['.O..', 27 | '.O..', 28 | '.O..', 29 | '.O..'], Point(1, 0), Point(1, 3), 'I', 1), 30 | Block(['....', 31 | '....', 32 | 'OOOO', 33 | '....'], Point(0, 2), Point(3, 2), 'I', 0)] 34 | # O型方块 35 | O_BLOCK = [Block(['OO', 36 | 'OO'], Point(0, 0), Point(1, 1), 'O', 0)] 37 | # J型方块 38 | J_BLOCK = [Block(['O..', 39 | 'OOO', 40 | '...'], Point(0, 0), Point(2, 1), 'J', 1), 41 | Block(['.OO', 42 | '.O.', 43 | '.O.'], Point(1, 0), Point(2, 2), 'J', 2), 44 | Block(['...', 45 | 'OOO', 46 | '..O'], Point(0, 1), Point(2, 2), 'J', 3), 47 | Block(['.O.', 48 | '.O.', 49 | 'OO.'], Point(0, 0), Point(1, 2), 'J', 0)] 50 | # L型方块 51 | L_BLOCK = [Block(['..O', 52 | 'OOO', 53 | '...'], Point(0, 0), Point(2, 1), 'L', 1), 54 | Block(['.O.', 55 | '.O.', 56 | '.OO'], Point(1, 0), Point(2, 2), 'L', 2), 57 | Block(['...', 58 | 'OOO', 59 | 'O..'], Point(0, 1), Point(2, 2), 'L', 3), 60 | Block(['OO.', 61 | '.O.', 62 | '.O.'], Point(0, 0), Point(1, 2), 'L', 0)] 63 | # T型方块 64 | T_BLOCK = [Block(['.O.', 65 | 'OOO', 66 | '...'], Point(0, 0), Point(2, 1), 'T', 1), 67 | Block(['.O.', 68 | '.OO', 69 | '.O.'], Point(1, 0), Point(2, 2), 'T', 2), 70 | Block(['...', 71 | 'OOO', 72 | '.O.'], Point(0, 1), Point(2, 2), 'T', 3), 73 | Block(['.O.', 74 | 'OO.', 75 | '.O.'], Point(0, 0), Point(1, 2), 'T', 0)] 76 | 77 | BLOCKS = {'O': O_BLOCK, 78 | 'I': I_BLOCK, 79 | 'Z': Z_BLOCK, 80 | 'T': T_BLOCK, 81 | 'L': L_BLOCK, 82 | 'S': S_BLOCK, 83 | 'J': J_BLOCK} 84 | 85 | 86 | def get_block(): 87 | block_name = random.choice('OIZTLSJ') 88 | b = BLOCKS[block_name] 89 | idx = random.randint(0, len(b) - 1) 90 | return b[idx] 91 | 92 | 93 | def get_next_block(block): 94 | b = BLOCKS[block.name] 95 | return b[block.next] 96 | -------------------------------------------------------------------------------- /Tetris/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import pygame 4 | from pygame.locals import * 5 | import blocks 6 | 7 | SIZE = 30 # 每个小方格大小 8 | BLOCK_HEIGHT = 25 # 游戏区高度 9 | BLOCK_WIDTH = 10 # 游戏区宽度 10 | BORDER_WIDTH = 4 # 游戏区边框宽度 11 | BORDER_COLOR = (40, 40, 200) # 游戏区边框颜色 12 | SCREEN_WIDTH = SIZE * (BLOCK_WIDTH + 5) # 游戏屏幕的宽 13 | SCREEN_HEIGHT = SIZE * BLOCK_HEIGHT # 游戏屏幕的高 14 | BG_COLOR = (40, 40, 60) # 背景色 15 | BLOCK_COLOR = (20, 128, 200) # 16 | BLACK = (0, 0, 0) 17 | RED = (200, 30, 30) # GAME OVER 的字体颜色 18 | 19 | 20 | def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)): 21 | imgText = font.render(text, True, fcolor) 22 | screen.blit(imgText, (x, y)) 23 | 24 | 25 | def main(): 26 | pygame.init() 27 | screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 28 | pygame.display.set_caption('俄罗斯方块') 29 | 30 | font1 = pygame.font.SysFont('SimHei', 24) # 黑体24 31 | font2 = pygame.font.Font(None, 72) # GAME OVER 的字体 32 | font_pos_x = BLOCK_WIDTH * SIZE + BORDER_WIDTH + 10 # 右侧信息显示区域字体位置的X坐标 33 | gameover_size = font2.size('GAME OVER') 34 | font1_height = int(font1.size('得分')[1]) 35 | 36 | cur_block = None # 当前下落方块 37 | next_block = None # 下一个方块 38 | cur_pos_x, cur_pos_y = 0, 0 39 | 40 | game_area = None # 整个游戏区域 41 | game_over = True 42 | start = False # 是否开始,当start = True,game_over = True 时,才显示 GAME OVER 43 | score = 0 # 得分 44 | orispeed = 0.5 # 原始速度 45 | speed = orispeed # 当前速度 46 | pause = False # 暂停 47 | last_drop_time = None # 上次下落时间 48 | last_press_time = None # 上次按键时间 49 | 50 | def _dock(): 51 | nonlocal cur_block, next_block, game_area, cur_pos_x, cur_pos_y, game_over, score, speed 52 | for _i in range(cur_block.start_pos.Y, cur_block.end_pos.Y + 1): 53 | for _j in range(cur_block.start_pos.X, cur_block.end_pos.X + 1): 54 | if cur_block.template[_i][_j] != '.': 55 | game_area[cur_pos_y + _i][cur_pos_x + _j] = '0' 56 | if cur_pos_y + cur_block.start_pos.Y <= 0: 57 | game_over = True 58 | else: 59 | # 计算消除 60 | remove_idxs = [] 61 | for _i in range(cur_block.start_pos.Y, cur_block.end_pos.Y + 1): 62 | if all(_x == '0' for _x in game_area[cur_pos_y + _i]): 63 | remove_idxs.append(cur_pos_y + _i) 64 | if remove_idxs: 65 | # 计算得分 66 | remove_count = len(remove_idxs) 67 | if remove_count == 1: 68 | score += 100 69 | elif remove_count == 2: 70 | score += 300 71 | elif remove_count == 3: 72 | score += 700 73 | elif remove_count == 4: 74 | score += 1500 75 | speed = orispeed - 0.03 * (score // 10000) 76 | # 消除 77 | _i = _j = remove_idxs[-1] 78 | while _i >= 0: 79 | while _j in remove_idxs: 80 | _j -= 1 81 | if _j < 0: 82 | game_area[_i] = ['.'] * BLOCK_WIDTH 83 | else: 84 | game_area[_i] = game_area[_j] 85 | _i -= 1 86 | _j -= 1 87 | cur_block = next_block 88 | next_block = blocks.get_block() 89 | cur_pos_x, cur_pos_y = (BLOCK_WIDTH - cur_block.end_pos.X - 1) // 2, -1 - cur_block.end_pos.Y 90 | 91 | def _judge(pos_x, pos_y, block): 92 | nonlocal game_area 93 | for _i in range(block.start_pos.Y, block.end_pos.Y + 1): 94 | if pos_y + block.end_pos.Y >= BLOCK_HEIGHT: 95 | return False 96 | for _j in range(block.start_pos.X, block.end_pos.X + 1): 97 | if pos_y + _i >= 0 and block.template[_i][_j] != '.' and game_area[pos_y + _i][pos_x + _j] != '.': 98 | return False 99 | return True 100 | 101 | while True: 102 | for event in pygame.event.get(): 103 | if event.type == QUIT: 104 | sys.exit() 105 | elif event.type == KEYDOWN: 106 | if event.key == K_RETURN: 107 | if game_over: 108 | start = True 109 | game_over = False 110 | score = 0 111 | last_drop_time = time.time() 112 | last_press_time = time.time() 113 | game_area = [['.'] * BLOCK_WIDTH for _ in range(BLOCK_HEIGHT)] 114 | cur_block = blocks.get_block() 115 | next_block = blocks.get_block() 116 | cur_pos_x, cur_pos_y = (BLOCK_WIDTH - cur_block.end_pos.X - 1) // 2, -1 - cur_block.end_pos.Y 117 | elif event.key == K_SPACE: 118 | if not game_over: 119 | pause = not pause 120 | elif event.key in (K_w, K_UP): 121 | # 旋转 122 | # 其实记得不是很清楚了,比如 123 | # .0. 124 | # .00 125 | # ..0 126 | # 这个在最右边靠边的情况下是否可以旋转,我试完了网上的俄罗斯方块,是不能旋转的,这里我们就按不能旋转来做 127 | # 我们在形状设计的时候做了很多的空白,这样只需要规定整个形状包括空白部分全部在游戏区域内时才可以旋转 128 | if 0 <= cur_pos_x <= BLOCK_WIDTH - len(cur_block.template[0]): 129 | _next_block = blocks.get_next_block(cur_block) 130 | if _judge(cur_pos_x, cur_pos_y, _next_block): 131 | cur_block = _next_block 132 | 133 | if event.type == pygame.KEYDOWN: 134 | if event.key == pygame.K_LEFT: 135 | if not game_over and not pause: 136 | if time.time() - last_press_time > 0.1: 137 | last_press_time = time.time() 138 | if cur_pos_x > - cur_block.start_pos.X: 139 | if _judge(cur_pos_x - 1, cur_pos_y, cur_block): 140 | cur_pos_x -= 1 141 | if event.key == pygame.K_RIGHT: 142 | if not game_over and not pause: 143 | if time.time() - last_press_time > 0.1: 144 | last_press_time = time.time() 145 | # 不能移除右边框 146 | if cur_pos_x + cur_block.end_pos.X + 1 < BLOCK_WIDTH: 147 | if _judge(cur_pos_x + 1, cur_pos_y, cur_block): 148 | cur_pos_x += 1 149 | if event.key == pygame.K_DOWN: 150 | if not game_over and not pause: 151 | if time.time() - last_press_time > 0.1: 152 | last_press_time = time.time() 153 | if not _judge(cur_pos_x, cur_pos_y + 1, cur_block): 154 | _dock() 155 | else: 156 | last_drop_time = time.time() 157 | cur_pos_y += 1 158 | 159 | _draw_background(screen) 160 | 161 | _draw_game_area(screen, game_area) 162 | 163 | _draw_gridlines(screen) 164 | 165 | _draw_info(screen, font1, font_pos_x, font1_height, score) 166 | # 画显示信息中的下一个方块 167 | _draw_block(screen, next_block, font_pos_x, 30 + (font1_height + 6) * 5, 0, 0) 168 | 169 | if not game_over: 170 | cur_drop_time = time.time() 171 | if cur_drop_time - last_drop_time > speed: 172 | if not pause: 173 | # 不应该在下落的时候来判断到底没,我们玩俄罗斯方块的时候,方块落到底的瞬间是可以进行左右移动 174 | if not _judge(cur_pos_x, cur_pos_y + 1, cur_block): 175 | _dock() 176 | else: 177 | last_drop_time = cur_drop_time 178 | cur_pos_y += 1 179 | else: 180 | if start: 181 | print_text(screen, font2, 182 | (SCREEN_WIDTH - gameover_size[0]) // 2, (SCREEN_HEIGHT - gameover_size[1]) // 2, 183 | 'GAME OVER', RED) 184 | 185 | # 画当前下落方块 186 | _draw_block(screen, cur_block, 0, 0, cur_pos_x, cur_pos_y) 187 | 188 | pygame.display.flip() 189 | 190 | 191 | # 画背景 192 | def _draw_background(screen): 193 | # 填充背景色 194 | screen.fill(BG_COLOR) 195 | # 画游戏区域分隔线 196 | pygame.draw.line(screen, BORDER_COLOR, 197 | (SIZE * BLOCK_WIDTH + BORDER_WIDTH // 2, 0), 198 | (SIZE * BLOCK_WIDTH + BORDER_WIDTH // 2, SCREEN_HEIGHT), BORDER_WIDTH) 199 | 200 | 201 | # 画网格线 202 | def _draw_gridlines(screen): 203 | # 画网格线 竖线 204 | for x in range(BLOCK_WIDTH): 205 | pygame.draw.line(screen, BLACK, (x * SIZE, 0), (x * SIZE, SCREEN_HEIGHT), 1) 206 | # 画网格线 横线 207 | for y in range(BLOCK_HEIGHT): 208 | pygame.draw.line(screen, BLACK, (0, y * SIZE), (BLOCK_WIDTH * SIZE, y * SIZE), 1) 209 | 210 | 211 | # 画已经落下的方块 212 | def _draw_game_area(screen, game_area): 213 | if game_area: 214 | for i, row in enumerate(game_area): 215 | for j, cell in enumerate(row): 216 | if cell != '.': 217 | pygame.draw.rect(screen, BLOCK_COLOR, (j * SIZE, i * SIZE, SIZE, SIZE), 0) 218 | 219 | 220 | # 画单个方块 221 | def _draw_block(screen, block, offset_x, offset_y, pos_x, pos_y): 222 | if block: 223 | for i in range(block.start_pos.Y, block.end_pos.Y + 1): 224 | for j in range(block.start_pos.X, block.end_pos.X + 1): 225 | if block.template[i][j] != '.': 226 | pygame.draw.rect(screen, BLOCK_COLOR, 227 | (offset_x + (pos_x + j) * SIZE, offset_y + (pos_y + i) * SIZE, SIZE, SIZE), 0) 228 | 229 | 230 | # 画得分等信息 231 | def _draw_info(screen, font, pos_x, font_height, score): 232 | print_text(screen, font, pos_x, 10, f'得分: ') 233 | print_text(screen, font, pos_x, 10 + font_height + 6, f'{score}') 234 | print_text(screen, font, pos_x, 20 + (font_height + 6) * 2, f'速度: ') 235 | print_text(screen, font, pos_x, 20 + (font_height + 6) * 3, f'{score // 10000}') 236 | print_text(screen, font, pos_x, 30 + (font_height + 6) * 4, f'下一个:') 237 | 238 | 239 | if __name__ == '__main__': 240 | main() 241 | -------------------------------------------------------------------------------- /Tetris/俄罗斯方块.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guliang21/pygame/e2ef5ab54a862d4add0a8a21c2e2b46b2509e981/Tetris/俄罗斯方块.png --------------------------------------------------------------------------------