EasyX 入门

· · 个人记录

1.0 什么是 EasyX?

“EasyX Graphics Library 是针对 Visual C++ 的免费绘图库,支持 VC6.0 ~ VC2022,简单易用,学习成本极低,应用领域广泛。目前已有许多大学将 EasyX 应用在教学当中。” (摘自 EasyX 官网。)

EasyX 是对 Windows GDI 的封装,使 Windows 下的绘图操作更加简便。

1.1 安装 EasyX

For Dev C++

这边挂个教程链接 link ,假如你要正式入坑的话不推荐用 Dev,只是体验一下是不错的。

做法就是把 .h 文件丢到 Dev 的 MinGW 文件夹里。

Visual Studio

建议下载 2022 版,然后安装时勾选 C++ 桌面开发。

然后上 easyx.cn ,右上角有个 EasyX 下载,下下来过后打开。

两个全部都点安装就行了。

新建一个控制台项目 (ConsoleApplycation),然后输入一下这段代码, Ctrl + F5 编译运行。

#include <easyx.h>  // 引用 EasyX 头文件
#include <conio.h>  // Windows 控制台输入输出头文件

int main()
{
    initgraph(640, 480);    // 创建绘图窗口,大小为 640x480 像素
    circle(200, 200, 100);  // 画圆,圆心(200, 200),半径 100
    _getch();               // 按任意键继续(控制台非缓冲输入,对应缓冲输入中的 getchar)
    closegraph();           // 关闭绘图窗口
    return 0;
}

运行过后会有个控制台窗口以及一个图形窗口,如下:

那么我们就可以开始了。

1.2 基本语句

1.2.-1 thread (多线程)

使用 thread 库

#include<thread>

新建线程

thread t;

构造函数 : thread(function f) ,对函数 f 新建一个线程。

这个是可以带参的,例如:

#include <iostream>
#include <thread>
// 普通函数
void simpleFunction() {
    std::cout << "This is a simple function running in a thread." << std::endl;
}
// 带参数的函数
void functionWithArgs(int num) {
    std::cout << "The number passed is: " << num << std::endl;
}
int main() {
    // 使用普通函数创建线程
    std::thread t1(simpleFunction);
    // 使用带参数的函数创建线程
    std::thread t2(functionWithArgs, 42);
    // 等待线程完成
    t1.join();
    t2.join();
    return 0;
}    

t.detach(); :将线程 t 分离,主线程不会等待它完成,t 会在后台继续运行。

使用 thread 时要注意数据竞争以及资源分配,避免出现离奇的错误

为了减少出现多线程之间数据竞争和不一致问题,需要使用同步机制来解决,常见的同步工具有 std::mutexstd::lock_guardstd::unique_lock 等。

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx;
int sharedVariable = 0;

// 线程函数
void increment() {
    for (int i = 0; i < 100000000; ++i) {
    //  std::lock_guard<std::mutex> lock(mtx);
        ++sharedVariable;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);
    t1.join();
    t2.join();
    std::cout << "Final value of sharedVariable: " << sharedVariable << std::endl;
    return 0;
}    

假如不加上注释的那一行那么大概率跑出来的结果是小于 2\times10^8 ,这就是因为出现了数据不同步。

加上锁过后就是对的了。

1.2.0 创建图形窗口

initgraph(640, 480);
// initgraph(width, height, flag = 0)
// 宽度,高度,以及类型(默认为 0)
#define EX_SHOWCONSOLE      1       // Maintain the console window when creating a graphics window
#define EX_NOCLOSE          2       // Disable the close button
#define EX_NOMINIMIZE       4       // Disable the minimize button
#define EX_DBLCLKS          8       // Support double-click events

在后边加上 flag 的值可以让窗口支持一些功能。

1 是保留控制台窗口, 2 是禁止用右上角 x 关闭, 4 是禁止最小化, 8 是支持双击。

假如要同时支持多个直接按位或起来就行,比如 15 就是所有功能全部支持。

setbkcolor(COLOR); //设置背景颜色
cleardevice(); //清空,初始化

1.2.1 点,线,矩形绘制

注意窗口中的坐标原点是窗口左上角,然后向右方向是 x 轴正方向,向下是 y 轴正方向。

我们可以通过 setorigin(x, y) 修改绘画的坐标原点至 (x, y)

putpixel(X, Y, COLOR) 可以在 (x,y) 处画一个颜色为 COLOR 的 1 x 1 的像素点。

线

line(X1, Y1, X2, Y2) 可以画一条 (X1, Y1)(X2, Y2) 的线。

setlinecolor(COLOR) 可以设置画线的颜色。

setlinestyle 函数可以设置线的类型,具体用法不展开,功能类似 windows 自带画图里边的,可以有波浪线,荧光笔状物,以及调整线粗细之类的东西。

矩形

rectangle(X1, Y1, X2, Y2) 可以绘制一个左上角端点在 (X1, Y1) ,右下角端点在 (X2, Y2) 的空心矩形。

fillrectangle(X1, Y1, X2, Y2) 可以绘制一个左上角端点在 (X1, Y1) ,右下角端点在 (X2, Y2) 的有边框有填充矩形。

用之前先 selfillcolor(COLOR) 可以更改填充颜色。

solidrectangle(X1, Y1, X2, Y2) 可以绘制一个左上角端点在 (X1, Y1) ,右下角端点在 (X2, Y2) 的无边框有填充矩形。

roundrect(X1, Y1, X2, Y2, H, W) 可以绘制一个左上角端点在 (X1, Y1) ,右下角端点在 (X2, Y2) ,圆角大小为 H x W 的圆角带填充矩形。

solidroundrect(X1, Y1, X2, Y2, H, W) 可以绘制一个左上角端点在 (X1, Y1) ,右下角端点在 (X2, Y2) ,圆角大小为 H x W 的圆角无边框矩形。

1.2.2 文字绘制

1.2.3 消息处理 (EXMessage)

1.2.4 清屏,缓冲,帧率控制

1.2.5 图片加载,输出

咕咕咕。

2.Examples

2.1 输出立方体 (Cube)

实现内容

输出一个立方体,支持鼠标上下滑动时变色以及旋转,每秒的时候上下浮动。

solution

考虑两种实现,第一种我们考虑维护三维下的点集 (x,y,z)\in G ,考虑把旋转拆分成关于 x,y,z 轴的旋转,那么每次旋转只会改变两维,可以转化成一个二维平面的旋转,用复数 complex 可以轻松实现。

(\sin \alpha+\cos \alpha i)(\sin \beta+\cos \beta i)=\sin (\alpha+\beta)+\cos(\alpha+\beta)i ,然后就可以轻松实现了。

输出的时候只保留 (x,y) 向下取整就行了。

然后考虑怎么获取鼠标左键滑动的信息,考虑存上一次左键的位置,然后用当前的坐标和上次的作差就能求出来。然后系数自己看着取就行了。

代码:

#include <easyx.h>
#include <conio.h> 
#include <math.h>
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <algorithm>
#include <windows.h>
#include <iostream>
#include <thread>
#include <random>
using namespace std;
struct cp {
    double x, y; cp() { x = y = 0; }
    cp(double X, double Y) { x = X, y = Y; }
};
inline cp operator +(const cp& x, const cp& y) { return cp( x.x + y.x, x.y + y.y ); }
inline cp operator -(const cp& x, const cp& y) { return cp( x.x - y.x, x.y - y.y ); }
inline cp operator *(const cp& x, const cp& y) { return cp( x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x); }
const double Pi = acos(-1.0);
const int OFFEST_X = 840, OFFEST_Y = 840;
struct Point { double x, y, z; };
struct Atom {
    static const int width = 2, height = 2;
    int x = 0, y = 0; COLORREF color;
};
vector<Point> C;
vector<Atom> Group;
double angle_x = 0, angle_y = 0;
double mulp = 1;
int delta_x = 0, Col = 0;
Point GetConvertPoint(Point P) {
    auto [x, y, z] = P; cp tmp(y, z);
    tmp = tmp * cp(sin(angle_x), cos(angle_x));
    y = tmp.x, z = tmp.y;
    tmp = cp(z, x);
    tmp = tmp * cp(sin(angle_y), cos(angle_y));
    z = tmp.x, x = tmp.y;
    return { x, y, z };
}
int Cube_Size;
void Init(int D, int type = 0) {
    Cube_Size = D;
    auto calc = [&](int x, int y, int z) {
        int v = (int)(x == D || x == 0) + (y == 0 || y == D) + (z == 0 || z == D);
        return v;
    };
    for (int i = 0; i <= D; ++i) {
        for (int j = 0; j <= D; ++j) {
            for (int k = 0; k <= D; ++k) {
                if(calc(i, j, k) > 1)
                C.push_back(Point{ (double)i - D / 2, (double)j - D / 2, (double)k - D / 2 });
            }
        }
    }
}
void DrawG(vector<Atom> Group) {
    for (auto P : Group) {
        setfillcolor(P.color);
        int a = P.width >> 1, b = P.height >> 1;
        solidrectangle(P.x - a, P.y - b + delta_x, P.x + a, P.y + b + delta_x);
    }
}
void GetAtom() {
    srand(time(0));
    Group.clear();
    int D = Cube_Size;
    struct node { int x, y, z, c; };
    vector<node> Vec;
    for (auto P : C) {
        int v = 1;
        P.x *= mulp, P.y *= mulp, P.z *= mulp;
        auto [x, y, z] = GetConvertPoint(P);
        int X = x + 0.5, Y = y + 0.5, Z = z + 0.5;
        Vec.push_back({ X, Y, Z, v });
    }
    sort(Vec.begin(), Vec.end(), [&](node x, node y) { return x.z < y.z; });
    Col += rand() / 100;
    for (auto p : Vec) {
        Atom A;
        A.x = p.x * A.width, A.y = p.y * A.height;
        A.color = Col;
        Group.push_back(A);
    }
}
void Draw() {
    BeginBatchDraw();
    cleardevice();
    GetAtom();
    DrawG(Group);
    EndBatchDraw();
}
struct KeyState {
    inline bool check(char c) { return ((GetAsyncKeyState(c) & 0x8000) > 0); }
    inline bool ctrl() { return ((GetAsyncKeyState(VK_CONTROL) & 0x8000) > 0); }
    inline bool right() { return ((GetAsyncKeyState(VK_RIGHT) & 0x8000) > 0); }
    inline bool left() { return ((GetAsyncKeyState(VK_LEFT) & 0x8000) > 0); }
    inline bool up() { return ((GetAsyncKeyState(VK_UP) & 0x8000) > 0); }
    inline bool down() { return ((GetAsyncKeyState(VK_DOWN) & 0x8000) > 0); }
} K;
void func() {
    while (1) {
        if (K.up()) angle_x -= 0.01;
        if (K.down()) angle_x += 0.01;
        if (K.left()) angle_y -= 0.01;
        if (K.right()) angle_y += 0.01;
        if (angle_x < 0) angle_x += Pi * 2;
        if (angle_y < 0) angle_y += Pi * 2;
        if (angle_x > Pi * 2) angle_x -= Pi * 2;
        if (angle_y > Pi * 2) angle_y -= Pi * 2;
        Sleep(4);
    }
}
void func2() {
    while (true) {
        Draw();
        Sleep(30);
    }
}
void func3() {
    //shake
    srand(time(0));
    int op = 1, lim = 50;
    double L = 0.4, R = 2.2, o = 1, e = 0.05;
    while (1) {
        if (delta_x > lim) op *= -1;
        if (delta_x < -lim) op *= -1;
        delta_x += op * 5;
        Sleep(20);
    }
}
void func4() {
    ExMessage lst, now;
    while (1) {
        ExMessage msg;
        while (peekmessage(&msg, EX_MOUSE)) {
            if (msg.message == WM_LBUTTONDOWN) {
                lst = msg;
            }
            if (msg.lbutton && msg.message == WM_MOUSEMOVE) {
                now = msg;
                angle_y += (now.x - lst.x) * 0.005;
                angle_x += (now.y - lst.y) * 0.005;
                lst = now;
            }
        }
        Sleep(16);
    }

}
int main()
{
    initgraph(OFFEST_X, OFFEST_Y);
    cleardevice();
    setbkcolor(WHITE);
    Col = RED;
    setorigin(400, 400);
    Init(80, 0);
    thread f1(func), f2(func2), f3(func3), f4(func4);
    f1.join(), f2.join(), f3.join(), f4.join();
    return 0;
}

2048

#include <easyx.h>
#include <conio.h> 
#include <math.h>
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <algorithm>
#include <windows.h>
#include <iostream>
#include <thread>
#include <random>
#include <mutex>
#define rep(i, x, y) for(int i = (x); i <= (y); ++i)
using namespace std;
std::mutex mtx;
struct KeyState {
    inline bool check(char c) { return ((GetAsyncKeyState(c) & 0x8000) > 0); }
    inline bool ctrl() { return ((GetAsyncKeyState(VK_CONTROL) & 0x8000) > 0); }
    inline bool right() { return ((GetAsyncKeyState(VK_RIGHT) & 0x8000) > 0); }
    inline bool left() { return ((GetAsyncKeyState(VK_LEFT) & 0x8000) > 0); }
    inline bool up() { return ((GetAsyncKeyState(VK_UP) & 0x8000) > 0); }
    inline bool down() { return ((GetAsyncKeyState(VK_DOWN) & 0x8000) > 0); }
} K;
struct cp {
    double x, y; cp() { x = y = 0; }
    cp(double X, double Y) { x = X, y = Y; }
};
inline cp operator +(const cp& x, const cp& y) { return cp( x.x + y.x, x.y + y.y ); }
inline cp operator -(const cp& x, const cp& y) { return cp( x.x - y.x, x.y - y.y ); }
inline cp operator *(const cp& x, const cp& y) { return cp( x.x * y.x - x.y * y.y, x.x * y.y + x.y * y.x); }
const int Block_Size = 100, MAXN = 10, MAXM = MAXN, Edge_Width = 15;
const int Fps = 60, Fps_t = 1000.0 / Fps;
int val[MAXN + 1][MAXM + 1], tmp[MAXN + 1][MAXM + 1], lst[MAXN + 1][MAXM + 1];
mt19937 mtrnd(233);
int rnd(int R) { return mtrnd() % R + 1; }
int rnd(int L, int R) { return rnd(R - L + 1) + L - 1; }
int rndval() { return 1 << rnd(2); }
void build() {
    int x = rnd(1, MAXN), y = rnd(1, MAXN);
    val[x][y] = rndval();
}
void Rotate() {
    memset(tmp, 0, sizeof tmp);
    rep(i, 1, MAXN) rep(j, 1, MAXN) tmp[i][j] = val[i][j];
    rep(i, 1, MAXN) rep(j, 1, MAXN) val[j][MAXN + 1 - i] = tmp[i][j];
}
void Memory() { rep(i, 1, MAXN) rep(j, 1, MAXN) lst[i][j] = val[i][j]; }
void Generate() {
    rep(i, 1, MAXN) {
        rep(j, 1, MAXN) {
            if(lst[i][j] ^ val[i][j]) {
                while(1) {
                    int x = rnd(1, MAXN), y = rnd(1, MAXN);
                    if(!val[x][y]) {
                        val[x][y] = rndval();
                        return; 
                    }
                }
                return;
            }
        }
    }
}
void Up() {
    Memory();
    memset(tmp, 0, sizeof tmp);
    rep(j, 1, MAXN) {
        int sz = 0;
        rep(i, 1, MAXM) {
            int x = val[i][j]; if(!x) continue;
            if(sz && x == tmp[sz][j]) tmp[sz][j] += x;
            else tmp[++sz][j] = val[i][j];
        }
        rep(i, 1, MAXN) val[i][j] = tmp[i][j];
    }
}
void Down() {
    Memory();
    Rotate(), Rotate(), Up();
    Rotate(), Rotate();
    Generate();
}
void Left() {
    Memory();
    Rotate(), Up();
    Rotate(), Rotate(), Rotate();
    Generate();
}
void Right() {
    Memory();
    Rotate(), Rotate(), Rotate();
    Up(), Rotate();
    Generate();
}
void Control() {
    while(1) {
        if(K.right()) { Right(); while(K.right()); }
        if(K.left()) { Left(); while(K.left()); }
        if(K.up()) { Up(); while(K.up()); Generate(); }
        if(K.down()) { Down(); while(K.down()); }
        if(K.ctrl()) { Rotate(); while(K.down()); }
    }
}
void print(int x, int y) {
    if(!val[x][y]) return;
    int tmp = val[x][y], len = 0; while(tmp) tmp /= 10, ++len;
    int N = Block_Size * 0.9, B = Block_Size * 0.05;
    settextcolor(WHITE);
    char str[20]; sprintf(str, "%d", val[x][y]);
    int L = N / len, V = N - (L / 2) * len, yl = (V + 1) / 2, xl = (N - L + 1) / 2;
    settextstyle(L, L / 2, _T("微软雅黑"));
    cout << "Debug : print on : " << B + (y - 1) * Block_Size + yl << " " << B + (x - 1) * Block_Size + xl << '\n';
    outtextxy(B + (y - 1) * Block_Size + yl, B + (x - 1) * Block_Size + xl, str );
}
void Draw() {
    BeginBatchDraw();
    cleardevice();
    setlinecolor(WHITE);
    rep(i, 0, MAXN) line(0, i * Block_Size, MAXN * Block_Size, i * Block_Size);
    rep(i, 0, MAXN) line(i * Block_Size, 0, i * Block_Size, MAXN * Block_Size);
    rep(i, 1, MAXN) rep(j, 1, MAXN) print(i, j);
    EndBatchDraw();
}
void Main() {
    while(1) {

        Draw();
        Sleep(Fps_t);
    }
}
int main() {
    initgraph(Block_Size * (MAXN + 2) + Edge_Width * 2, Block_Size * MAXM + Edge_Width * 2, 9);
    setorigin(Edge_Width, Edge_Width), build(); setbkcolor(BLACK);
    thread t1(Control), t2(Main);
    t1.join(), t2.join();
    return 0;
}