用Racket做一个拼图游戏——14 操作id

78 篇文章 16 订阅
28 篇文章 0 订阅

14 操作id

谈到互动操作,往往都绕不过一个话题,就是对象,而每个对象要进行区分,必然需要一个标识,对于有规律的对象,往往采用编号来做标识,这就是id(identification,身份,身份象征等)。

为了更好地识别单元格图片对象,也给每个单元格图片给定一个id,这个id与前面定义单元结构时的id一致。所有对拼图单元图片的操作均以其id为基础,因此准备一些函数来对其进行操作。

涉及id的操作有:

  • 之前已经介绍过的:根据id分割单元格图片(split-picture-by-id id);根据id创建单元格(make-cell id);根据id绘制单元格(draw-cell id dc);根据id交换列表中的两个单元格(swap-cell id/target id/source);将id转换为行列号(id->r&c id);将行列号转换为id值(r&c->id row col)。

  • 接下来要介绍的:根据坐标位置获取id,根据拖动的坐标位置获取id,在指定坐标位置绘制指定id单元格、在指定id单元格位置绘制指定id单元格内容、绘制id单元格外框、取得当前单元格id、设置当前单元格id、判断指定的id是否有效、绘制焦点单元格、绘制失焦单元格。

14.1 根据坐标位置获取id

之前介绍过,可以通过行列号转换为id。而通过坐标位置求值行列号是比较容易的,只要判断出坐标值在哪一个行列单元格的x、y最大最小值之间就可以了。如下:

;根据坐标位置判断行列号(返回row、col):
(define (x&y->r&c x y)
  (let* ([gh (+ height/cell gap)]
         [gw (+ width/cell gap)]
         [row (quotient y gh)]
         [col (quotient x gw)]
         [max-x/gap (- (* (+ col 1) gw) gap)]
         [min-x/gap (- max-x/gap width/cell)]
         [max-y/gap (- (* (+ row 1) gh) gap)]
         [min-y/gap (- max-y/gap height/cell)])
    (if (and (and (>= x min-x/gap)
                  (< x max-x/gap))
             (and (>= y min-y/gap)
                  (< y max-y/gap)))
        (values row col)
        (values -1 -1))))
;根据坐标位置获取id:
(define (x&y->id x y)
  (let-values ([(r c) (x&y->r&c x y)])
    (r&c->id r c)))

有了这个函数就比较方便了,比如用鼠标将一个拼图单元格的图片拖到另一个单元格里,需要判断拖入的目标单元格的id,这样来实现:

;根据拖动的坐标位置获取单元格id:
(define (drag-x&y->id x y)
  (x&y->id x y))
14.2 在指定坐标位置绘制指定id单元格

首先,按一般操作习惯,这个”指定坐标“应该是单元格中心位置。比如拖动一个单元格图片,鼠标光标指针停放在单元格图片中心,看起来才比较协调。

那么,在绘制该指定单元格时,需要从其左上角作为绘制的起始坐标才可以。由此,代码如下:

;在指定坐标位置绘制指定单元格:
(define (draw-cell-to-x&y x y id dc)
  (let ([x0 (- x (/ width/cell 2))]
        [y0 (- y (/ height/cell 2))]
        [cell (hash-ref cells id)])
    (send dc draw-bitmap
          (cell-bitmap cell)
          x0 y0)))

这里使用了draw-bitmap方法来绘制单元格图片。

14.3 在指定单元格位置绘制指定单元格内容

这个比较好理解,就是将指定id单元格内容绘制到指定id的目标单元格位置上。需要根据目标单元格id取得其位置信息(左上角坐标)。如下:

;在指定单元格位置绘制指定单元格内容:
(define (draw-cell-to-id/target id/t id dc)
  (let-values ([(r/t c/t) (id->r&c id/t)])
    (let ([x0/t (* c/t (+ width/cell gap))]
          [y0/t (* r/t (+ height/cell gap))]
          [cell (hash-ref cells id)])
      (send dc draw-bitmap
            (cell-bitmap cell)
            x0/t y0/t))))
14.4 绘制单元格外框

先看代码,再作解释:

;绘制单元格外框:
(define (draw-cell-outline id color dc)
  (let-values ([(r c) (id->r&c id)])
    (let ([x (* c (+ width/cell gap))]
          [y (* r (+ height/cell gap))]
          [pen/old (send dc get-pen)]
          [brush/old (send dc get-brush)])
      (send dc set-pen color 2 'solid)
      (send dc set-brush "white" 'transparent)
      (send dc draw-rectangle x y width/cell height/cell)
      (send dc set-pen pen/old)
      (send dc set-brush brush/old))))

这个函数用来在单元格周边画个外框,以使整个拼图各单元格彼此独立,产生马赛克的视觉效果。用到了笔、画刷、矩形绘图方法。整个过程就是racket/draw的基本绘图步骤:

  1. 保存原有画刷和笔;

  2. 设置笔颜色、宽度及类型;

  3. 设置画刷颜色及透明度;

  4. 调用绘图方法(这里是draw-rectangle)绘制图形;

  5. 恢复原有画刷和笔。

14.4.1 

set-pen用颜色、画笔宽度、类型设置笔对象。用get-pen取得笔对象。

笔(pen%)是一种具有颜色、宽度和样式的绘图工具,可以画图形、写文本以及位图。在单色绘图状态中,所有非白色的笔都被画成黑色。其中

  • 颜色是一个代表红-绿-蓝(RGB)三原色组合的对象(color%),加上一个不透明的 "alpha"。也可以直接给预定义的颜色名称。预定义颜色名称由color-database<%>描述,其全局的对象实例是the-color-database。

  • 笔的宽度范围在0~255之间。

  • 笔的类型列表类pen-list% 对象维护一个 pen% 对象的列表,以避免重复创建pen%对象。 笔列表中的一个 pen% 对象不能被改变,而一个全局笔列表 the-pen-list 是自动创建的。

  • 除了颜色、宽度和样式之外,笔还可以使用笔刷位图。

笔的创建方式为:

(new pen%
         [[color color]
          [width width]
          [style style]
          [cap cap]
          [join join]
          [stipple stipple]])
14.4.2 画刷

画刷(brush%)是一种具有颜色和样式的绘图工具,用于填充区域。在单色绘图状态中,所有非白色的笔刷都被绘制成黑色。一般用set-brush、get-brush设置或取得画刷对象。

画刷对象的创建方式为:

(new brush%
         [[color color]
          [style style]
          [stipple stipple]
          [gradient gradient]
          [transformation transformation]])

除了颜色和样式,画刷还可以有画刷位图;也可以有 渐变, 即 linear-gradient%(线型渐变) 或 radial-gradient%(射线渐变);还可以设置变换(transformation)。

这里为了绘制正方形,将画笔设置成了透明样式(’transparent)。

14.5 设置/取得当前单元格id

先看代码,再作解释:

;设置当前单元格id:
(define (set-current-id! id)
  (set! id/current id))
;取得当前单元格id:
(define (get-current-id)
  id/current)

当前单元格是指用户当前在操作的单元格。比如用鼠标拖动一个单元格移动,这个被拖动的单元格就是当前单元格。当前单元格是唯一的,而且需要被记住。因此在模块开始的时候定义一个标识用来记录当前单元格的id值:

(define id/current 0);当前单元格id

由于模块全局值如果不进行提供设置(provide),在模块外是不可见的,就无法直接使用。因此,定义set-current-id!和get-current-id来访问它。这是不是感觉有点像其它语言的类访问机制。

14.6 判断指定的id是否存在

由于所有单元格是使用散列表进行存储的,id值就是散列表的键值,因此,判断id是否做存在,就是检查键值是否存在。

;指定的id有效:
(define (has-id? id)
  (hash-has-key? cells id))

从这里可以看出,采用合适的方式组织数据,对简化算法是非常有利的。毕竟,”程序 = 数据 + 算法“。

14.7 绘制焦点单元格

把”焦点单元格“理解成”当前单元格“;”失焦单元格“理解成即将失去焦点的”焦点单元格“,一切就顺理成章了——都是操作id/current单元格。如下:

;绘制焦点单元格:
(define (draw-focus-cell dc)
  (draw-cell-outline id/current "red" dc))
;绘制失焦单元格:
(define (draw-lose-focus-cell dc)
  (draw-cell id/current dc))

对焦点单元格,绘制一个红色边框进行标记;对失去焦点的单元格,按通常的状态恢复单元格图片内容。

14.8 绘制空白单元格

先看代码,再作解释:

;绘制空白单元格:
(define (draw-blank-cell id dc)
  (let-values ([(row col) (id->r&c id)])
    (let ([x (* col (+ width/cell gap))]
          [y (* row (+ height/cell gap))]
          [pen/old (send dc get-pen)])
      (send dc set-pen "white" 0 'solid)
      (send dc draw-rectangle
            x y
            width/cell height/cell)
      (send dc set-pen pen/old))))

这个函数就是之前的绘制单元格图片和绘制焦点单元格的结合,完成了绘制一个白色的正方形(之前画焦点是采用透明色)。

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值