#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;
// fall through
case 'rS': // Replay speed (absolute)
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;
}
}