Modulo:mchessboard

 MODULO
Memtesto disponeblas sur la paĝo Ŝablono:ŝakdiagramo.

--[===[

MODULE "MCHESS" (chessboard)

"eo.wiktionary.org/wiki/Modulo:mchessboard" <!--2022-Oct-18-->
"sv.wiktionary.org/wiki/Modul:schackdiagram"

Purpose: brews a chessboard with pieces

Utilo: kreas sxakatabulon kun sxakpecoj

Manfaat: memperlihatkan ...

Syfte: skapar ett schackbraede med pjaeser

Used by templates / Uzata far sxablonoj:
- "Mall:schackdiagram"

Required submodules / Bezonataj submoduloj:
- none / neniuj

Required images:
- many from "commons.wikimedia.org/wiki/Category:SVG_chess_piec
  es/Standard" dark background and from "commons.wikimedia.org/wiki/Ca
  tegory:SVG_chess_pieces/Standard_light" light background, 4
  licenses apply: GPL GFDL CC BSD

This module can accept parameters whether sent to itself (own frame) or
to the caller (caller's frame). If there is a parameter "caller=true"
on the own frame then that own frame is discarded in favor of the
caller's one.

Incoming: * named and optional
            * "dim=" (for example "2x26", integer values 2...26,
               X-size -- lowercase "x" -- Y-size, no spaces, default "8x8",
              special value "all" to show all possible images, all
              other parameters prohibited then and board size is 2x7x4,
              special value "def" for default beginning position, anonymous
              parameters prohibited then)
            * "btm=" (length 2...200 or empty -- text below the board)
          * anonymous
            * either as many parameters as there are fields (hor * ver),
              every of them exactly 2 char:s long, "--" for empty field,
              "++" for highlighted empty field, or no anonymous parameters
              for empty chessboard, absence of parameters
              required for "dim=all" and "dim=def"
          * 1 hidden parameter
            * "nocat=true" no error possible

{{chess
|--|--|--|--|kd|--|--|rd
|--|--|--|--|--|--|--|--
|--|--|--|--|--|bl|--|pl
|--|--|--|rl|--|kl|pd|pl
|--|--|--|--|--|--|--|--
|--|--|--|--|--|--|--|--
|--|--|--|--|--|--|--|--
|--|--|--|--|--|--|--|--
|btm = deliver checkmate in two moves
}}

Pagename is never accessed.

Naming at commons:
"Chess bdd45.svg"   bishop dark, field dark
"Chess bDd45.svg"   bishop dark, field dark highlighted
"Chess bdl45.svg"   bishop dark, field light
"Chess d45.svg"     empty field dark
"Chess HLd45.svg"   empty field dark highlighted
"Chess l45.svg"     empty field light

Core part of the filename has 3 letters:
* type of piece (r,n,b,q,k,p) always lowercase for standard chess
* colour of piece (l,d,L,D) uppercase to highlight the field (!!!)
* colour of field (l,d) always lowercase, further "t" is
  neutral / transparent, but the field cannot be highlighted then

Pieces:
r rook
n knight
b bishop
q consilor
k king
p pawn

Number of image files required:
* genuine types of pieces -> 6
* every piece can be light or dark, mul by two -> 12
* plus empty field (no colour of piece) add one -> 13
* for any of above the field can be light or dark and
  highlighted or not, mul by 4 -> 52

The top left field is "a8" and is light.

Error codes:
* #E01 internal
* #E02 parameter "dim=" is bad
* #E03 parameter "btm=" is bad
* #E04 wrong number of anonymous parameters
* #E05 wrong content of anonymous parameter
* #E06 "dim=def" used together with anon parameter
* #E07 "dim=all" used together with other parameter

]===]

local exporttable = {}

------------------------------------------------------------------------

---- CONSTANTS [O] ----

------------------------------------------------------------------------

  local file_prefix   = 'Chess '
  local file_suffix   = '45.svg'

  local const_tab_six_pieces = {['r']=true,['n']=true,['b']=true,['q']=true,['k']=true,['p']=true}
  local const_tab_col_piece  = {['l']=true,['d']=true,['L']=true,['D']=true}
  local const_tab_col_bckg   = {[0]='l',[1]='d'}

  local const_tab_standard = {'rd','nd','bd','qd','kd','bd','nd','rd',
                              'pd','pd','pd','pd','pd','pd','pd','pd',
                              '--','--','--','--','--','--','--','--',
                              '--','--','--','--','--','--','--','--',
                              '--','--','--','--','--','--','--','--',
                              '--','--','--','--','--','--','--','--',
                              'pl','pl','pl','pl','pl','pl','pl','pl',
                              'rl','nl','bl','ql','kl','bl','nl','rl'} -- 8x8

  local const_tab_all = {'rl','nl','bl','ql','kl','pl','--',
                         'rd','nd','bd','qd','kd','pd','--',
                         'rL','nL','bL','qL','kL','pL','++',
                         'rD','nD','bD','qD','kD','pD','++'} -- 2x7x4

  local const_tracking = "[[Kategorio:Erara uzo de ".. string.char(0xC5,0x9D) .."ablono]]"

------------------------------------------------------------------------

---- NUMBER CONVERSION FUNCTIONS [N] ----

------------------------------------------------------------------------

-- Convert 1 decimal ASCII digit to integer 0...9 (255 if invalid).

local function lfdec1digit (num1digit)
  num1digit = num1digit - 48 -- may become invalid
  if ((num1digit<0) or (num1digit>9)) then
    num1digit = 255
  end--if
  return num1digit
end--function lfdec1digit

------------------------------------------------------------------------

-- Convert 1 or 2 decimal ASCII digits to integer 0...99 (255 if invalid).

-- Depends on functions :
-- [N] lfdec1digit

local function lfstr12dig2int (strmasuk)
  local numleen = 0
  local numaa   = 255 -- preassign to "error"
  local numbb   = 0
  numleen = string.len (strmasuk)
  if ((numleen==1) or (numleen==2)) then
    numaa = string.byte (strmasuk,1,1)
    numaa = lfdec1digit (numaa) -- 255 if invalid, ZERO would be valid
  end--if
  if ((numaa~=255) and (numleen==2)) then
    numbb = string.byte (strmasuk,2,2)
    numbb = lfdec1digit (numbb) -- 255 if invalid, ZERO would be valid
    if (numbb==255) then
      numaa = 255 -- 255 is invalid, note that ZERO would be valid
    else
      numaa = numaa * 10 + numbb -- valid integer number 0...99 now
    end--if
  end--if
  return numaa
end--function lfstr12dig2int

------------------------------------------------------------------------

---- HIGH LEVEL STRING FUNCTIONS [I] ----

------------------------------------------------------------------------

function iIntegerToCssHeight (num_height) -- called from iTrStyle only
  return 'line-height:' .. tostring(num_height-2) .. 'px;' -- this is obscure !!!FIXME!!!
end--function iIntegerToCssHeight

function iTrStyle (num_tr_height, boo_omit_tr_style)
  local my_tr = ''
  if (boo_omit_tr_style) then
    my_tr = '<tr>' -- for "show all" only
  else
    my_tr = '<tr style="' .. iIntegerToCssHeight(num_tr_height) .. '">'
  end--if
  return my_tr
end--function iTrStyle

------------------------------------------------------------------------

function iIntegerToCssWidth (num_width) -- also called from main
  return 'width:' .. tostring(num_width) .. 'px;'
end--function iIntegerToCssWidth

function iTdStyle (num_td_width, boo_omit_td_style)
  local my_td = ''
  if (boo_omit_td_style) then
    my_td = '<td style="text-align:center;">' -- for "show all" only
  else
    my_td = '<td style="' .. iIntegerToCssWidth(num_td_width) .. 'padding:0; font-size:12px; text-align:center;">'
  end--if
  return my_td
end--function iTdStyle

------------------------------------------------------------------------

---- HIGH LEVEL FUNCTIONS [H] ----

------------------------------------------------------------------------

-- Split string like "26x2" into 2 integers (here
-- values 0...99 are accepted).

-- Depends on functions :
-- [N] lfstr12dig2int

function hiSplitDimIntoTwoIntegers (str_combo_dim)

  local var_tnp = 0
  local in_len_min_three_max_five = 0
  local dim_xx = 255
  local dim_yy = 255

  while true do -- fake loop
    in_len_min_three_max_five = string.len(str_combo_dim)
    if ((in_len_min_three_max_five<3) or (in_len_min_three_max_five>5)) then
      break
    end--if
    var_tnp = string.find(str_combo_dim, "x", 1, true) -- plain text search
    if (type(var_tnp)~="number") then
      break
    end--if
    if ((var_tnp<=1) or (var_tnp>=in_len_min_three_max_five)) then
      break
    end--if
    dim_xx = lfstr12dig2int(string.sub(str_combo_dim,1,(var_tnp-1)))
    dim_yy = lfstr12dig2int(string.sub(str_combo_dim,(var_tnp+1),-1))
    if ((dim_xx>99) or (dim_yy>99)) then
      dim_xx = 255
      dim_yy = 255
    end--if
    break -- finally to join mark
  end--while

  return dim_xx, dim_yy

end--function hiSplitDimIntoTwoIntegers

------------------------------------------------------------------------

-- Brew one HTML table row using "tr" and "td" with index
-- letters from "a" to "h" or other.

function hiBrewTopBottom (num_field_dimension, num_outer_dimension, num_hor_size)

  local line_top_bottom = ''
  local str_nbsp_cell = ''
  local letter_index = 0

  str_nbsp_cell = iTdStyle(num_outer_dimension,false) .. '&nbsp;</td>'
  line_top_bottom = iTrStyle(num_outer_dimension,false) .. str_nbsp_cell
  while true do
    line_top_bottom = line_top_bottom .. iTdStyle(num_field_dimension,false) .. string.char(97+letter_index) .. '</td>'
    letter_index = letter_index + 1
    if (letter_index==num_hor_size) then
      break
    end--if
  end--while
  line_top_bottom = line_top_bottom .. str_nbsp_cell .. '</tr>'

  return line_top_bottom

end--function hiBrewTopBottom

------------------------------------------------------------------------

-- Convert two letters to bare filename on commons, "?" on error.

function hiTypeToCommonsBareName (str_two_letters, num_background_one_dark)

  local str_name_core_out = ''
  local str_colour_of_piece = ''

  while true do -- fake loop
    if (str_two_letters=='++') then
      str_name_core_out = 'HL'
      break
    end--if
    if (str_two_letters=='--') then
      break -- leave it empty
    end--if
    if (not const_tab_six_pieces[string.sub(str_two_letters,1,1)]) then
      str_name_core_out = '?' -- invalid
      break
    end--if
    if (not const_tab_col_piece[string.sub(str_two_letters,2,2)]) then
      str_name_core_out = '?' -- invalid
      break
    end--if
    str_name_core_out = str_two_letters
    break -- finally to join mark
  end--while -- fake loop -- join mark

  if (str_name_core_out~='?') then
    str_name_core_out = file_prefix .. str_name_core_out .. (const_tab_col_bckg[num_background_one_dark] or '!') .. file_suffix
  end--if

  return str_name_core_out

end--function hiTypeToCommonsBareName

------------------------------------------------------------------------

-- Convert filename on commons to full image syntax, optionally
-- with link below.

function hiNameToFullCell (str_name_in, str_two_lett_in, num_size_xy_px, boo_add_link_below)

  local str_full_wiki_image = ''

  str_full_wiki_image = '[[File:' .. str_name_in .. '|' .. tostring(num_size_xy_px) .. 'x' .. tostring(num_size_xy_px) .. 'px|link=|alt=' .. str_two_lett_in .. ']]'
  if (boo_add_link_below) then
    str_full_wiki_image = str_full_wiki_image .. '<br>[[:File:' .. str_name_in .. '|' .. str_two_lett_in .. ']]'
  end--if

  return str_full_wiki_image

end--function hiNameToFullCell

------------------------------------------------------------------------

---- VARIABLES [R] ----

------------------------------------------------------------------------

function exporttable.ek (arxframent)

  -- special type "args" AKA "arx"

  local arxsomons = 0 -- metaized "args" from our own or caller's "frame"

  -- general table

  local tab_input_anon = {} -- all fields, empty for empty board

  -- general str

  local str_dim     = '' -- from "dim=" NOT for "dim=all" or "dim=def"
  local str_btm     = '' -- from "btm="
  local strvisgud   = '' -- visible good output
  local strviserr   = '' -- visible error message
  local strtrakat   = '' -- invisible tracking categories
  local str_return  = ''

  -- general num

  local num_err = 0
  local size_inr_field_std_px = 26
  local size_inr_field_all_px = 60
  local size_outr_desc_px     = 20
  local size_hor = 0
  local size_ver = 0
  local anon_param_eval = 0

  -- general boo

  local boo_have_dim   = false -- NOT "true" for "dim=all" or "dim=def"
  local boo_have_btm   = false
  local boo_have_anony = false
  local boo_show_def   = false -- from "dim=def"
  local boo_show_all   = false -- from "dim=all"
  local boo_nocat      = false -- from "nocat=true"

  ---- GET THE ARX (ONE OF TWO) ----

  arxsomons = arxframent.args -- "args" from our own "frame"
  if (type(arxsomons)~="table") then
    arxsomons = {} -- guard against indexing error from our own
    num_err = 1 -- #E01 internal
  end--if
  if (arxsomons['caller']=="true") then
    arxsomons = arxframent:getParent().args -- "args" from caller's "frame"
  end--if
  if (type(arxsomons)~="table") then
    arxsomons = {} -- guard against indexing error again
    num_err = 1 -- #E01 internal
  end--if

  ---- GET NAMED PARAM:S ----

  if (num_err==0) then
    do -- scope
      local var_3_temp = 0
      var_3_temp = arxsomons["dim"]
      if (type(var_3_temp)=="string") then
        boo_show_def = (var_3_temp=="def")
        boo_show_all = (var_3_temp=="all")
        if ((not boo_show_def) and (not boo_show_all)) then
          str_dim = var_3_temp
          boo_have_dim = true
        end--if
      end--if
      var_3_temp = arxsomons["btm"]
      if (type(var_3_temp)=="string") then
        str_btm = var_3_temp
        boo_have_btm = true
      end--if
    end--do scope
  end--if

  ---- GET HIDDEN PARAM:S ----

  boo_nocat = (arxsomons['nocat']=='true')

  ---- GET AND CHECK ANONYMOUS PARAM:S ----

  if (num_err==0) then
    anon_param_eval = 0 -- result needed far below
    do -- scope
      local var_4_temp = 0
      while true do
        var_4_temp = arxsomons[anon_param_eval+1]
        if (type(var_4_temp)~="string") then
          break
        end--if
        var_4_temp = mw.text.trim (var_4_temp)
        if (string.len(var_4_temp)~=2) then
          num_err = 5 -- #E05
          break
        end--if
        anon_param_eval = anon_param_eval + 1
        tab_input_anon[anon_param_eval] = var_4_temp
        boo_have_anony = true
      end--while
    end--do scope
  end--if

  ---- SOME PREPARATIONS AND CHECKS ----

  while true do -- fake loop
    if (num_err~=0) then
      break
    end--if
    if (boo_have_dim) then
      size_hor, size_ver = hiSplitDimIntoTwoIntegers(str_dim)
      if ((size_hor<2) or (size_hor>26) or (size_ver<2) or (size_ver>26)) then
        num_err = 2 -- #E02
        break
      end--if
    else
      size_hor = 8
      size_ver = 8
    end--if
    do -- scope
      local num_len_btm = 0
      if (boo_have_btm) then
        num_len_btm = string.len(str_btm)
        if ((num_len_btm<2) or (num_len_btm>200)) then
          num_err = 3 -- #E03
          break
        end--if
      end--if
    end--do scope
    if (boo_show_def and boo_have_anony) then
      num_err = 6 -- #E06 -- "show def" excludes anon ("dim=" impossible)
      break
    end--if
    if (boo_show_all and (boo_have_anony or boo_have_btm)) then
      num_err = 7 -- #E07 -- "show all" excludes anon and "btm=" ("dim=" impossible)
      break
    end--if
    if (boo_show_all) then
      tab_input_anon = const_tab_all
      size_hor = 7 -- 2x7 -- !!! here 7 but we will get 14 columns !!!
      size_ver = 4
    end--if
    if (boo_show_def) then
      tab_input_anon = const_tab_standard
    end--if
    if ((not boo_show_all) and (not boo_show_def) and boo_have_anony and (anon_param_eval~=(size_hor*size_ver))) then
      num_err = 4 -- #E04
      break
    end--if
    break -- finally to join mark
  end--while

  ---- BREW THE BOARD ----

  if (num_err==0) then

    do -- scope

      local inner_border_and_padding = ''
      local top_and_bottom_line = ''
      local left_and_right_cell = ''
      local our_two_letters = ''
      local name_or_bad_quest = ''
      local size_inr_field_final_px = 0
      local index_xx = 0
      local index_yy = 0
      local index_zz = 0
      local index_one_dim_input_table = 0
      local num_bckg_color = 0 -- alternate between 0 and 1 also for "dim=all"

      if (boo_show_all) then
        size_inr_field_final_px = size_inr_field_all_px
        inner_border_and_padding = 'border:3px solid #909090; padding:4px;'
        strvisgud = '<table style="margin:0.3em auto 0.3em auto; border:1px solid #909090; padding:4px;"><tr><td>'
      else
        size_inr_field_final_px = size_inr_field_std_px
        inner_border_and_padding = 'border-collapse:collapse; border:1px solid #909090; padding:0;'
        strvisgud = '<table style="clear:right; float:right; margin:0.3em 0 0.3em auto; ' .. iIntegerToCssWidth(size_hor * size_inr_field_final_px + 2 * size_outr_desc_px + 10) .. ' border:1px solid #909090; padding:4px;"><tr><td>'
      end--if

      strvisgud = strvisgud .. '<table style="' .. inner_border_and_padding .. ' vertical-align:middle;">' -- inner table

      if (not boo_show_all) then
        top_and_bottom_line = hiBrewTopBottom (size_inr_field_final_px, size_outr_desc_px, size_hor)
        strvisgud = strvisgud .. top_and_bottom_line -- string is reused one time below
      end--if

      index_yy = 0
      while true do -- outer YY from "8" or other down to "1"
        if (not boo_show_all) then
          left_and_right_cell = iTdStyle(size_outr_desc_px,false) .. tostring(size_ver-index_yy) .. '</td>' -- string is reused one time below
        end--if
        strvisgud = strvisgud .. iTrStyle(size_inr_field_final_px,boo_show_all)
        if (not boo_show_all) then
          strvisgud = strvisgud .. left_and_right_cell
        end--if
        index_xx = 0
        if (not boo_show_all) then
          num_bckg_color = index_yy % 2 -- "a8" is light
        end--if
        while true do -- inner XX from "a" up to "h" or other
          index_one_dim_input_table = index_one_dim_input_table + 1
          our_two_letters = tab_input_anon[index_one_dim_input_table] or '--'
          index_zz = 0
          while true do -- deep -- for "dim=all" generate 2 fields for 1 pick
            name_or_bad_quest = hiTypeToCommonsBareName(our_two_letters,num_bckg_color)
            if (name_or_bad_quest=="?") then
              num_err = 5 -- #E05 -- no break
            end--if
            strvisgud = strvisgud .. iTdStyle(size_inr_field_final_px,boo_show_all) .. hiNameToFullCell(name_or_bad_quest,our_two_letters,size_inr_field_final_px,boo_show_all) .. '</td>'
            num_bckg_color = 1 - num_bckg_color -- alternate
            index_zz = index_zz + 1
            if ((index_zz==2) or (not boo_show_all)) then
              break
            end--if
          end--while -- deep
          index_xx = index_xx + 1
          if (index_xx==size_hor) then
            break
          end--if
        end--while -- inner XX
        if (not boo_show_all) then
          strvisgud = strvisgud .. left_and_right_cell
        end--if
        strvisgud = strvisgud .. '</tr>'
        index_yy = index_yy + 1
        if (index_yy==size_ver) then
          break
        end--if
      end--while -- outer YY

      if (not boo_show_all) then
        strvisgud = strvisgud .. top_and_bottom_line
      end--if

      strvisgud = strvisgud .. '</table>' -- inner table

      if (boo_have_btm) then
        strvisgud = strvisgud .. '</td></tr><tr><td><table><tr><td style="text-align:left;">' .. str_btm .. '</td></tr></table>'
      end--if

      strvisgud = strvisgud .. '</td></tr></table>' -- outer table

    end--do scope

  end--if

  ---- WHINE IF YOU MUST #E02...#E99 ----

  if (num_err~=0) then
    strviserr = 'ERROR'
    if (num_err==2) then
      strviserr = 'parameter "dim=" is bad'
    end--if
    if (num_err==3) then
      strviserr = 'parameter "btm=" is bad'
    end--if
    if (num_err==4) then
      strviserr = 'wrong number of anon parameters (found ' .. tostring (anon_param_eval) .. ')'
    end--if
    if (num_err==5) then
      strviserr = 'wrong content of anon parameter'
    end--if
    if (num_err==6) then
      strviserr = '"dim=def" used together with anon parameter'
    end--if
    if (num_err==7) then
      strviserr = '"dim=all" used together with other parameter'
    end--if
    strviserr = '<b>!!! ' .. strviserr .. ' in "Template:chess" !!!</b>' -- !!!FIXME!!!
    if (not boo_nocat) then
      strtrakat = const_tracking -- !!!FIXME!!!
    end--if
  end--if

  ---- RETURN THE JUNK STRING ----

  if (num_err==0) then
    str_return = strvisgud
  else
    str_return = strviserr .. strtrakat
  end--if
  return str_return

end--function

  ---- RETURN THE JUNK OUTER TABLE ----

return exporttable