Module:Chess viewer/sandbox
| This is the module sandbox page for Module:Chess viewer (diff). |
| This module is rated as alpha. It is ready for limited use and third-party feedback. It may be used on a small number of pages, but should be monitored closely. Suggestions for new features or adjustments to input and output are welcome. |
| This template uses Template:Calculator. |
| This module uses TemplateStyles: |
Usage
{{#invoke:Chess viewer|board|pgn=PGN HERE}}
Example:
| 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 | ||
Normally you would use {{Chess viewer}} instead of calling this module directly.
This module uses the calculator gadget. If javascript or the gadget is disabled, the user will see the starting ply but will not see the buttons to advance the game.
-- Creates a chess diagram with arrows and a play button in order to show an entire game.
-- Based on [[Module:Chessboard]], inspired by [[mw:Extension:Chessbrowser]]
-- This might be cleaner if it worked directly on the PGN notation instead of the converted form.
-- A future todo might be to have some sort of changing caption that shows what the current move is. This might improve accessibility as it would give screen readers something to read.
local p = {}
local yesno = require('Module:Yesno')
local cfg, nrows, ncols
cfg = {}
-- From Module:Chessboard/chess
function cfg.dims()
return 8, 8
end
function cfg.letters()
return {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}
end
-- type from [[:c:Category:Chessboard480]]
-- emerald, olivine, pearl, ruby, sapphire, steel, tourmaline, wood
function cfg.image_board(size, type)
local typestr = (type and type ~='' and '-' .. type) or ''
return string.format( '[[File:Chessboard480%s.svg|%dx%dpx|link=|class=notpageimage]]', typestr, 8 * size, 8 * size )
end
function cfg.image_square( pc, row, col, size )
local colornames = { l = 'white', d = 'black', u = 'unknown color' }
local piecenames = {
p = 'pawn',
r = 'rook',
n = 'knight',
b = 'bishop',
q = 'queen',
k = 'king',
a = 'archbishop',
c = 'chancellor',
z = 'champion',
w = 'wizard',
t = 'fool',
M = 'mann',
h = 'upside-down pawn',
m = 'upside-down rook',
B = 'upside-down bishop',
N = 'upside-down knight',
f = 'upside-down king',
g = 'upside-down queen',
e = 'elephant',
s = 'boat',
G = 'giraffe',
U = 'unicorn',
Z = 'zebra'
}
local symnames = {
xx = 'black cross',
ox = 'white cross',
xo = 'black circle',
oo = 'white circle',
ul = 'up-left arrow',
ua = 'up arrow',
ur = 'up-right arrow',
la = 'left arrow',
ra = 'right arrow',
dl = 'down-left arrow',
da = 'down arrow',
dr = 'down-right arrow',
lr = 'left-right arrow',
ud = 'up-down arrow',
db = 'up-right and down-left arrow',
dw = 'up-left and down-right arrow',
x0 = 'zero',
x1 = 'one',
x2 = 'two',
x3 = 'three',
x4 = 'four',
x5 = 'five',
x6 = 'six',
x7 = 'seven',
x8 = 'eight',
x9 = 'nine'
}
local colchar = {'a','b','c','d','e','f','g','h'}
local color = mw.ustring.gsub( pc, '^.*(%w)(%w).*$', '%2' ) or ''
local piece = mw.ustring.gsub( pc, '^.*(%w)(%w).*$', '%1' ) or ''
--local alt = colchar[col] .. row .. ' '
local alt = ''
if colornames[color] and piecenames[piece] then
alt = alt .. colornames[color] .. ' ' .. piecenames[piece]
else
alt = alt .. ( symnames[piece .. color] or piece .. ' ' .. color )
end
return string.format( '[[File:Chess %s%st45.svg|%dx%dpx|alt=%s|%s|link=|class=notpageimage|top]]', piece, color, size, size, alt, alt )
end
-- End Module:Chessboard/chess
-- start Module:Chessboard
local function innerboard(definedPieces, pieceInfo, size, type, rev, ply)
pattern = cfg.pattern or '%w%w'
local root = mw.html.create('div')
root:addClass('chess-pieces notheme')
:css('position', 'relative')
:wikitext(cfg.image_board(size, type))
for piece in pairs( definedPieces ) do
if piece:match( pattern ) then
local img = cfg.image_square(piece:match(pattern), row, col, size )
local curPos = pieceInfo[ply][piece] or {-1,-1}
local class = piece:match( "^[nN]" ) and 'knight' or 'nonknight'
root:tag('div')
:css('top', 'calc(var(--calculator-top_piece' .. piece .. ',' .. curPos[1] .. ')*1px)' )
:css('left', 'calc(var(--calculator-left_piece' .. piece .. ',' .. curPos[2] .. ')*1px)')
:css( 'opacity', 'calc(min(var(--calculator-top_piece' .. piece .. ',' .. curPos[1] .. '),0) + 1)' )
:addClass( class )
:addClass( 'calculator-field' )
:addClass( curPos[1] == -1 and "calculator-value-false" or "calculator-value-true" )
:attr( "data-calculator-type", "passthru" )
:attr( "data-calculator-formula", "ifgreaterorequal(top_piece" .. piece .. ",0)" )
:wikitext(img)
end
end
return tostring(root)
end
local function getPositions(args, size, rev)
pattern = cfg.pattern or '%w%w'
local pieceInfo = {}
local definedPieces = {}
local oldLayout = {}
local oldPieceNames = {}
local newPieceNames = {}
for moveIndex, move in ipairs( args ) do
--mw.log( "Doing move number " .. moveIndex )
pieceInfo[moveIndex] = {}
local layout = convertFenToArgs( move )
local checkPieceLoc = function ( layout, row, trow, tcol, allowOverride, registerPiece, realPieceName )
local col = rev and ( 1 + ncols - tcol ) or tcol
local piece = layout[ncols * ( nrows - row ) + col + 2] or ''
local pieceName = piece:match( pattern )
if pieceName then
while true do
if realPieceName then
pieceName = realPieceName
end
-- If there is a promotion you might suddenly have multiple of the same piece.
if pieceInfo[moveIndex][pieceName] or (not(allowOverride) and pieceInfo[moveIndex][pieceName] == false) then
pieceName = pieceName .. '_'
else
break
end
end
if registerPiece then
--mw.log( "placing " .. pieceName )
definedPieces[pieceName] = true
pieceInfo[moveIndex][pieceName] = { ( trow - 1 ) * size, ( tcol - 1 ) * size }
newPieceNames[ (( trow - 1 ) * size) .. (( tcol - 1 ) * size)] = pieceName
else
--mw.log( "Marking " .. pieceName .. " as used but so far skipped" )
pieceInfo[moveIndex][pieceName] = false
end
end
end
local availablePieces = {}
for trow = 1,nrows do
local row = rev and trow or ( 1 + nrows - trow )
for tcol = 1,ncols do
-- Hacky, but first do this for pieces that haven't moved, then for pieces that have.
local col = rev and ( 1 + ncols - tcol ) or tcol
local samePiece = (layout[ncols * ( nrows - row ) + col + 2] or '') == (oldLayout[ncols * ( nrows - row ) + col + 2] or '')
local realPiece = oldPieceNames[ (( trow - 1 ) * size) .. (( tcol - 1 ) * size)]
local originalPiece = (oldLayout[ncols * ( nrows - row ) + col + 2] or ''):match(pattern)
if samePiece then
checkPieceLoc( oldLayout, row, trow, tcol, false, true, realPiece )
--mw.log( "first round. doing " .. trow .. "x" .. tcol .. " for " .. (layout[ncols * ( nrows - row ) + col + 2] or '') )
else
checkPieceLoc( oldLayout, row, trow, tcol, false, false, realPiece )
if originalPiece and realPiece then
availablePieces[originalPiece] = realPiece --realPiece
end
--mw.log( "first round skip. doing " .. trow .. "x" .. tcol .. " for " .. (layout[ncols * ( nrows - row ) + col + 2] or '') )
end
end
end
for trow = 1,nrows do
local row = rev and trow or ( 1 + nrows - trow )
for tcol = 1,ncols do
-- Hacky, now do this for moved pieces
local col = rev and ( 1 + ncols - tcol ) or tcol
local samePiece = (layout[ncols * ( nrows - row ) + col + 2] or '') == (oldLayout[ncols * ( nrows - row ) + col + 2] or '')
if not(samePiece) then
--mw.log( "Second round. doing " .. trow .. "x" .. tcol .. " for " .. (layout[ncols * ( nrows - row ) + col + 2] or '') )
local originalPiece = (layout[ncols * ( nrows - row ) + col + 2] or ''):match(pattern)
checkPieceLoc( layout, row, trow, tcol, true, true, availablePieces[originalPiece] )
end
end
end
oldLayout = layout
oldPieceNames = newPieceNames
end
return definedPieces, pieceInfo
end
function chessboard(definedPieces, pieceInfo, size, type, rev, letters, numbers, header, footer, align, clear, ply)
function letters_row( rev, num_lt, num_rt )
local letters = cfg.letters()
local root = mw.html.create('')
if num_lt then
root:tag('td')
end
for k = 1,ncols do
root:tag('td')
:css('height', '18px')
:css('width', size .. 'px')
:addClass( 'boardlabel' )
:wikitext(rev and letters[1+ncols-k] or letters[k])
end
if num_rt then
root:tag('td')
end
return tostring(root)
end
local letters_top = letters:match( 'both' ) or letters:match( 'top' )
local letters_bottom = letters:match( 'both' ) or letters:match( 'bottom' )
local numbers_left = numbers:match( 'both' ) or numbers:match( 'left' )
local numbers_right = numbers:match( 'both' ) or numbers:match( 'right' )
local width = ncols * size + 2
if ( numbers_left ) then width = width + 18 end
if ( numbers_right ) then width = width + 18 end
local root = mw.html.create('div')
:addClass('chessboard')
:addClass( 'calculator-container' )
:addClass('thumb')
:addClass('noviewer')
:addClass(align)
if( header and header ~= '' ) then
root:tag('div')
:addClass('center')
:css('line-height', '130%')
:css('margin', '0 auto')
:css('max-width', (width + ncols) .. 'px')
:wikitext(header)
end
local div = root:tag('div')
:addClass('thumbinner')
:css('width', width .. 'px')
local b = div:tag('table')
:attr('cellpadding', '0')
:attr('cellspacing', '0')
:addClass( 'calculator-field' )
:attr( 'data-calculator-type', 'passthru' )
:attr( 'data-calculator-formula', 'rotate' )
if ( letters_top ) then
b:tag('tr')
:addClass( 'boardlabel' )
:wikitext(letters_row( rev, numbers_left, numbers_right ))
end
local tablerow = b:tag('tr')
if ( numbers_left ) then
tablerow:tag('td')
:css('width', '18px')
:css('height', size .. 'px')
:addClass( 'boardlabel' )
:wikitext(rev and 1 or nrows)
end
local td = tablerow:tag('td')
:attr('colspan', ncols)
:attr('rowspan', nrows)
:wikitext(innerboard(definedPieces, pieceInfo, size, type, rev, ply))
if ( numbers_right ) then
tablerow:tag('td')
:css('width', '18px')
:css('height', size .. 'px')
:addClass( 'boardlabel' )
:wikitext(rev and 1 or nrows)
end
if ( numbers_left or numbers_right ) then
for trow = 2, nrows do
local idx = rev and trow or ( 1 + nrows - trow )
tablerow = b:tag('tr')
if ( numbers_left ) then
tablerow:tag('td')
:css('height', size .. 'px')
:addClass( 'boardlabel' )
:wikitext(idx)
end
if ( numbers_right ) then
tablerow:tag('td')
:css('height', size .. 'px')
:addClass( 'boardlabel' )
:wikitext(idx)
end
end
end
if ( letters_bottom ) then
b:tag('tr')
:wikitext(letters_row( rev, numbers_left, numbers_right ))
end
if footer and mw.text.trim(footer)~='' then
div:tag('div')
:addClass('thumbcaption')
:wikitext(footer)
end
return tostring(root) ..
mw.getCurrentFrame():extensionTag( 'templatestyles', '', { src = 'Module:Chessboard/styles.css' } )
end
function convertFenToArgs( fen )
-- converts FEN notation to 64 entry array of positions, offset by 2
local res = { ' ', ' ' }
-- Loop over rows, which are delimited by /
for srow in string.gmatch( "/" .. fen, "/%w+" ) do
-- Loop over all letters and numbers in the row
for piece in srow:gmatch( "%w" ) do
if piece:match( "%d" ) then -- if a digit
for k=1,piece do
table.insert(res,' ')
end
else -- not a digit
local color = piece:match( '%u' ) and 'l' or 'd'
piece = piece:lower()
table.insert( res, piece .. color )
end
end
end
return res
end
function convertArgsToFen( args, offset )
function nullOrWhitespace( s ) return not s or s:match( '^%s*(.-)%s*$' ) == '' end
function piece( s )
return nullOrWhitespace( s ) and 1
or s:gsub( '%s*(%a)(%a)%s*', function( a, b ) return b == 'l' and a:upper() or a end )
end
local res = ''
offset = offset or 0
for row = 1, 8 do
for file = 1, 8 do
res = res .. piece( args[8*(row - 1) + file + offset] )
end
if row < 8 then res = res .. '/' end
end
return mw.ustring.gsub(res, '1+', function( s ) return #s end )
end
local function getPieceMoves( definedPieces, pieceInfo )
local ret = ''
for piece in pairs( definedPieces ) do
local curTop = -1
local curLeft = -1
local switchTop = 'switch(move,'
local switchLeft = 'switch(move,'
for moveNumber, move in ipairs( pieceInfo ) do
if (move[piece] and move[piece][1] or -1) ~= curTop then
switchTop = switchTop .. (moveNumber-1) .. ',' .. curTop .. ','
curTop = (move[piece] and move[piece][1] or -1)
end
if move[piece] and move[piece][2] or -1 ~= curLeft then
switchLeft = switchLeft .. (moveNumber-1) .. ',' .. curLeft .. ','
curLeft = (move[piece] and move[piece][2] or -1)
end
end
switchTop = switchTop .. curTop .. ')'
switchLeft = switchLeft .. curLeft .. ')'
ret = ret .. '{{calculator|type=hidden|formula=' .. switchTop .. '|id=top_piece' .. piece ..'}}'
ret = ret .. '{{calculator|type=hidden|formula=' .. switchLeft .. '|id=left_piece' .. piece .. '}}'
end
return ret
end
function p.board(frame)
local args = frame.args
local pargs = frame:getParent().args
local style = args.style or pargs.style or 'Chess'
nrows, ncols = cfg.dims()
local size = args.size or pargs.size or '26'
local type = args.type or pargs.type or nil
local reverse = ( args.reverse or pargs.reverse or '' ):lower() == "true"
local letters = ( args.letters or pargs.letters or 'both' ):lower()
local numbers = ( args.numbers or pargs.numbers or 'both' ):lower()
local header = args[2] or pargs[2] or ''
local footer = args[nrows*ncols + 3] or pargs[nrows*ncols + 3] or ''
local align = ( args[1] or pargs[1] or 'floatright' ):lower()
local clear = args.clear or pargs.clear or ( align:match('floatright') and 'right' ) or 'none'
local ply = tonumber(args.ply or 1)
local pgn = args.pgn or pargs.pgn
local moves, metadata
local definedPieces, pieceInfo
size = mw.ustring.match( size, '[%d]+' ) or '26' -- remove px from size
assert( pgn, "pgn argument required" )
local pgnModule = require('Module:Pgn')
metadata, moves = pgnModule.main(pgn)
definedPieces, pieceInfo = getPositions( moves, size, reverse )
ply = math.max( 1, math.min( ply, #moves ) )
align = args.align or pargs.align or 'floatright'
clear = args.clear or pargs.clear or ( align:match('floatright') and 'right' ) or 'none'
header = args.header or pargs.header or ''
footer = args.footer or pargs.footer or ''
footer = footer .. frame:extensionTag( 'templatestyles', '', { src = 'Module:Chess_viewer/styles.css' } )
if yesno(args.animate or pargs.animate or true) then
align = align .. ' animate'
end
header = header .. frame:preprocess( '<div style="display:none" class="calculatorgadget-enabled">'
.. '{{calculator button|disabled=iflessorequal(move,1)|contents=←|title=Go back one move|alt=Go back one move|for=move|type=default|formula=max(1, move-1)}}'
.. ' {{calculator button|contents=☯|alt=Rotate board|title=Rotate board|for=rotate|type=default|formula=(rotate+1)%2}} '
.. ' {{calculator button|disabled=ifgreaterorequal(move,'..#moves.. ')|contents={{calculator|type=plain|mapping={"▶": 0, "⏸": 1}|default=▶|formula=and(timer(),ifless(move,'..#moves.. '))}}|alt=Playback game|title=Play|for=move|type=default|formula=min(' .. (#moves) .. ', move+1)|delay=0.7|toggle=true|until=ifgreaterorequal(move,'.. #moves .. ')|max iterations=' .. #moves .. '}} '
.. '{{calculator button|disabled=ifgreaterorequal(move,'..#moves.. ')|contents=→|title=Go forward one move|alt=Go forward one move|for=move|type=default|formula=min(' .. (#moves) .. ', move+1)}}'
.. '{{calculator|type=hidden|default=' .. ply .. '|id=move}}'
.. '{{calculator|type=hidden|default=0|id=rotate}}</div>'
)
footer = footer .. frame:preprocess( getPieceMoves( definedPieces, pieceInfo ) )
return chessboard( definedPieces, pieceInfo, size, type, reverse, letters, numbers, header, footer, align, clear, ply )
end
return p
Content Disclaimer
Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.
- The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
- There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
- It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
- Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
- Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.