elsfkDifficult.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  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, Prop, 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. if (this.gameOver) {
  224. return;
  225. }
  226. if (event.code == "KeyW") {
  227. if (!this.checkKnocked(this.x, this.y, (this.dir + 1) % 4)) {
  228. this.dir = (this.dir + 1) % 4;
  229. }
  230. } else if (event.code == "KeyA") {
  231. if (!this.checkKnocked(this.x - 1, this.y, this.dir)) {
  232. this.x--;
  233. }
  234. } else if (event.code == "KeyD") {
  235. if (!this.checkKnocked(this.x + 1, this.y, this.dir)) {
  236. this.x++;
  237. }
  238. } else if (event.code == "KeyS") {
  239. if (!this.checkKnocked(this.x, this.y + 1, this.dir)) {
  240. this.y++;
  241. } else {
  242. this.fullMap();
  243. }
  244. }
  245. this.update();
  246. }
  247. /**
  248. * 键盘按下事件
  249. *
  250. * @param painter 绘制对象
  251. */
  252. onDraw(painter: SPainter): void {
  253. if (this.gameOver) {
  254. painter.brush.color = SColor.Blue;
  255. painter.font.size = 20;
  256. painter.font.textAlign = STextAlign.Center;
  257. painter.drawText("GAME OVER", 180, 30);
  258. return;
  259. }
  260. painter.clearRect(0, 0, this.width, this.height);
  261. painter.pen.color = new SColor("#0bc0ff");
  262. painter.brush.color = new SColor("#2accc7");
  263. this.drawMap(painter);
  264. painter.pen.color = this.boxColor;
  265. painter.brush.color = this.boxColor;
  266. this.drawBox(painter);
  267. if (Date.now() - this.t > 500) {
  268. if (!this.checkKnocked(this.x, this.y + 1, this.dir)) {
  269. this.y++;
  270. } else {
  271. this.fullMap();
  272. }
  273. this.t = Date.now();
  274. }
  275. this.update();
  276. }
  277. /**
  278. * 初始化背景
  279. */
  280. initMap(): void {
  281. this.gameOver = false;
  282. this.map = [];
  283. for (let row = 0; row < 22; row++) {
  284. const m1: number[] = [];
  285. for (let col = 0; col < 14; col++) {
  286. if (row > 19 || col < 2 || col > 11) {
  287. m1.push(-1);
  288. } else {
  289. m1.push(0);
  290. }
  291. }
  292. this.map.push(m1);
  293. }
  294. }
  295. /**
  296. * 初始化方块形状
  297. */
  298. initBox(): void {
  299. this.x = 5;
  300. this.y = 0;
  301. this.boxType = Number(Math.floor(Math.random() * 7));
  302. this.dir = Number(Math.floor(Math.random() * 4));
  303. this.initColor();
  304. }
  305. /**
  306. * 初始化方块颜色
  307. */
  308. initColor(): void {
  309. const a = Number(Math.floor(Math.random() * 128));
  310. const b = Number(Math.floor(Math.random() * 128));
  311. const c = Number(Math.floor(Math.random() * 128));
  312. this.boxColor = new SColor(a, b, c);
  313. }
  314. /**
  315. * 绘制背景
  316. *
  317. * @param painter 绘制对象
  318. */
  319. private drawMap(painter: SPainter): void {
  320. for (let row = 0; row < 22; row++) {
  321. for (let col = 0; col < 14; col++) {
  322. this.drawShape(painter, row, col, this.map[row][col]);
  323. // if (n == 0) {
  324. // painter.drawRect(col * 30 + 11 - 60, row * 30 + 11, 28, 28);
  325. // }
  326. }
  327. }
  328. }
  329. /**
  330. * 绘制实体图形
  331. *
  332. * @param painter 绘制对象
  333. * @param row 行
  334. * @param col 列
  335. * @param type 图类型
  336. */
  337. private drawShape(painter: SPainter, row: number, col: number, type: number): void {
  338. const x = col * 30 + 11 - 60;
  339. const y = row * 30 + 11;
  340. switch (type) {
  341. case 0:
  342. break;
  343. case -1:
  344. painter.drawRoundRect(x, y, 28, 28, 6);
  345. break;
  346. default:
  347. painter.drawRoundRect(x, y, 28, 28, 6);
  348. break;
  349. }
  350. }
  351. /**
  352. * 绘制方块
  353. *
  354. * @param painter 绘制对象
  355. */
  356. private drawBox(painter: SPainter): void {
  357. for (let row = 0; row < 4; row++) {
  358. for (let col = 0; col < 4; col++) {
  359. this.drawShape(painter, row + this.y, col + this.x, this.box[this.boxType * 4 + this.dir][row][col]);
  360. }
  361. }
  362. }
  363. /**
  364. * 是否碰撞
  365. *
  366. * @param x x坐标
  367. * @param y y坐标
  368. * @param dir 方块变形索引
  369. * @return 是否碰撞
  370. */
  371. private checkKnocked(x: number, y: number, dir: number): boolean {
  372. for (let row = 0; row < 4; row++) {
  373. for (let col = 0; col < 4; col++) {
  374. if (this.box[this.boxType * 4 + dir][row][col] != 0 && this.map[y + row][x + col] != 0) {
  375. return true;
  376. }
  377. }
  378. }
  379. return false;
  380. }
  381. /**
  382. * 将方块合到背景中
  383. */
  384. private fullMap(): void {
  385. for (let row = 0; row < 4; row++) {
  386. for (let col = 0; col < 4; col++) {
  387. if (this.box[this.boxType * 4 + this.dir][row][col] == 1) {
  388. this.map[this.y + row][this.x + col] = 1;
  389. }
  390. }
  391. }
  392. this.initBox();
  393. if (this.checkKnocked(this.x, this.y, this.dir)) {
  394. this.gameOver = true;
  395. }
  396. this.remove();
  397. }
  398. /**
  399. * 消除满行
  400. */
  401. private remove(): void {
  402. let removeCount = 1;
  403. for (let row = 0; row < 20; row++) {
  404. let flag = true;
  405. for (let col = 0; col < 14; col++) {
  406. if (this.map[row][col] == 0) {
  407. flag = false;
  408. break;
  409. }
  410. }
  411. if (flag) {
  412. this.map.splice(row, 1);
  413. this.map.unshift(this.randomRow(10, 0));
  414. removeCount++;
  415. }
  416. }
  417. if (removeCount > 1) {
  418. this.$emit("score", removeCount);
  419. this.map.shift();
  420. this.map.splice(19, 0, this.randomRow());
  421. }
  422. }
  423. /**
  424. * 随机数组生成
  425. *
  426. * @return 数组
  427. */
  428. private randomRow(len: number = 10, num: number = 2): number[] {
  429. let array = new Array(len + 4).fill(2);
  430. for (let i = 2; i < array.length - 2; i++) {
  431. array[i] = Math.floor(num * Math.random());
  432. }
  433. return array;
  434. }
  435. }
  436. @Component
  437. export default class ElsfkCanvas extends Vue {
  438. id: string = uuid();
  439. scoreList: number[] = [100, 300, 600, 1000];
  440. score: number = 0;
  441. view: TestView | undefined = undefined;
  442. mounted(): void {
  443. this.view = new TestView(this.id);
  444. this.view.connect("score", this, this.changeScore);
  445. document.getElementById(this.id)!!.focus();
  446. }
  447. changeScore(v: SCanvasView, count: number[]) {
  448. this.score+= this.scoreList[count[0] - 2];
  449. }
  450. reset(){
  451. this.view!!.initMap();
  452. this.view!!.initBox();
  453. this.score = 0;
  454. this.view!!.update();
  455. document.getElementById(this.id)!!.focus();
  456. }
  457. }
  458. </script>