implementation module board

import	StdBool, StdOrdList, StdTuple
import	language, types

/***************************************************************************************************************
	The dimensions of the board have range of 0..14 to 0..14.
****************************************************************************************************************/

::	Board	:==	(![[Char]],![[Char]])

initboard :: Board
initboard
	= (emptyboard,emptyboard)
where
	emptyboard = repeatn 15 (repeatn 15 ' ')

getplacedletters :: !Board -> [Char]
getplacedletters (h,_)
	= sort (removeDup (flatten h))


/***************************************************************************************************************
	The bonus fields on the scrabble board.
****************************************************************************************************************/

doubleletterpositions :: [Position]
doubleletterpositions
	=: [(0,3),(0,11),(2,6),(2,8),(3,7),(3,14),(6,2),(6,6),(6,8),(6,12),(7,3),(7,11),(8,2),(8,6),(8,8),(8,12),(11,7),(11,14),(12,6),(12,8),(14,3),(14,11)]

tripleletterpositions :: [Position]
tripleletterpositions
	=: [ (1,5),(1,9),(5,1),(5,5),(5,9),(5,13),(9,1),(9,5),(9,9),(9,13),(13,5),(13,9) ]

doublewordpositions :: [Position]
doublewordpositions
	=: [ (7,7),(1,1),(2,2),(3,3),(4,4),(1,13),(2,12),(3,11),(4,10),(10,4),(10,10),(11,3),(11,11),(12,12),(12,2),(13,13),(13,1) ]

triplewordpositions :: [Position]
triplewordpositions
	=: [ (0,0),(0,7),(0,14),(7,0),(7,14),(14,0),(14,7),(14,14) ]

lettervalueat :: !Char !Position -> Int
lettervalueat l pos
	| isMember pos doubleletterpositions	= lw*2
	| isMember pos tripleletterpositions	= lw*3
	| otherwise								= lw
where
	lw = lettervalue l

wordvalueat :: !Position -> Int
wordvalueat pos
	| isMember pos doublewordpositions	= 2
	| isMember pos triplewordpositions	= 3
	| otherwise							= 1

grab :: ![Char] !Int [Int] -> (![Char],![Char],[Int])
grab lb 0 rs
	= (lb,[],rs)
grab [] n rs
	= ([],[],rs)
grab lb n rs
	= (restbox,[l:rest],rs2)
where
	(r,rs1)				= random (length lb) rs
	l					= lb!!r
	(restbox,rest,rs2)	= grab (removeFirst l lb) (n-1) rs1
	
	random :: !Int ![Int] -> (!Int,![Int])
	random i [r:rs] = (r rem i,rs)
	
	removeFirst :: !x ![x] -> [x]	| Eq x
	removeFirst x [y:ys]
		| x==y		= ys
		| otherwise	= [y:removeFirst x ys]
	removeFirst _ _
		= []

getfreehorpositions :: !Board !Char -> [Position]
getfreehorpositions (h,_) l
	= flatten (map (freepositions True l 0) (zip2 [0..] h))

getfreeverpositions :: !Board !Char -> [Position]
getfreeverpositions (_,v) l
	= map swap (flatten (map (freepositions True l 0) (zip2 [0..] v)))

freepositions :: !Bool !Char !Int !(!Int,![Char]) -> [Position]
freepositions _ _ _ (_,[])
	= []
freepositions True k x (y,[l:ls])
	| l==k		= [(x,y) : freepositions False k (x+1) (y,ls)]
	| l==' '	= freepositions True  k (x+1) (y,ls)
	| otherwise	= freepositions False k (x+1) (y,ls)
freepositions False k x (y,[l:ls])
	| l==' '	= freepositions True  k (x+1) (y,ls)
	| otherwise	= freepositions False k (x+1) (y,ls)


/***************************************************************************************************************
	seekfreepositions is used by seekfree(hor/ver)positions. It determines the positions on a given board that 
	are valid starting positions for a word starting with a particular letter.
	
	The function is useful when atleast one letter on the board has to be used because a (some) letter(s) is 
	(are) missing on the letter bar.
****************************************************************************************************************/
seekfreehorpositions :: !Board !Char !Int -> [Position]
seekfreehorpositions (h,v) k p
	= flatten (map (seekfreepositions 0 p k 0) (zip2 [0..] h))

swap :: !(.a,.b) -> (.b,.a)
swap (a,b) = (b,a)

seekfreeverpositions :: !Board !Char !Int -> [Position]
seekfreeverpositions (h,v) k p
	= map swap (flatten (map (seekfreepositions 0 p k 0) (zip2 [0..] v) ))

seekfreepositions :: !Int !Int !Char !Int !(!Int,![Char]) -> [Position]
seekfreepositions a p k x (y,[l:ls])
	| a>=p && k==l		= [(x-p,y) : seekfreepositions 0 p k (x+1) (y,ls)]
	| l==' '			= seekfreepositions (a+1) p k (x+1) (y,ls)
	| otherwise			= seekfreepositions 0 p k (x+1) (y,ls)
seekfreepositions _ _ _ _ _
	= []


/***************************************************************************************************************
	tryaddword board word position direction adds word at position in direction to board. 
	The Board	result is the new board.
	The Boolean	result reports whether the word could be placed.
	The [Char]	result are the letters that have been used.
	The Int		result is the score by placing this word.
	The [Word]	result are the possibly new formed words.
	
	After tryaddword it should be verified if the new formed words are legal. 
	After tryaddword it also should be verified if a bonus should be added to the score in case all letters 
	have been used.
****************************************************************************************************************/
tryaddword :: !Board !Word !Position !Direction -> (!Board,!Bool,[Char],Int,[Word])
tryaddword board=:(h,v) w (x,y) Horizontal
	| w==""				= (board,True, [],0,[])
	| size w+x>15		= (board,False,[],0,[])
	| otherwise			= ((nh,nv),possible,newletters,score,newwords)
where
	(nh,possible,scorenewletters,worddoubling)
						= tryaddtolines h w (x,y) y
	(nv,vscore,newwords)= tryaddtransversetolines v (0,y) scorenewletters
	newletters			= map fst3 scorenewletters
	scoreoldletters		= oldletterscore w newletters
	hscore				= worddoubling*(sum (map thd3 scorenewletters) + scoreoldletters)
	score
		| not possible	= 0
		| otherwise		= hscore+vscore
tryaddword board=:(h,v) w (x,y) Vertical
	| w==""				= (board,True, [],0,[])
	| size w+y>15		= (board,False,[],0,[])
	| otherwise			= ((nh,nv),possible,newletters,vscore+hscore,newwords)
where
	(nv,possible,scorenewletters,worddoubling)
						= tryaddtolines v w (y,x) x
	(nh,hscore,newwords)= tryaddtransversetolines h (0,x) scorenewletters
	newletters			= map fst3 scorenewletters
	scoreoldletters		= oldletterscore w newletters
	vscore				= worddoubling*(sum (map thd3 scorenewletters) + scoreoldletters)

oldletterscore :: !Word ![Char] -> Int		// Sum the lettervalue of the chars in word that are not member of letters
oldletterscore word letters
	= sum (map lettervalue (removeMembers [c\\c<-:word] letters))

tryaddtolines :: ![[Char]] Word !Position !Int -> (![[Char]],!Bool,![(Char,Int,Int)],Int)
tryaddtolines [r:rs] w p=:(x,_) 0
	# (r,possible,scorenewletters,worddoubling) = tryaddtoline r w p x
	= ([r:rs],possible,scorenewletters,worddoubling)
where
	tryaddtoline :: ![Char] !Word !Position !Int -> (![Char],!Bool,![(Char,Int,Int)],Int)
	tryaddtoline rs word p 0
		| nrchars==1
		= addtoline
		with
			addtoline
				|	l==' ' && (ls==[] || hd ls==' ')= ([w:ls],True, [lwrd],wwrd)
				|	l==w   && (ls==[] || hd ls==' ')= ([w:ls],True, [],1)
				|	otherwise						= ([l:ls],False,[],1)
		| nrchars>=1
		= addtoline
		with
			(nr,possible,lwrds,wwrds)				= tryaddtoline ls (word%(1,nrchars-1)) (x+1,y) 0
			addtoline
				| l==' '							= ([w:nr],possible,[lwrd:lwrds],wwrd*wwrds)
				| l==w								= ([w:nr],possible,lwrds,wwrds)
				| otherwise							= ([l:ls],False,[],1)
	where
		nrchars		= size word
		(l,ls)		= hdtl rs
		w			= word.[0]
		(x,y)		= p
		wvalue		= lettervalueat w p
		lwrd		= (w,x,wvalue)
		wwrd		= wordvalueat p
	tryaddtoline rs word p 1
		| l<>' '					= (rs,False,[],1)
		# (ls,possible,lwrds,wwrds)	= tryaddtoline ls word p 0
		| otherwise					= ([l:ls],possible,lwrds,wwrds)
	where
		(l,ls)						= hdtl rs
	tryaddtoline [l:ls] word p n
		# (ls,possible,lwrds,wwrds)	= tryaddtoline ls word p (n-1)
		= ([l:ls],possible,lwrds,wwrds)
	
	hdtl :: !v:[u:x] -> v:(u:x,v:[u:x]), [v<=u]
	hdtl [x:xs] = (x,xs)
tryaddtolines [r:rs] w p j
	# (rs,possible,scorenewletters,worddoubling) = tryaddtolines rs w p (j-1)
	= ([r:rs],possible,scorenewletters,worddoubling)
tryaddtolines _ _ _ _
	= ([],False,[],0)

tryaddtransversetolines :: ![[Char]] !Position ![(Char,Int,Int)] -> (![[Char]],Int,![Word])
tryaddtransversetolines rs _ []
	= (rs,0,[])
tryaddtransversetolines [r:rs] p=:(x,y) [(nl,nx,ls)]
	| x==nx
	= ([nr:rs],score,newword)
	with
		(nr,score,newword)	= addtoline r nl p y ""
	| otherwise	= ([r:nrs],score,newword)
	with
		(nrs,score,newword)	= tryaddtransversetolines rs (x+1,y) [(nl,nx,ls)]
tryaddtransversetolines [r:rs] p=:(x,y) [(nl,nx,ls):lwrds]
	| x==nx
	= ([nr:nrs],score+restscore,newword++newwords)
	with
		(nrs,restscore,newwords)= tryaddtransversetolines rs (x+1,y) lwrds
		(nr,score,newword)		= addtoline r nl p y ""
	| otherwise
	= ([r:nrs],restscore,newwords)
	with
		(nrs,restscore,newwords)= tryaddtransversetolines rs (x+1,y) [(nl,nx,ls):lwrds]
tryaddtransversetolines [r:rs] (x,y) lwrds
	= ([r:nrs],score,newwords)
where
	(nrs,score,newwords) = tryaddtransversetolines rs (x-1,y) lwrds

addtoline :: ![Char] !Char Position !Int !Word -> (![Char],Int,![Word])
addtoline [l:ls] w p 0 initword
	| initwordscore+restwordscore<>0
	= ([w:ls],(initwordscore+lscore+restwordscore)*wscore,[word])
	| otherwise
	= ([w:ls],0,[])
where
	lscore				= lettervalueat w p
	wscore				= wordvalueat p
	restword			= takeWhile ((<>) ' ') ls
	initwordscore		= sum [lettervalue c \\ c<-:initword]
	restwordscore		= sum (map lettervalue restword)
	word				= initword +++ toString w +++ toString restword
addtoline [l:ls] w p ry initword
	# initword			= if (l<>' ') (initword+++toString l) ""
	  (ls,score,word)	= addtoline ls w p (ry-1) initword
	= ([l:ls],score,word)


/***************************************************************************************************************
	newmaximumplacings _ lexicon letterbar _ (Letter l _) _ _ determines all valid words from lexicon that 
	start with l and are member of letterbar.
****************************************************************************************************************/
newmaximumplacings :: !Board Tree [Char] !(!Int,!Int,!Int,!Int) !Progress !Strength Bool -> Placing
newmaximumplacings board wordlist computerletters (minx,maxx,miny,maxy) progress=:(Letter l placing) strength firstturn
	= scoremax strength [placing:newfoundplacings1++newfoundplacings2]
where
	startwith				= membersStartingWith l wordlist
	uniquecomputerletters	= removeDup computerletters
	poshor					= getfreehorpositions board l
	posver					= getfreeverpositions board l
	newfoundplacings1
		= [ {word=nw,pos=p,dir=r,/*endscore*/score=if (length gl==7) (s+50) s}
								\\	nw	<- startwith
								,	let (firstmissingletter,position) = difference nw uniquecomputerletters 0
								,	r <- [Horizontal,Vertical]
								,	p <- if (position<>7) (if (r==Horizontal)
															(seekfreehorpositions board firstmissingletter position)
															(seekfreeverpositions board firstmissingletter position)
														  )
														  (if (r==Horizontal)
															[(i,j) \\ i <- [max 0 (minx-size nw) .. min (14-size nw) maxx]
																	, j <- [max 0 (miny-1)       .. min 14 (maxy+1)]]
															[(i,j) \\ i <- [max 0 (minx-1)       .. min 14 (maxx+1)]
																	, j <- [max 0 (miny-size nw) .. min (14-size nw) maxy]]
														  )
								,	let (_,m,gl,s,nws) = tryaddword board nw p r
								|	ok_solution m gl nws nw
		  ]
	ok_solution m gl nws nw
		= m															&& 
		  (not (isEmpty gl))										&&
		  isEmpty (removeMembers gl computerletters)				&&
		  ((not (isEmpty nws)) || length gl<>size nw || firstturn)	&&
		  allexist wordlist nws
	newfoundplacings2
		= addatpositions board wordlist computerletters (poshor,posver) progress firstturn
	
//	difference word letterbar determines which letter and its position in word that is not a member of letterbar. 
	difference :: !Word ![Char] !Int -> (!Char,!Int)
	difference word letters p
		| word==""
		= ('a',7)
		# l					= word.[0]
		  word				= word%(1,size word-1)
		| isMember l letters
		= difference word letters (p+1)
		| otherwise
		= (l,p)


/***************************************************************************************************************
	newmaximumplacing board lexicon letterbar (hor,ver) (Letter l _) _ _ determines all valid words from lexicon 
	that start with l and are not member of letterbar. The positions hor++ver are assumed to be valid free 
	positions on board starting with l.
****************************************************************************************************************/
newmaximumplacing :: !Board Tree [Char] ([Position],[Position]) !Progress !Strength Bool -> Placing
newmaximumplacing board wordlist computerletters poshv progress=:(Letter l placing) strength firstturn
	= scoremax strength [placing:addatpositions board wordlist computerletters poshv progress firstturn]
newmaximumplacing _ _ _ _ (Finish ready) _ _
	= ready

addatpositions :: !Board Tree [Char] !([Position],[Position]) !Progress Bool -> [Placing]
addatpositions board wordlist computerletters (poshor,posver) (Letter l _) firstturn
	= [ {word=nw,pos=p,dir=r,/*endscore*/score=if (length gl==7) (s+50) s}
							\\	nw<- startwith
							|	wordcontainsletters nw uniquecomputerletters
							,	r <- [Horizontal,Vertical]
							,	p <- if (r==Horizontal) poshor posver
							,	let (_,m,gl,s,nws) = tryaddword board nw p r
							|	ok_solution m gl nws nw
	]
where
	ok_solution m gl nws nw
		= m															&&
		  not (isEmpty gl)											&&
		  isEmpty (removeMembers gl computerletters)				&&
		  (not (isEmpty nws) || length gl<>size nw || firstturn)	&&
		  allexist wordlist nws
	startwith				= membersStartingWith l wordlist
	uniquecomputerletters	= removeDup computerletters
	
	wordcontainsletters :: !Word [Char] -> Bool
	wordcontainsletters word letters
		| word==""					= False
		| isMember word.[0] letters	= True
		| otherwise					= wordcontainsletters (word%(1,size word-1)) letters


/***************************************************************************************************************
	scoremax selects a Placing depending on the Strength of the player.
****************************************************************************************************************/
scoremax :: !Strength ![Placing] -> Placing
scoremax First ps
	# ps	= dropWhile ((==) initplacing) ps
	| isEmpty ps
	= initplacing
	| otherwise
	= hd ps
scoremax Maximum ps
	= getmaxscore ps
where
	getmaxscore :: ![Placing] -> Placing
	getmaxscore [p]			= p
	getmaxscore [p1:ps]
		| p1.score>p2.score
		= p1
		| otherwise
		= p2
	where
		p2	= getmaxscore ps
	getmaxscore []
		= initplacing
scoremax (Strength percent) ps
	= scoremax Maximum (take nr ps)
where
	nr	= toInt (toReal (length ps)*percent) + 1


/***************************************************************************************************************
	allexists is true only if each of the words in the [Word] argument can be found in the Tree argument.
****************************************************************************************************************/
allexist :: Tree ![Word] -> Bool
allexist wordlist words
	= and (map (\word->isMemberDictionary word wordlist) words)
