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