#if 0 gcc ${CFLAGS:--s -O2} -c -Wno-unused-result -Wno-multichar -fwrapv game.c `sdl-config --cflags` exit #endif /* This program is part of Free Hero Mesh and is public domain. */ #include "SDL.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "sqlite3.h" #include "smallxrm.h" #include "heromesh.h" #include "quarks.h" #include "cursorshapes.h" #include "names.h" MoveItem*replay_list; size_t replay_size; Uint16 replay_count,replay_pos,replay_mark; Uint8 solution_replay=255; char*best_list; Sint32 best_score=NO_SCORE; static volatile Uint8 timerflag; static int exam_scroll; static MoveItem*inputs; static size_t inputs_size; static int inputs_count; static Uint8 side_mode=255; static Uint8 should_record_solution; static Uint8 should_record_private_solution; static Uint8 replay_speed; static Uint8 replay_time; static Uint8 solved; static Uint8 inserting,saved_inserting; static sqlite3_stmt*autowin; static size_t dum_size; // not used by Free Hero Mesh, but needed by some C library functions. int encode_move(FILE*fp,MoveItem v) { // Encodes a single move and writes the encoded move to the file. // Returns the number of bytes of the encoded move. if(v>=8 && v<256) { fputc(v,fp); return 1; } else if(v>=0x8000 && v<=0x8FFF) { fputc(KEY_XY,fp); fputc(((v>>6)&63)+1,fp); fputc((v&63)+1,fp); return 3; } else { fatal("Unencodable move (%u)\n",(int)v); } } int encode_move_list(FILE*fp) { // Encodes the current replay list into the file; returns the number of bytes. // Does not write a null terminator. int i; int c=0; for(i=0;i<replay_count;i++) c+=encode_move(fp,replay_list[i]); return c; } MoveItem decode_move(FILE*fp) { // Decodes a single move from the file, and returns the move. // Returns zero if there is no more moves. int v=fgetc(fp); if(v>=8 && v<256) { return v; } else if(v==KEY_XY) { v=0x8000|((fgetc(fp)-1)<<6); return v|(fgetc(fp)-1); } else if(v==EOF || !v) { return 0; } else { fatal("Undecodable move (%u)\n",v); } } int decode_move_list(FILE*fp) { // Decodes a move list from the file, and stores it in replay_list and replay_count. // Returns the number of moves (replay_count). MoveItem v; FILE*o; free(replay_list); replay_list=0; replay_size=0; replay_count=0; o=open_memstream((char**)&replay_list,&replay_size); if(!o) fatal("Allocation failed\n"); while(replay_count<0xFFFE && (v=decode_move(fp))) { fwrite(&v,1,sizeof(MoveItem),o); replay_count++; } fclose(o); if(replay_count && !replay_list) fatal("Allocation failed\n"); return replay_count; } #define MSIZ (sizeof(MoveItem)) #define memcpyM(a_,b_,c_) memcpy(a_,b_,(c_)*MSIZ); #define memmoveM(a_,b_,c_) memmove(a_,b_,(c_)*MSIZ); static void record_solution(void); static void setup_game(void) { const char*v; optionquery[1]=Q_showInventory; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; side_mode=boolxrm(v,1); optionquery[1]=Q_replaySpeed; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; replay_speed=strtol(v,0,10)?:16; optionquery[1]=Q_saveSolutions; optionquery[2]=Q_private; v=xrm_get_resource(resourcedb,optionquery,optionquery,3)?:""; should_record_private_solution=boolxrm(v,0); if(main_options['r']) { should_record_solution=0; } else { v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; should_record_solution=boolxrm(v,0); } solution_replay=0; optionquery[1]=Q_autoWin; v=xrm_get_resource(resourcedb,optionquery,optionquery,2); if(v && *v) sqlite3_prepare_v3(userdb,v,-1,SQLITE_PREPARE_PERSISTENT,&autowin,0); } static void redraw_game(void) { char buf[32]; SDL_Rect r; int x,y; r.x=r.y=0; r.h=screen->h; r.w=left_margin; SDL_FillRect(screen,&r,0xF0); r.x=left_margin-1; r.w=1; SDL_FillRect(screen,&r,0xF7); r.x=left_margin; r.w=screen->w-r.x; SDL_FillRect(screen,&r,back_color); for(x=1;x<=pfwidth;x++) for(y=1;y<=pfheight;y++) draw_cell(x,y); x=y=0; SDL_GetMouseState(&x,&y); SDL_LockSurface(screen); if(left_margin>=88) { snprintf(buf,32,"%5d/%5d",level_ord,level_nindex); draw_text(0,0,buf,0xF0,solution_replay?0xFE:solved?0xFA:0xFC); snprintf(buf,32,"%5d",level_id); draw_text(0,8,"ID",0xF0,0xF7); draw_text(48,8,buf,0xF0,0xFF); snprintf(buf,32,"%5d",level_version); draw_text(0,16,"VER",0xF0,0xF7); draw_text(48,16,buf,0xF0,0xFF); snprintf(buf,32,"%5d",level_code); draw_text(0,24,"CODE",0xF0,0xF7); draw_text(48,24,buf,0xF0,0xFF); } else { snprintf(buf,32,"%5d",level_ord); draw_text(16,0,buf,0xF0,solution_replay?0xFE:solved?0xFA:0xFC); snprintf(buf,32,"%5d",level_id); draw_text(0,8,"I",0xF0,0xF7); draw_text(16,8,buf,0xF0,0xFF); snprintf(buf,32,"%5d",level_version); draw_text(0,16,"V",0xF0,0xF7); draw_text(16,16,buf,0xF0,0xFF); snprintf(buf,32,"%5d",level_code); draw_text(0,24,"C",0xF0,0xF7); draw_text(16,24,buf,0xF0,0xFF); } if(!gameover) { snprintf(buf,8,"%2dx%2d",pfwidth,pfheight); draw_text(8,32,buf,0xF0,0xFD); draw_text(24,32,"x",0xF0,0xF5); } else if(gameover<0) { draw_text(4,32,"*LOSE*",0xF4,0xFC); } else { draw_text(4,32,"*WIN*",0xF2,0xFA); } x=x>=left_margin?(x-left_margin)/picture_size+1:0; y=y/picture_size+1; if(x>0 && y>0 && x<=pfwidth && y<=pfheight) snprintf(buf,8,"(%2d,%2d)",x,y); else strcpy(buf," "); draw_text(0,40,buf,0xF0,0xF1); if(side_mode) { // Inventory x=20-(left_margin-picture_size)/8; if(x>19) x=19; if(x<0) x=0; for(y=0;y<ninventory;y++) { if(y*picture_size+60>=screen->h) break; snprintf(buf,22,"%20d",inventory[y].value); draw_text(picture_size,y*picture_size+52,buf+x,0xF8,0xFE); } SDL_UnlockSurface(screen); r.x=0; r.y=52; r.w=picture_size; r.h=screen->h-52; SDL_FillRect(screen,&r,inv_back_color); for(y=0;y<ninventory;y++) { if(y*picture_size+60>=screen->h) break; if(classes[inventory[y].class]->nimages<inventory[y].image) continue; draw_picture(0,y*picture_size+52,classes[inventory[y].class]->images[inventory[y].image]&0x7FFF); } } else { // Move list snprintf(buf,8,"%5d",replay_pos); draw_text(8,52,buf,0xF0,0xF9); snprintf(buf,8,"%5d",replay_count); draw_text(8,screen->h-8,buf,0xF0,solution_replay?0xFA:0xFC); for(y=44,x=replay_pos-(screen->h-68)/32;;x++) { y+=16; if(y+24>screen->h) break; if(x>=0 && x<replay_count) { if(replay_list[x]<256) { draw_key(16,y,replay_list[x],0xF8,0xFB); } else if((replay_list[x]&0xF000)==0x8000) { sprintf(buf,"%02u",((replay_list[x]>>6)&63)+1); draw_text(16,y,buf,0xF8,0x47); sprintf(buf,"%02u",(replay_list[x]&63)+1); draw_text(16,y+8,buf,0xF8,0x45); } } if(x==replay_count) draw_key(16,y,1,0xF0,0xF8); if(x==replay_pos) draw_text(0,y,inserting?"I~":"~~",0xF0,0xFE); if(x==replay_mark) draw_text(32,y,"~~",0xF0,0xFD); } SDL_UnlockSurface(screen); } if(quiz_text) draw_popup(quiz_text); SDL_Flip(screen); set_cursor(XC_arrow); } static void continue_animation(void) { Uint32 n=firstobj; Object*o; Animation*a; DeadAnimation*d; int i; for(i=0;i<8;i++) if(anim_slot[i].length && ++anim_slot[i].vtime==anim_slot[i].speed && (anim_slot[i].vtime=0,++anim_slot[i].frame==anim_slot[i].length)) anim_slot[i].frame=0; while(n!=VOIDLINK) { o=objects[n]; if((a=o->anim) && (a->status&ANISTAT_VISUAL)) { i=a->vstep; if(a->step[i].flag&ANI_SYNC) { i=anim_slot[a->step[i].slot].frame+a->step[i].start; if(i!=a->vimage) { a->vimage=i; draw_cell(o->x,o->y); } } else if(++a->vtime>=a->step[i].speed) { a->vtime=0; if(a->vimage==a->step[i].end) { if(a->step[i].flag&ANI_ONCE) { if(a->vstep==a->lstep) { a->status&=~ANISTAT_VISUAL; } else { if(++a->vstep==max_animation) a->vstep=0; a->vimage=a->step[a->vstep].start; } } else if(a->step[i].flag&ANI_OSC) { a->step[i].end=a->step[i].start; a->step[i].start=a->vimage; goto advance; } else { a->vimage=a->step[i].start; } } else { advance: if(a->step[i].end>=a->step[i].start) ++a->vimage; else --a->vimage; } draw_cell(o->x,o->y); } } n=o->next; } if(ndeadanim) { for(i=0;i<ndeadanim;i++) { d=deadanim+i; draw_cell(d->x,d->y); if(!d->s.flag) continue; if(d->delay) { --d->delay; continue; } if(d->vimage<classes[d->class]->nimages) draw_picture((d->x-1)*picture_size+left_margin,(d->y-1)*picture_size,classes[d->class]->images[d->vimage]&0x7FFF); if(++d->vtime>=d->s.speed) { if(d->vimage==d->s.end) d->s.flag=0; if(d->s.end>=d->s.start) ++d->vimage; else --d->vimage; d->vtime=0; } } for(i=0;i<ndeadanim;i++) while(i<ndeadanim && !deadanim[i].s.flag) { draw_cell(deadanim[i].x,deadanim[i].y); if(i<ndeadanim-1) deadanim[i]=deadanim[ndeadanim-1]; --ndeadanim; } } SDL_Flip(screen); } static void show_mouse_xy(SDL_Event*ev) { char buf[32]; int x,y; x=(ev->motion.x-left_margin)/picture_size+1; y=ev->motion.y/picture_size+1; if(ev->motion.x<left_margin) { if(ev->button.y<48) { strcpy(buf," "); } else if(side_mode) { // Inventory x=(ev->button.y-52)/picture_size; if(x<0 || x>=ninventory) strcpy(buf," "); else snprintf(buf,8,"%7d",inventory[x].value); } else { // Move list x=replay_pos+(ev->button.y+4)/16-(screen->h-68)/32-4; if(x<0 || x>replay_count) strcpy(buf," "); else snprintf(buf,8,"%c%6d",x<replay_pos?0xAE:x>replay_pos?0xAF:0xFE,x); } } else { if(x>0 && y>0 && x<=pfwidth && y<=pfheight) snprintf(buf,8,"(%2d,%2d)",x,y); else strcpy(buf," "); } SDL_LockSurface(screen); draw_text(0,40,buf,0xF0,0xF1); SDL_UnlockSurface(screen); SDL_Flip(screen); } static void save_replay(void) { unsigned char*buf=0; size_t sz=0; FILE*fp; if(solution_replay || !replay_list || !replay_count) return; if(gameover==1) solved=1; fp=open_memstream((char**)&buf,&sz); if(!fp) fatal("Allocation failed\n"); fputc(0x00,fp); fputc(0x01,fp); encode_move_list(fp); fputc(0x00,fp); if(solved) { fputc(0x41,fp); fputc(level_version,fp); fputc(level_version>>8,fp); if(best_list) { fputc(0x02,fp); fwrite(best_list,1,strlen(best_list)+1,fp); fputc(0x81,fp); fputc(best_score,fp); fputc(best_score>>8,fp); fputc(best_score>>16,fp); fputc(best_score>>24,fp); } } if(replay_mark) { fputc(0x42,fp); fputc(replay_mark,fp); fputc(replay_mark>>8,fp); } fclose(fp); if(!buf) fatal("Allocation failed\n"); write_userstate(FIL_LEVEL,level_id,sz,buf); free(buf); } static void load_replay(void) { FILE*fp=0; unsigned char*buf=0; long sz; int i,j; free(replay_list); replay_list=0; replay_count=replay_mark=replay_size=0; free(best_list); best_list=0; if(solution_replay) { gameover_score=NO_SCORE; if(buf=read_lump(FIL_SOLUTION,level_id,&sz)) { fp=fmemopen(buf,sz,"r"); if(!fp) fatal("Allocation failed\n"); // Solution format: version (16-bits), flag (8-bits), score (32-bits), user name (null-terminated), timestamp (64-bits), move list if(sz>3) { i=fgetc(fp); i|=fgetc(fp)<<8; if(i==level_version) { j=fgetc(fp); if(j&128) { gameover_score=fgetc(fp); gameover_score|=fgetc(fp)<<8; gameover_score|=fgetc(fp)<<16; gameover_score|=fgetc(fp)<<24; } if(j&1) while(fgetc(fp)>0); if(j&2) for(i=0;i<8;i++) fgetc(fp); decode_move_list(fp); } } } } else if(buf=read_userstate(FIL_LEVEL,level_id,&sz)) { fp=fmemopen(buf,sz,"r"); if(!fp) fatal("Allocation failed\n"); best_score=NO_SCORE; if(sz>2 && *buf) { // Old format replay_count=(buf[sz-2]<<8)|buf[sz-1]; if(sz-replay_count>=4) replay_mark=(buf[replay_count]<<8)|buf[replay_count+1]; else replay_mark=0; if(sz-replay_count>=6) { i=(buf[replay_count+2]<<8)|buf[replay_count+3]; if(i==level_version) solved=1; } replay_list=malloc(replay_size=sizeof(MoveItem)*(replay_count+1)); if(!replay_list) fatal("Allocation failed\n"); for(i=0;i<replay_count;i++) replay_list[i]=buf[i]; } else { // New format fgetc(fp); // skip first null byte while((i=fgetc(fp))!=EOF) switch(i) { case 0x01: // Replay list if(replay_list) goto skip; decode_move_list(fp); break; case 0x02: // Best list if(best_list) goto skip; dum_size=0; getdelim(&best_list,&dum_size,0,fp); break; case 0x41: // Solved version i=fgetc(fp); i|=fgetc(fp)<<8; if(i==level_version) solved=1; break; case 0x42: // Mark replay_mark=fgetc(fp); replay_mark|=fgetc(fp)<<8; break; case 0x81: // Best score best_score=fgetc(fp); best_score|=fgetc(fp)<<8; best_score|=fgetc(fp)<<16; best_score|=fgetc(fp)<<24; break; default: skip: if(i<0x40) { while(fgetc(fp)>0); } else if(i<0x80) { fgetc(fp); fgetc(fp); } else if(i<0xC0) { for(i=0;i<4;i++) fgetc(fp); } else { for(i=0;i<8;i++) fgetc(fp); } } if(best_list && !solved) { free(best_list); best_list=0; best_score=NO_SCORE; } } } if(fp) fclose(fp); free(buf); } static void begin_level(int id) { const char*t; replay_time=0; if(replay_count) save_replay(); inputs_count=0; replay_pos=0; solved=0; inserting=0; t=load_level(id)?:init_level(); load_replay(); if(t) { gameover=-1; screen_message(t); } else { gameover=0; } timerflag=0; } static inline void exam_value(const char*t,int y,Value v) { char buf[256]; int i; y=(y-exam_scroll)*8; if(y<0 || y>screen->h-8) return; draw_text(0,y,t,0xF0,0xF7); switch(v.t) { case TY_NUMBER: snprintf(buf,255,"%12lu 0x%08lX %ld",(long)v.u,(long)v.u,(long)v.s); draw_text(200,y,buf,0xF0,0xFE); break; case TY_CLASS: draw_text(200,y,"$",0xF0,0xFB); draw_text(208,y,classes[v.u]->name,0xF0,0xFB); break; case TY_MESSAGE: snprintf(buf,255,"%s%s",v.u<256?"":"#",v.u<256?standard_message_names[v.u]:messages[v.u-256]); draw_text(200,y,buf,0xF0,0xFD); break; case TY_LEVELSTRING: case TY_STRING: draw_text(200,y,"<String>",0xF0,0xF9); break; case TY_SOUND: case TY_USOUND: draw_text(200,y,"<Sound>",0xF0,0xF6); break; case TY_MARK: draw_text(200,y,"<Mark>",0xF0,0xF3); break; case TY_ARRAY: draw_text(200,y,"<Array>",0xF0,0xF9); snprintf(buf,255,"0x%08lX",(long)v.u); draw_text(264,y,buf,0xF0,0xFE); break; default: snprintf(buf,80,"<%lu:%lu>",(long)v.u,(long)v.t); draw_text(200,y,buf,0xF0,0xFA); i=strlen(buf)*8+208; if(v.u<nobjects && objects[v.u] && objects[v.u]->generation==v.t) { snprintf(buf,80,"@ (%d,%d)",objects[v.u]->x,objects[v.u]->y); draw_text(i,y,buf,0xF0,0xF2); } else { draw_text(i,y,"(dead)",0xF0,0xF4); } break; } } static inline void exam_flags(int y,Uint16 v) { y=(y-exam_scroll)*8; if(y<0 || y>screen->h-8) return; draw_text(0,y,"Flags:",0xF0,0xF7); draw_text(200,y,"--- --- --- --- --- --- --- --- --- --- --- --- --- ---",0xF0,0xF8); if(v&OF_INVISIBLE) draw_text(200,y,"Inv",0xF0,0xFF); if(v&OF_VISUALONLY) draw_text(232,y,"Vis",0xF0,0xFF); if(v&OF_STEALTHY) draw_text(264,y,"Stl",0xF0,0xFF); if(v&OF_BUSY) draw_text(296,y,"Bus",0xF0,0xFF); if(v&OF_USERSTATE) draw_text(328,y,"Ust",0xF0,0xFF); if(v&OF_USERSIGNAL) draw_text(360,y,"Usg",0xF0,0xFF); if(v&OF_MOVED) draw_text(392,y,"Mov",0xF0,0xFF); if(v&OF_DONE) draw_text(424,y,"Don",0xF0,0xFF); if(v&OF_KEYCLEARED) draw_text(456,y,"Key",0xF0,0xFF); if(v&OF_DESTROYED) draw_text(488,y,"Des",0xF0,0xFF); if(v&OF_BIZARRO) draw_text(520,y,"Biz",0xF0,0xFF); if(v&OF_CONNECTION) draw_text(552,y,"Con",0xF0,0xFF); if(v&OF_MOVING) draw_text(584,y,"Mvi",0xF0,0xFF); if(v&OF_ORDERED) draw_text(616,y,"Ord",0xF0,0xFF); } static inline void exam_hardsharp(const char*t,int y,Uint16*v) { int i; char buf[16]; y=(y-exam_scroll)*8; if(y<0 || y>screen->h-8) return; draw_text(0,y,t,0xF0,0xF7); for(i=0;i<4;i++) { snprintf(buf,8,"%5u",v[i]); draw_text(200+i*56,y,buf,0xF0,v[i]?0xFF:0xF8); } } static void draw_back_line(int y,int c) { unsigned char*p=screen->pixels; int i; p+=screen->pitch*y; for(i=0;i<screen->w;i++) if(p[i]==0xF0 || p[i]==0xF1) p[i]=i&1?c:0xF0; } static void examine(Uint32 n) { sqlite3_stmt*st; SDL_Event ev; SDL_Rect r; Object*o; int i,y; y=0; i=sqlite3_prepare_v2(userdb,"SELECT '%'||`NAME`,`ID`&0xFFFF FROM `VARIABLES` WHERE `ID` BETWEEN ?1 AND (?1|0xFFFF) ORDER BY `ID`",-1,&st,0); if(i) fatal("SQL error (%d): %s",i,sqlite3_errmsg(userdb)); object: if(n==VOIDLINK) return; o=objects[n]; if(!o) return; sqlite3_bind_int(st,1,o->class<<16); exam_scroll=0; redraw: set_cursor(XC_arrow); r.x=r.y=0; r.w=screen->w; r.h=screen->h; SDL_FillRect(screen,&r,0xF0); SDL_LockSurface(screen); exam_value("Self:",0,OVALUE(n)); exam_value("Class:",1,CVALUE(o->class)); exam_value("Image:",2,NVALUE(o->image)); if(classes[o->class]->cflags&CF_QUIZ) goto quiz; exam_value("Dir:",3,NVALUE(o->dir)); exam_value("Misc1:",4,o->misc1); exam_value("Misc2:",5,o->misc2); exam_value("Misc3:",6,o->misc3); exam_value("Misc4:",7,o->misc4); exam_value("Misc5:",8,o->misc5); exam_value("Misc6:",9,o->misc6); exam_value("Misc7:",10,o->misc7); exam_value("Temperature:",11,NVALUE(o->temperature)); exam_flags(12,o->oflags); exam_value("Density:",13,NVALUE(o->density)); exam_value("Volume:",14,NVALUE(o->volume)); exam_value("Strength:",15,NVALUE(o->strength)); exam_value("Weight:",16,NVALUE(o->weight)); exam_value("Climb:",17,NVALUE(o->climb)); exam_value("Height:",18,NVALUE(o->height)); exam_value("Arrivals:",19,NVALUE(o->arrivals)); exam_value("Departures:",20,NVALUE(o->departures)); exam_value("Shape:",21,NVALUE(o->shape)); exam_value("Shovable:",22,NVALUE(o->shovable)); exam_value("Distance:",23,NVALUE(o->distance)); exam_value("Inertia:",24,NVALUE(o->inertia)); exam_hardsharp("Hardness:",25,o->hard); exam_hardsharp("Sharpness:",26,o->sharp); exam_value("NextR:",27,o->replacement); while(sqlite3_step(st)==SQLITE_ROW) { i=sqlite3_column_int(st,1); exam_value(sqlite3_column_text(st,0),i+29,o->uservars[i]); } quiz: sqlite3_reset(st); SDL_UnlockSurface(screen); SDL_Flip(screen); while(SDL_WaitEvent(&ev)) switch(ev.type) { case SDL_KEYDOWN: switch(ev.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_RETURN: case SDLK_KP_ENTER: sqlite3_finalize(st); return; case SDLK_UP: if(exam_scroll) exam_scroll--; break; case SDLK_DOWN: exam_scroll++; break; case SDLK_HOME: exam_scroll=0; break; case SDLK_PAGEUP: exam_scroll-=screen->h/8; if(exam_scroll<0) exam_scroll=0; break; case SDLK_PAGEDOWN: exam_scroll+=screen->h/8; break; case SDLK_F1: case SDLK_g: if(classes[o->class]->gamehelp) modal_draw_popup(classes[o->class]->gamehelp); break; case SDLK_F2: case SDLK_h: if(classes[o->class]->edithelp) modal_draw_popup(classes[o->class]->edithelp); break; case SDLK_1: case SDLK_4: if(o->misc1.t==TY_LEVELSTRING) modal_draw_popup(value_string_ptr(o->misc1)); break; case SDLK_2: case SDLK_5: if(o->misc2.t==TY_LEVELSTRING) modal_draw_popup(value_string_ptr(o->misc2)); break; case SDLK_3: case SDLK_6: if(o->misc3.t==TY_LEVELSTRING) modal_draw_popup(value_string_ptr(o->misc3)); break; } goto redraw; case SDL_MOUSEMOTION: if(ev.motion.y!=y && ev.motion.y<screen->h) { SDL_LockSurface(screen); draw_back_line(y,0xF0); draw_back_line(y=ev.motion.y,0xF1); SDL_UnlockSurface(screen); SDL_Flip(screen); } break; case SDL_VIDEOEXPOSE: goto redraw; case SDL_QUIT: exit(0); break; } } static void global_examine(void) { sqlite3_stmt*st; SDL_Event ev; SDL_Rect r; int i,y; y=0; i=sqlite3_prepare_v2(userdb,"SELECT '@'||`NAME`,`ID` FROM `VARIABLES` WHERE `ID` BETWEEN 0x0000 AND 0xFFFF ORDER BY `ID`",-1,&st,0); if(i) fatal("SQL error (%d): %s",i,sqlite3_errmsg(userdb)); exam_scroll=0; redraw: set_cursor(XC_arrow); r.x=r.y=0; r.w=screen->w; r.h=screen->h; SDL_FillRect(screen,&r,0xF0); SDL_LockSurface(screen); exam_value("MoveNumber:",0,NVALUE(move_number)); exam_value("LevelStrings:",1,NVALUE(nlevelstrings)); exam_value("Generation:",2,NVALUE(generation_number)); while(sqlite3_step(st)==SQLITE_ROW) { i=sqlite3_column_int(st,1); exam_value(sqlite3_column_text(st,0),i+4,globals[i]); } sqlite3_reset(st); SDL_UnlockSurface(screen); SDL_Flip(screen); while(SDL_WaitEvent(&ev)) switch(ev.type) { case SDL_KEYDOWN: switch(ev.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_RETURN: case SDLK_KP_ENTER: sqlite3_finalize(st); return; case SDLK_UP: if(exam_scroll) exam_scroll--; break; case SDLK_DOWN: exam_scroll++; break; case SDLK_HOME: exam_scroll=0; break; case SDLK_PAGEUP: exam_scroll-=screen->h/8; if(exam_scroll<0) exam_scroll=0; break; case SDLK_PAGEDOWN: exam_scroll+=screen->h/8; break; } goto redraw; case SDL_MOUSEMOTION: if(ev.motion.y!=y && ev.motion.y<screen->h) { SDL_LockSurface(screen); draw_back_line(y,0xF0); draw_back_line(y=ev.motion.y,0xF1); SDL_UnlockSurface(screen); SDL_Flip(screen); } break; case SDL_VIDEOEXPOSE: goto redraw; case SDL_QUIT: exit(0); break; } } static void list_objects_at(int xy,Uint32*pf,const char*s) { static const char*const dirs[8]={"E ","NE","N ","NW","W ","SW","S ","SE"}; SDL_Event ev; SDL_Rect r; char buf[256]; int scroll=0; int count=0; Uint32 n,t; Object*o; int i,j; if(xy<0 || xy>=64*64) return; n=pf[xy]; if(n==VOIDLINK) return; while(n!=VOIDLINK) t=n,count++,n=objects[n]->up; redraw: r.x=r.y=0; r.w=screen->w; r.h=screen->h; SDL_FillRect(screen,&r,0xF1); r.y=8; r.h-=8; scrollbar(&scroll,r.h/8,count,0,&r); snprintf(buf,255," %d %sobjects at (%d,%d): ",count,s,(xy&63)+1,(xy/64)+1); SDL_LockSurface(screen); draw_text(0,0,buf,0xF7,0xF0); n=t; for(i=0;i<scroll && n!=VOIDLINK;i++) n=objects[n]->down; for(i=0;i<screen->h/8 && n!=VOIDLINK;i++) { o=objects[n]; snprintf(buf,255," %8d: %-14.14s %3d %s",n,classes[o->class]->name,o->image,classes[o->class]->cflags&CF_QUIZ?"":dirs[o->dir&7]); draw_text(24,r.y,buf,0xF1,o->generation?(classes[o->class]->cflags&CF_PLAYER?0xFE:0xFF):0xF8); n=o->down; r.y+=8; } SDL_UnlockSurface(screen); SDL_Flip(screen); while(SDL_WaitEvent(&ev)) { if(ev.type!=SDL_VIDEOEXPOSE) { r.h=screen->h-8; r.x=0; r.y=8; if(scrollbar(&scroll,r.h/8,count,&ev,&r)) goto redraw; } switch(ev.type) { case SDL_MOUSEBUTTONDOWN: if(ev.button.button!=1 || ev.button.y<8) break; j=ev.button.y/8-scroll-1; if(j>=count) break; n=t; for(i=0;i<j;i++) n=objects[n]->down; examine(n); set_cursor(XC_draft_small); goto redraw; case SDL_MOUSEMOTION: set_cursor(XC_draft_small); break; case SDL_KEYDOWN: switch(ev.key.keysym.sym) { case SDLK_ESCAPE: case SDLK_RETURN: case SDLK_KP_ENTER: return; } goto redraw; case SDL_VIDEOEXPOSE: goto redraw; case SDL_QUIT: exit(0); break; } } } static void describe_at(int xy) { unsigned char*s; Uint32 n; if(xy<0 || xy>=64*64) return; n=playfield[xy]; if(n==VOIDLINK) return; while(n!=VOIDLINK && objects[n]->up!=VOIDLINK) n=objects[n]->up; while(n!=VOIDLINK && !classes[objects[n]->class]->gamehelp) n=objects[n]->down; if(n==VOIDLINK) return; if(classes[objects[n]->class]->gamehelp[0]==16 && !classes[objects[n]->class]->gamehelp[1]) { if(objects[n]->misc1.t!=TY_LEVELSTRING || objects[n]->misc1.u>=nlevelstrings) return; modal_draw_popup(levelstrings[objects[n]->misc1.u]); return; } s=sqlite3_mprintf("\x0C\x0E%s:%d\\ %s\x0B\x0F%s",classes[objects[n]->class]->name,objects[n]->image,classes[objects[n]->class]->name,classes[objects[n]->class]->gamehelp); if(!s) fatal("Allocation failed\n"); modal_draw_popup(s); sqlite3_free(s); } static void describe_inventory(int n) { unsigned char*s; if(n<0 || n>=ninventory) return; if(!classes[inventory[n].class]->gamehelp) return; if(classes[inventory[n].class]->gamehelp[0]==16) return; s=sqlite3_mprintf("\x0C\x0E%s:%d\\ %s\x0B\x0F%s",classes[inventory[n].class]->name,inventory[n].image,classes[inventory[n].class]->name,classes[inventory[n].class]->gamehelp); if(!s) fatal("Allocation failed\n"); modal_draw_popup(s); sqlite3_free(s); } static void do_import_moves(const char*arg) { FILE*fp; int i; if(!arg || !arg[strspn(arg," \t")]) return; fp=popen(arg,"r"); if(!fp) { screen_message("Unable to open pipe for reading"); return; } replay_mark=0; decode_move_list(fp); pclose(fp); } static void do_export_moves(const char*arg) { FILE*fp; int i; if(!arg || !arg[strspn(arg," \t")]) return; fp=popen(arg,"w"); if(!fp) { screen_message("Unable to open pipe for writing"); return; } encode_move_list(fp); pclose(fp); } static void do_load_moves(sqlite3_stmt*st) { FILE*fp; int i=sqlite3_column_bytes(st,1); fp=fmemopen((char*)sqlite3_column_blob(st,1)?:"",i,"r"); if(!fp) fatal("Allocation failed\n"); decode_move_list(fp); fclose(fp); } static int copy_text_to_plain(unsigned char*out,int maxlen,const unsigned char*in) { int at=0; if(!in) { *out=0; return 0; } while(*in && at<maxlen) switch(*in) { case 10: if(at && out[at-1]!=32) out[at++]=32; in++; break; case 14: case 30: in=strchrnul(in,'\\'); if(*in) in++; break; case 31: in++; if(*in) out[at++]=*in++; break; case 32 ... 255: out[at++]=*in++; break; default: in++; } out[at]=0; return at; } static inline void levels_column(int x,int y,int n,int bg,sqlite3_stmt*st,char*buf) { const DisplayColumn*dc=ll_disp+n; Uint8 co=dc->color; int w=dc->width; int nc=dc->data+(dc->flag&4?0:8); int t=sqlite3_column_type(st,nc); int a=0; int i; const char*p; sqlite3_int64 v; if(t==SQLITE_NULL) return; if(dc->flag&1) w=255; if(t==SQLITE_BLOB || t==SQLITE_TEXT) { blob: if(p=sqlite3_column_text(st,nc)) i=snprintf(buf,w,"%s",p); else *buf=i=0; if(dc->form[0]=='R' && i<w) a=w-i; if(dc->flag&2) co=0xFF; } else { // This implementation does not check that the format is necessarily valid. // You should not rely on the use of any undocumented format. v=sqlite3_column_int64(st,nc); if(dc->flag&2) { co=0xFF; for(i=0;i<dc->color;i++) if(ll_code[dc->ptr+i]>=0xFF00 || v+128<=(ll_code[dc->ptr+i]>>8)) { co=ll_code[dc->ptr+i]&0xFF; break; } } switch(dc->form[0]) { case 'L': case 'R': if(dc->form[1]) { if(v<0 || v>w) v=w; memset(buf,dc->form[1],v); buf[v]=0; if(dc->form[0]=='R') a=w-v; } else { goto blob; } break; case 'd': snprintf(buf,w+1,"%*lld",w,(long long)(dc->form[1] && v<0?-v:v)); if(dc->form[1]=='-') *buf=v<0?'-':' '; if(dc->form[1]=='+') *buf=v<0?'-':'+'; break; case 'o': snprintf(buf,w+1,dc->form[1]?"%0*llo":"%*llo",w,(unsigned long long)v); break; case 'u': snprintf(buf,w+1,dc->form[1]?"%0*llu":"%*llu",w,(unsigned long long)v); break; case 'x': snprintf(buf,w+1,dc->form[1]?"%0*llx":"%*llx",w,(unsigned long long)v); break; case 'X': snprintf(buf,w+1,dc->form[1]?"%0*llX":"%*llX",w,(unsigned long long)v); break; default: *buf=0; } } draw_text(a*8+x,y,buf,bg,co); } static int list_levels(void) { static Sint8 mo=-1; static Uint8 columns=0; static int scroll=0; static Uint16 divmin,divmax; // 1 less than level order number sqlite3_stmt*st; SDL_Event ev; SDL_Rect r; const char*v; int scrmax=0; int sel=level_ord-1; int b,i,j,x,y; char buf[256]; char rescroll=2; Uint16 ndiv; if(mo<0) { optionquery[1]=Q_listMode; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; mo=strtol(v,0,16); if(mo<0) mo=0; } if(!columns) { optionquery[1]=Q_listColumns; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; i=strtol(v,0,10); j=(screen->w-16)/070; if(i<1) i=j; if(i>j) i=j; if(i>255) i=255; columns=i; } if(!ll_head) ll_head="\xB3W \xB3H \xB3 TITLE"; // ID, ORD, CODE, WIDTH, HEIGHT, TITLE, SOLVED, SOLVABLE, ... prepare: if(sqlite3_prepare_v2(userdb,mo&4 ?"WITH D(F,L,H,R) AS (SELECT `FIRST`, LEAD(`FIRST`,1,MAX_LEVEL()+1) OVER (ORDER BY `FIRST`,_ROWID_), `HEADING`, _ROWID_ FROM `DIVISIONS`)" " SELECT F, L, H, PRINTF('(%d/%d)',(SELECT COUNT() FROM `LEVELS` WHERE `ORD` >= F AND `ORD` < L AND `SOLVED`),L-F) FROM D WHERE F <> L ORDER BY F, R;" :"SELECT * FROM `LEVELS` WHERE `ORD` NOT NULL AND `ORD` >= ?1 AND `ORD` < ?2 ORDER BY `ORD`;" ,-1,&st,0)) { screen_message(sqlite3_errmsg(userdb)); return 0; } if(mo&4) { ndiv=0; while(sqlite3_step(st)==SQLITE_ROW) ndiv++; if(!ndiv) { mo&=3; sqlite3_finalize(st); goto prepare; } } set_cursor(XC_arrow); redraw: if(sel<0) sel=0; if(sel>=level_nindex) sel=level_nindex-1; if((mo&8) && sel>=divmax-divmin) sel=divmax-divmin-1; SDL_FillRect(screen,0,0x02); r.x=r.y=0; r.w=screen->w; r.h=24; SDL_FillRect(screen,&r,0xF7); SDL_LockSurface(screen); draw_text(0,0,"<LMB/\x18\x19\x1A\x1B> Select <MMB/SP> Title <RMB/RET> Play <0-9> Find <ESC> Cancel",0xF7,0xF0); draw_text(0,8,"<F1> Wide/Tall <F2> ID/Ord <F3> Divisions <F4> All",0xF7,0xF0); if(mo&8) draw_text(0,16,"\x15",0xF7,0xF0); reset: sqlite3_reset(st); sqlite3_bind_int(st,2,mo&8?divmax:level_nindex+1); if(mo&4) { scrmax=ndiv; for(i=0;i<scroll;i++) if(sqlite3_step(st)!=SQLITE_ROW) sqlite3_reset(st),scroll=i-1; for(j=0,y=24;y<screen->h-7;y+=8,j++) { i=sqlite3_step(st); if(i!=SQLITE_ROW) break; if(rescroll==2 && sel>=sqlite3_column_int(st,0) && sel<sqlite3_column_int(st,1)) { found: sel=j; rescroll=1; goto reset; } if(j==sel) { divmin=sqlite3_column_int(st,0); divmax=sqlite3_column_int(st,1); r.x=16; r.y=y; r.w=screen->w-16; r.h=8; SDL_FillRect(screen,&r,0xF8); draw_text(2*8,y,"\x10",b=0xF8,0xFE); snprintf(buf,100,"#%d (%d - %d)",sel+1,divmin,divmax-1); draw_text(80,16,buf,0xF7,0xFE); } else { draw_text(2*8,y,"\xB3",b=0x02,0xF8); } if(v=sqlite3_column_text(st,2)) { x=sqlite3_column_bytes(st,2); draw_text(4*8,y,v,b,0xFF); if(v=sqlite3_column_text(st,3)) draw_text((x+5)*8,y,v,b,0xFB); } } if(rescroll) { if(rescroll==2 && i==SQLITE_ROW) { while((i=sqlite3_step(st))==SQLITE_ROW) { j++; if(sel>=sqlite3_column_int(st,0) && sel<sqlite3_column_int(st,1)) goto found; } } if(sel<scroll) scroll=sel; if(sel>=scroll+screen->h/8-3) scroll=sel+4-screen->h/8; rescroll=0; } if(y<screen->h-7 && sel>=j) { sel=j-1; goto reset; } } else if(mo&1) { scrmax=mo&8?(divmax-divmin):level_nindex; draw_text(16+060,16,ll_head,0xF7,0xF1); draw_text(16,16,mo&2?"\xB3 ID ":"\xB3 ORD ",0xF7,0xF1); if(rescroll) { if(sel<scroll) scroll=sel; if(sel>=scroll+screen->h/8-3) scroll=sel+4-screen->h/8; rescroll=0; } sqlite3_bind_int(st,1,scroll+(mo&8?divmin:1)); for(y=24;y<screen->h-7;y+=8) { if(sqlite3_step(st)!=SQLITE_ROW) break; i=sqlite3_column_int(st,1); if(mo&8) i-=divmin-1; if(i-1==sel) { r.x=16; r.y=y; r.w=screen->w-16; r.h=8; SDL_FillRect(screen,&r,0xF8); draw_text(2*8,y,"\x10",b=0xF8,0xFE); } else { draw_text(2*8,y,"\xB3",b=0x02,0xF8); } if(mo&8) i+=divmin-1; snprintf(buf,6,"%5u",mo&2?sqlite3_column_int(st,0):i); draw_text(3*8,y,buf,b,sqlite3_column_int(st,6)?0xFA:0xFC); draw_text(8*8,y,"\xB3",b,b^0xFA); if(ll_ndisp) { x=9*8; for(i=0;i<ll_ndisp && x<screen->w;i++) { levels_column(x,y,i,b,st,buf); x+=ll_disp[i].width*8+8; if(x<screen->w && !(ll_disp[i].flag&1)) draw_text(x-8,y,"\xB3",b,b^0xFA); } } else { snprintf(buf,6,"%2u %2u",sqlite3_column_int(st,3),sqlite3_column_int(st,4)); draw_text(9*8,y,buf,b,0xFF); draw_text(11*8,y,"\xB3",b,b^0xFA); draw_text(14*8,y,"\xB3",b,b^0xFA); copy_text_to_plain(buf,255,sqlite3_column_text(st,5)); draw_text(15*8,y,buf,b,0xFF); } } } else { scrmax=((mo&8?divmax-divmin:level_nindex)+columns-1)/columns; draw_text(8,16,mo&2?"(ID)":"(Ord)",0xF7,0xF1); if(rescroll) { if(sel<scroll*columns) scroll=sel/columns; if(sel>(scroll+screen->h/8-3)*columns) scroll=(sel+3-screen->h/8)/columns; rescroll=0; } sqlite3_bind_int(st,1,scroll*columns+(mo&8?divmin:1)); for(y=24;y<screen->h-7;y+=8) for(x=0;x<columns;x++) { if(sqlite3_step(st)!=SQLITE_ROW) goto done; i=sqlite3_column_int(st,1); if(mo&8) i-=divmin-1; if(i-1==sel) { draw_text(x*070+16,y,"\x10",b=0xF8,0xFE); snprintf(buf,128,"[ ID=%u Ord=%u Code=%u Size=%ux%u ]",sqlite3_column_int(st,0),i,sqlite3_column_int(st,2),sqlite3_column_int(st,3),sqlite3_column_int(st,4)); draw_text(80,16,buf,0xF7,0xFE); } else { draw_text(x*070+16,y," ",b=0x02,0x02); } if(mo&8) i+=divmin-1; snprintf(buf,7,"%5u ",mo&2?sqlite3_column_int(st,0):i); draw_text(x*070+24,y,buf,b,sqlite3_column_int(st,6)?0xFA:0xFC); if(!sqlite3_column_int(st,7)) draw_text(x*070+64,y,"\a",b,b==0xF8?0xF1:0xF9); } } done: SDL_UnlockSurface(screen); r.x=r.w=0; r.y=24; r.h=screen->h-24; scrollbar(&scroll,screen->h/8-3,scrmax,0,&r); SDL_Flip(screen); sqlite3_reset(st); while(SDL_WaitEvent(&ev)) { if(ev.type!=SDL_VIDEOEXPOSE && scrollbar(&scroll,screen->h/8-3,scrmax,&ev,&r)) goto redraw; switch(ev.type) { case SDL_MOUSEMOTION: set_cursor(XC_arrow); break; case SDL_KEYDOWN: if((ev.key.keysym.mod&KMOD_NUM) && ev.key.keysym.sym>=SDLK_KP0 && ev.key.keysym.sym<=SDLK_KP9) goto digit; switch(ev.key.keysym.sym) { case SDLK_ESCAPE: i=0; goto final; //TODO: Change the scroll to approximately the middle, instead of zero case SDLK_F1: scroll=0; mo^=1; rescroll=1; goto redraw; case SDLK_F2: mo^=2; goto redraw; case SDLK_F3: mo&=~8; if(mo&4) break; mo|=4; rescroll=2; sqlite3_finalize(st); goto prepare; case SDLK_F4: if(mo&8) { sel+=divmin-1; divmin=0; } if(mo&4) { sqlite3_finalize(st); mo&=3; sel=level_ord-1; goto prepare; } mo&=3; goto redraw; case SDLK_SPACE: title: if(mo&4) break; sqlite3_reset(st); sqlite3_bind_int(st,1,sel+1); if(sqlite3_step(st)==SQLITE_ROW) { v=sqlite3_column_text(st,5); // not Unicode, but add the null terminator if(v) modal_draw_popup(v); } goto redraw; case SDLK_0 ... SDLK_9: digit: if(mo&4) break; SDL_PushEvent(&ev); if(v=screen_prompt("Find?")) { if(!*v) goto redraw; i=strtol(v,0,10); if(mo&2) { for(j=0;j<level_nindex;j++) if(level_index[j]==i) sel=j; } else { if(i>0 && i<=level_nindex) sel=i-1; } rescroll=1; } goto redraw; case SDLK_DOWN: case SDLK_KP2: case SDLK_j: sel+=mo&5?1:columns; rescroll=1; goto redraw; case SDLK_UP: case SDLK_KP8: case SDLK_k: sel-=mo&5?1:columns; rescroll=1; goto redraw; case SDLK_LEFT: case SDLK_KP4: case SDLK_h: sel--; rescroll=1; goto redraw; case SDLK_RIGHT: case SDLK_KP6: case SDLK_l: sel++; rescroll=1; goto redraw; case SDLK_PAGEUP: case SDLK_KP9: sel-=(screen->h/8-3)*(mo&5?1:columns); scroll-=(screen->h/8-3)*(mo&5?1:columns); rescroll=1; goto redraw; case SDLK_PAGEDOWN: case SDLK_KP3: sel+=(screen->h/8-3)*(mo&5?1:columns); scroll+=(screen->h/8-3)*(mo&5?1:columns); rescroll=1; goto redraw; case SDLK_HOME: case SDLK_KP7: sel=scroll=0; rescroll=1; goto redraw; case SDLK_END: sel=mo&8?divmax-divmin-1:level_nindex-1; rescroll=1; goto redraw; case SDLK_RETURN: case SDLK_KP_ENTER: play: if(mo&4) { mo=(mo|8)&~4; sel=0; sqlite3_finalize(st); goto prepare; } i=1; goto final; } break; case SDL_MOUSEBUTTONDOWN: if(ev.button.x>=16 && ev.button.y>=24) { if(mo&1) i=scroll+ev.button.y/8-3; else i=(scroll+ev.button.y/8-3)*columns+(ev.button.x-16)/070; if(i>=0 && i<level_nindex) sel=i; if(ev.button.button==2) goto title; if(ev.button.button==3) goto play; goto redraw; } break; case SDL_VIDEOEXPOSE: goto redraw; case SDL_QUIT: exit(0); break; } } i=-1; final: sqlite3_finalize(st); if(i==1) begin_level(mo&8?-sel-divmin:~sel); return i; } static int game_command(int prev,int cmd,int number,int argc,sqlite3_stmt*args,void*aux) { switch(cmd) { case '\' ': play: // Play a move if(replay_time) { replay_time=0; return -3; } if(solution_replay) { screen_message("You cannot play your own moves during the solution replay"); return -3; } if(inputs_count*MSIZ>=inputs_size) { inputs=realloc(inputs,inputs_size+=32*MSIZ); if(!inputs) fatal("Allocation failed\n"); } inputs[inputs_count++]=number; return 0; case '+ ': replay: // Replay saved_inserting=inserting; inserting=0; replay_time=0; if(number>replay_count-replay_pos) number=replay_count-replay_pos; if(number<=0) return prev; if((inputs_count+number)*MSIZ>=inputs_size) { inputs=realloc(inputs,inputs_size+=(number+1)*MSIZ); if(!inputs) fatal("Allocation failed\n"); } memcpyM(inputs+inputs_count,replay_list+replay_pos,number); inputs_count+=number; return 0; case '- ': // Rewind saved_inserting=inserting; number=replay_pos-number; if(number<0) number=0; //fallthru case '= ': restart: // Restart begin_level(level_id); if(!number) return 1; if(number>replay_count) number=replay_count; if(number*MSIZ>=inputs_size) { inputs=realloc(inputs,inputs_size=(number+1)*MSIZ); if(!inputs) fatal("Allocation failed\n"); } memcpyM(inputs,replay_list,inputs_count=number); no_dead_anim=1; return 1; case '^<': // Rewind to mark number=replay_mark; goto restart; case '^>': // Replay to mark inputs_count=0; number=replay_mark-replay_pos; goto replay; case '^-': // Delete move inputs_count=0; if(solution_replay) { screen_message("You cannot delete moves during the solution replay"); return -3; } if(replay_pos==replay_count) return 0; memmoveM(replay_list+replay_pos,replay_list+replay_pos+1,replay_count-replay_pos-1); replay_count--; if(replay_mark>replay_pos) replay_mark--; return 0; case '^+': // Insert moves if(solution_replay) return 0; inputs_count=0; inserting^=1; return 0; case '^E': // Edit return main_options['r']?1:-2; case '^I': // Toggle inventory display side_mode^=1; return prev; case '^L': // List levels return list_levels(); case '^M': // Mark replay position replay_mark=replay_pos+inputs_count; return prev; case '^Q': // Quit return -1; case '^S': // Save solution if(gameover==1) record_solution(); return 1; case '^T': // Show title modal_draw_popup(level_title); return prev; case '^Y': // Sound test sound_test(); return prev; case '^d': // Describe object describe_at(number-65); return prev; case '^g': // Display global variables global_examine(); return prev; case '^n': // List objects (bizarro) list_objects_at(number-65,bizplayfield,"bizarro "); return prev; case '^o': // List objects list_objects_at(number-65,playfield,""); return prev; case '^p': // Slow replay replay_time=replay_time?0:1; return 0; case '^s': // Toggle solution replay inserting=0; if(replay_count) save_replay(); solution_replay^=1; if(replay_count) replay_count=0,begin_level(level_id); else load_replay(); return 1; case '^x': // Cancel dead animation ndeadanim=0; return prev; case 'go': // Select level begin_level(number); return 1; case 'lo': // Locate me locate_me(number&63?:64,number/64?:64); return prev; case 'mi': // Move list import if(argc<2 || solution_replay) break; if(replay_pos) begin_level(level_id); do_import_moves(sqlite3_column_text(args,1)); return 1; case 'ml': // Move list load if(argc<2 || solution_replay) break; if(replay_pos) begin_level(level_id); do_load_moves(args); return 1; case 'mx': // Move list export if(argc<2) break; do_export_moves(sqlite3_column_text(args,1)); return 0; case 'rs': // Replay speed number+=replay_speed; if(number<1) number=1; else if(number>255) number=255; replay_speed=number; return prev; case 'xy': // Coordinate input if(argc<3 || !has_xy_input) break; argc=sqlite3_column_int(args,1); number=sqlite3_column_int(args,2); if(argc<1 || argc>pfwidth || number<1 || number>pfheight) return 0; number=(number-1)|((argc-1)<<6)|0x8000; goto play; default: return prev; } } static void do_autowin(void) { const char*name; int i,j,k; int prev=0; sqlite3_reset(autowin); if(sqlite3_bind_parameter_count(autowin)) { for(i=sqlite3_bind_parameter_count(autowin);i;--i) if(name=sqlite3_bind_parameter_name(autowin,i)) { if(*name=='$') { if(!sqlite3_stricmp(name+1,"LEVEL")) { sqlite3_bind_int(autowin,i,level_ord); } else if(!sqlite3_stricmp(name+1,"LEVEL_ID")) { sqlite3_bind_int(autowin,i,level_id); } } } } while((i=sqlite3_step(autowin))==SQLITE_ROW) { if(i=sqlite3_data_count(autowin)) { j=(i>1&&sqlite3_column_type(autowin,1)!=SQLITE_NULL)?sqlite3_column_int(autowin,1):0; if((name=sqlite3_column_text(autowin,0)) && *name) { if(name[0]==':') { switch(name[1]) { case '!': if(i>1) i=system(sqlite3_column_text(autowin,1)?:(const unsigned char*)"# "); break; case ';': goto done; case '?': if(i>1) puts(sqlite3_column_text(autowin,1)?:(const unsigned char*)"(null)"); break; case 'm': if(i>1) screen_message(sqlite3_column_text(autowin,1)?:(const unsigned char*)"(null)"); break; } } else { k=name[0]*'\1\0'+name[1]*'\0\1'; while(i && sqlite3_column_type(autowin,i-1)==SQLITE_NULL) i--; prev=game_command(prev,k,j,i,autowin,0); if(prev<0) goto done; } } } } if(i!=SQLITE_DONE) screen_message("SQL error"); done: sqlite3_reset(autowin); } static void set_caption(void) { const char*r; char*s; sqlite3_str*m; int c; optionquery[1]=Q_gameTitle; r=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:"Free Hero Mesh - ~ - Game"; m=sqlite3_str_new(0); c=strcspn(r,"~"); sqlite3_str_append(m,r,c); if(r[c]=='~') { sqlite3_str_appendall(m,basefilename); sqlite3_str_appendall(m,r+c+1); } s=sqlite3_str_finish(m); if(s) SDL_WM_SetCaption(s,s); else SDL_WM_SetCaption("Free Hero Mesh","Free Hero Mesh"); sqlite3_free(s); } static Uint32 timer_callback(Uint32 n) { if(!timerflag) { static SDL_Event ev={SDL_USEREVENT}; SDL_PushEvent(&ev); } timerflag=1; return n; } static inline void input_move(MoveItem k) { const char*t=execute_turn(k); if(replay_pos>0xFFFE && !gameover) t="Too many moves played"; if(t) { screen_message(t); gameover=-1; return; } if(!key_ignored) { if(inserting) { if(replay_pos>=0xFFFE || replay_pos==replay_count) { inserting=0; } else { if(replay_count>0xFFFE) replay_count=0xFFFE; if(replay_size<0x10000*MSIZ) { replay_list=realloc(replay_list,replay_size=0x10000*MSIZ); if(!replay_list) fatal("Allocation failed\n"); } memmoveM(replay_list+replay_pos+1,replay_list+replay_pos,replay_count-replay_pos); replay_count++; } } if(replay_pos*MSIZ>=replay_size) { replay_list=realloc(replay_list,replay_size+=0x200*MSIZ); if(!replay_list) fatal("Allocation failed\n"); } replay_list[replay_pos++]=k; if(replay_pos>replay_count) replay_count=replay_pos; } } static void record_solution(void) { const char*v; const char*com; FILE*fp; Uint8 flag; long n; unsigned char*buf=0; size_t sz=0; if(solution_replay) return; if(buf=read_lump(FIL_SOLUTION,level_id,&n)) { if(n<3 || (buf[0]|(buf[1]<<8))!=level_version || (buf[2]&~0x83)) goto dontkeep; n-=3; if((buf[2]&128) && n>4) { Sint32 sco=buf[3]|(buf[4]<<8)|(buf[5]<<16)|(buf[6]<<24); if(gameover_score!=NO_SCORE && sco<=gameover_score) goto dontkeep; } else { if(buf[2]&1) n-=strnlen(buf+3,n); if(buf[2]&2) n-=8; if(n<=0 || n>replay_pos) goto dontkeep; } free(buf); return; dontkeep: free(buf); buf=0; } optionquery[1]=Q_solutionComment; com=xrm_get_resource(resourcedb,optionquery,optionquery,2); if(com && !*com) com=0; optionquery[1]=Q_solutionTimestamp; v=xrm_get_resource(resourcedb,optionquery,optionquery,2)?:""; flag=0; if(com) flag|=1; if(boolxrm(v,0)) flag|=2; if(gameover_score!=NO_SCORE) flag|=128; fp=open_memstream((char**)&buf,&sz); if(!fp) fatal("Allocation failed\n"); fputc(level_version,fp); fputc(level_version>>8,fp); fputc(flag,fp); if(flag&128) { fputc(gameover_score,fp); fputc(gameover_score>>8,fp); fputc(gameover_score>>16,fp); fputc(gameover_score>>24,fp); } if(flag&1) fwrite(com,1,strlen(com+1),fp); if(flag&2) { time_t t=time(0); fputc(t>>000,fp); fputc(t>>010,fp); fputc(t>>020,fp); fputc(t>>030,fp); #ifdef CONFIG_USING_32BIT_TIMESTAMPS fputc(0,fp); fputc(0,fp); fputc(0,fp); fputc(0,fp); #else fputc(t>>040,fp); fputc(t>>050,fp); fputc(t>>060,fp); fputc(t>>070,fp); #endif } n=replay_count; replay_count=replay_pos; encode_move_list(fp); replay_count=n; fclose(fp); if(!buf) fatal("Allocation failed\n"); write_lump(FIL_SOLUTION,level_id,sz,buf); free(buf); sqlite3_exec(userdb,"UPDATE `LEVELS` SET `SOLVABLE` = 1 WHERE `ID` = LEVEL_ID();",0,0,0); } static void record_private_solution(void) { FILE*fp; char*buf=0; int n; if(solution_replay) return; if(gameover_score==NO_SCORE) gameover_score=replay_pos; if(best_list && best_score!=NO_SCORE && gameover_score>best_score) return; dum_size=0; fp=open_memstream(&buf,&dum_size); if(!fp) fatal("Allocation failed\n"); n=replay_count; replay_count=replay_pos; encode_move_list(fp); replay_count=n; fclose(fp); if(buf) { free(best_list); best_list=buf; best_score=gameover_score; } } void run_game(void) { int i; SDL_Event ev; set_caption(); replay_count=0; replay_time=0; if(side_mode==255) setup_game(); begin_level(level_id); redraw_game(); timerflag=0; SDL_SetTimer(10,timer_callback); while(SDL_WaitEvent(&ev)) { switch(ev.type) { case SDL_VIDEOEXPOSE: redraw_game(); break; case SDL_QUIT: goto quit; break; case SDL_MOUSEMOTION: show_mouse_xy(&ev); break; case SDL_USEREVENT: if(!gameover && !quiz_text) continue_animation(); timerflag=0; if(replay_time && !--replay_time && !game_command(1,'+ ',1,0,0,0)) { replay_time=replay_speed; goto replay; } break; case SDL_MOUSEBUTTONDOWN: if(ev.button.x<left_margin) { if(ev.button.y<48) break; if(side_mode) { // Inventory describe_inventory((ev.button.y-52)/picture_size); timerflag=0; redraw_game(); } else { // Move list i=(ev.button.y+4)/16-(screen->h-68)/32-4; if(i<0) game_command(0,'- ',-i,0,0,0); else if(i>0) game_command(0,'+ ',i,0,0,0); goto replay; } break; } else { i=exec_key_binding(&ev,0,(ev.button.x-left_margin)/picture_size+1,ev.button.y/picture_size+1,game_command,0); goto command; } case SDL_KEYDOWN: i=exec_key_binding(&ev,0,0,0,game_command,0); command: if(i==-1) goto quit; if(i==-2) { main_options['e']=1; SDL_SetTimer(0,0); save_replay(); return; } replay: if(inputs_count) { for(i=0;i<inputs_count && !gameover;i++) if(inputs[i]) input_move(inputs[i]); inputs_count=0; if(saved_inserting) inserting=1,saved_inserting=0; no_dead_anim=0; if(gameover==1) { if(should_record_solution) record_solution(); if(should_record_private_solution) record_private_solution(); if(!solution_replay && !solved) sqlite3_exec(userdb,"UPDATE `LEVELS` SET `SOLVED` = 1 WHERE `ID` = LEVEL_ID();",0,0,0); if(autowin) do_autowin(); } } redraw_game(); timerflag=0; // ensure we have not missed a timer event break; } } quit: SDL_SetTimer(0,0); save_replay(); exit(0); } void run_auto_test(void) { Uint8 rc=0; int lvl,pro,i,n; const char*t; no_dead_anim=1; setbuf(stdout,0); solution_replay=1; optionquery[1]=Q_progress; t=xrm_get_resource(resourcedb,optionquery,optionquery,2); pro=t?strtol(t,0,10):0; if(main_options['t']) pro=0; if(main_options['+']) { lvl=1; goto start2; } optionquery[1]=Q_level; t=xrm_get_resource(resourcedb,optionquery,optionquery,2); if(n=lvl=t?strtol(t,0,10):0) goto start; for(n=1;n<=level_nindex;n++) { if(lvl) break; start: if(pro<0) sqlite3_sleep(-pro); if(main_options['t']) printf("*** Level %d\n",n); else printf("Level %d",n); if(t=load_level(-n)) { printf(": Error during loading: %s\n",t); rc=1; continue; } start2: load_replay(); if(!replay_count) { printf(": Solution is absent, invalid, or for wrong version of this level\n"); rc=1; continue; } if(t=init_level()) { printf(": Error during initialization: %s\n",t); rc=1; continue; } if(gameover==-1) { printf(": Lose during initialization\n"); rc=1; continue; } for(i=0;i<replay_count;i++) { if(gameover) { printf(": Premature termination on move %d\n",i); rc=1; goto cont; } if(pro>0 && !(i%pro)) putchar('.'); if(t=execute_turn(replay_list[i])) { printf(": Error on move %d: %s\n",i+1,t); rc=1; goto cont; } if(gameover==-1) { printf(": Game loss on move %d\n",i+1); rc=1; goto cont; } } if(gameover<=0) { printf(": Game not terminated after %d moves\n",replay_count); rc=1; continue; } printf(": OK\n"); cont: ; } exit(rc); } void export_private_solutions(void) { int rc=0; const char*t; int i0,i1,i2,i,j,n; unsigned char*data; long sz; for(n=1;n<=level_nindex;n++) { level_id=level_index[n-1]; data=read_userstate(FIL_LEVEL,level_id,&sz); if(!data) continue; if(!sz || *data) { free(data); continue; } i0=i1=i2=0; for(i=1;i<sz;) { if(data[i]==0x02 && i+2<=sz) i0=i+1; else if(data[i]==0x41 && i+3<=sz) i1=i+1; else if(data[i]==0x81 && i+5<=sz) i2=i+1; if(data[i]<0x40) i+=strnlen(data+i,sz-i)+1; else if(data[i]<0x80) i+=3; else if(data[i]<0xC0) i+=5; else i+=9; } if(i0 && i1) { i=strnlen(data+i0,sz-i0); printf("%d.SOL",level_id); putchar(0); // null terminator of lump name j=i+(i2?7:3); putchar(j>>16); putchar(j>>24); putchar(j); putchar(j>>8); // lump data size putchar(data[i1]); putchar(data[i1+1]); // level version putchar(i2?0x80:0x00); // flag if(i2) fwrite(data+i2,1,4,stdout); // score fwrite(data+i0,1,i,stdout); // move list } free(data); } exit(rc); } void locate_me(int x,int y) { Uint8 c=7; SDL_Rect r,rh,rv; SDL_Event ev; if(!screen) return; redraw_game(); r.x=(x-1)*picture_size+left_margin; r.y=(y-1)*picture_size; r.w=r.h=picture_size; rh.x=0; rh.y=r.y+picture_size/2; rh.w=screen->w; rh.h=1; rv.x=r.x+picture_size/2; rv.y=0; rv.w=1; rv.h=screen->h; show: timerflag=0; SDL_FillRect(screen,&rh,c+45); SDL_FillRect(screen,&rv,c+67); SDL_FillRect(screen,&r,c); SDL_Flip(screen); while(SDL_WaitEvent(&ev)) switch(ev.type) { case SDL_USEREVENT: if(c>240) return; c+=15; goto show; case SDL_KEYDOWN: case SDL_QUIT: case SDL_MOUSEBUTTONDOWN: SDL_PushEvent(&ev); return; } }