Пример #1
0
def execute():
    line = ShapeFactory.create(ShapeType.LINE, 0, 0, 100, 200)
    rectangle = ShapeFactory.create(ShapeType.RECTANGLE, 10, 20, 30, 40)
    oval = ShapeFactory.create(ShapeType.OVAL, 100, 200, 300, 400)

    shapes = [line, rectangle, oval]
    for shape in shapes:
        shape.draw()
Пример #2
0
        def reformat_shape(s):
            '''
            将json格式中的内容转换为shape
            包括label、构成区域的所有点以及区域形状
            '''
            result = []

            # shapes = ((
            #     s['label'],
            #     s['points'],
            #     s.get('shape_type', 'polygon'))
            # for s in data['shapes']

            for shape in s:
                label = shape[0]
                name = shape[2]
                points = []
                for point in shape[1]:
                    x, y = point
                    points.append(QPointF(x, y))
                result.append(
                    ShapeFactory.genShape(name=name,
                                          label=label,
                                          points=points))

            return result
Пример #3
0
    def __init__(self, autoplay=False, save_rnn=False):
        ''' 决定行列数 ...
        '''
        self.rows_ = 20
        self.cols_ = 14
        self.factory = ShapeFactory()
        self.over_ = False
        self.state_ = 0
        self.shapes_ = 0
        self.curr_level_ = START_LEVEL
        self.eliminate_rows_ = 10 * self.curr_level_  # :)
        self.level_intervals_ = [
            1.0, 0.75, 0.6, 0.55, 0.5, 0.475, 0.450, 0.4, 0.375, 0.35, 0.33,
            0.3, 0.25, 0.2, 0.1
        ]  #
        self.data_ = [[0 for i in range(self.cols_)]
                      for i in range(self.rows_)]  # 一个二维数组标识当前数据 ...
        self.shape_ = None
        self.quit_ = False
        self.pause_ = False
        self.show_ = Show()
        self.auto_ = autoplay
        self.reset_cnt_ = 0
        if self.auto_:
            self.blurred_ = False
            if not AUTO_USING_RULE:
                from inference_rnn import Inference
                self.pred_ = Inference(epoch=EPOCH)  # 加载 rnn 模型进行预测 ...

        #准备记录键盘操作序列
        self.save_rnn_ = save_rnn
        if save_rnn:
            try:
                import os
                os.mkdir(curr_path + '/rnn_{}'.format(MODE))
            except:
                pass
            self.rnn_fname_prefix = curr_path + '/rnn_{}'.format(
                MODE) + '/' + time.strftime('%j%H%M-', time.localtime())
            self.rnn_ops_ = []  # 记录操作序列,[(np(data_), key), ... ]
            # np(data_) 为当前 self.data_ 的np数组,key 为对当前的操作

        if MODE == 'online' and autoplay:
            self.prepare_online_train()
Пример #4
0
    def paintEvent(self, paintEvent):
        '''
        重载 QWidget中的 paintEvent 方法
        用来显示 pixmap 图片
        '''
        if not self.pixmap:
            return super(Canvas, self).paintEvent(paintEvent)

        # 使用 begin 方法开始绘制
        p = self._painter
        p.begin(self)

        # 设置渲染时的属性
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        # 对坐标系进行放缩
        p.scale(self._scale, self._scale)

        # 对坐标系进行平移
        # 在后续的渲染中将以这个平移后的坐标系为基准
        p.translate(self.getCenter())

        # 在上一步中已经平移过坐标系
        # 因此这里直接从(0, 0)开始画
        p.drawPixmap(0, 0, self.pixmap)

        # 设置图形的显示比例与图像的放缩比例一致
        ShapeFactory.setScale(self.scale)
        for shape in self.shapes:
            shape.hovered = shape == self.hoverShape
            shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)

        # 结束绘制
        p.end()
Пример #5
0
 def undoLastLine(self):
     assert self.shapes
     self.current = self.shapes.pop()
     self.current.open()
     if not self.line:
         self.line = ShapeFactory.genShape(self.createMode,
                                           label=None,
                                           points=[])
     if self.createMode == 'polygon':
         self.line.points = [self.current[-1], self.current[0]]
     elif self.createMode == 'rectangle' or self.createMode == 'circle':
         self.current.points = self.current.points[0:1]
     self.paintingShape.emit(True)
Пример #6
0
    def handleDrawing(self, pos):
        if self.current:
            if self.createMode == 'polygon':
                self.current.addPoint(self.line[1])
                self.line[0] = self.current[-1]
                if self.current.isClosed():
                    self.finalise()
            elif self.createMode in ['rectangle', 'circle']:
                assert len(self.current.points) == 1
                self.current.points = self.line.points
                self.finalise()

        # 开始一个新图形
        elif not outOfPixmap(self.pixmap, pos):
            self.current = ShapeFactory.genShape(self.createMode,
                                                 label=None,
                                                 points=[])
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.paintingShape.emit(True)
            self.update()
Пример #7
0
    def mouseMoveEvent(self, QMouseEvent):
        if not self.pixmap:
            return

        pos = self.transformPos(QMouseEvent.pos())
        self.restoreCursor()

        if self.drawing:
            if not self.line:
                self.line = ShapeFactory.genShape(self.createMode,
                                                  label=None,
                                                  points=[])
            self.overrideCursor(cursorType.CURSOR_DRAW.value)

            if not self.current:
                return

            # 如果鼠标位置超出了图片范围
            # 则计算当前操作点的正确位置
            if outOfPixmap(self.pixmap, pos):
                pos = intersectionPoint(self.pixmap, self.current[-1], pos)

            # 如果最后两点足够接近
            elif len(self.current) > 1 \
                    and self.createMode == 'polygon' \
                    and closeEnough(self.current[0], pos, 10.0 / self.scale):
                # 那么形成闭合多边形
                pos = self.current[0]
                self.overrideCursor(cursorType.CURSOR_POINT.value)
                # 突出显示最后一个顶点
                self.current.highlightVertex(0, highlightMode.NEAT_VERTEX)

            # 否则 还未形成闭合多边形
            # 那么点的显示位置为鼠标位置
            if self.createMode == 'polygon':
                self.line[0] = self.current[-1]
                self.line[1] = pos

            # 当创建矩形或圆时
            # 默认认为它是闭合状态
            elif self.createMode == 'rectangle' or self.createMode == 'circle':
                self.line.points = [self.current[0], pos]
                self.line.close()
            self.repaint()
            self.current.highlightClear()

        elif self.editing:
            # self.hasMovedShape = False

            # 鼠标处于拖动状态
            if Qt.LeftButton & QMouseEvent.buttons():

                # 如果选中的是一个顶点
                if self.selectedVertex:
                    self.overrideCursor(cursorType.CURSOR_MOVE.value)
                    index, shape = self.selectedVertex, self.hoverShape  # 正在处理的顶点序号 正在处理的图形
                    point = shape[index]  # 找到对应点
                    if outOfPixmap(self.pixmap, pos):
                        pos = intersectionPoint(self.pixmap, point, pos)
                    shape.moveVertexBy(index, pos - point)  # 调整该顶点位置
                    self.repaint()
                    self.hasMovedShape = True

                # 如果选中的是一个图形
                elif self.selectedShapes and self.prevPoint:
                    self.overrideCursor(cursorType.CURSOR_MOVE.value)
                    if outOfPixmap(self.pixmap, pos):
                        return

                    # 计算点的正确位置
                    o1 = pos + self.offsets[0]
                    if outOfPixmap(self.pixmap, o1):
                        pos -= QPoint(min(0, o1.x()), min(0, o1.y()))
                    o2 = pos + self.offsets[1]
                    if outOfPixmap(self.pixmap, o2):
                        pos += QPoint(min(0,
                                          self.pixmap.width() - o2.x()),
                                      min(0,
                                          self.pixmap.height() - o2.y()))

                    for shape in self.selectedShapes:
                        shape.moveBy(pos - self.prevPoint)
                    self.prevPoint = pos
                    self.repaint()
                    self.hasMovedShape = True

            # 鼠标悬浮
            else:
                for shape in reversed([s for s in self.shapes]):
                    # 寻找与当前鼠标位置最接近的顶点
                    index = nearestVertex(shape, pos, self.scale)
                    if index:
                        if self.selectedVertex:
                            self.hoverShape.highlightClear()
                        self.selectedVertex = index
                        self.hoverShape = shape
                        shape.highlightVertex(index, highlightMode.MOVE_VERTEX)
                        self.overrideCursor(cursorType.CURSOR_POINT.value)
                        self.setToolTip("Click & drag to move point")
                        self.update()

                        # 找到一个满足条件的标注区域后
                        # 直接退出
                        break
                    elif shape.containsPoint(pos):
                        if self.selectedVertex:
                            self.hoverShape.highlightClear()
                        self.selectedVertex = None
                        self.hoverShape = shape
                        self.setToolTip("Click & drag to move shape '%s'" %
                                        shape.label)
                        self.overrideCursor(cursorType.CURSOR_GRAB.value)
                        self.update()
                        break
                else:  # 鼠标在空白区域
                    if self.hoverShape:
                        # 清除之前区域的高亮显示
                        self.hoverShape.highlightClear()
                        self.update()
                    self.selectedVertex = self.hoverShape = None
Пример #8
0
class Game:
    def __init__(self, autoplay=False, save_rnn=False):
        ''' 决定行列数 ...
        '''
        self.rows_ = 20
        self.cols_ = 14
        self.factory = ShapeFactory()
        self.over_ = False
        self.state_ = 0
        self.shapes_ = 0
        self.curr_level_ = START_LEVEL
        self.eliminate_rows_ = 10 * self.curr_level_  # :)
        self.level_intervals_ = [
            1.0, 0.75, 0.6, 0.55, 0.5, 0.475, 0.450, 0.4, 0.375, 0.35, 0.33,
            0.3, 0.25, 0.2, 0.1
        ]  #
        self.data_ = [[0 for i in range(self.cols_)]
                      for i in range(self.rows_)]  # 一个二维数组标识当前数据 ...
        self.shape_ = None
        self.quit_ = False
        self.pause_ = False
        self.show_ = Show()
        self.auto_ = autoplay
        self.reset_cnt_ = 0
        if self.auto_:
            self.blurred_ = False
            if not AUTO_USING_RULE:
                from inference_rnn import Inference
                self.pred_ = Inference(epoch=EPOCH)  # 加载 rnn 模型进行预测 ...

        #准备记录键盘操作序列
        self.save_rnn_ = save_rnn
        if save_rnn:
            try:
                import os
                os.mkdir(curr_path + '/rnn_{}'.format(MODE))
            except:
                pass
            self.rnn_fname_prefix = curr_path + '/rnn_{}'.format(
                MODE) + '/' + time.strftime('%j%H%M-', time.localtime())
            self.rnn_ops_ = []  # 记录操作序列,[(np(data_), key), ... ]
            # np(data_) 为当前 self.data_ 的np数组,key 为对当前的操作

        if MODE == 'online' and autoplay:
            self.prepare_online_train()

    def gameover(self):
        return self.over_

    def data2np(self):
        ''' 将 self.data_ 转化为 numpy 数组 '''
        rs = [np.array(r) for r in self.data_]
        return np.vstack(rs)

    def quit(self):
        return self.quit_

    def isme(self, p, poss):
        ''' 目标位置是否还是属于自己
        '''
        for pos in poss:
            if pos[0] == p[0] and pos[1] == p[1]:
                return True
        return False

    def can_move(self, poss):
        # 检查 poss 位置是否有效, 坐标必须在有效范围, 并且self.data_[pos] == 0
        for p in poss:
            if p[0] < 0 or p[1] < 0 or p[0] >= self.rows_ or p[1] >= self.cols_:
                return False
            # 自己形状内的移动不受限制
            if self.data_[p[0]][p[1]] != 0 and not self.isme(
                    p, self.shape_.poss()):
                return False
        return True

    def move(self, poss, showing=True):
        # 移动 self.shape_ 到新的位置poss
        old_poss = self.shape_.poss()

        for p in old_poss:
            self.data_[p[0]][p[1]] = 0  # 旧位置清空

        for p in poss:
            self.data_[p[0]][p[1]] = self.shape_.d()
        self.shape_.set_poss(poss)

        if showing:
            self.show_.show(self.data_)

    def wait_key(self, timeout):
        timeout = int(timeout * 1000)
        if timeout <= 0:
            timeout = 1
        if not self.auto_:
            return 0xff & self.show_.wait_key(timeout)
        elif AUTO_USING_RULE:
            return 0xff & self.show_.wait_key(10)
        else:
            return self.pred_seq()

    def pred_seq(self):
        ''' 循环预测, 直到返回6, 总是返回 32
        '''
        for i in range(MAX_SEQ_LENGTH):
            k = self.pred_.pred(self.data2np())
            if k == 6:
                return 32  # 结束

            if k == 0 or k == 1 or k == 5:
                continue

            if k == 2:
                poss = self.shape_.left()
            elif k == 3:
                poss = self.shape_.rotate()
            elif k == 4:
                poss = self.shape_.right()

            if self.can_move(poss):
                self.move(poss)

            self.show_.wait_key(500)

        print('oooooh, cannot got space, JUST do it')
        return 32

    def interact(self):
        if self.auto_:
            if AUTO_USING_RULE:
                self.interact0(0.01)
            else:
                self.interact0(0.5, once=True)
        else:
            self.interact0(self.level_intervals_[self.curr_level_])

    def interact0(self, level_time, once=False):
        t0 = time.time()
        t = t0
        n = 2
        while n > 0:
            n -= 1
            elapse = t - t0  # 已经等待的时间
            if elapse >= level_time:
                break
            wait = level_time - elapse
            key = self.wait_key(wait)
            keymaps = {
                LEFT: self.shape_.left,
                UP: self.shape_.rotate,
                RIGHT: self.shape_.right,
                DOWN: self.shape_.down,
            }
            if key in keymaps:
                poss = keymaps[key]()
                if self.can_move(poss):
                    self.move(poss)
            elif key == 32:
                poss = self.shape_.down()
                while self.can_move(poss):
                    self.move(poss)
                    poss = self.shape_.down()
                break
            elif key == 27:
                self.quit_ = True
                break
            elif key == ord('P') or key == ord('p'):
                self.pause_ = not self.pause_
            elif key == ord('B') or key == ord('b'):
                self.bomb()
            t = time.time()
            if once:
                break

    def bomb(self):
        ''' 直接消掉地下十行
        '''
        del self.data_[self.rows_ - 10:self.rows_]
        for i in range(10):
            self.data_.insert(0, [0 for i in range(self.cols_)])

    def create_new_shape(self):
        ''' 如果空间不够,返回 False
        '''
        self.shape_ = self.factory.create(int(self.cols_ / 2 - 1))
        poss = self.shape_.poss()
        # poss 占用的空间不能有非 0
        empty = True
        for p in poss:
            if self.data_[p[0]][p[1]] != 0:
                empty = False
                break
        if not empty:
            return False
        self.move(poss)
        self.shapes_ += 1
        if self.auto_:
            if not AUTO_USING_RULE:
                self.pred_.reset()
        return True

    def step(self):
        ''' 每个 step 对应 self.level_intervals_[self.curr_level_] 的等待时间, 
            这段时间内响应键盘事件, 更新显示界面
        '''
        self.show_.show_info("L:{},rs:{},i:{},ss:{},{}".format(
            self.curr_level_, self.eliminate_rows_,
            self.level_intervals_[self.curr_level_], self.shapes_,
            'pause' if self.pause_ else 'playing'))
        if self.shape_ is None:
            # 新的形状 ..
            if not self.create_new_shape():
                if self.save_rnn_:
                    # 清空重来 ...
                    self.data_ = [[0 for i in range(self.cols_)]
                                  for i in range(self.rows_)
                                  ]  # 一个二维数组标识当前数据 ...
                    self.shape_ = None
                    self.reset_cnt_ += 1
                    print('GAME OVER, RESET ALL {} times'.format(
                        self.reset_cnt_))
                    self.eliminate_rows_ = 0
                else:
                    self.over_ = True
                return

            if self.auto_ and AUTO_USING_RULE:
                # 此时找到最佳位置
                self.save_all()
                pos = self.select_best_pos()
                keys = self.get_key_seq(self.shape_.poss(), pos)
                keys.append(32)  # 追加一个空格...
                self.restore_all()
                self.save_image_keys(keys)
                self.move(pos)

        # 当自动保存样本时,如果最高行超过10行,则强制清空所有
        # if self.save_rnn_:
        #     # if self.auto_get_empty_lines(self.data2np()) < self.rows_ / 2:
        #     # 为了生成初始状态的样本,总是尽快的清空,当消除行后,立即清空,然后从头再来
        #     if self.eliminate_rows_ > 1:
        #         self.data_ = [[0 for i in range(self.cols_)] for i in range(self.rows_)]  # 一个二维数组标识当前数据 ...
        #         self.shape_ = None
        #         self.reset_cnt_ += 1
        #         print('RESET ALL {} times'.format(self.reset_cnt_))
        #         self.eliminate_rows_ = 0
        #         return

        # 处理键盘事件
        self.interact()
        if self.quit_:
            return
        # 超时下落
        if not self.pause_:
            poss = self.shape_.down()
            if self.can_move(poss):
                self.move(poss)
            else:
                # 当前形状已经无法移动, 准备生成下一个 ...
                self.shape_ = None
                # 清除满行
                self.eliminate_rows_ += self.clear_rows()
                self.curr_level_ = self.eliminate_rows_ // 10  # 每十行增加一级 ...
                if self.curr_level_ >= len(self.level_intervals_):
                    self.curr_level_ = len(self.level_intervals_) - 1

    def clear_rows(self):
        ''' 消除满行, 操作 self.data_, 从后往前, 删除满行, 并且从头插入新的空行
            返回删除的满行数
        '''
        full_row_ids = []
        for i, r in enumerate(self.data_):
            f = True
            for c in r:
                if c == 0:
                    f = False
                    break
            if f:
                full_row_ids.insert(
                    0, i)  # full_row_ids 保存 self.data_ 需要删除的行的序号的倒序
        for i in full_row_ids:
            del self.data_[i]
        for i in range(len(full_row_ids)):
            self.data_.insert(0, [0 for c in range(self.cols_)])  # 插入新空行
        return len(full_row_ids)

    def wait_quit(self):
        # 当 gameover 后, 等待 q/esc 结束
        self.show_.show_gameover()
        while True:
            key = self.show_.wait_key(0)
            if key == 27:
                break

    def save_rnn_begin(self):
        ''' 对应一个新的形状产生 ...
        '''
        if self.save_rnn_:
            self.rnn_ops_ = []
            # 以后生成的不再包含 0, 方便 fix_label.py 工具识别
            # self.rnn_ops_.append((self.data2np(), 0))

    def save_rnn_end(self):
        ''' 当一个形状无法活动时 ...
            将 rnn_ops_ 中的操作合并
        '''
        if self.save_rnn_:
            #self.rnn_ops_.append((self.data2np(), 1))
            if len(self.rnn_ops_) >= MAX_SEQ_LENGTH:
                print('WARN: the rnn seq tooooo long !!!')
                return

            if MODE == 'online':
                if self.blurred_:
                    self.blurred_ = False  # 不要学习随机出错的 ..
                    print('... skip')
                else:
                    self.online_train_step(self.rnn_ops_)
            else:
                fname = '{}{}.npz'.format(self.rnn_fname_prefix, self.shapes_)
                ds = [r[0] for r in self.rnn_ops_]
                ks = [r[1] for r in self.rnn_ops_]
                np.savez_compressed(fname,
                                    imgs=np.stack(ds),
                                    keys=np.array(ks))
                print('save rnn sample: {}'.format(fname))

    def save_rnn_key(self, key):
        ''' 保存按键,仅仅四个,上下左右,特别的 "自由下落" 和 "快速下落" 都映射为 down 了,
            加上 "开始", "结束":
                0: 开始
                1: 结束
                2: left
                3: up
                4: right
                5: down
                6: 空格,快速下降
        '''
        if self.save_rnn_:
            keys = {LEFT: 2, UP: 3, RIGHT: 4, DOWN: 5, 32: 6}
            assert (key in keys)
            self.rnn_ops_.append((self.data2np(), keys[key]))

    #########################################################################################
    def get_key_seq(self, curr_poss, target_poss):
        ''' 找出, 从 curr_poss 到 target_poss 需要的按键序列
                通过 curr_poss[0], [1] 的方向,和 target_poss[0], [1] 的方向计算需要旋转的次数
                然后通过 target_poss[0] 与 curr_poss[0] 的位移, 计算平移的次数
                这里不会有下降的 ...
        '''
        key_seq = []

        poss0 = self.shape_.poss()

        curr_v = (curr_poss[1][0] - curr_poss[0][0],
                  curr_poss[1][1] - curr_poss[0][1])
        tar_v = (target_poss[1][0] - target_poss[0][0],
                 target_poss[1][1] - target_poss[0][1])

        for i in range(4):
            if curr_v[0] == tar_v[0] and curr_v[1] == tar_v[1]:
                break
            key_seq.append(UP)  # 旋转按键
            poss = self.shape_.rotate()
            self.move(poss)
            curr_v = (poss[1][0] - poss[0][0], poss[1][1] - poss[0][1])

        curr_poss = self.shape_.poss()
        dx = target_poss[0][1] - curr_poss[0][1]
        if dx > 0:
            for i in range(dx):
                key_seq.append(RIGHT)
        elif dx < 0:
            for i in range(-dx):
                key_seq.append(LEFT)

        self.shape_.set_poss(poss0)
        return key_seq

    def save_image_keys(self, keys_seq):
        ''' 根据 keys 生成 images, 保存, 用于生成训练样本
        '''
        keys = {LEFT: 2, UP: 3, RIGHT: 4, DOWN: 5, 32: 6}
        self.save_rnn_begin()
        for k in keys_seq:
            self.save_rnn_key(k)  # 保存当前图像 + 对应的按键

            # 根据按键, 进行移动
            if k == LEFT:
                poss = self.shape_.left()
                self.move(poss, showing=False)
            elif k == UP:
                poss = self.shape_.rotate()
                self.move(poss, showing=False)
            elif k == RIGHT:
                poss = self.shape_.right()
                self.move(poss, showing=False)
            elif k == DOWN:
                assert (k != DOWN)
        self.save_rnn_end()

    def save_all(self):
        ''' 保存当前 self.data_, self.shape_, ...
        '''
        self.saved_data_ = copy.deepcopy(self.data_)
        self.saved_shape_ = copy.deepcopy(self.shape_)

    def restore_all(self):
        self.data_ = copy.deepcopy(self.saved_data_)
        self.shape_ = copy.deepcopy(self.saved_shape_)

    def select_best_pos(self):
        ''' XXX: 为 self.shape_ 选择一个最佳目标位置, 下落后, 保证:
                1. 无"空洞"
                2. 多消除行
                3. 总高度最低
                4. 每列最高点的方差应该尽量小, 就是说, 应该让每列高度更平均

            FIXME: 采用最暴力的穷举, 从左到右, 旋转三次, 看每次下落的结果, 评估上面的指标
        '''
        self.save_all()

        if self.shape_.d() == 2:
            aa = 0
        all_start_poss = self.auto_get_all_start_poss()
        all_down_results = [
            self.auto_get_down_result(poss) for poss in all_start_poss
        ]
        assert (len(all_down_results) == len(all_start_poss))

        self.restore_all()

        # 使用权重吧, holes * 3.0 + (self.rows_ - elims) * 2.0 + (self.rows_ - els) * 1.0 + mse * .01
        rs = [
            h * 15.0 - e * 1.0 + n * 1.5 + m * 5.
            for h, e, n, m in all_down_results
        ]
        rs = np.array(rs)
        idx = np.argmin(rs)
        idx_min = np.where(rs == rs[idx])[0]
        idx = random.randint(0, len(idx_min) - 1)
        best_idx = idx_min[idx].item()

        # print('==> v={}, h:{}, elims:{}, els:{}, mse:{}'.format(
        #         rs[idx],
        #         all_down_results[best_idx][0],
        #         all_down_results[best_idx][1], all_down_results[best_idx][2],
        #         all_down_results[best_idx][3]))

        # XXX: 为了制造点混乱,需要偶尔随机选择一次
        # if self.shapes_ % 11 == 9:
        #     x = random.randint(0, len(all_start_poss)-1)
        #     print('blurring from {} to {}'.format(best_idx, x))
        #     best_idx = x
        #     self.blurred_ = False # 不要学习这个 ..
        return all_start_poss[best_idx]

    def auto_get_all_start_poss(self):
        ''' 生成当前水平移动 + 旋转的所有位置, 返回:
                1. 目标位置
        '''
        all_poss = []

        poss0 = self.shape_.poss()
        for i in range(4):
            ''' 四次旋转后, 每次移动到最左侧, 然后依次右移, 记录允许的启动位置 '''
            self.move(poss0)

            for n in range(i):
                poss = self.shape_.rotate()
                if not self.can_move(poss):
                    continue
                self.move(poss)

            # 移动到最左侧
            poss = self.shape_.left()
            while self.can_move(poss):
                self.move(poss, showing=False)
                poss = self.shape_.left()

            # 依次右移, 记录每个位置
            poss = self.shape_.poss()  # XXX: 这样只是为了方便 ...
            while self.can_move(poss):
                self.move(poss, showing=False)

                if poss not in all_poss:
                    all_poss.append(poss)

                poss = self.shape_.right()

        return all_poss

    def auto_get_down_result(self, poss):
        ''' 从 poss 作为其实点, 下降直到无法下降后, 返回 (holes, elims, emls)   (空洞数, 消除行数, 完整空行数)
        '''
        assert (self.can_move(poss))
        self.move(poss)

        poss = self.shape_.down()
        while self.can_move(poss):
            self.move(poss, showing=False)
            poss = self.shape_.down()
        data = self.data2np()

        return (self.auto_get_holes(data), self.auto_get_elims(data),
                self.auto_get_neighbor_delta(data), self.auto_get_mse(data))

    def auto_get_holes(self, d):
        ''' 从 self.data_ 最低开始, 水平找到 0 后, 向上找, 如果右非 0 则认为是空洞
        '''
        holes = 0
        cs = np.split(d, self.cols_, axis=1)  # cs 为每列
        for c in cs:
            found = False
            n = 0
            c = c.reshape((self.rows_, )).tolist()
            for i in range(len(c)):
                if c[i]:
                    found = True
                    continue
                if found and c[i] == 0:
                    n += 1
            holes += n
        return holes

    def auto_get_mse(self, d):
        ''' 每列从低向上看, 根据高度计算 mse, 要尽量保证 mse 比较小
        '''
        cs = np.split(d, self.cols_, axis=1)
        cs = [c.reshape((self.rows_, )) for c in cs]
        hs = [np.nonzero(c)[0] for c in cs]
        hs = [self.rows_ - h[0].item() if len(h) > 0 else 0 for h in hs]
        hs = np.array(hs)

        mse = np.sqrt(np.sum(np.power(hs - np.mean(hs), 2)))
        return mse.item()

    def auto_get_elims(self, d):
        ''' 检查消除的行数
        '''
        rows = 0
        for r in d:
            ds = dict(zip(*np.unique(r, return_counts=True)))
            if 0 not in ds:
                rows += 1
        return rows

    def auto_get_neighbor_delta(self, d):
        ''' 获取相邻高度差的和,就是说,要尽量不出现相邻海拔差大的情况,这种情况往往只能依赖“竖棍”才能消除
        '''
        cs = np.split(d, self.cols_, axis=1)
        cs = [c.reshape((self.rows_, )) for c in cs]
        hs = [np.nonzero(c)[0] for c in cs]
        hs = [self.rows_ - h[0].item() if len(h) > 0 else 0 for h in hs]

        s = 0
        import math
        for i in range(len(hs) - 1):
            s += math.fabs(hs[i + 1] - hs[i])

        return s

    def auto_get_empty_lines(self, d):
        ''' 返回完整的空行数
        '''
        assert (d.shape == (self.rows_, self.cols_))
        emls = np.sum(d, axis=1)
        cs = dict(zip(*np.unique(emls, return_counts=True)))
        if 0 in cs:
            return cs[0]
        return 0

    ############################################################
    def prepare_online_train(self):
        ''' 准备在线训练模型,总是加载 online-0000.params
        '''
        net, stack = build_net()
        self.mod_ = mx.mod.Module(net,
                                  data_names=('data', ),
                                  label_names=('label', ))
        self.mod_.bind(data_shapes=(('data', (1, MAX_SEQ_LENGTH, 20, 14)), ),
                       label_shapes=(('label', (1, MAX_SEQ_LENGTH)), ),
                       for_training=True)  # batch_size = 1

        if osp.isfile(curr_path + '/online-0000.params'):
            _, args, auxs = mx.rnn.load_rnn_checkpoint(stack,
                                                       curr_path + '/online',
                                                       0)
            self.mod_.set_params(args, auxs)
            print('====resume OK')
        else:
            init = mx.init.Xavier(factor_type='in', magnitude=2.34)
            self.mod_.init_params(init)

        self.mod_.init_optimizer(optimizer='sgd',
                                 optimizer_params=(('learning_rate', 0.0001),
                                                   ('momentum', 0.9)))
        self.online_train_cnt_ = 0
        self.online_train_stack_ = stack
        self.online_train_net_ = net

    def to_show_array(self, arr):
        ''' 将 arr 转化为 list,并且从后删除所有 0 '''
        if isinstance(arr, np.ndarray):
            arr = arr.reshape(-1).astype(np.int32).tolist()
        # while len(arr) > 0:
        #     if arr[-1] == 0:
        #         del arr[-1]
        #     else:
        #         break
        # if not arr:
        #     arr.append(0)
        return arr

    def online_train_step(self, rnn_ops):
        # rnn_ops 为记录的当前按键序列以及对应的 img
        assert (self.mod_)

        imgs = []
        keys = []
        for op in rnn_ops:
            imgs.append(op[0].reshape(
                (1, self.rows_, self.cols_)).astype(np.float32))
            keys.append(op[1])

        imgs += [np.zeros((1, self.rows_, self.cols_), dtype=np.float32)
                 ] * (MAX_SEQ_LENGTH - len(keys))
        imgs = np.vstack(imgs).reshape(
            (1, MAX_SEQ_LENGTH, self.rows_, self.cols_))
        labels = np.array(keys + [0] * (MAX_SEQ_LENGTH - len(keys))).reshape(
            (1, MAX_SEQ_LENGTH))

        batch = Batch([mx.nd.array(imgs)], ['data'], [mx.nd.array(labels)],
                      ['label'],
                      fname=None)
        self.mod_.forward_backward(batch)
        self.mod_.update()

        self.online_train_cnt_ += 1

        if self.online_train_cnt_ % 10 == 9:
            # 打印训练预测输出
            outss = self.mod_.get_outputs()
            outs = [np.argmax(o.asnumpy()) for o in outss]
            label = batch.label[0].asnumpy()
            pred = outs
            print('======= {} ======'.format(self.online_train_cnt_ + 1))
            print('    label:{}'.format(self.to_show_array(label)))
            print('    pred: {}'.format(self.to_show_array(pred)))

        if self.online_train_cnt_ % 100 == 99:
            # 保存 checkpoint
            print('saveing checkpoint')
            args, auxs = self.mod_.get_params()
            mx.rnn.save_rnn_checkpoint(self.online_train_stack_,
                                       curr_path + '/online', 0,
                                       self.online_train_net_, args, auxs)