cbvirtua 1.0.48 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/canvas-example-main/canvas-example-main/.github/workflows/main.yml +62 -0
  2. package/canvas-example-main/canvas-example-main/README.md +13 -0
  3. package/canvas-example-main/canvas-example-main/curved.html +52 -0
  4. package/canvas-example-main/canvas-example-main/eslint.config.js +30 -0
  5. package/canvas-example-main/canvas-example-main/index.html +13 -0
  6. package/canvas-example-main/canvas-example-main/package.json +51 -0
  7. package/canvas-example-main/canvas-example-main/pnpm-lock.yaml +4760 -0
  8. package/canvas-example-main/canvas-example-main/postcss.config.js +6 -0
  9. package/canvas-example-main/canvas-example-main/public/vite.svg +1 -0
  10. package/canvas-example-main/canvas-example-main/src/App.tsx +17 -0
  11. package/canvas-example-main/canvas-example-main/src/assets/github.svg +1 -0
  12. package/canvas-example-main/canvas-example-main/src/assets/react.svg +1 -0
  13. package/canvas-example-main/canvas-example-main/src/components/Iconfont/demo.css +539 -0
  14. package/canvas-example-main/canvas-example-main/src/components/Iconfont/demo_index.html +418 -0
  15. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.css +55 -0
  16. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.js +1 -0
  17. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.json +79 -0
  18. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.ttf +0 -0
  19. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.woff +0 -0
  20. package/canvas-example-main/canvas-example-main/src/components/Iconfont/iconfont.woff2 +0 -0
  21. package/canvas-example-main/canvas-example-main/src/components/Iconfont/index.tsx +39 -0
  22. package/canvas-example-main/canvas-example-main/src/main.css +9 -0
  23. package/canvas-example-main/canvas-example-main/src/main.tsx +10 -0
  24. package/canvas-example-main/canvas-example-main/src/pages/2048/g2048.ts +14 -0
  25. package/canvas-example-main/canvas-example-main/src/pages/2048/index.tsx +21 -0
  26. package/canvas-example-main/canvas-example-main/src/pages/clock/index.tsx +103 -0
  27. package/canvas-example-main/canvas-example-main/src/pages/demo/index.tsx +21 -0
  28. package/canvas-example-main/canvas-example-main/src/pages/editor/components/editor/index.module.less +3 -0
  29. package/canvas-example-main/canvas-example-main/src/pages/editor/components/editor/index.tsx +99 -0
  30. package/canvas-example-main/canvas-example-main/src/pages/editor/components/header/index.module.less +5 -0
  31. package/canvas-example-main/canvas-example-main/src/pages/editor/components/header/index.tsx +5 -0
  32. package/canvas-example-main/canvas-example-main/src/pages/editor/components/material/index.module.less +59 -0
  33. package/canvas-example-main/canvas-example-main/src/pages/editor/components/material/index.tsx +85 -0
  34. package/canvas-example-main/canvas-example-main/src/pages/editor/components/setting/index.module.less +7 -0
  35. package/canvas-example-main/canvas-example-main/src/pages/editor/components/setting/index.tsx +5 -0
  36. package/canvas-example-main/canvas-example-main/src/pages/editor/core/application.ts +35 -0
  37. package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/base.ts +17 -0
  38. package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/factory.ts +14 -0
  39. package/canvas-example-main/canvas-example-main/src/pages/editor/core/cmp/shape.tsx +43 -0
  40. package/canvas-example-main/canvas-example-main/src/pages/editor/core/editor.ts +61 -0
  41. package/canvas-example-main/canvas-example-main/src/pages/editor/core/type.ts +6 -0
  42. package/canvas-example-main/canvas-example-main/src/pages/editor/index.module.less +7 -0
  43. package/canvas-example-main/canvas-example-main/src/pages/editor/index.tsx +32 -0
  44. package/canvas-example-main/canvas-example-main/src/pages/editor/store/component-config.ts +61 -0
  45. package/canvas-example-main/canvas-example-main/src/pages/editor/store/components.ts +43 -0
  46. package/canvas-example-main/canvas-example-main/src/pages/editor/store/layout.ts +40 -0
  47. package/canvas-example-main/canvas-example-main/src/pages/home/index.tsx +59 -0
  48. package/canvas-example-main/canvas-example-main/src/pages/jigsaw/index.tsx +3 -0
  49. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/bomber.png +0 -0
  50. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/index.tsx +138 -0
  51. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/mark.png +0 -0
  52. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/minesweeper.ts +345 -0
  53. package/canvas-example-main/canvas-example-main/src/pages/minesweeper/utils.ts +24 -0
  54. package/canvas-example-main/canvas-example-main/src/pages/pageflip/index.tsx +200 -0
  55. package/canvas-example-main/canvas-example-main/src/pages/pageflip/page1.jpg +0 -0
  56. package/canvas-example-main/canvas-example-main/src/pages/practice/draw/index.ts +367 -0
  57. package/canvas-example-main/canvas-example-main/src/pages/practice/index.module.less +26 -0
  58. package/canvas-example-main/canvas-example-main/src/pages/practice/index.tsx +54 -0
  59. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/control.ts +174 -0
  60. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/editor.ts +91 -0
  61. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/index.raw.tsx +159 -0
  62. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/index.tsx +36 -0
  63. package/canvas-example-main/canvas-example-main/src/pages/shape-editor/shape.ts +248 -0
  64. package/canvas-example-main/canvas-example-main/src/router.tsx +53 -0
  65. package/canvas-example-main/canvas-example-main/src/utils/storage.ts +48 -0
  66. package/canvas-example-main/canvas-example-main/src/vite-env.d.ts +1 -0
  67. package/canvas-example-main/canvas-example-main/tailwind.config.js +8 -0
  68. package/canvas-example-main/canvas-example-main/tsconfig.app.json +30 -0
  69. package/canvas-example-main/canvas-example-main/tsconfig.json +7 -0
  70. package/canvas-example-main/canvas-example-main/tsconfig.node.json +22 -0
  71. package/canvas-example-main/canvas-example-main/vite.config.ts +18 -0
  72. package/package.json +1 -1
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import { create } from 'zustand';
3
+
4
+ import { IconName } from '@/componentsIconfont';
5
+
6
+ import { ComponentType } from '../core/type';
7
+ import { shapeComponent } from '../core/cmp/shape';
8
+
9
+ type CategoryType = 'base' | 'material' | 'image' | 'text';
10
+
11
+ export interface Component {
12
+ name: string;
13
+ icon: IconName | (() => React.ReactNode);
14
+ model: Record<string, any> & { type: ComponentType };
15
+ }
16
+
17
+ interface ComponentCategory {
18
+ label: string;
19
+ type: CategoryType;
20
+ icon: string;
21
+ components: Component[];
22
+ }
23
+
24
+ type State = {
25
+ componentConfig: ComponentCategory[];
26
+ };
27
+
28
+ type Action = {};
29
+
30
+ const componentConfig: ComponentCategory[] = [
31
+ {
32
+ label: '基础',
33
+ type: 'base',
34
+ icon: 'wenzi',
35
+ components: [...shapeComponent],
36
+ },
37
+ {
38
+ label: '文字',
39
+ type: 'text',
40
+ icon: 'wenzi',
41
+ components: [],
42
+ },
43
+ {
44
+ label: '图片',
45
+ type: 'image',
46
+ icon: 'tupian',
47
+ components: [],
48
+ },
49
+ {
50
+ label: '素材',
51
+ type: 'material',
52
+ icon: 'sucai',
53
+ components: [],
54
+ },
55
+ ];
56
+
57
+ const useComponentConfigStore = create<State & Action>(() => ({
58
+ componentConfig,
59
+ }));
60
+
61
+ export { useComponentConfigStore };
@@ -0,0 +1,43 @@
1
+ import { create } from 'zustand';
2
+ import Editor from '../core/editor';
3
+ import { BaseModel } from '../core/cmp/base';
4
+ import * as cmpFactory from '../core/cmp/factory';
5
+
6
+ type State = {
7
+ components: any[];
8
+ editor: Editor;
9
+ selectCmp: any;
10
+ };
11
+
12
+ type Action = {
13
+ addCmp: (model: BaseModel) => void;
14
+ updateCmp: (id: string, data, needUpdate: boolean) => void;
15
+ removeCmp: (id: string, needUpdate: boolean) => void;
16
+ updateEditor: (editor: Editor) => void;
17
+ updateSelectCmp: (selectCmp: any) => void;
18
+ };
19
+
20
+ const useComponentStore = create<State & Action>((set, get) => ({
21
+ components: [],
22
+ editor: null,
23
+ selectCmp: null,
24
+ addCmp(model: BaseModel) {
25
+ const { editor, components } = get();
26
+ set({ components: [...components, model] });
27
+ editor.addCmp(cmpFactory.create(model.type, model));
28
+ },
29
+ updateCmp(id: string, data, needUpdate: boolean) {
30
+ console.log(id, data, needUpdate);
31
+ },
32
+ removeCmp(id: string, needUpdate: boolean) {
33
+ console.log(id, needUpdate);
34
+ },
35
+ updateEditor(editor) {
36
+ set({ editor });
37
+ },
38
+ updateSelectCmp(selectCmp) {
39
+ set({ selectCmp });
40
+ },
41
+ }));
42
+
43
+ export { useComponentStore };
@@ -0,0 +1,40 @@
1
+ import { create } from 'zustand';
2
+
3
+ interface IHeader {
4
+ height: number;
5
+ }
6
+
7
+ type State = {
8
+ contentWidth: number;
9
+ contentHeight: number;
10
+
11
+ header: IHeader;
12
+
13
+ asideOpen: boolean;
14
+ };
15
+
16
+ type Action = {
17
+ updateAsideOpen: (asideOpen: State['asideOpen']) => void;
18
+ updateContentWidth: (width: State['contentWidth']) => void;
19
+ };
20
+
21
+ const useLayoutStore = create<State & Action>((set, get) => ({
22
+ contentWidth: window.innerWidth - 360,
23
+ contentHeight: window.innerHeight - 60,
24
+ header: { height: 60 },
25
+ asideOpen: false,
26
+ updateAsideOpen: (asideOpen) => {
27
+ if (asideOpen) {
28
+ if (!get().asideOpen) {
29
+ set(() => ({ contentWidth: get().contentWidth - 300, asideOpen }));
30
+ }
31
+ } else {
32
+ if (get().asideOpen) {
33
+ set(() => ({ contentWidth: get().contentWidth + 300, asideOpen }));
34
+ }
35
+ }
36
+ },
37
+ updateContentWidth: (width) => set(() => ({ contentWidth: width })),
38
+ }));
39
+
40
+ export { useLayoutStore };
@@ -0,0 +1,59 @@
1
+ import { Button } from 'antd';
2
+ import { useNavigate } from 'react-router-dom';
3
+
4
+ const data = [
5
+ {
6
+ name: '练习',
7
+ path: '/practice',
8
+ },
9
+ {
10
+ name: 'Leafer',
11
+ path: '/leafer',
12
+ },
13
+ {
14
+ name: '翻书',
15
+ path: '/pageflip',
16
+ },
17
+ {
18
+ name: '拼图',
19
+ path: '/jigsaw',
20
+ },
21
+ {
22
+ name: '扫雷',
23
+ path: '/minesweeper',
24
+ },
25
+ {
26
+ name: '编辑器',
27
+ path: '/editor',
28
+ },
29
+ {
30
+ name: '时钟',
31
+ path: '/clock',
32
+ },
33
+ {
34
+ name: '形状编辑器',
35
+ path: '/shape-editor',
36
+ },
37
+ ];
38
+
39
+ export default function Page() {
40
+ const navigate = useNavigate();
41
+
42
+ return (
43
+ <div className="w-[100vw] h-[100vh] flex items-center justify-center gap-[15px]">
44
+ {data.map((page) => {
45
+ return (
46
+ <Button
47
+ key={page.name}
48
+ type="primary"
49
+ onClick={() => {
50
+ navigate(page.path);
51
+ }}
52
+ >
53
+ {page.name}
54
+ </Button>
55
+ );
56
+ })}
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,3 @@
1
+ export default function Page() {
2
+ return <div></div>;
3
+ }
@@ -0,0 +1,138 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+
3
+ import { Alert, Button, InputNumber, message, Modal } from 'antd';
4
+ import MineSweeper from './minesweeper';
5
+ import { useDebounce, useWindowSize } from 'react-use';
6
+ import storage from '../../utils/storage';
7
+
8
+ const storagePrefix = 'minesweeper_';
9
+
10
+ const defaultPersistConfig = storage.get(`${storagePrefix}config`) || {
11
+ x: 15,
12
+ y: 15,
13
+ bomberCount: 20,
14
+ };
15
+
16
+ let defaultConfig = {
17
+ ...defaultPersistConfig,
18
+ width: 800,
19
+ height: 800,
20
+ gap: 2,
21
+ };
22
+
23
+ export default function Page() {
24
+ const minesweeperRef = useRef<MineSweeper>();
25
+
26
+ const { width, height } = useWindowSize();
27
+
28
+ defaultConfig = {
29
+ ...defaultConfig,
30
+ width: Math.min(
31
+ 1200,
32
+ Math.min(Math.round(height * 0.82), Math.round(width * 0.82))
33
+ ),
34
+ height: Math.min(
35
+ 1200,
36
+ Math.min(Math.round(height * 0.82), Math.round(width * 0.82))
37
+ ),
38
+ };
39
+
40
+ const [config, setConfig] = useState(defaultConfig);
41
+
42
+ const [resultOpen, setResultOpen] = useState(false);
43
+ const [result, setResult] = useState(false);
44
+
45
+ const { x, y, bomberCount } = config;
46
+
47
+ useDebounce(
48
+ () => {
49
+ if (!minesweeperRef.current) return;
50
+ storage.set(`${storagePrefix}config`, config);
51
+ minesweeperRef.current.update(config);
52
+ },
53
+ 1000,
54
+ [config]
55
+ );
56
+
57
+ useEffect(() => {
58
+ minesweeperRef.current = new MineSweeper({
59
+ ...config,
60
+ view: 'minesweeper',
61
+ onFail() {
62
+ setResult(false);
63
+ setResultOpen(true);
64
+ },
65
+ onSuccess() {
66
+ setResult(true);
67
+ setResultOpen(true);
68
+ },
69
+ });
70
+ }, []);
71
+
72
+ return (
73
+ <div className="w-[100vw] h-[100vh] flex items-center justify-center background-[#aaa] flex-col">
74
+ <div className="mb-[10px] flex flex-row">
75
+ <div>
76
+ 网格规模:&nbsp;
77
+ <InputNumber
78
+ value={x}
79
+ onChange={(value) => {
80
+ if (typeof value === 'number') {
81
+ setConfig({ ...config, x: value, y: value });
82
+ }
83
+ }}
84
+ ></InputNumber>
85
+ </div>
86
+ <div className="ml-[20px]">
87
+ 炸弹数量:&nbsp;
88
+ <InputNumber
89
+ value={bomberCount}
90
+ onChange={(value) => {
91
+ if (typeof value === 'number') {
92
+ const maxBomberCount = Math.round(0.6 * x * y);
93
+ if (value > maxBomberCount) {
94
+ message.error(`炸弹数量不能超过 ${maxBomberCount}!`);
95
+ setConfig({ ...config, bomberCount: maxBomberCount });
96
+ } else {
97
+ setConfig({ ...config, bomberCount: value });
98
+ }
99
+ }
100
+ }}
101
+ ></InputNumber>
102
+ </div>
103
+ <Button
104
+ type="primary"
105
+ onClick={() => {
106
+ minesweeperRef.current.reset();
107
+ }}
108
+ className="ml-[12px]"
109
+ >
110
+ 重新开始
111
+ </Button>
112
+ </div>
113
+ <div id="minesweeper" className="flex items-center justify-center"></div>
114
+ <Modal
115
+ title="游戏结束!"
116
+ open={resultOpen}
117
+ closable={true}
118
+ centered={true}
119
+ maskClosable={true}
120
+ onOk={() => {
121
+ setResultOpen(false);
122
+ }}
123
+ onCancel={() => {
124
+ setResultOpen(false);
125
+ }}
126
+ footer={null}
127
+ >
128
+ <div className="h-[100px] flex items-center justify-center">
129
+ {result ? (
130
+ <Alert message="挑战成功! 已清除所有炸弹!" type="success" />
131
+ ) : (
132
+ <Alert message="挑战失败! 再接再厉!" type="error" />
133
+ )}
134
+ </div>
135
+ </Modal>
136
+ </div>
137
+ );
138
+ }
@@ -0,0 +1,345 @@
1
+ import { Leafer, Rect, Text, PointerEvent } from 'leafer-ui';
2
+ import { Animate } from '@leafer-in/animate';
3
+ import { create2dimensionArr, getDelayTime, getTextColor } from './utils';
4
+ import bomberImage from './bomber.png';
5
+ import markImage from './mark.png';
6
+
7
+ export interface Config extends IBaseConfig {
8
+ view: string;
9
+ onFail: () => void;
10
+ onSuccess: () => void;
11
+ }
12
+
13
+ export interface IBaseConfig {
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ bomberCount: number;
19
+ gap: number;
20
+ }
21
+
22
+ type Status = 'success' | 'gaming' | 'fail';
23
+
24
+ export default class MineSweeper {
25
+ grids: number[][];
26
+ openGrids: number[][];
27
+ markGrids: Rect[][];
28
+ rectGrids: Rect[][];
29
+ config: Config;
30
+ w: number;
31
+ h: number;
32
+ leafer: Leafer;
33
+ remainCount: number;
34
+ status: Status = 'gaming';
35
+
36
+ constructor(config: Config) {
37
+ this.config = config;
38
+
39
+ const { width, height, view } = config;
40
+
41
+ this.leafer = new Leafer({ view, width, height });
42
+
43
+ this.leafer.on(PointerEvent.TAP, this.handleTap.bind(this));
44
+ this.leafer.on(PointerEvent.MENU_TAP, this.handleMenuTap.bind(this));
45
+
46
+ this.init();
47
+ }
48
+
49
+ init() {
50
+ this.leafer.clear();
51
+ const { x, y, width, height, gap } = this.config;
52
+
53
+ this.w = Math.floor((width - gap * x) / x);
54
+ this.h = Math.floor((height - gap * y) / y);
55
+
56
+ this.grids = this.generateGrids();
57
+ this.openGrids = create2dimensionArr(x, y, 0);
58
+ this.markGrids = create2dimensionArr(x, y, 0);
59
+ this.rectGrids = create2dimensionArr(x, y);
60
+ this.status = 'gaming';
61
+ this.remainCount = x * y;
62
+
63
+ for (let i = 0; i < x; i++) {
64
+ for (let j = 0; j < y; j++) {
65
+ const rect = new Rect({
66
+ x: i * (this.w + gap),
67
+ y: j * (this.h + gap),
68
+ width: this.w,
69
+ height: this.h,
70
+ fill: '#22d2ef',
71
+ cursor: 'pointer',
72
+ });
73
+ this.rectGrids[i][j] = rect;
74
+ this.leafer.add(rect);
75
+ }
76
+ }
77
+ }
78
+
79
+ update(config: IBaseConfig) {
80
+ this.config = { ...this.config, ...config };
81
+ this.init();
82
+ }
83
+
84
+ handleSuccess() {
85
+ const { x, y, onSuccess } = this.config;
86
+ for (let i = 0; i < x; i++) {
87
+ for (let j = 0; j < y; j++) {
88
+ if (this.grids[i][j] === -1) {
89
+ this.drawBomber(i, j);
90
+ } else if (this.grids[i][j] > 0) {
91
+ this.drawText(i, j, this.grids[i][j], 0);
92
+ } else {
93
+ this.drawText(i, j, undefined, 0);
94
+ }
95
+ }
96
+ }
97
+ this.status = 'success';
98
+ onSuccess();
99
+ }
100
+
101
+ handleFail() {
102
+ const { x, y, onFail } = this.config;
103
+ for (let i = 0; i < x; i++) {
104
+ for (let j = 0; j < y; j++) {
105
+ if (this.grids[i][j] === -1) {
106
+ this.drawBomber(i, j);
107
+ }
108
+ }
109
+ }
110
+ this.status = 'fail';
111
+ onFail();
112
+ }
113
+
114
+ handleTap(e) {
115
+ const { bomberCount, gap } = this.config;
116
+ if (this.status !== 'gaming') {
117
+ return;
118
+ }
119
+
120
+ if (e.target !== this.leafer) {
121
+ const { x, y } = e.target;
122
+
123
+ const i = x / (this.w + gap);
124
+ const j = y / (this.h + gap);
125
+ if (this.grids[i][j] !== -1) {
126
+ this.leafer.remove(this.markGrids[i][j]);
127
+ this.markGrids[i][j] = null;
128
+ this.openGrids[i][j] = 1;
129
+ this.turnOpen(i, j, 0);
130
+
131
+ if (this.remainCount === bomberCount) {
132
+ this.handleSuccess();
133
+ }
134
+ } else {
135
+ this.handleFail();
136
+ }
137
+ }
138
+ }
139
+
140
+ handleMenuTap(e) {
141
+ const { gap } = this.config;
142
+ if (this.status !== 'gaming') {
143
+ return;
144
+ }
145
+
146
+ if (e.target !== this.leafer) {
147
+ const { x, y } = e.target;
148
+
149
+ const i = x / (this.w + gap);
150
+ const j = y / (this.h + gap);
151
+ if (this.openGrids[i][j]) {
152
+ return;
153
+ }
154
+
155
+ if (this.markGrids[i][j]) {
156
+ this.leafer.remove(this.markGrids[i][j]);
157
+ this.markGrids[i][j] = null;
158
+ } else {
159
+ const markRect = new Rect({
160
+ x: i * (this.w + gap),
161
+ y: j * (this.h + gap),
162
+ width: this.w,
163
+ height: this.h,
164
+ fill: {
165
+ type: 'image',
166
+ url: markImage,
167
+ },
168
+ cursor: 'pointer',
169
+ });
170
+ this.markGrids[i][j] = markRect;
171
+ this.leafer.add(markRect);
172
+ }
173
+ }
174
+ }
175
+
176
+ diffusion(i: number, j: number, step: number) {
177
+ const { x, y } = this.config;
178
+ const drts = [
179
+ [-1, -1],
180
+ [-1, 0],
181
+ [-1, 1],
182
+ [0, -1],
183
+ [0, 1],
184
+ [1, -1],
185
+ [1, 0],
186
+ [1, 1],
187
+ ];
188
+
189
+ for (let k = 0; k < drts.length; k++) {
190
+ const x1 = drts[k][0] + i;
191
+ const y1 = drts[k][1] + j;
192
+
193
+ if (x1 >= 0 && x1 < x && y1 >= 0 && y1 < y) {
194
+ if (this.openGrids[x1][y1]) {
195
+ continue;
196
+ }
197
+
198
+ if (this.grids[x1][y1] !== -1) {
199
+ this.openGrids[x1][y1] = 1;
200
+ this.turnOpen(x1, y1, step + 1);
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ drawBomber(i: number, j: number) {
207
+ const { gap } = this.config;
208
+
209
+ const width = this.w - 1 * 2;
210
+ const height = this.h - 1 * 2;
211
+
212
+ const bomberRect = new Rect({
213
+ x: i * (this.w + gap) + 1,
214
+ y: j * (this.h + gap) + 1,
215
+ width,
216
+ height,
217
+ fill: {
218
+ type: 'image',
219
+ url: bomberImage,
220
+ },
221
+ });
222
+
223
+ this.leafer.add(bomberRect);
224
+ }
225
+
226
+ /**
227
+ * 绘制文本
228
+ *
229
+ * @param {number} i
230
+ * @param {number} j
231
+ * @param {number} num 第几个绘制的元素
232
+ * @memberof MineSweeper
233
+ */
234
+ drawText(i: number, j: number, num: number, step: number) {
235
+ const { gap } = this.config;
236
+
237
+ const delayTime = getDelayTime(step);
238
+
239
+ const rect = new Rect({
240
+ x: i * (this.w + gap),
241
+ y: j * (this.h + gap),
242
+ width: this.w,
243
+ height: this.h,
244
+ fill: '#c4cbcf',
245
+ cursor: 'pointer',
246
+ opacity: 0,
247
+ animationOut: null,
248
+ });
249
+ new Animate(rect, [{ opacity: 1 }], {
250
+ duration: 1,
251
+ delay: delayTime,
252
+ });
253
+ this.leafer.add(rect);
254
+ if (num > 0) {
255
+ const text = new Text({
256
+ x: i * (this.w + gap),
257
+ y: j * (this.h + gap),
258
+ width: this.w,
259
+ height: this.h,
260
+ text: String(num),
261
+ fill: getTextColor(num),
262
+ textAlign: 'center',
263
+ verticalAlign: 'middle',
264
+ fontSize: (28 * this.w) / (600 / 15),
265
+ fontWeight: 'bold',
266
+ opacity: 0,
267
+ animationOut: null,
268
+ });
269
+ new Animate(text, [{ opacity: 1 }], {
270
+ duration: 1,
271
+ delay: delayTime,
272
+ });
273
+
274
+ this.leafer.add(text);
275
+ }
276
+ }
277
+
278
+ turnOpen(i: number, j: number, step: number) {
279
+ const val = this.grids[i][j];
280
+ this.drawText(i, j, val > 0 ? val : undefined, step);
281
+ if (val === 0) {
282
+ this.diffusion(i, j, step);
283
+ }
284
+ this.remainCount--;
285
+ }
286
+
287
+ generateGrids() {
288
+ const { x, y, bomberCount } = this.config;
289
+
290
+ const grids = create2dimensionArr(x, y, 0);
291
+
292
+ const set = new Set();
293
+ let bomberCountTemp = bomberCount;
294
+
295
+ while (bomberCountTemp || bomberCountTemp === x * y - bomberCount) {
296
+ const randomx = Math.round((x - 1) * Math.random());
297
+ const randomy = Math.round((y - 1) * Math.random());
298
+
299
+ const key = `${randomx}_${randomy}`;
300
+ if (!set.has(key)) {
301
+ set.add(key);
302
+ grids[randomx][randomy] = -1;
303
+ bomberCountTemp--;
304
+ }
305
+ }
306
+
307
+ const updateRound = (x1: number, y1: number) => {
308
+ const drts = [
309
+ [-1, -1],
310
+ [-1, 0],
311
+ [-1, 1],
312
+ [0, -1],
313
+ [0, 1],
314
+ [1, -1],
315
+ [1, 0],
316
+ [1, 1],
317
+ ];
318
+ drts.forEach((drt) => {
319
+ let [x2, y2] = drt;
320
+ x2 += x1;
321
+ y2 += y1;
322
+ if (x2 >= 0 && x2 < x && y2 >= 0 && y2 < y && grids[x2][y2] !== -1) {
323
+ grids[x2][y2] += 1;
324
+ }
325
+ });
326
+ };
327
+
328
+ for (let i = 0; i < x; i++) {
329
+ for (let j = 0; j < y; j++) {
330
+ if (grids[i][j] === -1) {
331
+ updateRound(i, j);
332
+ }
333
+ }
334
+ }
335
+
336
+ return grids;
337
+ }
338
+ reset() {
339
+ this.init();
340
+ }
341
+
342
+ destroy() {
343
+ this.leafer.destroy();
344
+ }
345
+ }