eliza.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. // Author: Matthew Shiel
  2. // Code adapted from https://github.com/kennysong/goeliza/
  3. package eliza
  4. import (
  5. "fmt"
  6. "math/rand"
  7. "regexp"
  8. "strings"
  9. "time"
  10. )
  11. type InteractiveBot interface {
  12. ReplyTo(statement string) string
  13. }
  14. type BotPersonality struct {
  15. Personality *Personality
  16. }
  17. func NewBotPersonality(personality *Personality) *BotPersonality {
  18. return &BotPersonality{personality}
  19. }
  20. // Greetings will return a random introductory sentence for ELIZA.
  21. func (p *BotPersonality) Greetings() string {
  22. return p.randChoice(p.Personality.Introductions)
  23. }
  24. // GoodbyeResponse will return a random goodbye sentence for ELIZA.
  25. func (p *BotPersonality) GoodbyeResponse() string {
  26. return p.randChoice(p.Personality.Goodbyes)
  27. }
  28. // ReplyTo will construct a reply for a given statement using ELIZA's rules.
  29. func (p *BotPersonality) ReplyTo(statement string) string {
  30. // First, preprocess the statement for more effective matching
  31. statement = p.preprocess(statement)
  32. // Then, we check if this is a quit statement
  33. if p.IsQuitStatement(statement) {
  34. return p.GoodbyeResponse()
  35. }
  36. // Next, we try to match the statement to a statement that ELIZA can
  37. // recognize, and construct a pre-determined, appropriate response.
  38. for pattern, responses := range p.Personality.Psychobabble {
  39. re := regexp.MustCompile(pattern)
  40. matches := re.FindStringSubmatch(statement)
  41. // If the statement matched any recognizable statements.
  42. if len(matches) > 0 {
  43. // If we matched a regex group in parentheses, get the first match.
  44. // The matched regex group will match a "fragment" that will form
  45. // part of the response, for added realism.
  46. var fragment string
  47. if len(matches) > 1 {
  48. fragment = p.reflect(matches[1])
  49. }
  50. // Choose a random appropriate response, and format it with the
  51. // fragment, if needed.
  52. response := p.randChoice(responses)
  53. if strings.Contains(response, "%s") {
  54. response = fmt.Sprintf(response, fragment)
  55. }
  56. fmt.Printf("For Statement \"%s\" got a hit with pattern \"%s\" Responded With \"%s\"\n", statement, pattern, response)
  57. return response
  58. }
  59. }
  60. // If no patterns were matched, return a default response.
  61. return p.randChoice(p.Personality.DefaultResponses)
  62. }
  63. // IsQuitStatement returns if the statement is a quit statement
  64. func (p *BotPersonality) IsQuitStatement(statement string) bool {
  65. statement = p.preprocess(statement)
  66. for _, quitResponse := range p.Personality.QuitResponses {
  67. if statement == quitResponse {
  68. return true
  69. }
  70. }
  71. return false
  72. }
  73. // preprocess will do some normalization on a statement for better regex matching
  74. func (p *BotPersonality) preprocess(statement string) string {
  75. statement = strings.TrimRight(statement, "\n.!")
  76. statement = strings.ToLower(statement)
  77. return statement
  78. }
  79. // reflect flips a few words in an input fragment (such as "I" -> "you").
  80. func (p *BotPersonality) reflect(fragment string) string {
  81. words := strings.Split(fragment, " ")
  82. for i, word := range words {
  83. if reflectedWord, ok := p.Personality.ReflectedWords[word]; ok {
  84. words[i] = reflectedWord
  85. }
  86. }
  87. return strings.Join(words, " ")
  88. }
  89. // randChoice returns a random element in an (string) array.
  90. func (p *BotPersonality) randChoice(list []string) string {
  91. // Added for truly random generation of numbers with seeds
  92. if len(list) == 0 {
  93. return ""
  94. }
  95. rand.Seed(time.Now().UnixNano())
  96. randIndex := rand.Intn(len(list))
  97. return list[randIndex]
  98. }