Created
February 20, 2019 14:57
-
-
Save questsin/448fddcb9a01f628a1e79c81b3f2dc49 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
/** Helper function for 2 leading zeros padding. */ | |
var __pad2 = function __pad2(n) { | |
return ('00' + n).slice(-2); | |
}; | |
/** Format how dates will be displayed. Used in the highscore list. */ | |
var DATE_FMT = self.Intl ? new Intl.DateTimeFormat('de', { | |
year: 'numeric', month: '2-digit', day: '2-digit' | |
}) : { format: function format(tm) { | |
return __pad2(tm.getDate()) + '/' + __pad2(tm.getMonth() + 1) + '/' + tm.getFullYear(); | |
} }; | |
/* exported DATE_FMT */ | |
/** Global constants for the chess pieces. Has to agree with chess.js. */ | |
var PAWN = 'p'; | |
var KNIGHT = 'n'; | |
var BISHOP = 'b'; | |
var ROOK = 'r'; | |
var QUEEN = 'q'; | |
var KING = 'k'; | |
/* exported PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING */ | |
/** Global constants for the chess player colors. */ | |
var WHITE = 'w'; | |
var BLACK = 'b'; | |
/** All pieces in an ordered list */ | |
var ALL_PIECES = ['wk', 'wq', 'wr', 'wb', 'wn', 'wp', 'bk', 'bq', 'br', 'bb', 'bn', 'bp']; | |
/* exported WHITE, BLACK, ALL_PIECES */ | |
/*********************************************************************** | |
* Constants for game events. | |
*/ | |
/** A new game has started. */ | |
var GAME_EVENT_START = 'START'; | |
/** A game has ended by a winner or draw. */ | |
var GAME_EVENT_END = 'END'; | |
/** Undo of a move was requested. */ | |
var GAME_EVENT_UNDO = 'UNDO'; | |
/** Suggestion of a move. */ | |
var GAME_EVENT_SUGGEST = 'SUGGEST'; | |
/** The final dialog, which shows the results, has been dismissed. */ | |
var GAME_EVENT_RESULTDLG_DISMISS = 'RESULTDLG_DISMISS'; | |
/* exported GAME_EVENT_START, GAME_EVENT_END, GAME_EVENT_UNDO, GAME_EVENT_SUGGEST, GAME_EVENT_RESULTDLG_DISMISS */ | |
/************************************************************************* | |
* Constant for display | |
*/ | |
/** Class for squares on chessboard as defined by chessboard.js. */ | |
var CSS_SQUARE_CLS = '.square-55d63'; | |
/** Class to add to chess squares to mark them as a valid target for a move. */ | |
var CSS_LEGAL_SQUARE_CLS = 'possible-target-square'; | |
/* exported CSS_SQUARE_CLS, CSS_LEGAL_SQUARE_CLS */ | |
/** HTML unicode chess symbols stored in a <color><piece>-property schema. */ | |
var UNICODE_CHESS_SYMBOLS = { | |
wk: '♔', wq: '♕', wr: '♖', wb: '♗', wn: '♘', wp: '♙', | |
bk: '♚', bq: '♛', br: '♜', bb: '♝', bn: '♞', bp: '♟' | |
}; | |
var CHEQ_TT_CHESS_SYMBOLS = { | |
wk: 'k', wq: 'q', wr: 'r', wb: 'b', wn: 'h', wp: 'p', | |
bk: 'l', bq: 'w', br: 't', bb: 'n', bn: 'j', bp: 'o' | |
}; | |
/* Screen orientation is portrait. */ | |
var SCREEN_PORTRAIT = 1; | |
/* Screen orientation is landscape. */ | |
var SCREEN_LANDSCAPE = 2; | |
/* exported SCREEN_PORTRAIT, SCREEN_LANDSCAPE */ | |
/** Test if the move was the move of the human player. */ | |
function isPlayersMove(move) { | |
return move && move.color === chessieSettings.playerColor(); | |
} | |
function isOpponentsMove(move) { | |
return !isPlayersMove(move); | |
} | |
function isPlayersColor(color) { | |
return color && color === chessieSettings.playerColor(); | |
} | |
function isOpponentsColor(color) { | |
return isPlayersColor(oppositeColor(color)); | |
} | |
function isPlayersPiece(piece) { | |
return piece.charAt(0) == chessieSettings.playerColor(); | |
} | |
function oppositeColor(c) { | |
return c == WHITE ? BLACK : c == BLACK ? WHITE : null; | |
} | |
/* exported isOpponentsMove, isOpponentsColor, isPlayersPiece */ | |
/** Evaluates if a move as provided by chess.js is a capture of an opponent's piece.*/ | |
function isCapture(move) { | |
// flag contains either an 'c' (standard capture) | |
// or an 'e' (en passant capture) | |
return move && move.flags && move.flags.search(/e|c/) >= 0; | |
} | |
/* exported isCapture */ | |
/** | |
* Generates an HTML fragment for display of a chess piece. | |
* The function accepts either a combined key 'bk' (black king) in the 1st argument | |
* or separately the color in the 1st and the piece in the second argument. | |
* | |
* @param {any} color color or the full piece with color | |
* @param {any} piece piece only, color needs to be set to 'b' or 'w' | |
*/ | |
function pieceHtml(color, piece) { | |
var key = color && piece ? color + piece : color; | |
if (UNICODE_CHESS_SYMBOLS[key]) { | |
return pieceCheqTT(key); | |
} else { | |
return ''; | |
} | |
} | |
/* exported pieceHtml */ | |
function pieceHtmlUnicode(key) { | |
return '<span class="piecesymbol">' + UNICODE_CHESS_SYMBOLS[key] + '</span>'; | |
} | |
function pieceCheqTT(key) { | |
return '<span class="piecesymbol cheqtt">' + CHEQ_TT_CHESS_SYMBOLS[key] + '</span>'; | |
} | |
//# sourceMappingURL=globals.js.map | |
'use strict'; | |
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Structure with all settings, stored locally to be provided | |
*/ | |
var ChessieSettings = (function () { | |
function ChessieSettings() { | |
_classCallCheck(this, ChessieSettings); | |
this.name = 'Player'; | |
this.sound = true; | |
this.color = 'w'; | |
this.opponent = 'chessie'; | |
this.level = 1; | |
this.hintsButton = true; | |
this.showLegalMoves = true; | |
this.language = null; | |
} | |
/** Returns the orientation of the board depending on the player's color. */ | |
_createClass(ChessieSettings, [{ | |
key: 'boardOrientation', | |
value: function boardOrientation() { | |
return this.color == 'w' ? 'white' : 'black'; | |
} | |
}, { | |
key: 'playerColor', | |
value: function playerColor() { | |
switch (this.color) { | |
case 'w':case 'b': | |
return this.color; | |
default: | |
return 'w'; | |
} | |
} | |
}, { | |
key: 'apply', | |
/** Applies the settings to the application for those settings that do have immediate | |
* consequences on the UI. | |
*/ | |
value: function apply() { | |
$('#hintBtn').css('display', this.hintsButton ? 'block' : 'none'); | |
} | |
}, { | |
key: 'fullLevelDescription', | |
get: function get() { | |
return 'Level: ' + this.level + ', Color: ' + this.color; | |
} | |
/** | |
* Load the settings from local storage. Creates a new settings objects if no | |
* settings object can be found in the local storage (bootstrap). | |
* @return {ChessieSettings} the settings | |
*/ | |
}], [{ | |
key: 'load', | |
value: function load() { | |
var retval = new ChessieSettings(); | |
var savedSettings = localStorage.getItem('chessieSettings'); | |
try { | |
Object.assign(retval, JSON.parse(savedSettings)); | |
} catch (e) { | |
console.error('%s: Could not load saved settings... %s', e, savedSettings); | |
} | |
return retval; | |
} | |
}, { | |
key: 'save', | |
value: function save() { | |
localStorage.setItem('chessieSettings', JSON.stringify(chessieSettings)); | |
} | |
}]); | |
return ChessieSettings; | |
})(); | |
var chessieSettings = ChessieSettings.load(); | |
chessieSettings.apply(); | |
/** Show the game tab */ | |
function hideSettings() { | |
$('#tabGame').tab('show'); | |
} | |
$('#settingsBtn').on('click', showSettings); | |
/** Show the settings tab */ | |
function showSettings() { | |
$('#optionsName').val(chessieSettings.name); | |
$('#optionsLanguage').val(chessieSettings.language || 'default'); | |
$('#optionsSoundEnable').prop('checked', chessieSettings.sound); | |
$('input[name=optionsColor]').val([chessieSettings.color]); | |
$('input[name=optComputerPlayerMode]').val([chessieSettings.opponent]); | |
$('#optionsLevel').val(chessieSettings.level); | |
$('#optionsHintsEnable').prop('checked', chessieSettings.hintsButton); | |
$('#optionsHintsShowLegalMoves').prop('checked', chessieSettings.showLegalMoves); | |
$('#tabSettings').tab('show'); | |
} | |
/* exported showSettings */ | |
function showAbout() { | |
$('#tabAbout').tab('show'); | |
} | |
$('#aboutSection').click(hideSettings); | |
/* exported showAbout */ | |
function showRules() { | |
// extract the language without region from the i18n language code | |
var baseLang = (i18n.lng() || 'en').replace(/^([a-zA-Z]+).*/, '$1'); | |
$('#tabRules').tab('show'); | |
$('#rules').load('rules_' + baseLang + '.htm', function (response, status, xhr) { | |
if (status == 'error') { | |
console.error('Did not find rules in language %s. Loading default EN.', baseLang); | |
$('#rules').load('rules_en.htm'); | |
} | |
}); | |
} | |
$('#rules').on('click', '#rulesClose', hideSettings); | |
/* exported showRules */ | |
$('#settingsOkBtn').click(function () { | |
chessieSettings.name = $('#optionsName').val(); | |
chessieSettings.sound = $('#optionsSoundEnable').prop('checked'); | |
// check if the language has changed | |
var oldLang = chessieSettings.language; | |
chessieSettings.language = parseLanguage($('#optionsLanguage').val()); | |
if (oldLang != chessieSettings.language) { | |
initI18n(); | |
} | |
// collect other settings | |
var restartRequired = false; | |
/** Function to call the setter and return true if the value was actually changed. */ | |
function setConfigProperty(name, newValue) { | |
var oldVal = chessieSettings[name]; | |
var changed = oldVal != newValue; | |
chessieSettings[name] = newValue; | |
return changed; | |
} | |
restartRequired = restartRequired || setConfigProperty('color', $('input[name=optionsColor]:checked').val()); | |
// change level | |
setConfigProperty('opponent', $('input[name=optComputerPlayerMode]:checked').val()); | |
chessieSettings.level = Number.parseInt($('#optionsLevel').val()); | |
applyGameLevel(); | |
// hints | |
chessieSettings.hintsButton = $('#optionsHintsEnable').prop('checked'); | |
chessieSettings.showLegalMoves = $('#optionsHintsShowLegalMoves').prop('checked'); | |
ChessieSettings.save(); | |
chessieSettings.apply(); | |
// if anything relevant has changed we'll ask if the game should start as a new game | |
if (restartRequired) { | |
if (gameStarted) { | |
bootbox.confirm($.t('gameend.settingschange'), function (result) { | |
if (result) { | |
newGame(); | |
} | |
}); | |
} else { | |
// game not yet started ... | |
initChessBoard(); | |
} | |
} | |
}); | |
function parseLanguage(s) { | |
var sTrimmed = s ? s.trim() : null; | |
if (sTrimmed == null || sTrimmed == '') { | |
return null; | |
} else if (sTrimmed.toLowerCase() == 'default') { | |
return null; | |
} else { | |
return sTrimmed; | |
} | |
} | |
// the 'game' tab needs to be resized after the containing tab is redisplayed | |
$('#settingsCancelBtn, #settingsOkBtn').click(function (e) { | |
hideSettings(); | |
}); | |
//# sourceMappingURL=settings.js.map | |
'use strict'; | |
// Signal the application complete to the crosswalk browser 5s after start | |
if (window.screen.show) { | |
setTimeout(function () { | |
return window.screen.show(); | |
}, 5000); | |
} | |
// initialize i18n | |
i18n.init({ | |
resGetPath: 'locales/__ns__-__lng__.json', | |
ns: { namespaces: ['chessie'], defaultNs: 'chessie' }, | |
fallbackLng: 'en', | |
useCookie: false, | |
lng: chessieSettings.language | |
}); | |
function initI18n() { | |
// when i18next is initialized, translate the entire document body | |
i18n.setLng(chessieSettings.language); | |
i18n.init(function (err, t) { | |
$('body').i18n(); | |
$('[data-popover-id]').each(function (i, e) { | |
var el = $(e); | |
var key = el.attr('data-popover-id'); | |
el.popover('destroy'); | |
el.html('<i class="fa fa-info-circle fa-fw"></i>'); | |
// now this is funny - we must let some time pass before we can set a new popover | |
// after removal of existing ones... otherwise the new one will also be removed | |
// hence the - completely arbitrary value of a 2s delay, which will hopefully work on every device | |
setTimeout(function () { | |
el.popover({ title: $.t(key + '.title'), content: $.t(key + '.content'), trigger: 'focus', placement: 'bottom' }); | |
}, 1000); | |
}); | |
}); | |
} | |
initI18n(); | |
/** | |
* Screen orientation change event stream. Will provide the new orientation | |
* as value as either 1 (Portrait) or 2 (Landscape) | |
*/ | |
var orientationChange = new Rx.Subject(); | |
function windowOrientation() { | |
var aspectRatio = window.innerWidth / window.innerHeight; | |
return aspectRatio < 13 / 9 ? SCREEN_PORTRAIT : SCREEN_LANDSCAPE; | |
} | |
var currentWindowOrientation = windowOrientation(); | |
var checkOrientation = function checkOrientation() { | |
var newWindowOrientation = windowOrientation(); | |
if (currentWindowOrientation !== newWindowOrientation) { | |
currentWindowOrientation = newWindowOrientation; | |
$(window).trigger('jsfOrientationChange', newWindowOrientation); | |
orientationChange.onNext(newWindowOrientation); | |
} | |
}; | |
Rx.Observable.fromEvent($(window), 'resize').debounce(200).subscribe(function () { | |
console.log('Resize occurred'); | |
checkOrientation(); | |
adaptBoardContainerSize(); | |
chessBoard.resize(); | |
}); | |
orientationChange.subscribe(function (o) { | |
console.log('Orientation Change just now! bla' + (o == 1 ? 'Portrait' : 'Landscape')); | |
var oldCls = o == SCREEN_LANDSCAPE ? 'btn-group' : 'btn-group-vertical'; | |
var newCls = o == SCREEN_LANDSCAPE ? 'btn-group-vertical' : 'btn-group'; | |
$('#mainBtnGrp').removeClass(oldCls).addClass(newCls); | |
}); | |
orientationChange.onNext(currentWindowOrientation); | |
function adaptBoardContainerSize() { | |
var ct = $('#board-outer-container'); | |
var cpd = $('#capturedPiecesDisplay'); | |
// calculate the optimum width for the board, based on the available with of its outer container | |
// it seems like the | |
var availableWidth = ct.innerWidth(); | |
var availableHeight = ct.innerHeight(); | |
// make a correction for the attached container with captured pieces, | |
// which resides below (PORTRAIT) or right of (LANDSCAPE) the chess board | |
switch (windowOrientation()) { | |
case SCREEN_PORTRAIT: | |
availableHeight -= cpd.height();break; | |
case SCREEN_LANDSCAPE: | |
availableWidth -= cpd.width();break; | |
default: | |
throw 'unknown scren orientation'; | |
} | |
console.debug('outer container dimension: %d / %d', ct.innerWidth(), ct.innerHeight()); | |
console.debug(' => calculating with: %d / %d', availableWidth, availableHeight); | |
// the board is rectangular, so we have to use the minimum of the available size | |
// whatever is left over will be added as half-left-margin to center the board | |
// we also subtract a few pixel in order to cater for the board's miscalculating | |
// behaviour when adding a border size of 2px on both sides | |
var newBoardSize = Math.min(availableWidth, availableHeight) - 2; | |
// by experience: the board size must be a multiple of 4... round down to nearest 4x | |
newBoardSize -= newBoardSize % 4; | |
console.debug(' => final width: %d', newBoardSize); | |
$('#board-container').width(newBoardSize, newBoardSize); | |
$('#board-container').css('margin-left', (availableWidth - newBoardSize) / 2); | |
} | |
// the 'game' tab needs to be resized after the containing tab is redisplayed | |
$('#tabGame').on('shown.bs.tab', function (e) { | |
adaptBoardContainerSize(); | |
chessBoard.resize(); | |
}); | |
var toastEl = $('#toast'); | |
var toastTxtFld = $('#toastTxtFld'); | |
/** A toast function to display information about scores and other | |
* events of interest to the user. Text will be displyed in the footer. | |
*/ | |
function toast(text) { | |
toastEl.addClass('fade'); | |
Rx.Observable.timer(200).subscribe(function () { | |
toastTxtFld.html(text); | |
toastEl.removeClass('fade'); | |
}); | |
} | |
/* exported toast */ | |
// initialize the TWEEN library with a timer | |
var absTime = Rx.Observable.interval(50 /*ms*/); | |
absTime.subscribe(function (t) { | |
return TWEEN.update(performance.now()); | |
}); | |
var appMenuEl = $('#appMenu'); | |
var menuToggleEl = $('#menuToggle'); | |
// pushmenu functions | |
/** When the user selects the classic 'toggle' icon on the top left. | |
* Use CSS animation to open the menu using the classes pushmenu- 'open' / 'right'. | |
*/ | |
function pushmenuToggle(e) { | |
// prevent that the click also triggers the pushmenuHide() | |
// which is activated as a click trigger on the entire document body | |
e.stopPropagation(); | |
$(document.body).toggleClass('pushmenu-right'); | |
appMenuEl.toggleClass('pushmenu-open'); | |
} | |
/** The */ | |
function pushmenuHide(e) { | |
$(document.body).removeClass('pushmenu-right'); | |
appMenuEl.removeClass('pushmenu-open'); | |
} | |
menuToggleEl.click(pushmenuToggle); | |
$(document.body).click(pushmenuHide); | |
/** Method to center all modals on the scree when they are being | |
* displayed. If no such shift is done, the modals are placed | |
* so much to the top of the page that is esthetically less pleasing. | |
*/ | |
(function ($) { | |
'use strict'; | |
function centerModal() { | |
$(this).css('display', 'block'); | |
var $dialog = $(this).find('.modal-dialog'), | |
offset = ($(window).height() - $dialog.height()) / 4, | |
bottomMargin = parseInt($dialog.css('marginBottom'), 10); | |
// Make sure you don't hide the top part of the modal w/ a negative margin if it's longer than the screen height, and keep the margin equal to the bottom margin of the modal | |
if (offset < bottomMargin) offset = bottomMargin; | |
$dialog.css('margin-top', offset); | |
} | |
$(document).on('show.bs.modal', '.modal', centerModal); | |
$(window).on('resize', function () { | |
return $('.modal:visible').each(centerModal); | |
}); | |
$('[data-toggle="popover"]').popover(); | |
})(jQuery); | |
// send an UNDO event when the undo button is clicked | |
$('#undoBtn').click(function () { | |
return sendGameEvent(GAME_EVENT_UNDO); | |
}); | |
// send an SUGGEST event when the hint button is clicked | |
$('#hintBtn').click(function () { | |
return sendGameEvent(GAME_EVENT_SUGGEST); | |
}); | |
// remove the focus from all clicked buttons to prevent | |
// them from being framed by the 'current focus' rectangle | |
$('button').click(function (e) { | |
return $(e.currentTarget).blur(); | |
}); | |
// when the user clicks on the chess board and no game is | |
// currently in progress, we'll ask if we should start one | |
$('#board').click(function () { | |
if (!gameStarted) { | |
bootbox.confirm($.t('gameend.nogamestarted'), function (okSelected) { | |
if (okSelected) { | |
newGame(); | |
} | |
}); | |
} | |
}); | |
/** Prevents user input to the UI by stopping pointer-events on the document body. */ | |
function lockMainUi() { | |
var lock = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; | |
$(document.body).css('pointer-events', lock ? 'none' : 'auto'); | |
} | |
/* exported lockMainUi */ | |
/** Do some fancy animation to indicate that the AI player is thinking hard. */ | |
function showAiPlayerThinkingAnimation() { | |
var show = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0]; | |
$('#aiPlayerThinking').css('visibility', show ? 'visible' : 'hidden'); | |
} | |
/* exported showAiPlayerThinkingAnimation */ | |
/** Reveal one of the alternative bottom panels which either ho*/ | |
function displayBottomPaneEl(el, display) { | |
el.css('display', display ? 'flex' : 'none'); | |
setTimeout(function () { | |
return el.css('opacity', display ? 1 : 0); | |
}, 100); | |
} | |
function showGameEndPanel() { | |
//$('#gameHistoryBtn').attr('disabled', hasMoveHistory() ? null : 'disabled'); | |
displayBottomPaneEl($('#controlsbox'), false); | |
displayBottomPaneEl($('#gameEndPanel'), true); | |
} | |
function hideGameEndPanel() { | |
displayBottomPaneEl($('#gameEndPanel'), false); | |
displayBottomPaneEl($('#controlsbox'), true); | |
} | |
/* exported showGameEndPanel, hideGameEndPanel*/ | |
// --- remote logging for errors in order to provide hints about what can break in the app | |
var loggly = new LogglyTracker(); | |
loggly.push({ tag: 'chessie', logglyKey: 'bcf090ec-6bb7-4fe5-a2c2-6f1eab4fbd7c', sendConsoleErrors: false }); | |
function errorReportingOnErrorHandler(msg, url, line, col, err) { | |
loggly.push({ | |
category: 'UncaughtJSException', | |
exception: { | |
message: msg, | |
url: url, | |
lineno: line, | |
colno: col | |
}, | |
stack: err ? err.stack : '' | |
}); | |
} | |
window.onerror = errorReportingOnErrorHandler; | |
//# sourceMappingURL=main.js.map | |
'use strict'; | |
/** Interval that is used when pieces are moved over the board. */ | |
var MOVE_ANIMATION_DURATION = 400; | |
/** Display time of a hint. */ | |
var HINT_DISPLAY_TIME = 200; | |
/** Time to let the computer wait before a move is carried out by the computer player.*/ | |
var COMPUTER_PLAYER_THINK_TIME = 100; | |
/* Chess.js class to track the game and give insights into positions and pieces. */ | |
var game = new Chess(); | |
/** Chessboard.js instance, to draw the game's status. */ | |
var chessBoard = undefined; | |
/** This object keeps track of all pieces that were captured during the game. | |
* The number of pieces are stored in a property = <color><piece>, value = number of captures. */ | |
var capturedPieces = {}; | |
/** Global game status - is a game currently going on, or not yet/already finished. */ | |
var gameStarted = false; | |
// RX Subjects to control the game | |
/** The events stream with all moves. Provides the moves from both players. | |
* Stream values are the chess.js move objects such as | |
* { color: 'w', from: 'a2', to: 'a3', flags: 'n', piece: 'p', san 'a3' } | |
* plus additional attributes { id: 1674, tm: 3487298472, duration: 1031 }. | |
* Publish to the subject only with the method sendNext (defined below)! | |
*/ | |
var rxMoves = new Rx.Subject(); | |
/** All events related to points contributing to the score of the game. | |
* Stream values are { basePoints: n, multiplier: m, totalPoints: p } | |
*/ | |
var rxGameScore = new Rx.Subject(); | |
/* exported rxGameScore */ | |
/** Events to notify the game's end = winning or losing. | |
* The value object is { winner: 'w' / 'b', score: final score } | |
*/ | |
var rxGameEvents = new Rx.Subject(); | |
rxGameEvents.subscribe(function (e) { | |
return console.log('Game event %s: %o', e.event, e); | |
}); | |
// create the background thread for the AI player | |
var aiPlayer = new Worker('scripts/chessie-ai2.js'); | |
// translate post-back messages into events on the rx subject | |
var rxAiPlayerEvents = new Rx.Subject(); | |
aiPlayer.onmessage = function (e) { | |
return rxAiPlayerEvents.onNext(e); | |
}; | |
// notify the AI player about every move | |
rxMoves.subscribe(function (m) { | |
return aiPlayer.postMessage({ type: 'MOVE', from: m.from, to: m.to }); | |
}); | |
/** Sends a game event on the event stream and puts it into the window timeout queue | |
* for processing after the sending thread has ended. | |
*/ | |
function sendGameEvent(event, data) { | |
data = data || {}; | |
data.event = event; | |
window.setTimeout(function () { | |
return rxGameEvents.onNext(data, 0); | |
}); | |
} | |
/** Registers a game event listener for a certain game event type. */ | |
function listenToGameEvent(filter, callback) { | |
if (typeof filter === 'function') { | |
rxGameEvents.filter(filter).subscribe(callback); | |
} else { | |
rxGameEvents.filter(function (e) { | |
return filter == null || e.event == filter; | |
}).subscribe(callback); | |
} | |
} | |
/** Event stream for piece selection, i.e. tipping on one of one's own pieces. */ | |
var rxBoardTouchEvents = new Rx.Subject(); | |
/** Returns the square's address ('a5', 'f3', ...) from the DIV elements of the chess board.*/ | |
function getSquareAddress(squareDivEl) { | |
return squareDivEl.attr('data-square'); | |
} | |
/** Register handler on document ready - creating the chess board for display. */ | |
$(document).ready(function () { | |
var cfg = { | |
draggable: true, | |
orientation: chessieSettings.boardOrientation(), | |
position: 'start', | |
onDragStart: onDragStart, | |
onDrop: onDrop, | |
onSnapEnd: onSnapEnd, | |
moveSpeed: MOVE_ANIMATION_DURATION, | |
sparePieces: false, | |
pieceTheme: 'images/chesspieces/wikipedia/{piece}.png' | |
}; | |
adaptBoardContainerSize(); | |
chessBoard = new ChessBoard('board', cfg); | |
$('#board').on('click', CSS_SQUARE_CLS, function (e) { | |
var target = $(e.target).closest(CSS_SQUARE_CLS).attr('data-square'); | |
rxBoardTouchEvents.onNext({ type: 'square', target: target }); | |
}); | |
// create div-containers for the display of individual captured chess pieces | |
ALL_PIECES.forEach(function (p) { | |
$('#capturedPiecesDisplay').append('<div id="captured-' + p + '" style="display: none">' + pieceHtml(p) + '</div>'); | |
}); | |
$('#newGameBtn').click(newGame); | |
$('#highScoresBtn').click(function (e) { | |
return $('#highscores').modal('show'); | |
}); | |
$('#gameHistoryBtn').click(function (e) { | |
return showMoveHist(); | |
}); | |
}); | |
function initChessBoard() { | |
chessBoard.clear(true); | |
chessBoard.orientation(chessieSettings.boardOrientation()); | |
chessBoard.start(true); | |
} | |
/** | |
* Start a new game. This function will reset all the global state to the | |
* initial values and prepare the board for playing the game. | |
*/ | |
function newGame() { | |
gameStarted = false; | |
$('#tabGame').tab('show'); | |
game = new Chess(); | |
initChessBoard(); | |
resetCapturedPieces(); | |
aiPlayer.postMessage({ type: 'RESET' }); | |
scoreboard.reset(); | |
scoreMultiplier.reset(); | |
clearMoveHistory(); | |
hideGameEndPanel(); | |
gameStarted = true; | |
sendGameEvent(GAME_EVENT_START); | |
// start from here when the computer is white | |
if (isOpponentsColor('w')) { | |
makeOpponentsMove(); | |
} | |
} | |
function applyGameLevel() { | |
aiPlayer.postMessage({ type: 'SET_ENGINE', opponent: chessieSettings.opponent, level: chessieSettings.level }); | |
var levelChr = '&#' + (9312 /* encircled 1 in unicode */ + parseInt(chessieSettings.level) - 1) + ';'; | |
$('#computerPlayerLevel').html(levelChr); | |
$('#computerPlayerType').removeClass('fa-child fa-mortar-board'); | |
$('#computerPlayerType').addClass(chessieSettings.opponent == 'chessie' ? 'fa-child' : 'fa-mortar-board'); | |
} | |
// initialize the levels and apply the loaded game level | |
applyGameLevel(); | |
/** Sends the next move message out. This function will add important attributes: | |
* (1) a unique identifier (by using a simple integer counter) that allows to | |
* refer to moves unambigously | |
* (2) a timestamp, when the move was sent out to the stream | |
* (3) the duration it took between this move and the last move | |
*/ | |
rxMoves.sendNext = function (move) { | |
this.idCounter = this.idCounter ? this.idCounter + 1 : 1; | |
move.id = this.idCounter; | |
move.tm = Date.now(); | |
move.duration = this.lastSendTm ? move.tm - this.lastSendTm : undefined; | |
this.lastSendTm = move.tm; | |
this.onNext(move); | |
}; | |
/** Check for game over by listening on the move and testing if more moves are available. */ | |
rxMoves.subscribe(function (move) { | |
if (game.game_over() == true) { | |
var winner = game.in_draw() ? '-' : move.color; | |
if (game.in_checkmate()) { | |
var reason = $.t('gameend.checkmate'); | |
} else if (game.in_stalemate()) { | |
reason = $.t('gameend.stalemate'); | |
} else if (game.in_threefold_repetition()) { | |
reason = $.t('gameend.threefoldRepetition'); | |
} else if (game.insufficient_material()) { | |
reason = $.t('gameend.insufficientMaterial'); | |
} else if (game.history.length >= 50) { | |
reason = $.t('gameend.tooManyMoves'); | |
} else { | |
reason = $.t('gameend.unknown'); | |
} | |
sendGameEvent(GAME_EVENT_END, { winner: winner, reason: reason, score: scoreboard.score }); | |
} | |
}); | |
/** Main reaction on game end: display a message. */ | |
listenToGameEvent(GAME_EVENT_END, function (r) { | |
gameStarted = false; | |
showGameEndPanel(); | |
}); | |
// do not pick up pieces if the game is over | |
// only pick up pieces for White | |
var onDragStart = function onDragStart(source, piece, position, orientation) { | |
if (!gameStarted || game.game_over() || !isPlayersPiece(piece)) { | |
return false; | |
} | |
}; | |
function makeOpponentsMove() { | |
var delay = arguments.length <= 0 || arguments[0] === undefined ? COMPUTER_PLAYER_THINK_TIME : arguments[0]; | |
showAiPlayerThinkingAnimation(true); | |
lockMainUi(true); | |
setTimeout(function () { | |
return aiPlayer.postMessage({ type: 'SUGGEST_MOVE', fen: game.fen() }); | |
}, delay); | |
} | |
/** Callback on events from the AI player, running as a background thread */ | |
rxAiPlayerEvents.filter(function (e) { | |
return e.data.type == 'SUGGEST_MOVE'; | |
}).subscribe(function (e) { | |
showAiPlayerThinkingAnimation(false); | |
// do the move | |
var pickedMove = e.data.move; | |
if (pickedMove) { | |
var move = game.move(pickedMove); | |
if (move == null) { | |
throw 'Illegal move attempted by computer player!'; | |
} | |
chessBoard.move(pickedMove.from + '-' + pickedMove.to); | |
rxMoves.sendNext(move); | |
} | |
// unlock the UI | |
lockMainUi(false); | |
}); | |
function showPlayersPossibleMoves(fromSquare) { | |
if (chessieSettings.showLegalMoves) { | |
game.moves({ square: fromSquare, verbose: true }).forEach(function (s) { | |
squareEl(s.to).addClass(CSS_LEGAL_SQUARE_CLS); | |
}); | |
} | |
} | |
function hidePlayersPossibleMoves() { | |
$(CSS_SQUARE_CLS).removeClass(CSS_LEGAL_SQUARE_CLS); | |
} | |
var selectedSquare = null; | |
function squareEl(target) { | |
return $('#board .square-' + target); | |
} | |
function selectSquare(target) { | |
unselectSquare(); | |
var square = squareEl(target); | |
square.addClass('selected-square'); | |
selectedSquare = square; | |
showPlayersPossibleMoves(getSquareAddress(square)); | |
} | |
function unselectSquare() { | |
if (selectedSquare) { | |
selectedSquare.removeClass('selected-square'); | |
selectedSquare = null; | |
hidePlayersPossibleMoves(); | |
} | |
} | |
// any move taken will reset the selection | |
rxMoves.subscribe(unselectSquare); | |
rxBoardTouchEvents.subscribe(function (e) { | |
// when the type of the event is 'square' we will move the selected piece | |
// to this target (if there is a selected piece) | |
if (e.type == 'square') { | |
if (selectedSquare) { | |
var source = selectedSquare.attr('data-square'); | |
onDrop(source, e.target, false); | |
chessBoard.position(game.fen()); | |
} | |
// and remove the selection | |
unselectSquare(); | |
} else if (e.type == 'piece') { | |
selectSquare(e.target); | |
} | |
}); | |
function onDrop(source, target) { | |
var calledAsBoardEvent = arguments.length <= 2 || arguments[2] === undefined ? true : arguments[2]; | |
if (source == target) { | |
rxBoardTouchEvents.onNext({ type: 'piece', target: target }); | |
return; | |
} | |
// see if the move is legal | |
var move = game.move({ | |
from: source, | |
to: target, | |
promotion: 'q' // NOTE: always promote to a queen for simplicity | |
}); | |
if (move === null) { | |
// illegal move | |
return 'snapback'; | |
} else { | |
rxMoves.sendNext(move); | |
} | |
// when this method has been called as a result of a chessboard drop event | |
// there is going to be no animation end event (as there was no animation) | |
// we'll generate such an event manually | |
if (!game.game_over()) { | |
makeOpponentsMove(calledAsBoardEvent ? COMPUTER_PLAYER_THINK_TIME : COMPUTER_PLAYER_THINK_TIME + MOVE_ANIMATION_DURATION); | |
} | |
} | |
/** Update the board position after the piece snap for castling, en passant, pawn promotion. */ | |
function onSnapEnd() { | |
chessBoard.position(game.fen()); | |
} | |
/** Opposite of the above: remove history entries including the last player's move. */ | |
listenToGameEvent(GAME_EVENT_UNDO, function (e) { | |
var move = undefined; | |
do { | |
move = game.undo(); | |
updateCapturedPiece(move, true); | |
} while (move != null && !isPlayersColor(game.turn())); | |
chessBoard.position(game.fen(), true); | |
updateGameAlerts(); | |
// if we moved back to the beginning and the computer has white, we'll have to start again | |
if (isOpponentsColor(game.turn())) { | |
setTimeout(makeOpponentsMove, MOVE_ANIMATION_DURATION); | |
} | |
}); | |
/** Called when the player selects the 'gimme a hint'-button. | |
* Uses the real chess player engine to suggest a simple, but reasonable move. | |
*/ | |
listenToGameEvent(GAME_EVENT_SUGGEST, function () { | |
aiPlayer.postMessage({ type: 'HINT', fen: game.fen() }); | |
}); | |
/** Handler for the incoming hints from the web worker. */ | |
rxAiPlayerEvents.filter(function (e) { | |
return e.data.type == 'HINT'; | |
}).subscribe(function (e) { | |
if (isPlayersColor(game.turn())) { | |
(function () { | |
var hintMove = e.data.move; | |
var fen = game.fen(); | |
if (game.move(hintMove)) { | |
var hintFen = game.fen(); | |
game.undo(); | |
chessBoard.position(hintFen, true); | |
setTimeout(function () { | |
return chessBoard.position(fen, true); | |
}, HINT_DISPLAY_TIME + MOVE_ANIMATION_DURATION); | |
} | |
})(); | |
} | |
}); | |
/** Sound effect when either the human or the computer player makes a move. */ | |
var sndHumanPlayer = new Audio('sound/laptop_notebook_return_or_enter_key_press.mp3'); | |
var sndComputerPlayer = new Audio('sound/laptop_notebook_delete_key_press.mp3'); | |
// shift the volume to make the sound a bit softer - we don't want to click so loudly | |
[sndHumanPlayer, sndComputerPlayer].forEach(function (s) { | |
return s.volume = 0.25; | |
}); | |
/** Subscribe to the move events and play a sound when a move happens. */ | |
rxMoves.subscribe(function (m) { | |
if (chessieSettings.sound) { | |
(function () { | |
// chose the sound and the delay depending on who took the move | |
// the human player is greeted immediately wherease the computer | |
// player will give its sound only when the move animation finished | |
var snd = isPlayersMove(m) ? sndHumanPlayer : sndComputerPlayer; | |
var tim = isPlayersMove(m) ? 0 : MOVE_ANIMATION_DURATION; | |
// schedule the sound at the end of the move animation | |
setTimeout(function () { | |
return snd.play(); | |
}, tim); | |
})(); | |
} | |
}); | |
/** | |
* Keep track of captured pieces. | |
* @param {Object} move object | |
* @param {boolean} undo=false set to true when the move should be subtracted due to an undo | |
*/ | |
function updateCapturedPiece(move) { | |
var undo = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1]; | |
if (isCapture(move)) { | |
var key = oppositeColor(move.color) + move.captured; | |
var count = (capturedPieces[key] || 0) + (undo ? -1 : 1); | |
capturedPieces[key] = count; | |
console.debug('piece capture update for %s - %d', key, capturedPieces[key]); | |
// update the element that shows the capture cound | |
var el = $('#captured-' + key); | |
el.css('display', count > 0 ? 'block' : 'none'); | |
el.html(pieceHtml(key) + '<sub>' + count + '</sub>'); | |
} | |
} | |
/** Hides all captured piece divs and sets the counters to 0. */ | |
function resetCapturedPieces() { | |
$('#capturedPiecesDisplay div').css('display', 'none'); | |
ALL_PIECES.forEach(function (p) { | |
return capturedPieces[p] = 0; | |
}); | |
} | |
// update the list of captured pieces on every move | |
// note that undoing a move is handled separately in the corresponding UNDO handler | |
rxMoves.subscribe(function (m) { | |
return updateCapturedPiece(m); | |
}); | |
// display chess flags for being 'in check' and others | |
var gameAlerts = $('#gameAlerts'); | |
function updateGameAlerts() { | |
if (!game.game_over() && game.in_check()) { | |
var key = game.turn() + 'k'; | |
gameAlerts.html('' + pieceHtml(key)); | |
} else { | |
gameAlerts.html(''); | |
} | |
} | |
// after every move, update the game alert status icons | |
rxMoves.subscribe(function (m) { | |
return updateGameAlerts(); | |
}); | |
// report detailed errors | |
rxAiPlayerEvents.filter(function (e) { | |
return e.data.type == 'ERROR'; | |
}).subscribe(function (e) { | |
// forward to reporting error handler with a faked | |
// error object that has nothing but the reported stack | |
var d = e.data; | |
errorReportingOnErrorHandler(d.message, d.url, d.line, d.column, { stack: d.stack }); | |
}); | |
//# sourceMappingURL=game.js.map | |
'use strict'; | |
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/** | |
* Global score display board. | |
*/ | |
var scoreboard = { | |
score: 0, | |
moves: 0, | |
reset: function reset() { | |
this.score = 0; | |
this.displayScoreInternal = 0; | |
this.moves = 0; | |
this.updateDisplay(); | |
}, | |
incrementMoves: function incrementMoves() { | |
var num = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0]; | |
this.moves += num; | |
this.updateDisplay(); | |
}, | |
decrementMoves: function decrementMoves() { | |
var num = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0]; | |
this.moves -= num; | |
this.updateDisplay(); | |
}, | |
addPoints: function addPoints(score) { | |
var p = Math.floor(score.totalPoints); | |
this.score += p; | |
if (p > 0) { | |
toast(score.basePoints + ' (base) × ' + score.multiplier.toFixed(2) + ' (time) = ' + score.totalPoints); | |
} | |
this.updateDisplay(); | |
}, | |
updateDisplay: function updateDisplay() { | |
$('#movesFld').html(this.moves); | |
this.tween = new TWEEN.Tween(this).to({ displayScore: this.score }, 200).easing(TWEEN.Easing.Exponential.In); | |
this.tween.start(); | |
}, | |
get displayScore() { | |
return this.displayScoreInternal; | |
}, | |
set displayScore(val) { | |
this.displayScoreInternal = Math.round(val); | |
$('#scoreFld').html(this.displayScoreInternal); | |
} | |
}; | |
rxMoves.filter(function (move) { | |
return isPlayersMove(move); | |
}).subscribe(function () { | |
return scoreboard.incrementMoves(); | |
}); | |
rxGameScore.subscribe(function (score) { | |
return scoreboard.addPoints(score); | |
}); | |
/** The table with score points by piece. */ | |
var BASE_POINTS_BY_PIECE = {}; | |
BASE_POINTS_BY_PIECE[PAWN] = 5; | |
BASE_POINTS_BY_PIECE[KNIGHT] = 15; | |
BASE_POINTS_BY_PIECE[BISHOP] = 20; | |
BASE_POINTS_BY_PIECE[ROOK] = 30; | |
BASE_POINTS_BY_PIECE[QUEEN] = 50; | |
BASE_POINTS_BY_PIECE[KING] = 100; | |
function basePointsForMove(move) { | |
var points = BASE_POINTS_BY_PIECE[move.captured]; | |
return points || 0; | |
} | |
/** Game scoring function: we'll award points for good moves. | |
*/ | |
rxMoves.subscribe(function (move) { | |
var points = 0; | |
if (isPlayersMove(move)) { | |
// we'll award 5 points for every capture of an opponent's figure | |
if (isCapture(move)) { | |
points += basePointsForMove(move); | |
} | |
} else { | |
// we'll award 2x points whenever the opponent was forced to capture one of our pieces | |
if (isCapture(move)) { | |
points += 2 * basePointsForMove(move); | |
} | |
} | |
// now combine the points and the time-factor multiplier into a score event | |
// and send the score event out over the RxJs stream | |
var factor = Number(scoreMultiplier.factor.toFixed(2)); | |
var score = { | |
move: move, | |
basePoints: points, | |
multiplier: factor, | |
totalPoints: Math.floor(points * factor) | |
}; | |
rxGameScore.onNext(score); | |
}); | |
var timerProgressBar = $('#timer'); | |
var ScoreMultipier = (function () { | |
function ScoreMultipier(top, countDownTime) { | |
_classCallCheck(this, ScoreMultipier); | |
this.top = top; | |
this.countDownTime = countDownTime; | |
this.pct = 100; | |
} | |
_createClass(ScoreMultipier, [{ | |
key: 'reset', | |
value: function reset() { | |
stopTimer(); | |
this.pct = 100; | |
} | |
}, { | |
key: 'pct', | |
set: function set(val) { | |
this._pct = val; | |
timerProgressBar.width(this._pct + '%'); | |
timerProgressBar.html(this.factor.toFixed(1) + 'x'); | |
}, | |
get: function get() { | |
return this._pct; | |
} | |
}, { | |
key: 'factor', | |
get: function get() { | |
return this.pct / 100 * this.top; | |
} | |
}]); | |
return ScoreMultipier; | |
})(); | |
/** Time is connected with a multiplier, which starts with the factor 5 | |
* and is running down to 0 over a certain time period. | |
*/ | |
var scoreMultiplier = new ScoreMultipier(5, 60 * 1000); | |
var scoreMultTween = null; | |
function stopTimer() { | |
if (scoreMultTween) { | |
scoreMultTween.stop(); | |
scoreMultTween = null; | |
} | |
} | |
function startTimer() { | |
stopTimer(); | |
scoreMultiplier.reset(); | |
scoreMultTween = new TWEEN.Tween(scoreMultiplier).to({ pct: 0 }, scoreMultiplier.countDownTime).easing(TWEEN.Easing.Exponential.Out); | |
scoreMultTween.start(); | |
} | |
rxMoves.filter(function (m) { | |
return isOpponentsMove(m); | |
}).subscribe(startTimer); | |
rxMoves.filter(function (m) { | |
return isPlayersMove(m); | |
}).subscribe(stopTimer); | |
//# sourceMappingURL=score.js.map | |
'use strict'; | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/* | |
* Highscore list handling functions | |
*/ | |
var HS_MAX_ELEMENTS = 20; | |
var hsTableEl = $('#highscoresTbl'); | |
var hsTableRowFrag = $('<tr><td></td><td></td><td align="right"></td><td align="center"></td></tr>'); | |
var HsItem = function HsItem(name, score) { | |
_classCallCheck(this, HsItem); | |
// take over the initializer arguments | |
this.name = name; | |
this.score = score; | |
// computed attributes: date is initialized with current timestamp | |
// and the rank is set to 0 (will be assigned later when sorted) | |
this.tm = new Date(); | |
this.rank = 0; | |
}; | |
/** Saved high-scores, loaded from local storage. */ | |
var highScores = JSON.parse(localStorage.getItem('highScores')) || []; | |
highScores.forEach(function (h) { | |
if (typeof h.tm == 'string') h.tm = new Date(h.tm); | |
}); | |
renderHighScores(); | |
function addHighScore(name, score) { | |
var entry = new HsItem(name, score); | |
highScores.push(entry); | |
highScores.sort(function (i1, i2) { | |
return i2.score - i1.score; | |
}); | |
highScores.forEach(function (item, index) { | |
return item.rank = index + 1; | |
}); | |
highScores = highScores.slice(0, HS_MAX_ELEMENTS); | |
localStorage.setItem('highScores', JSON.stringify(highScores)); | |
} | |
/** Returns the position in which the score would go into the high score list. | |
*/ | |
function rankOfScore(score) { | |
var i = highScores.findIndex(function (i) { | |
return i.score <= score; | |
}); | |
return i < 0 ? highScores.length : i; | |
} | |
function renderHighScores() { | |
hsTableEl.find('tr').remove(); | |
highScores.forEach(function (i) { | |
var tr = hsTableRowFrag.clone(); | |
var tds = tr.children(); | |
$(tds[0]).text(i.rank); | |
$(tds[1]).text(i.name); | |
$(tds[2]).text(i.score); | |
$(tds[3]).text(DATE_FMT.format(i.tm)); | |
hsTableEl.append(tr); | |
}); | |
} | |
function gameEndSmiley(winner) { | |
if (isPlayersColor(winner)) { | |
return '<i class="fa fa-smile-o fa-lg"></i>'; | |
} else if (isOpponentsColor(winner)) { | |
return '<i class="fa fa-frown-o fa-lg"></i>'; | |
} else { | |
return '<i class="fa fa-balance-scale fa-lg"></i>'; | |
} | |
} | |
/** A decent handling of the game's end... */ | |
listenToGameEvent(GAME_EVENT_END, function (r) { | |
var simeleyHtml = gameEndSmiley(r.winner); | |
toast(simeleyHtml + ' ' + r.reason); | |
var endComment = undefined; | |
if (isPlayersColor(r.winner)) { | |
endComment = $.t('gameend.messageWin'); | |
} else if (isOpponentsColor(r.winner)) { | |
endComment = $.t('gameend.messageLose'); | |
} else { | |
endComment = $.t('gameend.messageDraw'); | |
} | |
var dlg = $('#gameEndModal'); | |
dlg.find('h4').html(simeleyHtml + ' ' + endComment); | |
dlg.find('#endDlgReason').text(r.reason); | |
dlg.find('#endDlgScore').text($.t('gameend.achievedScore', { points: r.score })); | |
dlg.find('input').val(chessieSettings.name); | |
dlg.modal('show'); | |
var isNewHighScore = rankOfScore(r.score) < HS_MAX_ELEMENTS; | |
dlg.find('#highScoreSection').css('display', isNewHighScore ? 'block' : 'none'); | |
var modalBtn = dlg.find('button'); | |
modalBtn.off(); | |
modalBtn.click(function () { | |
var name = dlg.find('input').val(); | |
addHighScore(name || '-', r.score); | |
renderHighScores(); | |
dlg.modal('hide'); | |
$('#highscores').modal('show'); | |
}); | |
}); | |
// fire the dismiss-result-dialog event when the result dialog has been removed | |
$('#gameEndModal').on('hidden.bs.modal', function () { | |
return sendGameEvent(GAME_EVENT_RESULTDLG_DISMISS); | |
}); | |
//# sourceMappingURL=highscores.js.map | |
'use strict'; | |
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); | |
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | |
/* | |
* Keeper of the move history. | |
*/ | |
var moveHistory = []; | |
/* exported hasMoveHistory */ | |
function hasMoveHistory() { | |
return moveHistory.length > 0; | |
} | |
var mhTableEl = $('#movehistoryTable tbody'); | |
var mhTableRowFrag = $('<tr><td></td><td class="center"></td><td></td><td class="center"></td><td></td><td></td><td></td><td></td></tr>'); | |
/** Captures a move of either the player or the oponent. | |
* Moves of players will have the extra information about the score | |
* which was achieved with the move. | |
*/ | |
var MhEntry = (function () { | |
function MhEntry(num, move, score) { | |
_classCallCheck(this, MhEntry); | |
console.assert(move.from != null, 'not a move object'); | |
this.number = num; | |
this.tr = null; | |
// copy all move attributes and the score | |
for (var attr in move) { | |
this[attr] = move[attr]; | |
} // the player's moves are constructed with a score element | |
if (score) { | |
for (var attr in score) { | |
this[attr] = score[attr]; | |
} | |
} else { | |
this.basePoints = ''; | |
this.multiplier = ''; | |
this.totalPoints = ''; | |
} | |
} | |
_createClass(MhEntry, [{ | |
key: 'toRow', | |
value: function toRow() { | |
var tr = mhTableRowFrag.clone(); | |
var tds = tr.children(); | |
var i = 0; | |
$(tds[i++]).html(this.number); | |
$(tds[i++]).html(pieceHtml(this.color, this.piece)); | |
$(tds[i++]).html(this.from + '-' + this.to); | |
$(tds[i++]).html(pieceHtml(this.opponentColor, this.captured)); | |
$(tds[i++]).html(this.durationHtml); | |
$(tds[i++]).html(this.basePointsHtml); | |
$(tds[i++]).html(this.multiplierHtml); | |
$(tds[i++]).html(this.totalPointsHtml); | |
return tr; | |
} | |
}, { | |
key: 'opponentColor', | |
get: function get() { | |
return this.color == 'w' ? 'b' : 'w'; | |
} | |
}, { | |
key: 'durationHtml', | |
get: function get() { | |
if (isOpponentsMove(this) || !this.duration) { | |
return ''; | |
} | |
var s = this.duration / 1000.; | |
if (s < 1) { | |
return '< 1s'; | |
} else if (s < 60) { | |
return s.toFixed(1) + 's'; | |
} else { | |
var m = Math.floor(s / 60); | |
var h = Math.floor(m / 60); | |
m -= h * 60; | |
s = Math.round(s - h * 3600 - m * 60); | |
return h > 0 ? h + 'h ' + m + 'm ' + s + 's' : m + 'm ' + s + 's'; | |
} | |
} | |
}, { | |
key: 'multiplierHtml', | |
get: function get() { | |
return this.basePoints ? '×' + this.multiplier.toFixed(2) : ''; | |
} | |
}, { | |
key: 'basePointsHtml', | |
get: function get() { | |
return this.basePoints ? this.basePoints : ''; | |
} | |
}, { | |
key: 'totalPointsHtml', | |
get: function get() { | |
return this.totalPoints ? this.totalPoints : ''; | |
} | |
}]); | |
return MhEntry; | |
})(); | |
function addHistoryEntry(move, score) { | |
var num = moveHistory.length + 1; | |
var e = new MhEntry(num, move, score); | |
moveHistory.push(e); | |
e.tr = e.toRow(); | |
mhTableEl.prepend(e.tr); | |
} | |
/* exported clearMoveHistory */ | |
function clearMoveHistory() { | |
moveHistory = []; | |
$('tr', mhTableEl).remove(); | |
} | |
/** Add an entry to the move history every time a move is made. */ | |
rxGameScore.subscribe(function (sc) { | |
return addHistoryEntry(sc.move, sc); | |
}); | |
/** Opposite of the above: remove history entries including the last player's move. */ | |
listenToGameEvent(GAME_EVENT_UNDO, function (e) { | |
// get the last index in the move history which is from the player | |
// a bit hard to read in reduce(): start with -1 (no entry) and assign the index on player's entries | |
var iLastOfPlayer = moveHistory.reduce(function (prev, h, i) { | |
return isPlayersColor(h.color) ? i : prev; | |
}, -1); | |
// we'll remove everything from the last index on from the list of history entry | |
if (iLastOfPlayer >= 0) { | |
var pointsToRemove = 0; | |
for (var i = iLastOfPlayer; i < moveHistory.length; i++) { | |
moveHistory[i].tr.remove(); | |
pointsToRemove += moveHistory[i].totalPoints; | |
} | |
scoreboard.addPoints({ totalPoints: -pointsToRemove }); | |
scoreboard.decrementMoves(1); | |
moveHistory = moveHistory.slice(0, iLastOfPlayer); | |
} | |
}); | |
/** Bring up the move history dialog. */ | |
function showMoveHist() { | |
$('#moveHistTitle').text($.t('movehistory.title', { points: scoreboard.displayScore || 0 })); | |
$('#movehistory').modal('show'); | |
} | |
/* exported showMoveHist */ | |
//# sourceMappingURL=movehistory.js.map | |
'use strict'; | |
/* global ga, isCordovaApp */ | |
window.ga = window.ga || function () { | |
(ga.q = ga.q || []).push(arguments); | |
};ga.l = +new Date(); | |
// when running from Cordova we'll have to initialize without cookies | |
ga('create', 'UA-65829240-3', { | |
'storage': 'none', | |
'clientId': localStorage.getItem('gaClientId') | |
}); | |
ga("set", "appName", "chessie"); | |
if (isCordovaApp) { | |
// and disable the check URL protocol task | |
ga('set', 'checkProtocolTask', null); | |
ga('set', 'checkStorageTask', null); | |
} | |
// callback, when we receive the tracking it - put it into the localStorage | |
ga(function (tracker) { | |
return localStorage.setItem('gaClientId', tracker.get('clientId')); | |
}); | |
ga('require', 'displayfeatures'); | |
// start out with the information, that the main screen is displyed (which it is after start...) | |
gaSendScreenView('Main'); | |
/** Subscribe to all game events and report them to Analytics. */ | |
listenToGameEvent(function (e) { | |
ga('send', { | |
'hitType': 'event', // Required. | |
'eventCategory': 'game', // Required. | |
'eventAction': e.event, // Required. | |
'eventLabel': '-' | |
}); | |
//event', '', , '-', chessieSettings.fullLevelDescription); | |
}); | |
/** Sends out a notification about a scren view to Analytics. */ | |
function gaSendScreenView(screen) { | |
ga('send', 'screenview', { | |
'appName': 'chessie', | |
'screenName': screen | |
}); | |
} | |
/** | |
* Event handler for switching the tab views on the page. We call each of | |
* the tabs a 'screen' and will report it to Analytics as an screeview event. | |
* Event data in the callback: | |
* e.target // newly activated tab | |
* e.relatedTarget // previous active tab | |
*/ | |
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) { | |
var screen = $(e.target).attr('data-screen-id'); | |
console.log('switched to screen %s', screen); | |
gaSendScreenView(screen); | |
}); | |
//# sourceMappingURL=analytics.js.map | |
'use strict'; | |
/* global AdMob */ | |
// select the right Ad Id according to platform | |
var admobId = { | |
banner: 'ca-app-pub-8455136100973745/2086286117', | |
interstitial: 'ca-app-pub-8455136100973745/1357929318' | |
}; | |
document.addEventListener('deviceready', function () { | |
// standard banner options | |
var bannerOptions = { | |
adId: admobId.banner, | |
adSize: 'SMART_BANNER', | |
position: AdMob.AD_POSITION.BOTTOM_CENTER, | |
overlap: true, | |
orientationRenew: false, | |
autoShow: true, | |
isTesting: window.localStorage.getItem('adTestMode') | |
}; | |
// is the admob plugin active? | |
if (typeof AdMob != 'undefined') { | |
AdMob.createBanner(bannerOptions); | |
orientationChange.subscribe(function () { | |
AdMob.removeBanner(); | |
setTimeout(function () { | |
return AdMob.createBanner(bannerOptions); | |
}, 1000); | |
}); | |
// as soon as a game has ended, we'll prepare an interstitial ad | |
listenToGameEvent(GAME_EVENT_END, function () { | |
// prepare at beginning of a game level | |
AdMob.prepareInterstitial({ adId: admobId.interstitial, autoShow: false }); | |
}); | |
// the interstitial ad will be displayed when the dialog with the result is dismissed | |
listenToGameEvent(GAME_EVENT_RESULTDLG_DISMISS, function () { | |
// check and show it at end of a game level | |
setTimeout(function () { | |
return AdMob.showInterstitial(); | |
}, 1500); | |
}); | |
} | |
}, false); | |
//# sourceMappingURL=monetize.js.map |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment