diff --git a/css/chess.css b/css/chess.css index 363fb77..cecac77 100644 --- a/css/chess.css +++ b/css/chess.css @@ -95,6 +95,9 @@ button:disabled .silhouette { #cb_control_form { flex: 0 1 auto; + display: flex; + flex-flow: column nowrap; + margin-right: auto; } #cb_scrollable { @@ -107,17 +110,69 @@ button:disabled .silhouette { margin-top: 0; } -#cb_controls, #cb_names, #cb_game, #cb_navigate { +#cb_controls, #cb_names, #cb_message, #cb_game, #cb_navigate { margin-top: 0.5em; white-space: nowrap; } +#cb_undo { + margin-left: auto; + margin-right: 0.1em; +} + +#cb_redo { + margin-left: 0.1em; + margin-right: auto; +} + +#cb_resign { + margin-left: 0.1em; + margin-right: 0; +} + +#cb_navigate { + display: flex; + flex-flow: row nowrap; + justify-items: center; + justify-content: center; + padding-left: 1em; +} + +#cb_navigate button { + margin-left: 0.1em; + margin-right: 0.1em; +} + +#cb_navigate button:first-child { + margin-left: 0; +} + +#cb_navigate button:last-child { + margin-right: 0; +} + #cb_history { max-width: 7.5in; } -#cb_reset, #cb_pass { - display: none; +.cb-hbox { + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +.cb-vbox { + display: flex; + flex-flow: column nowrap; + align-items: center; +} + +.cb-align-start { + align-items: start; +} + +.cb-align-stretch { + align-items: stretch; } .cb-square { @@ -204,20 +259,27 @@ button:disabled .silhouette { background-color: rgba(128,128,128,0.5); } -.cb-dk-piece { +.cb-lt-piece, .cb-dk-piece { position: absolute; top: 17.5%; - left: 47.5%; width: 55%; height: 65%; } .cb-lt-piece { - position: absolute; - top: 17.5%; left: -2.5%; - width: 55%; - height: 65%; +} + +.cb-dk-piece { + left: 47.5%; +} + +#cb_board.cb-reversed .cb-lt-piece { + left: 47.5%; +} + +#cb_board.cb-reversed .cb-dk-piece { + left: -2.5%; } #cb_phantom > .cb-dk-piece { @@ -267,6 +329,7 @@ button:disabled .silhouette { } #cb_control_container { + margin-top: 0.5em; margin-left: 8px; max-height: none; overflow: visible; diff --git a/index.html b/index.html index 28a1ece..d32414e 100644 --- a/index.html +++ b/index.html @@ -1,270 +1,284 @@ - - - - - - - Paco Ŝako - - -
-

Paco Ŝako

+ + + + + + + Paco Ŝako + + +
+

Paco Ŝako

-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
A
B
C
D
E
F
G
H
8
8
7
7
6
6
5
5
4
4
3
3
2
2
1
1
A
B
C
D
E
F
G
H
-
-
-
-
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A
B
C
D
E
F
G
H
8
8
7
7
6
6
5
5
4
4
3
3
2
2
1
1
A
B
C
D
E
F
G
H
+
+
+
+
-
-
-
- - - - - - -
-
-
- - vs. -
-
- - -
-
- - - - - - -
-
-
-

-
- Rule Reference -

The basic movement for each piece is the same as traditional chess.

-

Pieces are never removed from the board. Instead, the capturing piece shares the square with the captured piece, and the two pieces become a joined pair.

-

When either piece moves from a square containing two pieces, the other piece moves with it. - Each player can only move a joined pair according to the rules for their own piece. Joined pieces cannot capture other pieces.

-

A free (non-joined) piece may move into a square occupied by pieces of both colors. - When it does, the other piece of the same color becomes a free piece and must move to a new location following the normal movement rules. - This process may be repeated multiple times in the same turn.

-

Ŝako (checkmate) occurs when an opposing piece joins with the king. - Important: The king is not permitted to join with (capture) other pieces.

-

Due to joined movement, it is possible for a pawn to be moved backward to, or past, its starting row. - Pawns may move forward two spaces from either of the first two rows on their own side of the board.

-

When a pawn is captured en passant while joined with another piece, the captured pawn moves back one square to become joined with the capturing pawn - and the joined piece from the capturing side moves to a new location from its original position as with any other capture.

-

Pawns are promoted when they reach the final row on the opposite side of the board, even if they were moved there by the other player as part of a joined pair.

-
-

-
-
-
+
+
+
+
+
+
+
+
+
+
+ + + +
+
+ + + + + + +
+
+
+
+
+ + vs. +
+
+
+ + +
+
+
+

+
+ Rule Reference +

The basic movement for each piece is the same as traditional chess.

+

Pieces are never removed from the board. Instead, the capturing piece shares the square with the captured piece, and the two pieces become a joined pair.

+

When either piece moves from a square containing two pieces, the other piece moves with it. + Each player can only move a joined pair according to the rules for their own piece. Joined pieces cannot capture other pieces.

+

A free (non-joined) piece may move into a square occupied by pieces of both colors. + When it does, the other piece of the same color becomes a free piece and must move to a new location following the normal movement rules. + This process may be repeated multiple times in the same turn.

+

Ŝako (checkmate) occurs when an opposing piece joins with the king. + Important: The king is not permitted to join with (capture) other pieces.

+

Due to joined movement, it is possible for a pawn to be moved backward to, or past, its starting row. + Pawns may move forward two spaces from either of the first two rows on their own side of the board.

+

When a pawn is captured en passant while joined with another piece, the captured pawn moves back one square to become joined with the capturing pawn + and the joined piece from the capturing side moves to a new location from its original position as with any other capture.

+

Pawns are promoted when they reach the final row on the opposite side of the board, even if they were moved there by the other player as part of a joined pair.

+
+

+
+
+
- + - - - - - - - - - - - - - + +
+
+ + + + + + + + + + + + + + - + diff --git a/js/pacosako_ui.js b/js/pacosako_ui.js index 345048d..6a4cf89 100644 --- a/js/pacosako_ui.js +++ b/js/pacosako_ui.js @@ -57,29 +57,6 @@ $(function (){ } } - function squareClickDestination(ev, ui) { - let selected = $('#cb_board .cb-selected'); - if (selected.length !== 1) { - renderBoard(); - return; - } - - let from = selected.data('location'); - let to = this.id.replace(/^cb_/, ''); - selected.appendTo('#cb_hidden'); - - try { - const meta = { timestamp: new Date(Gun.state()).getTime() }; - currentGame.move(from, to, meta); - } catch (err) { - debug('unable to move', err); - } - - $('#cb_board').data('last_state', currentGame.moves); - setCurrentBoard(currentGame); - putState(); - } - function pieceStartMove(piece, event) { const side = piece.data('side'); const type = piece.data('type'); @@ -96,6 +73,36 @@ $(function (){ } } + function pieceEndMove(piece, to) { + let from = piece.data('location'); + piece.appendTo('#cb_hidden'); + + try { + const meta = { timestamp: new Date(Gun.state()).getTime() }; + currentGame.move(from, to, meta); + } catch (err) { + debug('unable to move', err); + } + + $('#cb_board').data('last_state', currentGame.moves); + setCurrentBoard(currentGame); + putState(); + } + + function squareClickDestination(ev, ui) { + let selected = $('#cb_board .cb-selected'); + if (selected.length !== 1) { + renderBoard(); + return; + } + + pieceEndMove(selected, this.id.replace(/^cb_/, '')); + } + + function squareDropDestination(ev, ui) { + pieceEndMove(ui.draggable, this.id.replace(/^cb_/, '')); + } + function pieceClickUnselect(ev, ui) { renderBoard(); } @@ -268,7 +275,19 @@ $(function (){ return res; } - //const PacoSakoUUID = '7c38edd4-c931-49c8-9f1a-84de560815db'; /* V1 release */ + function shortenName(name) { + name = name.replace(/^\s+/, ''); + name = name.replace(/\s+$/, ''); + name = name.replace(/\s+/, ' '); + + let found = name.match(/^(.{0,20})(\s|$)/); + if (found) { + return found[1]; + } + + return name.slice(0, 20) + '…'; + } + const PacoSakoUUID = 'b425b812-6bdb-11ea-9414-6f946662bac3'; /* V2 release */ function putState() { @@ -380,10 +399,10 @@ $(function (){ let callback = function(d) { d = d || {}; debug('got meta', d); - $('#cb_board').data('lightName', d.lightName || 'Light'); - $('#cb_board').data('darkName', d.darkName || 'Dark'); - $('#cb_light_name').val(d.lightName || ''); - $('#cb_dark_name').val(d.darkName || ''); + $('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light'))); + $('#cb_board').data('darkName', shortenName(String(d.darkName || 'Dark'))); + $('#cb_light_name').val(String(d.lightName || '')); + $('#cb_dark_name').val(String(d.darkName || '')); }; cancelMetaCallback = function() { callback = function() {}; }; @@ -453,6 +472,33 @@ $(function (){ } catch (err) {} } + function arrangeBoard(reversed) { + let rows = '9876543210'.split(''); + let columns = 'LabcdefghR'.split(''); + const boardElem = $('#cb_board'); + + if (reversed) { + rows.reverse(); + columns.reverse(); + boardElem.addClass('cb-reversed'); + } else { + boardElem.removeClass('cb-reversed'); + } + + const nd_img_src = $('#cb_img_nd_' + (reversed ? 'flipped' : 'normal'))[0].src; + const nl_img_src = $('#cb_img_nl_' + (reversed ? 'flipped' : 'normal'))[0].src; + $('.cb-piece.cb-dk-piece.cb-knight').attr('src', nd_img_src); + $('.cb-piece.cb-lt-piece.cb-knight').attr('src', nl_img_src); + + for (const row of rows) { + const rowElem = $('#cb_row' + row); + rowElem.appendTo(boardElem); + for (const column of columns) { + $('#cb_' + column + row).appendTo(rowElem); + } + } + } + try { if (Notification.permission === 'denied') { disableNotify(); @@ -463,25 +509,37 @@ $(function (){ disableNotify(); } + const LS_KEY_NOTIFY = 'pacosako/notify'; + const LS_KEY_REVERSE = 'pacosako/reverse'; + if ('localStorage' in window) { - function updateNotify(newValue){ - const doNotify = newValue === 'on'; - const cb_notify = $('#cb_notify')[0]; - if (doNotify) { - if (!cb_notify.checked) { - cb_notify.checked = true; + function fromStorage(key, value) { + debug('from localStorage', { key: key, value: value }); + if (key === LS_KEY_NOTIFY) { + const doNotify = value === 'on'; + const cb_notify = $('#cb_notify')[0]; + cb_notify.checked = doNotify; + if (doNotify && !cb_notify.checked) { requestNotify(); } - } else if (cb_notify.checked) { - cb_notify.checked = false; + } else if (key === LS_KEY_REVERSE) { + const doReverse = value === 'on'; + const cb_reverse = $('#cb_reverse')[0]; + cb_reverse.checked = doReverse; + arrangeBoard(doReverse); } } + $(window).on('storage', function(event){ - if (event.originalEvent.key === '/pacosako/notify') { - updateNotify(event.originalEvent.newValue); - } + fromStorage(event.originalEvent.key, event.originalEvent.newValue); }); - updateNotify(window.localStorage.getItem('/pacosako/notify')); + + for (const key of [LS_KEY_NOTIFY, LS_KEY_REVERSE]) { + const value = window.localStorage.getItem(key); + if (value !== null) { + fromStorage(key, value); + } + } } $('.cb-square').droppable({ @@ -490,23 +548,24 @@ $(function (){ deactivate: function(ev, ui){ $(this).droppable('disable'); }, - drop: function(ev, ui) { - let dragged = ui.draggable; - let from = dragged.data('location'); - let to = this.id.replace(/^cb_/, ''); - dragged.appendTo('#cb_hidden'); + drop: squareDropDestination, + }); - try { - const meta = { timestamp: new Date(Gun.state()).getTime() }; - currentGame.move(from, to, meta); - } catch (err) { - debug('unable to move', err); - } + $('#cb_notify').on('change', function(){ + if ('localStorage' in window) { + window.localStorage.setItem(LS_KEY_NOTIFY, this.checked ? 'on' : 'off'); + } + if (this.checked) { + requestNotify(); + } + }); - $('#cb_board').data('last_state', currentGame.moves); - setCurrentBoard(currentGame); - putState(); - }, + $('#cb_reverse').on('change', function(){ + debug('cb_reverse changed to ' + this.checked); + if ('localStorage' in window) { + window.localStorage.setItem(LS_KEY_REVERSE, this.checked ? 'on' : 'off'); + } + arrangeBoard(this.checked); }); $('#cb_undo').on('click', function(){ @@ -603,15 +662,6 @@ $(function (){ } }); - $('#cb_notify').on('change', function(){ - if ('localStorage' in window) { - window.localStorage.setItem('/pacosako/notify', this.checked ? 'on' : 'off'); - } - if (this.checked) { - requestNotify(); - } - }); - let updateMeta = function() { putMeta(); } $('#cb_light_name').on('input', updateMeta); $('#cb_dark_name').on('input', updateMeta); @@ -637,59 +687,61 @@ $(function (){ opt.text(opt.data('title') + age_str); } - gun.get(PacoSakoUUID).get('meta').map().on(function(d,key){ - if (key.match(/^[0-9a-f]{16}$/) && d) { - debug('got meta for key ' + key, d); - const lightName = d.lightName ? d.lightName : 'Light'; - const darkName = d.darkName ? d.darkName : 'Dark'; - const moves = !d.moves ? '' : - (', ' + d.moves + (d.moves === 1 ? ' move' : ' moves')); + window.setTimeout(function(){ + gun.get(PacoSakoUUID).get('meta').map().on(function(d,key){ + if (key.match(/^[0-9a-f]{16}$/) && d) { + debug('got meta for key ' + key, d); + const lightName = shortenName(String(d.lightName || 'Light')); + const darkName = shortenName(String(d.darkName || 'Dark')); + const moves = !d.moves ? '' : + (', ' + d.moves + (d.moves === 1 ? ' move' : ' moves')); - let opt = $('#cb_game_' + key); + let opt = $('#cb_game_' + key); - if (!(d.lightName || d.darkName) && !d.moves && key !== $('#cb_board').data('gameId')) { - if (opt.length >= 1) { - opt.remove(); - } - } else { - if (opt.length === 0) { - opt = $(''); - opt.attr('id', 'cb_game_' + key); + if (!d.lightName && !d.darkName && !d.moves && key !== $('#cb_board').data('gameId')) { + if (opt.length >= 1) { + opt.remove(); + } + } else { + if (opt.length === 0) { + opt = $(''); + opt.attr('id', 'cb_game_' + key); + } + + let stat = ''; + if (d.status) { + stat = ', ' + d.status; + } + + opt.data('gameId', key); + opt.data('title', lightName + ' vs. ' + darkName + moves + stat); + opt.data('then', d.timestamp || new Date(Gun.state()).getTime()); + opt.addClass('cb-game-option'); + opt.appendTo('#cb_select_game'); + updateTitle(opt); + + let select = $('#cb_select_game'); + let list = select.children('.cb-game-option').get(); + list.sort(function(a,b) { + const then_a = $(a).data('then'); + const then_b = $(b).data('then'); + return (then_a < then_b) ? 1 : (then_a === then_b) ? 0 : -1; + }); + + for (const e of list) { + $(e).appendTo(select); + } } - let stat = ''; - if (d.status) { - stat = ', ' + d.status; - } - - opt.data('gameId', key); - opt.data('title', lightName + ' vs. ' + darkName + moves + stat); - opt.data('then', d.timestamp || new Date(Gun.state()).getTime()); - opt.addClass('cb-game-option'); - opt.appendTo('#cb_select_game'); - updateTitle(opt); - - let select = $('#cb_select_game'); - let list = select.children('.cb-game-option').get(); - list.sort(function(a,b) { - const then_a = $(a).data('then'); - const then_b = $(b).data('then'); - return (then_a < then_b) ? 1 : (then_a === then_b) ? 0 : -1; - }); - - for (const e of list) { - $(e).appendTo(select); + let selOpt = $('#cb_game_' + $('#cb_board').data('gameId')); + if (selOpt.length === 1) { + $('#cb_select_game')[0].selectedIndex = selOpt.index(); + } else { + $('#cb_select_game')[0].selectedIndex = -1; } } - - let selOpt = $('#cb_game_' + $('#cb_board').data('gameId')); - if (selOpt.length === 1) { - $('#cb_select_game')[0].selectedIndex = selOpt.index(); - } else { - $('#cb_select_game')[0].selectedIndex = -1; - } - } - }); + }); + }, 1); window.setInterval(function(){ $('#cb_select_game').first().children('.cb-game-option').each(function(idx,opt){ diff --git a/svg/Chess_ndt45_flip.svg b/svg/Chess_ndt45_flip.svg new file mode 100644 index 0000000..e753782 --- /dev/null +++ b/svg/Chess_ndt45_flip.svg @@ -0,0 +1,2 @@ + + diff --git a/svg/Chess_nlt45_flip.svg b/svg/Chess_nlt45_flip.svg new file mode 100644 index 0000000..e6095e1 --- /dev/null +++ b/svg/Chess_nlt45_flip.svg @@ -0,0 +1,2 @@ + + diff --git a/svg/unoptimized/Chess_ndt45_flip.svg b/svg/unoptimized/Chess_ndt45_flip.svg new file mode 100644 index 0000000..a46c514 --- /dev/null +++ b/svg/unoptimized/Chess_ndt45_flip.svg @@ -0,0 +1,81 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/svg/unoptimized/Chess_nlt45_flip.svg b/svg/unoptimized/Chess_nlt45_flip.svg new file mode 100644 index 0000000..0de6942 --- /dev/null +++ b/svg/unoptimized/Chess_nlt45_flip.svg @@ -0,0 +1,75 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + +