jvozba/lujvo.go

278 lines
5.3 KiB
Go

package jvozba
import (
"strings"
)
func Score(lujvo string) int {
L := len(lujvo)
// apostrophe count, hyphen count, rafsi score count, vowel count
A := 0
H := 0
R := 0
V := 0
var curr string
for len(lujvo) > 0 {
curr, lujvo = katna(lujvo)
tai := rafsiTarmi(curr)
R += int(tai)
switch tai {
case Hyphen:
H++
case CVhV:
V += 2
A++
case CVCCV, CCVCV, CVV:
V += 2
default:
V += 1
}
}
return 1000*L - 500*A + 100*H - 10*R - V
}
// the tarmi enum directly corresponds to the scores for R
type tarmi int
const (
Hyphen tarmi = iota
CVCCV
CVCC
CCVCV
CCVC
CVC
CVhV
CCV
CVV
)
func rafsiTarmi(rafsi string) tarmi {
l := len(rafsi)
switch l {
case 1:
return Hyphen
case 5:
if isVowel(rafsi[2]) {
return CCVCV
} else {
return CVCCV
}
case 4:
if rafsi[2] == '\'' {
return CVhV
} else if !isVowel(rafsi[3]) {
if isVowel(rafsi[2]) {
return CCVC
} else {
return CVCC
}
} else {
return Hyphen
}
// case 3:
default:
if !isVowel(rafsi[2]) {
return CVC
} else {
if isVowel(rafsi[1]) {
return CVV
} else {
return CCV
}
}
}
}
type zunsna int
const (
Unvoiced zunsna = iota
Voiced
Liquid
)
func zunsnaType(one byte) zunsna {
switch one {
case 'b', 'd', 'g', 'v', 'j', 'z':
return Voiced
case 'p', 't', 'k', 'f', 'c', 's', 'x':
return Unvoiced
default:
return Liquid
}
}
func isCjsz(one byte) bool {
switch one {
case 'c', 'j', 's', 'z':
return true
default:
return false
}
}
// `needsY` checks if an y-hyphen needs to be inserted
func needsY(previous byte, current string) bool {
if isVowel(previous) {
return false
}
head := current[0]
prevType := zunsnaType(previous)
headType := zunsnaType(head)
if (prevType == Voiced && headType == Unvoiced) || (headType == Voiced && prevType == Unvoiced) || previous == head || (isCjsz(previous) && isCjsz(head)) {
return true
}
comb := string([]byte{previous, head})
switch comb {
case "cx", "kx", "xc", "xk", "mz":
return true
}
switch string(previous) + current {
case "ndj", "ndz", "ntc", "nts":
return true
}
return false
}
func isValidInitial(twoBytes ...byte) bool {
return len(twoBytes) == 2 &&
strings.Contains("bl~br~cf~ck~cl~cm~cn~cp~cr~ct~dj~dr~dz~fl~fr~gl~gr~jb~jd~jg~jm~jv~kl~kr~ml~mr~pl~pr~sf~sk~sl~sm~sn~sp~sr~st~tc~tr~ts~vl~vr~xl~xr~zb~zd~zg~zm~zv", string(twoBytes))
}
// Whether `lujvo` could lead to tosmabru on appending -y or is one already.
func isTosmabruInitial(lujvo string) bool {
var r string
var lastChar byte
i := 0
for lujvo != "" {
r, lujvo = katna(lujvo)
switch t := rafsiTarmi(r); t {
case CVC:
if i > 0 && !isValidInitial(lastChar, r[0]) {
return false
}
lastChar = r[2]
case CVCCV:
return i > 0 &&
isValidInitial(lastChar, r[0]) &&
isValidInitial(r[2], r[3])
case Hyphen:
return i > 1 && r == "y"
default:
return false
}
i++
}
return true
}
// Mind the lack of y.
func isVowel(one byte) bool {
switch one {
case 'a', 'e', 'i', 'o', 'u':
return true
default:
return false
}
}
// Make one cut. Atrocious code.
func katna(lujvo string) (string, string) {
var point /* of cission */ int
l := len(lujvo)
switch {
case l >= 4 && (lujvo[0] == 'n' || lujvo[0] == 'r' || lujvo[0] == 'y') && !isVowel(lujvo[1]):
point = 1
case l >= 8 && lujvo[4] == 'y':
point = 4
case l >= 7 && lujvo[3] == 'y':
point = 3
case l >= 7 && lujvo[2] == '\'' && isVowel(lujvo[3]):
point = 4
case l >= 6:
point = 3
default:
point = l
}
return lujvo[:point], lujvo[point:]
}
type scored struct {
lujvo string
score int
tosmabru bool
}
// -y-: L += 1 && H += 1 -> score += 1100
const yPenalty = 1100
func Lujvo(selci [][]string) (string, error) {
candidates := []scored{{"", 0, false}}
for selciN, cnino := range selci {
isLast := selciN == len(selci)-1
newCand := []scored{}
for _, rafsi := range cnino {
var best *scored
var bestTosmabru *scored
for _, laldo := range candidates {
l := len(laldo.lujvo)
hyphen := ""
if l > 0 && needsY(laldo.lujvo[l-1], rafsi[:2]) {
hyphen = "y"
} else if selciN == 1 {
tai := rafsiTarmi(laldo.lujvo)
if (tai == CVV || tai == CVhV) && !(isLast && rafsiTarmi(rafsi) == CCV) {
if rafsi[0] == 'r' {
hyphen = "n"
} else {
hyphen = "r"
}
}
}
if !isLast && (rafsiTarmi(rafsi) == CVCC || rafsiTarmi(rafsi) == CCVC) {
rafsi += "y"
}
newPart := hyphen + rafsi
newLujvo := laldo.lujvo + newPart
newScore := laldo.score + Score(newPart)
tosmabru := isTosmabruInitial(newLujvo)
if laldo.tosmabru {
newScore -= yPenalty
}
if tosmabru {
newScore += yPenalty
}
newScored := scored{newLujvo, newScore, tosmabru}
// DRY
if tosmabru {
if bestTosmabru == nil || bestTosmabru.score > newScore {
bestTosmabru = &newScored
}
} else {
if best == nil || best.score > newScore {
best = &newScored
}
}
}
if best != nil {
newCand = append(newCand, *best)
}
if bestTosmabru != nil {
newCand = append(newCand, *bestTosmabru)
}
}
candidates = newCand
}
bestOption := candidates[0]
for _, o := range candidates {
if bestOption.score > o.score {
bestOption = o
}
}
result := bestOption.lujvo
if bestOption.tosmabru {
result = result[:3] + "y" + result[3:]
}
return result, nil
}