面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,这样极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造。面向对象解决了程序的扩展性,对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中对象参数的特征和技能修改都很容易。
类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
实例变量:定义在方法中的变量,只作用于当前实例的类。
继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。
实例化:创建一个类的实例,类的具体对象。
方法:类中定义的函数。
对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
习题1 贪吃蛇
from abc import ABCMeta, abstractmethod
from random import randint
'''
程序通过abc模块的ABCMeta元类和abstractmethod包装器提供对抽象类的支持。一个类中存
在抽象方法那么这个类就不能够实例化(创建对象),但是继承它的子类可以对抽象方法进行重
写,实现更多的功能。
'''
import pygame
BLACK_COLOR = (0, 0, 0)
GREEN_COLOR = (0, 255, 0)
FOOD_COLOR = (230, 185, 185)
UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3
class GameObject(object, metaclass=ABCMeta):
# 使用 class 语句创建一个类,class 之后为类的名称并以冒号结尾
def __init__(self, x=0, y=0, color=BLACK_COLOR):
'''
__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了类的实
例时就会调用该方法。self 代表类的实例,self 在定义类的方法时必须的,调用时不必传
入相应的参数。
'''
self._x = x
self._y = y
self._color = color
@property
def x(self):
return self._x
'''
@property装饰器
若属性命名以单下划线开头,这种方式可以暗示属性是受保护的,不建议外界直接访问。那么如
果想访问属性可以通过属性的getter(访问器)和setter(修改器)方法进行对应的操作。如
果要做到这点,就可以考虑使用@property包装器来包装getter和setter方法,使得对属性
的访问既安全又方便。
'''
@property
def y(self):
return self._y
@abstractmethod
def draw(self, screen):
pass
class Wall(GameObject):
def __init__(self, x, y, width, height, color=BLACK_COLOR):
# (x,y) 表示墙的起始坐标,width表示墙的宽度,height墙的高度
super().__init__(x, y, color)
self._width = width
self._height = height
@property
def width(self):
return self._width
@property
def height(self):
return self._height
def draw(self, screen):
pygame.draw.rect(screen, self._color,
(self._x, self._y, self._width, self._height), 4)
class Food(GameObject):
def __init__(self, x, y, size, color=FOOD_COLOR):
super().__init__(x, y, color)
self._size = size
self._hidden = False
'''
子类有而父类没有的方法叫做子类的派生方法,而父类有子类也有的方法叫做对父类方法的重写。
按照类方法的搜索顺序一个方法如果在子类中有就不会再从父类中寻找,结果父类中的方法无法
调用。如果既想执行父类中的方法同时在子类中又能定义新功能,就需要先把父类中的这个方法
单独继承。在python中只能使用父类名.方法名(self,父类的其他参数)的方式,在python3中
使用super函数来实现,super().父类方法名(除self外的其他参数),其实在super函数中还
需要传入子类名和子类对象(在类中用self),但是我们使用时不需要特意去传,除非在类外单
独调用父类的方法。继承父类方法时父类的参数除了需要在父类的方法中传递还需要在子类重写
的方法中传递。
'''
def draw(self, screen):
if not self._hidden:
pygame.draw.circle(screen, self._color,
(self._x + self._size // 2, self._y + self._size // 2), # (x,y)表示左上角的坐标,size表示直径
self._size // 2, 0)
self._hidden = not self._hidden
class SnakeNode(GameObject):
def __init__(self, x, y, size, color=GREEN_COLOR):
super().__init__(x, y, color)
self._size = size
@property
def size(self):
return self._size
def draw(self, screen):
pygame.draw.rect(screen, self._color,
(self._x, self._y, self._size, self._size), 0)
pygame.draw.rect(screen, BLACK_COLOR,
(self._x, self._y, self._size, self._size), 1)
class Snake(GameObject):
def __init__(self):
super().__init__()
self._dir = LEFT
self._nodes = []
self._alive = True
for index in range(5):
node = SnakeNode(290 + index * 20, 290, 20)
self._nodes.append(node)
@property
def dir(self):
return self._dir
@property
def alive(self):
return self._alive
@property
def head(self):
return self._nodes[0]
def change_dir(self, new_dir):
# UP = 0
# RIGHT = 1
# DOWN = 2
# LEFT = 3 self._dir的初始值为3
if (self._dir + new_dir) % 2 != 0:
self._dir = new_dir
def move(self):
if self._alive:
snake_dir = self._dir
x, y, size = self.head.x, self.head.y, self.head.size
if snake_dir == UP:
y -= size
elif snake_dir == RIGHT:
x += size
elif snake_dir == DOWN:
y += size
else:
x -= size
new_head = SnakeNode(x, y, size)
self._nodes.insert(0, new_head)
# insert() 函数用于将指定对象插入列表的指定位置。
self._nodes.pop()
# pop() 函数用于移除列表中的一个元素(默认最后一个元素),并且返回该元素的值。
def collide(self, wall):
"""
撞墙
:param wall: 围墙
"""
head = self.head # head就是nodes[0]
if head.x < wall.x or head.x + head.size > wall.x + wall.width \
or head.y < wall.y or head.y + head.size > wall.y + wall.height:
self._alive = False
def eat_food(self, food):
if self.head.x == food.x and self.head.y == food.y:
tail = self._nodes[-1]
self._nodes.append(tail)
return True
return False
def eat_me(self):
head = self.head
for index, node in enumerate(self._nodes):
if index > 0 and head.x == node.x and head.y == node.y:
self._alive = False
def get_score(self, food, score=0):
if self.head.x == food.x and self.head.y == food.y:
score += 1
return score
def show_score(self):
pass
def draw(self, screen):
for node in self._nodes:
node.draw(screen)
def main():
def refresh():
"""刷新游戏窗口"""
screen.fill((242, 242, 242))
wall.draw(screen)
food.draw(screen)
snake.draw(screen)
snake.eat_me()
pygame.display.flip()
def handle_key_event(key_event):
"""处理按键事件"""
key = key_event.key
if key == pygame.K_F2:
reset_game()
elif key in (pygame.K_a, pygame.K_w, pygame.K_d, pygame.K_s):
if snake.alive:
new_dir = snake.dir
if key == pygame.K_w:
new_dir = UP
elif key == pygame.K_d:
new_dir = RIGHT
elif key == pygame.K_s:
new_dir = DOWN
elif key == pygame.K_a:
new_dir = LEFT
if new_dir != snake.dir:
snake.change_dir(new_dir)
def create_food():
row = randint(0, 29)
col = randint(0, 29)
return Food(10 + 20 * col, 10 + 20 * row, 20)
def reset_game():
nonlocal food, snake
food = create_food()
snake = Snake()
wall = Wall(10, 10, 600, 600)
food = create_food()
snake = Snake()
pygame.init()
screen = pygame.display.set_mode((620, 620))
pygame.display.set_caption('贪吃蛇')
screen.fill((242, 242, 242))
pygame.display.flip()
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
handle_key_event(event)
if snake.alive:
refresh()
clock.tick(10)
if snake.alive:
snake.move()
snake.collide(wall)
if snake.eat_food(food):
food = create_food()
pygame.quit()
if __name__ == '__main__':
main()
习题2 奥特曼大战小怪兽
from abc import ABCMeta, abstractmethod
from random import randint, randrange
class Fighter(object, metaclass=ABCMeta):
"""战斗者"""
__slots__ = ('_name', '_hp')
'''
__slots__魔法
需要限定自定义类型的对象只能绑定某些属性,可以通过在类中定义__slots__变量来进行
限定。需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起作用.
'''
def __init__(self, name, hp):
"""
初始化方法
:param name: 名字
:param hp: 生命值
"""
self._name = name
self._hp = hp
@property
def name(self):
return self._name
@property
def hp(self):
return self._hp
@hp.setter
def hp(self, hp):
self._hp = hp if hp >= 0 else 0
@property
def alive(self):
return self._hp > 0
@abstractmethod
def attack(self, other):
"""
攻击
:param other: 被攻击的对象
"""
pass
class Ultraman(Fighter):
"""奥特曼"""
__slots__ = ('_name', '_hp', '_mp')
def __init__(self, name, hp, mp):
"""
初始化方法
:param name: 名字
:param hp: 生命值
:param mp: 魔法值
"""
super().__init__(name, hp)
self._mp = mp
def attack(self, other):
other.hp -= randint(15, 25)
def huge_attack(self, other):
"""
究极必杀技(打掉对方至少50点或四分之三的血)
:param other: 被攻击的对象
:return: 使用成功返回True否则返回False
"""
if self._mp >= 50:
self._mp -= 50
injury = other.hp * 3 // 4
injury = injury if injury >= 50 else 50
other.hp -= injury
return True
else:
self.attack(other)
return False
def magic_attack(self, others):
"""
魔法攻击
:param others: 被攻击的群体
:return: 使用魔法成功返回True否则返回False
"""
if self._mp >= 20:
self._mp -= 20
for temp in others:
if temp.alive:
temp.hp -= randint(10, 15)
return True
else:
return False
def resume(self):
"""恢复魔法值"""
incr_point = randint(1, 10)
self._mp += incr_point
return incr_point
def __str__(self):
return '~~~%s奥特曼~~~\n' % self._name + \
'生命值: %d\n' % self._hp + \
'魔法值: %d\n' % self._mp
class Monster(Fighter):
"""小怪兽"""
__slots__ = ('_name', '_hp')
def attack(self, other):
other.hp -= randint(10, 20)
def __str__(self):
return '~~~%s小怪兽~~~\n' % self._name + \
'生命值: %d\n' % self._hp
def is_any_alive(monsters):
"""判断有没有小怪兽是活着的"""
for monster in monsters:
if monster.alive > 0:
return True
return False
def select_alive_one(monsters):
"""选中一只活着的小怪兽"""
monsters_len = len(monsters)
while True:
index = randrange(monsters_len)
monster = monsters[index]
if monster.alive > 0:
return monster
def display_info(ultraman, monsters):
"""显示奥特曼和小怪兽的信息"""
print(ultraman)
for monster in monsters:
print(monster, end='')
def main():
u = Ultraman('骆昊', 1000, 120)
m1 = Monster('舒小玲', 250)
m2 = Monster('白元芳', 500)
m3 = Monster('王大锤', 750)
ms = [m1, m2, m3]
fight_round = 1
while u.alive and is_any_alive(ms):
print('========第%02d回合========' % fight_round)
m = select_alive_one(ms) # 选中一只小怪兽
skill = randint(1, 10) # 通过随机数选择使用哪种技能
if skill <= 6: # 60%的概率使用普通攻击
print('%s使用普通攻击打了%s.' % (u.name, m.name))
u.attack(m)
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
elif skill <= 9: # 30%的概率使用魔法攻击(可能因魔法值不足而失败)
if u.magic_attack(ms):
print('%s使用了魔法攻击.' % u.name)
else:
print('%s使用魔法失败.' % u.name)
else: # 10%的概率使用究极必杀技(如果魔法值不足则使用普通攻击)
if u.huge_attack(m):
print('%s使用究极必杀技虐了%s.' % (u.name, m.name))
else:
print('%s使用普通攻击打了%s.' % (u.name, m.name))
print('%s的魔法值恢复了%d点.' % (u.name, u.resume()))
if m.alive > 0: # 如果选中的小怪兽没有死就回击奥特曼
print('%s回击了%s.' % (m.name, u.name))
m.attack(u)
display_info(u, ms) # 每个回合结束后显示奥特曼和小怪兽的信息
fight_round += 1
print('\n========战斗结束!========\n')
if u.alive > 0:
print('%s奥特曼胜利!' % u.name)
else:
print('小怪兽胜利!')
if __name__ == '__main__':
main()
习题3 扑克游戏
from random import randrange
class Card(object):
"""一张牌"""
def __init__(self, suite, face):
self._suite = suite
self._face = face
@property
def face(self):
return self._face
@property
def suite(self):
return self._suite
def __str__(self):
all_suites = ('♠', '♥', '♣', '♦')
if self._face == 1:
face_str = 'A'
elif self._face == 11:
face_str = 'J'
elif self._face == 12:
face_str = 'Q'
elif self._face == 13:
face_str = 'K'
else:
face_str = str(self._face)
return '%s%s' % (all_suites[self._suite], face_str)
class Poker(object):
"""一副牌"""
def __init__(self):
self._cards = []
self._current = 0
for suite in range(4):
for face in range(1, 14):
card = Card(suite, face)
self._cards.append(card)
@property
def cards(self):
return self._cards
def shuffle(self):
"""洗牌(随机乱序)"""
self._current = 0
cards_len = len(self._cards)
for index in range(cards_len):
pos = randrange(cards_len)
self._cards[index], self._cards[pos] = \
self._cards[pos], self._cards[index]
@property
def next(self):
"""发牌"""
card = self._cards[self._current]
self._current += 1
return card
@property
def has_next(self):
"""还有没有牌"""
return self._current < len(self._cards)
class Player(object):
"""玩家"""
def __init__(self, name):
self._name = name
self._cards_on_hand = []
@property
def name(self):
return self._name
@property
def cards_on_hand(self):
return self._cards_on_hand
def get(self, card):
"""摸牌"""
self._cards_on_hand.append(card)
def arrange(self, card_key):
"""玩家整理手上的牌"""
self._cards_on_hand.sort(key=card_key)
# 排序规则-先根据花色再根据点数排序
def get_key(card):
return (card.suite, card.face)
def main():
p = Poker()
p.shuffle()
players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')]
for _ in range(13):
for player in players:
player.get(p.next)
for player in players:
print(player.name + ':', end=' ')
player.arrange(get_key)
for card in player.cards_on_hand:
print(card, end=' ')
print()
if __name__ == '__main__':
main()
习题4 工资结算系统
"""
某公司有三种类型的员工,分别是部门经理、程序员和销售员。需要设计一个工资结算系统,根据
提供的员工信息来计算月薪。部门经理的月薪是每月固定15000元,程序员的月薪按本月工作时间
计算,每小时150元,销售员的月薪是1200元的底薪加上销售额5%的提成。
"""
from abc import ABCMeta, abstractmethod
class Employee(object, metaclass=ABCMeta):
"""员工"""
def __init__(self, name):
"""
初始化方法
:param name: 姓名
"""
self._name = name
@property
def name(self):
return self._name
@abstractmethod
def get_salary(self):
"""
获得月薪
:return: 月薪
"""
pass
class Manager(Employee):
"""部门经理"""
def get_salary(self):
return 15000.0
class Programmer(Employee):
"""程序员"""
def __init__(self, name, working_hour=0):
super().__init__(name)
self._working_hour = working_hour
@property
def working_hour(self):
return self._working_hour
@working_hour.setter
def working_hour(self, working_hour):
self._working_hour = working_hour if working_hour > 0 else 0
def get_salary(self):
return 150.0 * self._working_hour
class Salesman(Employee):
"""销售员"""
def __init__(self, name, sales=0):
super().__init__(name)
self._sales = sales
@property
def sales(self):
return self._sales
@sales.setter
def sales(self, sales):
self._sales = sales if sales > 0 else 0
def get_salary(self):
return 1200.0 + self._sales * 0.05
def main():
emps = [
Manager('刘备'), Programmer('诸葛亮'),
Manager('曹操'), Salesman('荀彧'),
Salesman('吕布'), Programmer('张辽'),
Programmer('赵云')
]
for emp in emps:
if isinstance(emp, Programmer):
emp.working_hour = int(input('请输入%s本月工作时间: ' % emp.name))
elif isinstance(emp, Salesman):
emp.sales = float(input('请输入%s本月销售额: ' % emp.name))
# 同样是接收get_salary这个消息但是不同的员工表现出了不同的行为(多态)
print('%s本月工资为: ¥%s元' %
(emp.name, emp.get_salary()))
if __name__ == '__main__':
main()
习题5 五子棋
import pygame
EMPTY = 0
BLACK = 1
WHITE = 2
black_color = [0, 0, 0]
white_color = [255, 255, 255]
class RenjuBoard(object):
def __init__(self):
self._board = [[]for _ in range(15)]
self.reset()
def reset(self):
for row in range(len(self._board)):
self._board[row] = [EMPTY] * 15
def move(self, row, col, is_black=True):
if self._board[row][col] == EMPTY:
self._board[row][col] = BLACK if is_black else WHITE
return True
# else:
# return False
def draw(self, screen):
for index in range(1, 16):
pygame.draw.line(screen, black_color,
[40, 40 * index], [600, 40 * index], 1)
pygame.draw.line(screen, black_color,
[40 * index, 40], [40 * index, 600], 1)
pygame.draw.rect(screen, black_color, [36, 36, 568, 568], 4) # 4个数字,(36,36)表示矩形的起始坐标,(568,568)表示离起始坐标的距离
pygame.draw.circle(screen, black_color, [320, 320], 5, 0)
pygame.draw.circle(screen, black_color, [160, 160], 5, 0)
pygame.draw.circle(screen, black_color, [480, 480], 5, 0)
pygame.draw.circle(screen, black_color, [480, 160], 5, 0)
pygame.draw.circle(screen, black_color, [160, 480], 5, 0)
for row in range(len(self._board)):
for col in range(len(self._board[row])):
if self._board[row][col] != EMPTY:
ccolor = black_color if self._board[row][col] == BLACK else white_color
pos = [40 * (col + 1), 40 * (row + 1)] # 坐标
pygame.draw.circle(screen, ccolor, pos, 20, 0)
def main():
board = RenjuBoard()
is_black = True
pygame.init()
pygame.display.set_caption('五子棋')
screen = pygame.display.set_mode([640, 640])
screen.fill([255, 255, 0])
board.draw(screen)
pygame.display.flip() # 更新显示到屏幕表面
running = True
while running:
for event in pygame.event.get():
# 上面的代码将会创建当前等待处理的事件的一个列表,使用for循环来遍历里面的事件。
# 这样,我们将会根据事件产生的顺序依次地进行不同的操作。常见的事件是按键按下,
# 按键释放以及鼠标移动。通常需要最先处理QUIT事件(在用户关闭窗口的时候会产生该事件。)
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYUP:
pass
elif event.type == pygame.MOUSEBUTTONDOWN\
and event.button == 1:
x, y = event.pos
row = round((y - 40) / 40)
col = round((x - 40) / 40)
if board.move(row, col, is_black):
is_black = not is_black
screen.fill([255, 255, 0])
board.draw(screen)
pygame.display.flip()
pygame.quit()
if __name__ == '__main__':
main()
习题6 大球吃小球
from random import randint
import pygame
class Ball(object):
def __init__(self, center, color, radius, speed):
self._center = center
self._color = color
self._radius = radius
self._speed = speed
@property
def center(self):
return self._center
@property
def radius(self):
return self._radius
def move(self):
x, y = self._center[0], self._center[1]
sx, sy = self._speed[0], self._speed[1]
self._center = (x, y) = (x + sx, y + sy)
if x + self._radius >= 800 or x - self._radius <= 0:
self._speed = -sx, sy
if y + self._radius >= 600 or y - self._radius <= 0:
self._speed = sx, -sy
def draw(self, screen):
pygame.draw.circle(screen, self._color, self._center,
self._radius, 0)
def main():
balls = []
pygame.init()
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption('大球吃小球')
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
color = random_color()
radius = randint(10, 100)
speed = randint(-10, 10), randint(-10, 10)
ball = Ball(event.pos, color, radius, speed)
balls.append(ball)
refresh(screen, balls)
clock.tick(24)
for ball in balls:
ball.move()
pygame.quit()
def refresh(screen, balls):
bg_color = (230, 230, 230)
screen.fill(bg_color)
for ball in balls:
ball.draw(screen)
pygame.display.flip()
def random_color():
red = randint(0, 255)
green = randint(0, 255)
blue = randint(0, 255)
return red, green, blue
if __name__ == '__main__':
main()