UNPKG

@reis/seki

Version:

Seki – A modern javascript based Go board renderer and player, that is simple to use, extensible, compact and intuitive.

597 lines (476 loc) 12.3 kB
import PlayerMode from './player-mode.js' import MarkupFactory from '../markup-factory.js' import {boardLayerTypes} from '../../constants/board.js' import {markupTypes} from '../../constants/markup.js' import {playerModes, playerActions} from '../../constants/player.js' import {throttle} from '../../helpers/util.js' /** * Replay game records with this mode */ export default class PlayerModeReplay extends PlayerMode { //Mode type mode = playerModes.REPLAY //Auto play settings isAutoPlaying = false autoPlayTimeout = null //Track last move and variation markers we've put on the board markers = [] /** * Initialise */ init() { //Extend player this.extendPlayer() //Create bound event listeners this.createBoundListeners({ keydown: 'onKeyDown', click: 'onClick', wheel: 'onMouseWheel', config: 'onConfigChange', pathChange: 'onPathChange', variationChange: 'onVariationChange', gameLoad: 'onGameLoad', }) //Create throttled config change handler const fn = throttle(event => { if (event.detail.key === 'autoPlayDelay' && this.isAutoPlaying) { this.queueNextAutoPlay() //This will reset the timeout } }, 100) //Config change listener this.player.on('config', fn) } /** * Extend the player with new methods */ extendPlayer() { //Get data const {player, mode} = this //Extend player player.extend('startAutoPlay', mode) player.extend('stopAutoPlay', mode) player.extend('toggleAutoPlay', mode) } /** * Activate this mode */ activate() { //Parent method super.activate() //Render markers this.renderMarkers() } /** * Deactivate this mode */ deactivate() { //Parent method super.deactivate() //Stop auto play and clear markers this.stopAutoPlay() this.clearMarkers() } /************************************************************************** * Event listeners ***/ /** * Keydown events */ onKeyDown(event) { //Get data const {player} = this const {nativeEvent} = event.detail const action = player.getActionForKeyDownEvent(nativeEvent) //Process action if (action) { this.processAction(action, event) } } /** * Handler for mousewheel events */ onMouseWheel(event) { //Get data const {player, board} = this const {nativeEvent} = event.detail const action = player.getActionForMouseEvent(nativeEvent) //Clear hover board.clearHoverLayer() //Process action if (action) { this.processAction(action, event) } } /** * Click handler */ onClick(event) { //Check if valid coordinates if (!this.hasValidCoordinates(event)) { return } //Get data const {player, game} = this const {x, y} = event.detail //Clicked on move variation, select that variation if (game.isMoveVariation(x, y)) { this.selectMoveVariation(x, y) } else { player.goToNextPosition() } } /** * Config change event */ onConfigChange(event) { //The following config keys require a board redraw const redrawKeys = [ 'showLastMove', 'showNextMove', 'showVariations', 'showSiblingVariations', 'showAllMoveNumbers', 'showLastMoveNumber', 'showVariationMoveNumbers', 'rememberVariationPaths', ] //Clear keys const clearKeys = [ 'showAllMoveNumbers', 'showVariationMoveNumbers', ] //Redraw board if needed if (redrawKeys.includes(event.detail.key)) { //Clear all markers if (clearKeys.includes(event.detail.key)) { this.clearMarkers() } //Use patch change event to render the markers this.onPathChange() } } /** * Path change event */ onPathChange() { //Get data const {player, game, isAutoPlaying} = this //Reset path index if not remembering if (!player.getConfig('rememberVariationPaths')) { game.resetCurrentPathIndex() } //Check if auto playing if (isAutoPlaying) { if (!game.hasNextPosition()) { this.stopAutoPlay() } else { this.queueNextAutoPlay() } } //Render markers this.renderMarkers() } /** * Game loaded */ onGameLoad() { this.stopAutoPlay() } /** * On variation change */ onVariationChange() { this.renderMarkers() } /************************************************************************** * Actions ***/ /** * Process an action */ processAction(action, event) { //Parent method if (super.processAction(action, event)) { return true } //Get data const {player} = this //Determine action switch (action) { //Auto play case playerActions.START_AUTO_PLAY: player.startAutoPlay() return true case playerActions.STOP_AUTO_PLAY: player.stopAutoPlay() return true case playerActions.TOGGLE_AUTO_PLAY: player.toggleAutoPlay() return true } //No action was performed return false } /** * Toggle auto play */ toggleAutoPlay() { if (this.isAutoPlaying) { this.stopAutoPlay() } else { this.startAutoPlay() } } /** * Start auto play */ startAutoPlay() { //Get data const {player, game, isAutoPlaying} = this const autoPlayStartsImmediately = player.getConfig('autoPlayStartsImmediately') //Already auto playing or no next position? if (isAutoPlaying || !game.hasNextPosition()) { return } //If starting immediately, go to the next position right away if (autoPlayStartsImmediately) { player.goToNextPosition() } //Toggle flag and queue next move this.isAutoPlaying = true this.queueNextAutoPlay() //Trigger event player.triggerEvent('autoPlayToggle', {isAutoPlaying: true}) } /** * Stop auto play */ stopAutoPlay() { //Get data const {player, autoPlayTimeout} = this //Clear timeout clearTimeout(autoPlayTimeout) //Clear flags this.isAutoPlaying = false this.autoPlayTimeout = null //Trigger event player.triggerEvent('autoPlayToggle', {isAutoPlaying: false}) } /** * Queue next auto play move */ queueNextAutoPlay() { //Get data const {player, autoPlayTimeout} = this const autoPlayDelay = player.getConfig('autoPlayDelay', 1000) //Clear any existing timeout clearTimeout(autoPlayTimeout) //Create timeout for next move this.autoPlayTimeout = setTimeout(() => { player.goToNextPosition() player.triggerEvent('autoPlayed') }, autoPlayDelay) } /** * Render markers */ renderMarkers() { //Get data const {player, game, board} = this const {node} = game //Get settings const showLastMove = player.getConfig('showLastMove') const showNextMove = player.getConfig('showNextMove') const showVariations = player.getConfig('showVariations') const showSiblingVariations = player.getConfig('showSiblingVariations') const showAllMoveNumbers = player.getConfig('showAllMoveNumbers') const showLastMoveNumber = player.getConfig('showLastMoveNumber') const showVariationMoveNumbers = player.getConfig('showVariationMoveNumbers') //Clear hover layer board.clearHoverLayer() //Clear exsting markers this.clearMarkers() //Show sibling variations if (showVariations && showSiblingVariations) { if (node.parent && node.parent.hasMultipleMoveVariations()) { this.addMoveVariationMarkers(node.parent) } } //Show child variations or next move if we have more than one move variation if ((showVariations || showNextMove) && node.hasMultipleMoveVariations()) { this.addMoveVariationMarkers(node, showVariations) } //Show next move only else if (showNextMove && node.hasMoveVariations()) { this.addMoveVariationMarkers(node, false) } //Show all move numbers if (showAllMoveNumbers) { this.numberAllMoves(node) } //Number variation moves else if (showVariationMoveNumbers && node.isVariationBranch()) { this.numberVariationMoves(node) } //Show last move number else if (showLastMoveNumber) { this.numberLastMove(node) } //Last move else if (showLastMove) { this.addLastMoveMarker(node) } } /** * Add move variation markers */ addMoveVariationMarkers(node, showText = false) { //Get data const {board, markers} = this const variations = node.getMoveVariations() //Loop variations variations.forEach((variation, i) => { //Get data const {move} = variation const {x, y, color: displayColor} = move //Not on top of stones (if displaying sibling variations) if (board.has(boardLayerTypes.STONES, x, y)) { return } //Already has markup on this coordinate, preserve it if (node.hasMarkup(x, y)) { return } //Construct data for factory const index = i const isSelected = node.isSelectedPath(variation) const data = {index, displayColor, showText, isSelected} //Add to markers markers.push({x, y}) //Add to board board .add(boardLayerTypes.MARKUP, x, y, MarkupFactory .create(markupTypes.VARIATION, board, data)) }) } /** * Select move variation */ selectMoveVariation(x, y) { //Get data const {player, game} = this const i = game.getMoveVariationIndex(x, y) //Follow a move variation player.goToNextPosition(i) } /** * Add last move marker */ addLastMoveMarker(node) { //Not a play move if (!node.isPlayMove()) { return } //Get data const {board, markers} = this const {x, y} = node.move //Already has markup on this coordinate, preserve it if (node.hasMarkup(x, y)) { return } //Store markers.push({x, y}) //Add to board board .add(boardLayerTypes.MARKUP, x, y, MarkupFactory .create(markupTypes.LAST_MOVE, board)) } /** * Number variation moves */ numberVariationMoves(node) { //Get variation nodes const {board, markers} = this const nodes = node.getVariationMoveNodes() //Loop each nodes.forEach((node, i) => { //Get node data const {x, y} = node.move const number = i + 1 //Already has markup on this coordinate, preserve it if (node.hasMarkup(x, y)) { return } //Store markers.push({x, y}) //Add to board board .add(boardLayerTypes.MARKUP, x, y, MarkupFactory .create(markupTypes.MOVE_NUMBER, board, {number})) }) } /** * Number all moves */ numberAllMoves(node) { //Get variation nodes const {board, markers} = this const nodes = node.getAllMoveNodes() //Loop each nodes.forEach((node, i) => { //Get node data const {x, y} = node.move const number = i + 1 //Already has markup on this coordinate, preserve it if (node.hasMarkup(x, y)) { return } //Store markers.push({x, y}) //Add to board board .add(boardLayerTypes.MARKUP, x, y, MarkupFactory .create(markupTypes.MOVE_NUMBER, board, {number})) }) } /** * Number last move */ numberLastMove(node) { //Not a move node if (!node.isMove()) { return } //Get data const {board, markers} = this const {x, y} = node.move const number = node.getMoveNumber() //Already has markup on this coordinate, preserve it if (node.hasMarkup(x, y)) { return } //Store markers.push({x, y}) //Add to board board .add(boardLayerTypes.MARKUP, x, y, MarkupFactory .create(markupTypes.MOVE_NUMBER, board, {number})) } /** * Clear markers */ clearMarkers() { //Get data const {board, markers} = this if (!board) { return } //Remove markers markers.forEach(({x, y}) => board.removeMarkup(x, y)) //Reset markers array this.markers = [] } }