ãŠã§ãã¯æè¿éåžžã«éãåããŠãããç§ãã¡ã¯çãããç¥ã£ãŠããŸãã 仿¥ããªã¢ã¯ãã£ãããã°ã©ãã³ã°ã¯Webéçºã§æãããããªãããã¯ã®1ã€ã§ãããAngularãReactãªã©ã®ãã¬ãŒã ã¯ãŒã¯ã䜿çšããããšã§ãç¹ã«çŸä»£ã®JavaScriptã®äžçã§éåžžã«äººæ°ãé«ãŸã£ãŠããŸãã ã³ãã¥ããã£ã¯ãåœä»€åããã°ã©ãã³ã°ãã©ãã€ã ããæ©èœçãªåå¿åãã©ãã€ã ãžã®å€§èŠæš¡ãªç§»è¡ãçµéšããŠããŸãã ãã ããå€ãã®éçºè
ã¯ããã«å¯ŸåŠããããšãããã®è€éãïŒå€§èŠæš¡ãªAPIïŒãæèã®æ ¹æ¬çãªå€åïŒåœä»€åãã宣èšåãžïŒãããã³å€ãã®æŠå¿µã«ãã°ãã°å§åãããŸãã
ããã¯æãç°¡åãªãããã¯ã§ã¯ãããŸããããçè§£ã§ãããšããã«ããããªãã§ã©ã®ããã«çããããšãã§ããããèªåããŸãã
ãã®èšäºã¯ããªã¢ã¯ãã£ãããã°ã©ãã³ã°ã®ç޹ä»ãç®çãšãããã®ã§ã¯ãããŸãããå®å
šãªåå¿è
ã®æ¹ã¯ã次ã®ãªãœãŒã¹ããå§ãããŸãã
ãã®åºçç©ã®ç®çã¯ãç§ãã¡å
šå¡ãç¥ã£ãŠããã倧奜ããªå€å
žçãªãããªã²ãŒã ãSnakeãäœæããããšã«ãããåå¿çã«èããããšãåŠã¶ããšã§ãã ããããããªã²ãŒã ïŒ ããã¯é¢çœãã§ãããäŸãã°ã¹ã³ã¢ãã¿ã€ããŒããã¬ãŒã€ãŒã®åº§æšãªã©ãå€ãã®å€éšç¶æ
ãå«ãè€éãªã·ã¹ãã ã§ãã ãã®ããŒãžã§ã³ã§ã¯ãObservablesïŒobservablesïŒãåºã䜿çšããããã€ãã®ç°ãªãæŒç®åã䜿çšããŠãå€éšç¶æ
ãžã®ãµãŒãããŒãã£ã®åœ±é¿ãå®å
šã«åé¿ããŸãã ããæç¹ã§ãObservableã¹ããªãŒã ã®å€éšã«ç¶æ
ãä¿åããããšæããããããŸããããç¶æ
ãä¿åããå¥ã®å€éšå€æ°ã«äŸåããããªã¢ã¯ãã£ãããã°ã©ãã³ã°ã䜿çšããããšãå¿ããªãã§ãã ããã
ãæ³šæ RxJSã®ã¿ã§
HTML5ãš
JavaScriptã䜿çšã
㊠ãã€ãã³ãã«ãŒãããªã¢ã¯ãã£ãã€ãã³ãããŒã¹ã®ã¢ããªã±ãŒã·ã§ã³ã«å€æããŸãã
ã³ãŒãã¯
Githubã§å
¥æã§ã
ãŸã ããã¢ã¯
ãã¡ãããå
¥æã§ã
ãŸã ã ãããžã§ã¯ãã®ã¯ããŒã³ãäœæããå°ãæãå ããè峿·±ãæ°ããã²ãŒã æ©èœãå®è£
ããããšããå§ãããŸãã ãããããªãã
Twitterã§ç§ã«ã¡ãŒã«ããŠãã ããã
å
容- ã²ãŒã
- ã·ãŒã³èšå®
- ãœãŒã¹ã¹ããªãŒã ã®èå¥
- ãã管ç
- æ¹åãããŒïŒæ¹å$ïŒ
- é·ãã®è¿œè·¡
- æããšããŠã®BehaviorSubject
- ã¢ã«ãŠã³ãã®å®è£
ïŒã¹ã³ã¢$ïŒ
- ããã飌ããªããïŒãã$ïŒ
- ãããäœã
- ãã¹ãŠããŸãšãã
- ããã©ãŒãã³ã¹ãç¶æãã
- ã·ãŒã³è¡šç€º
- ä»åŸã®ä»äº
- ç¹å¥ãªæè¬
ã²ãŒã åè¿°ããããã«ã1970幎代åŸåã®å€å
žçãªãããªã²ãŒã ã§ããSnakeãåçŸããŸãã ãã ããã²ãŒã ãã³ããŒããã ãã§ãªããå°ãããªãšãŒã·ã§ã³ã远å ããŸãã ãããã²ãŒã ã®ä»çµã¿ã§ãã
ãã¬ã€ã€ãŒãšããŠãããªãã¯ç©ºè
¹ã®ããã®ããã«èŠããã©ã€ã³ãå¶åŸ¡ããŸãã ç®æšã¯ãã§ããã ãé·ãæé·ããããã«ãã§ããã ãå€ãã®ãªã³ãŽãé£ã¹ãããšã§ãã ãªã³ãŽã¯ãç»é¢äžã®ã©ã³ãã ãªäœçœ®ã«ãããŸãã ããããªã³ãŽãé£ã¹ããã³ã«ããã®å°Ÿã¯é·ããªããŸãã å£ã¯ããªããæ¢ããŸããïŒ ããããèããŠãã ãããããªãã¯ããªãã®äœã«ã©ããªç ç²ãæã£ãŠãå
¥ããªãããã«ããã¹ãã§ãã ããããªããšãã²ãŒã ãªãŒããŒã§ãã ã©ã®ãããçãå»¶ã³ãããŸããïŒ
ããããè¡ãããšã®äºåçãªäŸã瀺ããŸãã

ãã®ç¹å®ã®å®è£
ã§ã¯ãããã¯é ãé»ãå¡ãããéãæ£æ¹åœ¢ã®ç·ãšããŠè¡šãããŸãã æç©ãã©ã®ããã«èŠãããæããŠããããŸããïŒ ãŸãã«ãèµ€ãåè§ã ãã¹ãŠã®ãšã³ãã£ãã£ã¯æ£æ¹åœ¢ã§ããããã¯ããã®æ¹æ³ã§ããçŸããèŠããããã§ã¯ãªããéåžžã«åçŽãªå¹ŸäœåŠç圢ç¶ã§ãããæç»ããããããã§ãã ã°ã©ãã£ãã¯ã¹ã¯ããã»ã©çŽ æŽããããã®ã§ã¯ãããŸããããã²ãŒã ã¢ãŒãã§ã¯ãªããåœä»€åããã°ã©ãã³ã°ãããªã¢ã¯ãã£ãããã°ã©ãã³ã°ãžã®ç§»è¡ã«ã€ããŠè©±ããŠãããšããã§ãã
ã·ãŒã³èšå®ã²ãŒã ã®æ©èœã䜿çšããåã«ã匷åãªJavaScriptæç»APIãæäŸãã
ãã£ã³ãã¹èŠçŽ ïŒãã£ã³ãã¹ïŒãäœæããå¿
èŠããããŸãã ãã£ã³ãã¹ã䜿çšããŠãç«¶æå Žãããããªã³ãŽãåºæ¬çã«ã²ãŒã ã«å¿
èŠãªãã¹ãŠã®ãã®ãå«ãã°ã©ãã£ãã¯ãæç»ããŸãã èšãæãããšãã²ãŒã ã¯
ãã£ã³ãã¹èŠçŽ ã«å®å
šã«è¡šç€ºãã
ãŸã ã
ãããçæ°ããå Žåã¯ã
ãã®ããŒã¹ããŒã¿ãŒãºã®ãšãã°ãããã³ãŒã¹ãã芧ãã ããã
index.htmlãã¡ã€ã«ã¯ãã»ãšãã©ã®éæ³ãJavaScriptã§çºçãããããéåžžã«åçŽã§ãã
<html> <head> <meta charset="utf-8"> <title>Reactive Snake</title> </head> <body> <script src="/main.bundle.js"></script> </body> </html>
æ¬æã«è¿œå ããã¹ã¯ãªããã¯ãåºæ¬çã«ãã«ãããã»ã¹ã®çµæã§ããããã¹ãŠã®ã³ãŒããå«ãŸããŠããŸãã ãããããªã
bodyã« canvasã®ãããªèŠçŽ ããªãã®ãçåã«æããããããŸããã ããã¯ãJavaScriptã䜿çšããŠãã®èŠçŽ ãäœæããããã§ãã ããã«ã
è¡ãš
åã®æ°ãããã³ãã£ã³ãã¹ã®
å¹
ãš
é«ããæ±ºå®ããããã€ãã®å®æ°ã远å
ããŸãã
export const COLS = 30; export const ROWS = 30; export const GAP_SIZE = 1; export const CELL_SIZE = 10; export const CANVAS_WIDTH = COLS * (CELL_SIZE + GAP_SIZE); export const CANVAS_HEIGHT = ROWS * (CELL_SIZE + GAP_SIZE); export function createCanvasElement() { const canvas = document.createElement('canvas'); canvas.width = CANVAS_WIDTH; canvas.height = CANVAS_HEIGHT; return canvas; }
ããã«ããããã®é¢æ°ãåŒã³åºããŠããªã³ã¶ãã©ã€ã§
ãã£ã³ãã¹èŠçŽ ãäœæãããããããŒãžã®
æ¬æã«è¿œå ã§ããŸãã
let canvas = createCanvasElement(); let ctx = canvas.getContext('2d'); document.body.appendChild(canvas);
ãã£ã³ãã¹èŠçŽ ã§
getContextïŒ '2d'ïŒãåŒã³åºãããšã«ããã
CanvasRenderingContext2Dãžã®åç
§ãååŸããããšã«æ³šæããŠãã ããã ãã®ãã£ã³ãã¹2Dã¬ã³ããªã³ã°ã³ã³ããã¹ãã䜿çšãããšãããšãã°ãåè§åœ¢ãããã¹ããç·ããã¹ãªã©ãæç»ã§ããŸãã
ç§»åããæºåãã§ããŸããïŒ ã²ãŒã ã®åºæ¬çãªä»çµã¿ãå§ããŸãããã
ãœãŒã¹ã¹ããªãŒã ã®èå¥ã²ãŒã ã®äŸãšèª¬æã«åºã¥ããŠãæ¬¡ã®æ©èœãå¿
èŠã§ããããšãç¥ã£ãŠããŸãã
- ç¢å°ããŒã䜿çšããã¹ããŒã¯ã³ã³ãããŒã«
- ãã¬ã€ã€ãŒã¹ã³ã¢ã®è¿œè·¡
- ããã®è¿œè·¡ïŒé£äºãšæ
è¡ãå«ãïŒ
- ãã£ãŒã«ãäžã®ãªã³ãŽã®è¿œè·¡ïŒæ°ãããªã³ãŽã®äœæãå«ãïŒ
ãªã¢ã¯ãã£ãããã°ã©ãã³ã°ã§ã¯ãããŒã¿ã¹ããªãŒã ãå
¥åããŒã¿ã¹ããªãŒã ã«åºã¥ããããã°ã©ãã³ã°ã«ã€ããŠè©±ããŠããŸãã æŠå¿µçã«ã¯ããªã¢ã¯ãã£ãããã°ã©ã ãå®è¡ãããŠããå Žåãæ
å ±ãœãŒã¹ã®ç£èŠã確ç«ããããšãã°ãããŒããŒãã§ããŒãæŒããããšãããŸãã¯åã«ééã®æ¬¡ã®æ®µéã§ãã¢ããªã±ãŒã·ã§ã³ãšã®ãŠãŒã¶ãŒã€ã³ã¿ã©ã¯ã·ã§ã³ãªã©ã®å€æŽã«å¿çããŸãã ã§ããããå
šäœã
äœãå€ããã®ããçè§£
ããããšã§ãã ãããã®
倿Žã¯ãå€ãã®å Žåã
ãœãŒã¹ã¹ã¬ãããæ±ºå®ã
ãŸã ã äž»ãªã¿ã¹ã¯ã¯ããœãŒã¹ã¹ããªãŒã ãç¹å®ããããããçµã¿åãããŠãããšãã°ã²ãŒã ã®ç¶æ
ãªã©ãå¿
èŠãªãã¹ãŠãèšç®ããããšã§ãã
äžèšã®é¢æ°ãèŠãŠããœãŒã¹ã¹ã¬ãããèŠã€ããŠã¿ãŸãããã
ãŸã第äžã«ããŠãŒã¶ãŒå
¥åã¯ééããªãæéãšãšãã«å€åããŸãã ãã¬ã€ã€ãŒã¯ç¢å°ããŒã䜿çšããŠç©ºè
¹ã®ãããåãããŸãã ã€ãŸããæåã®ãœãŒã¹ã¹ããªãŒã ã¯
keydown $ã§ãããããŒãæŒããããã³ã«å€ã®å€æŽãããªã¬ãŒãããŸãã
次ã«ããã¬ãŒã€ãŒã®ã¹ã³ã¢ãç£èŠããå¿
èŠããããŸãã ã¹ã³ã¢ã¯ãäž»ã«ãããé£ã¹ããªã³ãŽã®æ°ã«äŸåããŸãã ãããæé·ãããã³ã«ã«ãŠã³ãã
1å¢ãããããããã«ãŠã³ãã¯ããã®é·ãã«äŸåãããšèšãããšãã§ããŸãã ãããã£ãŠã次ã®ãœãŒã¹ã¹ããªãŒã ã¯
snakeLength $ã§ãã
ç¹°ãè¿ãã«ãªããŸãããããšãã°ã¢ã«ãŠã³ããªã©ãå¿
èŠãªãã¹ãŠãèšç®ã§ãã
ã¡ã€ã³ãããŒã決å®ããããšãéèŠã§ãã ã»ãšãã©ã®å Žåãå
ã®ã¹ããªãŒã ã¯çµåãããããå
·äœçãªããŒã¿ã¹ããªãŒã ã«å€æãããŸãã ããã«å®éã«åäœããŸãã ãšãããããã¡ã€ã³ãããŒã®å®çŸ©ãç¶ããŸãããã
çŸæç¹ã§ã¯ããŠãŒã¶ãŒå
¥åã€ãã³ããšã¢ã«ãŠã³ããåãåã£ãŠããŸãã ç§ãã¡ã«ã¯ãããããªã³ãŽãªã©ãããå€ãã®
ã²ãŒã ãŸãã¯
ã€ã³ã¿ã©ã¯ãã£ãã¹ããªãŒã ãæ®ã£ãŠããŸãã
ããããå§ããŸãããã ããã®åºæ¬çãªã¡ã«ããºã ã¯åçŽã§ããããã¯æéãšãšãã«ç§»åããé£ã¹ããªã³ãŽãå¢ããã»ã©æé·ããŸãã ããã
ãã¹ããŒã¯
ã¹ããªãŒã ãšã¯æ£ç¢ºã«ã¯äœã§ããïŒ çŸæç¹ã§ã¯ã圌女ãé£ã¹ããæé·ãããããããšãå¿ããããšãã§ããŸããæåã«éèŠãªã®ã¯ã
200 msããšã«
5ãã¯ã»ã«ãªã©ã
æéãšãšãã«ç§»åãã
æéèŠå ã«äŸåãããã
ã§ã ã ãããã£ãŠã
ãœãŒã¹ã¹ããªãŒã ã¯ãäžå®æéããšã«å€ãéä¿¡ããééã§ããã
ticks $ãšåŒã³ãŸãã ãã®æµãã¯ãèã®éåºŠãæ±ºå®ããŸãã
æåŸã«ãªããŸãããããªã³ãŽã ãã¹ãŠãèæ
®ããŠããã£ãŒã«ãã«ãªã³ãŽã眮ãããšã¯éåžžã«ç°¡åã§ãã ãã®æµãã¯äž»ã«ããã«äŸåããŠããŸãã ãããç§»åãããã³ã«ãããã®é ããªã³ãŽãšè¡çªãããã©ããã確èªããŸãã ãã®å Žåããã®ãªã³ãŽãåé€ããŠããã£ãŒã«ãäžã®ä»»æã®äœçœ®ã«æ°ãããªã³ãŽãçæããŸãã åè¿°ã®ããã«ããªã³ãŽçšã®æ°ããããŒã¿ã¹ããªãŒã ãå°å
¥ããå¿
èŠã¯ãããŸããã
ããŠãã¡ã€ã³ã¹ã¬ããã«ã€ããŠã¯ä»¥äžã§ãã ã²ãŒã ã«å¿
èŠãªãã¹ãŠã®ã¹ããªãŒã ã®æŠèŠã¯æ¬¡ã®ãšããã§ãã
- keydown $ ïŒããŒæŒäžã€ãã³ãïŒKeyboardEventïŒ
- snakeLength $ ïŒããã®é·ãïŒæ°å€ïŒã衚ããŸã
- ticks $ ïŒããã®åãã衚ãééïŒæ°å€ïŒ
ãããã®ãœãŒã¹ã¹ããªãŒã ã¯ã²ãŒã ã®åºç€ã圢æããããããã¹ã³ã¢ãããããªã³ãŽã®ç¶æ
ãªã©ãå¿
èŠãªä»ã®ãã¹ãŠã®å€ãèšç®ã§ããŸãã
以äžã®ã»ã¯ã·ã§ã³ã§ã¯ããããã®åãœãŒã¹ãããŒãå®è£
ããå¿
èŠãªããŒã¿ãçæããããã«ããããé©çšããæ¹æ³ãè©³çŽ°ã«æ€èšããŸãã
ãã管çã³ãŒãã«é£ã³èŸŒã¿ãããã®å¶åŸ¡ã¡ã«ããºã ãå®è£
ããŸãããã åã®ã»ã¯ã·ã§ã³ã§è¿°ã¹ãããã«ãå¶åŸ¡ã¯ããŒããŒãå
¥åã«äŸåããŸãã ããã¯éåžžã«ç°¡åã§ããããšã倿ããæåã®ã¹ãããã¯ããŒããŒãã€ãã³ããã
芳å¯å¯èœãªã·ãŒã±ã³ã¹ãäœæããããšã§ãã ãããè¡ãã«ã¯ã
fromEventïŒïŒæŒç®åã䜿çšã§ããŸãã
let keydown$ = Observable.fromEvent(document, 'keydown');
ãããæåã®ãœãŒã¹ã¹ã¬ããã§ããããŠãŒã¶ãŒãããŒãæŒããã³ã«
KeyboardEventãããªã¬ãŒããŸãã æåéããã¹ãŠã®
ããŒããŠã³ãã€ãã³ããããªã¬ãŒããããšã«æ³šæããŠãã ããã ãã®ãããèå³ã®ãªãããŒã®ã€ãã³ããåãåããŸãããããã¯åºæ¬çã«ãç¢å°ããŒãé€ãä»ã®ãã¹ãŠã®ããŒã§ãã ãããããã®ç¹å®ã®åé¡ã解決ããåã«ãæ¹åã®æ°žç¶çãªããããå®çŸ©ããŸãã
export interface Point2D export interface Directions export const DIRECTIONS: Directions = ,
KeyboardEventãªããžã§ã¯ããèŠããšãåããŒã«äžæã®
keyCodeããããšä»®å®ã§ããŸãã ç¢å°ããŒã®ã³ãŒããååŸããã«ã¯ã
ãã®ããŒãã«ã䜿çšã§ããŸãã
åæ¹åã®ã¿ã€ãã¯
Point2Dã§ ãããã¯åãªãxããã³yããããã£ãæã€ãªããžã§ã¯ãã§ãã åããããã£ã®å€ã¯
1 ã
-1ããŸãã¯
0ã§ããããã©ãã«ãããã瀺ããŸãã åŸã§ããã®æ¹åã䜿çšããŠãèã®é ãšå°Ÿã®æ°ããã¡ãã·ã¥äœçœ®ãååŸããŸãã
æ¹åãããŒïŒæ¹å$ïŒãã®ããããã§ã«ããŒ
ããŠã³ã€ãã³ãã®ã¹ããªãŒã ãããããã¬ãŒã€ãŒãããŒãæŒããã³ã«ãå€ïŒ
KeyboardEvent ïŒãäžèšã®æ¹åãã¯ãã«ã®ããããã«äžèŽãããå¿
èŠããããŸãã ãããè¡ãã«ã¯ã
mapïŒïŒæŒç®åã䜿çšããŠãåããŒããŒãã€ãã³ããæ¹åãã¯ãã«ã«æåœ±ããŸãã
let direction$ = keydown$ .map((event: KeyboardEvent) => DIRECTIONS[event.keyCode])
åè¿°ã®ããã«ãæåããŒãªã©ã®èå³ã®ãªãã€ãã³ããé€å€ããªãããã
ãã¹ãŠã®ããŒã¹ãããŒã¯ã€ãã³ããåãåããŸãã ãã ããæ¹åãããã§ã€ãã³ãã衚瀺ããããšã§ãæ¢ã«ã€ãã³ãããã£ã«ã¿ãªã³ã°ããŠãããšèšããŸãã ãã®ãããã§å®çŸ©ãããŠããªãå
keyCodeã«ã€ããŠã¯ã
undefinedãšããŠè¿ãããŸãã ãã ããå®éã«ã¯ã¹ããªãŒã å
ã®å€ã¯ãã£ã«ã¿ãªã³ã°ãããªãããã
filterïŒïŒæŒç®åã䜿çšããŠç®çã®å€ã®ã¿ãåŠçã§ããŸãã
let direction$ = keydown$ .map((event: KeyboardEvent) => DIRECTIONS[event.keyCode]) .filter(direction => !!direction)
ãŸããããã¯ç°¡åã§ããã äžèšã®ã³ãŒãã¯æ£åžžã«æ©èœããæåŸ
ã©ããã«æ©èœããŸãã ãããããŸã æ¹åãã¹ãããšããããŸãã äœãæ£ç¢ºã«æšæž¬ããŸãããïŒ
1ã€ã®ã¢ã€ãã¢ã¯ãããšãã°ãããå察æ¹åã«é²ãã®ãæ¢ããããšããããšã§ãã å³ããå·ŠãŸãã¯äžããäžã å®éãã«ãŒã«1ã¯èªåã®å°»å°Ÿã«å
¥ããªãããã«ããããããã®åäœãèš±å¯ããŠãæå³ããããŸããã
解決çã¯éåžžã«ç°¡åã§ãã åã®æ¹åããã£ãã·ã¥ããæ°ããã€ãã³ããããªã¬ãŒããããšãæ°ããæ¹åãå察æ¹åãšç°ãªããã©ããã確èªããŸãã
æ¬¡ã®æ¹åãèšç®ãã颿°ã¯
次ã®ãšããã§ãã
export function nextDirection(previous, next) { let isOpposite = (previous: Point2D, next: Point2D) => { return next.x === previous.x * -1 || next.y === previous.y * -1; }; if (isOpposite(previous, next)) { return previous; } return next; }
äœããã®æ¹æ³ã§ä»¥åã®æ¹åãæ£ãã远跡ããå¿
èŠããããããObservableïŒ
observable ïŒæ
å ±ãœãŒã¹ã®å€éšã«ç¶æ
ãä¿åãããã®ã¯ãããåããŠã§ã...ç°¡åãªè§£æ±ºçã¯ãå€éšç¶æ
倿°ã«ä»¥åã®æ¹åãåã«ä¿åããããšã§ãã ããããã¡ãã£ãšåŸ
ã£ãŠãã ããïŒ çµå±ããããé¿ãããã£ãã®ã§ãããïŒ
å€éšå€æ°ãåé¿ããã«ã¯ãéçŽãããç¡éã®ObservableããœãŒãããæ¹æ³ãå¿
èŠã§ãã RxJSã«ã¯ãåé¡ã®
scanïŒïŒã解決ããããã«äœ¿çšã§ããéåžžã«äŸ¿å©ãªæŒç®åããããŸãã
scanïŒïŒæŒç®åã¯
Array.reduceïŒïŒãšéåžžã«äŒŒãŠããŸãããæåŸã®å€ãè¿ãã ãã§ãªããåäžéçµæã®éä¿¡ãéå§ããŸãã
scanïŒïŒã䜿çš
ãããšãåºæ¬çã«å€ãèç©ããçä¿¡ã€ãã³ãã®ãããŒã1ã€ã®å€ã«ç¡éã«æžããããšãã§ããŸãã ãããã£ãŠãå€éšç¶æ
ã«äŸåããã«åã®æ¹åã远跡ã§ããŸãã
ãããé©çšããŠãæçµçãª
æ¹å$ã¹ããªãŒã ãèŠãŠã¿ãŸãããã
let direction$ = keydown$ .map((event: KeyboardEvent) => DIRECTIONS[event.keyCode]) .filter(direction => !!direction) .scan(nextDirection) .startWith(INITIAL_DIRECTION) .distinctUntilChanged();
ObservableïŒ
keydown $ ïŒãœãŒã¹ããã®å€ã®éä¿¡ãéå§ããåã«ã
startWithïŒïŒã䜿çšããŠåæå€ãéå§ããããšã«æ³šæããŠãã ããã ãã®æŒç®åããªããšãObservableã¯ãã¬ãŒã€ãŒãããŒãæŒãããšãã«ã®ã¿å€ã®éä¿¡ãéå§ããŸãã
2çªç®ã®æ¹åç¹ã¯ãæ°ããæ¹åãåã®æ¹åãšç°ãªãå Žåã«ã®ã¿å€ã®éä¿¡ãéå§ããããšã§ãã ã€ãŸãã
ç°ãªãå€ã®ã¿ãå¿
èŠ
ã§ã ã äžèšã®ã¹ããããã§
distinctUntilChangedïŒïŒã«æ°ã¥ãããããããŸããã ãã®æŒç®åã¯ç§ãã¡ã®ããã«æ±ãä»äºãããç¹°ãè¿ãèŠçŽ ãæå¶ããŸãã
distinctUntilChangedïŒïŒã¯ãå¥ã®å€ãéžæãããŠããªãéããåãå€ã®ã¿ãé€å€ããããšã«æ³šæããŠãã ããã
次ã®å³ã¯ã
$ã¹ããªãŒã ã®
æ¹åãšãã®ä»çµã¿ãèŠèŠåãããã®ã§ãã éè²ã®å€ã¯åæå€ã衚ããé»è²ã¯Observableã¹ããªãŒã ã§å€ã倿Žãããããšãæå³ãã
çµæã¹ããªãŒã ã§éä¿¡ãããå€ã¯ãªã¬ã³ãžè²ã«ãªããŸãã
é·ãã®è¿œè·¡ããèªäœã«æ°ä»ãåã«ããã®é·ãã远跡ããæ¹æ³ãçè§£ããŸãããã æåã«é·ããå¿
èŠãªã®ã¯ãªãã§ããïŒ ããŠããã®æ
å ±ã䜿çšããŠã¢ã«ãŠã³ããã¢ãã«åããŸãã åœä»€åã®äžçã§ã¯ããããç§»åãããã³ã«è¡çªããã£ããã©ããã確èªããããã§ããã°ã«ãŠã³ããå¢ãããŸãã ãããã£ãŠãå®éã«ã¯ãé·ãã远跡ããå¿
èŠã¯ãããŸããã ãã ãããã®ãããªã¢ãããŒãã§ã¯ãå¥ã®å€éšç¶æ
倿°ãå°å
¥ãããå¯èœæ§ããããŸãã
ãžã§ããã®äžçã§ã¯ã解決çã¯å°ãç°ãªããŸãã åçŽãªã¢ãããŒãã®1ã€ã¯ã
snake $ã¹ããªãŒã ã䜿çšããããšã§ããå€ãéä¿¡ãããã³ã«ãsnakeã®é·ããå¢å ããŠããããšãããããŸãã
snake $ã®å®è£
ã«æ¬åœã«äŸåããŠããŸããããã®ã¹ã¬ããã¯ãŸã å®è£
ããŸããã æåãããããã¯
ãã£ãã¯$ã«äŸåããŠããããšãããã£ãŠããŸããããã¯ããããäžå®ã®è·é¢ãç§»åããããã§ãã ãã®æ¹æ³ã§ãsnake $ã¯bodyã»ã°ã¡ã³ãã®é
åãèç©ãã
ãã£ãã¯$ã«åºã¥ããŠããããã
xããªç§ããšã«å€ãçæããŸãã ãã ããããšãã¹ããŒã¯ãäœããšè¡çªããªããŠãã
ã¹ããŒã¯$ã¯å€ãéä¿¡ããŸãã ããã¯ããããåžžã«ãã£ãŒã«ãäžãç§»åããŠãããããé
åãåžžã«ç°ãªãããã§ãã
ç°ãªãã¹ã¬ããéã«ã¯ç¹å®ã®äŸåé¢ä¿ãããããããããçè§£ããã®ã¯å°ãé£ããå ŽåããããŸãã ããšãã°ã
ããã$ã¯
è$ã«äŸåããŸãã ããã¯ããããç§»åãããã³ã«ããããã®éšåã®ããããããªã³ãŽãšè¡çªãããã©ããã確èªããããã«ãäœã®ã»ã°ã¡ã³ãã®é
åãå¿
èŠã ããã§ãã
apples $ã¹ããªãŒã ã¯
ãªã³ãŽã®é
åãèç©ããŸãããåæã«åŸªç°äŸåãåé¿ããè¡çªã¢ããªã³ã°ã¡ã«ããºã ãå¿
èŠã§ãã
æããšããŠã®BehaviorSubjectãã®åé¡ã®è§£æ±ºçã¯ã
BehaviorSubjectã䜿çšã
ãŠãããŒããã£ã¹ãã¡ã«ããºã ãå®è£
ããããšã§ãã RxJSã¯ãããŸããŸãªæ©èœãæã€ããŸããŸãªçš®é¡ã®ãµããžã§ã¯ããæäŸããŸãã ãããã£ãŠã
Subjectã¯ã©ã¹ã¯ãããå°éåããã
Subjectãäœæããããã®åºç€ãæäŸããŸãã äžèšã§èšãã°ãSubjectã¯ObserverïŒ
observer ïŒåãšObservableïŒ
observable ïŒåã®äž¡æ¹ãå®è£
ããåã§ãã ãªãã¶ãŒããã«ã¯ããŒã¿ã®æµããå®çŸ©ããŠããŒã¿ãäœæããŸããããªãã¶ãŒããŒã¯ãªãã¶ãŒããã«ã«ãµãã¹ã¯ã©ã€ãããŠããŒã¿ãåä¿¡ã§ããŸãã
BehaviorSubjectã¯ãæéã®çµéãšãšãã«å€åããå€ãæäŸãããããå°éçãªãµããžã§ã¯ãã§ãã ããã§ããªãã¶ãŒããŒã
BehaviorSubjectã«ãµãã¹ã¯ã©ã€ããããšãéä¿¡ãããæåŸã®å€ãåä¿¡ãããã®åŸãã¹ãŠã®å€ãåä¿¡ããŸãã ãã®äžææ§ã¯ã
åæå€ãå«ãŸããŠãããšããäºå®ã«ããããããã¹ãŠã®ãªãã¶ãŒããŒã¯ãµãã¹ã¯ã©ã€ãæã«å°ãªããšã1ã€ã®å€ãåãåããŸãã
ç¶ããŠã
SNAKE_LENGTHã®åæå€ãæã€æ°ãã
BehaviorSubjectãäœæããŸãã
// SNAKE_LENGTH specifies the initial length of our snake let length$ = new BehaviorSubject<number>(SNAKE_LENGTH);
ãã®æç¹ãããsnakeLength $ãå®è£
ããããã®å°ããªã¹ããããæ®ã£ãŠããŸãã
let snakeLength$ = length$ .scan((step, snakeLength) => snakeLength + step) .share();
äžèšã®ã³ãŒãã§ã¯ã
snakeLength $㯠BehaviorSubjectã§ãã
length $ã«åºã¥ããŠããããšã
ããããŸãã ããã¯ã
nextïŒïŒã䜿çšããŠSubjectã«æ°ããå€ãæž¡ããã³ã«ãå€ã
snakeLength $ã«éä¿¡ããããšã
æå³ããŸãã ããã«ã
scanïŒïŒã䜿çšããŠãæéã®çµéãšãšãã«é·ãã环ç©ããŸãã ã¯ãŒã«ã§ããããã®
å
±æïŒïŒãäœã§ãããçåã«æããããããŸããããïŒ
ãã§ã«è¿°ã¹ãããã«ã
snakeLength $ã¯åŸã§
snake $ã®å
¥åãšããŠäœ¿çšãããŸãããåæã«ãã¬ãŒã€ãŒã®ã¢ã«ãŠã³ãã®ãœãŒã¹ã¹ããªãŒã ãšããŠæ©èœããŸãã ãã®çµæãåãObservableãžã®2çªç®ã®ãµãã¹ã¯ãªãã·ã§ã³ã§ãã®å
ã®ã¹ããªãŒã ã
åäœæããŸãã ããã¯ã
é·ã$ã
å·ãã Observableã§ããããã§ãã
ããããªãã¶ãŒããã«ãšã³ãŒã«ããªãã¶ãŒããã«ã«å®å
šã«æ
£ããŠããªãå Žåã¯ãã³ãŒã«ããªã
ããããªãã¶ãŒããã«ã«é¢ããèšäºãæžããŸããã
å®éã
shareïŒïŒã䜿çšããŠObservableãžã®ãã«ããµãã¹ã¯ãªãã·ã§ã³ãèš±å¯ããŸããããããªããšããµãã¹ã¯ãªãã·ã§ã³ããšã«ãœãŒã¹ãåäœæããŸãã ãã®ã¹ããŒãã¡ã³ãã¯ãå
ã®ãœãŒã¹ãšå°æ¥ã®ãã¹ãŠã®ãµãã¹ã¯ã©ã€ããŒã®éã«ãµããžã§ã¯ããèªåçã«äœæããŸãã ãµãã¹ã¯ã©ã€ãã®æ°ã0ãã1ã«ãªããšããã«ãSubjectãããŒã¹ã®ObservableãœãŒã¹ã«æ¥ç¶ãããã¹ãŠã®éç¥ã®éä¿¡ãéå§ããŸãã å°æ¥ã®ãã¹ãŠã®ãµãã¹ã¯ã©ã€ããŒã¯ãã®äžéãµããžã§ã¯ãã«æ¥ç¶ããããããåºç€ãšãªãã³ãŒã«ããªãã¶ãŒããã«ã®ãµãã¹ã¯ãªãã·ã§ã³ã¯1ã€ã ãã§ããããšã广çã§ãã ããã¯
ãã«ããã£ã¹ããšåŒã°ããç®ç«ã€ã®ã«åœ¹ç«ã¡ãŸãã
ãããïŒ è€æ°ã®ãµãã¹ã¯ã©ã€ããŒã«å€ãæž¡ãããã«äœ¿çšã§ããã¡ã«ããºã ãçšæã§ããã®ã§ã次ã¯
ã¹ã³ã¢$ãå®è£
ããŸãã
ã¢ã«ãŠã³ãã®å®è£
ïŒã¹ã³ã¢$ïŒãã¬ãŒã€ãŒã®ã¢ã«ãŠã³ãã®å®è£
ã¯ã§ããã ãç°¡åã§ãã
snakeLength $ã
è£
åããŠã
ã¹ã³ã¢$ã¹ããªãŒã ãäœæã§ããŸããããã¯ã
scanïŒïŒã䜿çšããŠãã¬ãŒã€ãŒã®ã¹ã³ã¢ãåçŽã«çޝç©ããŸãã
let score$ = snakeLength$ .startWith(0) .scan((score, _) => score + POINTS_PER_APPLE);
æ¬è³ªçã«ãè¡çªãçºçããããšããµãã¹ã¯ã©ã€ããŒã«éç¥ããããã«
snakeLength $ãŸãã¯ããã
length $ã䜿çšããŸãããã®å Žåã¯ããªã³ãŽãããäžå®ã®ãã€ã³ãæ°ã§ãã
POINTS_PER_APPLEã ãã¹ã³ã¢ã
å¢ãããŸãã åæå€ã®æå®ãé¿ããããã«ã
startWithïŒ0ïŒã scanïŒïŒã®åã«è¿œå ããå¿
èŠãããããšã«æ³šæããŠãã ããã
å®è£
ããå
容ã®ããèŠèŠçãªè¡šçŸãèŠãŠã¿ãŸãããã

äžã®å³ãèŠããšããªã
BehaviorSubjectã®åæå€ã
snakeLength $ã«ã®ã¿è¡šç€ºããã
ã¹ã³ã¢$ã«ãªãã®ãçåã«æããããããŸããã ããã¯ãæåã®ãµãã¹ã¯ã©ã€ããŒã
å
±æïŒïŒãåºã«ãªãããŒã¿ãœãŒã¹ã«ãµãã¹ã¯ã©ã€ããããå
ã®ããŒã¿ãœãŒã¹ãçŽã¡ã«å€ãéä¿¡ããããããã®å€ã¯åŸç¶ã®ãµãã¹ã¯ãªãã·ã§ã³ãçºçãããŸã§ã«éä¿¡ãããããã§ãã
çŽ æŽãããã ãã®å Žæããå§ããŠãããã®ãããŒãå®è£
ããŸãããã ãšããµã€ãã£ã³ã°ã§ã¯ãããŸãããïŒ
ããã飌ããªããïŒãã$ïŒãã®æç¹ã§ãç§ãã¡ã¯ãã§ã«å€ãã®æŒç®åãåŠãã§ãããä»ã§ã¯ãããã䜿çšããŠ
snake $ã¹ããªãŒã ãå®è£
ã§ããŸãã ãã®èšäºã®åé ã§èª¬æããããã«ã空è
¹ã®ãããåããç¶ããããã®
ãã£ãã«ãŒãå¿
èŠã§ãã ãã®
ééïŒxïŒã«ã¯ ã
xããªç§ããšã«å€ãéä¿¡ãã䟿å©ãªã¹ããŒãã¡ã³ãããããŸãã åå€
tickãåŒã³åºããŸãããã
let ticks$ = Observable.interval(SPEED);
ãã®ç¬éããæçµã¹ããªãŒã ã®å®è£
ãŸã§ã
snake $ã¯ããªãã®éã§ãã ãã£ãã¯ããšã«ãããããªã³ãŽãé£ã¹ããã©ããã«å¿ããŠããªã³ãŽãåæ¹ã«ç§»åããããæ°ããã»ã°ã¡ã³ãã远å ããŸãã ãããã£ãŠã身è¿ãª
scanïŒïŒé¢æ°ã䜿çšããŠãäœã®ã»ã°ã¡ã³ãã®é
åãèç©ã§ããŸãã ãããããæ³åã®ãšãããç§ãã¡ã®åã«çåãçããŸãã
æ¹å$ãŸãã¯
snakeLength $ã®ãããŒã¯ã©ãã§
äœçšããŸããïŒ
çµ¶å¯Ÿã«æ£åœãªè³ªåã ãã®æ
å ±ã¯ã芳å¯ãããã¹ããªãŒã ã®å€éšã®å€æ°ã«ãã®æ
å ±ãæ ŒçŽãããšã
ããã®$ã¹ããªãŒã å
ã§ããã®é·ããšåæ§ã«ç°¡åã«ã¢ã¯ã»ã¹ã§ããŸãã ããããåã³ãå€éšã®ç¶æ
ãå€ããªããšããèŠåã«éåããŸãã
幞ããªããšã«ãRxJSã¯
withLatestFromïŒïŒãšåŒã°ããå¥ã®éåžžã«äŸ¿å©ãªæŒç®åãæäŸããŸãã ããã¯ã¹ã¬ãããçµåããããã«äœ¿çšãããæŒç®åã§ããããŸãã«ç§ãã¡ãæ¢ããŠãããã®ã§ãã ãã®ã¹ããŒãã¡ã³ãã¯ãçµæã¹ããªãŒã ã«ããŒã¿ãéä¿¡ããã¿ã€ãã³ã°ãå¶åŸ¡ãããã©ã€ããªã¹ããªãŒã ã«é©çšãããŸãã ã€ãŸãã
withLatestFromïŒïŒã¯ãã»ã«ã³ããªã¹ããªãŒã ããŒã¿ã®éä¿¡ãèŠå¶ããæ¹æ³ãšèããããšãã§ããŸãã
äžèšãèãããšã空è
¹ã®
$ snakeã®æçµçãªå®è£
ã«å¿
èŠãªããŒã«ããããŸãã
let snake$ = ticks$ .withLatestFrom(direction$, snakeLength$, (_, direction, snakeLength) => [direction, snakeLength]) .scan(move, generateSnake()) .share();
â
ticks$ , , ,
direction$ ,
snakeLength$ . , , , ,
.
,
(
)
withLatestFrom , ,
. , , .
move() , . ,
GitHub .
, :
direction$ ? ,
withLatestFrom() ,
, Observable (
),
.
, , . , .
,
direction$ ,
snakeLength$ ,
score$ snake$ . , . , . .
, . -, , . , , . . ?
,
scan() . , , , , . ,
.
distinctUntilChanged() .
let apples$ = snake$ .scan(eat, generateApples()) .distinctUntilChanged() .share();
ãã£ãããïŒ , ,
apples$ , , . , ,
snake$ ,
snakeLength$ , , .
, ? .
eat() :
export function eat(apples: Array<Point2D>, snake) { let head = snake[0]; for (let i = 0; i < apples.length; i++) { if (checkCollision(apples[i], head)) { apples.splice(i, 1); // length$.next(POINTS_PER_APPLE); return [...apples, getRandomPosition(snake)]; } } return apples; }
length$.next(POINTS_PER_APPLE) . , ( ES2015). ES2015 , . , , .
,
applesEaten$ .
apples$ , , -
,
length$.next() .
do() , .
. -
() ,
apples$ . , , . , RxJS ,
skip() .
,
applesEaten$ , .
.
let appleEaten$ = apples$ .skip(1) .do(() => length$.next(POINTS_PER_APPLE)) .subscribe();
, , , â
scene$ .
combineLatest .
withLatestFrom , . -, :
let scene$ = Observable.combineLatest(snake$, apples$, score$, (snake, apples, score) => ({ snake, apples, score }));
, , ,
Observables (
) . , , . , .

, - .
, 60 .
, ,
ticks$ , . :
// Interval expects the period to be in milliseconds which is why we devide FPS by 1000 Observable.interval(1000 / FPS)
, JavaScript . , . , . , , . , . .
,
requestAnimationFrame , . Observable? , ,
interval() ,
Scheduler (
). ,
Scheduler â ,
- .
RxJS , , ,
animationFrame .
window.requestAnimationFrame .
ãããïŒ , Observable
game$ :
16 , 60 FPS.
game$ scene$ . , ? , , , 60 .
game$ , , ,
scene$ . ? ,
withLatestFrom .
// Note the last parameter const game$ = Observable.interval(1000 / FPS, animationFrame) .withLatestFrom(scene$, (_, scene) => scene) .takeWhile(scene => !isGameOver(scene)) .subscribe({ next: (scene) => renderScene(ctx, scene), complete: () => renderGameOver(ctx) });
,
takeWhile() . , Observable. game$ ,
isGameOver() true .
:

, , . , ,
,
.
é£çµ¡ãåãåããŸãããïŒ
James Henry Brecht Billiet .