10 个让你相见恨晚的 Python 骚操作

10 个让你相见恨晚的 Python 骚操作

 

众所周知,Python 以语法简洁著称,同样实现一个功能,Java 可能要十来行,Python 一行就可以搞定。

Python 的代码之所以这么优雅,完全归功于其独有的特性,只要你掌握了这些特性,同样可以写出像诗一样的代码。

下面我们就一起看看 Python 中的骚操作。

0x00 Hello World
对于大部分程序员来说,*个程序应该都是 “Hello World!” 了,Python 直接将该入门程序做成了一个包。

In [1]: import __hello__
Hello world!
0x01 交换变量
一行代码搞定变量交换,无需临时变量,也无需异或操作。

In [1]: x,y = y,x
0x02 变量比较
对于变量的连续比较,Python 是支持的比较好的。

In [24]: x = 10

In [25]: 5 < x < 20
Out[25]: True

In [26]: 11 < x < 20
Out[26]: False
0x03 列表推导式
In [2]: list = list(range(10))
# 取偶数
In [3]: even = [x for x in list if x % 2 == 0]

In [4]: even
Out[4]: [0, 2, 4, 6, 8]
0x04 合并字符串
很多语言合并字符串大都是通过 + 号来实现的,但由于字符串的不可变性,不断合并后的字符串会不断的申请新的内存。

In [5]: x = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’]

In [6]: ”.join(x)
Out[6]: ‘abcdefg’
0x05 列表切片
In [2]: x
Out[2]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 取索引为 2 到 8 的值,步长为 2
In [4]: x[2:8:2]
Out[4]: [2, 4, 6]

# 步长为负值,表示从后面取值
In [6]: x[::-2]
Out[6]: [9, 7, 5, 3, 1]
0x06 反转字符串
In [7]: x = ‘Hello Python!’

In [8]: x[::-1]
Out[8]: ‘!nohtyP olleH’
0x07 同时获取下标和值
x = list(range(10))
for index, value in enumerate(x):
print(index, value)
0x08 zip()函数
In [7]: a = [1,2,3]

In [8]: b = [4,5,6]

In [9]: c = [7,8,9]

In [16]: list(zip(a, b, c))
Out[16]: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

# 逆操作
In [18]: zz = (zip(a, b, c))

In [19]: x, y, z = zip(*zz)

In [20]: x,y,z
Out[20]: ((1, 2, 3), (4, 5, 6), (7, 8, 9))

# 合并列表相邻项
In [22]: a = [1, 2, 3, 4, 5, 6]

In [23]: list(zip(a[::2], a[1::2]))
Out[23]: [(1, 2), (3, 4), (5, 6)]
0x09 闭包
def outer(x):
def inner(y):
# 在内函数中 用到了外函数的变量
nonlocal x
x += y
return x + y

# 外函数的返回值是内函数的引用
return inner

fun = outer(10)

print(fun(10)) # 30
print(fun(10)) # 40
print(fun(10)) # 50

用 Python 写个坦克大战

用 Python 写个坦克大战

 

坦克大战是一款策略类的平面射击游戏,于 1985 年由 Namco 游戏公司发布,尽管时至今日已经有了很多衍生类的游戏,但这款游戏仍然受到了相当一部分人的欢迎,本文我们看一下如何使用 Python 来实现这款游戏,游戏实现主要用到的 Python 库为 pygame。

简介
坦克大战的组成主要包括:场景、坦克、子弹、食物、大本营,其本质就是一个塔防类的游戏,游戏目标为:守住大本营并且消灭敌方坦克,通常支持单双人模式,下面我们来看一下具体实现。

实现
首先,我们来实现游戏场景,场景的组成主要包括:石头墙、钢墙、冰、河流、树、地图,我们暂时做两个关卡,代码实现如下:

# 石头墙
class Brick(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.brick = pygame.image.load(‘images/scene/brick.png’)
self.rect = self.brick.get_rect()
self.being = False

# 钢墙
class Iron(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.iron = pygame.image.load(‘images/scene/iron.png’)
self.rect = self.iron.get_rect()
self.being = False

# 冰
class Ice(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.ice = pygame.image.load(‘images/scene/ice.png’)
self.rect = self.ice.get_rect()
self.being = False

# 河流
class River(pygame.sprite.Sprite):
def __init__(self, kind=None):
pygame.sprite.Sprite.__init__(self)
if kind is None:
self.kind = random.randint(0, 1)
self.rivers = [‘images/scene/river1.png’, ‘images/scene/river2.png’]
self.river = pygame.image.load(self.rivers[self.kind])
self.rect = self.river.get_rect()
self.being = False

# 树
class Tree(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.tree = pygame.image.load(‘images/scene/tree.png’)
self.rect = self.tree.get_rect()
self.being = False

# 地图
class Map():
def __init__(self, stage):
self.brickGroup = pygame.sprite.Group()
self.ironGroup  = pygame.sprite.Group()
self.iceGroup = pygame.sprite.Group()
self.riverGroup = pygame.sprite.Group()
self.treeGroup = pygame.sprite.Group()
if stage == 1:
self.stage1()
elif stage == 2:
self.stage2()
# 关卡一
def stage1(self):
for x in [2, 3, 6, 7, 18, 19, 22, 23]:
for y in [2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 18, 19, 20, 21, 22, 23]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x in [10, 11, 14, 15]:
for y in [2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 16, 17, 18, 19, 20]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x in [4, 5, 6, 7, 18, 19, 20, 21]:
for y in [13, 14]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x in [12, 13]:
for y in [16, 17]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x, y in [(11, 23), (12, 23), (13, 23), (14, 23), (11, 24), (14, 24), (11, 25), (14, 25)]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x, y in [(0, 14), (1, 14), (12, 6), (13, 6), (12, 7), (13, 7), (24, 14), (25, 14)]:
self.iron = Iron()
self.iron.rect.left, self.iron.rect.top = 3 + x * 24, 3 + y * 24
self.iron.being = True
self.ironGroup.add(self.iron)
# 关卡二
def stage2(self):
for x in [2, 3, 6, 7, 18, 19, 22, 23]:
for y in [2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 18, 19, 20, 21, 22, 23]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x in [10, 11, 14, 15]:
for y in [2, 3, 4, 5, 6, 7, 8, 11, 12, 15, 16, 17, 18, 19, 20]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x in [4, 5, 6, 7, 18, 19, 20, 21]:
for y in [13, 14]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x in [12, 13]:
for y in [16, 17]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x, y in [(11, 23), (12, 23), (13, 23), (14, 23), (11, 24), (14, 24), (11, 25), (14, 25)]:
self.brick = Brick()
self.brick.rect.left, self.brick.rect.top = 3 + x * 24, 3 + y * 24
self.brick.being = True
self.brickGroup.add(self.brick)
for x, y in [(0, 14), (1, 14), (12, 6), (13, 6), (12, 7), (13, 7), (24, 14), (25, 14)]:
self.iron = Iron()
self.iron.rect.left, self.iron.rect.top = 3 + x * 24, 3 + y * 24
self.iron.being = True
self.ironGroup.add(self.iron)
def protect_home(self):
for x, y in [(11, 23), (12, 23), (13, 23), (14, 23), (11, 24), (14, 24), (11, 25), (14, 25)]:
self.iron = Iron()
self.iron.rect.left, self.iron.rect.top = 3 + x * 24, 3 + y * 24
self.iron.being = True
self.ironGroup.add(self.iron)
我们接着看大本营的实现,大本营的实现相对比较简单,其本质就是一个标识物,代码实现如下:

class Home(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.homes = [‘images/home/home1.png’, ‘images/home/home2.png’, ‘images/home/home_destroyed.png’]
self.home = pygame.image.load(self.homes[0])
self.rect = self.home.get_rect()
self.rect.left, self.rect.top = (3 + 12 * 24, 3 + 24 * 24)
self.alive = True
# 大本营置为摧毁状态
def set_dead(self):
self.home = pygame.image.load(self.homes[-1])
self.alive = False
再接着看食物的实现,食物主要用来提升坦克能力,如:坦克升级、增加生命等,代码实现如下:

# 食物类
class Food(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
# 消灭当前所有敌人
self.food_boom = ‘images/food/food_boom.png’
# 当前所有敌人静止一段时间
self.food_clock = ‘images/food/food_clock.png’
# 使得坦克子弹可碎钢板
self.food_gun = ‘images/food/food_gun.png’
# 使得大本营的墙变为钢板
self.food_iron = ‘images/food/food_gun.png’
# 坦克获得一段时间的保护罩
self.food_protect = ‘images/food/food_protect.png’
# 坦克升级
self.food_star = ‘images/food/food_star.png’
# 坦克生命 + 1
self.food_tank = ‘images/food/food_tank.png’
# 所有食物
self.foods = [self.food_boom, self.food_clock, self.food_gun, self.food_iron, self.food_protect, self.food_star, self.food_tank]
self.kind = None
self.food = None
self.rect = None
# 是否存在
self.being = False
# 存在时间
self.time = 1000
# 生成食物
def generate(self):
self.kind = random.randint(0, 6)
self.food = pygame.image.load(self.foods[self.kind]).convert_alpha()
self.rect = self.food.get_rect()
self.rect.left, self.rect.top = random.randint(100, 500), random.randint(100, 500)
self.being = True
再接着看坦克的实现,坦克包括我方坦克和敌方坦克,我方坦克由玩家自己控制移动、射击等操作,敌方坦克实现自动移动、射击等操作,代码实现如下:

# 我方坦克类
class myTank(pygame.sprite.Sprite):
def __init__(self, player):
pygame.sprite.Sprite.__init__(self)
# 玩家编号(1/2)
self.player = player
# 不同玩家用不同的坦克(不同等级对应不同的图)
if player == 1:
self.tanks = [‘images/myTank/tank_T1_0.png’, ‘images/myTank/tank_T1_1.png’, ‘images/myTank/tank_T1_2.png’]
elif player == 2:
self.tanks = [‘images/myTank/tank_T2_0.png’, ‘images/myTank/tank_T2_1.png’, ‘images/myTank/tank_T2_2.png’]
else:
raise ValueError(‘myTank class -> player value error.’)
# 坦克等级(初始0)
self.level = 0
# 载入(两个tank是为了轮子特效)
self.tank = pygame.image.load(self.tanks[self.level]).convert_alpha()
self.tank_0 = self.tank.subsurface((0, 0), (48, 48))
self.tank_1 = self.tank.subsurface((48, 0), (48, 48))
self.rect = self.tank_0.get_rect()
# 保护罩
self.protected_mask = pygame.image.load(‘images/others/protect.png’).convert_alpha()
self.protected_mask1 = self.protected_mask.subsurface((0, 0), (48, 48))
self.protected_mask2 = self.protected_mask.subsurface((48, 0), (48, 48))
# 坦克方向
self.direction_x, self.direction_y = 0, -1
# 不同玩家的出生位置不同
if player == 1:
self.rect.left, self.rect.top = 3 + 24 * 8, 3 + 24 * 24
elif player == 2:
self.rect.left, self.rect.top = 3 + 24 * 16, 3 + 24 * 24
else:
raise ValueError(‘myTank class -> player value error.’)
# 坦克速度
self.speed = 3
# 是否存活
self.being = True
# 有几条命
self.life = 3
# 是否处于保护状态
self.protected = False
# 子弹
self.bullet = Bullet()
# 射击
def shoot(self):
self.bullet.being = True
self.bullet.turn(self.direction_x, self.direction_y)
if self.direction_x == 0 and self.direction_y == -1:
self.bullet.rect.left = self.rect.left + 20
self.bullet.rect.bottom = self.rect.top – 1
elif self.direction_x == 0 and self.direction_y == 1:
self.bullet.rect.left = self.rect.left + 20
self.bullet.rect.top = self.rect.bottom + 1
elif self.direction_x == -1 and self.direction_y == 0:
self.bullet.rect.right = self.rect.left – 1
self.bullet.rect.top = self.rect.top + 20
elif self.direction_x == 1 and self.direction_y == 0:
self.bullet.rect.left = self.rect.right + 1
self.bullet.rect.top = self.rect.top + 20
else:
raise ValueError(‘myTank class -> direction value error.’)
if self.level == 0:
self.bullet.speed = 8
self.bullet.stronger = False
elif self.level == 1:
self.bullet.speed = 12
self.bullet.stronger = False
elif self.level == 2:
self.bullet.speed = 12
self.bullet.stronger = True
elif self.level == 3:
self.bullet.speed = 16
self.bullet.stronger = True
else:
raise ValueError(‘myTank class -> level value error.’)
# 等级提升
def up_level(self):
if self.level < 3:
self.level += 1
try:
self.tank = pygame.image.load(self.tanks[self.level]).convert_alpha()
except:
self.tank = pygame.image.load(self.tanks[-1]).convert_alpha()
# 等级降低
def down_level(self):
if self.level > 0:
self.level -= 1
self.tank = pygame.image.load(self.tanks[self.level]).convert_alpha()
# 向上
def move_up(self, tankGroup, brickGroup, ironGroup, myhome):
self.direction_x, self.direction_y = 0, -1
# 先移动后判断
self.rect = self.rect.move(self.speed*self.direction_x, self.speed*self.direction_y)
self.tank_0 = self.tank.subsurface((0, 0), (48, 48))
self.tank_1 = self.tank.subsurface((48, 0), (48, 48))
# 是否可以移动
is_move = True
# 地图顶端
if self.rect.top < 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞石头/钢墙
if pygame.sprite.spritecollide(self, brickGroup, False, None) or \
pygame.sprite.spritecollide(self, ironGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞其他坦克
if pygame.sprite.spritecollide(self, tankGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 大本营
if pygame.sprite.collide_rect(self, myhome):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
return is_move
# 向下
def move_down(self, tankGroup, brickGroup, ironGroup, myhome):
self.direction_x, self.direction_y = 0, 1
# 先移动后判断
self.rect = self.rect.move(self.speed*self.direction_x, self.speed*self.direction_y)
self.tank_0 = self.tank.subsurface((0, 48), (48, 48))
self.tank_1 = self.tank.subsurface((48, 48), (48, 48))
# 是否可以移动
is_move = True
# 地图底端
if self.rect.bottom > 630 – 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞石头/钢墙
if pygame.sprite.spritecollide(self, brickGroup, False, None) or \
pygame.sprite.spritecollide(self, ironGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞其他坦克
if pygame.sprite.spritecollide(self, tankGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 大本营
if pygame.sprite.collide_rect(self, myhome):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
return is_move
# 向左
def move_left(self, tankGroup, brickGroup, ironGroup, myhome):
self.direction_x, self.direction_y = -1, 0
# 先移动后判断
self.rect = self.rect.move(self.speed*self.direction_x, self.speed*self.direction_y)
self.tank_0 = self.tank.subsurface((0, 96), (48, 48))
self.tank_1 = self.tank.subsurface((48, 96), (48, 48))
# 是否可以移动
is_move = True
# 地图左端
if self.rect.left < 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞石头/钢墙
if pygame.sprite.spritecollide(self, brickGroup, False, None) or \
pygame.sprite.spritecollide(self, ironGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞其他坦克
if pygame.sprite.spritecollide(self, tankGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 大本营
if pygame.sprite.collide_rect(self, myhome):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
return is_move
# 向右
def move_right(self, tankGroup, brickGroup, ironGroup, myhome):
self.direction_x, self.direction_y = 1, 0
# 先移动后判断
self.rect = self.rect.move(self.speed*self.direction_x, self.speed*self.direction_y)
self.tank_0 = self.tank.subsurface((0, 144), (48, 48))
self.tank_1 = self.tank.subsurface((48, 144), (48, 48))
# 是否可以移动
is_move = True
# 地图右端
if self.rect.right > 630 – 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞石头/钢墙
if pygame.sprite.spritecollide(self, brickGroup, False, None) or \
pygame.sprite.spritecollide(self, ironGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 撞其他坦克
if pygame.sprite.spritecollide(self, tankGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
# 大本营
if pygame.sprite.collide_rect(self, myhome):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
is_move = False
return is_move
# 死后重置
def reset(self):
self.level = 0
self.protected = False
self.tank = pygame.image.load(self.tanks[self.level]).convert_alpha()
self.tank_0 = self.tank.subsurface((0, 0), (48, 48))
self.tank_1 = self.tank.subsurface((48, 0), (48, 48))
self.rect = self.tank_0.get_rect()
self.direction_x, self.direction_y = 0, -1
if self.player == 1:
self.rect.left, self.rect.top = 3 + 24 * 8, 3 + 24 * 24
elif self.player == 2:
self.rect.left, self.rect.top = 3 + 24 * 16, 3 + 24 * 24
else:
raise ValueError(‘myTank class -> player value error.’)
self.speed = 3

# 敌方坦克类
class enemyTank(pygame.sprite.Sprite):
def __init__(self, x=None, kind=None, is_red=None):
pygame.sprite.Sprite.__init__(self)
# 用于给刚生成的坦克播放出生特效
self.born = True
self.times = 90
# 坦克的种类编号
if kind is None:
self.kind = random.randint(0, 3)
else:
self.kind = kind
# 所有坦克
self.tanks1 = [‘images/enemyTank/enemy_1_0.png’, ‘images/enemyTank/enemy_1_1.png’, ‘images/enemyTank/enemy_1_2.png’, ‘images/enemyTank/enemy_1_3.png’]
self.tanks2 = [‘images/enemyTank/enemy_2_0.png’, ‘images/enemyTank/enemy_2_1.png’, ‘images/enemyTank/enemy_2_2.png’, ‘images/enemyTank/enemy_2_3.png’]
self.tanks3 = [‘images/enemyTank/enemy_3_0.png’, ‘images/enemyTank/enemy_3_1.png’, ‘images/enemyTank/enemy_3_2.png’, ‘images/enemyTank/enemy_3_3.png’]
self.tanks4 = [‘images/enemyTank/enemy_4_0.png’, ‘images/enemyTank/enemy_4_1.png’, ‘images/enemyTank/enemy_4_2.png’, ‘images/enemyTank/enemy_4_3.png’]
self.tanks = [self.tanks1, self.tanks2, self.tanks3, self.tanks4]
# 是否携带食物(红色的坦克携带食物)
if is_red is None:
self.is_red = random.choice((True, False, False, False, False))
else:
self.is_red = is_red
# 同一种类的坦克具有不同的颜色, 红色的坦克比同类坦克多一点血量
if self.is_red:
self.color = 3
else:
self.color = random.randint(0, 2)
# 血量
self.blood = self.color
# 载入(两个tank是为了轮子特效)
self.tank = pygame.image.load(self.tanks[self.kind][self.color]).convert_alpha()
self.tank_0 = self.tank.subsurface((0, 48), (48, 48))
self.tank_1 = self.tank.subsurface((48, 48), (48, 48))
self.rect = self.tank_0.get_rect()
# 坦克位置
if x is None:
self.x = random.randint(0, 2)
else:
self.x = x
self.rect.left, self.rect.top = 3 + self.x * 12 * 24, 3
# 坦克是否可以行动
self.can_move = True
# 坦克速度
self.speed = max(3 – self.kind, 1)
# 方向
self.direction_x, self.direction_y = 0, 1
# 是否存活
self.being = True
# 子弹
self.bullet = Bullet()
# 射击
def shoot(self):
self.bullet.being = True
self.bullet.turn(self.direction_x, self.direction_y)
if self.direction_x == 0 and self.direction_y == -1:
self.bullet.rect.left = self.rect.left + 20
self.bullet.rect.bottom = self.rect.top – 1
elif self.direction_x == 0 and self.direction_y == 1:
self.bullet.rect.left = self.rect.left + 20
self.bullet.rect.top = self.rect.bottom + 1
elif self.direction_x == -1 and self.direction_y == 0:
self.bullet.rect.right = self.rect.left – 1
self.bullet.rect.top = self.rect.top + 20
elif self.direction_x == 1 and self.direction_y == 0:
self.bullet.rect.left = self.rect.right + 1
self.bullet.rect.top = self.rect.top + 20
else:
raise ValueError(‘enemyTank class -> direction value error.’)
# 随机移动
def move(self, tankGroup, brickGroup, ironGroup, myhome):
self.rect = self.rect.move(self.speed*self.direction_x, self.speed*self.direction_y)
is_move = True
if self.direction_x == 0 and self.direction_y == -1:
self.tank_0 = self.tank.subsurface((0, 0), (48, 48))
self.tank_1 = self.tank.subsurface((48, 0), (48, 48))
if self.rect.top < 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
self.direction_x, self.direction_y = random.choice(([0, 1], [0, -1], [1, 0], [-1, 0]))
is_move = False
elif self.direction_x == 0 and self.direction_y == 1:
self.tank_0 = self.tank.subsurface((0, 48), (48, 48))
self.tank_1 = self.tank.subsurface((48, 48), (48, 48))
if self.rect.bottom > 630 – 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
self.direction_x, self.direction_y = random.choice(([0, 1], [0, -1], [1, 0], [-1, 0]))
is_move = False
elif self.direction_x == -1 and self.direction_y == 0:
self.tank_0 = self.tank.subsurface((0, 96), (48, 48))
self.tank_1 = self.tank.subsurface((48, 96), (48, 48))
if self.rect.left < 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
self.direction_x, self.direction_y = random.choice(([0, 1], [0, -1], [1, 0], [-1, 0]))
is_move = False
elif self.direction_x == 1 and self.direction_y == 0:
self.tank_0 = self.tank.subsurface((0, 144), (48, 48))
self.tank_1 = self.tank.subsurface((48, 144), (48, 48))
if self.rect.right > 630 – 3:
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
self.direction_x, self.direction_y = random.choice(([0, 1], [0, -1], [1, 0], [-1, 0]))
is_move = False
else:
raise ValueError(‘enemyTank class -> direction value error.’)
if pygame.sprite.spritecollide(self, brickGroup, False, None) \
or pygame.sprite.spritecollide(self, ironGroup, False, None) \
or pygame.sprite.spritecollide(self, tankGroup, False, None):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
self.direction_x, self.direction_y = random.choice(([0, 1], [0, -1], [1, 0], [-1, 0]))
is_move = False
if pygame.sprite.collide_rect(self, myhome):
self.rect = self.rect.move(self.speed*-self.direction_x, self.speed*-self.direction_y)
self.direction_x, self.direction_y = random.choice(([0, 1], [0, -1], [1, 0], [-1, 0]))
is_move = False
return is_move
# 重新载入坦克
def reload(self):
self.tank = pygame.image.load(self.tanks[self.kind][self.color]).convert_alpha()
self.tank_0 = self.tank.subsurface((0, 48), (48, 48))
self.tank_1 = self.tank.subsurface((48, 48), (48, 48))
再接着看子弹的实现,子弹的主要属性包括:方向、速度、是否存活、是否为加强版等,代码实现如下:

# 子弹类
class Bullet(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
# 子弹四个方向(上下左右)
self.bullets = [‘images/bullet/bullet_up.png’, ‘images/bullet/bullet_down.png’, ‘images/bullet/bullet_left.png’, ‘images/bullet/bullet_right.png’]
# 子弹方向(默认向上)
self.direction_x, self.direction_y = 0, -1
self.bullet = pygame.image.load(self.bullets[0])
self.rect = self.bullet.get_rect()
# 在坦克类中再赋实际值
self.rect.left, self.rect.right = 0, 0
# 速度
self.speed = 6
# 是否存活
self.being = False
# 是否为加强版子弹(可碎钢板)
self.stronger = False
# 改变子弹方向
def turn(self, direction_x, direction_y):
self.direction_x, self.direction_y = direction_x, direction_y
if self.direction_x == 0 and self.direction_y == -1:
self.bullet = pygame.image.load(self.bullets[0])
elif self.direction_x == 0 and self.direction_y == 1:
self.bullet = pygame.image.load(self.bullets[1])
elif self.direction_x == -1 and self.direction_y == 0:
self.bullet = pygame.image.load(self.bullets[2])
elif self.direction_x == 1 and self.direction_y == 0:
self.bullet = pygame.image.load(self.bullets[3])
else:
raise ValueError(‘Bullet class -> direction value error.’)
# 移动
def move(self):
self.rect = self.rect.move(self.speed*self.direction_x, self.speed*self.direction_y)
# 到地图边缘后消失
if (self.rect.top < 3) or (self.rect.bottom > 630 – 3) or (self.rect.left < 3) or (self.rect.right > 630 – 3):
self.being = False
*后,我们看一下程序的主要初始化代码,如下所示:

# 初始化
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((630, 630))
pygame.display.set_caption(“TANK”)
# 加载图片
bg_img = pygame.image.load(“images/others/background.png”)
# 加载音效
add_sound = pygame.mixer.Sound(“audios/add.wav”)
add_sound.set_volume(1)
bang_sound = pygame.mixer.Sound(“audios/bang.wav”)
bang_sound.set_volume(1)
blast_sound = pygame.mixer.Sound(“audios/blast.wav”)
blast_sound.set_volume(1)
fire_sound = pygame.mixer.Sound(“audios/fire.wav”)
fire_sound.set_volume(1)
Gunfire_sound = pygame.mixer.Sound(“audios/Gunfire.wav”)
Gunfire_sound.set_volume(1)
hit_sound = pygame.mixer.Sound(“audios/hit.wav”)
hit_sound.set_volume(1)
start_sound = pygame.mixer.Sound(“audios/start.wav”)
start_sound.set_volume(1)
# 开始界面
num_player = show_start_interface(screen, 630, 630)
# 播放游戏开始的音乐
start_sound.play()
# 关卡
stage = 0
num_stage = 2
# 游戏是否结束
is_gameover = False
# 时钟
clock = pygame.time.Clock()
看一下实现效果:

%title插图%num

再说一下玩家一、二的操作键,玩家一、二移动键分别为:WASD、←→↑↓,玩家一、二射击键分别为:J、0。

如何写出优雅的代码

如何写出优雅的代码

 

刚开始学编程的时候,*激动人心,也*捉人眼球的,莫过于各种炫酷拉风的功能了。

即使再深入一点,我们关注的通常也都是功能。只要功能实现了,什么代码风格、代码格式啥的,那都是无关痛痒的细节,who又care呢?

什么?PEP8?你说什么,大点儿声,我听不见——

代码规范是什么?
所谓“不以规矩,不能成方圆”。

在编程实践中,代码规范就是这个“规矩”;整洁易读可维护的代码就是这个“方圆”。而开发、协作的效率,就是层层掩藏下我们真正追求的东西。

——当然,更抽象地来说,如果说代码就是开发人员的作品,那么有的开发者是手工艺人,而有的开发者则可以称为艺术家。

艺术家怎么来的呢?齐白石老人家有云:学我者生,似我者死。成为艺术家的道路是曲折的——废话了哈哈。

首先我们得学习前人留下的宝贵经验,从中汲取养分,为我所用。不是说

我们为什么需要代码规范
不知道读者有没有被各种各样的代码文件格式搞晕过。

文件格式到底有多少种呢?只看一看vscode支持的保存格式就可见一斑:

%title插图%num
01
然鹅,实际上他们的内容没有什么本质的区别,都是“文本文件”。也就是说,都跟纯文本文件以及我们*常见的.txt文件是一伙的。这也就是为什么江湖上总流传着“大神都用记事本编程”这样的轶事——记事本写一个.txt也是写,多写一个.c也是写,再写写.py、.go、.java、.rs又有何不妥?“无他,唯手熟尔。”

也就是说,只要内容没问题,即使文件后缀是.txt,也是可以作为程序源文件的。*直接的例子就是Python。

将以下内容保存为文件test_text.txt文件:

print(“This is intterpreted from .txt”)
再在当前路径下命令行执行:

$ python test_text.txt
// This is intterpreted from .txt
可以看到,程序源文件的后缀并不影响Python解释器的解释。

更进一步,把.txt改为.cpp:

$ python test_text.cpps
// This is intterpreted from .cpp
众所周知,计算机对文本其实是不怎么感冒的,实际的文本文件存储在计算机内部依然是二进制数据,和其他的二进制文件没啥两样。

那我们为什么还要多此一举,把程序源文件存储为文本文件呢?直接用二进制难道不香吗?

是的。不香。

说一千道一万,我们写程序并不是为了给计算机看的,而是为别人甚至是为自己而写的——计算机只需要看到二进制就好了。

对计算机而言用不到的、各种曲里拐弯的编码规范,对人类而言就是一个整洁完备、定义良好的“接口”,更是一种人与人之间的“通信协议”。使用统一、高效的通信协议,能够大大降低开发者之间的交流成本,显著提升开发效率。

在网络通信中,通信协议有多重要,那么在软件开发领域,编程规范就有多重要。

编程规范老是忘,pylint来帮忙
说完编程规范的重要性,接下来我们还需要面对一个现实的问题:编程规范好是好,奈何记不住啊。

是的,编程规范的内容太多了。别的不说,单只Python一门语言,就有各种各样的增强提案(PEP),很多提案中都涉及到对编程风格的规范,即使有了一段时间的实际开发经验,也不一定真的能一一记住这些条目的内容。

这个时候自然而然地就会想到借助工具。好在,现代的IDE乃至vscode这样的编辑器,都能够针对不同的程序语言提供相应的代码检查功能。对于检查到的、不符合编程规范的条目都会划线甚至高亮显示出来,提示开发者进行修改。

但一来,这是被动的规范检查,可控性不强;二来,检查内容可能不是很全面,难免遗漏。

这里要介绍的就是一种主动检查代码规范的方式:使用pylint。

pylint的安装就不再赘述了,一如其他Python模块,直接pip一把梭就好:

$ pip install pylint
安装好之后使用就很简单了,pylint + [目标文件名]即可。

将如下文本保存为文件justdopython.py:

def just():
print(“just”)
pass

def do():
print(“dopython”)
pass
然后执行命令:

$ pylint justdopython.py

// ************* Module justdopython
// test_text.py:11:0: C0304: Final newline missing (missing-final-newline)
// test_text.py:1:0: C0111: Missing module docstring (missing-docstring)
// test_text.py:1:0: C0111: Missing function docstring (missing-docstring)
// test_text.py:3:4: W0107: Unnecessary pass statement (unnecessary-pass)
// test_text.py:6:0: C0103: Function name “do” doesn’t conform to snake_case naming style (invalid-name)
// test_text.py:6:0: C0111: Missing function docstring (missing-docstring)
// test_text.py:8:4: W0107: Unnecessary pass statement (unnecessary-pass)

// ——————————————————————-
// Your code has been rated at -1.67/10 (previous run: 0.00/10, -1.67)
逐条看过去,可以看到输出内容首先列出了不符合规范的条目,分别给出了与规范冲突的位置(行列)、违反的规范编号以及具体违反的内容和类型。*后pylint还给源文件打了个分。

好嘛,满分10分,得了个-1.67分……

注意其中*个与规范不符的条目“Final newline missing”。Python编程规范推荐我们在文件的*后留一个空行。

随后的“Missing module docstring”和“Missing function docstring”都是缺少对模块和函数的描述字符串导致的,我们按要求加上即可;既方便快速理解相应模块/函数,又可以用于之后自动化生成文档。

当然也有的人总是懒得给函数写描述文档,此时可以使用特殊的注释来禁止pylint对某项规范进行检查:

def just(): # pylint: disable=missing-docstring
print(“just”)
pass
这样再使用pylint扫描源文件,此处就不会再报告相关冲突了。

再往下,还提示我们函数名“do”不符合蛇形命名风格——所谓蛇形命名,也就是全小写、单词之间使用下划线连接的命名风格,也是Python推荐的命名风格。

剩下一种“Unnecessary pass statement”则是提示pass语句多余,修复也就是了。

*后还需要提示的是,虽然pylint可以通过注释或者配置文件,取消掉某些规范的提示。但是依然建议尽量不要人为禁止相关提示。

有问题需要解决,而不是掩耳盗铃当做问题不存在。

随着项目中代码量的增长,前期遗留的小问题再想修复,成本会越来越高。

Faker 了解一下

自动造数据,Faker 了解一下?

 

在系统开发过程中,不管作为开发还是测试,我们都要造一些假数据到系统中,来模拟真实环境的运行。比如要创建一批用户,输入一些车牌,或者是电话号码,或者是街道地址等等。对于我来说,要么是大量的“测试XX”,要么是随手在键盘上一敲,都是些无意义的货真价实的假数据。看完这篇文章,你就能告别这样的烦恼了。

安装及基本用法
安装
安装 Faker 很简单,使用 pip 方式安装:

pip install Faker

基本用法
Faker 的使用也是很简单的,从 faker 模块中导入类,然后实例化这个类,就可以调用方法使用了:

from faker import Faker

fake = Faker()
fake.name()
# Danny Clarke
print(fake.address())
# 909 Nichols Ferry
# Abigailfort, MA 69479

这里我们造了一个名字和一个地址,由于 Faker 默认是英文数据,所以如果我们需要造其他语言的数据,可以使用 locale参数,例如:

from faker import Faker

fake = Faker(locale=’zh_CN’)
fake.name()
# 史林

fake.address()
# 陕西省惠州市门头沟沈路d座 991298

是不是看起来还不错,但是有一点需要注意,这里的地址并不是真实的地址,而是随机组合出来的,也就是将省、市、道路之类的随机组合在一起。

地址相关方法
这里需要注意,有些方法是有地区倾向的,比如 province() 在中文中可以正常获取,但是在其他语种中可能会报错。

fake.building_number() # 楼栋名称
# ‘U座’

fake.postcode() # 邮政编码
# 257897

fake.street_address() # 街道地址
# 吴路e座

fake.street_name() # 街道名称
# 嘉禾路

条形码相关方法
fake.ean(length=13) # EAN条形码
# ‘5427745056706’

fake.ean13() # EAN13条形码
# ‘0937312282094’

fake.ean8() # EAN8条形码
# ‘52227936’

颜色相关方法
fake.color_name() # 颜色名称
# ‘White’

fake.hex_color() # 颜色十六进制值
# ‘#f5db7c’

fake.rgb_color() # 颜色RGB值
# ‘15,240,40’

货币相关方法
fake.cryptocurrency() #加密货币代码+名称
# (‘AMP’, ‘AMP’)

fake.cryptocurrency_code() # 加密货币代码
# ‘XMR’

fake.cryptocurrency_name() # 加密货币名称
# ‘Feathercoin’

fake.currency() # 货币代码+名称
(‘SEK’, ‘Swedish krona’)

fake.currency_code() # 货币代码
# ‘CRC’

fake.currency_name() # 货币名称
# ”Mozambican metical’

时间相关方法
fake.date(pattern=”%Y-%m-%d”, end_datetime=None) # 日期字符串(可设置格式和*大日期)
# ‘1992-02-13′

fake.date_between(start_date=”-7y”, end_date=”today”) # 日期(可设置限定范围)
# datetime.date(2017, 1, 24)

fake.date_of_birth(tzinfo=None, minimum_age=0, maximum_age=100) # 出生日期(可设置*大*小年龄)
# datetime.date(1964, 2, 27)

fake.date_this_month(before_today=True, after_today=False) # 本月中的日期
# datetime.date(2020, 7, 1)

fake.time(pattern=”%H:%M:%S”, end_datetime=None) # 时间(可设置格式和*大时间)
# ’13:00:18’

fake.timezone() # 时区
# ‘Africa/Kampala”

坐标相关方法
fake.latlng() # 经纬度
# (Decimal(‘-84.6293375’), Decimal(‘106.942208’))

fake.local_latlng(country_code=”CN”, coords_only=False) # 返回某个国家某地的经纬度
# (‘46.51872’, ‘86.00214’, ‘Hoxtolgay’, ‘CN’, ‘Asia/Urumqi’)

联系方式相关方法
fake.phone_number() # 手机号
# ‘13920149907’

fake.email(*args, **kwargs)
# caixiulan@35.cn’

文本相关方法
fake.paragraph(nb_sentences=3, variable_nb_sentences=True, ext_word_list=None) # 单个段落
# ‘法律国际评论网站这是.个人其他不断报告公司.’

fake.text(max_nb_chars=200, ext_word_list=None) # 单个文本
# ‘起来以后文章所以自己.一直一定以上电子.\n然后开始问题出来已经.信息这个空间各种经营.\n到了所有需要介绍支持.方式建设专业就是经营更多.主要功能比较事情帮助.\n之后点击成为选择资密码部分.’

fake.md5(raw_output=False) # Md5
# ‘3f9824429336952484a70de210f0794f’

fake.password(length=10, special_chars=True, digits=True, upper_case=True, lower_case=True) # 密码
# ‘xNGUixHi$1’

fake.uuid4()
# ‘a70b41c0-ccdb-4dd0-9c9b-9b650fa72a49’

python 相关方法
*后,我们来看看 python 相关的方法,可以为我们写测试方法提供一些方便。

fake.pydict(nb_elements=10, variable_nb_elements=True) # Python字典
# {‘女人’: ‘EsiJkxQOIyMEKMBLiraE’, ‘系列’: ‘shenxiulan@gmail.com’, ‘操作’: ‘VGiviSlGVputFAUSWfdL’, ‘不过’: ‘CZJaxfxXXIDFOThUxxOR’, ‘得到’: 3637, ‘搜索’: Decimal(‘-292113.66项目’: datetime.datetime(2000, 1, 12, 0, 54, 5), ‘那些’: ‘SbQuGqOGEnFhdPEYRrou’, ‘虽然’: ‘EvRyxlmTgrrhrVWxMXIu’, ‘方面’: -1623419454707.4, ‘历史’: ‘jsun@hotmail.com’, ‘对//moxiang.cn/’, ‘社区’: ‘http://lei.cn/’}

fake.pyiterable(nb_elements=10, variable_nb_elements=True) # Python可迭代对象
# [‘llyMSqurkCSNUwLcNJQg’, ‘bUxhBPmxovfOXixprnCr’, ‘nDNAnBFUWnqFTzJAPoYZ’, ‘aygoBknQESmPeMTdVoxz’, 6393, ‘http://ef.cn/category/posts/register/’]

fake.pystruct(count=3) # Python结构
# ([‘https://jieduan.cn/author/’, 2690, ‘http://litao.cn/category/’], {‘还是’: datetime.datetime(2000, 8, 20, 22, 17, 30), ‘一直’: 9796, ‘音乐’: ‘https://www.laizhou.cn/hom{‘全部’: {0: 6397, 1: [Decimal(‘833148.6’), datetime.datetime(2014, 2, 10, 20, 27, 58), 2722], 2: {0: datetime.datetime(1977, 5, 16, 17, 32, 12), 1: ‘WoIPYqFCHtXyQpbOJIBP 2: [‘yLJmsrTnTksLqBmMhDul’, datetime.datetime(2005, 2, 4, 6, 52, 20)]}}, ‘大小’: {1: ‘huangjun@gmail.com’, 2: [‘KLTasWcxRxpkodwDAaWn’, ‘kgmVQfTcQWSRWeznRLiC’, 46.727079]3: {1: ‘LNYNQybvzxVtioHSpdEC’, 2: ‘KtAnXvEEEoQjNuIORdul’, 3: [8000, 52227.700775]}}, ‘地区’: {2: ‘junxiao@jingxia.cn’, 3: [‘zhaona@jiemo.net’, ‘DHKalOGzfcDKSZtTRLsK’, ‘doGiBbxvuKAuqrIPpS’], 4: {2: 9444, 3: ‘RSqhHYIdViwynTTaIjuE’, 4: [1515, 6512]}}})

 

听说你的程序又双叒叕乱码了

听说你的程序又双叒叕乱码了

 

在我刚开始接触编程的时候,时不时就会遇到程序乱码问题。亦或是控制台输出乱码,亦或是数据库乱码,亦或是网页端显示乱码,总之乱码无处不在,它总是会在不应该出现的时间点出现在在不应该出现的位置。

那么乱码究竟是怎么回事呢,为啥好端端的文字就变成一堆乱七八糟的符号了呢?

这时如果你去网上搜索乱码问题,你就会发现大量的文章向你扑面而来,看完之后你更加郁闷了,还是不明其中缘由,今天我们就来探究下程序究竟为什么会乱码?

ASCII 码
大家都知道计算机是从美国开始走向全世界的,*初的用途是服务于美国军方,用来帮助人类做一些复杂的数学运算,比如计算导弹弹道之类的。

后来随着计算机的发展,人们发现这个工具简直太好用了,事实再一次证明人类是多么的贪婪。他们有了更多的需求和欲望,比如想让计算机处理文字,控制打印机等。这时候就不像处理数学运算那么简单了,因为计算机内部都是 0 和 1 的二进制代码,怎么才能让计算机显示文字呢,于是发明计算机的大叔们搞了一个字符和数字的对应关系。我们知道虽然单词有很多个,但每个单词都是由 26 个字母组成的,区分大小写的话也才 52 个,1 个字节 8 位可以有 256 种状态,再加上数字和一些标点符号 1 个字节也足够了。

于是那些大叔们把从 0 开始的 32 种状态规定了特殊的用途,比如打印机换行,彩色打印。然后又把数字、标点符号、大小写字母等逐一编码,直到编写到 127 号,也就是共计 128 个编码。*后给这张编码表起了个逼格很高的名字,叫「ASCII 码」,也叫「美国信息交换标准代码」(American Standard Code for Information Interchange)。

比如,小写字母 a 对应的 ASCII 码值是 97,十六进制 0x61;数字 0 对应的 ASCII 码是 48,十六进制 0x30。「ASCII 码」是计算机编码的基石。后面所有的编码都是在此基础上完善的。

GBK 编码
后来随着时代的不断发展,计算机在全球范围内开始普及。日本,俄罗斯,中国等很多国家都开始使用计算机,但其他很多国家用的并不是英文呀,比如我们中国的汉字,跟英文的字母简直是天壤之别,而且仅常用的汉字就要有 3000+ 了,1 个字节 256 种状态怎么够用呢。

于是聪慧的中国人民发明了我们自己的编码系统,用两个字节来表示一个汉字或者字符。*个字节称为「高位字节」(也称区字节),第二个字节称为「低位字节」(也称位字节)。但这两个字节是有限制的,「高位字节」使用范围为 0xA1-0xF7,「低位字节」使用范围为 0xA1-0xFE。

这样我们就可以组合出大概 8000 个左右的汉字了,对于日常使用来说是足够了。这套编码规则就是「GB2312」。

你知道的,中国文化博大精深,区区 8000 个汉字怎么够用,很多少数民族也迫切希望用上计算机,很多人名依然无法识别,于是不在对高位字节和低位字节做限制,只要发现*个字节大于 127 就认为这是一个汉字的开始,为什么要大于 127 呢,因为要兼容「ASCII 码」呀,127 以内说明是该字节表示的是「ASCII 码」。

这样子 2 个字节 15 位大概可以表示 65536 – 127 个汉字。

然而香港和台湾并没有使用大陆的 GBK 编码规则,因为他们使用繁体字,所以就单独设计了一套自己的编码系统,叫做「Big5」表,也叫大五码或者五大码。

然后日本、韩国等不同国家也都设计了自己独有的编码规则,因为是各自独立设计,所以大概率不会出现一个字符在不同的编码系统中对应同一个数字。于是就会出出现乱码这种很诡异的现象。

比如,你的韩国女朋友给你发了一封电子邮件,里面的真实内容为「么么哒」,由于电子邮件是在韩国写的,所以计算机肯定是使用韩国的编码系统,假设这三个字对应的编码分别是 0xA6B2、0xA6B2、0xC132;当你收到邮件之后,因为你是在国内所以肯定是使用 GBK 编码来解码,那么将三个编码转换为汉字之后发现是「分手吧」。

相信此时的你肯定是一脸懵逼,然后就又开始怀疑是不是自己做错了什么。

天下大统
为了解决各国编码不统一的问题,国际标准化组织制定了一套新的规则,叫做「Unicode码」,这套编码规则就厉害了,可以将全人类所有的字符都包含进去,包括人类走出非洲时的所使用的象形文字都可以包含进来。

那么如果要想包含这么多的字符进来的话,至少需要三个字节才可以,三个字节可以存储三百多万个字符,这应该可以覆盖所有文化的字符了吧,如果还不够的话,就用四个字节来表示一个字符,这样可以组合出大概 21 亿个字符,可能用到地球毁灭那一天都用不完。

可是随之而来的是另外一个问题,由于欧美大多使用英文,一个字节足以表示,现在让他们用三个字节,就造成了空间的巨大浪费。浪费率高达 2/3,做过网络编程的小伙伴们都知道,在网络中数据的传输是需要很大的成本的,而且还不可靠,字节数越多,浪费的资源也就越多。

于是,在接下来的时间内,出现了多种「Unicode码」的具体编码方式,分别是 UTF-8、UTF-16、UTF-32,其中 UTF-8 占用一到四个字节,UTF-16 占用二到四个字节,UTF-32 占用四个字节。显然  UTF-8 的设计更合理,欧美字符用一个字节来存储,中文或者其他字符用二个或者三个字节来存储,岂不美哉。

自此,天下大统。呃,不,是计算机编码得以大统,世上再无乱码。

Windows手动搭建邮箱服务器教程,操作简单易上手

简介:

本章介绍搭建邮箱服务器,安装文件地址:http://download.csdn.net/download/qq496013218/10049904,本地搭建邮箱服务器使用的是易邮的邮件服务器。

操作步骤:

1、打开软件首次展示页面,如下图:
%title插图%num
2、根据图中所描述的文字填写对应的信息,账号和密码,如下图:
%title插图%num
3、配置单域名,其实就是相当于163.com/qq.com
%title插图%num

4、创建账号成功后,安装foxmail邮箱客户端软件,新建账号–》手动配置,接受服务器类型:POP3,邮箱账号和密码是上图所创建的账号和密码,POP服务器和SMTP服务器如何填?因为是本地搭建的服务器添加127.0.0.1即可,如果是163邮箱,需要输入pop.163.com,smtp也是smtp.1163.com,*后确认即可。

%title插图%num

实现一个简单的SMTP服务器

实现一个简单的SMTP服务器
IP地址(IP Address)
端口(Port) SMTP 服务的端口号是 25
协议(Protocol)
数据传输方式:SOCK_STREAM 表示面向连接的数据传输方式;SOCK_DGRAM 表示无连接的数据传输方式
服务端:建立socket,声明自身的端口号和地址并绑定到socket,使用listen打开监听,然后不断用accept去查看是否有连接,如果有,捕获socket,并通过recv获取消息的内容,通信完成后调用closeSocket关闭这个对应accept到的socket,如果不再需要等待任何客户端连接,那么用closeSocket关闭掉自身的socket。
服务端编程
1、加载套接字库,创建套接字(WSAStartup()/socket());

int socket(int af, int type, int protocol);其中,af 是 IP 地址类型,常用的有 AF_INET 和 AF_INET6。AF_INET 表示 IPv4 地址。type 为数据传输方式,常用的有 SOCK_STREAM 和 SOCK_DGRAM。protocol 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。可以将 protocol 的值设为 0,系统会自动推演出应该使用什么协议。
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); //windows下创建TCP套接字
2、绑定套接字到一个IP地址和一个端口上(bind());
int bind(SOCKET sock, const struct sockaddr addr, int addrlen); //Windows
//创建sockaddr_in结构体变量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr)); //每个字节都用0填充
serv_addr.sin_family = AF_INET; //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr(“127.0.0.1”); //具体的IP地址
serv_addr.sin_port = htons(1234); //端口
//将套接字和IP、端口绑定
bind(serv_sock, (struct sockaddr)&serv_addr, sizeof(serv_addr));
3、将套接字设置为监听模式等待连接请求(listen());
int listen(SOCKET sock, int backlog); //Windows
sock 为需要进入监听状态的套接字,backlog 为请求队列的*大长度。
4、请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字(accept());
SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen); //Windows
sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。
5、用返回的套接字和客户端进行通信(send()/recv());
int send(SOCKET sock, const char *buf, int len, int flags);
sock 为要发送数据的套接字,buf 为要发送的数据的缓冲区地址,len 为要发送的数据的字节数,flags 为发送数据时的选项,设置0。
int recv(SOCKET sock, char *buf, int len, int flags);
6、返回,等待另一个连接请求;
7、关闭套接字,关闭加载的套接字库(closesocket()/WSACleanup());
https://blog.csdn.net/xiaoquantouer/article/details/58001960
详细编程函数解释
http://c.biancheng.net/cpp/html/3040.html
客户端:建立socket,通过端口号和地址确定目标服务器,使用Connect连接到服务器,send发送消息,等待处理,通信完成后调用closeSocket关闭socket。
1、connect函数用来建立连接
int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen); //Windows
类型
http://blog.chinaunix.net/uid-21411227-id-1826771.html

SMTP常用的响应

501——参数格式错误

502——命令不可实现

503——错误的命令序列

504——命令参数不可实现

211——系统状态或系统帮助响应

214——帮助信息

220——服务器就绪

221——服务关闭

421——服务器未就绪,关闭传输信道

250——要求的邮件操作完成

251——用户非本地,将转发向

450——要求的邮件操作未完成,邮箱不可用

550——要求的邮件操作未完成,邮箱不可用

451——放弃要求的操作,处理过程中出错

551——用户非本地,请尝试

452——系统存储不足,要求的操作未执行

552——过量的存储分配,要求的操作未执行

553——邮箱名不可用,要求的操作未执行

354——开始邮件输入,以“.”结束

554——操作失败
实现smtp

搭建Postfix邮箱服务器

一、为什么选择Postfix?
Postfix 是一种电子邮件服务器,它是由任职于IBM沃森研究中心(T.J. Watson Research Center)的荷兰籍研究员Wietse Venema为了改良sendmail邮件服务器而产生的MTA(邮件传输代理)软件。*早在1990年代晚期出现,是一个轻量级开放源代码的软件。postfix的产生是为了替代传统的sendmail,相较于sendmail,postfix在速度、性能和稳定性上都更胜一筹。如今许多的主流邮件服务事实上都在采用postfix。Postfix具备有以下几个特点。

1. 免费:

postfix想要作用的范围是广大的Internet用户。试图影响大多数的Internet上的电子邮件系统,因此它是免费的。

2. 更快:

postfix在性能上大约比sendmail快三倍。一部执行postfix的台式PC每天能够收发上百万封邮件。

3. 兼容性好:

postfix是兼容sendmail的,从而使sendmail用户能够非常方便地迁移到postfix。Postfix支持/var/spool/mail、/etc/aliases、 NIS、和 ~/.forward 文件。

4. 更健壮:

postfix被设计成在重负荷之下仍然能够正常工作。当系统执行超出了可用的内存或磁盘空间时,postfix会自己主动降低执行进程的数目。当处理的邮件数目增长时,postfix执行的进程不会跟着添加。

5. 更灵活:

postfix是由一组模块成,每一个程序拥有特定的功能。你能够通过配置文件设置每一个程序的执行参数。

6. 安全性

postfix具有多层防御结构,能够有效地抵御恶意入侵者。如大多数的postfix程序能够在较低的权限下运行。

二、 Postfix工作原理
1. 邮件相关协议

SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,工作在TCP的25端口。它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。跟名字一样smtp非常简单,无法做到认证,邮件存放等功能。

POP3(Post Office Protocol)邮局协议第3版,工作在TCP的110端口。本协议主要用于支持使用客户端远程管理在服务器上的电子邮件。POP 协议支持“离线”邮件处理。其具体过程是:邮件发送到服务器上,电子邮件客户端调用邮件客户机程序以连接服务器,并下载所有未阅读的电子邮件。这种离线访问模式是一种存储转发服务,将邮件从邮件服务器端送到个人终端PC机上。一旦邮件发送到 PC 机上,邮件服务器上的邮件将会被删除。但目前的POP3邮件服务器大都可以“只下载邮件,服务器端并不删除”,也就是改进的POP3协议。

IMAP4(Internet Mail Access Protocol)因特网邮件访问协议第4版,工作在TCP的143端口。IMAP4协议与POP3协议一样也是规定个人计算机如何访问网上的邮件的服务器进行收发邮件的协议,但是IMAP4协议同POP3协议相比更高级。IMAP4支持协议客户机在线或者离开访问并阅读服务器上的邮件,还能交互式的操作服务器上的邮件。IMAP4协议更人性化的地方是不需要像POP3协议那样把邮件下载到本地,用户可以通过客户端直接对服务器上的邮件进行操作(这里的操作是指:在线阅读邮件 在线查看邮件主题 大小 发件地址等信息)。用户还可以在服务器上维护自己邮件目录(维护是指移动)新建 删除重命名共享 抓取文本 等操作)。IMAP4协议弥补了POP3协议的很多缺陷。

2. 邮件相关名词

一般情况下,我们把电子邮件程序分解成用户代理、传输代理和投递代理三部分。用户代理用来接受用户的指令,将用户的信件传送至信件传输代理,而投递代理则从信件传输代理取得信件传送至*终用户的邮箱;当用户试图发送一封电子邮件的时候,他并不能直接将信件发送到对方的机器上,用户代理必须试图去寻找一个信件传输代理,把邮件提交给它。信件传输代理得到了邮件后,首先将它保存在自身的缓冲队列中,然后,根据邮件的目标地址,信件传输代理程序将找到应该对这个目标地址负责的邮件传输代理服务器,并且通过网络将邮件传送给它。对方的服务器接收到邮件之后,将其缓冲存储在本地,直到电子邮件的接收者查看自己的电子信箱。

1)MTA(Mail Transfer Agent)

邮件传输代理,通过SMTP协议所指定的服务器,就可以把E-mail寄到收信人的服务器上了,整个过程只要几分钟。SMTP服务器则是遵循SMTP协议的发送邮件服务器,用来发送或中转发出的电子邮件。常用的MTA有:

SendMail:邮件的鼻祖。

Qmail:一个数学家开发,作为Linux下面主流的邮件系统内核,大量著名的商业邮件系统都是在Qmail内核下开发,比如Hotmail。

Postfix:IBM安全专家开发,模块化设计,并且比sendmail兼容效率更高。

Exim:是基于GPL协议的开放源代码软件,由英国剑桥大学的Philip Hazel开发。

Exchange:微软公司的一套电子邮件服务组件,是个消息与协作系统。

2)MDA(Mail Delivery Agent)

邮件投递代理,投递代理则从信件传输代理取得信件传送至*终用户的邮箱。常用的MDA有:

ProcMail:Postfix默认的邮件投递工具。

MailDrop:相对比较专业的投递代理工具。

3)MRA(Mail Retrieval Agent)

邮件取回代理,使用POP3或IMAP4协议工作。一般用于从用户信箱取回邮件到邮件用户代理客户端。常用的MRA有:

Dovecot:是一个开源的支持IMAP和POP3协议的收邮件服务器(自带SASL功能)。

4)MUA(Mail User Agent)

邮件用户代理工具。常用的MUA有:

OE:Windows旧版本自带的工具。

Outlook:Office套件带的工具。

FoxMail:腾讯公司的邮件客户端工具。

Thunderbird:雷鸟是Linux下的客户端工具。

Mutt:Linux下的字符界面客户端工具。

5)WebMail

基于Web的电子邮件收发系统,扮演邮件用户代理角色,一般而言,WebMail系统提供邮件收发、用户在线服务和系统服务管理等功能。WebMail的界面直观、友好,不需要借助客户端,免除了用户对E-mail客户软件(如:Foxmail、Outlook等)进行配置时的麻烦,只要能上网就能使用WebMail,方便用户对邮件进行接收和发送。WebMail使得E-mail在Internet上的应用广泛。常用的Webmail有:

Openwebmail:台湾开发的。

Squirrelmail:Centos系统自带。

Extmail,Extman:国内开发的也被称为EMOS系统。

6)Mail Relay

邮件中继,先要明白一个概念,一封邮件只要不是发送给本域内用户的,比如从当前域发送到另一个域,或从当前域发送到另一个域然后转到另外一个域的,这就属于中继。但是一般邮件服务器都会允许本地或本域内的用户进行中继。不然就只能在本域内发送邮件而不能给外部邮箱发送邮件。Postfix默认只能基于IP地址做中继认证。

7)SASL(simple authentication secure layer)

简单认证安全层,是一种用来扩充C/S模式验证能力的机制。在Postfix可以利用SASL来判断用户是否有权使用转发服务,或是辨认谁在使用你的服务器。

常用的Sasl有:

cyrus-sasl:Redhat系列自带的SASL认证框架。

dovecot-sasl:dovecot组件带的SASL认证框架。

courier-authlib:这是一个带有MTA,MDA以及SASL认证的软件,但是一般只是用它的SASL功能。

3. Postfix的工作原理

邮件服务器工作在两种情况下:一种是相同域内转发;二是不同域内转发。所以MTA(postfix)内部一般都会有smtpd(服务端)、smtp(客户端)和MDA(邮件投递代理)。smtpd邮件服务器非常简单,只负责转发邮件别的什么功能也没有,并且发送方不管是什么地址都可以从smtpd服务器转发到目标地址。
%title插图%num

①相同域内转发:用户A通过工具连接到SMTPD服务端,然后发送一份邮件,而目标收件人是B用户。当SMPTD收到邮件后判断发现是同域内的用户,就会直接启动MDA进程把邮件投进用户邮箱中,当用户通过MUA工具(如mail命令)就可以直接收到邮件。

②不同域内转发:用户A通过工具连接到SMTPD服务端,然后发送一份邮件,而目标收件人是C用户。当SMPTD收到邮件后判断发现是不同域内用户,就会启动SMTP客户端来转发此邮件。SMTP收到请求后就会解析对方MX记录,然后连接对方SMTPD服务器并把邮件发送过去。当对方SMTPD发现此邮件就是本域内的邮件后也会调用MDA进程把邮件投递进用户邮箱中,当用户通过MUA工具(如mail命令)就可以直接收到邮件。

三、 安装Postfix
安装postfix

# yum install postfix

四、 Postfix模块解析
postfix是一个非常安全高效免费开源的邮件服务器,在redhat6之后的系统默认安装的邮件服务器,之所以高效是因为采用模块化设计,由多个具有不同功能的半驻留进程组成,并且在这些进程中并无特定的进程间父子关系,大多数的postfix进程由一个进程统一进行管理,该进程负责在需要的时候调用其他进程,这个管理进程就是master进程。该进程也是一个后台程序。

Postalias             用于构造、修改和查询别名表

Postmap             用于构造、修改或者查询别名表

Postconf             用于显示和编辑main.cf配置文件

Postfix                 用于启动、停止postfix,但是要求root权限

Postqueue          用于管理邮件队列,一般用户使用

Postsuper           用于管理邮件队列,root用户使用

五、配置Postfix
/etc/postfix/master.cf            master主程序的配置文件

/etc/postfix/main.cf               postfix服务的配置文件

* postconf   [选项]           #查看当前有效的配置

-n                 #查看非默认配置

-d                 #查看默认配置

-e                 #直接编辑某个配置的参数

* postfix check #检查配置

* postfis reload  #重新加载配置

需要重点掌握的几个参数

inet_interfaces:postfix监听的接口,默认localhost

myorigin:邮件的发送者所在的域

mydestination:postfix接收邮件时的收件人域名

mynetworks:受信任的网络或主机,即你所在网络的网络地址,postfix通过该值来判定用户是远程还是本地的,如果是本地网络用户则允许其访问

myhostname:我的主机名

mydomain:我的域名,即我负责的邮件域,E-mail地址“@”后面的部分

relayhost:中继主机的地址

home_mailbox = Maildir/:指定用户邮箱文件夹

注意:地址的*后有没有”/”决定了使用mailbox格式还是maildir格式,没有”/”表示mailbox。Mailbox格式是所有邮件都在一个文件里,而Maildir是每个邮件都是单独的一个文件。

六、发送*封邮件
1. 发送邮件

# mail -s “hello world”user1@zhyx.com

2. 查看邮件

输入mail,就进入了当前用户的收件箱

搭建邮件服务器,过程非常简单

搭建邮件服务器,过程非常简单,只需几个步骤即可。
通常在Linux搭建邮件服务器是需要安装很多软件和配置文件的,所以我们可以使用一个开源邮件服务器软件来搭建的。

准备一台服务器与系统
云服务器或本地虚拟的服务器都可以。
服务器需要能链接网络。
安装centos6/7系统,需要全新干净的系统环境。
配置系统
检查swap是否已启动,如没启动需要配置启动swap。
1
下载EwoMail邮件服务器
在git下载EwoMail开源版
git地址:https://gitee.com/laowu5/EwoMail

EwoMail集成的软件:

Postfix:邮件服务器
Dovecot:IMAP/POP3/邮件存储
Amavisd:反垃圾和反病毒
Fail2ban:监控策略
LNAMP:apache2.2,nginx1.8, mysql5.5,php5.4
EwoMail-Admin:WEB邮箱管理后台
Rainloop:webmail

安装
将安装包重新命名ewomail.zip
解压安装

unzip -o ewomail.zip
cd EwoMail/install
#需要输入一个邮箱域名,不需要前缀,列如下面的xxx.com
sh ./start.sh xxx.com

安装成功会出现“Complete installation”
剩下只需要配置域名DNS即可使用了

配置域名DNS
%title插图%num

请参考上图DNS配置
将mail.ewomail.cn 改成你的安装的域名,例如(mail.安装的域名)
红色部分请改为你的服务器IP

管理后台

%title插图%num

管理后台用于管理你的邮箱和域名,可以无限添加域名和邮箱。
地址:http://IP:8010 (IP是你的服务器公网IP)

WebMail

%title插图%num

WebMail用于登录邮箱,发送与接收邮件。

结尾

经过以上几个步骤,基本已经完成搭建一个邮件服务器了,使用过程中如果有什么问题可以留言给我

服务器完美安装可道云,打造私有网盘

可道云是一个网盘工具,可以方便服务器与主机之间的文件传输。
安装环境:ubuntu 18.04 64bit
具体步骤
1、apt-get install apache2 # 安装Apache

2、apt-get install mysql-server mysql-client # 安装MySQL

3、apt-get install phpmyadmin # 安装phpmyadmin
注:有些可能不是*新版本。根据需要安装不同版本即可。

4、获取可道云

cd /var/www/html # Apache的网站目录下操作 默认配置环境的根目录是/var/www/html
wget http://static.kodcloud.com/update/download/kodexplorer4.25.zip # 下载可道云
unzip kodexplorer4.25.zip # 解压可道云
chmod -R 777 /var/www/html/ # 基于目录权限。如果想给其他文件夹访问权限,修改目录即可。
5、在浏览器中输入服务器IP地址,尝试登录可道云,发现出现问题
%title插图%num

6、解决GD库支持问题

sudo apt-get install php5-gd # 安装GD库
sudo /etc/init.d/apache2 restart # 重启apache服务
7、重新尝试登录

%title插图%num