FaiChou

Python的2048之旅

03 May 2016


2014年开始了python的学习,小巧轻便却不乏功能强大使其深深的吸引着我。
偶然在网上接触到了pygame可以编写小巧的游戏,于是就通过网络搜索pygame教程,简单了解Pygame教程中的游戏开发,由此切入,模仿网络中的代码,开始了2048之旅。

首先我们需要创建Map类。
Map类的属性:

  • size
  • score
  • fc_map

Map类的方法:

  • add()
  • adjust()
  • rotate90()
  • over()
  • moveUp()
  • moveDown()
  • moveRight()
  • moveLeft()

此地图类是这些属性和方法的 logical group,这是python的OOP思想体现。对于代码中类方法的实现有很多细节需要介绍。

class


二维数组的创建

self.fc_map = [[0 for i in range(size)] for j in range(size)]  

用此种方法创建二维数组,而非暴力直接[[0]*size]*size,原因在于暴力方法是一种shadow copy,当赋值list[0][0] = 1时候,list(二维)每行的首元素都会赋值成1.


随机位置产生随机数

在4*4的二维列表中,(0, 0)(3, 3)编号0-15,问:12是几行几列?
答案是12/4 (这里是整除)行12%4列.


Python 条件运算符

以前在简书上写过关于Python条件运算符的介绍。


二维数组旋转90°

self.fc_map = map(list, zip(*self.fc_map)[::-1]  

上行代码实现了逆时针转90°。

顺时针旋转:

rotated = zip(*original[::-1])

zip()的作用简单说就是矩阵转置,不过会将二维list内维转化成tuple,需要用map(list, [tuple, tuple, tuple])转化回来。

关于zip(),还有一个打印杨辉三角的程序可以参考下。

 def pascals_triangles(n):
    x = [[1]]
    for i in range(n-1):
        x.append(map(sum, zip([0]+x[-1], x[-1]+[0])))
    return x
    

关于切片操作,参考廖雪峰的python教程.


game over

判断gameover可以先判断是否有0在map里,或者是否有两个相邻的(上下/左右)值是否一样,true的话返回false,否则返回true。


移动

只写了左移的方法adjust,上移是通过逆向旋转90°再调整,再旋转3*90°。 下移右移同之。




pygame


初始化

pygame.init()
screen = pygame.display.set_mode((PIXEL * SIZE, PIXEL * SIZE + SCORE_PIXEL))
pygame.display.set_caption("2048")
  • pygame.init()初始化
  • pygame.display.set_mode((x, y))创建一个大小为(x, y)的窗口
  • pygame.display.set_caption('title')设置窗口的标题。


Surface对象

pygame object for representing images

  • pygame.Surface.blit()将一个image画到另一个image上
  • pygame.Surface.convert()pygame.Surface.convert_alpha()转化image像素格式
  • pygame.Surface.fill()填充给Surface颜色


字体 & 事件 & 定时

  • pygame.font.Font(filename, size)创建字体
  • pygame.font.Font.render()书写内容
  • pygame.event.get()从队列中获得事件
  • pygame.event.wait()从队列中等待一个single事件
  • pygame.key.get_pressed()按下的键
  • pygame.time.Clock()创造一个追踪时间的对象
  • pygame.time.Clock().tick(12)窗口每秒更新至少12次
  • pygame.time.delay(3000)延时




代码:

# -*- coding: utf-8 -*-
#!/usr/bin/env python

import random, sys, pygame
from pygame import *

PIXEL = 150
SCORE_PIXEL = 100
SIZE = 4
FC_ZERO = -1

class Map:

    def __init__(self, size):
        self.size = size
        self.score = 0
        self.fc_map = [[0 for i in range(size)] for j in range(size)]
        self.add()
        self.add()

    def add(self):
        while True:
            p = random.randint(0, self.size*self.size - 1)
            if self.fc_map[p/self.size][p%self.size] == 0:
                x = 2 if random.randint(0, 3) > 0 else 4
                self.fc_map[p/self.size][p%self.size] = x
                self.score += x
                break

    def adjust(self):
        changed = False
        for a in self.fc_map:
            b = []
            b = a[:]
            while 0 in b:
                b.remove(0)
            if len(b)>1:
                for i in range(1, len(b)):
                    if b[i] == b[i-1]:
                        b[i-1] += b[i]
                        b[i] = FC_ZERO
                while FC_ZERO in b:
                    b.remove(FC_ZERO)
            b += [0] * (self.size-len(b))
            for i in range(self.size):
                if a[i]!=b[i]:
                    changed = True
            a[:] = b
        return changed

    def rotate90(self):
        # self.fc_map = [[self.fc_map[c][r] for c in range(self.size)] for r in reversed(range(self.size))] # bad way
        self.fc_map = map(list, zip(*self.fc_map)[::-1])

    def over(self):
        for r in range(self.size):
            for c in range(self.size):
                if self.fc_map[r][c] == 0:
                    return False
        for r in range(self.size):
            for c in range(self.size-1):
                if self.fc_map[r][c] == self.fc_map[r][c+1]:
                    return False
        for r in range(self.size-1):
            for c in range(self.size):
                if self.fc_map[r][c] == self.fc_map[r+1][c]:
                    return False
        return True

    def moveUp(self):
        self.rotate90()
        if self.adjust():
            self.add()
        self.rotate90
        self.rotate90
        self.rotate90

    def moveDown(self):
        self.rotate90()
        self.rotate90()
        self.rotate90()
        if self.adjust():
            self.add()
        self.rotate90()

    def moveRight(self):
        self.rotate90()
        self.rotate90()
        if self.adjust():
            self.add()
        self.rotate90()
        self.rotate90()

    def moveLeft(self):
        if self.adjust():
            self.add()

def show(M):
    for i in range(SIZE):
        for j in range(SIZE):
            screen.blit(block[(i+j)%2] if M.fc_map[i][j]==0 else block[2 + (i+j)%2], (j*PIXEL, i*PIXEL))
            if M.fc_map[i][j] != 0:
                map_text = map_font.render(str(M.fc_map[i][j]), True, (106, 90, 205))
                text_rect = map_text.get_rect()
                text_rect.center = (j*PIXEL + PIXEL/2, i*PIXEL + PIXEL/2)
                screen.blit(map_text, text_rect)
    screen.blit(score_block, (0, PIXEL*SIZE))
    score_text = score_font.render(("Game Over with score: " if M.over() else "Score: ") + str(M.score), True, (106, 90, 205))
    score_rect = score_text.get_rect()
    score_rect.center = (PIXEL*SIZE/2, PIXEL*SIZE+SCORE_PIXEL/2)
    screen.blit(score_text, score_rect)
    pygame.display.update()

M = Map(SIZE)

pygame.init()
screen = pygame.display.set_mode((PIXEL*SIZE, PIXEL*SIZE+SCORE_PIXEL))
pygame.display.set_caption("2048")
block = [pygame.Surface((PIXEL, PIXEL)) for i in range(4)]
block[0].fill((151, 251, 152))
block[1].fill((240, 255, 255))
block[2].fill((0, 255, 127))
block[3].fill((255, 255, 255))
score_block = pygame.Surface((PIXEL*SIZE, SCORE_PIXEL))
score_block.fill((245, 245, 245))

map_font = pygame.font.Font(None, PIXEL*2/3)
score_font = pygame.font.Font(None, SCORE_PIXEL*2/3)
clock = pygame.time.Clock()
show(M)

while not M.over():
    clock.tick(12)
    for event in pygame.event.get():
        if event.type == QUIT:
            sys.exit()

    pressed_keys = pygame.key.get_pressed()
    if pressed_keys[K_w] or pressed_keys[K_UP]:
        M.moveUp()
    elif pressed_keys[K_s] or pressed_keys[K_DOWN]:
        M.moveDown()
    elif pressed_keys[K_a] or pressed_keys[K_LEFT]:
        M.moveLeft()
    elif pressed_keys[K_d] or pressed_keys[K_RIGHT]:
        M.moveRight()
    show(M)

pygame.time.delay(2000)

comments powered by Disqus