手动摆放七巧板
游戏介绍
手动摆放七巧板的程序,可以拼成各种图案,也可以再添加一套七巧板一起拼。
操作方法:
- 鼠标左键拖动图形;
- 鼠标右键使七巧板顺时针旋转 45 度;
- 按住左键时按空格可以让四边形轴对称翻转。
游戏截图
完整游戏源代码
完整的 VC 项目在 gitee 上:https://gitee.com/ProtagonistMan/tangram
编译环境
VisualStudio 2019,EasyX_20200902
文件描述
Point.h
用于存三角形和四边形的点的值,用整型限制很多,例如求一个点相对于一个圆心旋转一定角度后的位置,我的做法是用三角函数来求,就是半径长度乘以三角函数值得到 x,y 的坐标,最终结果一定是小数,如果这个值不保存下来,而是强转为整型,那么这个点的位置就不准确,下次旋转时得到的值也就不准确,还有,对于几何图形的中心点,它通常不会是一个能用整数表示的点,如果这个用于旋转的中心点无法精确地得到,那么旋转功能也就无从谈起,这个点的类都用 double 型来存可以省去很多麻烦。
Line.h
对于直线,三角形和四边形需要的直线都是有两个端点的直线,根据两点式 (y-y1)/(x-x1)=(y-y2)/(x-x2) 转化后得到 (y2-y1)x+(x1-x2)y+x2y1-x1y2=0,也就是一般式方程 Ax+By+C=0 中的 A、B、C 三个参数都能得到,根据点到直线的距离的方程 |Ax0+By0+C|/sqrt(A^2+B^2) 可以求点到直线的距离,联立 A1x+B1y+C1=0 和 A2x+B2y+C2=0 可以求得两直线交点,为 ( (B2C1-B1C2)/(A2B1-A1B2),(A1C2-A2C1)/(A2B1-A1B2) ),有了这些可以求四边形中心点,也就是对角线的交点。
也可以判断一个点是否在三角形内,判断方法为,让这个点 O 与三角形 ABC 三个点相连,OA 与 BC 交点记为 A_N 如果 A_N < OA 那么 O 点在三角形外,同理 B_N < OB 或 C_N < OC 那么点在三角形外,如果 A_N >= OA && B_N >= OB && C_N >= OC 则点 O 在三角形 ABC 内。对于四边形 ABCD,则判断点 O 是否在三角形 ABC 或三角形 ACD 内就能判断点是否在四边形 ABCD 内。这种判断方法需要求两直线交点的函数。具体判断时只要 A_N-OA > -0.5 就能通过。
Line 类中还有获取 Line 上点的函数,其原理为:
若 B 不为 0,y = -(A / B) x - C / B,设两端点为 p0,p1,for (int i = min(p0.x, p1.x); i < max(p0.x, p1.x); i++) 然后 push_back(Point(i, y)) 进去,
若 B = 0,x = -C / A,设两端点为 p0,p1,for(int i = min(p0.y, p1.y); i < max(p0.y, p1.y); i++) 然后 push_back(Point(-C / A, i)) 进去
需要注意,由于 EasyX 的每个像素点的位置都是整型,而直线两端点的值都是浮点型,我们可以把两端点的值先抽出来,得到能用整型表示的两端点的值,例如对于 A(12.5, 22.5) B(22.5, 32.5),我们得到的两端点的值为 A'=(13, 23) B'=(22, 32),可是这两个端点并不好求,通常只用求最左端的端点,这样做的好处是得到的每一个点都只用在 y 上进行四舍五入操作,得到的每一个点在 EasyX 上表示更精确,具体的函数实现可以看 getLinePoint 函数。
源.cpp
七巧板中只有两类图形,三角形和四边形,我让三角形 (Triangle) 和四边形 (Quadrangle) 继承自图形类 (Graph),然后用 Graph 来初始化每个图形,操作 Graph 类就可以了,不用分辨是哪一个图形。
这个程序需要两个功能:
1、判断点中的位置是否在某个图形内,这里我用链表 list 来存七个图形,只用遍历 list 中的图形,调用 Graph 中声明的,Triangle,Quadrangle 中重写的 ifInSide(Point num) 函数就能判断点是否在某个图形里面,用 list 的初衷是想设置吞噬触摸,就是没点中一个图形就把该图形放到链表的顶端,遍历时一旦首次点中就直接退出,只是 list 模板类里不包含这个内容,所以算是失策,重新写一个链表又有点麻烦,如果有闲工夫的话可以这么一改。
2、点中了某个图形并移动时让该图形区域变回原来的颜色,这里我走了不少弯路。首先给图形上色,最简单的方法是调用 floodfill 函数,但是原来的点就无法获得了,恕我水平有限无法十分灵活地用这个函数,所以我给图形上色的方法用了相当原始的遍历矩形区域的所有点,例如对于三角形 A、B、C 最左端为 min(min(A.x, B.x), C.x) ,最右端为 max(max(A.x, B.x), C.x),最顶端为 min(min(A.y, B.y), C.y) ,最底端为 max(max(A.y, B.y), C.y),从上到下从左到右遍历一遍,每个点都判断一下是否在三角形 ABC 内,在就涂色即 putpixel。但是不要想着用 getpixel 得到每一个点原先的颜色并存起来,因为一旦两个图形重叠在一起,底下那个先抽走,顶上那个还会留着底下那个的颜色,出现的效果就会很难看。至于用 cleardevice 来搞你是不知道有多闪,我是一秒钟贴 10 次图,cleardevice 一秒钟贴一次图都很闪了。最终我的解决办法是用一个三维数组 vector<KeyValue<COLORREF, int>> PUBLICORIGINAL[SIDELENGTH * 5][SIDELENGTH * 5]; 来存这一张图每一层的颜色。
SIDELIGHT 是边长,七巧板最大那个三角形斜边的长度,边长乘以五是七巧板每个图形里最长的线段的长度的和,这个值比 SIDELIGHT * 5 略小,所以我让图形窗口的大小为 SIDELIGHT * 5 × SIDELIGHT * 5。
最开始在 PUBLICORIGINAL 里存入窗口中每个点的颜色,然后给每个图形上色时,在对应的 PUBLICORIGINAL[y][x] 里插入该图形的颜色,移除该图形时就在相对应的点那里找到这个颜色,erase 掉,然后 putpixel(PUBLICORIGINAL[y][x].back())。这是我想出来最快的方法了,也许还有更好的方法,但我还不知道。这里用键值对来表示每一层的颜色,每个图形都有一个 ID,这个颜色是哪个图形的查一下 ID 就行了,不然要是两个图形颜色一样就会出一点问题。
不过以前写的代码有点烂,没有用双缓冲内存也挺浪费,另一个的代码老是老了点,但是应该比我的好。