elsfk.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <template>
  2. <div>
  3. <canvas :id="id" width="320" height="620" tabindex="0"/>
  4. </div>
  5. </template>
  6. <script lang="ts">
  7. import { Component, Vue } from "vue-property-decorator";
  8. import { v1 as uuid } from "uuid";
  9. import { SCanvasView, SColor, SPainter } from "@persagy-web/draw/lib";
  10. /**
  11. * 俄罗斯方块视图
  12. *
  13. * @author 郝洁 <haojie@persagy.com>
  14. */
  15. class TestView extends SCanvasView {
  16. /** 背景表示数组 */
  17. map: number[][] = [];
  18. /** 方块类型索引 */
  19. fkType: number = Number(Math.floor(Math.random() * 7)); // 0-6
  20. /** 方块变形索引 */
  21. dir: number = Number(Math.floor((Math.random() * 4))); // 0-3
  22. /** 所有方块集合 */
  23. fk: number[][][] = [
  24. [
  25. [0, 0, 0, 0],
  26. [0, 1, 1, 0],
  27. [0, 1, 1, 0],
  28. [0, 0, 0, 0],
  29. ],
  30. [
  31. [0, 0, 0, 0],
  32. [0, 1, 1, 0],
  33. [0, 1, 1, 0],
  34. [0, 0, 0, 0],
  35. ],
  36. [
  37. [0, 0, 0, 0],
  38. [0, 1, 1, 0],
  39. [0, 1, 1, 0],
  40. [0, 0, 0, 0],
  41. ],
  42. [
  43. [0, 0, 0, 0],
  44. [0, 1, 1, 0],
  45. [0, 1, 1, 0],
  46. [0, 0, 0, 0],
  47. ],
  48. [
  49. [0, 1, 0, 0],
  50. [0, 1, 0, 0],
  51. [0, 1, 0, 0],
  52. [0, 1, 0, 0],
  53. ],
  54. [
  55. [0, 0, 0, 0],
  56. [1, 1, 1, 1],
  57. [0, 0, 0, 0],
  58. [0, 0, 0, 0],
  59. ],
  60. [
  61. [0, 0, 1, 0],
  62. [0, 0, 1, 0],
  63. [0, 0, 1, 0],
  64. [0, 0, 1, 0],
  65. ],
  66. [
  67. [0, 0, 0, 0],
  68. [0, 0, 0, 0],
  69. [1, 1, 1, 1],
  70. [0, 0, 0, 0],
  71. ],
  72. [
  73. [0, 0, 0, 0],
  74. [0, 1, 1, 0],
  75. [1, 1, 0, 0],
  76. [0, 0, 0, 0],
  77. ],
  78. [
  79. [0, 1, 0, 0],
  80. [0, 1, 1, 0],
  81. [0, 0, 1, 0],
  82. [0, 0, 0, 0],
  83. ],
  84. [
  85. [0, 0, 0, 0],
  86. [0, 0, 1, 1],
  87. [0, 1, 1, 0],
  88. [0, 0, 0, 0],
  89. ],
  90. [
  91. [0, 0, 0, 0],
  92. [0, 1, 0, 0],
  93. [0, 1, 1, 0],
  94. [0, 0, 1, 0],
  95. ],
  96. [
  97. [0, 0, 0, 0],
  98. [0, 1, 1, 0],
  99. [0, 0, 1, 1],
  100. [0, 0, 0, 0],
  101. ],
  102. [
  103. [0, 0, 0, 0],
  104. [0, 0, 1, 0],
  105. [0, 1, 1, 0],
  106. [0, 1, 0, 0],
  107. ],
  108. [
  109. [0, 0, 0, 0],
  110. [1, 1, 0, 0],
  111. [0, 1, 1, 0],
  112. [0, 0, 0, 0],
  113. ],
  114. [
  115. [0, 0, 1, 0],
  116. [0, 1, 1, 0],
  117. [0, 1, 0, 0],
  118. [0, 0, 0, 0],
  119. ],
  120. [
  121. [0, 0, 0, 0],
  122. [0, 1, 1, 0],
  123. [0, 0, 1, 0],
  124. [0, 0, 1, 0],
  125. ],
  126. [
  127. [0, 0, 0, 0],
  128. [0, 0, 1, 0],
  129. [1, 1, 1, 0],
  130. [0, 0, 0, 0],
  131. ],
  132. [
  133. [0, 1, 0, 0],
  134. [0, 1, 0, 0],
  135. [0, 1, 1, 0],
  136. [0, 0, 0, 0],
  137. ],
  138. [
  139. [0, 0, 0, 0],
  140. [0, 1, 1, 1],
  141. [0, 1, 0, 0],
  142. [0, 0, 0, 0],
  143. ],
  144. [
  145. [0, 0, 0, 0],
  146. [0, 1, 1, 0],
  147. [0, 1, 0, 0],
  148. [0, 1, 0, 0],
  149. ],
  150. [
  151. [0, 0, 0, 0],
  152. [1, 1, 1, 0],
  153. [0, 0, 1, 0],
  154. [0, 0, 0, 0],
  155. ],
  156. [
  157. [0, 0, 1, 0],
  158. [0, 0, 1, 0],
  159. [0, 1, 1, 0],
  160. [0, 0, 0, 0],
  161. ],
  162. [
  163. [0, 0, 0, 0],
  164. [0, 1, 0, 0],
  165. [0, 1, 1, 1],
  166. [0, 0, 0, 0],
  167. ],
  168. [
  169. [0, 0, 0, 0],
  170. [0, 0, 1, 0],
  171. [0, 1, 1, 1],
  172. [0, 0, 0, 0],
  173. ],
  174. [
  175. [0, 0, 0, 0],
  176. [0, 1, 0, 0],
  177. [0, 1, 1, 0],
  178. [0, 1, 0, 0],
  179. ],
  180. [
  181. [0, 0, 0, 0],
  182. [1, 1, 1, 0],
  183. [0, 1, 0, 0],
  184. [0, 0, 0, 0],
  185. ],
  186. [
  187. [0, 0, 1, 0],
  188. [0, 1, 1, 0],
  189. [0, 0, 1, 0],
  190. [0, 0, 0, 0],
  191. ]
  192. ];
  193. /** 方块初始位置x坐标 */
  194. x = 5;
  195. /** 方块初始位置y坐标 */
  196. y = 0;
  197. /** 记录上次刷新时间 */
  198. t = Date.now();
  199. /**
  200. * 构造函数
  201. * @param id canvas DOM id
  202. */
  203. constructor(id: string) {
  204. super(id);
  205. this.initMap();
  206. }
  207. /**
  208. * 键盘按下事件
  209. * @param event 事件对象
  210. */
  211. onKeyDown(event: KeyboardEvent): void {
  212. if (event.code == "KeyW") { // 按键 w
  213. if (!this.isPz(this.x, this.y, (this.dir + 1) % 4)) { // 检查变形后是否碰撞
  214. this.dir = (this.dir + 1) % 4;
  215. }
  216. } else if (event.code == "KeyA") { // 按键 a
  217. if (!this.isPz(this.x - 1, this.y, this.dir)) { // 检查左移后是否碰撞
  218. this.x--;
  219. }
  220. } else if (event.code == "KeyD") { // 按键 d
  221. if (!this.isPz(this.x + 1, this.y, this.dir)) { // 检查右移后是否碰撞
  222. this.x++;
  223. }
  224. } else if (event.code == "KeyS") { // 按键 s
  225. if (!this.isPz(this.x, this.y + 1, this.dir)) { // 检查下移后是否碰撞
  226. this.y++;
  227. } else {
  228. this.fullMap();
  229. this.xc();
  230. }
  231. }
  232. this.update();
  233. }
  234. /**
  235. * Item 绘制操作
  236. *
  237. * @param painter 绘制对象
  238. */
  239. onDraw(painter: SPainter): void {
  240. //清除画布
  241. painter.clearRect(0, 0, this.width, this.height);
  242. //绘制操作相关命令
  243. painter.pen.color = SColor.Transparent;
  244. painter.brush.color = SColor.Red;
  245. this.drawMap(painter);
  246. this.drawFk(painter);
  247. if (Date.now() - this.t > 500) { // 下落速度,下移一格
  248. if (!this.isPz(this.x, this.y + 1, this.dir)) { // 下移是否碰撞
  249. this.y++;
  250. } else {
  251. this.fullMap();
  252. this.xc();
  253. }
  254. this.t = Date.now();
  255. }
  256. this.update();
  257. }
  258. /**
  259. *初始化背景
  260. */
  261. private initMap(): void {
  262. this.map = [];
  263. for (let row = 0; row < 22; row++) { // 循环行数
  264. const m1: number[] = []
  265. for (let col = 0; col < 14; col++) { // 循环列数
  266. if (row > 19 || col < 2 || col > 11) { // 左侧,右侧,底部补充两个格
  267. // -1 代表左右填充
  268. m1.push(2);
  269. } else {
  270. m1.push(0);
  271. }
  272. }
  273. this.map.push(m1);
  274. }
  275. }
  276. /**
  277. * 绘制背景
  278. *
  279. * @param painter 绘制对象
  280. */
  281. private drawMap(painter: SPainter): void {
  282. for (let row = 0; row < 22; row++) { // 行数
  283. for (let col = 0; col < 14; col++) { // 列数
  284. const x = col * 30 + 11 - 60;
  285. const y = row * 30 + 11;
  286. if (this.map[row][col] == 1) { //赋到背景
  287. painter.drawRect(col * 30 + 11 - 60, row * 30 + 11, 28, 28);
  288. }
  289. if (this.map[row][col] == 2) { //底加一行
  290. painter.drawRect(col * 30 + 11 - 60, row * 30 + 11, 28, 28);
  291. }
  292. }
  293. }
  294. }
  295. /**
  296. * 绘制实体图形
  297. *
  298. * @param painter 绘制对象
  299. */
  300. private drawFk(painter: SPainter): void {
  301. for (let row = 0; row < 4; row++) { // 绘制图形行数
  302. for (let col = 0; col < 4; col++) { // 绘制图形列数
  303. if (this.fk[this.fkType * 4 + this.dir][row][col] == 1) {
  304. painter.drawRect((col + this.x) * 30 + 11 - 60, (row + this.y) * 30 + 11, 28, 28);
  305. }
  306. }
  307. }
  308. }
  309. /**
  310. * 是否碰撞
  311. *
  312. * @param x x坐标
  313. * @param y y坐标
  314. * @param dir 方块变形索引
  315. * @return 是否碰撞
  316. */
  317. private isPz(x: number, y: number, dir: number): boolean {
  318. for (let row = 0; row < 4; row++) { // 绘制图形行数
  319. for (let col = 0; col < 4; col++) { // 绘制图形列数
  320. // 判断是否重合,每种图形四个一组,四种变形的某种变形,不为零表示绘制(确定方块),坐标转换,背景图中绘制
  321. if (this.fk[this.fkType * 4 + dir][row][col] == 1 && this.map[y + row][x + col] != 0) {
  322. return true;
  323. }
  324. }
  325. }
  326. return false;
  327. }
  328. /**
  329. * 将方块合到背景中
  330. */
  331. private fullMap() {
  332. for (let row = 0; row < 4; row++) { // 遍历图形的行数
  333. for (let col = 0; col < 4; col++) { // 遍历图形的列数
  334. if (this.fk[this.fkType * 4 + this.dir][row][col] == 1) {
  335. this.map[this.y + row][this.x + col] = 1;
  336. }
  337. }
  338. }
  339. this.x = 5;
  340. this.y = 0;
  341. this.fkType = Number((Math.random() * 6).toFixed());
  342. this.dir = Number((Math.random() * 2).toFixed());
  343. }
  344. /**
  345. * 消除满行
  346. */
  347. private xc(): void {
  348. // 循环行数,不包含底部填充2行
  349. for (let row = 0; row < 20; row++) {
  350. // 消除标记,true 是
  351. let flag = true;
  352. // 若当前行有一个空格,则不消除
  353. for (let col = 2; col < 12; col++) {
  354. if (this.map[row][col] == 0) { // 存在空格
  355. flag = false;
  356. break;
  357. }
  358. }
  359. if (flag) { // 标记删除
  360. this.map.splice(row, 1);
  361. this.map.unshift()
  362. // 顶部加一行空行
  363. this.map.unshift(this.randomRow(10, 0))
  364. // this.map.unshift([2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2]);
  365. // row--;
  366. }
  367. }
  368. }
  369. /**
  370. * 随机数组生成
  371. *
  372. * @return 数组
  373. */
  374. private randomRow(len: number = 10, num: number = 2): number[] {
  375. let array = new Array(len + 4).fill(2);
  376. // 生成随机数,循环不包含左右填充格
  377. for (let i = 2; i < array.length - 2; i++) {
  378. array[i] = Math.floor(num * Math.random())
  379. }
  380. return array
  381. }
  382. }
  383. @Component
  384. export default class ElsfkCanvas extends Vue {
  385. /** 图 id */
  386. id: string = uuid();
  387. /**
  388. * 页面挂载
  389. */
  390. mounted(): void {
  391. new TestView(this.id);
  392. }
  393. }
  394. </script>