1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16@Entry 17@Component 18struct GameCard { 19 @State btnFontSize: string = '20'; 20 @State infoFontSize: string = '30'; 21 private BLACK_WIN: string = 'BLACK WINS!'; 22 private WHITE_WIN: string = 'WHITE WINS!'; 23 private BLACK_FIRST: string = 'BLACK FIRST'; 24 private BLACK_STEP: string = 'BLACK STEP'; 25 private WHITE_STEP: string = 'WHITE STEP'; 26 private ALREADY_STEP: string = 'ALREADY STEP'; 27 private setInfo: RenderingContextSettings = new RenderingContextSettings(true); 28 private ctxInfo: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.setInfo); 29 private settings: RenderingContextSettings = new RenderingContextSettings(true); 30 private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); 31 private infoAreaWidth: number = 0; 32 private infoAreaHeight: number = 0; 33 private canvasWidth: number = 0; 34 private canvasHeight: number = 0; 35 private chessboardMargin: number = 0.05; 36 private chessboardPosX: number = 0; 37 private chessboardPosY: number = 0; 38 private chessboardWidth: number = 0; 39 private chessboardHeight: number = 0; 40 private chessboardLines: number = 14; 41 private chessboardDotInfo: number = 4; 42 private chessboardLineWidth: number = 0; 43 private chessboardDotRadius: number = 0; 44 private chessPiecesCount: number = 0; 45 private chessPiecesArray: Array<object> = []; 46 private processRecord: Array<object> = []; 47 48 chessPiecesInit(areaWidth: number, areaHeight: number): void { 49 this.canvasWidth = areaWidth; 50 this.canvasHeight = areaHeight; 51 this.chessboardPosX = this.canvasWidth * this.chessboardMargin; 52 this.chessboardPosY = this.canvasHeight * this.chessboardMargin; 53 this.chessboardWidth = this.canvasWidth - this.chessboardPosX * 2; 54 this.chessboardHeight = this.canvasHeight - this.chessboardPosY * 2; 55 this.processRecord = new Array(); 56 this.chessPiecesArray = new Array(); 57 for (let i = 0; i <= this.chessboardLines; ++i) { 58 this.chessPiecesArray[i] = new Array(); 59 for (let j = 0; j <= this.chessboardLines; ++j) { 60 // -1 for blank space; 0 for black chess; 1 for white chess 61 this.chessPiecesArray[i][j] = -1; 62 } 63 } 64 console.info(`[ArkTSCard] ${this.chessboardWidth} -- ${this.chessboardHeight}`) 65 } 66 67 restart(): void { 68 console.info('[ArkTSCard] Paint chessboard restart'); 69 this.chessPiecesCount = 0; 70 this.processRecord.length = 0; 71 for (let i = 0; i <= this.chessboardLines; ++i) { 72 for (let j = 0; j <= this.chessboardLines; ++j) { 73 // -1 for blank space; 0 for black chess; 1 for white chess 74 this.chessPiecesArray[i][j] = -1; 75 } 76 } 77 this.paintStartInfo(); 78 this.paintBackground(); 79 this.paintChessboard(); 80 } 81 82 paintForSizeChanged(areaWidth: number, areaHeight: number): void { 83 console.info(`[ArkTSCard] ${this.canvasWidth}-${this.canvasHeight} to ${areaWidth}-${areaHeight}`); 84 this.canvasWidth = areaWidth; 85 this.canvasHeight = areaHeight; 86 this.chessboardPosX = this.canvasWidth * this.chessboardMargin; 87 this.chessboardPosY = this.canvasHeight * this.chessboardMargin; 88 this.chessboardWidth = this.canvasWidth - this.chessboardPosX * 2; 89 this.chessboardHeight = this.canvasHeight - this.chessboardPosY * 2; 90 this.paintBackground(); 91 this.paintChessboard(); 92 for (let i = 0; i < this.processRecord.length; ++i) { 93 let process = this.processRecord[i]; 94 this.paintChessPieces(process[0], process[1], i); 95 } 96 this.paintProcess(); 97 } 98 99 fallback(): void { 100 console.info(`[ArkTSCard] Fallback ${this.processRecord.length} - ${this.chessPiecesCount}`); 101 if (this.chessPiecesCount <= 0) { 102 this.paintStartInfo(); 103 return; 104 } 105 let prev: object | undefined = this.processRecord.pop(); 106 107 if (prev) { 108 this.chessPiecesArray[prev[0]][prev[1]] = -1; 109 this.paintBackground(); 110 this.paintChessboard(); 111 for (let i = 0; i < this.processRecord.length; ++i) { 112 let process = this.processRecord[i]; 113 this.paintChessPieces(process[0], process[1], i); 114 } 115 this.chessPiecesCount--; 116 this.paintProcess(); 117 } 118 119 } 120 121 paintBackground(): void { 122 console.info('[ArkTSCard] Paint Background'); 123 this.context.fillStyle = 'rgba(250, 204, 164, 1.00)'; 124 this.context.fillRect(0, 0, this.canvasWidth, this.canvasHeight); 125 this.context.fillStyle = 'rgba(255, 119, 0, 1.00)'; 126 this.context.fillRect(this.chessboardPosX, this.chessboardPosY, this.chessboardWidth, this.chessboardHeight); 127 } 128 129 paintChessboard(): void { 130 console.info('[ArkTSCard] Paint Chessboard'); 131 let widthGap = this.chessboardWidth / this.chessboardLines; 132 let heightGap = this.chessboardHeight / this.chessboardLines; 133 this.context.beginPath(); 134 this.context.strokeStyle = '#000000'; 135 this.context.lineWidth = this.chessboardLineWidth; 136 for (let i = 0; i <= this.chessboardLines; ++i) { 137 let startPosX = this.chessboardPosX; 138 let startPosY = this.chessboardPosY + i * heightGap; 139 let endPosX = this.chessboardPosX + this.chessboardWidth; 140 let endPosY = this.chessboardPosY + i * heightGap; 141 this.context.moveTo(startPosX, startPosY); 142 this.context.lineTo(endPosX, endPosY); 143 console.info(`[ArkTSCard] Paint ${startPosX} ${startPosY} ${endPosX} ${endPosY}`); 144 } 145 for (let i = 0; i <= this.chessboardLines; ++i) { 146 let startPosX = this.chessboardPosX + i * widthGap; 147 let startPosY = this.chessboardPosY; 148 let endPosX = this.chessboardPosX + i * widthGap; 149 let endPosY = this.chessboardPosY + this.chessboardHeight; 150 this.context.moveTo(startPosX, startPosY); 151 this.context.lineTo(endPosX, endPosY); 152 console.info(`[ArkTSCard] Paint ${startPosX} ${startPosY} ${endPosX} ${endPosY}`); 153 } 154 this.context.stroke(); 155 this.context.closePath(); 156 157 let dotIndex = this.chessboardLines % this.chessboardDotInfo; 158 this.paintChessboardDot( 159 this.chessboardPosX + dotIndex * widthGap, 160 this.chessboardPosY + dotIndex * heightGap, 161 this.chessboardDotRadius, '#000000' 162 ); 163 this.paintChessboardDot( 164 this.chessboardPosX + (this.chessboardLines - dotIndex) * widthGap, 165 this.chessboardPosY + dotIndex * heightGap, 166 this.chessboardDotRadius, '#000000' 167 ); 168 this.paintChessboardDot( 169 this.chessboardPosX + dotIndex * widthGap, 170 this.chessboardPosY + (this.chessboardLines - dotIndex) * heightGap, 171 this.chessboardDotRadius, '#000000' 172 ); 173 this.paintChessboardDot( 174 this.chessboardPosX + (this.chessboardLines - dotIndex) * widthGap, 175 this.chessboardPosY + (this.chessboardLines - dotIndex) * heightGap, 176 this.chessboardDotRadius, '#000000' 177 ); 178 } 179 180 paintChessboardDot(posX: number, poxY: number, radius: number, color: string): void { 181 this.context.beginPath(); 182 this.context.moveTo(posX - radius, poxY); 183 this.context.arc(posX, poxY, radius, 2 * Math.PI, 0, true); 184 this.context.strokeStyle = color; 185 this.context.closePath(); 186 this.context.fillStyle = color; 187 this.context.fill(); 188 } 189 190 processClick(posX: number, posY: number): void { 191 let widthGap = this.chessboardWidth / this.chessboardLines; 192 let heightGap = this.chessboardHeight / this.chessboardLines; 193 let chessIndexX = Math.round((posX - this.chessboardPosX) / widthGap); 194 let chessIndexY = Math.round((posY - this.chessboardPosY) / heightGap); 195 chessIndexX = Math.min(chessIndexX, this.chessboardLines); 196 chessIndexY = Math.min(chessIndexY, this.chessboardLines); 197 if (chessIndexX < 0 || chessIndexY < 0) { 198 return; 199 } 200 if (this.chessPiecesArray[chessIndexX][chessIndexY] != -1) { 201 this.paintPrompting(); 202 return; 203 } 204 console.info(`[ArkTSCard] Click:[${posX} ${posY}] Put:[${chessIndexX} ${chessIndexY}]`); 205 this.chessPiecesArray[chessIndexX][chessIndexY] = this.chessPiecesCount % 2; 206 let currentProcess = [chessIndexX, chessIndexY]; 207 this.processRecord.push(currentProcess); 208 this.paintChessPieces(chessIndexX, chessIndexY, this.chessPiecesCount); 209 this.chessPiecesCount++; 210 this.paintProcess(); 211 this.checkVictoryDefeat(); 212 } 213 214 paintChessPieces(chessIndexX: number, chessIndexY: number, count: number): void { 215 let widthGap = this.chessboardWidth / this.chessboardLines; 216 let heightGap = this.chessboardHeight / this.chessboardLines; 217 let drawPosX = chessIndexX * widthGap + this.chessboardPosX; 218 let drawPosY = chessIndexY * heightGap + this.chessboardPosY; 219 let chessPiecesRadius = Math.min(widthGap, heightGap) / 2; 220 let color: string = count % 2 == 0 ? '#000000' : '#ffffff'; 221 console.info(`[ArkTSCard] Put:[${chessIndexX} ${chessIndexY}] draw:[${drawPosX} ${drawPosY}]`); 222 this.paintChessboardDot(drawPosX, drawPosY, chessPiecesRadius, color); 223 } 224 225 dfsCheckCountLW(type: number, count: number, indexX: number, indexY: number): number { 226 let curCount: number = count; 227 if (indexX - 1 >= 0) { // Left 228 if (this.chessPiecesArray[indexX - 1][indexY] == type) { 229 this.chessPiecesArray[indexX - 1][indexY] = type + 100; // Avoid dead cycle 230 curCount = Math.max(curCount, this.dfsCheckCountLW(type, count + 1, indexX - 1, indexY)); 231 } 232 } 233 if (indexX + 1 <= this.chessboardLines) { // Write 234 if (this.chessPiecesArray[indexX + 1][indexY] == type) { 235 this.chessPiecesArray[indexX + 1][indexY] = type + 100; // Avoid dead cycle 236 curCount = Math.max(curCount, this.dfsCheckCountLW(type, count + 1, indexX + 1, indexY)); 237 } 238 } 239 return curCount; 240 } 241 242 dfsCheckCountUD(type: number, count: number, indexX: number, indexY: number): number { 243 let curCount: number = count; 244 if (indexY - 1 >= 0) { // Up 245 if (this.chessPiecesArray[indexX][indexY - 1] == type) { 246 this.chessPiecesArray[indexX][indexY - 1] = type + 100; // Avoid dead cycle 247 curCount = Math.max(curCount, this.dfsCheckCountUD(type, count + 1, indexX, indexY - 1)); 248 } 249 } 250 if (indexY + 1 <= this.chessboardLines) { // Down 251 if (this.chessPiecesArray[indexX][indexY + 1] == type) { 252 this.chessPiecesArray[indexX][indexY + 1] = type + 100; // Avoid dead cycle 253 curCount = Math.max(curCount, this.dfsCheckCountUD(type, count + 1, indexX, indexY + 1)); 254 } 255 } 256 return curCount 257 } 258 259 dfsCheckCountLU(type: number, count: number, indexX: number, indexY: number): number { 260 let curCount: number = count; 261 if (indexX - 1 >= 0 && indexY - 1 >= 0) { // Left & Up 262 if (this.chessPiecesArray[indexX - 1][indexY -1] == type) { 263 this.chessPiecesArray[indexX - 1][indexY -1] = type + 100; // Avoid dead cycle 264 curCount = Math.max(curCount, this.dfsCheckCountLU(type, count + 1, indexX - 1, indexY - 1)); 265 } 266 } 267 if (indexX + 1 <= this.chessboardLines && indexY + 1 <= this.chessboardLines) { // Write & Down 268 if (this.chessPiecesArray[indexX + 1][indexY + 1] == type) { 269 this.chessPiecesArray[indexX + 1][indexY + 1] = type + 100; // Avoid dead cycle 270 curCount = Math.max(curCount, this.dfsCheckCountLU(type, count + 1, indexX + 1, indexY + 1)); 271 } 272 } 273 return curCount; 274 } 275 276 dfsCheckCountWU(type: number, count: number, indexX: number, indexY: number): number { 277 let curCount: number = count; 278 if (indexX + 1 <= this.chessboardLines && indexY - 1 >= 0) { // Write & Up 279 if (this.chessPiecesArray[indexX + 1][indexY - 1] == type) { 280 this.chessPiecesArray[indexX + 1][indexY - 1] = type + 100; // Avoid dead cycle 281 curCount = Math.max(curCount, this.dfsCheckCountWU(type, count + 1, indexX + 1, indexY - 1)); 282 } 283 } 284 if (indexX - 1 >= 0 && indexY + 1 <= this.chessboardLines) { // Left & Down 285 if (this.chessPiecesArray[indexX - 1][indexY + 1] == type) { 286 this.chessPiecesArray[indexX - 1][indexY + 1] = type + 100; // Avoid dead cycle 287 curCount = Math.max(curCount, this.dfsCheckCountWU(type, count + 1, indexX - 1, indexY + 1)); 288 } 289 } 290 return curCount; 291 } 292 293 restoreChessPiecesArray(): void { 294 for (let i = 0; i <= this.chessboardLines; ++i) { 295 for (let j = 0; j <= this.chessboardLines; ++j) { 296 if (this.chessPiecesArray[i][j] >= 10) { 297 this.chessPiecesArray[i][j] -= 100; 298 } 299 } 300 } 301 } 302 303 dfsCheckCount(type: number, count: number, indexX: number, indexY: number): number { 304 let curCount = count; 305 this.chessPiecesArray[indexX][indexY] += 100; // Avoid dead cycle 306 curCount = Math.max(curCount, this.dfsCheckCountLW(type, count, indexX, indexY)); 307 this.restoreChessPiecesArray(); 308 this.chessPiecesArray[indexX][indexY] += 100; // Avoid dead cycle 309 curCount = Math.max(curCount, this.dfsCheckCountUD(type, count, indexX, indexY)); 310 this.restoreChessPiecesArray(); 311 this.chessPiecesArray[indexX][indexY] += 100; // Avoid dead cycle 312 curCount = Math.max(curCount, this.dfsCheckCountLU(type, count, indexX, indexY)); 313 this.restoreChessPiecesArray(); 314 this.chessPiecesArray[indexX][indexY] += 100; // Avoid dead cycle 315 curCount = Math.max(curCount, this.dfsCheckCountWU(type, count, indexX, indexY)); 316 this.restoreChessPiecesArray(); 317 return curCount; 318 } 319 320 checkVictoryDefeat(): void { 321 let countWhite = 0; 322 let countBlack = 0; 323 for (let i = 0; i <= this.chessboardLines; ++i) { 324 for (let j = 0; j <= this.chessboardLines; ++j) { 325 if (this.chessPiecesArray[i][j] == -1) { 326 continue; 327 } 328 if (this.chessPiecesArray[i][j] == 0) { 329 countBlack = Math.max(countBlack, this.dfsCheckCount(0, 1, i, j)); 330 if (countBlack >= 5) { 331 break; 332 } 333 } 334 if (this.chessPiecesArray[i][j] == 1) { 335 countWhite = Math.max(countWhite, this.dfsCheckCount(1, 1, i, j)); 336 if (countWhite >= 5) { 337 break; 338 } 339 } 340 } 341 } 342 for (let i = 0; i <= this.chessboardLines; ++i) { 343 for (let j = 0; j <= this.chessboardLines; ++j) { 344 if (this.chessPiecesArray[i][j] >= 10) { 345 this.chessPiecesArray[i][j] -= 100; 346 } 347 } 348 } 349 console.info(`[ArkTSCard] countBlack:[${countBlack}] countWhite:[${countWhite}]`); 350 if (countBlack >= 5) { 351 this.paintGameOver(0); 352 } 353 if (countWhite >= 5) { 354 this.paintGameOver(1); 355 } 356 } 357 358 paintPrompting(): void { 359 console.info('[ArkTSCard] Paint prompting info'); 360 this.ctxInfo.fillStyle = 'rgba(207, 186, 170, 1.00)'; 361 this.ctxInfo.fillRect(0, 0, this.infoAreaWidth, this.infoAreaHeight); 362 this.context.textAlign = 'center'; 363 this.ctxInfo.fillStyle = 'rgba(0, 0, 0, 1.00)'; 364 this.ctxInfo.font = this.infoFontSize + ' sans-serif'; 365 this.ctxInfo.fillText(this.ALREADY_STEP, 0, this.infoAreaHeight / 1.5); 366 } 367 368 paintProcess(): void { 369 console.info('[ArkTSCard] Paint process info'); 370 let info = this.BLACK_STEP; 371 if (this.chessPiecesCount % 2 == 1) { 372 info = this.WHITE_STEP; 373 } 374 this.ctxInfo.fillStyle = 'rgba(207, 186, 170, 1.00)'; 375 this.ctxInfo.fillRect(0, 0, this.infoAreaWidth, this.infoAreaHeight); 376 this.context.textAlign = 'center'; 377 this.ctxInfo.fillStyle = 'rgba(0, 0, 0, 1.00)'; 378 this.ctxInfo.font = this.infoFontSize + ' sans-serif'; 379 this.ctxInfo.fillText(info, 0, this.infoAreaHeight / 1.5); 380 } 381 382 paintStartInfo(): void { 383 console.info('Paint Start info'); 384 this.ctxInfo.fillStyle = 'rgba(207, 186, 170, 1.00)'; 385 this.ctxInfo.fillRect(0, 0, this.infoAreaWidth, this.infoAreaHeight); 386 this.context.textAlign = 'center'; 387 this.ctxInfo.fillStyle = 'rgba(0, 0, 0, 1.00)'; 388 this.ctxInfo.font = this.infoFontSize + ' sans-serif'; 389 this.ctxInfo.fillText(this.BLACK_FIRST, 0, this.infoAreaHeight / 1.5); 390 } 391 392 paintGameOver(type: number): void { 393 console.info('[ArkTSCard] Paint game over'); 394 let info = this.BLACK_WIN; 395 if (type == 1) { 396 info = this.WHITE_WIN; 397 } 398 this.ctxInfo.fillStyle = 'rgba(207, 186, 170, 1.00)'; 399 this.ctxInfo.fillRect(0, 0, this.infoAreaWidth, this.infoAreaHeight); 400 this.context.textAlign = 'center'; 401 this.ctxInfo.fillStyle = 'rgba(0, 0, 0, 1.00)'; 402 this.ctxInfo.font = this.infoFontSize + ' sans-serif'; 403 this.ctxInfo.fillText(info, 0, this.infoAreaHeight / 1.5); 404 } 405 406 build() { 407 Column() { 408 Column() { 409 Row() { 410 Column() { 411 Button($r('app.string.res_restart')).width('80%').height('95%') 412 .fontSize(this.btnFontSize) 413 .onClick(() => { 414 this.restart(); 415 }) 416 }.width('30%') 417 418 Column() { 419 Canvas(this.ctxInfo) 420 .width('100%') 421 .height('100%') 422 .backgroundColor('#fff5cccc') 423 .onReady(() => { 424 console.info('[ArkTSCard] onReady for canvas draw info'); 425 this.infoAreaWidth = Number(this.ctxInfo.width); 426 this.infoAreaHeight = Number(this.ctxInfo.height); 427 }) 428 }.width('40%').height('100%') 429 .backgroundColor('#fff8c6c6') 430 431 Column() { 432 Button($r('app.string.res_fallback')).width('80%').height('95%') 433 .fontSize(this.btnFontSize) 434 .onClick(() => { 435 this.fallback(); 436 }) 437 }.width('30%') 438 } 439 }.height('10%').width('100%') 440 441 Row() { 442 Canvas(this.context) 443 .width('100%') 444 .height('90%') 445 .onReady(() => { 446 let minSize = Math.min(this.context.width, this.context.height); 447 this.btnFontSize = minSize * 0.035 + 'vp'; 448 this.infoFontSize = minSize * 0.09 + 'px'; 449 this.chessboardLineWidth = minSize * 0.004; 450 this.chessboardDotRadius = this.chessboardLineWidth * 3; 451 if (this.canvasWidth === 0 && this.canvasHeight === 0) { 452 console.info('[ArkTSCard] onReady for canvas draw content'); 453 this.chessPiecesInit(Number(this.context.width), Number(this.context.height)); 454 this.paintBackground(); 455 this.paintChessboard(); 456 this.paintStartInfo(); 457 } else { 458 console.info('[ArkTSCard] onReady for canvas size changed'); 459 this.paintForSizeChanged(Number(this.context.width), Number(this.context.height)); 460 } 461 }) 462 .onClick((event: ClickEvent) => { 463 console.info(`[ArkTSCard] Click: PosX:${event.x} PosX:${event.y}`); 464 this.processClick(event.x, event.y); 465 }) 466 } 467 }.height('100%').width('100%') 468 } 469 470 aboutToAppear() { 471 console.info('[ArkTSCard] aboutToAppear'); 472 } 473 474 aboutToDisappear() { 475 console.info('[ArkTSCard] aboutToDisappear'); 476 } 477}