001 #ifndef CHAT_HH
002 #define CHAT_HH 21
003 #define LOG_SZ 8
004 #define LOG_WIDTH 40
005 #include "GarbageCollector";
006 #include "Set.HC"
007 Bool StartsWith(U8 *a,U8 *ending) {
008   return !StrNICmp(a,ending,StrLen(ending));
009 }
010 Bool EndsWith(U8 *a,U8 *ending) {
011   I64 el=StrLen(ending),al=StrLen(a);
012   if(el>al)
013     return FALSE;
014   return !StrNICmp(a+al-el,ending,el);
015 }
016 U8 *BaseWord(U8 *str) {
017   U8 buf[STR_LEN];
018   StrCpy(buf,str);
019   if(EndsWith(buf,"ing")) {
020     buf[StrLen(buf)-3]=0;
021   } else if(EndsWith(buf,"ed")) {
022     buf[StrLen(buf)-2]=0;
023   } else if(EndsWith(buf,"er")) {
024     buf[StrLen(buf)-2]=0;
025   } else if(EndsWith(buf,"in")) {
026     buf[StrLen(buf)-2]=0;
027   } else if(EndsWith(buf,"ies")) {
028     buf[StrLen(buf)-3]=0;
029     CatPrint(buf,"y");
030   } else if(EndsWith(buf,"es")) {
031     buf[StrLen(buf)-2]=0;
032   } else if(EndsWith(buf,"s")) {
033     buf[StrLen(buf)-1]=0;
034   }
035   return StrNew(buf,mem_task);
036 }
037 CI64Set *TokenizeSentence(U8 *str) {
038   U8 buf[STR_LEN];
039   I64 idx;
040   CI64Set *ret=I64SetNew;
041 again:;
042   while(*str&&Bt(char_bmp_white_space,*str))
043     ++str;
044   if(!*str)
045     return ret;
046   if(Bt(char_bmp_alpha_numeric,*str)) {
047     for(idx=0;Bt(char_bmp_alpha_numeric,*str);++idx,++str)
048       buf[idx]=*str;
049     buf[idx]=0;
050     I64SetAdd(ret,BaseWord(buf));
051   } else {
052     buf[0]=*str++;
053     buf[1]=0;    
054     I64SetAdd(ret,StrNew(buf,mem_task));
055   }
056   goto again;
057 }
058 U0 FreeToks(CI64Set *t) {
059   I64  c=t->cnt;
060   while(--c>=0)
061     Free(t->body[c]);
062   I64SetDel(t);
063 }
064 U8 *NextToken(CI64Set *et,I64 *_i) {
065   I64 i=*_i;
066   if(i>=et->cnt)
067     return NULL;
068   U8 *ret=et->body[i];
069   *_i=i+1;
070   if(!StrCmp(ret,"*")) {
071     return NextToken(et,_i);
072   }
073   return ret;
074 }
075 I64 ElizaMatch0(U8 *s,U8 *eliza) {
076   I64 hit_cnt=0;
077   CI64Set *st=TokenizeSentence(s);
078   CI64Set *et=TokenizeSentence(eliza);
079   U8 *next,*t1,*t2;
080   I64 si,ei=0;
081   if(!et->cnt)
082     goto done;
083   next=NextToken(et,&ei);
084   if(!next)
085     goto done;
086   for(si=0;si<st->cnt;++si) {
087     if(!StrICmp(st->body[si],next)) {
088       next=NextToken(et,&ei);
089       ++hit_cnt;
090       if(!next) {
091         goto done;
092       }
093     }
094   }
095   hit_cnt=I32_MIN;
096 done:;
097   FreeToks(st);
098   FreeToks(et);
099   return hit_cnt;
100 }
101 
102 
103 I64 ElizaMatch(U8 *s,U8 *eliza) {
104   I64 idx,best=I32_MIN;
105   U8 *tmp;
106   for(idx=0;tmp=LstSub(idx,eliza);++idx) {
107     best=MaxI64(best,ElizaMatch0(s,tmp));
108   }
109   return best;
110 }
111 
112 class CBot:CQue {
113   U0 (*read_cb)(U8 *chat_log,CBot *,U8 *str,CBot *str_bot);
114   I64 color;
115   U8 name[STR_LEN];
116   U8 *user_data;
117   CI64Set *chat_data;
118   CI64Set *flags;
119   I64 key_num;
120 //Private
121   CTask *typing_delay_task;
122 };
123 class CElizaResponse {
124   I64 key_num;
125   U8 input[STR_LEN];
126   U8 output[STR_LEN];
127   U8 concept[STR_LEN];
128   U8 need_vars[STR_LEN];
129   U8 add_vars[STR_LEN];
130   U8 rem_vars[STR_LEN];
131   Bool negative;
132 };
133 class CChat {
134   CFifoI64 *log;
135   I64 buf_pos;
136   U8 *user_data;
137   U0 (*say_cb)(CChat *,U8 *,CBot *user_data);
138   CQue bots;
139   U8 buf[LOG_WIDTH+1];
140   U8 idle_message[LOG_WIDTH+1];
141   Bool focus;
142 };
143 
144 CTask *mem_task=Fs;
145 
146 extern U0 ChatAdd(CChat *chat,U8 *str,CBot *who_is_saying=NULL);
147 Bool KeyPasses0(U8 *str,U8 *key) {
148   Bool pass=FALSE;
149   CI64Set *st=TokenizeSentence(str);
150   CI64Set *kt=TokenizeSentence(key);
151   I64 i,j;
152   for(i=0;i!=st->cnt;++i)
153     for(j=0;j!=kt->cnt;++j) {
154       if(!(StrICmp(kt->body[j],st->body[i])))
155         pass=TRUE;
156     }
157   FreeToks(kt);
158   FreeToks(st);
159   return pass;
160 }
161 Bool KeyPasses(U8 *str,U8 *k) {
162   if(!*k)
163     return TRUE;
164   I64 sub=0;
165   U8 *tmp;
166   while(*k&&(tmp=LstSub(sub++,k)))
167     if(KeyPasses0(str,k))
168       return TRUE;
169   return FALSE;
170 }
171 Bool BotHasFlag(CBot *bot,U8 *f) {
172   I64 cnt=bot->flags->cnt;
173   U8 **body=bot->flags->body;
174   while(--cnt>=0)
175     if(!StrICmp(f,body[cnt]))
176       return TRUE;
177   return FALSE;
178 }
179 U0 TypingDelayTask(U8 *str) {
180   Sleep(100.+500.*Rand);
181   ExePutS(str);
182   Free(str);
183 }
184 
185 U0 BotReadCbDft(CChat *c,CBot *bot,U8 *str,CBot *who) {
186   I64 r=bot->chat_data->cnt;
187   CElizaResponse *resp;
188   CI64Set *passes=I64SetNew;
189   I64 best_len=-1,cnt,sub,idx,idx2;
190   Bool pass;
191   U8 *tmp;
192 
193   r=bot->chat_data->cnt;
194   while(--r>=0) {
195     resp=bot->chat_data->body[r];
196 
197     if(resp->key_num==bot->key_num) {
198       pass=TRUE;
199       cnt=0;
200       if(*resp->need_vars)
201         for(idx=0;tmp=LstSub(idx,resp->need_vars);++idx) {
202 "%s.T\n",tmp;
203           if(!BotHasFlag(bot,tmp)) {
204             pass=FALSE;
205             break;
206           } else
207            cnt+=10;
208 "%s,%d\n",tmp,cnt;
209         }
210 "%d,%d\n",pass,resp->negative;
211       if(pass!=resp->negative) {
212 "%s,%d,%d\n",resp->need_vars,pass,cnt;
213 "%s,%s\n",str,resp->input;
214         cnt+=ElizaMatch(str,resp->input);
215 "!%s,%d,%d\n",resp->need_vars,pass,cnt;
216         if(cnt>best_len&&cnt>=0) {
217           best_len=cnt;
218           passes->cnt=0;
219 "BEST:%s\n",resp->output;
220           I64SetAdd(passes,resp);
221         } else if(cnt==best_len&&cnt>=0) {
222 "EQ:%s,%d\n",resp->output,cnt;
223           I64SetAdd(passes,resp);
224         }
225       }
226     }
227   }
228 
229 
230   if(passes->cnt) {
231     resp=passes->body[RandU64%passes->cnt];
232     if(*resp->add_vars) {
233       for(idx=0;tmp=LstSub(idx,resp->add_vars);++idx) {
234         if(!BotHasFlag(bot,tmp))
235           I64SetAdd(bot->flags,GCStrNew(tmp)); 
236       }
237     }
238     if(*resp->rem_vars) {
239         "%s\n",resp->rem_vars;
240 
241 rem_again:;
242       for(idx=0;tmp=LstSub(idx,resp->rem_vars);++idx) {
243         for(idx2=0;idx2!=bot->flags->cnt;++idx2) {
244           if(!StrICmp(bot->flags->body[idx2],tmp)) {
245             I64SetRem(bot->flags,bot->flags->body[idx2]);
246             goto rem_again;
247           }
248         }
249       }
250     }
251     str=MStrPrint("ChatAdd(%d,%d,%d);;\n",c,resp->output,bot);
252 //Chain them
253     if(bot->typing_delay_task)
254       Kill(bot->typing_delay_task,FALSE);
255     bot->typing_delay_task=Spawn(&TypingDelayTask,str,,,Fs);
256   }
257   I64SetDel(passes);
258 }
259 U0 BotDel(CBot *bot) {
260   I64 c=bot->chat_data->cnt;
261   Kill(bot->typing_delay_task,FALSE);
262   if(bot->next) QueRem(bot);
263 //  I64SetDel(bot->chat_data);
264 }
265 U0 AddToList(U8 *l,U8 *thing) {
266   if(*l) {
267 again:;
268     l+=StrLen(l)+1;
269     if(*l) {
270       goto again;
271     }
272   }
273   StrCpy(l,thing);
274   l+=StrLen(thing);
275   l[1]=0;
276 }
277 CBot *BotNewFromPersonalityFile(U8 *file) {
278   CBot *bot=GCCAlloc(sizeof(CBot));
279   CCmpCtrl *cc=CmpCtrlNew(FileRead(file),,file);
280   I64 l,concept_num=-1;
281   U8 cur_input[STR_LEN];
282   U8 cur_concept[STR_LEN];
283   U8 add_vars[STR_LEN];
284   U8 rem_vars[STR_LEN];
285   U8 need_vars[STR_LEN];
286   U8 *tag;
287   StrCpy(cur_input,"*");
288   StrCpy(cur_concept,"");
289   StrCpy(add_vars,"");
290   StrCpy(need_vars,"");
291   StrCpy(rem_vars,"");
292   bot->chat_data=I64SetNew;
293   bot->flags=I64SetNew;
294   CElizaResponse *resp;
295   Lex(cc);
296   while(cc->token) {
297     switch(cc->token) {
298       case '.': //Concept
299         concept_num++;
300         StrCpy(cur_concept,"");
301 again_concept:
302         if(Lex(cc)!=TK_STR)
303           LexExcept(cc,"Expected a concept string");
304         AddToList(cur_concept,cc->cur_str);
305         if(Lex(cc)==',') {
306           goto again_concept;
307         }
308         break;
309       case '!': //Input
310         StrCpy(cur_input,"");
311         StrCpy(need_vars,"");
312 again_input:
313         if(Lex(cc)!=TK_STR)
314           LexExcept(cc,"Expected a input string");
315         AddToList(cur_input,cc->cur_str);
316         if(Lex(cc)==',') {
317           goto again_input;
318         }
319         break;
320       case '+': //Pass vars
321         resp=GCCAlloc(sizeof(CElizaResponse));
322         MemCpy(resp->concept,cur_concept,STR_LEN);
323         MemCpy(resp->input,cur_input,STR_LEN);
324         MemCpy(resp->need_vars,need_vars,STR_LEN);
325         I64SetAdd(bot->chat_data,resp);
326         goto fin_output;
327         break;
328       case '-': //Not pass vars
329         resp=GCCAlloc(sizeof(CElizaResponse));
330         MemCpy(resp->concept,cur_concept,STR_LEN);
331         MemCpy(resp->need_vars,need_vars,STR_LEN);
332         MemCpy(resp->input,cur_input,STR_LEN);
333         I64SetAdd(bot->chat_data,resp);
334         resp->negative=TRUE;
335 fin_output:"%s\n",need_vars;
336         if(Lex(cc)!=TK_STR)
337           LexExcept(cc,"Expected a result string");
338         StrCpy(resp->output,cc->cur_str);
339         Lex(cc);
340         if(cc->token=='{') {
341 flagger:
342           switch(Lex(cc)) {
343             start:
344               if(Lex(cc)!=TK_STR)
345                 LexExcept(cc,"Expected a flags string");
346               case '-':
347                 AddToList(resp->rem_vars,cc->cur_str);
348                 break;
349               case '+':
350                 AddToList(resp->add_vars,cc->cur_str);
351                 break;
352             end:;
353               goto flagger;
354             case '}':
355               break;
356             default:
357               LexExcept(cc,"Expected a '-'/'+'.");
358           }
359           Lex(cc);
360         }
361         resp->key_num=concept_num;
362         break;
363       case '[':
364         StrCpy(need_vars,"");
365         Lex(cc);
366 again_need:;
367         if(cc->token==']') {
368           Lex(cc);
369           break;
370         }
371         if(cc->token!=TK_STR)
372           LexExcept(cc,"Expected flags.");
373         AddToList(need_vars,cc->cur_str);
374         Lex(cc);
375         goto again_need;
376       default:
377         LexExcept(cc,"Unexpected token at ");
378     }
379   }
380   bot->read_cb=&BotReadCbDft;
381   return bot;
382 }
383 
384 U0 ChatAdd(CChat *chat,U8 *str,CBot *user_data=NULL) {
385   I64 color=LTCYAN;
386   CBot *cur,*head=&chat->bots;
387   CFifoI64 *f=chat->log;
388   CFifoI64 *buf;
389   U8 *tmp;
390   U8 sbuf[STR_LEN+16];
391   StrPrint(sbuf,"%d:%s",user_data,str);
392   str=StrNew(sbuf,mem_task);
393   if(FifoI64Cnt(f)==f->mask) {
394     FifoI64Rem(f,&tmp);
395     Free(tmp);
396     buf=FifoI64New(LOG_SZ,mem_task);
397     while(FifoI64Rem(f,&tmp)) {
398       if(!FifoI64Ins(buf,tmp))
399         Free(tmp);
400     }
401     FifoI64Del(f);
402     FifoI64Ins(buf,str);
403     chat->log=buf;
404   } else
405     FifoI64Ins(f,str);
406   for(cur=head->next;cur!=head;cur=cur->next) {
407     if(cur->read_cb&&cur!=user_data)
408       (*cur->read_cb)(chat,cur,str,user_data);
409   }
410 }
411 CChat *ChatNew() {
412   CChat *chat=GCCAlloc(sizeof(CChat));
413   chat->log=FifoI64New(LOG_SZ,mem_task);
414   chat->say_cb=&ChatAdd;
415   QueInit(&chat->bots);
416   StrCpy(chat->idle_message,"Talk to your homies.");
417   return chat;
418 }
419 Bool ChatInteract(CChat *c,I64 m,I64 x,I64 y,U8 *user_data=NULL) {
420   I64 l=StrLen(c->buf);
421   U8 *tmp;
422   U8 alt[STR_LEN];
423   Bool pass=FALSE;
424   if(m==MSG_KEY_DOWN) {
425     if(x==CH_ESC) {
426       c->focus=FALSE;
427       pass=TRUE;
428     } else if(y.u8[0]==SC_CURSOR_LEFT) {
429       if(y&SCF_CTRL)
430         c->buf_pos=0;
431       else
432         --c->buf_pos;
433       pass=TRUE;
434     } else if(y.u8[0]==SC_CURSOR_RIGHT) {
435       if(y&SCF_CTRL)
436         c->buf_pos=l;
437       else
438         ++c->buf_pos;
439       pass=TRUE;
440     } else if(x=='\n') {
441       if(c->say_cb)
442         c->say_cb(c,c->buf,user_data);
443       c->focus=FALSE;
444       StrCpy(c->buf,"");
445       l=0;
446       pass=TRUE;
447     } else if(x==CH_BACKSPACE) {
448       --c->buf_pos;
449       tmp=&c->buf[c->buf_pos];
450       if(c->buf_pos>=0) {
451         StrCpy(tmp,tmp+1);
452         --l;
453       }
454       pass=TRUE;
455     } else if(l+1<LOG_WIDTH) {
456       if(Bt(char_bmp_displayable,x&0xff)) {
457         tmp=&c->buf[c->buf_pos];
458         StrCpy(alt,tmp);
459         *tmp=x;
460         StrCpy(tmp+1,alt);
461         ++l;
462         ++c->buf_pos;
463         pass=TRUE;
464       }
465     }
466   }
467   c->buf_pos=ClampI64(c->buf_pos,0,l);
468   return pass;
469 }
470 #define CHATBOX_WIDTH (8*LOG_WIDTH)
471 #define CHATBOX_HEIGHT (LOG_SZ*8)
472 U0 ChatDraw(CDC *dc,I64 x,I64 y,CChat *c) {
473   CFifoI64 old;
474   CBot *bot;
475   I64 oy=y;
476   U8 *str;
477   U8 buf[STR_LEN];
478   MemCpy(&old,c->log,sizeof(CFifoI64));
479   dc->thick=1;
480   dc->color=RED;
481   GrRect(dc,x,y,8*LOG_WIDTH,LOG_SZ*8);
482   dc->color=GREEN;
483   dc->thick=8;
484   GrBorder(dc,x-4,y-4,4+x+8*LOG_WIDTH,y+4+LOG_SZ*8);
485   dc->color=RED;
486   dc->thick=2;
487   GrBorder(dc,x-4,y-4,4+x+8*LOG_WIDTH,y+4+LOG_SZ*8);
488 
489 
490   while(FifoI64Rem(&old,&str))
491     if(StrOcc(str,':')) {
492       dc->color=LTCYAN; 
493       str=StrFirstOcc(str,":")+1;//See ChatAdd,we prepend the user_data
494       StrCpy(buf,str);
495       buf[LOG_WIDTH]=0;
496       GrPrint3(dc,x,y,0,"%s",str);
497       y+=8;
498     }
499   y=oy+8*LOG_SZ-8;
500   if(c->focus) {
501     dc->color=LTCYAN;
502     GrPrint3(dc,x,y,"%s",c->buf);
503     dc->color=ROP_XOR|WHITE;
504     GrRect(dc,x+8*c->buf_pos,y,8,8);
505   } else {
506     dc->color=LTCYAN;
507     GrPrint3(dc,x,y,"%s",c->idle_message);
508   }
509 }
510 #if __CMD_LINE__
511 CChat *c=ChatNew;
512 ChatAdd(c,"1");
513 ChatAdd(c,"2");
514 ChatAdd(c,"a3");
515 ChatAdd(c,"4");
516 ChatAdd(c,"5");
517 ChatAdd(c,"6");
518 ChatAdd(c,"7");
519 ChatAdd(c,"8");
520 ChatAdd(c,"9");
521 ChatAdd(c,"9");
522 ChatAdd(c,"10");
523 ChatAdd(c,"11");
524 ChatAdd(c,"12");
525 ChatAdd(c,"13");
526 ChatAdd(c,"14");
527 ChatAdd(c,"15");
528 ChatAdd(c,"16");
529 StrCpy(c->buf,"Hello world 21");
530 c->buf_pos=3;
531 QueIns(BotNewFromPersonalityFile("AssHole_Chat.HC"),c->bots.last);
532 U0 DrawIt(CTask *,CDC *dc) {
533 ChatDraw(dc,100,100,c);
534 }
535 U0 ChatDemo() {
536   I64 m,x,y;
537   SettingsPush;
538   Fs->draw_it=&DrawIt;
539   c->focus=TRUE;
540   while(TRUE) {
541     while(m=ScanMsg(&x,&y)) {
542       if(m==MSG_KEY_DOWN&&x==CH_ESC)
543         goto done;
544       ChatInteract(c,m,x,y);
545     }
546     Refresh;
547   }
548 done:;
549   SettingsPop;
550 }
551 ChatDemo;
552 #endif