JavaScript (+LWC)ã®ãã¥ã¼ããªã¢ã«: ãªãã¼ã·ãä½ã(3)
JavaScript (+LWC)ã®ãã¥ã¼ããªã¢ã«: ãªãã¼ã·ãä½ã(3)
æ¦è¦
LWC ãããã³ã³ãã¼ãã³ãåãã¦ããã¾ãã
åæç¥è
ååã¾ã§ã®ç¥è
å®è£
ãã¼ãã®é©ç¨
åæç¶æ ã¯ãããªæã
è¦ãã°ãããã¾ããããªãã¼ã·ã®ãã¼ãã¯ãåç´ã« 1 ãã¹ 1 ãã¹ã®ãããã¯ã®éã¾ãã®ç¹°ãè¿ãã§ãã®ã§ãåç´ã«ãããï¼ã³ã³ãã¼ãã³ãã«ãã¦ã¿ã¾ãã
- src
- modules
- reversi
- column (æ°è¦ä½æ)
- column.css
- column.js
- column.html
- column (æ°è¦ä½æ)
- reversi
- modules
ãããä½æãã¾ãã
åºæ¬çã«ç©ºã®ããã¹ããã¡ã¤ã«ã§ç¨æãã¾ãã
1 ãã¹ã®ç¶æ³ãè¦ã㨠ãä½ãç½®ããã¦ããªãããç½ç³ããé»ç³ã ã¨ããç¶æ
ãæã¡ã¾ãã®ã§ãã¾ãã¯ããã表示ã§ããããã«ãã¾ãããã
column.js
ãã¾ãã¯æ¸ãã¦ããã¾ãã
import { api, LightningElement } from 'lwc'; import { Constants } from 'reversi/logic'; export default class Column extends LightningElement { @api status = null; get isBlank() { return this.status === Constants.BOARD_EMPTY; } get isWhite() { return this.status === Constants.BOARD_WHITE; } get isBlack() { return this.status === Constants.BOARD_BLACK; } }
@api
ã¨è¨ãã®ã¯ããã®ã¯ã©ã¹ã®å¤å´ããã£ã¨ããã¨ãã®ã³ã³ãã¼ãã³ãã使ãå´ããã¿ãã¤ã³ã¿ã¼ãã§ã¼ã¹ã§ãã
åç´ã«ã¯ã©ã¹å¤æ°ã«å¯¾ã㦠@api
ã使ãã¨ã親ã³ã³ãã¼ãã³ãããã¯å±æ§å¤ã¨ãã¦è¨å®ã§ãã¾ãã
ã¾ãããããã getter
ãç¨æãã¦ããã®ã¯ãLWC ã®ãã³ãã¬ã¼ãå
㧠Boolean è¨ç®åºæ¥ãªãããã§ãã
çµæ§ãã³ãã¬ã¼ã㧠Boolean
è¨ç®ã¯ã§ãããã¬ã¼ã ã¯ã¼ã¯ã¯å¤ãã®ã§ãããã¾ããããããã¨ãããã¨ã
次ã«ç»é¢ï¼ column.html
ï¼ãä½æãã¾ãã
<template> <div class="backgroud"> <template if:true={isBlank}> </template> <template if:true={isWhite}> <div class="circle white"> </div> </template> <template if:true={isBlack}> <div class="circle black"> </div> </template> </div> </template>
ãã®ã¾ã¾ã ã¨ãã ãã çã£ç½ãªç»é¢ãåºãã ããªã®ã§ãã¹ã¿ã¤ã«ï¼column.css
ï¼ãè¨å®ãã¾ãã
.backgroud { display: inline-block; width: 40px; height: 40px; margin: 0; background-color: darkgreen; border: 1px solid midnightblue; } .circle { width: 38px; height: 38px; border-radius: 50%; } .white { background-color: white; border: 1px solid gray; } .black { background-color: #444; border: 1px solid black; }
ããããããããã¼ã表示 app
ã«é©ç¨ãã¦ããã¾ãããã
ãã¼ãã¯åç´ã«æ°åã®äºæ¬¡å
é
åã§ãããLWC ã®ãã³ãã¬ã¼ãã§ã«ã¼ãããã«ã¯åã«ã¼ãåä½ã§ ãkeyã ãå¿
è¦ãªã®ã§ãã²ã¨æéããã¾ãã
get renderRows() { let rowId = 0; return this.board.map(row => { let colId = 0; const renderRow = row.map(col => { return { key: colId++, value: col }; }); return { key: rowId++, value: renderRow } }) }
ã§ã¯ç»é¢ãä¿®æ£ãã¾ãããã
<!-- <div style="display: inline-block; border: 1px solid #000;"> <template for:each={boardRows} for:item="row"> <div key={row.key}>{row.value}</div> </template> </div> --> <template for:each={renderRows} for:item="row"> <div key={row.key} style="height: 40px;"> <template for:each={row.value} for:item="col"> <reversi-column key={col.key} status={col.value}></reversi-column> </template> </div> </template>
ããã¨
ç³ãç´æ¥ç½®ããããã«ããã
å
¥åããã¤ã¾ã§ãæ°åå
¥åãªãã¦ç´æçã§ã¯ããã¾ããã®ã§ãç³ãç½®ããããã«å¤æ´ãã¦ããã¾ãã
ã¾ãã¯ãã¦ã¹ã«ã¼ã½ã«ãã¯ãªãã¯å¯è½ç¶æ
ã§å®ç¾© column.css
.click_able { cursor: pointer; display: inline-block; width: 100%; height: 100%; background-color: transparent; }
ããã¦ãç»é¢å´ (column.html
) ã§
<template> <div class="backgroud"> <template if:true={isBlank}> <div class="click_able"> </div> </template> <template if:true={isWhite}> <div class="circle white"> </div> </template> <template if:true={isBlack}> <div class="circle black"> </div> </template> </div> </template>
ãã£ã¦ã¿ãã°ãããã¾ããããã®æç¹ã§ç©ºç½ãã¹ã«ãã¦ã¹ã«ã¼ã½ã«ãæã£ã¦ããã¨ãã¯ãªãã¯ã§ããããªè¦ãç®ã«ãªãã¾ãï¼ã¾ã ã¯ãªãã¯ãã¦ãä½ãã§ãã¾ãããï¼ã
ã§ã¯ã¤ãã§ãã¯ãªãã¯ããã¨ãã«éç¥ãé£ã°ãããã«ãã¾ãã
LWC ã®è¦ªã¿ã°ãåã¿ã°ã®ããåãã¯ä»¥ä¸ã®ããã«è¨å®ãã¾ãã
- 親ç»é¢ â åã³ã³ãã¼ãã³ã : åã³ã³ãã¼ãã³ãå´ã®
@api status
ã®å¤ã«å¯¾ãã¦ã親ã³ã³ãã¼ãã³ãå´ããå±æ§<reversi-column status={col.value}></reversi-column>
ã§å¤ã渡ãã¾ãã - åã³ã³ãã¼ãã³ã â 親ç»é¢ : ã¤ãã³ãçºçã§éç¥ããã
ãããLWCã®ååçãªè¦ªåé¢ä¿ã®ãã¼ã¿ã®åã渡ãã«ãªãã¾ãã
ããã«åããã¯ãªãã¯å¯è½ï¼ä¸èº«ããã©ã³ã¯ï¼ã§ãã¯ãªãã¯ããããã¤ãã³ãã§è¦ªã³ã³ãã¼ãã³ãã«éç¥ããããä¿®æ£ãã¾ãã
column.js
ã«ã¦
handleClick() { if (!this.isBlank) { return; } this.dispatchEvent(new CustomEvent('putstone')); }
ãã®ã¤ãã³ããã³ãã©ãç»é¢ (column.html
) ã§è¨å®ãã¾ãã
<template> <div class="backgroud" onclick={handleClick}> ...
親ã³ã³ãã¼ãã³ãã§ããããé©ç¨ãã¦ããã¾ãããã
ã¾ãã¯ã¤ãã³ããåãåããã³ãã©ããå®ç¾©(app.js
)
handlePutStone(event) { console.log(event); console.log(event.target); }
ãããããHTML (app.html
) å´ãä¿®æ£ãã¾ãã
ãã®ã¨ããã©ã®ãã¹ãã¯ãªãã¯ããã®ã解ãããã« data-x
/ data-y
ãè¨å®ãã¾ãã
ã¡ãªã¿ã« data-xxxx
ãªã© data-
ã§å§ã¾ãå±æ§ã¯ãHTML è¦ç´ä¸å¥½ããªå¤ã好ãã«è¨å®ãã¦è¯ã ãã¨ã«ãªã£ã¦ã¾ãã
<template for:each={renderRows} for:item="row"> <div key={row.key} style="height: 40px;"> <template for:each={row.value} for:item="col"> <reversi-column data-y={row.key} data-x={col.key} key={col.key} status={col.value} onputstone={handlePutStone}></reversi-column> </template> </div> </template>
ããã¾ã§ã§ç»é¢ã表示ããChrome/FireFox ãªãF12 ãã¼ï¼éçºè
ã³ã³ã½ã¼ã«ï¼ã表示ãã¾ãã
ã¯ãªãã¯ããã¨â¦
ãã® data
ãèªã¿åã£ã¦ç³ãç½®ãã¾ãããã
handlePutStone(event) { console.log(event); console.log(event.target); // ç³ãç½®ããã¨ããã¿ã¼ã²ãã const target = event.target; // ã¿ã¼ã²ãããã座æ¨ãåå¾ const y = parseInt(target.getAttribute('data-y')); const x = parseInt(target.getAttribute('data-x')); // ç³ãç½®ãã¾ããã this.errorMessage = this.reversiLogic.putStone(x, y, this.isBlackTurn); this.isBlackTurn = this.reversiLogic.isBlackTurn; this.summary = this.reversiLogic.summary; }
ããã¨ã
ããã¾ã§æ¥ããã座æ¨å ¥åã¯ããã¾ããããæ¶ãæ¹ã¯â¦ã¾ãã³ã¼ãåé¤ã ããªã®ã§ãèªåã§ï½
å®æ
app.js
import { LightningElement, track } from 'lwc'; import ReversiLogic from 'reversi/logic'; import { Constants } from 'reversi/logic'; export default class HelloWorldApp extends LightningElement { reversiLogic = new ReversiLogic(); errorMessage = null; @track board = this.reversiLogic.board; @track isBlackTurn = this.reversiLogic.isBlackTurn; @track summary = this.reversiLogic.summary; constants = { MAX_WIDTH: Constants.BOARD_WIDTH, MAX_HEIGHT: Constants.BOARD_HEIGHT } get summaryString() { return `(é»: ${this.summary.black}, ç½: ${this.summary.white})`; } get renderRows() { let rowId = 0; return this.board.map(row => { let colId = 0; const renderRow = row.map(col => { return { key: colId++, value: col }; }); return { key: rowId++, value: renderRow } }) } handleSkip() { this.reversiLogic.skipTurn(); this.isBlackTurn = this.reversiLogic.isBlackTurn; } handlePutStone(event) { console.log(event); console.log(event.target); // ç³ãç½®ããã¨ããã¿ã¼ã²ãã const target = event.target; // ã¿ã¼ã²ãããã座æ¨ãåå¾ const y = parseInt(target.getAttribute('data-y')); const x = parseInt(target.getAttribute('data-x')); // ç³ãç½®ãã¾ããã this.errorMessage = this.reversiLogic.putStone(x, y, this.isBlackTurn); this.isBlackTurn = this.reversiLogic.isBlackTurn; this.summary = this.reversiLogic.summary; } }
app.html
<template> <main> <template for:each={renderRows} for:item="row"> <div key={row.key} style="height: 40px;"> <template for:each={row.value} for:item="col"> <reversi-column data-y={row.key} data-x={col.key} key={col.key} status={col.value} onputstone={handlePutStone}></reversi-column> </template> </div> </template> <template if:true={errorMessage}> <div style="background-color: red; padding: .25rem; text-align: center;">{errorMessage}</div> </template> <div style="text-align: center;"> <template if:true={isBlackTurn}><span>é»ã®ã¿ã¼ã³ã§ãï¼</span></template> <template if:false={isBlackTurn}><span>ç½ã®ã¿ã¼ã³ã§ãï¼</span></template> <span>{summaryString}</span> </div> <div style="text-align: center;"> <button type="button" onclick={handleSkip}>æçªãã¹ããã</button> </div> </main> </template>
app.css
main { margin: 30px; display: flex; flex-direction: column; align-items: center; }
column.css
.backgroud { display: inline-block; width: 40px; height: 40px; margin: 0; background-color: darkgreen; border: 1px solid midnightblue; } .circle { width: 38px; height: 38px; border-radius: 50%; } .white { background-color: white; border: 1px solid gray; } .black { background-color: #444; border: 1px solid black; } .click_able { cursor: pointer; display: inline-block; width: 100%; height: 100%; background-color: transparent; }
column.html
<template> <div class="backgroud" onclick={handleClick}> <template if:true={isBlank}> <div class="click_able"> </div> </template> <template if:true={isWhite}> <div class="circle white"> </div> </template> <template if:true={isBlack}> <div class="circle black"> </div> </template> </div> </template>
column.js
import { api, LightningElement } from 'lwc'; import { Constants } from 'reversi/logic'; export default class Column extends LightningElement { @api status = null; get isBlank() { return this.status === Constants.BOARD_EMPTY; } get isWhite() { return this.status === Constants.BOARD_WHITE; } get isBlack() { return this.status === Constants.BOARD_BLACK; } handleClick() { if (!this.isBlank) { return; } this.dispatchEvent(new CustomEvent('putstone')); } }
logic.js
const BOARD_WIDTH = 8; const BOARD_HEIGHT = 8; const BOARD_EMPTY = 0; const BOARD_BLACK = 1; const BOARD_WHITE = 2; const Constants = { BOARD_WIDTH, BOARD_HEIGHT, BOARD_EMPTY, BOARD_BLACK, BOARD_WHITE }; class Reversi { board = []; isBlackTurn = true; constructor() { this.initBoard(); } /** * ç¤é¢ã®ç¶æ ãè¿ã */ get summary() { let white = 0; let black = 0; for (let y = 0; y < BOARD_HEIGHT; y++) { for (let x = 0; x < BOARD_WIDTH; x++) { const col = this.board[y][x]; if (col === BOARD_BLACK) { black++; } if (col === BOARD_WHITE) { white++; } } } return { black, white }; } /** * æçªãã¹ããããã */ skipTurn() { this.isBlackTurn = !this.isBlackTurn; } /** * ç³ãç½®ã座æ¨ããããé»ç³ãã©ãã * @param {number} x * @param {number} y * @param {boolean} isBlack * @returns error message */ putStone(x, y, isBlack) { if (this.board.at(y)?.at(x) === null) { return 'ãããªæã«ç³ã¯ç½®ããªãï¼'; } if (this.board[y][x] !== BOARD_EMPTY) { return 'ããã«ã¯æ¢ã«ç³ãããã¾ãã'; } const vectors = [ [0, 1], [0, -1], // ä¸, ä¸ [1, 1], [1, -1], // å³ä¸, å³ä¸ [-1, 1], [-1, -1], // å·¦ä¸, å·¦ä¸ [1, 0], [-1, 0] // å³, å·¦ ]; // ç½®ãç³ã®è² const putStone = isBlack ? BOARD_BLACK : BOARD_WHITE; // ç³ãé ç½® this.board[y][x] = putStone; // ç³ãå転æä½ï¼å ¨æ¹åï¼ let revStones = 0; vectors.forEach(v => { revStones += this.flipStone(x, y, v[0], v[1], putStone, 0); }); // è£è¿ããç³ãç¡ããªããç³ã¯ç½®ããªã if (revStones === 0) { this.board[y][x] = BOARD_EMPTY; return 'è£è¿ããç³ãç¡ããããé ç½®ã§ãã¾ããã'; } // ã¿ã¼ã³å¤æ´ this.isBlackTurn = !isBlack; return null; } flipStone(currentX, currentY, vX, vY, putColor, depth) { let result = 0; // console.log(`flipStone(${currentX}, ${currentY}, ${vX}, ${vY}, ${putColor}, ${depth})`); // ç»é¢å¤ã«åºããå¼·å¶çµäº if (currentX < 0 || currentX >= BOARD_WIDTH || currentY < 0 || currentY >= BOARD_HEIGHT) { return 0; } if (depth === 0) { // èµ·ç¹åº§æ¨ã¯è©ä¾¡ããªãï¼putColor ç½®ãã座æ¨ã putColor ã¨ä¸è´ããã®ã¯èªæãªã®ã§ï¼ // 次ã®åº§æ¨ããã§ãã¯ãã result = this.flipStone(currentX + vX, currentY + vY, vX, vY, putColor, depth + 1); } else { // ãã以å¤ã¯è©ä¾¡ãã // ç¾å¨ã®åº§æ¨ã®ãã¹ãåå¾ const current = this.board.at(currentY)?.at(currentX); // ç»é¢ç«¯ â è¿ãç³ã¯ãªã if (current === null) { return 0; } // 空ç½ãåºç¾ â è¿ãç³ã¯ãªã if (current === BOARD_EMPTY) { return 0; } // åãè²ã®ç³ããã if (current === putColor) { return depth - 1; } // ä¸è¨ã©ãã§ããªã â æµå¯¾è²ã®ç³ããã(ãããã¯èµ·ç¹ã§ãã) // 次ã®åº§æ¨ããã§ãã¯ãã result = this.flipStone(currentX + vX, currentY + vY, vX, vY, putColor, depth + 1); if (result > 0) { // è¿ãç³ããã â ç¾å¨ã®åº§æ¨ã®ç³ãç½®ãæã㦠return this.board[currentY][currentX] = putColor; } } return result; } initBoard() { // ã·ã³ãã«ã«ãã¼ãã®é«ããå¹ ã§äºæ¬¡å é åãä½ã const root = []; for (let y=0; y<BOARD_HEIGHT; y++) { const row = []; for (let x=0; x<BOARD_WIDTH; x++) { row.push(BOARD_EMPTY); } root.push(row); } // çãä¸ã«åæé 置㮠ç½/é» ãç½®ã const X_CENTER = BOARD_WIDTH / 2; const Y_CENTER = BOARD_HEIGHT / 2; // é å㯠0 ããéå§ãªã®ã§ã4 ã¯ç»é¢å·¦ãã 5 ãã¹ç® root[X_CENTER - 1][Y_CENTER - 1] = BOARD_BLACK; root[X_CENTER][Y_CENTER - 1] = BOARD_WHITE; root[X_CENTER - 1][Y_CENTER] = BOARD_WHITE; root[X_CENTER][Y_CENTER] = BOARD_BLACK; this.board = root; } } export { Constants }; export default Reversi;