今天跟着实验楼的python小项目走了一遍2048小游戏,感觉收获颇丰,
在这里分享一下代码及遇到问题及感悟
首先是跟着实验楼在linux上敲了一遍整体的代码
没有pycharm的自动补全,还得自己注意PEP-8规范,着实写的很累,但很爽
直接在linux记事本上敲代码,难免语法错误频发,其实这都是很不应该的。
在后期调试时也都一一修改并反思了
这个小项目代码不多,适合现阶段去练习,十分推荐,强烈安利
里面涉及到的知识95%都是学过的,只有些许未涉及,但都实验楼代码中都有注释可以参考
1.未涉及知识补充
curse模块:用来在终端上显示图形界面
collection模块中的defaultdict:提供了一个字典的子类defaultdict。可以指定key值不存在时,value的默认值
any()函数:用于判断给定的可迭代参数 iterable 是否全部为 False,则返回 False,如果有一个为 True,则返回 True。
2.代码分享
以下是实现的代码
import curses from random import randrange, choice from collections import defaultdict letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] actions = ['Up', 'Left', 'Down', 'Right', 'Restart', 'Exit'] actions_dict = dict(zip(letter_codes, actions * 2)) def get_user_action(keyboard): char = "N" while char not in actions_dict: char = keyboard.getch() return actions_dict[char] def transpose(field): return [list(row) for row in zip(*field)] def invert(field): return [row[::-1] for row in field] class GameField(object): def __init__(self, height=4, width=4, win=2048): self.height = height self.width = width self.win_value = 2048 self.score = 0 self.highscore = 0 self.reset() def spawn(self): new_element = 4 if randrange(100) > 89 else 2 (i, j) = choice([(i, j) for i in range(self.width) for j in range(self.height) if self.field[i][j] == 0]) self.field[i][j] = new_element def reset(self): if self.score > self.highscore: self.highscore = self.score self.score = 0 self.field = [[0 for i in range(self.width)] for j in range(self.height)] self.spawn() self.spawn() def move(self,direction): def move_row_left(row): def tighten(row): new_row = [i for i in row if i != 0] new_row += [0 for i in range(len(row) - len(new_row))] return new_row def merge(row): pair = False new_row = [] for i in range(len(row)): if pair: new_row.append(2 * row[i]) self.score += 2 * row[i] pair = False else: if i + 1 < len(row) and row[i] == row[i + 1]: pair = True new_row.append(0) else: new_row.append(row[i]) assert len(new_row) == len(row) return new_row return tighten(merge(tighten(row))) moves = {} moves['Left'] = lambda field: [move_row_left(row) for row in field] moves['Right'] = lambda field: invert(moves['Left'](invert(field))) moves['Up'] = lambda field: transpose(moves['Left'](transpose(field))) moves['Down'] = lambda field: transpose(moves['Right'](transpose(field))) if direction in moves: if self.move_is_possible(direction): self.field = moves[direction](self.field) self.spawn() return True else: return False def is_win(self): return any(any(i >= self.win_value for i in row) for row in self.field) def is_gameover(self): return not any(self.move_is_possible(move) for move in actions) def move_is_possible(self, direction): def row_is_left_movable(row): def change(i): if row[i] == 0 and row[i + 1] != 0: return True if row[i] != 0 and row[i + 1] == row[i]: return True return False return any(change(i) for i in range(len(row) - 1)) check = {} check['Left'] = lambda field: any(row_is_left_movable(row) for row in field) check['Right'] = lambda field: check['Left'](invert(field)) check['Up'] = lambda field: check['Left'](transpose(field)) check['Down'] = lambda field: check['Right'](transpose(field)) if direction in check: return check[direction](self.field) else: return False def draw(self, screen): help_string1 = '(W)Up (S)Down (A)Left (D)Right' help_string2 = ' (R)Restart (Q)Exit ' gameover_string = ' GAME OVER ' win_string = ' YOU WIN! ' def cast(string): screen.addstr(string + ' ') def draw_hor_separator(): line = '+' + ('+------' * self.width + '+')[1:] cast(line) def draw_row(row): cast(''.join('|{: ^5} '.format(num) if num > 0 else '| ' for num in row) + '|') screen.clear() cast('SCORE: ' + str(self.score)) if 0 != self.highscore: cast('HIGHSCORE: ' + str(self.highscore)) for row in self.field: draw_hor_separator() draw_row(row) draw_hor_separator() if self.is_win(): cast(win_string) else: if self.is_gameover(): cast(gameover_string) else: cast(help_string1) cast(help_string2) def main(stdscr): def init(): game_field.reset() return 'Game' def not_game(state): game_field.draw(stdscr) action = get_user_action(stdscr) responses = defaultdict(lambda: state) responses['Restart'], responses['Exit'] = 'Init', 'Exit' return responses[action] def game(): game_field.draw(stdscr) action = get_user_action(stdscr) if action == 'Restart': return 'Init' if action == 'Exit': return 'Exit' if game_field.move(action): # move successful if game_field.is_win(): return 'Win' if game_field.is_gameover(): return 'Gameover' return 'Game' state_actions = { 'Init': init, 'Win': lambda: not_game('Win'), 'Gameover': lambda: not_game('Gameover'), 'Game': game } curses.use_default_colors() game_field = GameField(win=2048) state = 'Init'
while state != 'Exit': state = state_actions[state]() curses.wrapper(main)
3.调试结果
由于是在linux记事本下直接敲代码,没有pycharm的自动补全和时时提醒错误标黄,语法错误犯了很多
在调试中才会发现,这其实是很不应该的
在对应文件夹下 python3 2048.py
然后开始错误频发,不停的回去调试
调试完成并运行成功
在终端操作通关游戏
同样 又在pycharm上敲了一遍 并在mac的终端上运行了
实验楼python2048地址:https://www.shiyanlou.com/courses/368
后面还有附上一份实验楼的用面向对象重构的代码一份
# -*- coding: utf-8 -*- import random import curses from itertools import chain class Action(object): UP = 'up' LEFT = 'left' DOWN = 'down' RIGHT = 'right' RESTART = 'restart' EXIT = 'exit' letter_codes = [ord(ch) for ch in 'WASDRQwasdrq'] actions = [UP, LEFT, DOWN, RIGHT, RESTART, EXIT] actions_dict = dict(zip(letter_codes, actions * 2)) def __init__(self, stdscr): self.stdscr = stdscr def get(self): char = "N" while char not in self.actions_dict: char = self.stdscr.getch() return self.actions_dict[char] class Grid(object): def __init__(self, size): self.size = size self.cells = None self.reset() def reset(self): self.cells = [[0 for i in range(self.size)] for j in range(self.size)] self.add_random_item() self.add_random_item() def add_random_item(self): empty_cells = [(i, j) for i in range(self.size) for j in range(self.size) if self.cells[i][j] == 0] (i, j) = random.choice(empty_cells) self.cells[i][j] = 4 if random.randrange(100) >= 90 else 2 def transpose(self): self.cells = [list(row) for row in zip(*self.cells)] def invert(self): self.cells = [row[::-1] for row in self.cells] @staticmethod def move_row_left(row): def tighten(row): new_row = [i for i in row if i != 0] new_row += [0 for i in range(len(row) - len(new_row))] return new_row def merge(row): pair = False new_row = [] for i in range(len(row)): if pair: new_row.append(2 * row[i]) # self.score += 2 * row[i] pair = False else: if i + 1 < len(row) and row[i] == row[i + 1]: pair = True new_row.append(0) else: new_row.append(row[i]) assert len(new_row) == len(row) return new_row return tighten(merge(tighten(row))) def move_left(self): self.cells = [self.move_row_left(row) for row in self.cells] def move_right(self): self.invert() self.move_left() self.invert() def move_up(self): self.transpose() self.move_left() self.transpose() def move_down(self): self.transpose() self.move_right() self.transpose() @staticmethod def row_can_move_left(row): def change(i): if row[i] == 0 and row[i + 1] != 0: return True if row[i] != 0 and row[i + 1] == row[i]: return True return False return any(change(i) for i in range(len(row) - 1)) def can_move_left(self): return any(self.row_can_move_left(row) for row in self.cells) def can_move_right(self): self.invert() can = self.can_move_left() self.invert() return can def can_move_up(self): self.transpose() can = self.can_move_left() self.transpose() return can def can_move_down(self): self.transpose() can = self.can_move_right() self.transpose() return can class Screen(object): help_string1 = '(W)up (S)down (A)left (D)right' help_string2 = ' (R)Restart (Q)Exit' over_string = ' GAME OVER' win_string = ' YOU WIN!' def __init__(self, screen=None, grid=None, score=0, best_score=0, over=False, win=False): self.grid = grid self.score = score self.over = over self.win = win self.screen = screen self.counter = 0 def cast(self, string): self.screen.addstr(string + ' ') def draw_row(self, row): self.cast(''.join('|{: ^5}'.format(num) if num > 0 else '| ' for num in row) + '|') def draw(self): self.screen.clear() self.cast('SCORE: ' + str(self.score)) for row in self.grid.cells: self.cast('+-----' * self.grid.size + '+') self.draw_row(row) self.cast('+-----' * self.grid.size + '+') if self.win: self.cast(self.win_string) else: if self.over: self.cast(self.over_string) else: self.cast(self.help_string1) self.cast(self.help_string2) class GameManager(object): def __init__(self, size=4, win_num=2048): self.size = size self.win_num = win_num self.reset() def reset(self): self.state = 'init' self.win = False self.over = False self.score = 0 self.grid = Grid(self.size) self.grid.reset() @property def screen(self): return Screen(screen=self.stdscr, score=self.score, grid=self.grid, win=self.win, over=self.over) def move(self, direction): if self.can_move(direction): getattr(self.grid, 'move_' + direction)() self.grid.add_random_item() return True else: return False @property def is_win(self): self.win = max(chain(*self.grid.cells)) >= self.win_num return self.win @property def is_over(self): self.over = not any(self.can_move(move) for move in self.action.actions) return self.over def can_move(self, direction): return getattr(self.grid, 'can_move_' + direction)() def state_init(self): self.reset() return 'game' def state_game(self): self.screen.draw() action = self.action.get() if action == Action.RESTART: return 'init' if action == Action.EXIT: return 'exit' if self.move(action): if self.is_win: return 'win' if self.is_over: return 'over' return 'game' def _restart_or_exit(self): self.screen.draw() return 'init' if self.action.get() == Action.RESTART else 'exit' def state_win(self): return self._restart_or_exit() def state_over(self): return self._restart_or_exit() def __call__(self, stdscr): curses.use_default_colors() self.stdscr = stdscr self.action = Action(stdscr) while self.state != 'exit': self.state = getattr(self, 'state_' + self.state)() if __name__ == '__main__': curses.wrapper(GameManager())