elsfkDifficult.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. <template>
  2. <div style="position: relative;">
  3. <canvas :id="id" width="320" height="620" tabindex="0"/>
  4. <div style="position: absolute;top: 0;left: 350px;">
  5. <p>{{score}}</p>
  6. <el-button @click="reset">reset</el-button>
  7. </div>
  8. </div>
  9. </template>
  10. <script lang="ts">
  11. import { Component, Vue } from "vue-property-decorator";
  12. import { v1 as uuid } from "uuid";
  13. import { SCanvasView, SColor, SPainter, STextAlign } from "@persagy-web/draw/lib";
  14. /**
  15. * 俄罗斯方块视图
  16. *
  17. * @author 郝建龙
  18. * */
  19. class TestView extends SCanvasView {
  20. /** 背景表示数组 */
  21. map: number[][] = [];
  22. /** 方块类型索引 */
  23. boxType: number = Number(Math.floor(Math.random() * 7)); // 0-6
  24. /** 方块变形索引 */
  25. dir: number = Number(Math.floor(Math.random() * 4)); // 0-3
  26. /** 所有方块集合 */
  27. box: number[][][] = [
  28. [
  29. [0, 0, 0, 0],
  30. [0, 1, 1, 0],
  31. [0, 1, 1, 0],
  32. [0, 0, 0, 0],
  33. ],
  34. [
  35. [0, 0, 0, 0],
  36. [0, 1, 1, 0],
  37. [0, 1, 1, 0],
  38. [0, 0, 0, 0],
  39. ],
  40. [
  41. [0, 0, 0, 0],
  42. [0, 1, 1, 0],
  43. [0, 1, 1, 0],
  44. [0, 0, 0, 0],
  45. ],
  46. [
  47. [0, 0, 0, 0],
  48. [0, 1, 1, 0],
  49. [0, 1, 1, 0],
  50. [0, 0, 0, 0],
  51. ],
  52. [
  53. [0, 1, 0, 0],
  54. [0, 1, 0, 0],
  55. [0, 1, 0, 0],
  56. [0, 1, 0, 0],
  57. ],
  58. [
  59. [0, 0, 0, 0],
  60. [1, 1, 1, 1],
  61. [0, 0, 0, 0],
  62. [0, 0, 0, 0],
  63. ],
  64. [
  65. [0, 0, 1, 0],
  66. [0, 0, 1, 0],
  67. [0, 0, 1, 0],
  68. [0, 0, 1, 0],
  69. ],
  70. [
  71. [0, 0, 0, 0],
  72. [0, 0, 0, 0],
  73. [1, 1, 1, 1],
  74. [0, 0, 0, 0],
  75. ],
  76. [
  77. [0, 0, 0, 0],
  78. [0, 1, 1, 0],
  79. [1, 1, 0, 0],
  80. [0, 0, 0, 0],
  81. ],
  82. [
  83. [0, 1, 0, 0],
  84. [0, 1, 1, 0],
  85. [0, 0, 1, 0],
  86. [0, 0, 0, 0],
  87. ],
  88. [
  89. [0, 0, 0, 0],
  90. [0, 0, 1, 1],
  91. [0, 1, 1, 0],
  92. [0, 0, 0, 0],
  93. ],
  94. [
  95. [0, 0, 0, 0],
  96. [0, 1, 0, 0],
  97. [0, 1, 1, 0],
  98. [0, 0, 1, 0],
  99. ],
  100. [
  101. [0, 0, 0, 0],
  102. [0, 1, 1, 0],
  103. [0, 0, 1, 1],
  104. [0, 0, 0, 0],
  105. ],
  106. [
  107. [0, 0, 0, 0],
  108. [0, 0, 1, 0],
  109. [0, 1, 1, 0],
  110. [0, 1, 0, 0],
  111. ],
  112. [
  113. [0, 0, 0, 0],
  114. [1, 1, 0, 0],
  115. [0, 1, 1, 0],
  116. [0, 0, 0, 0],
  117. ],
  118. [
  119. [0, 0, 1, 0],
  120. [0, 1, 1, 0],
  121. [0, 1, 0, 0],
  122. [0, 0, 0, 0],
  123. ],
  124. [
  125. [0, 0, 0, 0],
  126. [0, 1, 1, 0],
  127. [0, 0, 1, 0],
  128. [0, 0, 1, 0],
  129. ],
  130. [
  131. [0, 0, 0, 0],
  132. [0, 0, 1, 0],
  133. [1, 1, 1, 0],
  134. [0, 0, 0, 0],
  135. ],
  136. [
  137. [0, 1, 0, 0],
  138. [0, 1, 0, 0],
  139. [0, 1, 1, 0],
  140. [0, 0, 0, 0],
  141. ],
  142. [
  143. [0, 0, 0, 0],
  144. [0, 1, 1, 1],
  145. [0, 1, 0, 0],
  146. [0, 0, 0, 0],
  147. ],
  148. [
  149. [0, 0, 0, 0],
  150. [0, 1, 1, 0],
  151. [0, 1, 0, 0],
  152. [0, 1, 0, 0],
  153. ],
  154. [
  155. [0, 0, 0, 0],
  156. [1, 1, 1, 0],
  157. [0, 0, 1, 0],
  158. [0, 0, 0, 0],
  159. ],
  160. [
  161. [0, 0, 1, 0],
  162. [0, 0, 1, 0],
  163. [0, 1, 1, 0],
  164. [0, 0, 0, 0],
  165. ],
  166. [
  167. [0, 0, 0, 0],
  168. [0, 1, 0, 0],
  169. [0, 1, 1, 1],
  170. [0, 0, 0, 0],
  171. ],
  172. [
  173. [0, 0, 0, 0],
  174. [0, 0, 1, 0],
  175. [0, 1, 1, 1],
  176. [0, 0, 0, 0],
  177. ],
  178. [
  179. [0, 0, 0, 0],
  180. [0, 1, 0, 0],
  181. [0, 1, 1, 0],
  182. [0, 1, 0, 0],
  183. ],
  184. [
  185. [0, 0, 0, 0],
  186. [1, 1, 1, 0],
  187. [0, 1, 0, 0],
  188. [0, 0, 0, 0],
  189. ],
  190. [
  191. [0, 0, 1, 0],
  192. [0, 1, 1, 0],
  193. [0, 0, 1, 0],
  194. [0, 0, 0, 0],
  195. ]
  196. ];
  197. /** 方块初始位置x坐标 */
  198. x = 5;
  199. /** 方块初始位置y坐标 */
  200. y = 0;
  201. /** 记录上次刷新时间 */
  202. t = Date.now();
  203. /** 是否游戏结束 */
  204. gameOver: boolean = false;
  205. /** 方块颜色 */
  206. boxColor: SColor = new SColor();
  207. /**
  208. * 构造函数
  209. *
  210. * @param id canvas DOM id
  211. */
  212. constructor(id: string) {
  213. super(id);
  214. this.initMap();
  215. this.initColor();
  216. }
  217. /**
  218. * 键盘按下事件
  219. *
  220. * @param event 事件对象
  221. */
  222. onKeyDown(event: KeyboardEvent): void {
  223. //游戏结束
  224. if (this.gameOver) {
  225. return;
  226. }
  227. if (event.code == "KeyW") {
  228. //检查变形后是否碰撞
  229. if (!this.checkKnocked(this.x, this.y, (this.dir + 1) % 4)) {
  230. this.dir = (this.dir + 1) % 4;
  231. }
  232. } else if (event.code == "KeyA") {
  233. //检查左移后是否碰撞
  234. if (!this.checkKnocked(this.x - 1, this.y, this.dir)) {
  235. this.x--;
  236. }
  237. } else if (event.code == "KeyD") {
  238. //检查右移后是否碰撞
  239. if (!this.checkKnocked(this.x + 1, this.y, this.dir)) {
  240. this.x++;
  241. }
  242. } else if (event.code == "KeyS") {
  243. //检查下移后是否碰撞
  244. if (!this.checkKnocked(this.x, this.y + 1, this.dir)) {
  245. this.y++;
  246. } else {
  247. this.fullMap();
  248. }
  249. }
  250. this.update();
  251. }
  252. /**
  253. * Item 绘制操作
  254. *
  255. * @param painter 绘制对象
  256. */
  257. onDraw(painter: SPainter): void {
  258. //游戏结束绘制文字
  259. if (this.gameOver) {
  260. painter.brush.color = SColor.Blue;
  261. painter.font.size = 20;
  262. painter.font.textAlign = STextAlign.Center;
  263. painter.drawText("GAME OVER", 180, 30);
  264. return;
  265. }
  266. painter.clearRect(0, 0, this.width, this.height);
  267. painter.pen.color = new SColor("#0bc0ff");
  268. painter.brush.color = new SColor("#2accc7");
  269. this.drawMap(painter);
  270. painter.pen.color = this.boxColor;
  271. painter.brush.color = this.boxColor;
  272. //绘制图形
  273. this.drawBox(painter);
  274. //下落速度,下移一格
  275. if (Date.now() - this.t > 500) {
  276. //下移是否碰撞
  277. if (!this.checkKnocked(this.x, this.y + 1, this.dir)) {
  278. this.y++;
  279. } else {
  280. this.fullMap();
  281. }
  282. this.t = Date.now();
  283. }
  284. this.update();
  285. }
  286. /**
  287. * 初始化背景
  288. */
  289. initMap(): void {
  290. this.gameOver = false;
  291. this.map = [];
  292. //循环行数
  293. for (let row = 0; row < 22; row++) {
  294. const m1: number[] = [];
  295. //循环列数
  296. for (let col = 0; col < 14; col++) {
  297. //左侧,右侧,底部补充两个格
  298. if (row > 19 || col < 2 || col > 11) {
  299. //-1代表左右填充
  300. m1.push(-1);
  301. } else {
  302. m1.push(0);
  303. }
  304. }
  305. this.map.push(m1);
  306. }
  307. }
  308. /**
  309. * 初始化方块形状
  310. */
  311. initBox(): void {
  312. this.x = 5;
  313. this.y = 0;
  314. this.boxType = Number(Math.floor(Math.random() * 7));
  315. this.dir = Number(Math.floor(Math.random() * 4));
  316. this.initColor();
  317. }
  318. /**
  319. * 初始化方块颜色
  320. */
  321. initColor(): void {
  322. const a = Number(Math.floor(Math.random() * 128));
  323. const b = Number(Math.floor(Math.random() * 128));
  324. const c = Number(Math.floor(Math.random() * 128));
  325. this.boxColor = new SColor(a, b, c);
  326. }
  327. /**
  328. * 绘制背景
  329. *
  330. * @param painter 绘制对象
  331. */
  332. private drawMap(painter: SPainter): void {
  333. //行数
  334. for (let row = 0; row < 22; row++) {
  335. //列数
  336. for (let col = 0; col < 14; col++) {
  337. this.drawShape(painter, row, col, this.map[row][col]);
  338. // if (n == 0) {
  339. // painter.drawRect(col * 30 + 11 - 60, row * 30 + 11, 28, 28);
  340. // }
  341. }
  342. }
  343. }
  344. /**
  345. * 绘制实体图形
  346. *
  347. * @param painter 绘制对象
  348. * @param row 行
  349. * @param col 列
  350. * @param type 图类型
  351. */
  352. private drawShape(painter: SPainter, row: number, col: number, type: number): void {
  353. const x = col * 30 + 11 - 60;
  354. const y = row * 30 + 11;
  355. //类型判断,0为空白,-1填充
  356. switch (type) {
  357. case 0:
  358. break;
  359. case -1:
  360. painter.drawRoundRect(x, y, 28, 28, 6);
  361. break;
  362. default:
  363. painter.drawRoundRect(x, y, 28, 28, 6);
  364. break;
  365. }
  366. }
  367. /**
  368. * 绘制方块
  369. *
  370. * @param painter 绘制对象
  371. */
  372. private drawBox(painter: SPainter): void {
  373. //绘制图形行数
  374. for (let row = 0; row < 4; row++) {
  375. //绘制图形列数
  376. for (let col = 0; col < 4; col++) {
  377. this.drawShape(painter, row + this.y, col + this.x, this.box[this.boxType * 4 + this.dir][row][col]);
  378. }
  379. }
  380. }
  381. /**
  382. * 是否碰撞
  383. *
  384. * @param x x坐标
  385. * @param y y坐标
  386. * @param dir 方块变形索引
  387. * @return 是否碰撞
  388. */
  389. private checkKnocked(x: number, y: number, dir: number): boolean {
  390. //绘制图形行数
  391. for (let row = 0; row < 4; row++) {
  392. //绘制图形列数
  393. for (let col = 0; col < 4; col++) {
  394. //判断是否重合,每种图形四个一组,四种变形的某种变形,不为零表示绘制(确定方块),坐标转换,背景图中绘制
  395. if (this.box[this.boxType * 4 + dir][row][col] != 0 && this.map[y + row][x + col] != 0) {
  396. return true;
  397. }
  398. }
  399. }
  400. return false;
  401. }
  402. /**
  403. * 将方块合到背景中
  404. */
  405. private fullMap(): void {
  406. //遍历图形的行数
  407. for (let row = 0; row < 4; row++) {
  408. //遍历图形的列数
  409. for (let col = 0; col < 4; col++) {
  410. //判断图形绘制,填充至背景中
  411. if (this.box[this.boxType * 4 + this.dir][row][col] == 1) {
  412. this.map[this.y + row][this.x + col] = 1;
  413. }
  414. }
  415. }
  416. this.initBox();
  417. //判断背景上的图形与初始化图形是否重合
  418. if (this.checkKnocked(this.x, this.y, this.dir)) {
  419. //游戏结束
  420. this.gameOver = true;
  421. }
  422. this.remove();
  423. }
  424. /**
  425. * 消除满行
  426. */
  427. private remove(): void {
  428. //消除行数
  429. let removeCount = 0;
  430. //循环行数,不包含底部填充2行
  431. for (let row = 0; row < 20; row++) {
  432. //消除标记,true 是
  433. let flag = true;
  434. //
  435. // for (let col = 0; col < 14; col++) {
  436. // if (this.map[row][col] == 0) {
  437. // flag = false;
  438. // break;
  439. // }
  440. // }
  441. //若当前行有一个空格,则不消除
  442. for (let col = 2; col < 12; col++) {
  443. if (this.map[row][col] == 0) {
  444. flag = false;
  445. break;
  446. }
  447. }
  448. if (flag) {
  449. this.map.splice(row, 1);
  450. //顶部加一行空行
  451. this.map.unshift(this.randomRow(10, 0));
  452. removeCount++;
  453. }
  454. }
  455. //记录消除行数
  456. if (removeCount > 0) {
  457. this.$emit("score", removeCount);
  458. //删除行
  459. this.map.shift();
  460. //底部随机产生一行
  461. this.map.splice(19, 0, this.randomRow());
  462. }
  463. }
  464. /**
  465. * 随机数组生成
  466. *
  467. * @return 数组
  468. */
  469. private randomRow(len: number = 10, num: number = 2): number[] {
  470. let array = new Array(len + 4).fill(-1);
  471. //生成随机数,循环不包含左右填充格
  472. for (let i = 2; i < array.length - 2; i++) {
  473. array[i] = Math.floor(num * Math.random());
  474. }
  475. return array;
  476. }
  477. }
  478. @Component
  479. export default class ElsfkCanvas extends Vue {
  480. id: string = uuid();
  481. scoreList: number[] = [100, 300, 600, 1000];
  482. score: number = 0;
  483. view: TestView | undefined = undefined;
  484. mounted(): void {
  485. this.view = new TestView(this.id);
  486. this.view.connect("score", this, this.changeScore);
  487. document.getElementById(this.id)!!.focus();
  488. }
  489. changeScore(v: SCanvasView, count: number[]) {
  490. //根据消除行数判断分值
  491. this.score += this.scoreList[count[0] - 1];
  492. }
  493. reset() {
  494. this.view!!.initMap();
  495. this.view!!.initBox();
  496. this.score = 0;
  497. this.view!!.update();
  498. document.getElementById(this.id)!!.focus();
  499. }
  500. }
  501. </script>