HAL8212.pl 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/usr/bin/perl
  2. # Author: Daniel "Trizen" Șuteu
  3. # License: GPLv3
  4. # Date: 14 April 2014
  5. # Website: https://github.com/trizen
  6. # A basic A.I. concept, inspired by HAL9000.
  7. use utf8;
  8. use 5.014;
  9. use autodie;
  10. use warnings;
  11. no if $] >= 5.018, warnings => "experimental::smartmatch";
  12. # For saving the memory
  13. use Data::Dump qw(pp);
  14. # For contracting the words ("I am" into "I'm")
  15. use Lingua::EN::Contraction qw(contraction);
  16. # Stemming of words
  17. use Lingua::Stem qw(stem);
  18. # For correcting common mistakes
  19. use Lingua::EN::CommonMistakes qw(%MISTAKES_COMMON);
  20. use Lingua::EN::CommonMistakes qw(:no-defaults :american %MISTAKES_GB_TO_US);
  21. # UTF-8 ready
  22. use open IO => ':utf8';
  23. # Constants
  24. use constant {
  25. NAME => 'HAL8212',
  26. MEMORY_FILE => 'HAL8212.memory',
  27. };
  28. # For getting STDIN
  29. require Term::ReadLine;
  30. my $term = Term::ReadLine->new(NAME);
  31. # For tagging words
  32. require Lingua::EN::Tagger;
  33. my $ltag = Lingua::EN::Tagger->new;
  34. # For /dev/null
  35. use File::Spec qw();
  36. # Save memory
  37. sub save_mem {
  38. my ($memory) = @_;
  39. open my $fh, '>', MEMORY_FILE;
  40. print {$fh} <<"HEADER", "scalar ", pp($memory), "\n";
  41. #!/usr/bin/perl
  42. # This file is part of the ${\NAME} program.
  43. # Don't edit this file, unless you know what are you doing!
  44. # Updated on: ${\scalar localtime}
  45. # by: $0
  46. HEADER
  47. close $fh;
  48. }
  49. # Create the memory if doesn't exist
  50. if (not -e MEMORY_FILE) {
  51. save_mem(scalar {});
  52. }
  53. # Load the memory
  54. my $MEM = (do MEMORY_FILE);
  55. # Read or create memories
  56. sub hal {
  57. my ($items, $ref) = @_;
  58. foreach my $item (@{$items}) {
  59. $ref = ($ref->{$item} //= {});
  60. }
  61. return $ref;
  62. }
  63. # Speak the text (with espeak)
  64. sub speak {
  65. my ($text) = @_;
  66. state $null = File::Spec->devnull;
  67. `espeak -ven-us \Q$text\E 2>$null`;
  68. }
  69. # Transform GB to US (colour -> color)
  70. sub gb_to_us {
  71. my ($word) = @_;
  72. if (defined(my $us_word = $MISTAKES_GB_TO_US{$word})) {
  73. return $us_word;
  74. }
  75. return $word;
  76. }
  77. # Fix common mistakes
  78. sub fix_word {
  79. my ($word) = @_;
  80. if (defined(my $fixed_word = $MISTAKES_COMMON{$word})) {
  81. return $fixed_word;
  82. }
  83. return $word =~ s/^i('|$)/I$1/gr;
  84. }
  85. # Ask for a question
  86. sub ask_question {
  87. state $one = 'a';
  88. my $q = "Ask me $one question: ";
  89. if ($one eq 'a') {
  90. speak($q), $one = 'another';
  91. }
  92. my $question = $term->readline("\n[?] " . $q);
  93. if (not defined $question or $question eq '') {
  94. say "[!] Insert 'q' if you're bored already...";
  95. }
  96. elsif ($question eq 'q') {
  97. return;
  98. }
  99. return contraction($question =~ s/[<>]+//gr);
  100. }
  101. sub not_a_question {
  102. say "[*] This is not a question! :-)";
  103. speak("This is not a question!");
  104. }
  105. # Split a question into words
  106. sub get_words {
  107. my ($text) = @_;
  108. my @words;
  109. foreach my $word (split(' ', $text)) {
  110. my @ws;
  111. if ($word =~ s/([[:punct:]]+)\z//) {
  112. push @ws, $1;
  113. }
  114. push @words, gb_to_us(fix_word($word)), @ws;
  115. }
  116. return @words;
  117. }
  118. sub untag_word {
  119. my ($word) = @_;
  120. return scalar {$word =~ /^<([^>]+)>(.*?)<[^>]+>/s};
  121. }
  122. sub locate {
  123. my ($couple, $pairs, $pos) = @_;
  124. foreach my $i ($pos .. $#{$pairs}) {
  125. if (exists $pairs->[$i]{$couple->[0]}) {
  126. if (exists $couple->[1]) {
  127. if ($pairs->[$i]{$couple->[0]} eq $couple->[1]) {
  128. return $i;
  129. }
  130. }
  131. else {
  132. return $i;
  133. }
  134. }
  135. }
  136. return;
  137. }
  138. sub flip_pers {
  139. my (@pairs) = @_;
  140. my @output;
  141. foreach my $pair (@pairs) {
  142. my $val;
  143. if (defined($val = $pair->{prps})) {
  144. given (lc $val) {
  145. when ('your') {
  146. push @output, 'my';
  147. }
  148. when ('my') {
  149. push @output, 'your';
  150. }
  151. default {
  152. push @output, $val;
  153. }
  154. }
  155. }
  156. elsif (defined($val = $pair->{prp})) {
  157. given (lc $val) {
  158. when ('mine') {
  159. push @output, 'yours';
  160. }
  161. when ('yours') {
  162. push @output, 'mine';
  163. }
  164. when ('you') {
  165. push @output, 'I';
  166. }
  167. when ('I') {
  168. push @output, 'you';
  169. }
  170. default {
  171. push @output, $val;
  172. }
  173. }
  174. }
  175. elsif (defined($val = $pair->{vbp})) {
  176. given (lc $val) {
  177. when (['are', "'re"]) {
  178. push @output, 'am';
  179. }
  180. default {
  181. push @output, $val;
  182. }
  183. }
  184. }
  185. else {
  186. push @output, values %{$pair};
  187. }
  188. }
  189. return @output;
  190. }
  191. sub INIT {
  192. print <<"EOF";
  193. ********************************************************************************
  194. Hello there! My name is ${\NAME}.
  195. I'm a "Heuristically programmed ALgorithmic computer", a descendant of HAL9000.
  196. In this training program, I'm ready to answer and learn new things about your
  197. awesome world. So, please, don't hesitate and ask me anything. I'll try my best.
  198. ********************************************************************************
  199. EOF
  200. speak("Hello!");
  201. }
  202. while (1) {
  203. # Get a question
  204. my $question = ask_question() // last;
  205. # Split the question into words
  206. my @words = get_words($question);
  207. # Stem words
  208. my @s_words = grep { $_ ne '' } @{stem(@words)};
  209. # On empty questions, do this:
  210. @words || next;
  211. say join('--', @words);
  212. say join('==', @s_words);
  213. #say join('~~', $ltag->get_words($question));
  214. #my $xml = $ltag->add_tags(join(" ", @words));
  215. my $correct_q = join(' ', @words);
  216. my @pairs = map { untag_word($_) }
  217. split(' ', $ltag->add_tags($correct_q));
  218. pp \@pairs;
  219. my @requestion = flip_pers(@pairs);
  220. pp \@requestion;
  221. my $answer = 'yes'; # let's just assume
  222. =cut
  223. my @question;
  224. if (defined(my $i = locate([wp => 'what'], \@pairs, 0))) {
  225. if (defined(locate([vbz => "'s"], \@pairs, $i))) { # what is
  226. if (defined(my $j = locate(['prps'], \@pairs, $i))) { # what is your
  227. if ($pairs[$j]{prps} eq 'yours') {
  228. push @question, "my";
  229. while (defined(my $k = locate(['jj'], \@pairs, $j))) {
  230. push @question, $pairs[$k]{jj};
  231. $j = $k+1;
  232. }
  233. #if (defined(my $k = locate(['nn'], \@pairs,
  234. }
  235. }
  236. }
  237. }
  238. =cut
  239. =cut
  240. if (exists $pairs[0]{wp}) {
  241. if( $pairs[0]{wp} eq 'what'){
  242. if (exists $pairs[1]{vbz}) {
  243. if ($pairs[1]{vbz} eq "'s") { # what is
  244. }
  245. }
  246. }
  247. }
  248. =cut
  249. #say $xml;
  250. #pp \@pairs;
  251. =cut
  252. my $tags = xml2hash($xml);
  253. while (my ($key, $value) = each %{$tags}) {
  254. if (ref $value ne 'ARRAY') {
  255. $tags->{$key} = [$value];
  256. }
  257. }
  258. if (not exists $tags->{pp} or $tags->{pp}[-1] ne '?') {
  259. not_a_question();
  260. next;
  261. }
  262. pp $tags;
  263. =cut
  264. ##### NEEDS WORK #####
  265. =cut
  266. my $requestion = $question;
  267. $requestion =~ s/\byour\b/my/g; # your => my
  268. $requestion =~ s/\bare\b/am/g; # are => am
  269. $requestion =~ s/\byou\b/I/g; # you => I
  270. $requestion =~ s/\byours\b/mine/g; # yours => mine
  271. my $answer = $requestion;
  272. my $q_suffix = '';
  273. if ($answer =~ s/^what\h+//) {
  274. if ($answer =~ /am\b/) { } # ok
  275. elsif ($answer =~ s/^(\w+)\h*//) {
  276. $q_suffix = " $1";
  277. }
  278. }
  279. my $an_suffix = '';
  280. if ($answer =~ s/^how\h+//) {
  281. if ($answer =~ /^am\b/) { } # ok
  282. elsif ($answer =~ s/^(\w+)\h*//) {
  283. $an_suffix = " $1";
  284. }
  285. }
  286. $answer =~ s/^where\b\h*//;
  287. $answer =~ s/\bam\h+I\b/I am/g;
  288. $answer =~ s/\?+\z//;
  289. #$answer =~ s/^does\b\h*//;
  290. my @input = quotewords(qr/\s+/o, 0, $question);
  291. next if scalar(@input) == 0;
  292. my $ref = hal(\@input, $MEM);
  293. if (exists $ref->{ANSWER}) {
  294. print "[*] ";
  295. my $ans;
  296. if ($ref->{ANSWER} =~ /^(yes|no)[[:punct:]]?\z/i) {
  297. $ans = "\u\L$1\E!";
  298. }
  299. else {
  300. $ans = "\u$answer$q_suffix $ref->{ANSWER}$an_suffix.";
  301. }
  302. say $ans;
  303. speak($ans);
  304. }
  305. else {
  306. say "\n[*] I don't know... :(";
  307. speak("I don't know...");
  308. speak($requestion);
  309. my $input = $term->readline("[?] \u$requestion ");
  310. speak("Are you sure?");
  311. if ($term->readline("[!] Are you sure? ") =~ /^y/i) {
  312. $ref->{ANSWER} = $input;
  313. speak("Roger that!");
  314. }
  315. }
  316. =cut
  317. }
  318. # Save what we learned
  319. save_mem($MEM);