* chesspos
* Version 1.1
* 06/12/2022
* Dominik Flügel

program define chesspos
version 14

syntax anything [, Black name(passthru) saving(passthru) nodraw]


* Check Input
capture assert strlen("`1'") >= 15
if c(rc) != 0 {
	dis in red "This does not seem to be a valid chess position in FEN notation."
	exit 198
}

local n_slash = strlen("`1'") - strlen(subinstr("`1'", "/", "", .))
capture assert `n_slash' == 7
if c(rc) != 0 {
	dis in red "This does not seem to be a valid chess position in FEN notation."
	exit 198
}


* Maintain Scheme and Font
local scheme "`c(scheme)'"
set scheme s1mono


* Check value labels don't already exist
qui label dir
foreach label in lb_file lb_rank_black {
	if strpos("`r(names)'", "`label'") {
		dis as text "You have already defined one or more of the following three value labels:"
		dis as input "lb_file"
		dis as input "lb_rank_black"
		dis as text "Stata does not allow temporary value labels," ///
					"please rename any of the three so that chesspos can use them."
		exit
	}
}


* Initiate temporary variables
tempvar touse file rank piece rank_white btm_file btm_rank btm_rank_white


* Generate and mark new observations
qui des, short
local n = r(N)
local new_start = r(N) + 1
local new_end	= r(N) + 64
qui set obs `new_end'

qui mark `touse' in `new_start'/`new_end'


* distribute ranks (1-8) and files (A-H)
qui gen `file' = .
qui label def lb_file 1 "A" -1 "A" 2 "B" -2 "B" 3 "C" -3 "C" 4 "D" -4 "D" ///
					  5 "E" -5 "E" 6 "F" -6 "F" 7 "G" -7 "G" 8 "H" -8 "H", modify
qui label val `file' lb_file
qui gen `rank' = .

local i = 0
forval x = 1/8 {
	forval y = 1/8 {
		local ++i
		qui replace `file' = `x' if _n == `n' + `i'
		qui replace `rank' = `y' if _n == `n' + `i'
	}
}


* parse position
local pos `1'
forval i = 1/8 {
	local x = substr("xxxxxxxx", 1, `i')
	local pos = ustrregexra("`pos'", "`i'", "`x'") // replace the number i (marking number of empty fields) in the string pos with a string of "x"s with the length of i
	*dis as text "`pos'"
}
local pos = ustrregexra("`pos'", "/", " ") // replace "/" with " "


* insert pieces in FEN notation
qui gen `piece' = ""
local y = 8 // FEN positions start with 8th rank
foreach col in `pos' {
	forval x = 1/8 {
		qui replace `piece' = substr("`col'", `x' , 1) if `rank' == `y' & `file' == `x'
	}
	local --y
}


* replace pieces with font letter
qui replace `piece' = "" if `piece' == "x"					 // empty field

qui replace `piece' = `"{fontface "Arial Unicode MS":♔}"' if `piece' == "K" // white king
qui replace `piece' = `"{fontface "Arial Unicode MS":♕}"' if `piece' == "Q" // white queen
qui replace `piece' = `"{fontface "Arial Unicode MS":♖}"' if `piece' == "R" // white rook
qui replace `piece' = `"{fontface "Arial Unicode MS":♘}"' if `piece' == "N" // white knight
qui replace `piece' = `"{fontface "Arial Unicode MS":♗}"' if `piece' == "B" // white bishop
qui replace `piece' = `"{fontface "Arial Unicode MS":♙}"' if `piece' == "P" // white pawn

qui replace `piece' = `"{fontface "Arial Unicode MS":♚}"' if `piece' == "k" // black king
qui replace `piece' = `"{fontface "Arial Unicode MS":♛}"' if `piece' == "q" // black queen
qui replace `piece' = `"{fontface "Arial Unicode MS":♜}"' if `piece' == "r" // black rook
qui replace `piece' = `"{fontface "Arial Unicode MS":♞}"' if `piece' == "n" // black knight
qui replace `piece' = `"{fontface "Arial Unicode MS":♝}"' if `piece' == "b" // black bishop
qui replace `piece' = `"{fontface "Arial Unicode MS":♟}"' if `piece' == "p" // black pawn


* create second variable for white fields (missings)
qui gen `rank_white' = `rank'

foreach y in 2 4 6 8 {
	qui replace `rank_white' = . if inlist(`file', 1, 3, 5, 7) & `rank' == `y'
}

foreach y in 1 3 5 7 {
	qui replace `rank_white' = . if inlist(`file', 2, 4, 6, 8) & `rank' == `y'
}


* Mirror board; (if black is to move, "btm")
qui gen `btm_rank' = `rank' * (-1)
qui gen `btm_rank_white' = `rank_white' * (-1)
label def lb_rank_black -1 "1" -2 "2" -3 "3" -4 "4" -5 "5" -6 "6" -7 "7" -8 "8"
label val `btm_rank' `btm_rank_white' lb_rank_black

qui gen `btm_file' = `file' * (-1)
label val `btm_file' lb_file


* Output (white to move)
capture noisily {
	if "`black'" == "" {
		scatter `rank_white' `file',						 						///
			msymbol(S) mcolor(black*.4) msize(*6.4) 								///
			xtitle("") ytitle("") aspectratio(1) ||									///
		scatter `rank' `file', msymbol(none) 										///
			mlabel(`piece') mlabpos(12) mlabsize(*3) mlabgap(*-5)					///
			xlabel(1(1)8, noticks nogrid val)		xscale(range(0.5 8.5))			///
			ylabel(1(1)8, noticks nogrid angle(0)) 	yscale(range(0.5 8.5))			///
			yline(0.5 8.5, lpattern(solid) lcolor(black))							///
			xline(0.5 8.5, lpattern(solid) lcolor(black))							///
			legend(off) `name' `draw' `saving'
	}
	* Output (black to move)
	else {
		scatter `btm_rank_white' `btm_file',				 						///
			msymbol(S) mcolor(black*.4) msize(*6.4) 								///
			xtitle("") ytitle("") aspectratio(1) ||									///
		scatter `btm_rank' `btm_file', msymbol(none)								///
			mlabel(`piece') mlabpos(12) mlabsize(*3) mlabgap(*-5)					///
			xlabel(-1(1)-8, noticks nogrid val) 		 xscale(range(-0.5 -8.5))	///
			ylabel(-1(1)-8, noticks nogrid val angle(0)) yscale(range(-0.5 -8.5))	///
			yline(-0.5 -8.5, lpattern(solid) lcolor(black))							///
			xline(-0.5 -8.5, lpattern(solid) lcolor(black))							///
			legend(off) `name' `draw' `saving'
	}
}


* Reset Scheme and delete observations
set scheme `scheme'
qui drop if `touse'
label drop lb_file lb_rank_black

end