commit a9f55d3076334f03f26be0413de3d3871dfc94ae Author: Gergely POLONKAI (W00d5t0ck) Date: Sat Sep 21 23:05:25 2013 +0200 Initial release diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e682bb1 --- /dev/null +++ b/Makefile @@ -0,0 +1,68 @@ +######################################## +# +# build parameters +# +######################################## + +# installation prefix +PREFIX=/usr/local + +######################################## +# +# do not modify rest of this file +# +######################################## + +OPTIMIZE=#-O6 -march=pentium4 -mfpmath=sse -fomit-frame-pointer -funroll-loops +PROFILER=#-pg +DEBUG=#-ggdb +CXXFLAGS=-pipe -Wall $(OPTIMIZE) $(DEBUG) `sdl-config --cflags` -DPREFIX=L\"$(PREFIX)\" $(PROFILER) +LNFLAGS=-pipe -lSDL_ttf -lfreetype `sdl-config --libs` -lz -lSDL_mixer $(PROFILER) +INSTALL=install + +TARGET=einstein + +SOURCES=puzgen.cpp main.cpp screen.cpp resources.cpp utils.cpp game.cpp \ + widgets.cpp iconset.cpp puzzle.cpp rules.cpp \ + verthins.cpp random.cpp horhints.cpp menu.cpp font.cpp \ + conf.cpp storage.cpp tablestorage.cpp regstorage.cpp \ + topscores.cpp opensave.cpp descr.cpp options.cpp messages.cpp \ + formatter.cpp buffer.cpp unicode.cpp convert.cpp table.cpp \ + i18n.cpp lexal.cpp streams.cpp tokenizer.cpp sound.cpp +OBJECTS=puzgen.o main.o screen.o resources.o utils.o game.o \ + widgets.o iconset.o puzzle.o rules.o verthints.o random.o \ + horhints.o menu.o font.o conf.o storage.o options.o \ + tablestorage.o regstorage.o topscores.o opensave.o descr.o \ + messages.o formatter.o buffer.o unicode.o convert.o table.o \ + i18n.o lexal.o streams.o tokenizer.o sound.o +HEADERS=screen.h main.h exceptions.h resources.h utils.h \ + widgets.h iconset.h puzzle.h verthints.h random.h horhints.h \ + font.h conf.h storage.h tablestorage.h regstorage.h \ + topscores.h opensave.h game.h descr.h options.h messages.h \ + foramtter.h buffer.h visitor.h unicode.h convert.h table.h \ + i18n.h lexal.h streams.h tokenizer.h sound.h + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< + +all: $(TARGET) + + +$(TARGET): $(OBJECTS) + $(CXX) $(LNFLAGS) $(OBJECTS) -o $(TARGET) + +clean: + rm -f $(OBJECTS) core* *core $(TARGET) *~ + +depend: + @makedepend $(SOURCES) 2> /dev/null + +run: $(TARGET) + ./$(TARGET) + +install: $(TARGET) + $(INSTALL) -s -D $(TARGET) $(PREFIX)/bin/$(TARGET) + $(INSTALL) -D einstein.res $(PREFIX)/share/einstein/res/einstein.res + +# DO NOT DELETE THIS LINE -- make depend depends on it. + diff --git a/Makefile.macosx b/Makefile.macosx new file mode 100644 index 0000000..70bcbec --- /dev/null +++ b/Makefile.macosx @@ -0,0 +1,63 @@ +######################################## +# +# build parameters +# +######################################## + +# installation prefix +PREFIX=/usr/local + +######################################## +# +# do not modify rest of this file +# +######################################## + +OPTIMIZE=#-O6 -march=pentium4 -mfpmath=sse -fomit-frame-pointer -funroll-loops +PROFILER=#-pg +DEBUG=-ggdb +CXXFLAGS=-pipe -Wall $(OPTIMIZE) $(DEBUG) -I/Library/Frameworks/SDL.framework/Headers/ -I/Library/Frameworks/SDL_ttf.framework/Headers/ -I/Library/Frameworks/SDL_mixer.framework/Headers/ -DPREFIX=L\"$(PREFIX)\" $(PROFILER) +LNFLAGS=-pipe -framework Cocoa -framework SDL_ttf -framework SDL -framework SDL_mixer -lSDLmain -lz $(PROFILER) + +TARGET=einstein + +SOURCES=puzgen.cpp main.cpp screen.cpp resources.cpp utils.cpp game.cpp \ + widgets.cpp iconset.cpp puzzle.cpp rules.cpp \ + verthins.cpp random.cpp horhints.cpp menu.cpp font.cpp \ + conf.cpp storage.cpp tablestorage.cpp regstorage.cpp \ + topscores.cpp opensave.cpp descr.cpp options.cpp messages.cpp \ + formatter.cpp buffer.cpp unicode.cpp convert.cpp table.cpp \ + i18n.cpp lexal.cpp streams.cpp tokenizer.cpp sound.cpp +OBJECTS=puzgen.o main.o screen.o resources.o utils.o game.o \ + widgets.o iconset.o puzzle.o rules.o verthints.o random.o \ + horhints.o menu.o font.o conf.o storage.o options.o \ + tablestorage.o regstorage.o topscores.o opensave.o descr.o \ + messages.o formatter.o buffer.o unicode.o convert.o table.o \ + i18n.o lexal.o streams.o tokenizer.o sound.o +HEADERS=screen.h main.h exceptions.h resources.h utils.h \ + widgets.h iconset.h puzzle.h verthints.h random.h horhints.h \ + font.h conf.h storage.h tablestorage.h regstorage.h \ + topscores.h opensave.h game.h descr.h options.h messages.h \ + foramtter.h buffer.h visitor.h unicode.h convert.h table.h \ + i18n.h lexal.h streams.h tokenizer.h sound.h + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< + +all: $(TARGET) + + +$(TARGET): $(OBJECTS) + $(CXX) $(LNFLAGS) $(OBJECTS) -o $(TARGET) + +clean: + rm -f $(OBJECTS) core* *core $(TARGET) *~ + +depend: + @makedepend $(SOURCES) 2> /dev/null + +run: $(TARGET) + ./$(TARGET) + +# DO NOT DELETE + diff --git a/Makefile.win b/Makefile.win new file mode 100644 index 0000000..69fe102 --- /dev/null +++ b/Makefile.win @@ -0,0 +1,49 @@ +OPTIMIZE=-O3 #-march=pentium4 -mfpmath=sse -fomit-frame-pointer -funroll-loops +DEBUG=#-ggdb +CXXFLAGS=-Wall $(OPTIMIZE) $(DEBUG) -Ic:/mingw/include/sdl -mwindows +LNFLAGS=-lmingw32 -lSDLmain -mwindows +LIBS=-lmingw32 -lSDLmain -lSDL_ttf -lSDL -lfreetype -lz -lSDL_mixer + +TARGET=einstein + +SOURCES=puzgen.cpp main.cpp screen.cpp resources.cpp utils.cpp game.cpp \ + widgets.cpp iconset.cpp puzzle.cpp rules.cpp \ + verthins.cpp random.cpp horhints.cpp menu.cpp font.cpp \ + conf.cpp storage.cpp tablestorage.cpp regstorage.cpp \ + topscores.cpp opensave.cpp descr.cpp options.cpp messages.cpp \ + formatter.cpp buffer.cpp unicode.cpp convert.cpp table.cpp \ + i18n.cpp lexal.cpp streams.cpp tokenizer.cpp sound.cpp +OBJECTS=puzgen.o main.o screen.o resources.o utils.o game.o \ + widgets.o iconset.o puzzle.o rules.o verthints.o random.o \ + horhints.o menu.o font.o conf.o storage.o options.o \ + tablestorage.o regstorage.o topscores.o opensave.o descr.o \ + messages.o formatter.o buffer.o unicode.o convert.o table.o \ + i18n.o lexal.o streams.o tokenizer.o sound.o +HEADERS=screen.h main.h exceptions.h resources.h utils.h \ + widgets.h iconset.h puzzle.h verthints.h random.h horhints.h \ + font.h conf.h storage.h tablestorage.h regstorage.h \ + topscores.h opensave.h game.h descr.h options.h messages.h \ + foramtter.h buffer.h visitor.h unicode.h convert.h table.h \ + i18n.h lexal.h streams.h tokenizer.h sound.h + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $< + +all: $(TARGET) + + +$(TARGET): $(OBJECTS) + $(CXX) $(LNFLAGS) $(OBJECTS) $(LIBS) -o $(TARGET) + +clean: + del $(TARGET).exe + del *.o + +depend: + @makedepend $(SOURCES) 2> /dev/null + +run: $(TARGET) + ./$(TARGET) + +# DO NOT DELETE + diff --git a/README b/README new file mode 100644 index 0000000..3543a53 --- /dev/null +++ b/README @@ -0,0 +1,3 @@ + +To build and install einstein edit Makefile and run `make install` as root. + diff --git a/buffer.cpp b/buffer.cpp new file mode 100644 index 0000000..c8c0fef --- /dev/null +++ b/buffer.cpp @@ -0,0 +1,104 @@ +#include "buffer.h" +#include +#include "exceptions.h" +#include "unicode.h" + + +Buffer::Buffer(int sz, int alloc) +{ + allocated = alloc; + size = sz; + if (size > allocated) + allocated = size; + if (allocated < 1024) + allocated = 1024; + data = malloc(allocated); + if (! data) + throw Exception(L"Error allocating memory for Buffer"); +} + +Buffer::~Buffer() +{ + free(data); +} + +void Buffer::setSize(size_t sz) +{ + if (sz > allocated) { + int newAl = allocated + sz + 1024; + void *d = realloc(data, newAl); + if (! d) + throw Exception(L"Error expanding buffer memory"); + data = d; + allocated = newAl; + } + + size = sz; +} + +size_t Buffer::getSize() +{ + return size; +} + +size_t Buffer::getAllocated() +{ + return allocated; +} + +void* Buffer::getData() +{ + return data; +} + + +void Buffer::gotoPos(int offset) +{ + currentPos = offset; +} + + +size_t Buffer::putData(const unsigned char *d, size_t length) +{ + if (size < currentPos + length) + setSize(currentPos + length); + memcpy((unsigned char*)data + currentPos, d, length); + currentPos += length; + return length; +} + + +size_t Buffer::putInteger(int v) +{ + unsigned char b[4]; + int i, ib; + + for (i = 0; i < 4; i++) { + ib = v & 0xFF; + v = v >> 8; + b[i] = ib; + } + + return putData(b, 4); +} + + +size_t Buffer::putUtf8(const std::wstring &string) +{ + std::string s(toUtf8(string)); + putInteger(s.length()); + putData((const unsigned char*)s.c_str(), s.length()); + return 4 + s.length(); +} + + +size_t Buffer::putByte(unsigned char value) +{ + if (size < (size_t)currentPos + 1) + setSize(currentPos + 1); + ((unsigned char*)data)[currentPos] = value; + currentPos++; + return 1; +} + + diff --git a/buffer.h b/buffer.h new file mode 100644 index 0000000..0091e1d --- /dev/null +++ b/buffer.h @@ -0,0 +1,79 @@ +#ifndef __BUFFER_H__ +#define __BUFFER_H__ + + +#include +#include + + +/// Dynamic growing buffer +class Buffer +{ + private: + size_t size; + size_t allocated; + void *data; + int currentPos; + + public: + /// Create buffer + /// \param size initial size of buffer + /// \param allocated bytes to allocate + Buffer(int size=0, int allocated=1024); + + ~Buffer(); + + public: + /// Set buffer size, expands memory if needed + /// \param size new size + void setSize(size_t size); + + /// Get current size of buffer + size_t getSize(); + + /// Get actual bytes used by buffer + size_t getAllocated(); + + /// Get pointer to data + void* getData(); + + public: + /// Move pointer to specified position. + /// \param offset offset from buffer start + void gotoPos(int offset); + + /// Add data to buffer and advance current position by data length. + /// Grow buffer if needed. + /// \param data pointer to data + /// \param length data size. + size_t putData(const unsigned char *data, size_t length); + + /// Add data to buffer and advance current position by data length. + /// Grow buffer if needed. + /// \param data pointer to data + /// \param length data size. + size_t putData(const char *data, size_t length) { + return putData((const unsigned char*)data, length); + }; + + /// Add integer to buffer and advance current position by 4. + /// Grow buffer if needed. + /// \param value value to add. + size_t putInteger(int value); + + /// Add string to buffer encoded in UTF-8 and advance current + /// position by string length. String stored prefixed by + /// string length. + /// Grow buffer if needed. + /// \param string value to add. + size_t putUtf8(const std::wstring &string); + + /// Add byte to buffer and advance current position by 1. + /// Grow buffer if needed. + /// \param value value to add. + size_t putByte(unsigned char value); +}; + + +#endif + diff --git a/conf.cpp b/conf.cpp new file mode 100644 index 0000000..85c32d9 --- /dev/null +++ b/conf.cpp @@ -0,0 +1,1449 @@ +#include +#include +#include +#include +#include "conf.h" + + +typedef struct _SField +{ + char *name; + int type; + union { + char *str_value; + int int_value; + double double_value; + HTable table_value; + } value; + struct _SField *next; +} SField; + + +typedef struct _STable +{ + SField *fields; + char *stat_buf; +} STable; + + +typedef struct _STableIterator +{ + HTable table; + SField *cur_field; + int before_start; + char *stat_buf; +} STableIterator; + + + +HTable table_create() +{ + HTable table; + + table = (STable*)calloc(1, sizeof(STable)); + return table; +} + + +static void free_field(SField *field) +{ + if (! field) + return; + if (field->name) + free(field->name); + switch (field->type) + { + case TYPE_STRING: free(field->value.str_value); break; + case TYPE_TABLE: table_free(field->value.table_value); break; + } + free(field); +} + + +static SField* find_field(HTable table, const char *name) +{ + SField *f; + + if ((! table) || (! name)) + return NULL; + + f = table->fields; + while (f) + { + if (f->name && (! strcmp(f->name, name))) + return f; + f = f->next; + } + + return NULL; +} + + +int table_remove_field(HTable table, char *field) +{ + SField *f, *pf; + + if ((! table) || (! field)) + return -1; + + f = find_field(table, field); + if (f) + { + if (table->fields == f) + table->fields = f->next; + else + { + pf = table->fields; + while (pf && (pf->next != f)) + pf = pf->next; + if (pf) + pf->next = f->next; + } + free_field(f); + } + + return 0; +} + + +static SField* add_empty_field(HTable table, const char *field) +{ + SField *f, *nf; + + if ((! table) || (! field)) + return NULL; + + f = find_field(table, field); + if (f) + table_remove_field(table, f->name); + + nf = (SField*)calloc(1, sizeof(SField)); + if (! nf) return NULL; + nf->name = strdup(field); + + f = table->fields; + if (f) + { + while (f->next) + f = f->next; + f->next = nf; + } + else + table->fields = nf; + + return nf; +} + + +int table_set_str(HTable table, const char *field, const char *value) +{ + SField *f; + + if ((! table) || (! field) || (! value)) + return -1; + + f = add_empty_field(table, field); + if (! f) return -1; + + f->type = TYPE_STRING; + f->value.str_value = strdup(value); + return 0; +} + + +int table_set_int(HTable table, const char *field, int value) +{ + SField *f; + + if ((! table) || (! field)) + return -1; + + f = add_empty_field(table, field); + if (! f) return -1; + + f->type = TYPE_INT; + f->value.int_value = value; + return 0; +} + + +int table_set_double(HTable table, const char *field, double value) +{ + SField *f; + + if ((! table) || (! field)) + return -1; + + f = add_empty_field(table, field); + if (! f) return -1; + + f->type = TYPE_FLOAT; + f->value.double_value = value; + return 0; +} + + +int table_set_table(HTable table, const char *field, HTable value) +{ + SField *f; + + if ((! table) || (! field) || (! value)) + return -1; + + f = add_empty_field(table, field); + if (! f) return -1; + + f->type = TYPE_TABLE; + f->value.table_value = value; + return 0; +} + + +static int skip_spaces(char *buf, long int *pos); + + +static int skip_multy_comment(char *buf, long int *pos) +{ + while (buf[*pos]) + { + if (buf[*pos] == '*') + { + (*pos)++; + if (buf[*pos] == '/') + { + (*pos)++; + return skip_spaces(buf, pos); + } + } + (*pos)++; + } + + return -1; +} + + +static int skip_line_comment(char *buf, long int *pos) +{ + while (buf[*pos] && ((buf[*pos]) != '\n')) + (*pos)++; + return skip_spaces(buf, pos); +} + + +static int is_cspace(char *buf, long int *pos) +{ + if (isspace(buf[*pos])) + return 1; + if (buf[*pos] == '/') + { + if ((buf[(*pos) + 1] == '*') || (buf[(*pos) + 1] == '/')) + return 1; + } + return 0; +} + + +static int skip_spaces(char *buf, long int *pos) +{ + while (buf[*pos] && isspace(buf[*pos])) + (*pos)++; + + if (buf[*pos] == '/') + { + (*pos)++; + if (buf[*pos] == '*') + { + (*pos)++; + return skip_multy_comment(buf, pos); + } + else if (buf[*pos] == '/') + { + (*pos)++; + return skip_line_comment(buf, pos); + } + else + return -1; + } + return 0; +} + + +static int is_symbol(char sym) +{ + static char syms[] = { '=', '{', '}', ';', '\'', '"', ',', '/', '\\' }; + unsigned int i; + + for (i = 0; i < sizeof(syms) / sizeof(char); i++) + { + if (sym == syms[i]) + return 1; + } + + return 0; +} + + +static char* read_string(char* buf, long int *pos) +{ + int allocated=50, len=1; + char *b; + + b = (char*)malloc(allocated); + if (! b) return NULL; + b[0] = buf[*pos]; + (*pos)++; + while (buf[*pos]) + { + if (len + 3 > allocated) + { + char *s = (char*)realloc(b, allocated+=20); + if (! s) + { + free(b); + return NULL; + } + b = s; + } + if ((buf[*pos] == '\\') && (buf[*pos + 1] == b[0])) + { + b[len++] = '\\'; + b[len] = b[0]; + } + else + { + b[len++] = buf[*pos]; + if (buf[*pos] == b[0]) + { + b[len] = 0; + (*pos)++; + return b; + } + } + (*pos)++; + } + free(b); + return NULL; +} + + +static char* read_word(char* buf, long int *pos) +{ + int allocated=50, len=0; + char *b; + + b = (char*)malloc(allocated); + if (! b) return NULL; + while (buf[*pos] && (! is_cspace(buf, pos)) && (! is_symbol(buf[*pos]))) + { + if (len + 3 > allocated) + { + char *s = (char*)realloc(b, allocated+=20); + if (! s) + { + free(b); + return NULL; + } + b = s; + } + b[len++] = buf[*pos]; + (*pos)++; + } + if (! len) + { + free(b); + return NULL; + } + b[len] = 0; + return b; +} + + +static char* read_token(char *buf, long int *pos) +{ + if (skip_spaces(buf, pos) || (! buf[*pos])) + return NULL; + + if ((buf[*pos] == '\'') || (buf[*pos] == '"')) + return read_string(buf, pos); + + if (is_symbol(buf[*pos])) + { + char *b = (char*)malloc(2); + if (! b) return NULL; + b[0] = buf[*pos]; + b[1] = 0; + (*pos)++; + return b; + } + + return read_word(buf, pos); +} + + +static long int get_file_size(FILE *f) +{ + long int len; + + if (fseek(f, 0L, SEEK_END)) + return -1; + + len = ftell(f); + + if (fseek(f, 0L, SEEK_SET)) + return -1; + + return len; +} + + +static char* read_file(const char *filename, long int *length) +{ + FILE *f; + char *buf; + long int len; + + f = fopen(filename, "rb"); + if (! f) return NULL; + + len = get_file_size(f); + if (len <= 0) + { + fclose(f); + return NULL; + } + + buf = (char*)malloc(len + 1); + if (! buf) + { + fclose(f); + return NULL; + } + + if (fread(buf, 1, len, f) != (unsigned)len) + { + free(buf); + fclose(f); + return NULL; + } + + buf[len] = 0; + + fclose(f); + *length = len + 1; + return buf; +} + + +static int get_next_index(HTable table) +{ + SField *f; + int v, max_v=-1; + char *endptr; + + for (f = table->fields; f; f = f->next) + { + v = strtol(f->name, &endptr, 10); + if ((! f->name[0]) || (endptr[0])) + continue; + if (v > max_v) + max_v = v; + } + + return max_v + 1; +} + + +static int is_ident(char *str) +{ + char *p; + + if (! str) + return 0; + if (strlen(str) < 1) + return 0; + if (! isalpha(str[0])) + return 0; + + for (p = str; *p; p++) + if (! (isalnum(*p) || (*p == '_') || (*p == '.'))) + return 0; + + return 1; +} + + +static int is_string_value(char *value) +{ + int len; + + if ((value[0] == '\'') || (value[0] == '"')) + { + if ((len = strlen(value)) >= 2) + { + if (value[len - 1] == value[0]) + return 1; + } + } + + return 0; +} + + +static int is_int_value(char *value) +{ + char *p; + + for (p = value; *p; p++) + { + if (! isdigit(*p)) + { + if (p != value) + return 0; + else + if (! ((*p == '+') || (*p == '-'))) + return 0; + } + } + return 1; +} + + +static int is_float_value(char *value) +{ + char *p; + int pcount = 0; + + for (p = value; *p; p++) + { + if (! isdigit(*p)) + { + if (p != value) + { + if (*p != '.') + return 0; + else + { + pcount++; + if (pcount > 1) + return 0; + } + } + else + { + if (! ((*p == '+') || (*p == '-'))) + return 0; + } + } + } + return 1; +} + + +static int get_value_type(char *value) +{ + if ((! value) || (! value[0])) + return -1; + + if (is_string_value(value)) + return TYPE_STRING; + if (! strcmp(value, "{")) + return TYPE_TABLE; + if (is_int_value(value)) + return TYPE_INT; + if (is_float_value(value)) + return TYPE_FLOAT; + + return -1; +} + + +static char* unescape_string(char *str) +{ + char *buf, quote, *p; + int allocated=50, len=0; + + buf = (char*)malloc(allocated); + + quote = str[0]; + p = str + 1; + while ((*p) && (*p != quote)) + { + if (len + 10 > allocated) + { + char *s = (char*)realloc(buf, allocated+=20); + if (! s) + { + free(buf); + return NULL; + } + buf = s; + } + if (*p == '\\') + { + p++; + switch (*p) + { + case 'n': buf[len++] = '\n'; break; + case 't': buf[len++] = '\t'; break; + case 'r': buf[len++] = '\r'; break; + default: buf[len++] = *p; + } + } + else + buf[len++] = *p; + p++; + } + buf[len] = 0; + + if ((*p == quote) && (*(p + 1))) + { + free(buf); + return NULL; + } + return buf; +} + + +static int parse_table(HTable table, char *buf, long int *pos, int top_level); + + +static int add_field(HTable table, char *name, char *value, char *buf, long int *pos) +{ + int value_type; + char *s; + HTable tbl; + + value_type = get_value_type(value); + switch (value_type) + { + case TYPE_INT: + table_set_int(table, name, atoi(value)); + break; + case TYPE_STRING: + s = unescape_string(value); + if (! s) + return -1; + table_set_str(table, name, s); + free(s); + break; + case TYPE_FLOAT: + table_set_double(table, name, atof(value)); + break; + case TYPE_TABLE: + tbl = table_create(); + if (table_set_table(table, name, tbl)) + { + table_free(tbl); + return -1; + } + if (parse_table(tbl, buf, pos, 0)) + return -1; + break; + default: + return -1; + } + return 0; +} + + +static int add_field_to_array(HTable table, char *token, char *buf, long int *pos) +{ + int idx = get_next_index(table); + char b[100]; + sprintf(b, "%i", idx); + return add_field(table, b, token, buf, pos); +} + + + +/* + * TODO: add field as in array highly unoptimized. it scans for max index + * number on every element addition, converting idexes from strings + * to numbers where it possible. optimizations method: process + * indexes in batch + */ + +static int parse_table(HTable table, char *buf, long int *pos, int top_level) +{ + char *token=NULL; + char *name; + int res; + + while (1) + { + if (! token) + token = read_token(buf, pos); + if (! token) + { + if (top_level) + return 0; + else + return -1; + } + + if (! strcmp(token, "{")) + { + if (add_field_to_array(table, token, buf, pos)) + { + free(token); + return -1; + } + free(token); + token = read_token(buf, pos); + if (! token) + { + if (top_level) + return 0; + else + return -1; + } + if ((! strcmp(token, ";")) || (! strcmp(token, ","))) + { + free(token); + token = NULL; + } + continue; + } + else if (! strcmp(token, "}")) + { + free(token); + if (top_level) + return -1; + else + return 0; + } + + name = token; + + token = read_token(buf, pos); + + if (token && (! strcmp(token, "="))) + { + free(token); + + if (! is_ident(name)) + { + free(name); + return -1; + } + + token = read_token(buf, pos); + if (! token) + { + free(name); + return -1; + } + + res = add_field(table, name, token, buf, pos); + free(name); + free(token); + if (res) + { + return -1; + } + + token = read_token(buf, pos); + if (! token) + { + if (top_level) + return 0; + else + return -1; + } + if ((! strcmp(token, ";")) || (! strcmp(token, ","))) + { + free(token); + token = NULL; + } + else + if (strcmp(token, "}")) + { + free(token); + return -1; + } + } + else + { + if ((! token) && (! top_level)) + { + free(name); + return -1; + } + if (! (token && ((! strcmp(token, ";")) || (! strcmp(token, ",")) + || (! strcmp(token, "}"))))) + { + free(name); + if (token) free(token); + return -1; + } + + res = add_field_to_array(table, name, buf, pos); + free(name); + if (res) + { + free(token); + return -1; + } + + if ((! token) || (! strcmp(token, "}"))) + { + if (token) free(token); + return 0; + } + + if (token && ((! strcmp(token, ",")) || ((! strcmp(token, ";"))))) + { + free(token); + token = NULL; + } + } + } + + return 0; +} + + +HTable table_read(const char *filename) +{ + long int len, pos; + char *buf; + HTable table; + int res; + + buf = read_file(filename, &len); + if (! buf) return NULL; + + pos = 0; + table = table_create(); + res = parse_table(table, buf, &pos, 1); + if (res) + { + table_free(table); + table = NULL; + } + + free(buf); + return table; +} + + +void table_free(HTable table) +{ + SField *f, *nf; + + if (! table) return; + + f = table->fields; + while (f) + { + nf = f->next; + free_field(f); + f = nf; + } + if (table->stat_buf) + free(table->stat_buf); + free(table); +} + + +HTableIterator table_get_iter(HTable table) +{ + HTableIterator it; + + it = (HTableIterator)calloc(sizeof(STableIterator), 1); + if (! it) return NULL; + + it->table = table; + it->before_start = 1; + + return it; +} + + +void table_free_iter(HTableIterator iterator) +{ + if (! iterator) return; + if (iterator->stat_buf) free(iterator->stat_buf); + free(iterator); +} + + +int table_iter_next(HTableIterator iterator) +{ + if (! iterator) + return 0; + + if (iterator->before_start) + { + iterator->before_start = 0; + iterator->cur_field = iterator->table->fields; + } + else + { + if (iterator->cur_field) + iterator->cur_field = iterator->cur_field->next; + } + + if (! iterator->cur_field) + return 0; + + return 1; +} + + +char* table_iter_get_name(HTableIterator iterator) +{ + if ((! iterator) || (! iterator->cur_field)) + return NULL; + + return iterator->cur_field->name; +} + + +int table_iter_get_type(HTableIterator iterator) +{ + if ((! iterator) || (! iterator->cur_field)) + return -1; + + return iterator->cur_field->type; +} + + +static char* encode_string(char *str) +{ + char *buf, *s; + int allocated, len; + + if (! str) + return strdup("''"); + + allocated = 20; + buf = (char*)malloc(allocated); + if (! buf) return NULL; + buf[0] = '\''; + len = 1; + for (s = str; *s; s++) + { + if (len + 2 < allocated - 2) + { + char *m = (char*)realloc(buf, allocated += 20); + if (! m) + { + free(buf); + return NULL; + } + buf = m; + } + switch (*s) + { + case '\n': buf[len++] = '\\'; buf[len++] = 'n'; break; + case '\r': buf[len++] = '\\'; buf[len++] = 'r'; break; + case '\'': buf[len++] = '\\'; buf[len++] = '\''; break; + case '\\': buf[len++] = '\\'; buf[len++] = '\\'; break; + default: + buf[len++] = *s; + } + } + buf[len++] = '\''; + buf[len] = 0; + return buf; +} + + +static char* print_field(SField *field, int butify, int spaces) +{ + char buf[100]; + + if (! field) return NULL; + + switch (field->type) + { + case TYPE_STRING: + return encode_string(field->value.str_value); + case TYPE_INT: + sprintf(buf, "%i", field->value.int_value); + return strdup(buf); + case TYPE_FLOAT: + sprintf(buf, "%g", field->value.double_value); + if (! strchr(buf, '.')) + strcat(buf, ".0"); + return strdup(buf); + case TYPE_TABLE: + return table_to_str(field->value.table_value, 1, butify, spaces); + } + + return NULL; +} + + +static int isSimpleArray(HTable table) +{ + SField *f; + char buf[50]; + int i; + + for (f = table->fields, i = 0; f; f = f->next, i++) + { + sprintf(buf, "%i", i); + if (! (f && f->name && (! strcmp(f->name, buf)))) + return 0; + } + return 1; +} + + +#define APPEND_BUF(buf, s) { int l = strlen(s); \ + if (len + l > allocated - 5) { \ + char *nb; \ + allocated += l + 50; \ + nb = (char*)realloc(buf, allocated); \ + if (! nb) { \ + free(buf); \ + return NULL; \ + } \ + buf = nb; \ + } \ + strcat(buf, s); \ + len += l; \ + } + +#define APPEND_SPACES(buf, n) \ + for (int i = 0; i < n; i++) { APPEND_BUF(buf, " "); } + +char* table_to_str(HTable table, int print_braces, int butify, int spaces) +{ + char *b, *fs; + int len=0, allocated; + SField *f; + + if (! table) return NULL; + + allocated = 100; + b = (char*)malloc(allocated); + if (! b) return NULL; + + if (print_braces) { + if (butify) + strcpy(b, "{\n"); + else + strcpy(b, "{ "); + } else + strcpy(b, ""); + len = strlen(b); + int printNames = ! isSimpleArray(table); + for (f = table->fields; f; f = f->next) { + if (butify) { + APPEND_SPACES(b, spaces); + } else + if (f != table->fields) { + APPEND_BUF(b, " "); + } + if (printNames) { + APPEND_BUF(b, f->name); + APPEND_BUF(b, " = "); + } + fs = print_field(f, butify, spaces + 4); + APPEND_BUF(b, fs); + free(fs); + if (butify) { + APPEND_BUF(b, ";\n"); + } else { + APPEND_BUF(b, ";"); + } + } + if (print_braces) { + if (! butify) { + APPEND_BUF(b, " }"); + } else { + APPEND_SPACES(b, spaces); + APPEND_BUF(b, "}"); + } + } + return b; +} + + +static char* get_field_str(SField *field, int *err) +{ + char buf[100]; + + if (err) *err = 0; + + if (! field) + { + if (err) *err = 1; + return NULL; + } + + switch (field->type) + { + case TYPE_STRING: + return strdup(field->value.str_value); + case TYPE_INT: + sprintf(buf, "%i", field->value.int_value); + return strdup(buf); + case TYPE_FLOAT: + sprintf(buf, "%f", field->value.double_value); + return strdup(buf); + case TYPE_TABLE: + return table_to_str(field->value.table_value, 1, 0, 0);; + } + + if (err) *err = 1; + return NULL; +} + + +char* table_iter_get_str(HTableIterator iterator, int *err) +{ + if ((! iterator) || (! iterator->cur_field)) + { + if (err) *err = 1; + return NULL; + } + + return get_field_str(iterator->cur_field, err); +} + + +char* table_iter_get_strs(HTableIterator iterator, int *err) +{ + if ((! iterator) || (! iterator->cur_field)) + { + if (err) *err = 1; + return NULL; + } + + if (iterator->stat_buf) + free(iterator->stat_buf); + iterator->stat_buf = get_field_str(iterator->cur_field, err); + return iterator->stat_buf; +} + + +static int get_field_int(SField *field, int *err) +{ + char *endptr; + int n; + + if (err) *err = 0; + + if (! field) + { + if (err) *err = 1; + return 0; + } + + switch (field->type) + { + case TYPE_STRING: + n = strtol(field->value.str_value, &endptr, 10); + if ((! field->value.str_value[0]) || (endptr[0])) + { + if (err) *err = 1; + return 0; + } + return n; + case TYPE_INT: + return field->value.int_value; + case TYPE_FLOAT: + return (int)field->value.double_value; + } + + if (err) *err = 1; + return 0; +} + + +int table_iter_get_int(HTableIterator iterator, int *err) +{ + if ((! iterator) || (! iterator->cur_field)) + { + if (err) *err = 1; + return 0; + } + + return get_field_int(iterator->cur_field, err); +} + + +static double get_field_double(SField *field, int *err) +{ + char *endptr; + double n; + + if (err) *err = 0; + + if (! field) + { + if (err) *err = 1; + return 0.0; + } + + switch (field->type) + { + case TYPE_STRING: + n = strtod(field->value.str_value, &endptr); + if ((! field->value.str_value[0]) || (endptr[0])) + { + if (err) *err = 1; + return 0.0; + } + return n; + case TYPE_INT: + return field->value.int_value; + case TYPE_FLOAT: + return field->value.double_value; + } + + if (err) *err = 1; + return 0.0; +} + + +double table_iter_get_double(HTableIterator iterator, int *err) +{ + if ((! iterator) || (! iterator->cur_field)) + { + if (err) *err = 1; + return 0.0; + } + + return get_field_double(iterator->cur_field, err); +} + + +HTable table_iter_get_table(HTableIterator iterator, int *err) +{ + if ((! iterator) || (! iterator->cur_field)) + { + if (err) *err = 1; + return NULL; + } + + if (iterator->cur_field->type != TYPE_TABLE) + { + if (err) *err = 1; + return NULL; + } + + if (err) *err = 0; + return iterator->cur_field->value.table_value; +} + + +int table_is_field_exists(HTable table, char *name) +{ + SField *f; + + f = find_field(table, name); + if (f) + return 1; + else + return 0; +} + + +int table_get_field_type(HTable table, char *field) +{ + SField *f; + + f = find_field(table, field); + if (! f) + return -1; + + return f->type; +} + + +char* table_get_str(HTable table, char *field, char *dflt, int *err) +{ + SField *f; + char *s; + int e; + + f = find_field(table, field); + if (! f) + { + if (err) *err = 1; + if (dflt) + return strdup(dflt); + else + return NULL; + } + + s = get_field_str(f, &e); + if (err) *err = e; + if (e) + { + if (dflt) + return strdup(dflt); + else + return NULL; + } + else + { + if (s) + return strdup(s); + else + return NULL; + } +} + + +char* table_get_strs(HTable table, char *field, char *dflt, int *err) +{ + SField *f; + char *s; + int e; + + if (! table) + { + if (err) *err = 1; + return dflt; + } + + if (table->stat_buf) + { + free(table->stat_buf); + table->stat_buf = NULL; + } + + f = find_field(table, field); + if (! f) + { + if (err) *err = 1; + return dflt; + } + + s = get_field_str(f, &e); + table->stat_buf = s; + if (err) *err = e; + if (e) + s = dflt; + + return s; +} + + +int table_get_int(HTable table, char *field, int dflt, int *err) +{ + SField *f; + int v, e; + + f = find_field(table, field); + if (! f) + { + if (err) *err = 1; + return dflt; + } + + v = get_field_int(f, &e); + if (err) *err = e; + if (e) + v = dflt; + + return v; +} + + +double table_get_double(HTable table, char *field, double dflt, int *err) +{ + SField *f; + double v; + int e; + + f = find_field(table, field); + if (! f) + { + if (err) *err = 1; + return dflt; + } + + v = get_field_double(f, &e); + if (err) *err = e; + if (e) + v = dflt; + + return v; +} + + +HTable table_get_table(HTable table, char *field, HTable dflt, int *err) +{ + SField *f; + HTable v; + + f = find_field(table, field); + if ((! f) || (f->type != TYPE_TABLE)) + { + if (err) *err = 1; + return dflt; + } + + v = f->value.table_value; + if (err) *err = 0; + + return v; +} + + +int table_append_str(HTable table, const char *val) +{ + int idx; + char b[100]; + + if ((! table) || (! val)) + return -1; + + idx = get_next_index(table); + sprintf(b, "%i", idx); + table_set_str(table, b, val); + return 0; +} + + +int table_append_int(HTable table, int val) +{ + int idx; + char b[100]; + + if ((! table) || (! val)) + return -1; + + idx = get_next_index(table); + sprintf(b, "%i", idx); + table_set_int(table, b, val); + return 0; +} + + +int table_append_double(HTable table, double val) +{ + int idx; + char b[100]; + + if ((! table) || (! val)) + return -1; + + idx = get_next_index(table); + sprintf(b, "%i", idx); + table_set_double(table, b, val); + return 0; +} + + +int table_append_table(HTable table, HTable val) +{ + int idx; + char b[100]; + + if ((! table) || (! val)) + return -1; + + idx = get_next_index(table); + sprintf(b, "%i", idx); + table_set_table(table, b, val); + return 0; +} + diff --git a/conf.h b/conf.h new file mode 100644 index 0000000..5925531 --- /dev/null +++ b/conf.h @@ -0,0 +1,107 @@ +/* + * Configuration file parser + * Copyright (c) 2002 Alexander Babichev + */ + +#ifndef __CONF_H__ +#define __CONF_H__ + +struct _STable; +typedef struct _STable* HTable; +struct _STableIterator; +typedef struct _STableIterator* HTableIterator; + +#define TYPE_INT 1 +#define TYPE_STRING 2 +#define TYPE_FLOAT 3 +#define TYPE_TABLE 4 + +/* create empty table */ +HTable table_create(); + +/* read table from file */ +HTable table_read(const char *filename); + +/* free table */ +void table_free(HTable table); + +/* get table iterator positioned before table start */ +HTableIterator table_get_iter(HTable table); + +/* free table iterator */ +void table_free_iter(HTableIterator iterator); + +/* move iterator. return 1 on success */ +int table_iter_next(HTableIterator iterator); + +/* get name of table field */ +char* table_iter_get_name(HTableIterator iterator); + +/* get type of table field */ +int table_iter_get_type(HTableIterator iterator); + +/* get current table field as newly allocated string */ +char* table_iter_get_str(HTableIterator iterator, int *err); + +/* get current table field as static string valid till next iterator operation */ +char* table_iter_get_strs(HTableIterator iterator, int *err); + +/* get current table field as int */ +int table_iter_get_int(HTableIterator iterator, int *err); + +/* get current table field as double */ +double table_iter_get_double(HTableIterator iterator, int *err); + +/* get current table field as table */ +HTable table_iter_get_table(HTableIterator iterator, int *err); + +/* return 0 if field not exists in table */ +int table_is_field_exists(HTable table, char *name); + +/* get type of table field or -1 if field not exists */ +int table_get_field_type(HTable table, char *field); + +/* get table field as newly allocated string */ +char* table_get_str(HTable table, char *field, char *dflt, int *err); + +/* get table field as static string */ +char* table_get_strs(HTable table, char *field, char *dflt, int *err); + +/* get table field as int */ +int table_get_int(HTable table, char *field, int dflt, int *err); + +/* get table field as double */ +double table_get_double(HTable table, char *field, double dflt, int *err); + +/* get table field as table */ +HTable table_get_table(HTable table, char *field, HTable dflt, int *err); + +/* print table to new allocated string */ +char* table_to_str(HTable table, int print_braces, int butify, int spaces); + +/* set table field as string */ +int table_set_str(HTable table, const char *field, const char *val); + +/* set table field as int */ +int table_set_int(HTable table, const char *field, int val); + +/* set table field as double */ +int table_set_double(HTable table, const char *field, double val); + +/* set table field as table */ +int table_set_table(HTable table, const char *field, HTable val); + +/* append string field to table */ +int table_append_str(HTable table, const char *val); + +/* append int field to table */ +int table_append_int(HTable table, int val); + +/* append double field to table */ +int table_append_double(HTable table, double val); + +/* append tablee field to table */ +int table_append_table(HTable table, HTable val); + +#endif + diff --git a/convert.cpp b/convert.cpp new file mode 100644 index 0000000..d4e16c9 --- /dev/null +++ b/convert.cpp @@ -0,0 +1,69 @@ +#include "convert.h" + +std::wstring toLowerCase(const std::wstring &s) +{ + std::wstring res; + + int len = s.length(); + for (int i = 0; i < len; i++) + res += (wchar_t)towlower(s[i]); + + return res; +} + +std::wstring toUpperCase(const std::wstring &s) +{ + std::wstring res; + + int len = s.length(); + for (int i = 0; i < len; i++) + res += (wchar_t)towupper(s[i]); + + return res; +} + +std::wstring numToStr(int num) +{ + wchar_t buf[30]; +#ifdef WIN32 + swprintf(buf, L"%i", num); +#else + swprintf(buf, 29, L"%i", num); +#endif + buf[29] = 0; + return std::wstring(buf); +} + +std::wstring numToStr(unsigned int num) +{ + wchar_t buf[30]; +#ifdef WIN32 + swprintf(buf, L"%u", num); +#else + swprintf(buf, 29, L"%i", num); +#endif + buf[29] = 0; + return std::wstring(buf); +} + +int strToInt(const std::wstring &str) +{ + int n; + wchar_t *endptr; + + n = wcstol(str.c_str(), &endptr, 10); + if ((! str.c_str()[0]) || (endptr[0])) + throw Exception(L"Invalid integer '" + str + L"'"); + return n; +} + +double strToDouble(const std::wstring &str) +{ + double n; + wchar_t *endptr; + + n = wcstod(str.c_str(), &endptr); + if ((! str.c_str()[0]) || (endptr[0])) + throw Exception(L"Invalid double '" + str + L"'"); + return n; +} diff --git a/convert.h b/convert.h new file mode 100644 index 0000000..2dca970 --- /dev/null +++ b/convert.h @@ -0,0 +1,53 @@ +#ifndef __CONVERT_H__ +#define __CONVERT_H__ + + +#include +#include +#include + +#include "exceptions.h" +#include "unicode.h" + + +/// Convert value to string +/// \param x value +template +inline std::wstring toString(const T &x) +{ +#ifndef WIN32 + std::wostringstream o; + if (! (o << x)) + throw Exception(L"Can't convert " + fromMbcs(typeid(x).name()) + + L" to string"); + return o.str(); +#else // Mingw doesn't support std::wostringstream yet :-( + std::ostringstream o; + if (! (o << x)) + throw Exception(L"Can't convert " + fromMbcs(typeid(x).name()) + + L" to string"); + return fromMbcs(o.str()); +#endif +} + + +/// Convert string to lower case. +std::wstring toLowerCase(const std::wstring &s); + +/// Convert string to upper case +std::wstring toUpperCase(const std::wstring &s); + +/// Convert integer to string. +std::wstring numToStr(int num); + +/// Convert unsigned integer to string. +std::wstring numToStr(unsigned int num); + +/// Convert string to integer +int strToInt(const std::wstring &str); + +/// Conver string to double +double strToDouble(const std::wstring &str); + + +#endif diff --git a/descr.cpp b/descr.cpp new file mode 100644 index 0000000..29bdcac --- /dev/null +++ b/descr.cpp @@ -0,0 +1,367 @@ +#include "descr.h" + +#include +#include +#include +#include + +#include "widgets.h" +#include "unicode.h" +#include "messages.h" +#include "convert.h" +#include "utils.h" +#include "tokenizer.h" + + +#define WIDTH 600 +#define HEIGHT 500 +#define CLIENT_WIDTH 570 +#define CLIENT_HEIGHT 390 +#define START_X 115 +#define START_Y 100 + + +class TextPage +{ + private: + std::vector widgets; + + public: + TextPage() { }; + ~TextPage(); + + public: + Widget* getWidget(int no) { return widgets[no]; }; + int getWidgetsCount() const { return widgets.size(); }; + void add(Widget *widget) { widgets.push_back(widget); }; + bool isEmpty() const { return ! widgets.size(); }; +}; + + +class TextParser +{ + private: + Tokenizer tokenizer; + std::vector pages; + Font &font; + int spaceWidth; + int charHeight; + std::map images; + int offsetX; + int offsetY; + int pageWidth; + int pageHeight; + + public: + TextParser(const std::wstring &text, Font &font, + int x, int y, int width, int height); + ~TextParser(); + + public: + TextPage* getPage(unsigned int no); + + private: + void addLine(TextPage *page, std::wstring &line, int &curPosY, + int &lineWidth); + void parseNextPage(); + bool isImage(const std::wstring &name); + std::wstring keywordToImage(const std::wstring &name); + SDL_Surface* getImage(const std::wstring &name); +}; + + +class CursorCommand; + + +// Otobrazhaet pravila igry na ekran +class Description +{ + private: + typedef std::list WidgetsList; + WidgetsList widgets; + + CursorCommand *prevCmd; // Sobytie na nazhatie knopki + CursorCommand *nextCmd; // Sobytie na nazhatie knopki + + Area area; // Mesto gde risovat' + //std::vector pages; // Spisok stranits teksta + unsigned int currentPage; // Tekuschaja stranitsa dlja prosmotra + + Font *titleFont; // Shrift dlja risovanija zagolovka okna + Font *buttonFont; // Shrift dlja risovanija knopok v okne + Font *textFont; // Shrift dlja risovanija teksta + + unsigned int textHeight; // Vysota stroki teksta + TextParser *text; + + public: + Description(Area *parentArea); + ~Description(); + + public: + void run(); + void updateInfo(); // Vyvodit informatsiju na stranitsu + TextPage *getPage(unsigned int no) { return text->getPage(no); }; + + private: + void printPage(); // Vyvodit tekuschuju stranitsu pravil + void deleteWidgets(); // Udaljaet neispol'zuemye metki i kartinki iz oblasti vyvoda informatsii (Area) +}; + + +//Nuzhen pri nazhatii na knopku ili v dialoge spiska pravil +class CursorCommand: public Command +{ + private: + int step; // Cherez skol'ko stranits listat' + Description &description; // Ukazatel' na ob"ekt Description + unsigned int *value; // Ukazatel' na tekuschij nomer stranitsy + + public: + CursorCommand(int step, Description &d, unsigned int *v); + virtual ~CursorCommand() { }; + + public: + virtual void doAction(); // Obrabatyvaet sobytija +}; + + + +void showDescription(Area *parentArea) +{ + Description d(parentArea); + d.run(); +} + + + +Description::Description(Area *parentArea) +{ + currentPage = 0; + //area.add(parentArea, false); + titleFont = new Font(L"nova.ttf", 26); + buttonFont = new Font(L"laudcn2.ttf", 14); + textFont = new Font(L"laudcn2.ttf", 16); + textHeight = (int)(textFont->getHeight(L"A") * 1.0); + text = new TextParser(msg(L"rulesText"), *textFont, START_X, START_Y, + CLIENT_WIDTH, CLIENT_HEIGHT); + prevCmd = new CursorCommand(-1, *this, ¤tPage); + nextCmd = new CursorCommand(1, *this, ¤tPage); +} + +Description::~Description() +{ + deleteWidgets(); + delete text; + delete titleFont; + delete buttonFont; + delete textFont; +} + + +void Description::deleteWidgets() +{ + for (WidgetsList::iterator i = widgets.begin(); + i != widgets.end(); i++) + area.remove(*i); + widgets.clear(); +} + + +void Description::updateInfo() +{ + deleteWidgets(); + printPage(); + area.draw(); +} + +void Description::run() +{ + area.add(new Window(100, 50, WIDTH, HEIGHT, L"blue.bmp")); + area.add(new Label(titleFont, 250, 60, 300, 40, Label::ALIGN_CENTER, Label::ALIGN_MIDDLE, 255, 255, 0, msg(L"rules"))); + area.add(new Button(110, 515, 80, 25, buttonFont, 255, 255, 0, L"blue.bmp", msg(L"prev"), prevCmd)); + area.add(new Button(200, 515, 80, 25, buttonFont, 255, 255, 0, L"blue.bmp", msg(L"next"), nextCmd)); + ExitCommand exitCmd(area); + area.add(new Button(610, 515, 80, 25, buttonFont, 255, 255, 0, L"blue.bmp", msg(L"close"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + printPage(); + area.run(); +} + +void Description::printPage() +{ + TextPage *page = text->getPage(currentPage); + if (! page) + return; + int len = page->getWidgetsCount(); + for (int i = 0; i < len; i++) { + Widget *w = page->getWidget(i); + if (w) { + widgets.push_back(w); + area.add(w); + } + } +} + +CursorCommand::CursorCommand(int s, Description &d, unsigned int *v): + description(d) +{ + step = s; + value = v; +} + +void CursorCommand::doAction() +{ + if ((! *value) && (0 > step)) + return; + unsigned int newPageNo = *value + step; + TextPage *page = description.getPage(newPageNo); + if (page) { + *value = newPageNo; + description.updateInfo(); + } +} + + +TextPage::~TextPage() +{ + for (std::vector::iterator i = widgets.begin(); + i != widgets.end(); i++) + delete *i; +} + + +TextParser::TextParser(const std::wstring &text, Font &font, + int x, int y, int width, int height): tokenizer(text), font(font) +{ + spaceWidth = font.getWidth(L' '); + charHeight = font.getWidth(L'A'); + offsetX = x; + offsetY = y; + pageWidth = width; + pageHeight = height; +} + + +TextParser::~TextParser() +{ + for (std::vector::iterator i = pages.begin(); + i != pages.end(); i++) + delete *i; + for (std::map::iterator i = images.begin(); + i != images.end(); i++) + delete (*i).second; +} + + +void TextParser::addLine(TextPage *page, std::wstring &line, int &curPosY, + int &lineWidth) +{ + if (0 < line.length()) { + page->add(new Label(&font, offsetX, offsetY + curPosY, + 255,255,255, line, false)); + line.clear(); + curPosY += 10 + charHeight; + lineWidth = 0; + } +} + +bool TextParser::isImage(const std::wstring &name) +{ + int len = name.length(); + return (3 < len) && (L'$' == name[0]) && (L'$' == name[len - 1]); +} + +std::wstring TextParser::keywordToImage(const std::wstring &name) +{ + return name.substr(1, name.length() - 2); +} + +SDL_Surface* TextParser::getImage(const std::wstring &name) +{ + SDL_Surface *img = images[name]; + if (! img) { + img = loadImage(name); + images[name] = img; + } + return img; +} + +void TextParser::parseNextPage() +{ + if (tokenizer.isFinished()) + return; + + int curPosY = 0; + int lineWidth = 0; + TextPage *page = new TextPage(); + std::wstring line; + + while (true) { + Token t = tokenizer.getNextToken(); + if (Token::Eof == t.getType()) + break; + if (Token::Para == t.getType()) { + if (0 < line.length()) + addLine(page, line, curPosY, lineWidth); + if (! page->isEmpty()) + curPosY += 10; + } else if (Token::Word == t.getType()) { + const std::wstring &word = t.getContent(); + if (isImage(word)) { + addLine(page, line, curPosY, lineWidth); + SDL_Surface *image = getImage(keywordToImage(word)); + if ((image->h + curPosY < pageHeight) || page->isEmpty()) { + int x = offsetX + (pageWidth - image->w) / 2; + page->add(new Picture(x, offsetY + curPosY, image)); + curPosY += image->h; + } else { + tokenizer.unget(t); + break; + } + } else { + int width = font.getWidth(word); + if (lineWidth + width > pageWidth) { + if (! lineWidth) { + line = word; + addLine(page, line, curPosY, lineWidth); + } else { + addLine(page, line, curPosY, lineWidth); + if (curPosY >= pageHeight) { + tokenizer.unget(t); + break; + } + line = word; + lineWidth = width; + } + } else { + lineWidth += width; + if (line.size()) { + line += L' '; + lineWidth += spaceWidth; + } + line += word; + } + } + } + if (curPosY >= pageHeight) + break; + } + addLine(page, line, curPosY, lineWidth); + if (! page->isEmpty()) + pages.push_back(page); + else + delete page; +} + + +TextPage* TextParser::getPage(unsigned int no) +{ + while ((! tokenizer.isFinished()) && (pages.size() <= no)) + parseNextPage(); + if (pages.size() <= no) + return NULL; + else + return pages[no]; +} + diff --git a/descr.h b/descr.h new file mode 100644 index 0000000..4b88752 --- /dev/null +++ b/descr.h @@ -0,0 +1,12 @@ +#ifndef __DESCR_H__ +#define __DESCR_H__ + + +#include "widgets.h" + + +void showDescription(Area *parentArea); + + +#endif + diff --git a/einstein.res b/einstein.res new file mode 100644 index 0000000..086cb1b Binary files /dev/null and b/einstein.res differ diff --git a/exceptions.h b/exceptions.h new file mode 100644 index 0000000..ffaff26 --- /dev/null +++ b/exceptions.h @@ -0,0 +1,24 @@ +#ifndef __EXCEPTIONS_H__ +#define __EXCEPTIONS_H__ + + +#include +#include + + +class Exception +{ + private: + std::wstring message; + + public: + Exception(const std::wstring& msg) { message = msg; /*std::cout << msg << std::endl;*/ }; + virtual ~Exception() { }; + + public: + const std::wstring& getMessage() const { return message; }; +}; + + +#endif + diff --git a/font.cpp b/font.cpp new file mode 100644 index 0000000..d535605 --- /dev/null +++ b/font.cpp @@ -0,0 +1,117 @@ +#include "font.h" +#include "main.h" +#include "utils.h" +#include "unicode.h" + + +static Uint16 *convBuf = NULL; +static size_t bufSize = 0; + + +static Uint16 *strToUint16(const std::wstring &text) +{ + const wchar_t *str = text.c_str(); + if ((! str) || (sizeof(wchar_t) == sizeof(Uint16))) + return (Uint16*)str; + else { + size_t len = wcslen(str); + if (! convBuf) { + size_t sz = len * 2 + 1; + convBuf = (Uint16*)malloc(sizeof(Uint16) * sz); + bufSize = sz; + } else + if (bufSize < len + 1) { + size_t sz = len * 2 + 1; + convBuf = (Uint16*)realloc(convBuf, sizeof(Uint16) * sz); + // I should check if it is NULL, but I'm too lazy today + bufSize = sz; + } + for (unsigned int i = 0; i <= len; i++) + convBuf[i] = (Uint16)str[i]; + return convBuf; + } +} + + + +Font::Font(const std::wstring &name, int ptsize) +{ + int size; + + data = resources->getRef(name, size); + if (! data) + throw Exception(name + L" not found"); + SDL_RWops *op = SDL_RWFromMem(data, size); + font = TTF_OpenFontRW(op, 1, ptsize); + + if (! font) + throw Exception(L"Error loading font " + name); +} + + +Font::~Font() +{ + TTF_CloseFont(font); + resources->delRef(data); +} + + +void Font::draw(SDL_Surface *s, int x, int y, int r, int g, int b, + bool shadow, const std::wstring &text) +{ + if (text.length() < 1) + return; + + Uint16 *str = strToUint16(text); + + if (shadow) { + SDL_Color color = { 1, 1, 1, 1 }; + SDL_Surface *surface = TTF_RenderUNICODE_Blended(font, str, color); + SDL_Rect src = { 0, 0, surface->w, surface->h }; + SDL_Rect dst = { x+1, y+1, surface->w, surface->h }; + SDL_BlitSurface(surface, &src, s, &dst); + SDL_FreeSurface(surface); + } + SDL_Color color = { r, g, b, 0 }; + SDL_Surface *surface = TTF_RenderUNICODE_Blended(font, str, color); + SDL_Rect src = { 0, 0, surface->w, surface->h }; + SDL_Rect dst = { x, y, surface->w, surface->h }; + SDL_BlitSurface(surface, &src, s, &dst); + SDL_FreeSurface(surface); +} + +void Font::draw(int x, int y, int r, int g, int b, bool shadow, + const std::wstring &text) +{ + draw(screen.getSurface(), x, y, r,g,b, shadow, text); +} + +int Font::getWidth(const std::wstring &text) +{ + int w, h; + Uint16 *str = strToUint16(text); + TTF_SizeUNICODE(font, str, &w, &h); + return w; +} + +int Font::getWidth(wchar_t ch) +{ + int minx, maxx, miny, maxy, advance; + TTF_GlyphMetrics(font, (Uint16)ch, &minx, &maxx, &miny, &maxy, &advance); + return advance; +} + +int Font::getHeight(const std::wstring &text) +{ + int w, h; + Uint16 *str = strToUint16(text); + TTF_SizeUNICODE(font, str, &w, &h); + return h; +} + +void Font::getSize(const std::wstring &text, int &width, int &height) +{ + Uint16 *str = strToUint16(text); + TTF_SizeUNICODE(font, str, &width, &height); +} + diff --git a/font.h b/font.h new file mode 100644 index 0000000..2e8e44d --- /dev/null +++ b/font.h @@ -0,0 +1,32 @@ +#ifndef __FONT_H__ +#define __FONT_H__ + + +#include +#include + + +class Font +{ + private: + TTF_Font *font; + void *data; + + public: + Font(const std::wstring &name, int ptsize); + ~Font(); + + public: + void draw(SDL_Surface *s, int x, int y, int r, int g, int b, + bool shadow, const std::wstring &text); + void draw(int x, int y, int r, int g, int b, bool shadow, + const std::wstring &text); + int getWidth(const std::wstring &text); + int getWidth(wchar_t ch); + int getHeight(const std::wstring &text); + void getSize(const std::wstring &text, int &width, int &height); +}; + + +#endif + diff --git a/formatter.cpp b/formatter.cpp new file mode 100644 index 0000000..8d76c12 --- /dev/null +++ b/formatter.cpp @@ -0,0 +1,187 @@ +#include "formatter.h" +#include "utils.h" +#include "convert.h" + + +#define ADD_ARG(t) \ + commands[commandsCnt].type = t; \ + argNo = readInt(data + offset); \ + if (argNo > maxArg) \ + maxArg = argNo; \ + commands[commandsCnt].data = (void*)argNo; \ + commandsCnt++; + +Formatter::Formatter(unsigned char *data, int offset) +{ + int cnt = readInt(data + offset); + if (! cnt) { + commandsCnt = argsCnt = 0; + commands = NULL; + args = NULL; + } + + offset += 4; + commands = new Command[cnt]; + commandsCnt = 0; + + int maxArg = 0, argNo; + + for (int i = 0; i < cnt; i++) { + int type = data[offset]; + offset++; + int size = readInt(data + offset); + offset += 4; + switch (type) { + case 1: + commands[commandsCnt].type = TEXT_COMMAND; + commands[commandsCnt].data = new std::wstring( + fromUtf8((char*)data + offset, size)); + commandsCnt++; + break; + + case 2: ADD_ARG(INT_ARG); break; + case 3: ADD_ARG(STRING_ARG); break; + case 4: ADD_ARG(FLOAT_ARG); break; + case 5: ADD_ARG(DOUBLE_ARG); break; + } + offset += size; + } + + argsCnt = maxArg; + if (! argsCnt) + args = NULL; + else { + args = new CmdType[argsCnt]; + memset(args, 0, sizeof(CmdType) * argsCnt); + for (int i = 0; i < commandsCnt; i++) { + Command &c = commands[i]; + if ((c.type == INT_ARG) || (c.type == STRING_ARG) || + (c.type == FLOAT_ARG) || (c.type == DOUBLE_ARG)) + { + int no = (int)c.data; + args[no - 1] = c.type; + } + } + } +} + +Formatter::~Formatter() +{ + for (int i = 0; i < commandsCnt; i++) + if (TEXT_COMMAND == commands[i].type) + delete (std::wstring*)(commands[i].data); + if (commands) + delete[] commands; + if (args) + delete[] args; +} + +std::wstring Formatter::getMessage() const +{ + std::wstring s; + + for (int i = 0; i < commandsCnt; i++) + if (TEXT_COMMAND == commands[i].type) + s += *(std::wstring*)(commands[i].data); + return s; +} + + +class ArgValue +{ + public: + virtual ~ArgValue() { }; + virtual std::wstring format(Formatter::Command *command) = 0; +}; + +template +class TemplatedArgValue: public ArgValue +{ + private: + T value; + + public: + TemplatedArgValue(const T &v) { value = v; }; + virtual std::wstring format(Formatter::Command *command) { + return toString(value); + }; +}; + +class StrArgValue: public ArgValue +{ + private: + std::wstring value; + + public: + StrArgValue(const std::wstring &v): value(v) { }; + virtual std::wstring format(Formatter::Command *command) { + return value; + }; +}; + + +std::wstring Formatter::format(std::vector &argValues) const +{ + std::wstring s; + int no; + + for (int i = 0; i < commandsCnt; i++) { + Command *cmd = &commands[i]; + + switch (cmd->type) { + case TEXT_COMMAND: + s += *(std::wstring*)(cmd->data); + break; + + case STRING_ARG: + case INT_ARG: + no = (int)cmd->data - 1; + if (no < (int)argValues.size()) + s += argValues[no]->format(cmd); + break; + + default: ; + } + } + + return s; +} + +std::wstring Formatter::format(va_list ap) const +{ + if (! argsCnt) + return getMessage(); + + std::vector argValues; + + for (int i = 0; i < argsCnt; i++) { + switch (args[i]) { + case INT_ARG: + argValues.push_back(new TemplatedArgValue + (va_arg(ap, int))); + break; + case STRING_ARG: + argValues.push_back(new StrArgValue(va_arg(ap, wchar_t*))); + break; + case DOUBLE_ARG: + argValues.push_back(new TemplatedArgValue + (va_arg(ap, double))); + break; + case FLOAT_ARG: + argValues.push_back(new TemplatedArgValue + ((float)va_arg(ap, double))); + break; + default: + i = argsCnt; + } + } + + std::wstring s = format(argValues); + + for (std::vector::iterator i = argValues.begin(); + i != argValues.end(); i++) + delete *i; + + return s; +} + diff --git a/formatter.h b/formatter.h new file mode 100644 index 0000000..9622795 --- /dev/null +++ b/formatter.h @@ -0,0 +1,63 @@ +#ifndef __FORMATTER_H__ +#define __FORMATTER_H__ + + +#include +#include +#include + + +class ArgValue; + + +/// Localized message formatter +class Formatter +{ + public: + typedef enum { + EMPTY_CMD = 0, + TEXT_COMMAND, + INT_ARG, + STRING_ARG, + DOUBLE_ARG, + FLOAT_ARG + } CmdType; + + typedef struct + { + CmdType type; + void *data; + } Command; + + private: + int commandsCnt; + int argsCnt; + + Command *commands; + + CmdType *args; + + public: + /// Create localized message from message buffer. + /// \param data buffer contained message file + /// \param offset offset to message from buffer start + Formatter(unsigned char *data, int offset); + ~Formatter(); + + public: + /// Get message text. + std::wstring getMessage() const; + + /// Fromat message + /// \param ap list of arguments + std::wstring format(va_list ap) const; + + private: + std::wstring format(std::vector &argValues) const; +}; + + + + +#endif + diff --git a/game.cpp b/game.cpp new file mode 100644 index 0000000..de6d443 --- /dev/null +++ b/game.cpp @@ -0,0 +1,569 @@ +#include "main.h" +#include "utils.h" +#include "widgets.h" +#include "puzzle.h" +#include "verthints.h" +#include "horhints.h" +#include "widgets.h" +#include "font.h" +#include "topscores.h" +#include "opensave.h" +#include "options.h" +#include "game.h" +#include "messages.h" +#include "sound.h" +#include "descr.h" + + + +class GameBackground: public Widget +{ + public: + virtual void draw(); +}; + + +void GameBackground::draw() +{ + // draw background + drawWallpaper(L"rain.bmp"); + + // draw title + SDL_Surface *tile = loadImage(L"title.bmp"); + screen.draw(8, 10, tile); + SDL_FreeSurface(tile); + + Font titleFont(L"nova.ttf", 28); + titleFont.draw(screen.getSurface(), 20, 20, 255,255,0, true, + msg(L"einsteinPuzzle")); + + screen.addRegionToUpdate(0, 0, screen.getWidth(), screen.getHeight()); +} + + +class ToggleHintCommand: public Command +{ + private: + VertHints *verHints; + HorHints *horHints; + + public: + ToggleHintCommand(VertHints *v, HorHints *h) { + verHints = v; + horHints = h; + }; + + virtual void doAction() { + verHints->toggleExcluded(); + horHints->toggleExcluded(); + }; +}; + + +class Watch: public TimerHandler, public Widget +{ + private: + Uint32 lastRun; + Uint32 elapsed; + bool stoped; + int lastUpdate; + Font *font; + + public: + Watch(); + Watch(std::istream &stream); + virtual ~Watch(); + + public: + virtual void onTimer(); + void stop(); + void start(); + virtual void draw(); + int getElapsed() { return elapsed; }; + void save(std::ostream &stream); + void reset(); +}; + + +Watch::Watch() +{ + lastRun = elapsed = lastUpdate = 0; + stop(); + font = new Font(L"luximb.ttf", 16); +} + +Watch::Watch(std::istream &stream) +{ + elapsed = readInt(stream); + lastUpdate = 0; + stop(); + font = new Font(L"luximb.ttf", 16); +} + +Watch::~Watch() +{ + delete font; +} + +void Watch::onTimer() +{ + if (stoped) + return; + + Uint32 now = SDL_GetTicks(); + elapsed += now - lastRun; + lastRun = now; + + int seconds = elapsed / 1000; + if (seconds != lastUpdate) + draw(); +} + +void Watch::stop() +{ + stoped = true; +} + +void Watch::start() +{ + stoped = false; + lastRun = SDL_GetTicks(); +} + +void Watch::draw() +{ + int time = elapsed / 1000; + std::wstring s = secToStr(time); + + int x = 700; + int y = 24; + int w, h; + font->getSize(s, w, h); + SDL_Rect rect = { x-2, y-2, w+4, h+4 }; + SDL_FillRect(screen.getSurface(), &rect, + SDL_MapRGB(screen.getSurface()->format, 0, 0, 255)); + font->draw(x, y, 255,255,255, true, s); + screen.addRegionToUpdate(x-2, y-2, w+4, h+4); + + lastUpdate = time; +} + +void Watch::save(std::ostream &stream) +{ + writeInt(stream, elapsed); +} + +void Watch::reset() +{ + elapsed = lastUpdate = 0; + lastRun = SDL_GetTicks(); +} + + +class PauseGameCommand: public Command +{ + private: + Area *gameArea; + Watch *watch; + Widget *background; + + public: + PauseGameCommand(Area *a, Watch *w, Widget *bg) { + gameArea = a; + watch = w; + background = bg; + }; + + virtual void doAction() { + watch->stop(); + Area area; + area.add(background, false); + Font font(L"laudcn2.ttf", 16); + area.add(new Window(280, 275, 240, 50, L"greenpattern.bmp", 6)); + area.add(new Label(&font, 280, 275, 240, 50, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, 255,255,0, msg(L"paused"))); + area.add(new AnyKeyAccel()); + area.run(); + sound->play(L"click.wav"); + gameArea->updateMouse(); + gameArea->draw(); + watch->start(); + }; +}; + + +class WinCommand: public Command +{ + private: + Area *gameArea; + Watch *watch; + Game *game; + + public: + WinCommand(Area *a, Watch *w, Game *g) { + gameArea = a; + watch = w; + game = g; + }; + + virtual void doAction() { + sound->play(L"applause.wav"); + watch->stop(); + Font font(L"laudcn2.ttf", 20); + showMessageWindow(gameArea, L"marble1.bmp", + 500, 70, &font, 255,0,0, msg(L"won")); + gameArea->draw(); + TopScores scores; + int score = watch->getElapsed() / 1000; + int pos = -1; + if (! game->isHinted()) { + if ((! scores.isFull()) || (score < scores.getMaxScore())) { + std::wstring name = enterNameDialog(gameArea); + pos = scores.add(name, score); + } + } + showScoresWindow(gameArea, &scores, pos); + gameArea->finishEventLoop(); + }; +}; + +class OkDlgCommand: public Command +{ + private: + bool &res; + Area *area; + + public: + OkDlgCommand(Area *a, bool &r): res(r) { + area = a; + }; + + virtual void doAction() { + res = true; + area->finishEventLoop(); + }; +}; + +class FailCommand: public Command +{ + private: + Area *gameArea; + Game *game; + + public: + FailCommand(Area *a, Game *g) { gameArea = a; game = g; }; + + virtual void doAction() { + sound->play(L"glasbk2.wav"); + bool restart = false; + bool newGame = false; + Font font(L"laudcn2.ttf", 24); + Font btnFont(L"laudcn2.ttf", 14); + Area area; + area.add(gameArea); + area.add(new Window(220, 240, 360, 140, L"redpattern.bmp", 6)); + area.add(new Label(&font, 250, 230, 300, 100, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, 255,255,0, msg(L"loose"))); + OkDlgCommand newGameCmd(&area, newGame); + area.add(new Button(250, 340, 90, 25, &btnFont, 255,255,0, + L"redpattern.bmp", msg(L"startNew"), &newGameCmd)); + OkDlgCommand restartCmd(&area, restart); + area.add(new Button(350, 340, 90, 25, &btnFont, 255,255,0, + L"redpattern.bmp", msg(L"tryAgain"), &restartCmd)); + ExitCommand exitCmd(area); + area.add(new Button(450, 340, 90, 25, &btnFont, 255,255,0, + L"redpattern.bmp", msg(L"exit"), &exitCmd)); + area.run(); + if (restart || newGame) { + if (newGame) + game->newGame(); + else + game->restart(); + gameArea->draw(); + gameArea->updateMouse(); + } else + gameArea->finishEventLoop(); + }; +}; + + +class CheatAccel: public Widget +{ + private: + Command *command; + std::wstring typed; + std::wstring cheat; + + public: + CheatAccel(const std::wstring s, Command *cmd): cheat(s) { + command = cmd; + }; + + public: + virtual bool onKeyDown(SDLKey key, unsigned char ch) { + if ((key >= SDLK_a) && (key <= SDLK_z)) { + wchar_t s = L'a' + key - SDLK_a; + typed += s; + if (typed.length() == cheat.length()) { + if (command && (typed == cheat)) + command->doAction(); + } else { + int pos = typed.length() - 1; + if (typed[pos] == cheat[pos]) + return false; + } + } + if (typed.length() > 0) + typed = L""; + return false; + } +}; + + +class CheatCommand: public Command +{ + private: + Area *gameArea; + + public: + CheatCommand(Area *a) { gameArea = a; }; + + virtual void doAction() { + Font font(L"nova.ttf", 30); + showMessageWindow(gameArea, L"darkpattern.bmp", + 500, 100, &font, 255,255,255, + msg(L"iddqd")); + gameArea->draw(); + }; +}; + + +class SaveGameCommand: public Command +{ + private: + Area *gameArea; + Watch *watch; + Widget *background; + Game *game; + + public: + SaveGameCommand(Area *a, Watch *w, Widget *bg, Game *g) { + gameArea = a; + watch = w; + background = bg; + game = g; + }; + + virtual void doAction() { + watch->stop(); + + Area area; + area.add(background, false); + saveGame(&area, game); + + gameArea->updateMouse(); + gameArea->draw(); + watch->start(); + }; +}; + + +class GameOptionsCommand: public Command +{ + private: + Area *gameArea; + + public: + GameOptionsCommand(Area *a) { + gameArea = a; + }; + + virtual void doAction() { + showOptionsWindow(gameArea); + gameArea->updateMouse(); + gameArea->draw(); + }; +}; + + +class HelpCommand: public Command +{ + private: + Area *gameArea; + Watch *watch; + Widget *background; + + public: + HelpCommand(Area *a, Watch *w, Widget *b) { + gameArea = a; + watch = w; + background = b; + }; + + virtual void doAction() { + watch->stop(); + Area area; + area.add(background, false); + area.draw(); + showDescription(&area); + gameArea->updateMouse(); + gameArea->draw(); + watch->start(); + }; +}; + + + +Game::Game() +{ + genPuzzle(); + + possibilities = new Possibilities(); + openInitial(*possibilities, rules); + + puzzle = new Puzzle(iconSet, solvedPuzzle, possibilities); + verHints = new VertHints(iconSet, rules); + horHints = new HorHints(iconSet, rules); + watch = new Watch(); +} + +Game::Game(std::istream &stream) +{ + pleaseWait(); + + loadPuzzle(solvedPuzzle, stream); + loadRules(rules, stream); + memcpy(savedSolvedPuzzle, solvedPuzzle, sizeof(solvedPuzzle)); + savedRules = rules; + possibilities = new Possibilities(stream); + puzzle = new Puzzle(iconSet, solvedPuzzle, possibilities); + verHints = new VertHints(iconSet, rules, stream); + horHints = new HorHints(iconSet, rules, stream); + watch = new Watch(stream); + hinted = true; +} + +Game::~Game() +{ + delete watch; + delete possibilities; + delete verHints; + delete horHints; + delete puzzle; + deleteRules(); +} + +void Game::save(std::ostream &stream) +{ + savePuzzle(solvedPuzzle, stream); + saveRules(rules, stream); + possibilities->save(stream); + verHints->save(stream); + horHints->save(stream); + watch->save(stream); +} + +void Game::deleteRules() +{ + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) + delete *i; + rules.clear(); +} + +void Game::pleaseWait() +{ + drawWallpaper(L"rain.bmp"); + Window window(230, 260, 340, 80, L"greenpattern.bmp", 6); + window.draw(); + Font font(L"laudcn2.ttf", 16); + Label label(&font, 280, 275, 240, 50, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, 255,255,0, msg(L"loading")); + label.draw(); + screen.addRegionToUpdate(0, 0, screen.getWidth(), screen.getHeight()); + screen.flush(); +} + +void Game::genPuzzle() +{ + pleaseWait(); + + int horRules, verRules; + do { + if (rules.size() > 0) + deleteRules(); + ::genPuzzle(solvedPuzzle, rules); + getHintsQty(rules, verRules, horRules); + } while ((horRules > 24) || (verRules > 15)); + + memcpy(savedSolvedPuzzle, solvedPuzzle, sizeof(solvedPuzzle)); + savedRules = rules; + + hinted = false; +} + +void Game::resetVisuals() +{ + possibilities->reset(); + openInitial(*possibilities, rules); + puzzle->reset(); + verHints->reset(rules); + horHints->reset(rules); + watch->reset(); +} + +void Game::newGame() +{ + genPuzzle(); + resetVisuals(); +} + +void Game::restart() +{ + memcpy(solvedPuzzle, savedSolvedPuzzle, sizeof(solvedPuzzle)); + rules = savedRules; + + resetVisuals(); + hinted = true; +} + +#define BUTTON(x, y, text, cmd) \ + area.add(new Button(x, y, 94, 30, &btnFont, 255,255,0, \ + L"btn.bmp", msg(text), false, cmd)); + +void Game::run() +{ + Area area; + Font btnFont(L"laudcn2.ttf", 14); + + area.setTimer(300, watch); + + GameBackground *background = new GameBackground(); + area.add(background); + CheatCommand cheatCmd(&area); + area.add(new CheatAccel(L"iddqd", &cheatCmd)); + WinCommand winCmd(&area, watch, this); + FailCommand failCmd(&area, this); + puzzle->setCommands(&winCmd, &failCmd); + area.add(puzzle, false); + area.add(verHints, false); + area.add(horHints, false); + + PauseGameCommand pauseGameCmd(&area, watch, background); + BUTTON(12, 400, L"pause", &pauseGameCmd) + ToggleHintCommand toggleHintsCmd(verHints, horHints); + BUTTON(119, 400, L"switch", &toggleHintsCmd) + SaveGameCommand saveCmd(&area, watch, background, this); + BUTTON(12, 440, L"save", &saveCmd) + GameOptionsCommand optionsCmd(&area); + BUTTON(119, 440, L"options", &optionsCmd) + ExitCommand exitGameCmd(area); + BUTTON(226, 400, L"exit", &exitGameCmd) + area.add(new KeyAccel(SDLK_ESCAPE, &exitGameCmd)); + HelpCommand helpCmd(&area, watch, background); + BUTTON(226, 440, L"help", &helpCmd) + area.add(watch, false); + + watch->start(); + area.run(); +} + diff --git a/game.h b/game.h new file mode 100644 index 0000000..223704d --- /dev/null +++ b/game.h @@ -0,0 +1,58 @@ +#ifndef __GAME_H__ +#define __GAME_H__ + + +#include +#include "puzgen.h" +#include "verthints.h" +#include "horhints.h" +#include "puzzle.h" + + + +class Watch; + + + +class Game +{ + private: + SolvedPuzzle solvedPuzzle; + Rules rules; + Possibilities *possibilities; + VertHints *verHints; + HorHints *horHints; + IconSet iconSet; + Puzzle *puzzle; + Watch *watch; + bool hinted; + SolvedPuzzle savedSolvedPuzzle; + Rules savedRules; + + public: + Game(); + Game(std::istream &stream); + ~Game(); + + public: + SolvedPuzzle& getSolvedPuzzle() { return solvedPuzzle; }; + Rules& getRules() { return rules; }; + Possibilities* getPossibilities() { return possibilities; }; + VertHints* getVerHints() { return verHints; }; + HorHints* getHorHints() { return horHints; }; + void save(std::ostream &stream); + void run(); + bool isHinted() { return hinted; }; + void setHinted() { hinted = true; }; + void restart(); + void newGame(); + + private: + void deleteRules(); + void pleaseWait(); + void genPuzzle(); + void resetVisuals(); +}; + +#endif + diff --git a/horhints.cpp b/horhints.cpp new file mode 100644 index 0000000..4ee4498 --- /dev/null +++ b/horhints.cpp @@ -0,0 +1,207 @@ +#include "horhints.h" +#include "main.h" +#include "utils.h" +#include "sound.h" + + +#define HINTS_COLS 3 +#define HINTS_ROWS 8 +#define TILE_GAP_X 4 +#define TILE_GAP_Y 4 +#define TILE_X 348 +#define TILE_Y 68 +#define TILE_WIDTH 48 +#define TILE_HEIGHT 48 + + +HorHints::HorHints(IconSet &is, Rules &r): iconSet(is) +{ + reset(r); +} + + +HorHints::HorHints(IconSet &is, Rules &rl, std::istream &stream): iconSet(is) +{ + int qty = readInt(stream); + + for (int i = 0; i < qty; i++) { + int no = readInt(stream); + numbersArr.push_back(no); + Rule *r = getRule(rl, no); + int excluded = readInt(stream); + if (excluded) { + excludedRules.push_back(r); + rules.push_back(NULL); + } else { + excludedRules.push_back(NULL); + rules.push_back(r); + } + } + + showExcluded = readInt(stream); + + int x, y; + SDL_GetMouseState(&x, &y); + highlighted = getRuleNo(x, y); +} + + +void HorHints::reset(Rules &r) +{ + rules.clear(); + excludedRules.clear(); + numbersArr.clear(); + + int no = 0; + for (Rules::iterator i = r.begin(); i != r.end(); i++) { + Rule *rule = *i; + if (rule->getShowOpts() == Rule::SHOW_HORIZ) { + rules.push_back(rule); + excludedRules.push_back(NULL); + numbersArr.push_back(no); + } + no++; + } + + showExcluded = false; + + int x, y; + SDL_GetMouseState(&x, &y); + highlighted = getRuleNo(x, y); +} + +void HorHints::draw() +{ + for (int i = 0; i < HINTS_ROWS; i++) + for (int j = 0; j < HINTS_COLS; j++) + drawCell(j, i, true); +} + +void HorHints::drawCell(int col, int row, bool addToUpdate) +{ + int x = TILE_X + col * (TILE_WIDTH*3 + TILE_GAP_X); + int y = TILE_Y + row * (TILE_HEIGHT + TILE_GAP_Y); + + Rule *r = NULL; + int no = row * HINTS_COLS + col; + if (no < (int)rules.size()) + if (showExcluded) + r = excludedRules[no]; + else + r = rules[no]; + if (r) + r->draw(x, y, iconSet, no == highlighted); + else + for (int i = 0; i < 3; i++) + screen.draw(x + TILE_HEIGHT*i, y, iconSet.getEmptyHintIcon()); + + if (addToUpdate) + screen.addRegionToUpdate(x, y, TILE_WIDTH*3, TILE_HEIGHT); +} + + +bool HorHints::onMouseButtonDown(int button, int x, int y) +{ + if (button != 3) + return false; + + int no = getRuleNo(x, y); + if (no < 0) return false; + int row = no / HINTS_COLS; + int col = no - row * HINTS_COLS; + + if (showExcluded) { + Rule *r = excludedRules[no]; + if (r) { + sound->play(L"whizz.wav"); + rules[no] = r; + excludedRules[no] = NULL; + drawCell(col, row); + } + } else { + Rule *r = rules[no]; + if (r) { + sound->play(L"whizz.wav"); + rules[no] = NULL; + excludedRules[no] = r; + drawCell(col, row); + } + } + + return true; +} + + +void HorHints::toggleExcluded() +{ + showExcluded = !showExcluded; + draw(); +} + + +bool HorHints::onMouseMove(int x, int y) +{ + int no = getRuleNo(x, y); + + if (no != highlighted) { + int old = highlighted; + highlighted = no; + if (isActive(old)) { + int row = old / HINTS_COLS; + int col = old - row * HINTS_COLS; + drawCell(col, row); + } + if (isActive(no)) { + int row = no / HINTS_COLS; + int col = no - row * HINTS_COLS; + drawCell(col, row); + } + } + + return false; +} + + +int HorHints::getRuleNo(int x, int y) +{ + if (! isInRect(x, y, TILE_X, TILE_Y, (TILE_WIDTH*3 + TILE_GAP_X) * HINTS_COLS, + (TILE_HEIGHT + TILE_GAP_Y) * HINTS_ROWS)) + return -1; + + x = x - TILE_X; + y = y - TILE_Y; + + int col = x / (TILE_WIDTH*3 + TILE_GAP_X); + if (col * (TILE_WIDTH*3 + TILE_GAP_X) + TILE_WIDTH*3 < x) + return -1; + int row = y / (TILE_HEIGHT + TILE_GAP_Y); + if (row * (TILE_HEIGHT + TILE_GAP_Y) + TILE_HEIGHT < y) + return -1; + + int no = row * HINTS_COLS + col; + if (no >= (int)rules.size()) + return -1; + + return no; +} + +bool HorHints::isActive(int ruleNo) +{ + if ((ruleNo < 0) || (ruleNo >= (int)rules.size())) + return false; + Rule *r = showExcluded ? excludedRules[ruleNo] : rules[ruleNo]; + return r != NULL; +} + + +void HorHints::save(std::ostream &stream) +{ + int cnt = numbersArr.size(); + writeInt(stream, cnt); + for (int i = 0; i < cnt; i++) { + writeInt(stream, numbersArr[i]); + writeInt(stream, rules[i] ? 0 : 1); + } + writeInt(stream, showExcluded ? 1 : 0); +} + diff --git a/horhints.h b/horhints.h new file mode 100644 index 0000000..9f2d80e --- /dev/null +++ b/horhints.h @@ -0,0 +1,41 @@ +#ifndef __HORHINTS_H__ +#define __HORHINTS_H__ + + +#include +#include "iconset.h" +#include "puzgen.h" +#include "widgets.h" + + + +class HorHints: public Widget +{ + private: + IconSet &iconSet; + typedef std::vector RulesArr; + RulesArr rules; + RulesArr excludedRules; + std::vector numbersArr; + bool showExcluded; + int highlighted; + + public: + HorHints(IconSet &is, Rules &rules); + HorHints(IconSet &is, Rules &rules, std::istream &stream); + + public: + virtual void draw(); + void drawCell(int col, int row, bool addToUpdate=true); + virtual bool onMouseButtonDown(int button, int x, int y); + void toggleExcluded(); + int getRuleNo(int x, int y); + virtual bool onMouseMove(int x, int y); + bool isActive(int ruleNo); + void save(std::ostream &stream); + void reset(Rules &rules); +}; + + +#endif + diff --git a/i18n.cpp b/i18n.cpp new file mode 100644 index 0000000..7cb9229 --- /dev/null +++ b/i18n.cpp @@ -0,0 +1,430 @@ +#include "i18n.h" +#include +#include "unicode.h" +#include "convert.h" + +#ifdef WIN32 +#include +//#include +#endif + + +Locale locale; + + +#ifdef WIN32 + +static struct _CountryMap { + wchar_t iso2[3]; + char iso3[4]; +} countries[] = { + { L"AF", "AFG" }, + { L"AL", "ALB" }, + { L"DZ", "DZA" }, + { L"AS", "ASM" }, + { L"AD", "AND" }, + { L"AO", "AGO" }, + { L"AI", "AIA" }, + { L"AQ", "ATA" }, + { L"AG", "ATG" }, + { L"AR", "ARG" }, + { L"AM", "ARM" }, + { L"AW", "ABW" }, + { L"AU", "AUS" }, + { L"AT", "AUT" }, + { L"AZ", "AZE" }, + { L"BS", "BHS" }, + { L"BH", "BHR" }, + { L"BD", "BGD" }, + { L"BB", "BRB" }, + { L"BY", "BLR" }, + { L"BE", "BEL" }, + { L"BZ", "BLZ" }, + { L"BJ", "BEN" }, + { L"BM", "BMU" }, + { L"BT", "BTN" }, + { L"BO", "BOL" }, + { L"BA", "BIH" }, + { L"BW", "BWA" }, + { L"BV", "BVT" }, + { L"BR", "BRA" }, + { L"IO", "IOT" }, + { L"BN", "BRN" }, + { L"BG", "BGR" }, + { L"BF", "BFA" }, + { L"BI", "BDI" }, + { L"KH", "KHM" }, + { L"CM", "CMR" }, + { L"CA", "CAN" }, + { L"CV", "CPV" }, + { L"KY", "CYM" }, + { L"CF", "CAF" }, + { L"TD", "TCD" }, + { L"CL", "CHL" }, + { L"CN", "CHN" }, + { L"CX", "CXR" }, + { L"CC", "CCK" }, + { L"CO", "COL" }, + { L"KM", "COM" }, + { L"CD", "COD" }, + { L"CG", "COG" }, + { L"CK", "COK" }, + { L"CR", "CRI" }, + { L"CI", "CIV" }, + { L"HR", "HRV" }, + { L"CU", "CUB" }, + { L"CY", "CYP" }, + { L"CZ", "CZE" }, + { L"DK", "DNK" }, + { L"DJ", "DJI" }, + { L"DM", "DMA" }, + { L"DO", "DOM" }, + { L"TL", "TLS" }, + { L"EC", "ECU" }, + { L"EG", "EGY" }, + { L"SV", "SLV" }, + { L"GQ", "GNQ" }, + { L"ER", "ERI" }, + { L"EE", "EST" }, + { L"ET", "ETH" }, + { L"FK", "FLK" }, + { L"FO", "FRO" }, + { L"FJ", "FJI" }, + { L"FI", "FIN" }, + { L"FR", "FRA" }, + { L"FX", "FXX" }, + { L"GF", "GUF" }, + { L"PF", "PYF" }, + { L"TF", "ATF" }, + { L"GA", "GAB" }, + { L"GM", "GMB" }, + { L"GE", "GEO" }, + { L"DE", "DEU" }, + { L"GH", "GHA" }, + { L"GI", "GIB" }, + { L"GR", "GRC" }, + { L"GL", "GRL" }, + { L"GD", "GRD" }, + { L"GP", "GLP" }, + { L"GU", "GUM" }, + { L"GT", "GTM" }, + { L"GN", "GIN" }, + { L"GW", "GNB" }, + { L"GY", "GUY" }, + { L"HT", "HTI" }, + { L"HM", "HMD" }, + { L"HN", "HND" }, + { L"HK", "HKG" }, + { L"HU", "HUN" }, + { L"IS", "ISL" }, + { L"IN", "IND" }, + { L"ID", "IDN" }, + { L"IR", "IRN" }, + { L"IQ", "IRQ" }, + { L"IE", "IRL" }, + { L"IL", "ISR" }, + { L"IT", "ITA" }, + { L"JM", "JAM" }, + { L"JP", "JPN" }, + { L"JO", "JOR" }, + { L"KZ", "KAZ" }, + { L"KE", "KEN" }, + { L"KI", "KIR" }, + { L"KP", "PRK" }, + { L"KR", "KOR" }, + { L"KW", "KWT" }, + { L"KG", "KGZ" }, + { L"LA", "LAO" }, + { L"LV", "LVA" }, + { L"LB", "LBN" }, + { L"LS", "LSO" }, + { L"LR", "LBR" }, + { L"LY", "LBY" }, + { L"LI", "LIE" }, + { L"LT", "LTU" }, + { L"LU", "LUX" }, + { L"MO", "MAC" }, + { L"MK", "MKD" }, + { L"MG", "MDG" }, + { L"MW", "MWI" }, + { L"MY", "MYS" }, + { L"MV", "MDV" }, + { L"ML", "MLI" }, + { L"MT", "MLT" }, + { L"MH", "MHL" }, + { L"MQ", "MTQ" }, + { L"MR", "MRT" }, + { L"MU", "MUS" }, + { L"YT", "MYT" }, + { L"MX", "MEX" }, + { L"FM", "FSM" }, + { L"MD", "MDA" }, + { L"MC", "MCO" }, + { L"MN", "MNG" }, + { L"MS", "MSR" }, + { L"MA", "MAR" }, + { L"MZ", "MOZ" }, + { L"MM", "MMR" }, + { L"NA", "NAM" }, + { L"NR", "NRU" }, + { L"NP", "NPL" }, + { L"NL", "NLD" }, + { L"AN", "ANT" }, + { L"NC", "NCL" }, + { L"NZ", "NZL" }, + { L"NI", "NIC" }, + { L"NE", "NER" }, + { L"NG", "NGA" }, + { L"NU", "NIU" }, + { L"NF", "NFK" }, + { L"MP", "MNP" }, + { L"NO", "NOR" }, + { L"OM", "OMN" }, + { L"PK", "PAK" }, + { L"PW", "PLW" }, + { L"PS", "PSE" }, + { L"PA", "PAN" }, + { L"PG", "PNG" }, + { L"PY", "PRY" }, + { L"PE", "PER" }, + { L"PH", "PHL" }, + { L"PN", "PCN" }, + { L"PL", "POL" }, + { L"PT", "PRT" }, + { L"PR", "PRI" }, + { L"QA", "QAT" }, + { L"RE", "REU" }, + { L"RO", "ROU" }, + { L"RU", "RUS" }, + { L"RW", "RWA" }, + { L"KN", "KNA" }, + { L"LC", "LCA" }, + { L"VC", "VCT" }, + { L"WS", "WSM" }, + { L"SM", "SMR" }, + { L"ST", "STP" }, + { L"SA", "SAU" }, + { L"SN", "SEN" }, + { L"SC", "SYC" }, + { L"SL", "SLE" }, + { L"SG", "SGP" }, + { L"SK", "SVK" }, + { L"SI", "SVN" }, + { L"SB", "SLB" }, + { L"SO", "SOM" }, + { L"ZA", "ZAF" }, + { L"GS", "SGS" }, + { L"ES", "ESP" }, + { L"LK", "LKA" }, + { L"SH", "SHN" }, + { L"PM", "SPM" }, + { L"SD", "SDN" }, + { L"SR", "SUR" }, + { L"SJ", "SJM" }, + { L"SZ", "SWZ" }, + { L"SE", "SWE" }, + { L"CH", "CHE" }, + { L"SY", "SYR" }, + { L"TW", "TWN" }, + { L"TJ", "TJK" }, + { L"TZ", "TZA" }, + { L"TH", "THA" }, + { L"TG", "TGO" }, + { L"TK", "TKL" }, + { L"TO", "TON" }, + { L"TT", "TTO" }, + { L"TN", "TUN" }, + { L"TR", "TUR" }, + { L"TM", "TKM" }, + { L"TC", "TCA" }, + { L"TV", "TUV" }, + { L"UG", "UGA" }, + { L"UA", "UKR" }, + { L"AE", "ARE" }, + { L"GB", "GBR" }, + { L"US", "USA" }, + { L"UM", "UMI" }, + { L"UY", "URY" }, + { L"UZ", "UZB" }, + { L"VU", "VUT" }, + { L"VA", "VAT" }, + { L"VE", "VEN" }, + { L"VN", "VNM" }, + { L"VG", "VGB" }, + { L"VI", "VIR" }, + { L"WF", "WLF" }, + { L"EH", "ESH" }, + { L"YE", "YEM" }, + { L"YU", "YUG" }, + { L"ZM", "ZMB" }, + { L"ZW", "ZWE" }, + { L"", "" } + }; + +static wchar_t* mapIso3ContryToIso2(char *iso3) +{ + if (! iso3) + return L""; + + struct _CountryMap *m = countries; + while (m && m->iso3[0]) { + if (! strcmp(iso3, m->iso3)) + return m->iso2; + m++; + } + + return L""; +} + +#endif + + +Locale::Locale() +{ +#ifndef WIN32 + parseLocale(fromMbcs(setlocale(LC_ALL, ""))); +#else + setlocale(LC_ALL, ""); + + char buf[100]; + int len; + len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVCTRYNAME, buf, 99); + if (len > 0) + country = mapIso3ContryToIso2(buf); + + len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, buf, 99); + if (len > 0) { + /* according to MSDN: + LOCALE_SABBREVLANGNAME Abbreviated name of the language, + created by taking the 2-letter language abbreviation from the + ISO Standard 639 and adding a third letter, as appropriate, + to indicate the sublanguage. + */ + if (len == 4) // exclude subvariant letter + buf[2] = 0; + language = toLowerCase(fromMbcs(buf)); + } + + len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDEFAULTANSICODEPAGE, buf, 99); + if (len > 0) + encoding = L"CP" + std::wstring(fromMbcs(buf)); // FIXME! +#endif + + setlocale(LC_NUMERIC, "C"); // hack because of numbers in Lua +} + + +Locale::Locale(const Locale &locale): language(locale.language), + country(locale.country), encoding(locale.encoding) +{ +} + +void Locale::parseLocale(const std::wstring &name) +{ + int pos = name.find(L'.'); + std::wstring langAndCountry; + if (pos >= 0) { + encoding = name.substr(pos + 1); + langAndCountry = name.substr(0, pos); + } else { + encoding = L""; + langAndCountry = name; + } + pos = langAndCountry.find(L'_'); + if (pos < 0) { + language = langAndCountry; + country = L""; + } else { + language = langAndCountry.substr(0, pos); + country = langAndCountry.substr(pos + 1); + } + language = toLowerCase(language); + country = toUpperCase(country); + encoding = toUpperCase(encoding); +} + + +static bool isLowerCase(const std::wstring &s) +{ + int len = s.length(); + for (int i = 0; i < len; i++) { + char ch = s[i]; + if ((ch < L'a') || (ch > L'z')) + return false; + } + return true; +} + + +static bool isUpperCase(const std::wstring &s) +{ + int len = s.length(); + for (int i = 0; i < len; i++) { + char ch = s[i]; + if ((ch < L'A') || (ch > L'Z')) + return false; + } + return true; +} + + +void splitFileName(const std::wstring &fileName, std::wstring &name, + std::wstring &ext, std::wstring &lang, std::wstring &country) +{ + int pos = fileName.find_last_of(L'.'); + if (pos <= 0) { + ext = L""; + name = fileName; + } else { + name = fileName.substr(0, pos); + ext = fileName.substr(pos + 1); + } + + pos = name.find_last_of('_'); + if ((pos <= 0) || (name.length() - pos != 3)) { + lang = L""; + country = L""; + } else { + std::wstring l = name.substr(pos + 1); + std::wstring s = name.substr(0, pos); + if (isUpperCase(l)) { // country + name = s; + country = l; + pos = name.find_last_of('_'); + if ((pos <= 0) || (name.length() - pos != 3)) + lang = L""; + else { + std::wstring l = name.substr(pos + 1); + std::wstring s = name.substr(0, pos); + if (isLowerCase(l)) { // language + name = s; + lang = l; + } else // invalid + lang = L""; + } + } else if (isLowerCase(l)) { // language + name = s; + lang = l; + country = L""; + } else { // invalid + lang = L""; + country = L""; + } + } +} + +int getScore(const std::wstring &lang, const std::wstring &country, + const Locale &locale) +{ + if ((! country.length()) && (! lang.length())) // locale independent + return 1; + + int score = 0; + if (locale.getCountry().length() && (locale.getCountry() == country)) + score += 2; + if (locale.getLanguage().length() && (locale.getLanguage() == lang)) + score += 4; + + return score; +} + diff --git a/i18n.h b/i18n.h new file mode 100644 index 0000000..636e0ac --- /dev/null +++ b/i18n.h @@ -0,0 +1,58 @@ +#ifndef __I18N_H__ +#define __I18N_H__ + + +/// \file i18n.h +/// Locale related functions + + +#include + + +/// Description of current locale +class Locale +{ + private: + std::wstring language; + std::wstring country; + std::wstring encoding; + + public: + /// Load locale + Locale(); + + /// Copy constructor + Locale(const Locale &locale); + + public: + /// Get current country. + const std::wstring& getCountry() const { return country; }; + + /// Get current language. + const std::wstring& getLanguage() const { return language; }; + + /// Get current encoding. + const std::wstring& getEncoding() const { return encoding; }; + + private: + void parseLocale(const std::wstring &name); +}; + + +// split file name to file name, extension, language name and country +// for exmaple, "story_ru_RU.txt" shoud be splited to +// name="story", extension="txt", language="ru", country="RU" +void splitFileName(const std::wstring &fileName, std::wstring &name, + std::wstring &ext, std::wstring &lang, std::wstring &country); + +// calculate relevance score between language, country and +// current locale +int getScore(const std::wstring &lang, const std::wstring &country, + const Locale &locale); + + +extern Locale locale; + + +#endif + diff --git a/iconset.cpp b/iconset.cpp new file mode 100644 index 0000000..c5626e3 --- /dev/null +++ b/iconset.cpp @@ -0,0 +1,57 @@ +#include +#include "iconset.h" +#include "utils.h" + + +IconSet::IconSet() +{ + std::wstring buf = L"xy.bmp"; + + for (int i = 0; i < 6; i++) + for (int j = 0; j < 6; j++) { + buf[1] = L'1' + j; + buf[0] = L'a' + i; + smallIcons[i][j][0] = loadImage(buf); + smallIcons[i][j][1] = adjustBrightness(smallIcons[i][j][0], 1.5, false); + buf[0] = L'A' + i; + largeIcons[i][j][0] = loadImage(buf); + largeIcons[i][j][1] = adjustBrightness(largeIcons[i][j][0], 1.5, false); + } + emptyFieldIcon = loadImage(L"tile.bmp"); + emptyHintIcon = loadImage(L"hint-tile.bmp"); + nearHintIcon[0] = loadImage(L"hint-near.bmp"); + nearHintIcon[1] = adjustBrightness(nearHintIcon[0], 1.5, false); + sideHintIcon[0] = loadImage(L"hint-side.bmp"); + sideHintIcon[1] = adjustBrightness(sideHintIcon[0], 1.5, false); + betweenArrow[0] = loadImage(L"betwarr.bmp", true); + betweenArrow[1] = adjustBrightness(betweenArrow[0], 1.5, false); +} + +IconSet::~IconSet() +{ + for (int i = 0; i < 6; i++) + for (int j = 0; j < 6; j++) + for (int k = 0; k < 2; k++) { + SDL_FreeSurface(smallIcons[i][j][k]); + SDL_FreeSurface(largeIcons[i][j][k]); + } + SDL_FreeSurface(emptyFieldIcon); + SDL_FreeSurface(emptyHintIcon); + SDL_FreeSurface(nearHintIcon[0]); + SDL_FreeSurface(nearHintIcon[1]); + SDL_FreeSurface(sideHintIcon[0]); + SDL_FreeSurface(sideHintIcon[1]); + SDL_FreeSurface(betweenArrow[0]); + SDL_FreeSurface(betweenArrow[1]); +} + +SDL_Surface* IconSet::getLargeIcon(int row, int num, bool h) +{ + return largeIcons[row][num-1][h ? 1 : 0]; +} + +SDL_Surface* IconSet::getSmallIcon(int row, int num, bool h) +{ + return smallIcons[row][num-1][h ? 1 : 0]; +} + diff --git a/iconset.h b/iconset.h new file mode 100644 index 0000000..0f971ac --- /dev/null +++ b/iconset.h @@ -0,0 +1,32 @@ +#ifndef __ICONSET_H__ +#define __ICONSET_H__ + + +#include + + +class IconSet +{ + private: + SDL_Surface *smallIcons[6][6][2]; + SDL_Surface *largeIcons[6][6][2]; + SDL_Surface *emptyFieldIcon, *emptyHintIcon, *nearHintIcon[2]; + SDL_Surface *sideHintIcon[2], *betweenArrow[2]; + + public: + IconSet(); + virtual ~IconSet(); + + public: + SDL_Surface* getLargeIcon(int row, int num, bool highlighted); + SDL_Surface* getSmallIcon(int row, int num, bool highlighted); + SDL_Surface* getEmptyFieldIcon() { return emptyFieldIcon; }; + SDL_Surface* getEmptyHintIcon() { return emptyHintIcon; }; + SDL_Surface* getNearHintIcon(bool h) { return nearHintIcon[h ? 1 : 0]; }; + SDL_Surface* getSideHintIcon(bool h) { return sideHintIcon[h ? 1 : 0]; }; + SDL_Surface* getBetweenArrow(bool h) { return betweenArrow[h ? 1 : 0]; }; +}; + + +#endif + diff --git a/lexal.cpp b/lexal.cpp new file mode 100644 index 0000000..5a36eff --- /dev/null +++ b/lexal.cpp @@ -0,0 +1,248 @@ +#include "lexal.h" +#include "convert.h" + + +Lexeme::Lexeme(Type t, const std::wstring &cont, int line, int pos) +{ + type = t; + content = cont; + this->line = line; + this->pos = pos; +} + +std::wstring Lexeme::getPosStr() const +{ + return Lexal::posToStr(line, pos); +} + + + +Lexal::Lexal(UtfStreamReader &rdr): reader(rdr) +{ + line = 1; + pos = 0; +} + + +static bool isIdentStart(wchar_t ch) { + return ((L'a' <= ch) && (L'z' >= ch)) || ((L'A' <= ch) && (L'Z' >= ch)) + || (L'_' == ch); +} + +static bool isIdentCont(wchar_t ch) { + return ((L'a' <= ch) && (L'z' >= ch)) || ((L'A' <= ch) && (L'Z' >= ch)) + || (L'_' == ch) || (L'.' == ch) || ((L'0' <= ch) && (L'9' >= ch)); +} + +static bool isWhiteSpace(wchar_t ch) { + return (L' ' == ch) || (L'\t' == ch) || (L'\n' == ch) || (L'\r' == ch); +} + +static bool isDigit(wchar_t ch) { + return (L'0' <= ch) && (L'9' >= ch); +} + +static bool isSymbol(wchar_t ch) { + return (L'{' == ch) || (L'}' == ch) || (L',' == ch) || (L'=' == ch) + || (L';' == ch); +} + +static bool isQuote(wchar_t ch) { + return (L'\'' == ch) || (L'"' == ch); +} + + +std::wstring Lexal::posToStr(int line, int pos) +{ + return L"(" + toString(line) + L":" + toString(pos) + L")"; +} + + +Lexeme Lexal::getNext() +{ + skipSpaces(); + if (reader.isEof()) + return Lexeme(Lexeme::Eof, L"", line, pos); + + int startLine = line; + int startPos = pos; + + wchar_t ch = reader.getNextChar(); + pos++; + + if (isIdentStart(ch)) + return readIdent(startLine, startPos, ch); + else if (isDigit(ch)) + return readNumber(startLine, startPos, ch); + else if (isQuote(ch)) + return readString(startLine, startPos, ch); + else if (isSymbol(ch)) + return Lexeme(Lexeme::Symbol, toString(ch), startLine, startPos); + + throw Exception(L"Invalid character at "+ posToStr(startLine, startPos)); +} + + +Lexeme Lexal::readString(int startLine, int startPos, wchar_t quote) +{ + std::wstring str; + bool closed = false; + + while (! reader.isEof()) { + wchar_t ch = reader.getNextChar(); + pos++; + if ('\n' == ch) { + line++; + pos = 0; + } else if ('\\' == ch) { + if (! reader.isEof()) { + wchar_t nextCh = reader.getNextChar(); + if (isWhiteSpace(nextCh)) + throw Exception(L"Invalid escape sequence at " + + posToStr(line, pos)); + pos++; + switch (nextCh) { + case L'\t': str += L'\t'; break; + case L'\n': str += L'\n'; break; + case L'\r': str += L'\r'; break; + default: + str += nextCh; + } + } + } else if (quote == ch) { + closed = true; + break; + } else + str += ch; + } + + if (! closed) + throw Exception(L"String at " + posToStr(startLine, startPos) + + L" doesn't closed"); + + return Lexeme(Lexeme::String, str, startLine, startPos); +} + +Lexeme Lexal::readNumber(int startLine, int startPos, wchar_t first) +{ + std::wstring number; + number += first; + Lexeme::Type type = Lexeme::Integer; + + while (! reader.isEof()) { + wchar_t ch = reader.getNextChar(); + pos++; + if (isDigit(ch)) + number += ch; + else if (L'.' == ch) { + if (Lexeme::Integer == type) { + type = Lexeme::Float; + number += ch; + } else + throw Exception(L"To many dots in number at " + + posToStr(line, pos)); + } else if ((! isSymbol(ch)) && (! isWhiteSpace(ch))) + throw Exception(L"invalid number at " + posToStr(line, pos)); + else { + pos--; + reader.ungetChar(ch); + break; + } + } + + if (L'.' == number[number.length() - 1]) + throw Exception(L"Missing digit after dot at " + posToStr(line, pos)); + + return Lexeme(type, number, startLine, startPos); +} + +Lexeme Lexal::readIdent(int startLine, int startPos, wchar_t first) +{ + std::wstring ident; + ident += first; + + while (! reader.isEof()) { + wchar_t ch = reader.getNextChar(); + if (! isIdentCont(ch)) { + reader.ungetChar(ch); + break; + } + ident += ch; + pos++; + } + + return Lexeme(Lexeme::Ident, ident, startLine, startPos); +} + + +void Lexal::skipToLineEnd() +{ + while (! reader.isEof()) { + wchar_t ch = reader.getNextChar(); + pos++; + if ('\n' == ch) { + pos = 0; + line++; + return; + } + } +} + + +void Lexal::skipMultilineComment(int startLine, int startPos) +{ + while (! reader.isEof()) { + wchar_t ch = reader.getNextChar(); + pos++; + if ('\n' == ch) { + pos = 0; + line++; + } else if (('*' == ch) && (! reader.isEof())) { + wchar_t nextCh = reader.getNextChar(); + if ('/' != nextCh) + reader.ungetChar(nextCh); + } + } + throw Exception(L"Remark started at " + posToStr(startLine, startPos) + + L" is not closed"); +} + + +void Lexal::skipSpaces() +{ + while (! reader.isEof()) { + wchar_t ch = reader.getNextChar(); + pos++; + if (! isWhiteSpace(ch)) { + if ('#' == ch) + skipToLineEnd(); + else { + bool finish = false; + if (('/' == ch) && (! reader.isEof())) { + wchar_t nextCh = reader.getNextChar(); + pos++; + if ('/' == nextCh) + skipToLineEnd(); + else if ('*' == nextCh) + skipMultilineComment(line, pos); + else { + pos--; + reader.ungetChar(nextCh); + finish = true; + } + } else + finish = true; + if (finish) { + pos--; + reader.ungetChar(ch); + return; + } + } + } else + if ('\n' == ch) { + pos = 0; + line++; + } + } +} + diff --git a/lexal.h b/lexal.h new file mode 100644 index 0000000..f5e2b9a --- /dev/null +++ b/lexal.h @@ -0,0 +1,66 @@ +#ifndef __LEXAL_H__ +#define __LEXAL_H__ + + +#include "streams.h" + + +class Lexeme +{ + public: + typedef enum Type { + String, + Integer, + Float, + Ident, + Symbol, + Eof + }; + + private: + int line; + int pos; + Type type; + std::wstring content; + + public: + Lexeme() { }; + Lexeme(Type type, const std::wstring &content, int line, int pos); + ~Lexeme() { }; + + public: + const Type getType() const { return type; }; + const std::wstring getContent() const { return content; }; + std::wstring getPosStr() const; + int getLine() const { return line; }; + int getPos() const { return pos; }; +}; + + +class Lexal +{ + private: + UtfStreamReader &reader; + int line; + int pos; + + public: + Lexal(UtfStreamReader &reader); + ~Lexal() { }; + + public: + Lexeme getNext(); + static std::wstring posToStr(int line, int pos); + + private: + void skipSpaces(); + void skipToLineEnd(); + void skipMultilineComment(int startLine, int startPos); + Lexeme readIdent(int startLine, int startPos, wchar_t first); + Lexeme readNumber(int startLine, int startPos, wchar_t first); + Lexeme readString(int startLine, int startPos, wchar_t quote); +}; + + +#endif + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..0fcae9a --- /dev/null +++ b/main.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include "main.h" +#include "utils.h" +#include "storage.h" +#include "unicode.h" +#include "messages.h" +#include "sound.h" + + +Screen screen; +Random rndGen; + + +static void initScreen() +{ + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) + throw Exception(std::wstring(L"Error initializing SDL: ") + + fromMbcs(SDL_GetError())); + atexit(SDL_Quit); + if (TTF_Init()) + throw Exception(L"Error initializing font engine"); + screen.setMode(VideoMode(800, 600, 24, + getStorage()->get(L"fullscreen", 1) != 0)); + screen.initCursors(); + + SDL_Surface *mouse = loadImage(L"cursor.bmp"); + SDL_SetColorKey(mouse, SDL_SRCCOLORKEY, SDL_MapRGB(mouse->format, 0, 0, 0)); + screen.setMouseImage(mouse); + SDL_FreeSurface(mouse); + SDL_WM_SetCaption("Einstein", NULL); + +#ifdef __APPLE__ + screen.setCursor(false); +#else + screen.setCursor(getStorage()->get(L"niceCursor", 1)); +#endif +} + +static void initAudio() +{ + sound = new Sound(); + sound->setVolume((float)getStorage()->get(L"volume", 20) / 100.0f); +} + + +#ifdef __APPLE__ +static std::wstring getResourcesPath(const std::wstring& path) +{ + int idx = path.find_last_of(L'/'); + return path.substr(0, idx) + L"/../../"; +} +#endif + +static void loadResources(const std::wstring &selfPath) +{ + StringList dirs; +#ifdef WIN32 + dirs.push_back(getStorage()->get(L"path", L"") + L"\\res"); +#else +#ifdef __APPLE__ + dirs.push_back(getResourcesPath(selfPath)); +#else + dirs.push_back(PREFIX L"/share/einstein/res"); + dirs.push_back(fromMbcs(getenv("HOME")) + L"/.einstein/res"); +#endif +#endif + dirs.push_back(L"res"); + dirs.push_back(L"."); + resources = new ResourcesCollection(dirs); + msg.load(); +} + + +/*static void checkBetaExpire() +{ + if (1124832535L + 60L*60L*24L*40L < time(NULL)) { + Font font(L"laudcn2.ttf", 16); + Area area; + showMessageWindow(&area, L"darkpattern.bmp", + 700, 100, &font, 255,255,255, + msg(L"expired")); + } +}*/ + + + +int main(int argc, char *argv[]) +{ +#ifndef WIN32 + ensureDirExists(fromMbcs(getenv("HOME")) + std::wstring(L"/.einstein")); +#endif + + try { + loadResources(fromUtf8(argv[0])); + initScreen(); + initAudio(); +// checkBetaExpire(); + menu(); + getStorage()->flush(); + } catch (Exception &e) { + std::cerr << L"ERROR: " << e.getMessage() << std::endl; + } catch (...) { + std::cerr << L"ERROR: Unknown exception" << std::endl; + } + screen.doneCursors(); + + return 0; +} + diff --git a/main.h b/main.h new file mode 100644 index 0000000..ab66891 --- /dev/null +++ b/main.h @@ -0,0 +1,16 @@ +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include "exceptions.h" +#include "screen.h" +#include "resources.h" +#include "random.h" + + +extern Screen screen; +extern Random rndGen; + +void menu(); + +#endif + diff --git a/menu.cpp b/menu.cpp new file mode 100644 index 0000000..f15becf --- /dev/null +++ b/menu.cpp @@ -0,0 +1,201 @@ +#include +#include "main.h" +#include "utils.h" +#include "widgets.h" +#include "topscores.h" +#include "opensave.h" +#include "game.h" +#include "descr.h" +#include "options.h" +#include "messages.h" + + + +class MenuBackground: public Widget +{ + public: + virtual void draw(); +}; + + +void MenuBackground::draw() +{ + SDL_Surface *title = loadImage(L"nova.bmp"); + screen.draw(0, 0, title); + SDL_FreeSurface(title); + Font font(L"nova.ttf", 28); + std::wstring s(msg(L"einsteinFlowix")); + int width = font.getWidth(s); + font.draw((screen.getWidth() - width) / 2, 30, 255,255,255, true, s); + Font urlFont(L"luximb.ttf", 16); + s = L"http://games.flowix.com"; + width = urlFont.getWidth(s); + urlFont.draw((screen.getWidth() - width) / 2, 60, 255,255,0, true, s); + screen.addRegionToUpdate(0, 0, screen.getWidth(), screen.getHeight()); +} + + + +class NewGameCommand: public Command +{ + private: + Area *area; + + public: + NewGameCommand(Area *a) { area = a; }; + + virtual void doAction() { + Game game; + game.run(); + area->updateMouse(); + area->draw(); + }; +}; + + +class LoadGameCommand: public Command +{ + private: + Area *area; + + public: + LoadGameCommand(Area *a) { area = a; }; + + virtual void doAction() { + Game *game = loadGame(area); + if (game) { + game->run(); + delete game; + } + area->updateMouse(); + area->draw(); + }; +}; + + + +class TopScoresCommand: public Command +{ + private: + Area *area; + + public: + TopScoresCommand(Area *a) { area = a; }; + + virtual void doAction() { + TopScores scores; + showScoresWindow(area, &scores); + area->updateMouse(); + area->draw(); + }; +}; + + +class RulesCommand: public Command +{ + private: + Area *area; + + public: + RulesCommand(Area *a) { area = a; }; + + virtual void doAction() { + showDescription(area); + area->updateMouse(); + area->draw(); + }; +}; + + +class OptionsCommand: public Command +{ + private: + Area *area; + + public: + OptionsCommand(Area *a) { area = a; }; + + virtual void doAction() { + showOptionsWindow(area); + area->updateMouse(); + area->draw(); + }; +}; + + +class AboutCommand: public Command +{ + private: + Area *parentArea; + + public: + AboutCommand(Area *a) { parentArea = a; }; + + virtual void doAction() { + Area area; + Font titleFont(L"nova.ttf", 26); + Font font(L"laudcn2.ttf", 14); + Font urlFont(L"luximb.ttf", 16); + +#define LABEL(pos, c, f, text) area.add(new Label(&f, 220, pos, 360, 20, \ + Label::ALIGN_CENTER, Label::ALIGN_MIDDLE, 255,255,c, text)); + area.add(parentArea); + area.add(new Window(220, 160, 360, 280, L"blue.bmp")); + area.add(new Label(&titleFont, 250, 165, 300, 40, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, 255,255,0, msg(L"about"))); + LABEL(240, 255, font, msg(L"einsteinPuzzle")) + LABEL(260, 255, font, msg(L"version")) + LABEL(280, 255, font, msg(L"copyright")) + LABEL(330, 0, urlFont, L"http://games.flowix.com") +#undef LABEL + ExitCommand exitCmd(area); + area.add(new Button(360, 400, 80, 25, &font, 255,255,0, L"blue.bmp", + msg(L"ok"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + area.add(new KeyAccel(SDLK_RETURN, &exitCmd)); + area.run(); + + parentArea->updateMouse(); + parentArea->draw(); + }; +}; + + +static Button* menuButton(int y, Font *font, const std::wstring &text, + Command *cmd=NULL) +{ + return new Button(550, y, 220, 30, font, 0,240,240, 30,255,255, text, cmd); +} + + +void menu() +{ + Area area; + Font font(L"laudcn2.ttf", 20); + + area.add(new MenuBackground()); + area.draw(); + + NewGameCommand newGameCmd(&area); + area.add(menuButton(340, &font, msg(L"newGame"), &newGameCmd)); + LoadGameCommand loadGameCmd(&area); + area.add(menuButton(370, &font, msg(L"loadGame"), &loadGameCmd)); + TopScoresCommand topScoresCmd(&area); + area.add(menuButton(400, &font, msg(L"topScores"), &topScoresCmd)); + RulesCommand rulesCmd(&area); + area.add(menuButton(430, &font, msg(L"rules"), &rulesCmd)); + OptionsCommand optionsCmd(&area); + area.add(menuButton(460, &font, msg(L"options"), &optionsCmd)); + AboutCommand aboutCmd(&area); + area.add(menuButton(490, &font, msg(L"about"), &aboutCmd)); + ExitCommand exitMenuCmd(area); + area.add(menuButton(520, &font, msg(L"exit"), &exitMenuCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitMenuCmd)); + + area.draw(); + screen.addRegionToUpdate(0, 0, screen.getWidth(), screen.getHeight()); + screen.flush(); + + area.run(); +} + diff --git a/messages.cpp b/messages.cpp new file mode 100644 index 0000000..2c9980c --- /dev/null +++ b/messages.cpp @@ -0,0 +1,135 @@ +#include + +#include "messages.h" +#include "formatter.h" +#include "resources.h" +#include "exceptions.h" +#include "buffer.h" +#include "utils.h" +#include "unicode.h" + + +Messages msg; + + +Messages::Messages() +{ +} + +Messages::~Messages() +{ +} + +class ResVisitor: public Visitor +{ + private: + Messages &messages; + Buffer *buffer; + + public: + ResVisitor(Messages &m, Buffer *b): messages(m) { buffer = b; }; + + virtual void onVisit(Resource *&r) { + messages.loadFromResource(r, buffer); + } +}; + +void Messages::load() +{ + Buffer buffer; + ResVisitor loader(*this, &buffer); + resources->forEachInGroup(L"messages", loader); +} + +void Messages::loadFromResource(Resource *res, Buffer *buffer) +{ + if (! res) return; + + int cnt = res->getVariantsCount(); + for (int i = 0; i < cnt; i++) { + ResVariant *var = res->getVariant(i); + if (var) { + try { + int score = var->getI18nScore(); + var->getData(*buffer); + loadBundle(score, (unsigned char*)buffer->getData(), + buffer->getSize()); + } catch (Exception &e) { + std::cerr << std::wstring(L"Error loading text bundle " + + res->getName() + L": " + e.getMessage()); + } + } + } +} + +std::wstring Messages::getMessage(const std::wstring &key) const +{ + StrMap::const_iterator i = messages.find(key); + if (i != messages.end()) + return (*i).second.message->getMessage(); + else + return key; +} + +std::wstring Messages::format(const wchar_t *key, va_list ap) const +{ + std::wstring s; + StrMap::const_iterator i = messages.find(key); + if (i != messages.end()) + s = (*i).second.message->format(ap); + else + s = key; + return s; +} + +std::wstring Messages::format(const wchar_t *key, ...) const +{ + va_list ap; + va_start(ap, key); + std::wstring s = format(key, ap); + va_end(ap); + return s; +} + +std::wstring Messages::operator ()(const wchar_t *key, ...) const +{ + va_list ap; + va_start(ap, key); + std::wstring s = format(key, ap); + va_end(ap); + return s; +} + +void Messages::loadBundle(int score, unsigned char *data, size_t size) +{ + if ((data[0] != 'C') || (data[1] != 'M') || (data[2] != 'F')) + throw Exception(L"Invalid format of message file"); + if (readInt(data + 3) != 1) + throw Exception(L"Unknown version of message file"); + + int offset = readInt(data + size - 4); + int cnt = readInt(data + offset); + offset += 4; + + for (int i = 0; i < cnt; i++) { + int sz = readInt(data + offset); + offset += 4; + if (sz > 0) { + std::wstring name(fromUtf8((char*)data + offset, sz)); + int msgOffset = readInt(data + offset + sz); + StrMap::iterator i = messages.find(name); + if (i == messages.end()) { + ScoredStr ss = { score, new Formatter(data, msgOffset) }; + messages[name] = ss; + } else { + ScoredStr &ss = (*i).second; + if (ss.score <= score) { + ss.score = score; + ss.message = new Formatter(data, msgOffset); + } + } + } + offset += sz + 4; + } +} + diff --git a/messages.h b/messages.h new file mode 100644 index 0000000..9e539cc --- /dev/null +++ b/messages.h @@ -0,0 +1,67 @@ +#ifndef __MESSAGES_H__ +#define __MESSAGES_H__ + + +#include +#include +#include + + +class Resource; +class Formatter; +class Buffer; + + +/// Localized messages formatter +class Messages +{ + private: + typedef struct { + int score; + Formatter *message; + } ScoredStr; + typedef std::map StrMap; + StrMap messages; + + public: + /// Create empty messages table. + Messages(); + ~Messages(); + + public: + /// Load message tables from resources. + void load(); + + /// Get simple text string + /// \param key message key + std::wstring getMessage(const std::wstring &key) const; + + /// Shorter alias for getMessage + /// \param key message key + std::wstring operator [](const std::wstring &key) const { + return getMessage(key); + }; + + /// Format message + /// \param key message key + std::wstring format(const wchar_t *key, ...) const; + + /// Shorter alias for format + /// \param key message key + std::wstring operator ()(const wchar_t *key, ...) const; + + /// Load messages from resource + /// \param res resource + void loadFromResource(Resource *res, Buffer *buffer); + + private: + void loadBundle(int score, unsigned char *data, size_t size); + std::wstring format(const wchar_t *key, va_list ap) const; +}; + + +extern Messages msg; + + +#endif + diff --git a/opensave.cpp b/opensave.cpp new file mode 100644 index 0000000..ad6dc36 --- /dev/null +++ b/opensave.cpp @@ -0,0 +1,285 @@ +#include +#include +#include "exceptions.h" +#include "utils.h" +#include "widgets.h" +#include "storage.h" +#include "opensave.h" +#include "unicode.h" +#include "convert.h" +#include "messages.h" + + + +#define MAX_SLOTS 10 + + +class SavedGame +{ + private: + std::wstring fileName; + bool exists; + std::wstring name; + + public: + SavedGame(const std::wstring &fileName); + SavedGame(const SavedGame &s): fileName(s.fileName), name(s.name) { + exists = s.exists; + }; + + public: + const std::wstring& getFileName() { return fileName; }; + std::wstring getName() { return exists ? name : msg(L"empty"); }; + bool isExists() { return exists; }; +}; + + +SavedGame::SavedGame(const std::wstring &s): fileName(s) +{ + exists = false; + + try { + std::ifstream stream(toMbcs(fileName).c_str(), std::ifstream::in | + std::ifstream::binary); + if (stream.fail()) + throw Exception(L"Can't open file"); + name = readString(stream); + stream.close(); + exists = true; + } catch (...) { } +} + + + +class OkCommand: public Command +{ + private: + Area &area; + bool *ok; + + public: + OkCommand(Area &a, bool *o): area(a) { ok = o; }; + + virtual void doAction() { + *ok = true; + area.finishEventLoop(); + }; +}; + + + +class SaveCommand: public Command +{ + private: + SavedGame &savedGame; + Area *parentArea; + bool *saved; + Font *font; + std::wstring defaultName; + Game *game; + + public: + SaveCommand(SavedGame &sg, Font *f, Area *area, bool *s, + const std::wstring &dflt, Game *g): savedGame(sg), defaultName(dflt) + { + parentArea = area; + saved = s; + font = f; + game = g; + }; + + public: + virtual void doAction() { + Area area; + area.add(parentArea, false); + area.add(new Window(170, 280, 460, 100, L"blue.bmp")); + std::wstring name; + if (savedGame.isExists()) + name = savedGame.getName(); + else + name = defaultName; + area.add(new Label(font, 180, 300, 255,255,0, msg(L"enterGame"))); + area.add(new InputField(340, 300, 280, 26, L"blue.bmp", name, 20, + 255,255,0, font)); + ExitCommand exitCmd(area); + OkCommand okCmd(area, saved); + area.add(new Button(310, 340, 80, 25, font, 255,255,0, L"blue.bmp", + msg(L"ok"), &okCmd)); + area.add(new Button(400, 340, 80, 25, font, 255,255,0, L"blue.bmp", + msg(L"cancel"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + area.add(new KeyAccel(SDLK_RETURN, &okCmd)); + area.run(); + + if (*saved) { + *saved = false; + try { + std::ofstream stream(toMbcs(savedGame.getFileName()). + c_str(), std::ofstream::out | std::ofstream::binary); + if (stream.fail()) + throw Exception(L"Error creating save file"); + writeString(stream, name); + game->save(stream); + if (stream.fail()) + throw Exception(L"Error saving game"); + stream.close(); + *saved = true; + } catch (...) { + showMessageWindow(&area, L"redpattern.bmp", 300, 80, font, + 255,255,255, msg(L"saveError")); + } + parentArea->finishEventLoop(); + } else { + parentArea->updateMouse(); + parentArea->draw(); + } + }; +}; + + +static std::wstring getSavesPath() +{ +#ifndef WIN32 + std::wstring path(fromMbcs(getenv("HOME")) + + std::wstring(L"/.einstein/save")); +#else + std::wstring path(getStorage()->get(L"path", L"")); + if (path.length() > 0) + path += L"\\"; + path += L"save"; +#endif + ensureDirExists(path); + + return path; +} + + +typedef std::list SavesList; + + +static void showListWindow(SavesList &list, Command **commands, + const std::wstring &title, Area &area, Font *font) +{ + Font titleFont(L"nova.ttf", 26); + + area.add(new Window(250, 90, 300, 420, L"blue.bmp")); + area.add(new Label(&titleFont, 250, 95, 300, 40, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, 255,255,0, title)); + ExitCommand exitCmd(area); + area.add(new Button(360, 470, 80, 25, font, 255,255,0, L"blue.bmp", + msg(L"close"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + + int pos = 150; + int no = 0; + for (SavesList::iterator i = list.begin(); i != list.end(); i++) { + SavedGame &game = *i; + area.add(new Button(260, pos, 280, 25, font, 255,255,255, L"blue.bmp", + game.getName(), commands[no++])); + pos += 30; + } + + area.run(); +} + + +bool saveGame(Area *parentArea, Game *game) +{ + std::wstring path = getSavesPath(); + + Area area; + area.add(parentArea, false); + Font font(L"laudcn2.ttf", 14); + bool saved = false; + + SavesList list; + Command **commands = new Command*[MAX_SLOTS]; + for (int i = 0; i < MAX_SLOTS; i++) { + SavedGame sg(path + L"/" + toString(i) + L".sav"); + list.push_back(sg); + commands[i] = new SaveCommand(*(--(list.end())), &font, + &area, &saved, L"game " + toString(i+1), game); + } + + showListWindow(list, commands, msg(L"saveGame"), area, &font); + + for (int i = 0; i < MAX_SLOTS; i++) + delete commands[i]; + delete[] commands; + + return saved; +} + + +class LoadCommand: public Command +{ + private: + SavedGame &savedGame; + Area *parentArea; + bool *saved; + Font *font; + std::wstring defaultName; + Game **game; + + public: + LoadCommand(SavedGame &sg, Font *f, Area *area, Game **g): + savedGame(sg) + { + parentArea = area; + font = f; + game = g; + }; + + public: + virtual void doAction() { + try { + std::ifstream stream(toMbcs(savedGame.getFileName()).c_str(), + std::ifstream::in | std::ifstream::binary); + if (stream.fail()) + throw Exception(L"Error opening save file"); + readString(stream); + Game *g = new Game(stream); + if (stream.fail()) + throw Exception(L"Error loading game"); + stream.close(); + *game = g; + } catch (...) { + showMessageWindow(parentArea, L"redpattern.bmp", 300, 80, font, + 255,255,255, L"Error loadng game"); + } + parentArea->finishEventLoop(); + }; +}; + + +Game* loadGame(Area *parentArea) +{ + std::wstring path = getSavesPath(); + + Area area; + area.add(parentArea, false); + Font font(L"laudcn2.ttf", 14); + + Game *newGame = NULL; + + SavesList list; + Command **commands = new Command*[MAX_SLOTS]; + for (int i = 0; i < MAX_SLOTS; i++) { + SavedGame sg(path + L"/" + toString(i) + L".sav"); + list.push_back(sg); + if (sg.isExists()) + commands[i] = new LoadCommand(*(--(list.end())), &font, &area, + &newGame); + else + commands[i] = NULL; + } + + showListWindow(list, commands, msg(L"loadGame"), area, &font); + + for (int i = 0; i < MAX_SLOTS; i++) + delete commands[i]; + delete[] commands; + + return newGame; +} + diff --git a/opensave.h b/opensave.h new file mode 100644 index 0000000..8bcdc33 --- /dev/null +++ b/opensave.h @@ -0,0 +1,16 @@ +#ifndef __OPENSAVE_H__ +#define __OPENSAVE_H__ + + +#include "widgets.h" +#include "game.h" + + + +bool saveGame(Area *parentArea, Game *game); +Game* loadGame(Area *parentArea); + + + +#endif + diff --git a/options.cpp b/options.cpp new file mode 100644 index 0000000..63a7e44 --- /dev/null +++ b/options.cpp @@ -0,0 +1,88 @@ +#include "options.h" +#include "storage.h" +#include "main.h" +#include "messages.h" +#include "sound.h" + + +class OptionsChangedCommand: public Command +{ + private: + bool &fullscreen; + bool &niceCursor; + float &volume; + Area *area; + + public: + OptionsChangedCommand(Area *a, bool &fs, bool &ns, float &v): + fullscreen(fs), niceCursor(ns), volume(v) { + area = a; + }; + + virtual void doAction() { + bool oldFullscreen = (getStorage()->get(L"fullscreen", 1) != 0); + bool oldCursor = (getStorage()->get(L"niceCursor", 1) != 0); + float oldVolume = (float)getStorage()->get(L"volume", 20) / 100.0f; + if (fullscreen != oldFullscreen) { + getStorage()->set(L"fullscreen", fullscreen); + screen.setMode(VideoMode(800, 600, 24, fullscreen)); + } +#ifndef __APPLE__ + if (niceCursor != oldCursor) { + getStorage()->set(L"niceCursor", niceCursor); + screen.setCursor(niceCursor); + } +#endif + if (volume != oldVolume) { + getStorage()->set(L"volume", (int)(volume * 100.0f)); + sound->setVolume(volume); + } + getStorage()->flush(); + area->finishEventLoop(); + }; +}; + + +#define LABEL(y, s) \ + area.add(new Label(&font, 300, y, 300, 20, Label::ALIGN_LEFT, \ + Label::ALIGN_MIDDLE, 255,255,255, msg(s))); +#define CHECKBOX(y, var) \ + area.add(new Checkbox(265, y, 20, 20, &font, 255,255,255, L"blue.bmp", \ + var)); +#define OPTION(y, s, var) LABEL(y, s) CHECKBOX(y, var) + +void showOptionsWindow(Area *parentArea) +{ + Font titleFont(L"nova.ttf", 26); + Font font(L"laudcn2.ttf", 14); + + bool fullscreen = (getStorage()->get(L"fullscreen", 1) != 0); + bool niceCursor = (getStorage()->get(L"niceCursor", 1) != 0); + float volume = ((float)getStorage()->get(L"volume", 20)) / 100.0f; + + Area area; + + area.add(parentArea); + area.add(new Window(250, 170, 300, 260, L"blue.bmp")); + area.add(new Label(&titleFont, 250, 175, 300, 40, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, 255,255,0, msg(L"options"))); + OPTION(260, L"fullscreen", fullscreen); +#ifndef __APPLE__ + OPTION(280, L"niceCursor", niceCursor); +#endif + + area.add(new Label(&font, 265, 330, 300, 20, Label::ALIGN_LEFT, + Label::ALIGN_MIDDLE, 255,255,255, msg(L"volume"))); + area.add(new Slider(360, 332, 160, 16, volume)); + + ExitCommand exitCmd(area); + OptionsChangedCommand okCmd(&area, fullscreen, niceCursor, volume); + area.add(new Button(315, 390, 85, 25, &font, 255,255,0, L"blue.bmp", + msg(L"ok"), &okCmd)); + area.add(new Button(405, 390, 85, 25, &font, 255,255,0, L"blue.bmp", + msg(L"cancel"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + area.add(new KeyAccel(SDLK_RETURN, &okCmd)); + area.run(); +} + diff --git a/options.h b/options.h new file mode 100644 index 0000000..a616719 --- /dev/null +++ b/options.h @@ -0,0 +1,13 @@ +#ifndef __OPTIONS_H__ +#define __OPTIONS_H__ + + +#include "widgets.h" + + + +void showOptionsWindow(Area *area); + + +#endif + diff --git a/puzgen.cpp b/puzgen.cpp new file mode 100644 index 0000000..6c6c18d --- /dev/null +++ b/puzgen.cpp @@ -0,0 +1,394 @@ +#include +#include +#include +#include +#include +#include "puzgen.h" +#include "exceptions.h" +#include "utils.h" + + + +Possibilities::Possibilities() +{ + reset(); +} + +Possibilities::Possibilities(std::istream &stream) +{ + for (int row = 0; row < PUZZLE_SIZE; row++) + for (int col = 0; col < PUZZLE_SIZE; col++) + for (int element = 0; element < PUZZLE_SIZE; element++) + pos[col][row][element] = readInt(stream); +} + +void Possibilities::reset() +{ + for (int i = 0; i < PUZZLE_SIZE; i++) + for (int j = 0; j < PUZZLE_SIZE; j++) + for (int k = 0; k < PUZZLE_SIZE; k++) + pos[i][j][k] = k + 1; +} + +void Possibilities::checkSingles(int row) +{ + int cellsCnt[PUZZLE_SIZE]; // count of elements in cells + int elsCnt[PUZZLE_SIZE]; // total count of elements in row + int elements[PUZZLE_SIZE]; // one element of each cell + int elCells[PUZZLE_SIZE]; // one cell of each element + + memset(cellsCnt, 0, sizeof(cellsCnt)); + memset(elsCnt, 0, sizeof(elsCnt)); + memset(elements, 0, sizeof(elements)); + memset(elCells, 0, sizeof(elCells)); + + // check if there is only one element left in cell(col, row) + for (int col = 0; col < PUZZLE_SIZE; col++) + for (int i = 0; i < PUZZLE_SIZE; i++) { + if (pos[col][row][i]) { + elsCnt[i]++; + elCells[i] = col; + cellsCnt[col]++; + elements[col] = i + 1; + } + } + + bool changed = false; + + // check for cells with single element + for (int col = 0; col < PUZZLE_SIZE; col++) { + if ((cellsCnt[col] == 1) && (elsCnt[elements[col] - 1] != 1)) { + // there is only one element in cell but it used somewhere else + int e = elements[col] - 1; + for (int i = 0; i < PUZZLE_SIZE; i++) + if (i != col) + pos[i][row][e] = 0; + changed = true; + } + } + + // check for single element without exclusive cell + for (int el = 0; el < PUZZLE_SIZE; el++) + if ((elsCnt[el] == 1) && (cellsCnt[elCells[el]] != 1)) { + int col = elCells[el]; + for (int i = 0; i < PUZZLE_SIZE; i++) + if (i != el) + pos[col][row][i] = 0; + changed = true; + } + + if (changed) + checkSingles(row); +} + +void Possibilities::exclude(int col, int row, int element) +{ + if (! pos[col][row][element - 1]) + return; + + pos[col][row][element - 1] = 0; + + checkSingles(row); +} + +void Possibilities::set(int col, int row, int element) +{ + for (int i = 0; i < PUZZLE_SIZE; i++) + if ((i != element - 1)) + pos[col][row][i] = 0; + else + pos[col][row][i] = element; + + for (int j = 0; j < PUZZLE_SIZE; j++) + if (j != col) + pos[j][row][element - 1] = 0; + + checkSingles(row); +} + +bool Possibilities::isPossible(int col, int row, int element) +{ + return pos[col][row][element - 1] == element; +} + +bool Possibilities::isDefined(int col, int row) +{ + int solvedCnt = 0, unsolvedCnt = 0; + for (int i = 0; i < PUZZLE_SIZE; i++) + if (! pos[col][row][i]) + unsolvedCnt++; + else + solvedCnt++; + return ((unsolvedCnt == PUZZLE_SIZE-1) && (solvedCnt == 1)); +} + + +int Possibilities::getDefined(int col, int row) +{ + for (int i = 0; i < PUZZLE_SIZE; i++) + if (pos[col][row][i]) + return i + 1; + return 0; +} + + +bool Possibilities::isSolved() +{ + for (int i = 0; i < PUZZLE_SIZE; i++) + for (int j = 0; j < PUZZLE_SIZE; j++) + if (! isDefined(i, j)) + return false; + return true; +} + + +bool Possibilities::isValid(SolvedPuzzle &puzzle) +{ + for (int row = 0; row < PUZZLE_SIZE; row++) + for (int col = 0; col < PUZZLE_SIZE; col++) + if (! isPossible(col, row, puzzle[row][col])) + return false; + return true; +} + + +int Possibilities::getPosition(int row, int element) +{ + int cnt = 0; + int lastPos = -1; + + for (int i = 0; i < PUZZLE_SIZE; i++) + if (pos[i][row][element - 1] == element) { + cnt++; + lastPos = i; + } + + return cnt == 1 ? lastPos : -1; +} + +void Possibilities::print() +{ + for (int row = 0; row < PUZZLE_SIZE; row++) { + std::cout << (char)('A' + row) << " "; + for (int col = 0; col < PUZZLE_SIZE; col++) { + for (int i = 0; i < PUZZLE_SIZE; i++) + if (pos[col][row][i]) + std::cout << pos[col][row][i]; + else + std::cout << " "; + std::cout << " "; + } + std::cout << std::endl; + } +} + +void Possibilities::makePossible(int col, int row, int element) +{ + pos[col][row][element-1] = element; +} + +void Possibilities::save(std::ostream &stream) +{ + for (int row = 0; row < PUZZLE_SIZE; row++) + for (int col = 0; col < PUZZLE_SIZE; col++) + for (int element = 0; element < PUZZLE_SIZE; element++) + writeInt(stream, pos[col][row][element]); +} + + +static void shuffle(short arr[PUZZLE_SIZE]) +{ + int a, b, c; + + for (int i = 0; i < 30; i++) { + a = (int)(((double)PUZZLE_SIZE)*rand()/(RAND_MAX+1.0)); + if ((a < 0) || (a >= PUZZLE_SIZE)) { + std::cerr << "Index error" << std::endl; + exit(1); + } + b = (int)(((double)PUZZLE_SIZE)*rand()/(RAND_MAX+1.0)); + if ((b < 0) || (b >= PUZZLE_SIZE)) { + std::cerr << "Index error" << std::endl; + exit(1); + } + c = arr[a]; + arr[a] = arr[b]; + arr[b] = c; + } +} + + +static bool canSolve(SolvedPuzzle &puzzle, Rules &rules) +{ + Possibilities pos; + bool changed = false; + + do { + changed = false; + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) { + Rule *rule = *i; + if (rule->apply(pos)) { + changed = true; + if (! pos.isValid(puzzle)) { +std::cout << "after error:" << std::endl; +pos.print(); + throw Exception(L"Invalid possibilities after rule " + + rule->getAsText()); + } + } + } + } while (changed); + + bool res = pos.isSolved(); + return res; +} + + +static void removeRules(SolvedPuzzle &puzzle, Rules &rules) +{ + bool possible; + + do { + possible = false; + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) { + Rule *rule = *i; + Rules excludedRules = rules; + excludedRules.remove(rule); + if (canSolve(puzzle, excludedRules)) { + possible = true; + rules.remove(rule); + delete rule; + break; + } + } + } while (possible); +} + + +static void genRules(SolvedPuzzle &puzzle, Rules &rules) +{ + bool rulesDone = false; + + do { + Rule *rule = genRule(puzzle); + if (rule) { + std::wstring s = rule->getAsText(); + for (std::list::iterator i = rules.begin(); + i != rules.end(); i++) + if ((*i)->getAsText() == s) { + delete rule; + rule = NULL; + break; + } + if (rule) { +//printf("adding rule %s\n", rule->getAsText().c_str()); + rules.push_back(rule); + rulesDone = canSolve(puzzle, rules); + } + } + } while (! rulesDone); +} + + +/*static void printPuzzle(SolvedPuzzle &puzzle) +{ + for (int i = 0; i < PUZZLE_SIZE; i++) { + char prefix = 'A' + i; + for (int j = 0; j < PUZZLE_SIZE; j++) { + if (j) + std::cout << " "; + std::cout << prefix << puzzle[i][j]; + } + std::cout << std::endl; + } +} + + +static void printRules(Rules &rules) +{ + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) + std::cout << (*i)->getAsText() << std::endl;; +}*/ + + +void genPuzzle(SolvedPuzzle &puzzle, Rules &rules) +{ + for (int i = 0; i < PUZZLE_SIZE; i++) { + for (int j = 0; j < PUZZLE_SIZE; j++) + puzzle[i][j] = j + 1; + shuffle(puzzle[i]); + } + + genRules(puzzle, rules); + removeRules(puzzle, rules); +//printPuzzle(puzzle); +//printRules(rules); +} + + +void openInitial(Possibilities &possib, Rules &rules) +{ + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) { + Rule *r = *i; + if (r->applyOnStart()) + r->apply(possib); + } +} + + +void getHintsQty(Rules &rules, int &vert, int &horiz) +{ + vert = 0; + horiz = 0; + + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) { + Rule::ShowOptions so = (*i)->getShowOpts(); + switch (so) { + case Rule::SHOW_VERT: vert++; break; + case Rule::SHOW_HORIZ: horiz++; break; + default: ; + } + } +} + +void savePuzzle(SolvedPuzzle &puzzle, std::ostream &stream) +{ + for (int row = 0; row < PUZZLE_SIZE; row++) + for (int col = 0; col < PUZZLE_SIZE; col++) + writeInt(stream, puzzle[row][col]); +} + +void loadPuzzle(SolvedPuzzle &puzzle, std::istream &stream) +{ + for (int row = 0; row < PUZZLE_SIZE; row++) + for (int col = 0; col < PUZZLE_SIZE; col++) + puzzle[row][col] = readInt(stream); +} + + +Rule* getRule(Rules &rules, int no) +{ + int j = 0; + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) { + if (j == no) + return *i; + j++; + } + throw Exception(L"Rule is not found"); +} + + +/*int main(int argc, char *argv[]) +{ + srand(time(NULL)); + + Rules rules; + Puzzle puzzle; + + genPuzzle(puzzle, rules); + printPuzzle(puzzle); + printRules(rules); + + return 0; +}*/ + diff --git a/puzgen.h b/puzgen.h new file mode 100644 index 0000000..855dbbd --- /dev/null +++ b/puzgen.h @@ -0,0 +1,81 @@ +#ifndef __PUZGEN_H__ +#define __PUZGEN_H__ + + +#include +#include +#include +#include "iconset.h" + + +#define PUZZLE_SIZE 6 + + +typedef short SolvedPuzzle[PUZZLE_SIZE][PUZZLE_SIZE]; + + +class Possibilities +{ + private: + short pos[PUZZLE_SIZE][PUZZLE_SIZE][PUZZLE_SIZE]; + + public: + Possibilities(); + Possibilities(std::istream &stream); + + public: + void exclude(int col, int row, int element); + void set(int col, int row, int element); + bool isPossible(int col, int row, int element); + bool isDefined(int col, int row); + int getDefined(int col, int row); + int getPosition(int row, int element); + bool isSolved(); + void print(); + bool isValid(SolvedPuzzle &puzzle); + void makePossible(int col, int row, int element); + void save(std::ostream &stream); + void reset(); + void checkSingles(int row); +}; + + +class Rule +{ + public: + typedef enum { + SHOW_VERT, + SHOW_HORIZ, + SHOW_NOTHING + } ShowOptions; + + public: + virtual ~Rule() { }; + + public: + virtual std::wstring getAsText() = 0; + virtual bool apply(Possibilities &pos) = 0; + virtual bool applyOnStart() { return false; }; + virtual ShowOptions getShowOpts() { return SHOW_NOTHING; }; + virtual void draw(int x, int y, IconSet &iconSet, bool highlight) = 0; + virtual void save(std::ostream &stream) = 0; +}; + + +typedef std::list Rules; + + +void genPuzzle(SolvedPuzzle &puzzle, Rules &rules); +void openInitial(Possibilities &possib, Rules &rules); +Rule* genRule(SolvedPuzzle &puzzle); +void getHintsQty(Rules &rules, int &vert, int &horiz); +Rule* getRule(Rules &rules, int no); + +void savePuzzle(SolvedPuzzle &puzzle, std::ostream &stream); +void loadPuzzle(SolvedPuzzle &puzzle, std::istream &stream); +void saveRules(Rules &rules, std::ostream &stream); +void loadRules(Rules &rules, std::istream &stream); + + +#endif + diff --git a/puzzle.cpp b/puzzle.cpp new file mode 100644 index 0000000..c0825cf --- /dev/null +++ b/puzzle.cpp @@ -0,0 +1,196 @@ +#include "puzzle.h" +#include "main.h" +#include "utils.h" +#include "sound.h" + + +#define FIELD_OFFSET_X 12 +#define FIELD_OFFSET_Y 68 +#define FIELD_GAP_X 4 +#define FIELD_GAP_Y 4 +#define FIELD_TILE_WIDTH 48 +#define FIELD_TILE_HEIGHT 48 + + +Puzzle::Puzzle(IconSet &is, SolvedPuzzle &s, Possibilities *p): + iconSet(is), solved(s) +{ + possib = p; + + reset(); +} + + +Puzzle::~Puzzle() +{ +} + +void Puzzle::reset() +{ + valid = true; + win = false; + + int x, y; + SDL_GetMouseState(&x, &y); + getCellNo(x, y, hCol, hRow, subHNo); +} + +void Puzzle::draw() +{ + for (int i = 0; i < PUZZLE_SIZE; i++) + for (int j = 0; j < PUZZLE_SIZE; j++) + drawCell(i, j, true); +} + +void Puzzle::drawCell(int col, int row, bool addToUpdate) +{ + int posX = FIELD_OFFSET_X + col * (FIELD_TILE_WIDTH + FIELD_GAP_X); + int posY = FIELD_OFFSET_Y + row * (FIELD_TILE_HEIGHT + FIELD_GAP_Y); + + if (possib->isDefined(col, row)) { + int element = possib->getDefined(col, row); + if (element > 0) + screen.draw(posX, posY, iconSet.getLargeIcon(row, element, + (hCol == col) && (hRow == row))); + } else { + screen.draw(posX, posY, iconSet.getEmptyFieldIcon()); + int x = posX; + int y = posY + (FIELD_TILE_HEIGHT / 6); + for (int i = 0; i < 6; i++) { + if (possib->isPossible(col, row, i + 1)) + screen.draw(x, y, iconSet.getSmallIcon(row, i + 1, + (hCol == col) && (hRow == row) && (i + 1 == subHNo))); + if (i == 2) { + x = posX; + y += (FIELD_TILE_HEIGHT / 3); + } else + x += (FIELD_TILE_WIDTH / 3); + } + } + if (addToUpdate) + screen.addRegionToUpdate(posX, posY, FIELD_TILE_WIDTH, + FIELD_TILE_HEIGHT); +} + + +void Puzzle::drawRow(int row, bool addToUpdate) +{ + for (int i = 0; i < PUZZLE_SIZE; i++) + drawCell(i, row, addToUpdate); +} + + +bool Puzzle::onMouseButtonDown(int button, int x, int y) +{ + int col, row, element; + + if (! getCellNo(x, y, col, row, element)) + return false; + + if (! possib->isDefined(col, row)) { + /*if (button == 3) { + for (int i = 1; i <= PUZZLE_SIZE; i++) + possib->makePossible(col, row, i); + drawCell(col, row); + } + } else {*/ + if (element == -1) + return false; + if (button == 1) { + if (possib->isPossible(col, row, element)) { + possib->set(col, row, element); + sound->play(L"laser.wav"); + } + } else if (button == 3) { + if (possib->isPossible(col, row, element)) { + possib->exclude(col, row, element); + sound->play(L"whizz.wav"); + } + /*else + possib->makePossible(col, row, element);*/ + } + drawRow(row); + } + + bool valid = possib->isValid(solved); + if (! valid) + onFail(); + else + if (possib->isSolved() && valid) + onVictory(); + + return true; +} + + +void Puzzle::onFail() +{ + if (failCommand) + failCommand->doAction(); +} + + +void Puzzle::onVictory() +{ + if (winCommand) + winCommand->doAction(); +} + +bool Puzzle::getCellNo(int x, int y, int &col, int &row, int &subNo) +{ + col = row = subNo = -1; + + if (! isInRect(x, y, FIELD_OFFSET_X, FIELD_OFFSET_Y, + (FIELD_TILE_WIDTH + FIELD_GAP_X) * PUZZLE_SIZE, + (FIELD_TILE_HEIGHT + FIELD_GAP_Y) * PUZZLE_SIZE)) + return false; + + x = x - FIELD_OFFSET_X; + y = y - FIELD_OFFSET_Y; + + col = x / (FIELD_TILE_WIDTH + FIELD_GAP_X); + if (col * (FIELD_TILE_WIDTH + FIELD_GAP_X) + FIELD_TILE_WIDTH < x) + return false; + row = y / (FIELD_TILE_HEIGHT + FIELD_GAP_Y); + if (row * (FIELD_TILE_HEIGHT + FIELD_GAP_Y) + FIELD_TILE_HEIGHT < y) + return false; + + x = x - col * (FIELD_TILE_WIDTH + FIELD_GAP_X); + y = y - row * (FIELD_TILE_HEIGHT + FIELD_GAP_Y) + - FIELD_TILE_HEIGHT / 6; + if ((y < 0) || (y >= (FIELD_TILE_HEIGHT / 3) * 2)) + return true; + int cCol = x / (FIELD_TILE_WIDTH / 3); + if (cCol >= 3) { + col = row = -1; + return false; + } + int cRow = y / (FIELD_TILE_HEIGHT / 3); + subNo = cRow * 3 + cCol + 1; + + return true; +} + +bool Puzzle::onMouseMove(int x, int y) +{ + int oldCol = hCol; + int oldRow = hRow; + int oldElement = subHNo; + + getCellNo(x, y, hCol, hRow, subHNo); + if ((hCol != oldCol) || (hRow != oldRow) || (subHNo != oldElement)) { + if ((oldCol != -1) && (oldRow != -1)) + drawCell(oldCol, oldRow); + if ((hCol != -1) && (hRow != -1)) + drawCell(hCol, hRow); + } + + return false; +} + +void Puzzle::setCommands(Command *win, Command *fail) +{ + winCommand = win; + failCommand = fail; +} + diff --git a/puzzle.h b/puzzle.h new file mode 100644 index 0000000..53c576e --- /dev/null +++ b/puzzle.h @@ -0,0 +1,44 @@ +#ifndef __PUZZLE_H__ +#define __PUZZLE_H__ + + +#include "iconset.h" +#include "puzgen.h" +#include "widgets.h" + + +class Puzzle: public Widget +{ + private: + Possibilities *possib; + IconSet &iconSet; + bool valid; + bool win; + SolvedPuzzle &solved; + int hCol, hRow; + int subHNo; + Command *winCommand, *failCommand; + + public: + Puzzle(IconSet &is, SolvedPuzzle &solved, Possibilities *possib); + virtual ~Puzzle(); + + public: + virtual void draw(); + void drawRow(int row, bool addToUpdate=true); + void drawCell(int col, int row, bool addToUpdate=true); + Possibilities* getPossibilities() { return possib; }; + virtual bool onMouseButtonDown(int button, int x, int y); + bool isValid() const { return valid; }; + bool victory() const { return win; }; + void onFail(); + void onVictory(); + bool getCellNo(int x, int y, int &col, int &row, int &subNo); + virtual bool onMouseMove(int x, int y); + void setCommands(Command *winCommand, Command *failCommand); + void reset(); +}; + + +#endif + diff --git a/random.cpp b/random.cpp new file mode 100644 index 0000000..3550ee1 --- /dev/null +++ b/random.cpp @@ -0,0 +1,159 @@ +#include "random.h" +#include "utils.h" + +/* Period parameters */ +#define M 397 +#define MATRIX_A 0x9908b0dfUL /* constant vector a */ +#define UPPER_MASK 0x80000000UL /* most significant w-r bits */ +#define LOWER_MASK 0x7fffffffUL /* least significant r bits */ + + +Random::Random() +{ + mti = RAND_N+1; /* mti==RAND_N+1 means mt[RAND_N] is not initialized */ + struct timeval tv; + gettimeofday(&tv); + unsigned long long int s = tv.tv_sec * 1000000 + tv.tv_usec; + initLong(s); +} + +Random::Random(unsigned long int seed) +{ + mti = RAND_N+1; /* mti==RAND_N+1 means mt[RAND_N] is not initialized */ + initLong(seed); +} + + +/* initialize by an array with array-length */ +/* init_key is the array for initializing keys */ +/* key_length is its length */ +Random::Random(int init_key[], int key_length) +{ + mti = RAND_N+1; /* mti==RAND_N+1 means mt[RAND_N] is not initialized */ + + int i, j, k; + initLong(19650218UL); + i=1; j=0; + k = (RAND_N>key_length ? RAND_N : key_length); + for (; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL)) + + init_key[j] + j; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; j++; + if (i>=RAND_N) { mt[0] = mt[RAND_N-1]; i=1; } + if (j>=key_length) j=0; + } + for (k=RAND_N-1; k; k--) { + mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL)) + - i; /* non linear */ + mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */ + i++; + if (i>=RAND_N) { mt[0] = mt[RAND_N-1]; i=1; } + } + + mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */ +} + + + +Random::~Random() +{ +} + +/* initializes mt[RAND_N] with a seed */ +void Random::initLong(unsigned long s) +{ + mt[0]= s & 0xffffffffUL; + for (mti=1; mti> 30)) + mti); + /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */ + /* In the previous versions, MSBs of the seed affect */ + /* only MSBs of the array mt[]. */ + /* 2002/01/09 modified by Makoto Matsumoto */ + mt[mti] &= 0xffffffffUL; + /* for >32 bit machines */ + } +} + + +/* generates a random number on [0,0xffffffff]-interval */ +unsigned long Random::genInt32(void) +{ + unsigned long y; + static unsigned long mag01[2]={0x0UL, MATRIX_A}; + /* mag01[x] = x * MATRIX_A for x=0,1 */ + + if (mti >= RAND_N) { /* generate N words at one time */ + int kk; + + if (mti == RAND_N+1) /* if init_genrand() has not been called, */ + initLong(5489UL); /* a default initial seed is used */ + + for (kk=0;kk> 1) ^ mag01[y & 0x1UL]; + } + for (;kk> 1) ^ mag01[y & 0x1UL]; + } + y = (mt[RAND_N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK); + mt[RAND_N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL]; + + mti = 0; + } + + y = mt[mti++]; + + /* Tempering */ + y ^= (y >> 11); + y ^= (y << 7) & 0x9d2c5680UL; + y ^= (y << 15) & 0xefc60000UL; + y ^= (y >> 18); + + return y; +} + +/* generates a random number on [0,0x7fffffff]-interval */ +long Random::genInt31() +{ + return (long)(genInt32()>>1); +} + +/* generates a random number on [0,1]-real-interval */ +double Random::genReal1(void) +{ + return genInt32()*(1.0/4294967295.0); + /* divided by 2^32-1 */ +} + +/* generates a random number on [0,1)-real-interval */ +double Random::genReal2() +{ + return genInt32()*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on (0,1)-real-interval */ +double Random::genReal3(void) +{ + return (((double)genInt32()) + 0.5)*(1.0/4294967296.0); + /* divided by 2^32 */ +} + +/* generates a random number on [0,1) with 53-bit resolution*/ +double Random::genReal53(void) +{ + unsigned long a=genInt32()>>5, b=genInt32()>>6; + return(a*67108864.0+b)*(1.0/9007199254740992.0); +} + +/* generate integer random number on [0, range) int interval */ +int Random::genInt(int range) +{ + int v = (int)(genReal2() * range); +if (v == range) printf("ooops!\n"); + return v; +} + diff --git a/random.h b/random.h new file mode 100644 index 0000000..9bb00a6 --- /dev/null +++ b/random.h @@ -0,0 +1,41 @@ +#ifndef __RANDOM_H__ +#define __RANDOM_H__ + + + +class Random +{ + private: +#define RAND_N 624 + unsigned long mt[RAND_N]; /* the array for the state vector */ + int mti; /* mti==RAND_N+1 means mt[RAND_N] is not initialized */ + + public: + Random(); + Random(unsigned long int seed); + Random(int keys[], int length); + ~Random(); + + public: + /* generates a random number on [0,0xffffffff]-interval */ + unsigned long int genInt32(); + /* generates a random number on [0,0x7fffffff]-interval */ + long int genInt31(); + /* generates a random number on [0,1]-real-interval */ + double genReal1(); + /* generates a random number on [0,1)-real-interval */ + double genReal2(); + /* generates a random number on (0,1)-real-interval */ + double genReal3(); + /* generates a random number on [0,1) with 53-bit resolution*/ + double genReal53(); + /* generate integer random number on [0, range) int interval */ + int genInt(int range); + + private: + void initLong(unsigned long int s); +}; + + +#endif + diff --git a/regstorage.cpp b/regstorage.cpp new file mode 100644 index 0000000..a02be8b --- /dev/null +++ b/regstorage.cpp @@ -0,0 +1,131 @@ +#ifdef WIN32 // Win32 only +#include "regstorage.h" +#include "unicode.h" + + +RegistryStorage::RegistryStorage() +{ + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, + "SOFTWARE\\Flowix Games\\Einstein\\2.0", + 0, KEY_READ, &globalKey)) + globalKey = NULL; + if (RegOpenKeyEx(HKEY_CURRENT_USER, + "SOFTWARE\\Flowix Games\\Einstein\\2.0", + 0, KEY_READ | KEY_WRITE, &userKey)) + { + if (RegCreateKeyEx(HKEY_CURRENT_USER, + "SOFTWARE\\Flowix Games\\Einstein\\2.0", 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE, + NULL, &userKey, NULL)) + userKey = NULL; + } +} + +RegistryStorage::~RegistryStorage() +{ + if (globalKey) + RegCloseKey(globalKey); + if (userKey) + RegCloseKey(userKey); +} + +int RegistryStorage::get(const std::wstring &name, int dflt) +{ + std::string uname(toUtf8(name)); + + if (globalKey) { + DWORD data; + DWORD size = sizeof(data); + DWORD type; + if (! RegQueryValueEx(globalKey, uname.c_str(), NULL, &type, + (BYTE*)&data, &size)) + if (type == REG_DWORD) + return data; + } + + if (userKey) { + DWORD data; + DWORD size = sizeof(data); + DWORD type; + if (! RegQueryValueEx(userKey, uname.c_str(), NULL, &type, + (BYTE*)&data, &size)) + if (type == REG_DWORD) + return data; + } + + return dflt; +} + +std::wstring RegistryStorage::get(const std::wstring &name, const std::wstring &dflt) +{ + std::string uname(toUtf8(name)); + + if (globalKey) { + DWORD size = 0; + DWORD type; + if (! RegQueryValueEx(globalKey, uname.c_str(), NULL, &type, + NULL, &size)) + { + if ((type == REG_SZ) && (size > 0)) { + char *data = new char[size + 1]; + if (! RegQueryValueEx(globalKey, uname.c_str(), NULL, &type, + (BYTE*)data, &size)) + { + std::wstring s(fromUtf8(data)); + delete[] data; + return s; + } + delete[] data; + } else + return L""; + } + } + + if (userKey) { + DWORD size = 0; + DWORD type; + if (! RegQueryValueEx(userKey, uname.c_str(), NULL, &type, + NULL, &size)) + { + if ((type == REG_SZ) && (size > 0)) { + char *data = new char[size]; + if (! RegQueryValueEx(userKey, uname.c_str(), NULL, &type, + (BYTE*)data, &size)) + { + std::wstring s(fromUtf8(data)); + delete[] data; + return s; + } + delete[] data; + } else + return L""; + } + } + + return dflt; +} + +void RegistryStorage::set(const std::wstring &name, int value) +{ + std::string uname(toUtf8(name)); + + if (userKey) { + DWORD data = value; + RegSetValueEx(userKey, uname.c_str(), 0, REG_DWORD, + (BYTE*)&data, sizeof(data)); + } +} + +void RegistryStorage::set(const std::wstring &name, const std::wstring &value) +{ + std::string uname(toUtf8(name)); + std::string uval(toUtf8(value)); + + if (userKey) + RegSetValueEx(userKey, uname.c_str(), 0, REG_SZ, + (BYTE*)uval.c_str(), (uval.length() + 1) * sizeof(char)); +} + + +#endif + diff --git a/regstorage.h b/regstorage.h new file mode 100644 index 0000000..a53a96c --- /dev/null +++ b/regstorage.h @@ -0,0 +1,32 @@ +#ifndef __REGSTORAGE_H__ +#define __REGSTORAGE_H__ +#ifdef WIN32 // Win32 only + + +#include +#include "storage.h" + + +class RegistryStorage: public Storage +{ + private: + HKEY globalKey; + HKEY userKey; + + public: + RegistryStorage(); + virtual ~RegistryStorage(); + + public: + virtual int get(const std::wstring &name, int dflt) ; + virtual std::wstring get(const std::wstring &name, + const std::wstring &dflt); + virtual void set(const std::wstring &name, int value); + virtual void set(const std::wstring &name, const std::wstring &value); + virtual void flush() { }; +}; + + +#endif +#endif + diff --git a/resources.cpp b/resources.cpp new file mode 100644 index 0000000..5d18fbc --- /dev/null +++ b/resources.cpp @@ -0,0 +1,674 @@ +#include +#include +#include +#include + +#include "resources.h" +#include "exceptions.h" +#include "unicode.h" +#include "convert.h" +#include "i18n.h" +#include "utils.h" + + +ResourcesCollection *resources = NULL; + + +/////////////////////////////////////////////////////////////////// +// +// UnpackedResourceStream +// +/////////////////////////////////////////////////////////////////// + + +class UnpackedResourceStream: public ResourceStream +{ + private: + std::ifstream &stream; + size_t size; + long offset; + long pos; + + public: + UnpackedResourceStream(std::ifstream &stream, long offset, + size_t size); + + public: + virtual size_t getSize() { return size; }; + virtual void seek(long offset); + virtual void read(char *buffer, size_t size); + virtual long getPos() { return pos; }; +}; + +UnpackedResourceStream::UnpackedResourceStream(std::ifstream &s, + long off, size_t sz): stream(s) +{ + offset = off; + size = sz; + pos = 0; +} + +void UnpackedResourceStream::seek(long off) +{ + if ((off < 0) || ((size_t)off > size)) + throw Exception(L"Invalid seek in ResourceStream"); + pos = off; +} + +void UnpackedResourceStream::read(char *buffer, size_t sz) +{ + if (! buffer) + throw Exception(L"Invalid buffer in ResourceStream"); + if (sz + pos > size) + throw Exception(L"Attempt of reading after resource end"); + stream.seekg(offset + pos, std::ios::beg); + stream.read(buffer, sz); + pos += sz; +} + + +/////////////////////////////////////////////////////////////////// +// +// MemoryResourceStream +// +/////////////////////////////////////////////////////////////////// + + +class MemoryResourceStream: public ResourceStream +{ + private: + char *data; + size_t size; + ResVariant *resource; + long pos; + + public: + MemoryResourceStream(ResVariant *resource); + virtual ~MemoryResourceStream(); + + public: + virtual size_t getSize() { return size; }; + virtual void seek(long offset); + virtual void read(char *buffer, size_t size); + virtual long getPos() { return pos; }; +}; + +MemoryResourceStream::MemoryResourceStream(ResVariant *res) +{ + resource = res; + data = (char*)res->getRef(size); + pos = 0; +} + +MemoryResourceStream::~MemoryResourceStream() +{ + resource->delRef(data); +} + +void MemoryResourceStream::seek(long off) +{ + if ((off < 0) || ((size_t)off > size)) + throw Exception(L"Invalid seek in ResourceStream"); + pos = off; +} + +void MemoryResourceStream::read(char *buffer, size_t sz) +{ + if (! buffer) + throw Exception(L"Invalid buffer in ResourceStream"); + if (sz + pos > size) + throw Exception(L"Attempt of reading after resource end"); + memcpy(buffer, data, sz); + pos += sz; +} + + +/////////////////////////////////////////////////////////////////// +// +// ResourceFile +// +/////////////////////////////////////////////////////////////////// + + +ResourceFile::ResourceFile(const std::wstring &fileName, Buffer *buf): + name(fileName) +{ + if (buf) { + buffer = buf; + ownBuffer = false; + } else { + buffer = new Buffer(); + ownBuffer = true; + } + + stream.open(toMbcs(fileName).c_str(), std::ios::in | std::ios::binary); + if (stream.fail()) + throw Exception(L"Error loading resource file '" + name + L"'"); + + char sign[4]; + stream.read(sign, 4); + int readed = stream.gcount(); + if (stream.fail() || (readed != 4) || (sign[0] != 'C') || + (sign[1] != 'R') || (sign[2] != 'F') || sign[3]) + throw Exception(L"Invalid resource file '" + name + L"'"); + + int major = readInt(stream); + readed = stream.gcount(); + int minor = readInt(stream); + readed += stream.gcount(); + priority = readInt(stream); + readed += stream.gcount(); + if (stream.fail() || (readed != 12) || (major != 2) || (minor < 0)) + throw Exception(L"Incompatible version of resource file '" + + name + L"'"); +} + + +ResourceFile::~ResourceFile() +{ + stream.close(); + if (ownBuffer) + delete buffer; +} + + +void ResourceFile::getDirectory(Directory &directory) +{ + stream.seekg(-8, std::ios::end); + if (stream.fail()) + throw Exception(L"Error reading " + name + L" directory"); + int start = readInt(stream); + int count = readInt(stream); + stream.seekg(start, std::ios::beg); + if (stream.fail()) + throw Exception(L"Error reading " + name + L" directory"); + + for (int i = 0; i < count; i++) { + DirectoryEntry entry; + entry.name = readString(stream); + entry.unpackedSize = readInt(stream); + entry.offset = readInt(stream); + entry.packedSize = readInt(stream); + entry.level = readInt(stream); + entry.group = readString(stream); + directory.push_back(entry); + } + + if (stream.fail()) + throw Exception(L"Error reading " + name + L" directory"); +} + + +void ResourceFile::unpack(char *in, int inSize, char *out, int outSize) +{ + z_stream zs; + + memset(&zs, 0, sizeof(z_stream)); + zs.next_in = (Bytef*)in; + zs.avail_in = inSize; + zs.next_out = (Bytef*)out; + zs.avail_out = outSize; + + if (inflateInit(&zs) != Z_OK) + throw Exception(name + L": Error initializing inflate stream."); + + if (inflate(&zs, Z_FINISH) != Z_STREAM_END) + throw Exception(name + L": Error decompresing element."); + + if (inflateEnd(&zs) != Z_OK) + throw Exception(name + L": Error finishing decompresing."); +} + + +void ResourceFile::load(char *buf, long offset, long packedSize, + long unpackedSize, int level) +{ + char *inBuf=NULL; + + try { + if (! level) { + stream.seekg(offset, std::ios::beg); + stream.read(buf, unpackedSize); + return; + } + + buffer->setSize(packedSize); + stream.seekg(offset, std::ios::beg); + stream.read((char*)buffer->getData(), packedSize); + unpack((char*)buffer->getData(), packedSize, buf, unpackedSize); + } catch (Exception &e) { + if (inBuf) free(inBuf); + throw e; + } catch (...) { + if (inBuf) free(inBuf); + throw Exception(name + L": Error loading resource"); + } +} + + +void* ResourceFile::load(long offset, long packedSize, long unpackedSize, + int level) +{ + char *outBuf=NULL; + + try { + outBuf = (char*)malloc(unpackedSize); + if (! outBuf) + throw Exception(name + L": Error allocating memory"); + load(outBuf, offset, packedSize, unpackedSize, level); + } catch (Exception &e) { + if (outBuf) free(outBuf); + throw e; + } catch (...) { + if (outBuf) free(outBuf); + throw Exception(name + L": Error loading resource"); + } + + return outBuf; +} + + +/////////////////////////////////////////////////////////////////// +// +// SimpleResourceFile +// +/////////////////////////////////////////////////////////////////// + + +SimpleResourceFile::SimpleResourceFile(const std::wstring &fileName, + Buffer *buf): ResourceFile(fileName, buf) +{ + Directory entries; + getDirectory(entries); + for (Directory::iterator i = entries.begin(); i != entries.end(); i++) { + DirectoryEntry &e = *i; + directory[e.name] = e; + } +} + +void* SimpleResourceFile::load(const std::wstring &name, int &size) +{ + DirectoryMap::iterator i = directory.find(name); + if (i != directory.end()) { + DirectoryEntry &e = (*i).second; + size = e.unpackedSize; + return ResourceFile::load(e.offset, e.packedSize, e.unpackedSize, + e.level); + } else + throw Exception(L"Resource '" + name + L"' not found"); +} + +void SimpleResourceFile::load(const std::wstring &name, Buffer &outBuf) +{ + DirectoryMap::iterator i = directory.find(name); + if (i != directory.end()) { + DirectoryEntry &e = (*i).second; + outBuf.setSize(e.unpackedSize); + ResourceFile::load((char*)outBuf.getData(), e.offset, + e.packedSize, e.unpackedSize, e.level); + } else + throw Exception(L"Resource '" + name + L"' not found"); +} + +/////////////////////////////////////////////////////////////////// +// +// ResVariant +// +/////////////////////////////////////////////////////////////////// + + +ResVariant::ResVariant(ResourceFile *f, int score, + const ResourceFile::DirectoryEntry &e) +{ + file = f; + i18nScore = score; + offset = e.offset; + unpackedSize = e.unpackedSize; + packedSize = e.packedSize; + level = e.level; + refCnt = 0; + data = NULL; +} + + +ResVariant::~ResVariant() +{ + if (data) + free((char*)data - sizeof(ResVariant*)); +} + +void* ResVariant::getRef() +{ + if (! refCnt) { + char* d = (char*)malloc(unpackedSize + sizeof(void*)); + if (! d) + throw Exception(L"ResVariant::getRef memory allocation error"); + ResVariant *self = this; + file->load(d + sizeof(self), offset, packedSize, unpackedSize, + level); + memcpy(d, &self, sizeof(self)); + data = d + sizeof(self); + } + + refCnt++; + return data; +} + +void* ResVariant::getRef(size_t &sz) +{ + sz = unpackedSize; + return getRef(); +} + +void ResVariant::delRef(void *dta) +{ + if ((! refCnt) || (dta != data)) + throw Exception(L"Invalid ResVariant::delRef call"); + + refCnt--; + if (! refCnt) { + free((char*)data - sizeof(ResVariant*)); + data = NULL; + } +} + +void* ResVariant::getDynData() +{ + if (! refCnt) + return file->load(offset, packedSize, unpackedSize, level); + else { + char* d = (char*)malloc(unpackedSize); + if (! d) + throw Exception(L"ResVariant::getDynData memory allocation error"); + memcpy(d, data, unpackedSize); + return data; + } +} + +void ResVariant::getData(Buffer &buffer) +{ + buffer.setSize(unpackedSize); + if (! refCnt) + file->load((char*)buffer.getData(), offset, packedSize, + unpackedSize, level); + else + memcpy((char*)buffer.getData(), data, unpackedSize); +} + +ResourceStream* ResVariant::createStream() +{ + if (refCnt || level) + return new MemoryResourceStream(this); + else + return new UnpackedResourceStream(file->getStream(), offset, + packedSize); +} + + +/////////////////////////////////////////////////////////////////// +// +// Resource +// +/////////////////////////////////////////////////////////////////// + + +Resource::Resource(ResourceFile *file, int i18nScore, + const ResourceFile::DirectoryEntry &entry, const std::wstring &n): + name(n) +{ + addVariant(file, i18nScore, entry); +} + +Resource::~Resource() +{ + for (Variants::iterator i = variants.begin(); i != variants.end(); i++) + delete *i; +} + +class ScorePredicate +{ + public: + int score; + + ScorePredicate(int sc) { score = sc; } + + bool operator() (const ResVariant *r) const { + return r->getI18nScore() == score; + }; +}; + +class ResVariantMoreThen +{ + public: + bool operator() (const ResVariant *v1, const ResVariant *v2) const { + return v1->getI18nScore() > v2->getI18nScore(); + }; +}; + +void Resource::addVariant(ResourceFile *file, int i18nScore, + const ResourceFile::DirectoryEntry &entry) +{ + if (! variants.size()) { + variants.push_back(new ResVariant(file, i18nScore, entry)); + return; + } + + ScorePredicate p(i18nScore); + Variants::iterator i = std::find_if(variants.begin(), variants.end(), p); + if (i != variants.end()) { + delete *i; + *i = new ResVariant(file, i18nScore, entry); + } else { + variants.push_back(new ResVariant(file, i18nScore, entry)); + ResVariantMoreThen comparator; + std::sort(variants.begin(), variants.end(), comparator); + } +} + + +void* Resource::getRef(int variant) +{ + return variants[variant]->getRef(); +} + + +void* Resource::getRef(int *size, int variant) +{ + if (size) + *size = variants[variant]->getSize(); + return variants[variant]->getRef(); +} + + +void Resource::delRef(void *data) +{ + ResVariant *v = *(ResVariant**)((char*)data - sizeof(ResVariant*)); + v->delRef(data); +} + + +void Resource::getData(Buffer &buffer, int variant) +{ + variants[variant]->getData(buffer); +} + +ResourceStream* Resource::createStream(int variant) +{ + return variants[variant]->createStream(); +} + + +/////////////////////////////////////////////////////////////////// +// +// ResourcesCollection +// +/////////////////////////////////////////////////////////////////// + + +class ResFileMoreThen +{ + public: + bool operator() (const ResourceFile *f1, const ResourceFile *f2) const { + return f1->getPriority() > f2->getPriority(); + }; +}; + +ResourcesCollection::ResourcesCollection(StringList &directories) +{ + loadResourceFiles(directories); + ResFileMoreThen comparator; + std::sort(files.begin(), files.end(), comparator); + processFiles(); +} + + +ResourcesCollection::~ResourcesCollection() +{ + for (ResourcesMap::iterator i = resources.begin(); i != resources.end(); i++) + delete (*i).second; + for (ResourceFiles::iterator i = files.begin(); i != files.end(); i++) + delete *i; +} + + +void ResourcesCollection::loadResourceFiles(StringList &directories) +{ + for (StringList::iterator i = directories.begin(); + i != directories.end(); i++) + { + const std::wstring &d = *i; + DIR *dir = opendir(toMbcs(d).c_str()); + if (dir) { + struct dirent *de; + while ((de = readdir(dir))) + if (de->d_name[0] != '.') { + std::wstring s(fromMbcs(de->d_name)); + if ((s.length() > 4) && + (toLowerCase(s.substr(s.length() - 4)) == L".res")) + files.push_back(new ResourceFile(d + L"/" + s, &buffer)); + } + closedir(dir); + } + } +} + + +void ResourcesCollection::processFiles() +{ + ResourceFile::Directory dir; + for (std::vector::iterator i = files.begin(); + i != files.end(); i++) + { + ResourceFile *file = *i; + file->getDirectory(dir); + for (ResourceFile::Directory::iterator j = dir.begin(); + j != dir.end(); j++) + { + ResourceFile::DirectoryEntry &de = *j; + std::wstring name, ext, language, country; + splitFileName(de.name, name, ext, language, country); + int score = getScore(language, country, locale); + if (score > 0) { + std::wstring resName = name + L"." + ext; + Resource *res = resources[resName]; + if (! res) { + res = new Resource(file, score, de, resName); + resources[resName] = res; + if (de.group.length()) + groups[de.group].push_back(res); + } else + res->addVariant(file, score, de); + } + } + dir.clear(); + } +} + + +Resource* ResourcesCollection::getResource(const std::wstring &name) +{ + Resource *r = resources[name]; + if (! r) + throw Exception(L"Resource '" + name + L"' not found"); + return r; +} + + +void* ResourcesCollection::getRef(const std::wstring &name, int &size) +{ + Resource *r = getResource(name); + ResVariant *v = r->getVariant(0); + size = v->getSize(); + return v->getRef(); +} + + +void* ResourcesCollection::getRef(const std::wstring &name) +{ + Resource *r = getResource(name); + ResVariant *v = r->getVariant(0); + return v->getRef(); +} + +ResourceStream* ResourcesCollection::createStream(const std::wstring &name) +{ + Resource *r = getResource(name); + return r->createStream(); +} + +void ResourcesCollection::delRef(void *data) +{ + ResVariant *v = *(ResVariant**)((char*)data - sizeof(ResVariant*)); + v->delRef(data); +} + +void ResourcesCollection::forEachInGroup(const std::wstring &name, + Visitor &visitor) +{ + if (groups.count(name) > 0) { + ResourcesList &l = groups[name]; + for (ResourcesList::iterator i = l.begin(); i != l.end(); i++) { + Resource *r = *i; + visitor.onVisit(r); + } + } +} + +void ResourcesCollection::loadData(const std::wstring &name, Buffer &buffer) +{ + Resource *r = getResource(name); + r->getData(buffer); +} + + +/////////////////////////////////////////////////////////////////// +// +// ResDataHolder +// +/////////////////////////////////////////////////////////////////// + + +ResDataHolder::ResDataHolder() +{ + data = NULL; + size = 0; +} + +ResDataHolder::ResDataHolder(const std::wstring &name) +{ + load(name); +} + +ResDataHolder::~ResDataHolder() +{ + if (data) + resources->delRef(data); +} + +void ResDataHolder::load(const std::wstring &name) +{ + int s; + data = resources->getRef(name, s); + size = (size_t)s; +} + diff --git a/resources.h b/resources.h new file mode 100644 index 0000000..721f9c3 --- /dev/null +++ b/resources.h @@ -0,0 +1,413 @@ +#ifndef __RESOURCES_H__ +#define __RESOURCES_H__ + +/** \file resources.h + * Classes for resources loading and unloading. + */ + +#include +#include +#include +#include +#include + +#include "visitor.h" +#include "buffer.h" + +typedef std::list StringList; + + +class ResourceFile; + + +/// Abstract interface for streamed resource access +class ResourceStream +{ + public: + virtual ~ResourceStream() { }; + + /// Get size of resource + virtual size_t getSize() = 0; + + /// Seek into resource. + /// \param offset offset from resource start + virtual void seek(long offset) = 0; + + /// Read data from resource into buffer. + /// \param buffer buffer of size bytes. + /// \param size number of bytes to read. + virtual void read(char *buffer, size_t size) = 0; + + /// Get current position + virtual long getPos() = 0; + + /// Return true if end of resource reached + virtual bool isEof() { return (size_t)getPos() >= getSize(); }; + + /// Get count of bytes left + virtual long getAvailable() { return (long)getSize() - getPos(); }; +}; + + +/*** + * Low level resource file interface. + * Never use it, use ResourcesCollection instead. + ***/ +class ResourceFile +{ + private: + std::ifstream stream; /// resource file input stream + int priority; /// priority of resource file + std::wstring name; /// resource file name + Buffer *buffer; + bool ownBuffer; + + public: + /// Resource file directory entry + typedef struct { + std::wstring name; /// resource name + long offset; /// offset from start of resource file + long packedSize; /// compressed size + long unpackedSize; /// uncompressed size + std::wstring group; /// group name + int level; /// pack level + } DirectoryEntry; + /// List of directory entries. + typedef std::list Directory; + + public: + /// Create resource file. Throws exception if file can't be opened. + /// \param fileName the name of resource file. + /// \param buffer buffer for temporary data. + /// Can be shared with other resource files. + ResourceFile(const std::wstring &fileName, Buffer *buffer=NULL); + virtual ~ResourceFile(); + + public: + /// Load directory listing. + /// \param directory list where resource entries will be placed. + void getDirectory(Directory &directory); + + /// Load data in preallocated buffer buf of size unpackedSize. + /// \param buf buffer of unpackedSize bytes where unpacked data will + /// be placed + /// \param offset offset from start of resource file to packed data + /// \param packedSize size of packed resource + /// \param unpackedSize size of unpacked resource + void load(char *buf, long offset, long packedSize, + long unpackedSize, int level); + + /// Allocate buffer and load data. Memory returned by this + /// method must be freed by free() function call. + /// \param offset offset from start of resource file to packed data + /// \param packedSize size of packed resource + /// \param unpackedSize size of unpacked resource + void* load(long offset, long packedSize, long unpackedSize, + int level); + + /// Get priority of this resource file. + int getPriority() const { return priority; }; + + /// Get the name of resource file. + const std::wstring& getFileName() const { return name; }; + + /// Get file stream. + std::ifstream& getStream() { return stream; }; + + private: + /// Unpack memory buffer + /// \param in buffer of inSize bytes filled with packed data + /// \param inSize size of packed data + /// \param out buffer of outSize bytes where unpacked data will + /// be placed + /// \param outSize size of unpacked data + void unpack(char *in, int inSize, char *out, int outSize); +}; + + +/// Simple resource file wrapper. +/// Used at boot time when ResourcesCollection +/// is not available yet. +class SimpleResourceFile: public ResourceFile +{ + private: + typedef std::map DirectoryMap; + DirectoryMap directory; /// Directory map. + + public: + /// Open resource file. Throws exception if file can't be opened. + /// \param fileName the name of resource file. + /// \param buffer buffer for temporary data. + /// Can be shared with other resource files. + SimpleResourceFile(const std::wstring &fileName, Buffer *buffer=NULL); + + public: + /// Load data. Memory returned by this method should be freed + /// by free() function call. + /// \param name name of resource + /// \param size returns size of resource + virtual void* load(const std::wstring &name, int &size); + + /// Load data into the buffer. + /// \param name name of resource + /// \param buffer buffer for resource data + virtual void load(const std::wstring &name, Buffer &buffer); +}; + + +/// Internationalized resource entity. +class ResVariant +{ + private: + int i18nScore; + ResourceFile *file; + long offset; + long unpackedSize; + long packedSize; + int refCnt; + void *data; + int level; + + public: + /// Create resource variation. + /// \param file reesource file + /// \param score locale compability score + /// \param entry entry in global resources directory + ResVariant(ResourceFile *file, int score, + const ResourceFile::DirectoryEntry &entry); + + ~ResVariant(); + + public: + /// Return locale compability score. + int getI18nScore() const { return i18nScore; }; + + /// Get pointer to unpacked resource data. + /// Must be freed after use this delRef() + void* getRef(); + + /// Get pointer to unpacked resource data and return resource size. + /// Must be freed after use this delRef(). + /// \param size returned size of resource data. + void* getRef(size_t &size); + + /// Delete referene to data. + /// \param data pointer to unpacked data. + void delRef(void *data); + + /// Return reference counter. + int getRefCount() const { return refCnt; }; + + /// Is data managed by this object + /// \param data pointer to dataa + bool isDataOwned(void *data) const { return refCnt && data == this->data; }; + + /// return data. + /// destroy it after use with free() + void* getDynData(); + + /// returns size of data + long getSize() const { return unpackedSize; }; + + /// Return data in buffer + /// \param buffer buffer to store data. + void getData(Buffer &buffer); + + /// Create ResourceStream for resource. + /// This may be usefull for large streams unpacked data, + /// for example video and sound. + /// Delete stream after use. + ResourceStream* createStream(); +}; + + +/// Internationalized resources. +/// Collection of localized data (ResVariant) with single name. +class Resource +{ + private: + typedef std::vector Variants; + Variants variants; + std::wstring name; + + public: + /// Create resource and add first entry + /// \param file resource file + /// \param i18nScore locale compability score + /// \param entry resource entry in global directory + /// \param name name of resource + Resource(ResourceFile *file, int i18nScore, + const ResourceFile::DirectoryEntry &entry, + const std::wstring &name); + + ~Resource(); + + public: + /// Add resource variant. + /// \param file resource file + /// \param i18nScore locale compability score + /// \param entry resource entry in global directory + void addVariant(ResourceFile *file, int i18nScore, + const ResourceFile::DirectoryEntry &entry); + + /// Get number of variants. + int getVariantsCount() const { return variants.size(); }; + + /// Geturns variant object. + /// \param variant variant number. + ResVariant* getVariant(int variant=0) { return variants[variant]; }; + + /// Load data from variant. + /// Data must be freed with delRef(). + /// \param variant variant number. + void* getRef(int variant=0); + + /// Load data from variant. + /// data must be freed with delRef() + /// \param size size of data. + /// \param variant variant number. + void* getRef(int *size, int variant=0); + + /// Unload data + /// param data pointer to data. + void delRef(void *data); + + /// Get size of data. + /// \param variant variant number. + long getSize(int variant=0) { return variants[variant]->getSize(); }; + + /// Get name of this resource. + const std::wstring& getName() const { return name; }; + + /// Load data into buffer. + /// \param buffer buffer for data. + /// \param variant variant number. + void getData(Buffer &buffer, int variant=0); + + /// Create ResourceStream for resource. + /// This may be usefull for large streams unpacked data, + /// for example video and sound. + /// Delete stream after use. + /// \param variant variant number. + ResourceStream* createStream(int variant=0); +}; + + +/// Internationalized resource files collection. +/// When ResourceCollection created all resources checked against +/// current locale. Resources not belonged to current locale are +/// ignored, resources that can be used in current locale sorted +/// by locale relevance score. +/// All resources names interpeted as name[_language][_COUNTRY].extension +/// where name is name of resource, language is optional two letters +/// ISO language code, country is two letters ISO country code +/// and extension is file extension. +/// After processing resource name will be truncated to name.extension. +/// By default resource with highest locale relevance score will +/// be loaded. Howether, it is possible to enumerate all variants +/// by using Resource class interface. +/// Resource files loaded in order specified by their priority. +/// Resources from file loaded later can replace resources +/// loaded before it. +class ResourcesCollection +{ + private: + /// Map resource names to resources. + typedef std::map ResourcesMap; + + /// List of resources. + typedef std::list ResourcesList; + + /// Map group names to resources list. + typedef std::map ResourcesListMap; + + /// List of resource files. + typedef std::vector ResourceFiles; + + ResourcesMap resources; /// Map of all available resources. + ResourcesListMap groups; /// Map of all available groups. + ResourceFiles files; /// List of resource files. + Buffer buffer; /// Temporary buffer for resource files. + + public: + /// Load resource files, make grouping and i18n optimizations. + ResourcesCollection(StringList &directories); + ~ResourcesCollection(); + + public: + /// Returns resource entry. + /// If resource not found Exception will be thrown. + Resource* getResource(const std::wstring &name); + + /// Load resource. + /// Loaded data must be freed with delRef method. + void* getRef(const std::wstring &name, int &size); + + /// Load resource. + /// Loaded data must be freed with delRef method. + void* getRef(const std::wstring &name); + + /// Delete reference to resource. + void delRef(void *data); + + /// Visit all group members. + void forEachInGroup(const std::wstring &groupName, + Visitor &visitor); + + /// Create ResourceStream for resource. + /// This may be usefull for large streams unpacked data, + /// for example video and sound. + /// Delete stream after use. + /// \param name name of resource. + ResourceStream* createStream(const std::wstring &name); + + /// Load data into buffer. + /// Usually you don't need this, use getRef instead. + /// \param name name of resource. + /// \param buffer buffer for data. + void loadData(const std::wstring &name, Buffer &buffer); + + private: + /// Open resource files. + void loadResourceFiles(StringList &directories); + + /// Make grouping and locale processing. + void processFiles(); +}; + + +/// Helper class for simple resource loading and unloading +class ResDataHolder +{ + private: + void *data; + size_t size; + + public: + /// Create holder without data + ResDataHolder(); + + /// Create holder and load data + ResDataHolder(const std::wstring &name); + + ~ResDataHolder(); + + public: + /// Load resource data + void load(const std::wstring &name); + + /// Returns pointer to resource data + void* getData() const { return data; }; + + /// Returns size of data + size_t getSize() const { return size; }; +}; + + +/// Global collection of resources. +/// Careated at first step at boot time stage2 +extern ResourcesCollection *resources; + + +#endif + diff --git a/rules.cpp b/rules.cpp new file mode 100644 index 0000000..153a78b --- /dev/null +++ b/rules.cpp @@ -0,0 +1,563 @@ +#include "puzgen.h" +#include "utils.h" +#include "main.h" +#include "convert.h" +#include "unicode.h" + + +static std::wstring getThingName(int row, int thing) +{ + std::wstring s; + s += (wchar_t)(L'A' + row); + s += toString(thing); + return s; +} + + + +class NearRule: public Rule +{ + private: + int thing1[2]; + int thing2[2]; + + public: + NearRule(SolvedPuzzle puzzle); + NearRule(std::istream &stream); + virtual bool apply(Possibilities &pos); + virtual std::wstring getAsText(); + + private: + bool applyToCol(Possibilities &pos, int col, int nearRow, int nearNum, + int thisRow, int thisNum); + virtual void draw(int x, int y, IconSet &iconSet, bool highlighted); + virtual ShowOptions getShowOpts() { return SHOW_HORIZ; }; + virtual void save(std::ostream &stream); +}; + + +NearRule::NearRule(SolvedPuzzle puzzle) +{ + int col1 = rndGen.genInt(PUZZLE_SIZE); + thing1[0] = rndGen.genInt(PUZZLE_SIZE); + thing1[1] = puzzle[thing1[0]][col1]; + + int col2; + if (col1 == 0) + col2 = 1; + else + if (col1 == PUZZLE_SIZE-1) + col2 = PUZZLE_SIZE-2; + else + if (rndGen.genInt(2)) + col2 = col1 + 1; + else + col2 = col1 - 1; + + thing2[0] = rndGen.genInt(PUZZLE_SIZE); + thing2[1] = puzzle[thing2[0]][col2]; +} + + +NearRule::NearRule(std::istream &stream) +{ + thing1[0] = readInt(stream); + thing1[1] = readInt(stream); + thing2[0] = readInt(stream); + thing2[1] = readInt(stream); +} + + +bool NearRule::applyToCol(Possibilities &pos, int col, int nearRow, int nearNum, + int thisRow, int thisNum) +{ + bool hasLeft, hasRight; + + if (col == 0) + hasLeft = false; + else + hasLeft = pos.isPossible(col - 1, nearRow, nearNum); + if (col == PUZZLE_SIZE-1) + hasRight = false; + else + hasRight = pos.isPossible(col + 1, nearRow, nearNum); + + if ((! hasRight) && (! hasLeft) && pos.isPossible(col, thisRow, thisNum)) { + pos.exclude(col, thisRow, thisNum); + return true; + } else + return false; +} + + +bool NearRule::apply(Possibilities &pos) +{ + bool changed = false; + + for (int i = 0; i < PUZZLE_SIZE; i++) { + if (applyToCol(pos, i, thing1[0], thing1[1], thing2[0], thing2[1])) + changed = true; + if (applyToCol(pos, i, thing2[0], thing2[1], thing1[0], thing1[1])) + changed = true; + } + + if (changed) + apply(pos); + + return changed; +} + +std::wstring NearRule::getAsText() +{ + return getThingName(thing1[0], thing1[1]) + + L" is near to " + getThingName(thing2[0], thing2[1]); +} + +void NearRule::draw(int x, int y, IconSet &iconSet, bool h) +{ + SDL_Surface *icon = iconSet.getLargeIcon(thing1[0], thing1[1], h); + screen.draw(x, y, icon); + screen.draw(x + icon->h, y, iconSet.getNearHintIcon(h)); + screen.draw(x + icon->h*2, y, iconSet.getLargeIcon(thing2[0], thing2[1], h)); +} + +void NearRule::save(std::ostream &stream) +{ + writeString(stream, L"near"); + writeInt(stream, thing1[0]); + writeInt(stream, thing1[1]); + writeInt(stream, thing2[0]); + writeInt(stream, thing2[1]); +} + + +class DirectionRule: public Rule +{ + private: + int row1, thing1; + int row2, thing2; + + public: + DirectionRule(SolvedPuzzle puzzle); + DirectionRule(std::istream &stream); + virtual bool apply(Possibilities &pos); + virtual std::wstring getAsText(); + + private: + virtual void draw(int x, int y, IconSet &iconSet, bool highlighted); + virtual ShowOptions getShowOpts() { return SHOW_HORIZ; }; + virtual void save(std::ostream &stream); +}; + + +DirectionRule::DirectionRule(SolvedPuzzle puzzle) +{ + row1 = rndGen.genInt(PUZZLE_SIZE); + row2 = rndGen.genInt(PUZZLE_SIZE); + int col1 = rndGen.genInt(PUZZLE_SIZE - 1); + int col2 = rndGen.genInt(PUZZLE_SIZE - col1 - 1) + col1 + 1; + thing1 = puzzle[row1][col1]; + thing2 = puzzle[row2][col2]; +} + +DirectionRule::DirectionRule(std::istream &stream) +{ + row1 = readInt(stream); + thing1 = readInt(stream); + row2 = readInt(stream); + thing2 = readInt(stream); +} + +bool DirectionRule::apply(Possibilities &pos) +{ + bool changed = false; + + for (int i = 0; i < PUZZLE_SIZE; i++) { + if (pos.isPossible(i, row2, thing2)) { + pos.exclude(i, row2, thing2); + changed = true; + } + if (pos.isPossible(i, row1, thing1)) + break; + } + + for (int i = PUZZLE_SIZE-1; i >= 0; i--) { + if (pos.isPossible(i, row1, thing1)) { + pos.exclude(i, row1, thing1); + changed = true; + } + if (pos.isPossible(i, row2, thing2)) + break; + } + + return changed; +} + +std::wstring DirectionRule::getAsText() +{ + return getThingName(row1, thing1) + + L" is from the left of " + getThingName(row2, thing2); +} + +void DirectionRule::draw(int x, int y, IconSet &iconSet, bool h) +{ + SDL_Surface *icon = iconSet.getLargeIcon(row1, thing1, h); + screen.draw(x, y, icon); + screen.draw(x + icon->h, y, iconSet.getSideHintIcon(h)); + screen.draw(x + icon->h*2, y, iconSet.getLargeIcon(row2, thing2, h)); +} + +void DirectionRule::save(std::ostream &stream) +{ + writeString(stream, L"direction"); + writeInt(stream, row1); + writeInt(stream, thing1); + writeInt(stream, row2); + writeInt(stream, thing2); +} + + +class OpenRule: public Rule +{ + private: + int col, row, thing; + + public: + OpenRule(SolvedPuzzle puzzle); + OpenRule(std::istream &stream); + virtual bool apply(Possibilities &pos); + virtual std::wstring getAsText(); + virtual bool applyOnStart() { return true; }; + virtual void draw(int x, int y, IconSet &iconSet, bool highlighted) { }; + virtual ShowOptions getShowOpts() { return SHOW_NOTHING; }; + virtual void save(std::ostream &stream); +}; + + +OpenRule::OpenRule(SolvedPuzzle puzzle) +{ + col = rndGen.genInt(PUZZLE_SIZE); + row = rndGen.genInt(PUZZLE_SIZE); + thing = puzzle[row][col]; +} + +OpenRule::OpenRule(std::istream &stream) +{ + col = readInt(stream); + row = readInt(stream); + thing = readInt(stream); +} + +bool OpenRule::apply(Possibilities &pos) +{ + if (! pos.isDefined(col, row)) { + pos.set(col, row, thing); + return true; + } else + return false; +} + +std::wstring OpenRule::getAsText() +{ + return getThingName(row, thing) + L" is at column " + toString(col+1); +} + +void OpenRule::save(std::ostream &stream) +{ + writeString(stream, L"open"); + writeInt(stream, col); + writeInt(stream, row); + writeInt(stream, thing); +} + + +class UnderRule: public Rule +{ + private: + int row1, thing1, row2, thing2; + + public: + UnderRule(SolvedPuzzle puzzle); + UnderRule(std::istream &stream); + virtual bool apply(Possibilities &pos); + virtual std::wstring getAsText(); + virtual void draw(int x, int y, IconSet &iconSet, bool highlighted); + virtual ShowOptions getShowOpts() { return SHOW_VERT; }; + virtual void save(std::ostream &stream); +}; + + +UnderRule::UnderRule(SolvedPuzzle puzzle) +{ + int col = rndGen.genInt(PUZZLE_SIZE); + row1 = rndGen.genInt(PUZZLE_SIZE); + thing1 = puzzle[row1][col]; + do { + row2 = rndGen.genInt(PUZZLE_SIZE); + } while (row2 == row1) ; + thing2 = puzzle[row2][col]; +} + +UnderRule::UnderRule(std::istream &stream) +{ + row1 = readInt(stream); + thing1 = readInt(stream); + row2 = readInt(stream); + thing2 = readInt(stream); +} + +bool UnderRule::apply(Possibilities &pos) +{ + bool changed = false; + + for (int i = 0; i < PUZZLE_SIZE; i++) { + if ((! pos.isPossible(i, row1, thing1)) && + pos.isPossible(i, row2, thing2)) + { + pos.exclude(i, row2, thing2); + changed = true; + } + if ((! pos.isPossible(i, row2, thing2)) && + pos.isPossible(i, row1, thing1)) + { + pos.exclude(i, row1, thing1); + changed = true; + } + } + + return changed; +} + + +std::wstring UnderRule::getAsText() +{ + return getThingName(row1, thing1) + L" is the same column as " + + getThingName(row2, thing2); +} + +void UnderRule::draw(int x, int y, IconSet &iconSet, bool h) +{ + SDL_Surface *icon = iconSet.getLargeIcon(row1, thing1, h); + screen.draw(x, y, icon); + screen.draw(x, y + icon->h, iconSet.getLargeIcon(row2, thing2, h)); +} + +void UnderRule::save(std::ostream &stream) +{ + writeString(stream, L"under"); + writeInt(stream, row1); + writeInt(stream, thing1); + writeInt(stream, row2); + writeInt(stream, thing2); +} + + + +class BetweenRule: public Rule +{ + private: + int row1, thing1; + int row2, thing2; + int centerRow, centerThing; + + public: + BetweenRule(SolvedPuzzle puzzle); + BetweenRule(std::istream &stream); + virtual bool apply(Possibilities &pos); + virtual std::wstring getAsText(); + + private: + virtual void draw(int x, int y, IconSet &iconSet, bool highlighted); + virtual ShowOptions getShowOpts() { return SHOW_HORIZ; }; + virtual void save(std::ostream &stream); +}; + + +BetweenRule::BetweenRule(SolvedPuzzle puzzle) +{ + centerRow = rndGen.genInt(PUZZLE_SIZE); + row1 = rndGen.genInt(PUZZLE_SIZE); + row2 = rndGen.genInt(PUZZLE_SIZE); + + int centerCol = rndGen.genInt(PUZZLE_SIZE - 2) + 1; + centerThing = puzzle[centerRow][centerCol]; + if (rndGen.genInt(2)) { + thing1 = puzzle[row1][centerCol - 1]; + thing2 = puzzle[row2][centerCol + 1]; + } else { + thing1 = puzzle[row1][centerCol + 1]; + thing2 = puzzle[row2][centerCol - 1]; + } +} + +BetweenRule::BetweenRule(std::istream &stream) +{ + row1 = readInt(stream); + thing1 = readInt(stream); + row2 = readInt(stream); + thing2 = readInt(stream); + centerRow = readInt(stream); + centerThing = readInt(stream); +} + +bool BetweenRule::apply(Possibilities &pos) +{ + bool changed = false; + + if (pos.isPossible(0, centerRow, centerThing)) { + changed = true; + pos.exclude(0, centerRow, centerThing); + } + + if (pos.isPossible(PUZZLE_SIZE-1, centerRow, centerThing)) { + changed = true; + pos.exclude(PUZZLE_SIZE-1, centerRow, centerThing); + } + + bool goodLoop; + do { + goodLoop = false; + + for (int i = 1; i < PUZZLE_SIZE-1; i++) { + if (pos.isPossible(i, centerRow, centerThing)) { + if (! ((pos.isPossible(i-1, row1, thing1) && + pos.isPossible(i+1, row2, thing2)) || + (pos.isPossible(i-1, row2, thing2) && + pos.isPossible(i+1, row1, thing1)))) + { + pos.exclude(i, centerRow, centerThing); + goodLoop = true; + } + } + } + + for (int i = 0; i < PUZZLE_SIZE; i++) { + bool leftPossible, rightPossible; + + if (pos.isPossible(i, row2, thing2)) { + if (i < 2) + leftPossible = false; + else + leftPossible = (pos.isPossible(i-1, centerRow, centerThing) + && pos.isPossible(i-2, row1, thing1)); + if (i >= PUZZLE_SIZE - 2) + rightPossible = false; + else + rightPossible = (pos.isPossible(i+1, centerRow, centerThing) + && pos.isPossible(i+2, row1, thing1)); + if ((! leftPossible) && (! rightPossible)) { + pos.exclude(i, row2, thing2); + goodLoop = true; + } + } + + if (pos.isPossible(i, row1, thing1)) { + if (i < 2) + leftPossible = false; + else + leftPossible = (pos.isPossible(i-1, centerRow, centerThing) + && pos.isPossible(i-2, row2, thing2)); + if (i >= PUZZLE_SIZE - 2) + rightPossible = false; + else + rightPossible = (pos.isPossible(i+1, centerRow, centerThing) + && pos.isPossible(i+2, row2, thing2)); + if ((! leftPossible) && (! rightPossible)) { + pos.exclude(i, row1, thing1); + goodLoop = true; + } + } + } + + if (goodLoop) + changed = true; + } while (goodLoop); + + return changed; +} + +std::wstring BetweenRule::getAsText() +{ + return getThingName(centerRow, centerThing) + + L" is between " + getThingName(row1, thing1) + L" and " + + getThingName(row2, thing2); +} + +void BetweenRule::draw(int x, int y, IconSet &iconSet, bool h) +{ + SDL_Surface *icon = iconSet.getLargeIcon(row1, thing1, h); + screen.draw(x, y, icon); + screen.draw(x + icon->w, y, iconSet.getLargeIcon(centerRow, centerThing, h)); + screen.draw(x + icon->w*2, y, iconSet.getLargeIcon(row2, thing2, h)); + SDL_Surface *arrow = iconSet.getBetweenArrow(h); + screen.draw(x + icon->w - (arrow->w - icon->w) / 2, y + 0, arrow); +} + +void BetweenRule::save(std::ostream &stream) +{ + writeString(stream, L"between"); + writeInt(stream, row1); + writeInt(stream, thing1); + writeInt(stream, row2); + writeInt(stream, thing2); + writeInt(stream, centerRow); + writeInt(stream, centerThing); +} + + + +Rule* genRule(SolvedPuzzle &puzzle) +{ + int a = rndGen.genInt(14); + switch (a) { + case 0: + case 1: + case 2: + case 3: return new NearRule(puzzle); + case 4: return new OpenRule(puzzle); + case 5: + case 6: return new UnderRule(puzzle); + case 7: + case 8: + case 9: + case 10: return new DirectionRule(puzzle); + case 11: + case 12: + case 13: return new BetweenRule(puzzle); + default: return genRule(puzzle); + } +} + + +void saveRules(Rules &rules, std::ostream &stream) +{ + writeInt(stream, rules.size()); + for (Rules::iterator i = rules.begin(); i != rules.end(); i++) + (*i)->save(stream); +} + + +void loadRules(Rules &rules, std::istream &stream) +{ + int no = readInt(stream); + + for (int i = 0; i < no; i++) { + std::wstring ruleType = readString(stream); + Rule *r; + if (ruleType == L"near") + r = new NearRule(stream); + else if (ruleType == L"open") + r = new OpenRule(stream); + else if (ruleType == L"under") + r = new UnderRule(stream); + else if (ruleType == L"direction") + r = new DirectionRule(stream); + else if (ruleType == L"between") + r = new BetweenRule(stream); + else + throw Exception(L"invalid rule type " + ruleType); + rules.push_back(r); + } +} + + diff --git a/screen.cpp b/screen.cpp new file mode 100644 index 0000000..7e48934 --- /dev/null +++ b/screen.cpp @@ -0,0 +1,309 @@ +#include +#include "screen.h" +#include "exceptions.h" +#include "unicode.h" + + +Screen::Screen() +{ + screen = NULL; + mouseImage = NULL; + mouseSave = NULL; + mouseVisible = false; + regionsList = NULL; + maxRegionsList = 0; +} + +Screen::~Screen() +{ + SDL_SetCursor(cursor); + if (mouseImage) SDL_FreeSurface(mouseImage); + if (mouseSave) SDL_FreeSurface(mouseSave); + if (regionsList) free(regionsList); +} + + +const VideoMode Screen::getVideoMode() const +{ + return VideoMode(screen->w, screen->h, screen->format->BitsPerPixel, fullScreen); +} + + +void Screen::setMode(const VideoMode& mode) +{ + fullScreen = mode.isFullScreen(); + + int flags = SDL_SWSURFACE /*| SDL_OPENGL*/; + if (fullScreen) + flags = flags | SDL_FULLSCREEN; + screen = SDL_SetVideoMode(mode.getWidth(), mode.getHeight(), mode.getBpp(), flags); + if (! screen) + throw Exception(L"Couldn't set video mode: " + + fromMbcs((SDL_GetError()))); +} + + +std::vector Screen::getFullScreenModes() const +{ + std::vector modes; + return modes; +} + + +int Screen::getWidth() const +{ + if (screen) + return screen->w; + else + throw Exception(L"No video mode selected"); +} + + +int Screen::getHeight() const +{ + if (screen) + return screen->h; + else + throw Exception(L"No video mode selected"); +} + +void Screen::centerMouse() +{ + if (screen) + SDL_WarpMouse(screen->w / 2, screen->h / 2); + else + throw Exception(L"No video mode selected"); +} + +void Screen::setMouseImage(SDL_Surface *image) +{ + if (mouseImage) { + SDL_FreeSurface(mouseImage); + mouseImage = NULL; + } + if (mouseSave) { + SDL_FreeSurface(mouseSave); + mouseSave = NULL; + } + + if (! image) return; + + mouseImage = SDL_DisplayFormat(image); + if (! mouseImage) + throw Exception(L"Error creating surface"); + //mouseSave = SDL_DisplayFormat(image); + mouseSave = SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_SRCCOLORKEY, + image->w, image->h, screen->format->BitsPerPixel, + screen->format->Rmask, screen->format->Gmask, + screen->format->Bmask, screen->format->Amask); + if (! mouseSave) { + SDL_FreeSurface(mouseImage); + throw Exception(L"Error creating buffer surface"); + } + SDL_SetColorKey(mouseImage, SDL_SRCCOLORKEY, + SDL_MapRGB(mouseImage->format, 0, 0, 0)); +} + + +void Screen::hideMouse() +{ + if (! mouseVisible) + return; + + if (! niceCursor) { + mouseVisible = false; + return; + } + + if (mouseSave) { + SDL_Rect src = { 0, 0, mouseSave->w, mouseSave->h }; + SDL_Rect dst = { saveX, saveY, mouseSave->w, mouseSave->h }; + if (src.w > 0) { + SDL_BlitSurface(mouseSave, &src, screen, &dst); + addRegionToUpdate(dst.x, dst.y, dst.w, dst.h); + } + } + mouseVisible = false; +} + +void Screen::showMouse() +{ + if (mouseVisible) + return; + + if (! niceCursor) { + mouseVisible = true; + return; + } + + if (mouseImage && mouseSave) { + int x, y; + SDL_GetMouseState(&x, &y); + saveX = x; + saveY = y; + SDL_Rect src = { 0, 0, mouseSave->w, mouseSave->h }; + SDL_Rect dst = { x, y, mouseImage->w, mouseImage->h }; + if (src.w > 0) { + SDL_BlitSurface(screen, &dst, mouseSave, &src); + SDL_BlitSurface(mouseImage, &src, screen, &dst); + addRegionToUpdate(dst.x, dst.y, dst.w, dst.h); + } + } + mouseVisible = true; +} + +void Screen::updateMouse() +{ + hideMouse(); + showMouse(); +} + +void Screen::flush() +{ + if (! regions.size()) return; + + if (! regionsList) { + regionsList = (SDL_Rect*)malloc(sizeof(SDL_Rect) * regions.size()); + if (! regionsList) { + regions.clear(); + throw Exception(L"Error allocating regions buffer"); + } + maxRegionsList = regions.size(); + } else { + if (maxRegionsList < (int)regions.size()) { + SDL_Rect *r = (SDL_Rect*)realloc(regionsList, + sizeof(SDL_Rect) * regions.size()); + if (! r) { + regions.clear(); + free(regionsList); + throw Exception(L"Error incrementing regions buffer"); + } + regionsList = r; + maxRegionsList = regions.size(); + } + } + + int j = 0; + for (std::list::iterator i = regions.begin(); + i != regions.end(); i++, j++) + regionsList[j] = *i; + + SDL_UpdateRects(screen, regions.size(), regionsList); + regions.clear(); +} + + +void Screen::addRegionToUpdate(int x, int y, int w, int h) +{ + if (((x >= getWidth()) || (y >= getHeight())) || (0 >= w) || (0 >= h)) + return; + if ((x + w < 0) || (y + h < 0)) + return; + if (x + w > getWidth()) + w = getWidth() - x; + if (y + h > getHeight()) + h = getHeight() - y; + if (0 > x) { + w = w + x; + x = 0; + } + if (0 > y) { + h = h + y; + y = 0; + } + SDL_Rect r = { x, y, w, h }; + regions.push_back(r); +} + + +void Screen::setPixel(int x, int y, int r, int g, int b) +{ + SDL_LockSurface(screen); + int bpp = screen->format->BytesPerPixel; + Uint32 pixel = SDL_MapRGB(screen->format, r, g, b); + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8*)screen->pixels + y * screen->pitch + x * bpp; + + switch(bpp) { + case 1: + *p = pixel; + break; + + case 2: + *(Uint16 *)p = pixel; + break; + + case 3: + if(SDL_BYTEORDER == SDL_BIG_ENDIAN) { + p[0] = (pixel >> 16) & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = pixel & 0xff; + } else { + p[0] = pixel & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = (pixel >> 16) & 0xff; + } + break; + + case 4: + *(Uint32 *)p = pixel; + break; + } + SDL_UnlockSurface(screen); +} + + +void Screen::draw(int x, int y, SDL_Surface *tile) +{ + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { x, y, tile->w, tile->h }; + SDL_BlitSurface(tile, &src, screen, &dst); +} + +void Screen::setCursor(bool nice) +{ + if (nice == niceCursor) + return; + + bool oldVisible = mouseVisible; + if (mouseVisible) + hideMouse(); + niceCursor = nice; + + if (niceCursor) + SDL_SetCursor(emptyCursor); + else + SDL_SetCursor(cursor); + + if (oldVisible) + showMouse(); +} + +void Screen::initCursors() +{ + cursor = SDL_GetCursor(); + Uint8 t = 0; + emptyCursor = SDL_CreateCursor(&t, &t, 8, 1, 0, 0); +} + +void Screen::doneCursors() +{ + if (niceCursor) + SDL_SetCursor(cursor); + SDL_FreeCursor(emptyCursor); +} + +SDL_Surface* Screen::createSubimage(int x, int y, int width, int height) +{ + SDL_Surface *s = SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_SRCCOLORKEY, + width, height, screen->format->BitsPerPixel, + screen->format->Rmask, screen->format->Gmask, + screen->format->Bmask, screen->format->Amask); + if (! s) + throw Exception(L"Error creating buffer surface"); + SDL_Rect src = { x, y, width, height }; + SDL_Rect dst = { 0, 0, width, height }; + SDL_BlitSurface(screen, &src, s, &dst); + return s; +} + diff --git a/screen.h b/screen.h new file mode 100644 index 0000000..2b5253d --- /dev/null +++ b/screen.h @@ -0,0 +1,78 @@ +#ifndef __SCREEN_H__ +#define __SCREEN_H__ + + +#include "SDL.h" +#include +#include + + +class VideoMode +{ + private: + int width; + int height; + int bpp; + bool fullScreen; + + public: + VideoMode(int w, int h, int bpp, bool fullscreen) + { + width = w; + height = h; + this->bpp = bpp; + this->fullScreen = fullscreen; + } + + public: + int getWidth() const { return width; }; + int getHeight() const { return height; }; + int getBpp() const { return bpp; }; + bool isFullScreen() const { return fullScreen; }; +}; + + +class Screen +{ + private: + SDL_Surface *screen; + bool fullScreen; + SDL_Surface *mouseImage; + SDL_Surface *mouseSave; + std::list regions; + bool mouseVisible; + SDL_Rect *regionsList; + int maxRegionsList; + int saveX, saveY; + bool niceCursor; + SDL_Cursor *cursor, *emptyCursor; + + public: + Screen(); + ~Screen(); + + public: + const VideoMode getVideoMode() const; + int getWidth() const; + int getHeight() const; + void setMode(const VideoMode& mode); + std::vector getFullScreenModes() const; + void centerMouse(); + void setMouseImage(SDL_Surface *image); + void hideMouse(); + void showMouse(); + void updateMouse(); + void flush(); + void addRegionToUpdate(int x, int y, int w, int h); + void setPixel(int x, int y, int r, int g, int b); + SDL_Surface* getSurface() { return screen; }; + void draw(int x, int y, SDL_Surface *surface); + void setCursor(bool nice); + void initCursors(); + void doneCursors(); + SDL_Surface* createSubimage(int x, int y, int width, int height); +}; + + +#endif + diff --git a/sound.cpp b/sound.cpp new file mode 100644 index 0000000..998245f --- /dev/null +++ b/sound.cpp @@ -0,0 +1,62 @@ +#include "sound.h" + +#include +#include +#include "resources.h" + + +Sound *sound; + + +Sound::Sound() +{ + int audio_rate = 22050; + Uint16 audio_format = AUDIO_S16; /* 16-bit stereo */ + int audio_channels = 2; + int audio_buffers = 1024; + disabled = Mix_OpenAudio(audio_rate, audio_format, audio_channels, + audio_buffers); + if (disabled) + std::cout << "Audio is disabled" << std::endl; +} + +Sound::~Sound() +{ + if (! disabled) + Mix_CloseAudio(); + for (ChunkMap::iterator i = chunkCache.begin(); i != chunkCache.end(); i++) + Mix_FreeChunk((*i).second); + Mix_CloseAudio(); +} + + +void Sound::play(const std::wstring &name) +{ + if (disabled || (! enableFx)) + return; + + Mix_Chunk *chunk = NULL; + + ChunkMap::iterator i = chunkCache.find(name); + if (i != chunkCache.end()) + chunk = (*i).second; + else { + ResDataHolder data(name); + chunk = Mix_LoadWAV_RW(SDL_RWFromMem(data.getData(), data.getSize()), + 0); + chunkCache[name] = chunk; + } + + if (chunk) { + Mix_VolumeChunk(chunk, (int)(volume * 128.0f)); + Mix_PlayChannel(-1, chunk, 0); + } + SDL_PumpEvents(); +} + +void Sound::setVolume(float v) +{ + volume = v; + enableFx = 0.01 < volume; +} + diff --git a/sound.h b/sound.h new file mode 100644 index 0000000..dc2a449 --- /dev/null +++ b/sound.h @@ -0,0 +1,35 @@ +#ifndef __SOUND_H__ +#define __SOUND_H__ + + +#include +#include +#include + + +class Sound +{ + private: + bool disabled; + + typedef std::map ChunkMap; + ChunkMap chunkCache; + + bool enableFx; + float volume; + + public: + Sound(); + ~Sound(); + + public: + void play(const std::wstring &name); + void setVolume(float volume); +}; + + +extern Sound *sound; + + +#endif + diff --git a/storage.cpp b/storage.cpp new file mode 100644 index 0000000..ff2b6b3 --- /dev/null +++ b/storage.cpp @@ -0,0 +1,48 @@ +#include "storage.h" + +#ifndef WIN32 +#include "tablestorage.h" +#else +#include "regstorage.h" +#endif + + +class StorageHolder +{ + private: + Storage *storage; + + public: + StorageHolder(); + ~StorageHolder(); + + public: + Storage* getStorage() { return storage; }; +}; + + +StorageHolder::StorageHolder() +{ +#ifndef WIN32 + storage = new TableStorage(); +#else + storage = new RegistryStorage(); +#endif +} + + +StorageHolder::~StorageHolder() +{ + if (storage) + delete storage; +} + + +static StorageHolder storageHolder; + + +Storage* getStorage() +{ + return storageHolder.getStorage(); +} + diff --git a/storage.h b/storage.h new file mode 100644 index 0000000..5b68d9f --- /dev/null +++ b/storage.h @@ -0,0 +1,27 @@ +#ifndef __STORAGE_H__ +#define __STORAGE_H__ + + +#include + + +class Storage +{ + public: + virtual ~Storage() { }; + + public: + virtual int get(const std::wstring &name, int dflt) = 0; + virtual std::wstring get(const std::wstring &name, + const std::wstring &dflt) = 0; + virtual void set(const std::wstring &name, int value) = 0; + virtual void set(const std::wstring &name, const std::wstring &value) = 0; + virtual void flush() = 0; +}; + + +Storage* getStorage(); + + +#endif + diff --git a/streams.cpp b/streams.cpp new file mode 100644 index 0000000..3644d3b --- /dev/null +++ b/streams.cpp @@ -0,0 +1,57 @@ +#include "streams.h" +#include "exceptions.h" +#include "unicode.h" + + +UtfStreamReader::UtfStreamReader(std::ifstream *s) +{ + stream = s; +} + +UtfStreamReader::~UtfStreamReader() +{ +} + +// This function is very slow because of poor fromUtf8 function design +wchar_t UtfStreamReader::getNextChar() +{ + unsigned char buf[10]; + + if (0 < backBuf.size()) { + wchar_t wc = backBuf.front(); + backBuf.pop_front(); + return wc; + } + + if (! stream->good()) + throw Exception(L"Error reading from stream 1"); + + int sz = stream->readsome((char*)buf, 1); + if (1 != sz) + throw Exception(L"Error reading from stream 2"); + int size = getUtf8Length(buf[0]); + if (size > 1) { + sz = stream->readsome((char*)buf + 1, size - 1); + if (size - 1 != sz) + throw Exception(L"Error reading from stream 3"); + } + buf[size] = 0; + std::string s((char*)buf); + std::wstring ws(fromUtf8(s)); + if (1 != ws.length()) + throw Exception(L"Error converting UTF-8 character to wide character"); + return ws[0]; +} + +void UtfStreamReader::ungetChar(wchar_t ch) +{ + backBuf.push_back(ch); +} + +bool UtfStreamReader::isEof() +{ + if (stream->eof()) + return true; // FIXME: it doesn't work. why? + return EOF == stream->peek(); +} + diff --git a/streams.h b/streams.h new file mode 100644 index 0000000..deb75ce --- /dev/null +++ b/streams.h @@ -0,0 +1,41 @@ +#ifndef __STREAMS_H__ +#define __STREAMS_H__ + + +#include +#include + + +/// Read utf-8 file and convert it to wide characters +class UtfStreamReader +{ + private: + /// Pointer to file stream + std::ifstream *stream; + + /// Push back buffet + std::list backBuf; + + public: + /// Create utf-8 stream reader. + /// \param stream pointer to file stream. + UtfStreamReader(std::ifstream *stream); + + /// Destructor + ~UtfStreamReader(); + + public: + /// Read next unicode character. + wchar_t getNextChar(); + + /// Push back character. + /// \param ch character to push back + void ungetChar(wchar_t ch); + + /// Check if end of file reached. + bool isEof(); +}; + + +#endif + diff --git a/table.cpp b/table.cpp new file mode 100644 index 0000000..58de711 --- /dev/null +++ b/table.cpp @@ -0,0 +1,434 @@ +#include "table.h" + +#include +#include "convert.h" +#include "unicode.h" +#include "streams.h" +#include "lexal.h" +#include "exceptions.h" + + +class IntValue: public Value +{ + private: + int value; + + public: + IntValue(int val) { value = val; }; + virtual ~IntValue() { }; + + public: + virtual Type getType() const { return Value::Integer; }; + virtual int asInt() const { return value; }; + virtual double asDouble() const { return value; }; + virtual std::wstring asString() const { return toString(value); }; + virtual ::Table* asTable() const { + throw Exception(L"Can't convert integer to table"); + }; + virtual Value* clone() const { return new IntValue(value); }; +}; + + +class DoubleValue: public Value +{ + private: + double value; + + public: + DoubleValue(double val) { value = val; }; + virtual ~DoubleValue() { }; + + public: + virtual Type getType() const { return Value::Double; }; + virtual int asInt() const { return (int)value; }; + virtual double asDouble() const { return value; }; + virtual std::wstring asString() const { return toString(value); }; + virtual ::Table* asTable() const { + throw Exception(L"Can't convert double to table"); + }; + virtual Value* clone() const { return new DoubleValue(value); }; +}; + + +class StringValue: public Value +{ + private: + std::wstring value; + + public: + StringValue(const std::wstring& val): value(val) { }; + virtual ~StringValue() { }; + + public: + virtual Type getType() const { return Value::String; }; + virtual int asInt() const { return strToInt(value); }; + virtual double asDouble() const { return strToDouble(value); }; + virtual std::wstring asString() const { return value; }; + virtual ::Table* asTable() const { + throw Exception(L"Can't convert string to table"); + }; + virtual Value* clone() const { return new StringValue(value); }; +}; + + +class TableValue: public Value +{ + private: + ::Table *value; + + public: + TableValue(::Table *val) { value = val; }; + virtual ~TableValue() { delete value; }; + + public: + virtual Type getType() const { return Value::Table; }; + virtual int asInt() const { + throw Exception(L"Can't convert table to int"); + }; + virtual double asDouble() const { + throw Exception(L"Can't convert table to double"); + }; + virtual std::wstring asString() const { + throw Exception(L"Can't convert table to string"); + }; + virtual ::Table* asTable() const { return value; }; + virtual Value* clone() const { + return new TableValue(new ::Table(*value)); + }; +}; + + +Table::Table(const Table &table) +{ + *this = table; +} + +Table::Table(const std::string &fileName) +{ + lastArrayIndex = 0; + std::ifstream stream(fileName.c_str(), + std::ios::binary | std::ios::in); + if (! stream.good()) + throw Exception(L"Error opening file '" + fromMbcs(fileName) + L""); + UtfStreamReader reader(&stream); + Lexal lexal = Lexal(reader); + parse(lexal, false, 0, 0); +} + +Table::Table(Lexal &lexal, int line, int pos) +{ + lastArrayIndex = 0; + parse(lexal, true, line, pos); +} + +Table::Table() +{ + lastArrayIndex = 0; +} + + +Table::~Table() +{ + for (ValuesMap::iterator i = fields.begin(); i != fields.end(); i++) + delete (*i).second; +} + + +Table& Table::operator = (const Table &table) +{ + if (this == &table) + return *this; + + fields.clear(); + lastArrayIndex = table.lastArrayIndex; + for (ValuesMap::const_iterator i = table.fields.begin(); + i != table.fields.end(); i++) + fields[(*i).first] = (*i).second->clone(); + + return *this; +} + + +static Value* lexToValue(Lexal &lexal, const Lexeme &lexeme) +{ + switch (lexeme.getType()) + { + case Lexeme::Ident: + case Lexeme::String: + return new StringValue(lexeme.getContent()); + case Lexeme::Integer: + return new IntValue(strToInt(lexeme.getContent())); + case Lexeme::Float: + return new DoubleValue(strToDouble(lexeme.getContent())); + case Lexeme::Symbol: + if (L"{" == lexeme.getContent()) + return new TableValue(new Table(lexal, lexeme.getLine(), + lexeme.getPos())); + default: + throw Exception(L"Invalid lexeme type at " + lexeme.getPosStr()); + } +} + + +void Table::addValuePair(Lexal &lexal, const std::wstring &name) +{ + Lexeme lex = lexal.getNext(); + if (Lexeme::Eof == lex.getType()) + throw Exception(L"Unexpected end of file"); + fields[name] = lexToValue(lexal, lex); +} + +void Table::addArrayElement(Lexal &lexal, const Lexeme &lexeme) +{ + fields[numToStr(lastArrayIndex)] = lexToValue(lexal, lexeme); + lastArrayIndex++; +} + +void Table::parse(Lexal &lexal, bool needBracket, int startLine, int startPos) +{ + Lexeme lex; + bool read = true; + + while (true) { + if (read) { + lex = lexal.getNext(); + read = true; + } + Lexeme::Type type = lex.getType(); + if (Lexeme::Eof == type) { + if (! needBracket) + return; + else + throw Exception(L"Table started at " + Lexal::posToStr( + startLine, startPos) + L" is never finished"); + } else if ((Lexeme::Symbol == type) && (L"}" == lex.getContent()) + && needBracket) + return; + else if ((Lexeme::Symbol == type) && ((L"," == lex.getContent()) || + (L";" == lex.getContent()))) { + // ignore separator + } else if (Lexeme::Ident == type) { + Lexeme nextLex = lexal.getNext(); + if (Lexeme::Symbol == nextLex.getType()) { + if (L"=" == nextLex.getContent()) + addValuePair(lexal, lex.getContent()); + else { + addArrayElement(lexal, lex); + lex = nextLex; + read = false; + } + } else + throw Exception(L"Unexpected token at " + Lexal::posToStr( + startLine, startPos)); + } else + addArrayElement(lexal, lex); + } +} + + +bool Table::hasKey(const std::wstring &key) const +{ + return (fields.end() != fields.find(key)); +} + + +static std::wstring encodeString(const std::wstring &str) +{ + std::wstring res; + int len = str.length(); + + res += L"\""; + for (int i = 0; i < len; i++) { + wchar_t ch = str[i]; + switch (ch) { + case '\n': res += L"\\n"; break; + case '\r': res += L"\\r"; break; + case '\'': res += L"\\'"; break; + case '\"': res += L"\\\""; break; + case '\\': res += L"\\\\"; break; + default: + res += ch; + } + } + res += L"\""; + + return res; +} + +static std::wstring printValue(Value *value, bool butify, int spaces) +{ + if (! value) + return L""; + + switch (value->getType()) { + case Value::Integer: + return toString(value->asInt()); + + case Value::Double: + { + std::wstring s = toString(value->asDouble()); + if (s.find(L'.') >= s.length()) + s.append(L".0"); + return s; + } + + case Value::String: + return encodeString(value->asString()); + + case Value::Table: + return value->asTable()->toString(true, butify, spaces); + } + + return L""; +} + + +bool Table::isArray() const +{ + int size = fields.size(); + + for (int i = 0; i < size; i++) { + if (! hasKey(::toString(i))) + return false; + } + + return true; +} + + +std::wstring Table::toString() const +{ + return toString(false, true, 0); +} + + +static bool isInteger(const std::wstring &str) +{ + int sz = str.length(); + for (int i = 0; i < sz; i++) { + wchar_t ch = str[i]; + if ((ch < L'0') || (ch > L'9')) + return false; + } + return true; +} + + +std::wstring Table::toString(bool printBraces, bool butify, int spaces) const +{ + std::wstring res; + + if (printBraces) + res += butify ? L"{\n" : L"{"; + bool printNames = ! isArray(); + + for (ValuesMap::const_iterator i = fields.begin(); i != fields.end(); + i++) + { + const std::wstring &name = (*i).first; + Value *value = (*i).second; + if (butify) + for (int j = 0; j < spaces; j++) + res += L" "; + else + res += L" "; + if (printNames && (! isInteger(name))) + res += name + L" = "; + res += printValue(value, butify, spaces + 4); + if (printNames) + res += butify ? L";\n" : L";"; + else + res += butify ? L",\n" : L","; + } + + if (printBraces) { + if (! butify) + res += L" }"; + else { + int ident = (spaces >= 4) ? spaces - 4 : 0; + for (int j = 0; j < ident; j++) + res += L" "; + res += L"}"; + } + } + + return res; +} + + +Value::Type Table::getType(const std::wstring &key) const +{ + ValuesMap::const_iterator i = fields.find(key); + if (i == fields.end()) + throw Exception(L"Field '" + key + L"' doesn't exists in the table"); + return (*i).second->getType(); +} + + +std::wstring Table::getString(const std::wstring &key, + const std::wstring &dflt) const +{ + ValuesMap::const_iterator i = fields.find(key); + return (i != fields.end()) ? (*i).second->asString() : dflt; +} + +int Table::getInt(const std::wstring &key, int dflt) const +{ + ValuesMap::const_iterator i = fields.find(key); + return (i != fields.end()) ? ((*i).second ? (*i).second->asInt() : dflt) : dflt; +} + +double Table::getDouble(const std::wstring &key, double dflt) const +{ + ValuesMap::const_iterator i = fields.find(key); + return (i != fields.end()) ? (*i).second->asDouble() : dflt; +} + +Table* Table::getTable(const std::wstring &key, Table *dflt) const +{ + ValuesMap::const_iterator i = fields.find(key); + return (i != fields.end()) ? (*i).second->asTable() : dflt; +} + +void Table::setValue(const std::wstring &key, Value *value) +{ + ValuesMap::const_iterator i = fields.find(key); + if (i != fields.end()) + delete (*i).second; + fields[key] = value; +} + +void Table::setString(const std::wstring &key, const std::wstring &value) +{ + setValue(key, new StringValue(value)); +} + +void Table::setInt(const std::wstring &key, int value) +{ + setValue(key, new IntValue(value)); +} + +void Table::setDouble(const std::wstring &key, double value) +{ + setValue(key, new DoubleValue(value)); +} + +void Table::setTable(const std::wstring &key, Table *value) +{ + setValue(key, new TableValue(value)); +} + + +void Table::save(const std::wstring &fileName) const +{ + std::ofstream stream(toMbcs(fileName).c_str(), std::ios::out + | std::ios::binary); + if (! stream.good()) + throw Exception(L"Can't open '" + fileName + L"' for writing"); + std::string utfStr(toUtf8(toString())); + stream.write(utfStr.c_str(), utfStr.length()); + if (! stream.good()) + throw Exception(L"Can't write table to file '" + fileName + L"'"); + stream.close(); +} + diff --git a/table.h b/table.h new file mode 100644 index 0000000..de2188e --- /dev/null +++ b/table.h @@ -0,0 +1,84 @@ +#ifndef __TABLE_H__ +#define __TABLE_H__ + + +#include +#include +#include "lexal.h" + + +class Table; + + +class Value +{ + public: + typedef enum Type { + Integer, + Double, + String, + Table + }; + + public: + virtual ~Value() { }; + + public: + virtual ::Table* asTable() const = 0; + virtual Type getType() const = 0; + virtual int asInt() const = 0; + virtual double asDouble() const = 0; + virtual std::wstring asString() const = 0; + virtual Value* clone() const = 0; +}; + + +class Table +{ + public: + typedef std::map ValuesMap; + typedef ValuesMap::const_iterator Iterator; + + private: + ValuesMap fields; + int lastArrayIndex; + + public: + Table(); + Table(const Table &table); + Table(const std::string &fileName); + Table(Lexal &lexal, int startLine, int startPos); + ~Table(); + + public: + Table& operator = (const Table &table); + std::wstring toString() const; + std::wstring toString(bool printBraces, bool butify, int ident) const; + bool isArray() const; + void save(const std::wstring &fileName) const; + + public: + Iterator begin() const { return fields.begin(); }; + Iterator end() const { return fields.end(); }; + bool hasKey(const std::wstring &key) const; + Value::Type getType(const std::wstring &key) const; + std::wstring getString(const std::wstring &key, const std::wstring &dflt = L"") const; + int getInt(const std::wstring &key, int dflt=0) const; + double getDouble(const std::wstring &key, double dflt=0) const; + Table* getTable(const std::wstring &key, Table *dflt=NULL) const; + void setString(const std::wstring &key, const std::wstring &value); + void setInt(const std::wstring &key, int value); + void setDouble(const std::wstring &key, double value); + void setTable(const std::wstring &key, Table *value); + + private: + void parse(Lexal &lexal, bool needBracket, int startLine, int startPos); + void addArrayElement(Lexal &lexal, const Lexeme &lexeme); + void addValuePair(Lexal &lexal, const std::wstring &name); + void setValue(const std::wstring &key, Value *value); +}; + + + +#endif + diff --git a/tablestorage.cpp b/tablestorage.cpp new file mode 100644 index 0000000..2c25808 --- /dev/null +++ b/tablestorage.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include "tablestorage.h" +#include "unicode.h" +#include "exceptions.h" + + +TableStorage::TableStorage() +{ + try { + table = Table(toMbcs(getFileName())); + } catch (Exception &e) { + std::cerr << e.getMessage() << std::endl; + } catch (...) { + std::cerr << "Unknown config file error" << std::endl; + } +} + +TableStorage::~TableStorage() +{ + flush(); +} + +std::wstring TableStorage::getFileName() +{ +#ifndef WIN32 + return std::wstring(fromMbcs(getenv("HOME"))) + L"/.einstein/einsteinrc"; +#else + return L"einstein.cfg"; +#endif +} + +int TableStorage::get(const std::wstring &name, int dflt) +{ + return table.getInt(name, dflt); +} + +std::wstring TableStorage::get(const std::wstring &name, + const std::wstring &dflt) +{ + return table.getString(name, dflt); +} + +void TableStorage::set(const std::wstring &name, int value) +{ + table.setInt(name, value); +} + +void TableStorage::set(const std::wstring &name, const std::wstring &value) +{ + table.setString(name, value); +} + +void TableStorage::flush() +{ + table.save(getFileName()); +} + diff --git a/tablestorage.h b/tablestorage.h new file mode 100644 index 0000000..77c82a4 --- /dev/null +++ b/tablestorage.h @@ -0,0 +1,32 @@ +#ifndef __TABLESTORAGE_H__ +#define __TABLESTORAGE_H__ + + +#include "storage.h" +#include "table.h" + + +class TableStorage: public Storage +{ + private: + Table table; + + public: + TableStorage(); + virtual ~TableStorage(); + + public: + virtual int get(const std::wstring &name, int dflt); + virtual std::wstring get(const std::wstring &name, + const std::wstring &dflt); + virtual void set(const std::wstring &name, int value); + virtual void set(const std::wstring &name, const std::wstring &value); + virtual void flush(); + + private: + std::wstring getFileName(); +}; + + +#endif + diff --git a/tokenizer.cpp b/tokenizer.cpp new file mode 100644 index 0000000..a540a46 --- /dev/null +++ b/tokenizer.cpp @@ -0,0 +1,72 @@ +#include "tokenizer.h" + +#include + + +std::wstring Token::toString() const +{ + if (Word == type) + return L"Word: '" + content + L"'"; + else if (Para == type) + return L"Para"; + else if (Eof == type) + return L"Eof"; + else + return L"Unknown"; +} + + +static bool wisspace(wchar_t ch) +{ +//std::cout << "'" << ch << "' " << ((int)ch) << " " << L'\n' << std::endl; + return (L' ' == ch) || (L'\n' == ch) || (L'\r' == ch) + || (L'\t' == ch) || (L'\f' == ch) || (L'\v' == ch); +} + + +bool Tokenizer::skipSpaces(bool notSearch) +{ + int len = text.length(); + bool foundDoubleReturn = false; + while ((len > currentPos) && wisspace(text[currentPos])) { + currentPos++; + if ((! notSearch) && (L'\n' == text[currentPos - 1]) + && (currentPos < len) && (L'\n' == text[currentPos])) + notSearch = foundDoubleReturn = true; + } + return foundDoubleReturn; +} + + +Token Tokenizer::getNextToken() +{ + if (0 < stack.size()) { + Token t(*stack.begin()); + stack.pop_front(); + return t; + } + int len = text.length(); + if (skipSpaces(! currentPos) && (currentPos < len)) + return Token(Token::Para); + if (currentPos >= len) + return Token(Token::Eof); + int wordStart = currentPos; + while ((len > currentPos) && (! wisspace(text[currentPos]))) + currentPos++; + return Token(Token::Word, text.substr(wordStart, currentPos - wordStart)); +} + + +void Tokenizer::unget(const Token &token) +{ + stack.push_back(token); +} + + +bool Tokenizer::isFinished() +{ + if (0 < stack.size()) + return false; + return currentPos >= (int)text.length(); +} + diff --git a/tokenizer.h b/tokenizer.h new file mode 100644 index 0000000..21c2249 --- /dev/null +++ b/tokenizer.h @@ -0,0 +1,61 @@ +#ifndef __TOKENIZER_H__ +#define __TOKENIZER_H__ + + +#include +#include + + +class Tokenizer; + + +class Token +{ + friend class Tokenizer; + + public: + enum Type { + Word, + Para, + Eof + }; + + private: + Type type; + std::wstring content; + + private: + Token(Type type) { this->type = type; }; + Token(Type type, const std::wstring &content): content(content) { + this->type = type; + } + + public: + Type getType() const { return type; }; + const std::wstring& getContent() const { return content; }; + std::wstring toString() const; +}; + + +class Tokenizer +{ + private: + std::wstring text; + int currentPos; + std::list stack; + + public: + Tokenizer(const std::wstring &s): text(s) { currentPos = 0; }; + + public: + Token getNextToken(); + void unget(const Token &token); + bool isFinished(); + + private: + bool skipSpaces(bool notSearch); +}; + + +#endif + diff --git a/topscores.cpp b/topscores.cpp new file mode 100644 index 0000000..07f9149 --- /dev/null +++ b/topscores.cpp @@ -0,0 +1,175 @@ +#include "topscores.h" +#include "storage.h" +#include "utils.h" +#include "font.h" +#include "convert.h" +#include "messages.h" + + +TopScores::TopScores() +{ + Storage *storage = getStorage(); + + for (int i = 0; i < MAX_SCORES; i++) { + int score = storage->get(L"top_score_" + toString(i), -1); + if (score < 0) + break; + std::wstring name = storage->get(L"top_name_" + toString(i), L""); + add(name, score); + } + + modifed = false; +} + + +TopScores::~TopScores() +{ + save(); +} + +int TopScores::add(const std::wstring &name, int score) +{ + if (score >= getMaxScore() || (scores.size() < 1)) { + if (! isFull()) { + Entry e = { name, score }; + scores.push_back(e); + modifed = true; + return scores.size() - 1; + } + return -1; + } + + int pos = 0; + for (ScoresList::iterator i = scores.begin(); i != scores.end(); i++) { + Entry &e = *i; + if (e.score > score) { + Entry ne = { name, score }; + scores.insert(i, ne); + modifed = true; + break; + } + pos++; + } + + while (scores.size() > MAX_SCORES) { + modifed = true; + scores.erase(--scores.end()); + } + + return modifed ? pos : -1; +} + +void TopScores::save() +{ + if (! modifed) + return; + + Storage *storage = getStorage(); + int no = 0; + + for (ScoresList::iterator i = scores.begin(); i != scores.end(); i++) { + Entry &e = *i; + storage->set(L"top_name_" + toString(no), e.name); + storage->set(L"top_score_" + toString(no), e.score); + no++; + } + + storage->flush(); + modifed = false; +} + +TopScores::ScoresList& TopScores::getScores() +{ + return scores; +} + +int TopScores::getMaxScore() +{ + if (scores.size() < 1) + return -1; + ScoresList::iterator i = scores.end(); + i--; + return (*i).score; +} + + +class ScoresWindow: public Window +{ + public: + ScoresWindow(int x, int y, TopScores *scores, int highlight); +}; + + +ScoresWindow::ScoresWindow(int x, int y, TopScores *scores, int highlight): + Window(x, y, 320, 350, L"blue.bmp") +{ + Font titleFont(L"nova.ttf", 26); + Font entryFont(L"laudcn2.ttf", 14); + Font timeFont(L"luximb.ttf", 14); + + std::wstring txt = msg(L"topScores"); + int w = titleFont.getWidth(txt); + titleFont.draw(background, (320 - w) / 2, 15, 255,255,0, true, txt); + + TopScores::ScoresList &list = scores->getScores(); + int no = 1; + int pos = 70; + for (TopScores::ScoresList::iterator i = list.begin(); + i != list.end(); i++) + { + TopScores::Entry &e = *i; + std::wstring s(toString(no) + L"."); + int w = entryFont.getWidth(s); + int c = ((no - 1) == highlight) ? 0 : 255; + entryFont.draw(background, 30 - w, pos, 255,255,c, true, s); + SDL_Rect rect = { 40, pos-20, 180, 40 }; + SDL_SetClipRect(background, &rect); + entryFont.draw(background, 40, pos, 255,255,c, true, e.name); + SDL_SetClipRect(background, NULL); + s = secToStr(e.score); + w = timeFont.getWidth(s); + timeFont.draw(background, 305-w, pos, 255,255,c, true, s); + pos += 20; + no++; + } +} + + +void showScoresWindow(Area *parentArea, TopScores *scores, int highlight) +{ + Area area; + + Font font(L"laudcn2.ttf", 16); + area.add(parentArea); + area.add(new ScoresWindow(240, 125, scores, highlight)); + ExitCommand exitCmd(area); + area.add(new Button(348, 430, 90, 25, &font, 255,255,0, L"blue.bmp", + msg(L"ok"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + area.run(); +} + + +std::wstring enterNameDialog(Area *parentArea) +{ + Area area; + + Font font(L"laudcn2.ttf", 16); + area.add(parentArea); + area.add(new Window(170, 280, 460, 100, L"blue.bmp")); + Storage *storage = getStorage(); + std::wstring name = storage->get(L"lastName", msg(L"anonymous")); + area.add(new Label(&font, 180, 300, 255,255,0, msg(L"enterName"))); + area.add(new InputField(350, 300, 270, 26, L"blue.bmp", name, 20, + 255,255,0, &font)); + ExitCommand exitCmd(area); + area.add(new Button(348, 340, 90, 25, &font, 255,255,0, L"blue.bmp", + msg(L"ok"), &exitCmd)); + area.add(new KeyAccel(SDLK_ESCAPE, &exitCmd)); + area.add(new KeyAccel(SDLK_RETURN, &exitCmd)); + area.run(); + storage->set(L"lastName", name); + return name; +} + + diff --git a/topscores.h b/topscores.h new file mode 100644 index 0000000..23b4f81 --- /dev/null +++ b/topscores.h @@ -0,0 +1,44 @@ +#ifndef __TOPSCORES_H__ +#define __TOPSCORES_H__ + + +#include +#include +#include "widgets.h" + + +#define MAX_SCORES 10 + + +class TopScores +{ + public: + typedef struct { + std::wstring name; + int score; + } Entry; + typedef std::list ScoresList; + + private: + ScoresList scores; + bool modifed; + + public: + TopScores(); + ~TopScores(); + + public: + int add(const std::wstring &name, int scores); + void save(); + ScoresList& getScores(); + int getMaxScore(); + bool isFull() { return scores.size() >= MAX_SCORES; }; +}; + + +void showScoresWindow(Area *area, TopScores *scores, int highlightPos=-1); +std::wstring enterNameDialog(Area *area); + + +#endif + diff --git a/unicode.cpp b/unicode.cpp new file mode 100644 index 0000000..5a12260 --- /dev/null +++ b/unicode.cpp @@ -0,0 +1,595 @@ +#include +#include +#ifdef WIN32 +#include +#endif +#include "unicode.h" +#include "exceptions.h" + + +/// Returns length of wide character in utf-8 +#define UTF8_LENGTH(Char) \ + ((Char) < 0x80 ? 1 : \ + ((Char) < 0x800 ? 2 : \ + ((Char) < 0x10000 ? 3 : \ + ((Char) < 0x200000 ? 4 : \ + ((Char) < 0x4000000 ? 5 : 6))))) + +#define UTF8_COMPUTE(Char, Mask, Len) \ + if (Char < 128) \ + { \ + Len = 1; \ + Mask = 0x7f; \ + } \ + else if ((Char & 0xe0) == 0xc0) \ + { \ + Len = 2; \ + Mask = 0x1f; \ + } \ + else if ((Char & 0xf0) == 0xe0) \ + { \ + Len = 3; \ + Mask = 0x0f; \ + } \ + else if ((Char & 0xf8) == 0xf0) \ + { \ + Len = 4; \ + Mask = 0x07; \ + } \ + else if ((Char & 0xfc) == 0xf8) \ + { \ + Len = 5; \ + Mask = 0x03; \ + } \ + else if ((Char & 0xfe) == 0xfc) \ + { \ + Len = 6; \ + Mask = 0x01; \ + } \ + else \ + Len = -1; + + + +#ifndef WIN32 + +static const char utf8_skip_data[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 +}; + +const char * const g_utf8_skip = utf8_skip_data; + +#define g_utf8_next_char(p) (char *)((p) + g_utf8_skip[*(unsigned char *)(p)]) + +#define UTF8_GET(Result, Chars, Count, Mask, Len) \ + (Result) = (Chars)[0] & (Mask); \ + for ((Count) = 1; (Count) < (Len); ++(Count)) \ + { \ + if (((Chars)[(Count)] & 0xc0) != 0x80) \ + { \ + (Result) = -1; \ + break; \ + } \ + (Result) <<= 6; \ + (Result) |= ((Chars)[(Count)] & 0x3f); \ + } + +/* Like g_utf8_get_char, but take a maximum length + * and return (wchar_t)-2 on incomplete trailing character + */ +static inline wchar_t +g_utf8_get_char_extended (const char *p, + size_t max_len) +{ + unsigned int i, len; + wchar_t wc = (unsigned char) *p; + + if (wc < 0x80) + { + return wc; + } + else if (wc < 0xc0) + { + return (wchar_t)-1; + } + else if (wc < 0xe0) + { + len = 2; + wc &= 0x1f; + } + else if (wc < 0xf0) + { + len = 3; + wc &= 0x0f; + } + else if (wc < 0xf8) + { + len = 4; + wc &= 0x07; + } + else if (wc < 0xfc) + { + len = 5; + wc &= 0x03; + } + else if (wc < 0xfe) + { + len = 6; + wc &= 0x01; + } + else + { + return (wchar_t)-1; + } + + if (max_len >= 0 && len > max_len) + { + for (i = 1; i < max_len; i++) + { + if ((((unsigned char *)p)[i] & 0xc0) != 0x80) + return (wchar_t)-1; + } + return (wchar_t)-2; + } + + for (i = 1; i < len; ++i) + { + wchar_t ch = ((unsigned char *)p)[i]; + + if ((ch & 0xc0) != 0x80) + { + if (ch) + return (wchar_t)-1; + else + return (wchar_t)-2; + } + + wc <<= 6; + wc |= (ch & 0x3f); + } + + if (UTF8_LENGTH(wc) != len) + return (wchar_t)-1; + + return wc; +} + +/** + * g_utf8_get_char: + * @p: a pointer to Unicode character encoded as UTF-8 + * + * Converts a sequence of bytes encoded as UTF-8 to a Unicode character. + * If @p does not point to a valid UTF-8 encoded character, results are + * undefined. If you are not sure that the bytes are complete + * valid Unicode characters, you should use g_utf8_get_char_validated() + * instead. + * + * Return value: the resulting character + **/ +wchar_t +g_utf8_get_char (const char *p) +{ + int i, mask = 0, len; + wchar_t result; + unsigned char c = (unsigned char) *p; + + UTF8_COMPUTE (c, mask, len); + if (len == -1) + return (wchar_t)-1; + UTF8_GET (result, p, i, mask, len); + + return result; +} + + +/** + * g_utf8_to_ucs4: + * @str: a UTF-8 encoded string + * @len: the maximum length of @str to use. If @len < 0, then + * the string is nul-terminated. + * @items_read: location to store number of bytes read, or %NULL. + * If %NULL, then %G_CONVERT_ERROR_PARTIAL_INPUT will be + * returned in case @str contains a trailing partial + * character. If an error occurs then the index of the + * invalid input is stored here. + * @items_written: location to store number of characters written or %NULL. + * The value here stored does not include the trailing 0 + * character. + * @error: location to store the error occuring, or %NULL to ignore + * errors. Any of the errors in #GConvertError other than + * %G_CONVERT_ERROR_NO_CONVERSION may occur. + * + * Convert a string from UTF-8 to a 32-bit fixed width + * representation as UCS-4. A trailing 0 will be added to the + * string after the converted text. + * + * Return value: a pointer to a newly allocated UCS-4 string. + * This value must be freed with g_free(). If an + * error occurs, %NULL will be returned and + * @error set. + **/ +wchar_t * +g_utf8_to_ucs4 (const char *str, + long len, + long *items_read, + long *items_written, + wchar_t **error) +{ + wchar_t *result = NULL; + int n_chars, i; + const char *in; + + in = str; + n_chars = 0; + while ((len < 0 || str + len - in > 0) && *in) + { + wchar_t wc = g_utf8_get_char_extended (in, str + len - in); + if (wc & 0x80000000) + { + if (wc == (wchar_t)-2) + { + if (items_read) + break; + else + if (error) + *error = L"Partial character sequence at end of input"; + } + else + if (error) + *error = L"Invalid byte sequence in conversion input"; + + goto err_out; + } + + n_chars++; + + in = g_utf8_next_char (in); + } + + result = (wchar_t*)malloc((n_chars + 1) * sizeof(wchar_t)); + + in = str; + for (i=0; i < n_chars; i++) + { + result[i] = g_utf8_get_char (in); + in = g_utf8_next_char (in); + } + result[i] = 0; + + if (items_written) + *items_written = n_chars; + + err_out: + if (items_read) + *items_read = in - str; + + return result; +} + +/** + * g_unichar_to_utf8: + * @c: a ISO10646 character code + * @outbuf: output buffer, must have at least 6 bytes of space. + * If %NULL, the length will be computed and returned + * and nothing will be written to @outbuf. + * + * Converts a single character to UTF-8. + * + * Return value: number of bytes written + **/ +int +g_unichar_to_utf8 (wchar_t c, + char *outbuf) +{ + unsigned int len = 0; + int first; + int i; + + if (c < 0x80) + { + first = 0; + len = 1; + } + else if (c < 0x800) + { + first = 0xc0; + len = 2; + } + else if (c < 0x10000) + { + first = 0xe0; + len = 3; + } + else if (c < 0x200000) + { + first = 0xf0; + len = 4; + } + else if (c < 0x4000000) + { + first = 0xf8; + len = 5; + } + else + { + first = 0xfc; + len = 6; + } + + if (outbuf) + { + for (i = len - 1; i > 0; --i) + { + outbuf[i] = (c & 0x3f) | 0x80; + c >>= 6; + } + outbuf[0] = c | first; + } + + return len; +} + +/** + * g_ucs4_to_utf8: + * @str: a UCS-4 encoded string + * @len: the maximum length of @str to use. If @len < 0, then + * the string is terminated with a 0 character. + * @items_read: location to store number of characters read read, or %NULL. + * @items_written: location to store number of bytes written or %NULL. + * The value here stored does not include the trailing 0 + * byte. + * @error: location to store the error occuring, or %NULL to ignore + * errors. Any of the errors in #GConvertError other than + * %G_CONVERT_ERROR_NO_CONVERSION may occur. + * + * Convert a string from a 32-bit fixed width representation as UCS-4. + * to UTF-8. The result will be terminated with a 0 byte. + * + * Return value: a pointer to a newly allocated UTF-8 string. + * This value must be freed with g_free(). If an + * error occurs, %NULL will be returned and + * @error set. + **/ +char * +g_ucs4_to_utf8 (const wchar_t *str, + long len, + long *items_read, + long *items_written, + wchar_t **error) +{ + int result_length; + char *result = NULL; + char *p; + int i; + + result_length = 0; + for (i = 0; len < 0 || i < len ; i++) + { + if (!str[i]) + break; + + if ((unsigned)str[i] >= 0x80000000) + { + if (items_read) + *items_read = i; + if (error) + *error = L"Character out of range for UTF-8"; + goto err_out; + } + + result_length += UTF8_LENGTH (str[i]); + } + + result = (char*)malloc (result_length + 1); + p = result; + + i = 0; + while (p < result + result_length) + p += g_unichar_to_utf8 (str[i++], p); + + *p = '\0'; + + if (items_written) + *items_written = p - result; + + err_out: + if (items_read) + *items_read = i; + + return result; +} + +std::string toUtf8(const std::wstring &str) +{ + long readed, writed; + wchar_t *errMsg = NULL; + + char *res = g_ucs4_to_utf8(str.c_str(), str.length(), &readed, + &writed, &errMsg); + if (! res) { + if (errMsg) + throw Exception(errMsg); + else + throw Exception(L"Error converting text to UTF-8"); + } + + std::string s(res); + free(res); + + return s; +} + +std::wstring fromUtf8(const std::string &str) +{ + long readed, writed; + wchar_t *errMsg = NULL; + + wchar_t *res = g_utf8_to_ucs4(str.c_str(), str.length(), &readed, + &writed, &errMsg); + if (! res) { + if (errMsg) + throw Exception(errMsg); + else + throw Exception(L"Error converting text from UTF-8"); + } + + std::wstring s(res); + free(res); + + return s; +} + + +#else + + + +std::string toUtf8(const std::wstring &str) +{ + if (! str.length()) + return ""; + + int len = str.length(); + int bufSize = (len + 1) * 6 + 1; + char buf[bufSize]; + int res = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), len + 1, + buf, bufSize, NULL, NULL); + + if (! res) + throw Exception(L"Error converting UCS-2 to UTF-8"); + return buf; +} + +std::wstring fromUtf8(const std::string &str) +{ + if (! str.length()) + return L""; + + int len = str.length(); + wchar_t buf[len + 1]; + + int res = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), len + 1, + buf, len + 1); + if (! res) + throw Exception(L"Error converting UTF-8 to UCS-2"); + return buf; +} + + +std::string toOem(const std::wstring &str) +{ + if (! str.length()) + return ""; + + int len = str.length(); + int bufSize = (len + 1) * 6 + 1; + char buf[bufSize]; + int res = WideCharToMultiByte(CP_OEMCP, 0, str.c_str(), len + 1, + buf, bufSize, NULL, NULL); + + if (! res) + throw Exception(L"Error converting UCS-2 to OEM"); + return buf; +} + +std::wstring fromOem(const std::string &str) +{ + if (! str.length()) + return L""; + + int len = str.length(); + wchar_t buf[len + 1]; + + int res = MultiByteToWideChar(CP_OEMCP, 0, str.c_str(), len + 1, + buf, len + 1); + if (! res) + throw Exception(L"Error converting OEM to UCS-2"); + return buf; +} + +#endif + + +std::wstring fromUtf8(const char *str, int len) +{ + char *buf = (char*)malloc(len + 1); + if (! buf) + throw Exception(L"Error allocating memory"); + memcpy(buf, str, len); + buf[len] = 0; + std::string s(buf); + free(buf); + return fromUtf8(s); +} + + +std::string toMbcs(const std::wstring &str) +{ + int len = str.length(); + if (! len) + return ""; + else { + int maxSize = MB_CUR_MAX * len; + char buf[maxSize + 1]; + size_t l = wcstombs(buf, str.c_str(), maxSize); + if ((size_t)-1 == -l) { // convert what we can + std::string res; + for (int i = 0; i < len; i++) { + int b = wctomb(buf, str[i]); + if (0 < b) { + buf[b] = 0; + res += buf; + } + } + return res; + } else { + buf[l] = 0; + return buf; + } + } +} + + +std::wstring fromMbcs(const std::string &str) +{ + int maxLen = str.length(); + wchar_t ws[maxLen + 1]; + size_t cnt = mbstowcs(ws, str.c_str(), maxLen); + if (cnt == (size_t)-1) { + return L""; + } + ws[cnt] = 0; + return ws; +} + + +std::ostream& operator << (std::ostream &stream, const std::wstring &str) +{ +#ifdef WIN32 + if ((stream == std::cout) || (stream == std::cerr) || + (stream == std::clog)) + stream << toOem(str); + else +#endif + stream << toMbcs(str); + return stream; +} + + +int getUtf8Length(unsigned char c) +{ + int mask, len; + UTF8_COMPUTE(c, mask, len); + if (-1 == len) + throw Exception(L"Invalid utf-8 character"); + else + return len; +} + diff --git a/unicode.h b/unicode.h new file mode 100644 index 0000000..93fd0b9 --- /dev/null +++ b/unicode.h @@ -0,0 +1,44 @@ +#ifndef __UNICODE_H__ +#define __UNICODE_H__ + + +/// \file unicode.h +/// Definition of UNICODE handling rotinues. + + +#include +#include + + +/// Convert unicode string to multibyte string in UTF-8 encoding +/// \param str string in unicode +std::string toUtf8(const std::wstring &str); + +/// Convert multibyte string in UTF-8 encoding to unicode string +/// \param str string in UTF-8 encoding +std::wstring fromUtf8(const std::string &str); + +/// Convert multibyte string in UTF-8 encoding to unicode string +/// \param str string in UTF-8 encoding +/// \param len string length in bytes +std::wstring fromUtf8(const char *str, int len); + +/// Convert unicode string to multibyte string in system default encoding +/// \param str string in unicode +std::string toMbcs(const std::wstring &str); + +/// Convert unicode string to multibyte string in system default encoding +/// \param str string in unicode +std::wstring fromMbcs(const std::string &str); + +/// Convert unicode string to default multibyte encoding +/// and write it to stream. +/// \param stream stream +/// \param str string to output +std::ostream& operator << (std::ostream &stream, const std::wstring &str); + +/// Returns length of UTF-8 character in bytes by first UTF-8 character byte. +/// \param c first byte of UTF-8 character. +int getUtf8Length(unsigned char c); + +#endif diff --git a/utils.cpp b/utils.cpp new file mode 100644 index 0000000..38c7780 --- /dev/null +++ b/utils.cpp @@ -0,0 +1,419 @@ +#include +#include +#include +#include +#include + +//#ifndef WIN32 +#include +#include +#include +//#endif + +#include + +#include "utils.h" +#include "main.h" +#include "unicode.h" +#include "sound.h" + + + +int getCornerPixel(SDL_Surface *surface) +{ + SDL_LockSurface(surface); + int bpp = surface->format->BytesPerPixel; + Uint8 *p = (Uint8*)surface->pixels; + int pixel = 0; + switch (bpp) { + case 1: pixel = *p; break; + case 2: pixel = *(Uint16 *)p; break; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) + pixel = p[0] << 16 | p[1] << 8 | p[2]; + else + pixel = p[0] | p[1] << 8 | p[2] << 16; + break; + case 4: pixel = *(Uint32 *)p; break; + default: pixel = 0; /* shouldn't happen, but avoids warnings */ + } + SDL_UnlockSurface(surface); + return pixel; +} + + + +SDL_Surface* loadImage(const std::wstring &name, bool transparent) +{ + int size; + void *bmp; + + bmp = resources->getRef(name, size); + if (! bmp) + throw Exception(name + L" is not found"); + SDL_RWops *op = SDL_RWFromMem(bmp, size); + SDL_Surface *s = SDL_LoadBMP_RW(op, 0); + SDL_FreeRW(op); + resources->delRef(bmp); + if (! s) + throw Exception(L"Error loading " + name); + SDL_Surface *screenS = SDL_DisplayFormat(s); + SDL_FreeSurface(s); + if (! screenS) + throw Exception(L"Error translating to screen format " + name); + if (transparent) + SDL_SetColorKey(screenS, SDL_SRCCOLORKEY, getCornerPixel(screenS)); + return screenS; +} + + +#ifdef WIN32 +#include +struct timezone { }; + +int gettimeofday(struct timeval* tp, int* /*tz*/) +{ + struct timeb tb; + ftime(&tb); + tp->tv_sec = tb.time; + tp->tv_usec = 1000*tb.millitm; + return 0; +} + +int gettimeofday(struct timeval* tp, struct timezone* /*tz*/) +{ + return gettimeofday(tp, (int*)NULL); +} +#endif + + + +int gettimeofday(struct timeval* tp) +{ +#ifdef WIN32 + return gettimeofday(tp, (int*)NULL); +#else + struct timezone tz; + return gettimeofday(tp, &tz); +#endif +} + +void drawWallpaper(const std::wstring &name) +{ + SDL_Surface *tile = loadImage(name); + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { 0, 0, tile->w, tile->h }; + for (int y = 0; y < screen.getHeight(); y += tile->h) + for (int x = 0; x < screen.getWidth(); x += tile->w) { + dst.x = x; + dst.y = y; + SDL_BlitSurface(tile, &src, screen.getSurface(), &dst); + } + SDL_FreeSurface(tile); +} + + +void setPixel(SDL_Surface *s, int x, int y, int r, int g, int b) +{ + int bpp = s->format->BytesPerPixel; + Uint32 pixel = SDL_MapRGB(s->format, r, g, b); + /* Here p is the address to the pixel we want to set */ + Uint8 *p = (Uint8*)s->pixels + y * s->pitch + x * bpp; + + switch (bpp) { + case 1: + *p = pixel; + break; + case 2: + *(Uint16 *)p = pixel; + break; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { + p[0] = (pixel >> 16) & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = pixel & 0xff; + } else { + p[0] = pixel & 0xff; + p[1] = (pixel >> 8) & 0xff; + p[2] = (pixel >> 16) & 0xff; + } + break; + case 4: + *(Uint32 *)p = pixel; + break; + } +} + + +void getPixel(SDL_Surface *surface, int x, int y, + Uint8 *r, Uint8 *g, Uint8 *b) +{ + int bpp = surface->format->BytesPerPixel; + /* Here p is the address to the pixel we want to retrieve */ + Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp; + + Uint32 pixel; + switch (bpp) { + case 1: pixel = *p; break; + case 2: pixel = *(Uint16 *)p; break; + case 3: + if (SDL_BYTEORDER == SDL_BIG_ENDIAN) + pixel = p[0] << 16 | p[1] << 8 | p[2]; + else + pixel = p[0] | p[1] << 8 | p[2] << 16; + break; + case 4: pixel = *(Uint32 *)p; break; + default: pixel = 0; /* shouldn't happen, but avoids warnings */ + } + SDL_GetRGB(pixel, surface->format, r, g, b); +} + + +static int gammaTable[256]; +static double lastGamma = -1.0; + + +void adjustBrightness(SDL_Surface *image, int x, int y, double k) +{ + if (lastGamma != k) { + for (int i = 0; i <= 255; i++) { + gammaTable[i] = (int)(255.0 * pow((double)i / 255.0, 1.0 / k) + 0.5); + if (gammaTable[i] > 255) + gammaTable[i] = 255; + } + lastGamma = k; + } + + Uint8 r, g, b; + getPixel(image, x, y, &r, &g, &b); + setPixel(image, x, y, gammaTable[r], gammaTable[g], gammaTable[b]); +} + + +SDL_Surface* adjustBrightness(SDL_Surface *image, double k, bool transparent) +{ + if (lastGamma != k) { + for (int i = 0; i <= 255; i++) { + gammaTable[i] = (int)(255.0 * pow((double)i / 255.0, 1.0 / k) + 0.5); + if (gammaTable[i] > 255) + gammaTable[i] = 255; + } + lastGamma = k; + } + + SDL_Surface *s = SDL_DisplayFormat(image); + if (! s) + throw Exception(L"Error converting image to display format"); + + SDL_LockSurface(s); + + Uint8 r, g, b; + for (int j = 0; j < s->h; j++) + for (int i = 0; i < s->w; i++) { + getPixel(s, i, j, &r, &g, &b); + setPixel(s, i, j, gammaTable[r], gammaTable[g], gammaTable[b]); + } + + SDL_UnlockSurface(s); + + if (transparent) + SDL_SetColorKey(s, SDL_SRCCOLORKEY, getCornerPixel(s)); + + return s; +} + + +class CenteredBitmap: public Widget +{ + private: + SDL_Surface *tile; + int x, y; + + public: + CenteredBitmap(const std::wstring &fileName) { + tile = loadImage(fileName); + x = (screen.getWidth() - tile->w) / 2; + y = (screen.getHeight() - tile->h) / 2; + }; + + virtual ~CenteredBitmap() { + SDL_FreeSurface(tile); + }; + + virtual void draw() { + screen.draw(x, y, tile); + screen.addRegionToUpdate(x, y, tile->w, tile->h); + }; +}; + + +void showWindow(Area *parentArea, const std::wstring &fileName) +{ + Area area; + + area.add(parentArea); + area.add(new CenteredBitmap(fileName)); + area.add(new AnyKeyAccel()); + area.run(); + sound->play(L"click.wav"); +} + + +bool isInRect(int evX, int evY, int x, int y, int w, int h) +{ + return ((evX >= x) && (evX < x + w) && (evY >= y) && (evY < y + h)); +} + +std::wstring secToStr(int time) +{ + int hours = time / 3600; + int v = time - hours * 3600; + int minutes = v / 60; + int seconds = v - minutes * 60; + + wchar_t buf[50]; +#ifdef WIN32 + swprintf(buf, L"%02i:%02i:%02i", hours, minutes, seconds); +#else + swprintf(buf, 50, L"%02i:%02i:%02i", hours, minutes, seconds); +#endif + + return buf; +} + + +void showMessageWindow(Area *parentArea, const std::wstring &pattern, + int width, int height, Font *font, int r, int g, int b, + const std::wstring &msg) +{ + Area area; + + int x = (screen.getWidth() - width) / 2; + int y = (screen.getHeight() - height) / 2; + + area.add(parentArea); + area.add(new Window(x, y, width, height, pattern, 6)); + area.add(new Label(font, x, y, width, height, Label::ALIGN_CENTER, + Label::ALIGN_MIDDLE, r, g, b, msg)); + area.add(new AnyKeyAccel()); + area.run(); + sound->play(L"click.wav"); +} + + +void drawBevel(SDL_Surface *s, int left, int top, int width, int height, + bool raised, int size) +{ + double k, f, kAdv, fAdv; + if (raised) { + k = 2.6; + f = 0.1; + kAdv = -0.2; + fAdv = 0.1; + } else { + f = 2.6; + k = 0.1; + fAdv = -0.2; + kAdv = 0.1; + } + for (int i = 0; i < size; i++) { + for (int j = i; j < height - i - 1; j++) + adjustBrightness(s, left + i, top + j, k); + for (int j = i; j < width - i; j++) + adjustBrightness(s, left + j, top + i, k); + for (int j = i+1; j < height - i; j++) + adjustBrightness(s, left + width - i - 1, top + j, f); + for (int j = i; j < width - i - 1; j++) + adjustBrightness(s, left + j, top + height - i - 1, f); + k += kAdv; + f += fAdv; + } +} + +//#ifndef WIN32 + +void ensureDirExists(const std::wstring &fileName) +{ + std::string s(toMbcs(fileName)); + struct stat buf; + if (! stat(s.c_str(), &buf)) { + if (! S_ISDIR(buf.st_mode)) + unlink(s.c_str()); + else + return; + } +#ifndef WIN32 + mkdir(s.c_str(), S_IRUSR | S_IWUSR | S_IXUSR); +#else + mkdir(s.c_str()); +#endif +} + +/*#else + +void ensureDirExists(const std::wstring &fileName) +{ + PORT ME! +} + +#endif*/ + +int readInt(std::istream &stream) +{ + if (stream.fail()) + throw Exception(L"Error reading string"); + unsigned char buf[4]; + stream.read((char*)buf, 4); + if (stream.fail()) + throw Exception(L"Error reading string"); + return buf[0] + buf[1] * 256 + buf[2] * 256 * 256 + + buf[3] * 256 * 256 * 256; +} + + +std::wstring readString(std::istream &stream) +{ + std::string str; + char c; + + if (stream.fail()) + throw Exception(L"Error reading string"); + + c = stream.get(); + while (c && (! stream.fail())) { + str += c; + c = stream.get(); + } + + if (stream.fail()) + throw Exception(L"Error reading string"); + + return fromUtf8(str); +} + +void writeInt(std::ostream &stream, int v) +{ + unsigned char b[4]; + int i, ib; + + for (i = 0; i < 4; i++) { + ib = v & 0xFF; + v = v >> 8; + b[i] = ib; + } + + stream.write((char*)&b, 4); +} + +void writeString(std::ostream &stream, const std::wstring &value) +{ + std::string s(toUtf8(value)); + stream.write(s.c_str(), s.length() + 1); +} + +int readInt(unsigned char *buf) +{ + return buf[0] + buf[1] * 256 + buf[2] * 256 * 256 + + buf[3] * 256 * 256 * 256; +} + diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..9ce7cb1 --- /dev/null +++ b/utils.h @@ -0,0 +1,44 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +#include +#include +#ifdef WIN32 +#include +#endif +#include +#include "resources.h" +#include "widgets.h" + + + +SDL_Surface* loadImage(const std::wstring &name, bool transparent=false); +SDL_Surface* adjustBrightness(SDL_Surface *image, double k, bool transparent=false); +int gettimeofday(struct timeval* tp); +void drawWallpaper(const std::wstring &name); +void showWindow(Area *area, const std::wstring &fileName); +bool isInRect(int evX, int evY, int x, int y, int w, int h); +std::wstring numToStr(int no); +void adjustBrightness(SDL_Surface *image, int x, int y, double k); +std::wstring secToStr(int time); +void showMessageWindow(Area *area, const std::wstring &pattern, + int width, int height, Font *font, int r, int g, int b, + const std::wstring &msg); +int getCornerPixel(SDL_Surface *surface); +void getPixel(SDL_Surface *surface, int x, int y, + Uint8 *r, Uint8 *g, Uint8 *b); +void setPixel(SDL_Surface *s, int x, int y, int r, int g, int b); +void drawBevel(SDL_Surface *s, int left, int top, int width, int height, + bool raised, int size); +void ensureDirExists(const std::wstring &fileName); +int readInt(std::istream &stream); +std::wstring readString(std::istream &stream); +void writeInt(std::ostream &stream, int value); +void writeString(std::ostream &stream, const std::wstring &value); + +/// Read 4-bytes integer from memory. +int readInt(unsigned char *buffer); + + +#endif + diff --git a/verthints.cpp b/verthints.cpp new file mode 100644 index 0000000..8d2ffd0 --- /dev/null +++ b/verthints.cpp @@ -0,0 +1,193 @@ +#include "verthints.h" +#include "main.h" +#include "utils.h" +#include "puzgen.h" +#include "sound.h" + + +#define TILE_NUM 15 +#define TILE_GAP 4 +#define TILE_X 12 +#define TILE_Y 495 +#define TILE_WIDTH 48 +#define TILE_HEIGHT 48 + + +VertHints::VertHints(IconSet &is, Rules &r): iconSet(is) +{ + reset(r); +} + + +VertHints::VertHints(IconSet &is, Rules &rl, std::istream &stream): iconSet(is) +{ + int qty = readInt(stream); + + for (int i = 0; i < qty; i++) { + int no = readInt(stream); + numbersArr.push_back(no); + Rule *r = getRule(rl, no); + int excluded = readInt(stream); + if (excluded) { + excludedRules.push_back(r); + rules.push_back(NULL); + } else { + excludedRules.push_back(NULL); + rules.push_back(r); + } + } + + showExcluded = readInt(stream); + + int x, y; + SDL_GetMouseState(&x, &y); + highlighted = getRuleNo(x, y); +} + +void VertHints::reset(Rules &r) +{ + rules.clear(); + excludedRules.clear(); + numbersArr.clear(); + + int no = 0; + for (Rules::iterator i = r.begin(); i != r.end(); i++) { + Rule *rule = *i; + if (rule->getShowOpts() == Rule::SHOW_VERT) { + rules.push_back(rule); + excludedRules.push_back(NULL); + numbersArr.push_back(no); + } + no++; + } + + showExcluded = false; + + int x, y; + SDL_GetMouseState(&x, &y); + highlighted = getRuleNo(x, y); +} + +void VertHints::draw() +{ + for (int i = 0; i < TILE_NUM; i++) + drawCell(i, true); +} + + +void VertHints::drawCell(int col, bool addToUpdate) +{ + int x = TILE_X + col * (TILE_WIDTH + TILE_GAP); + int y = TILE_Y; + + Rule *r = NULL; + if (col < (int)rules.size()) { + if (showExcluded) + r = excludedRules[col]; + else + r = rules[col]; + } + if (r) + r->draw(x, y, iconSet, highlighted == col); + else { + screen.draw(x, y, iconSet.getEmptyHintIcon()); + screen.draw(x, y + TILE_HEIGHT, iconSet.getEmptyHintIcon()); + } + + if (addToUpdate) + screen.addRegionToUpdate(x, y, TILE_WIDTH, TILE_HEIGHT*2); +} + + +bool VertHints::onMouseButtonDown(int button, int x, int y) +{ + if (button != 3) + return false; + + int no = getRuleNo(x, y); + if (no < 0) return false; + + if (no < (int)rules.size()) { + if (showExcluded) { + Rule *r = excludedRules[no]; + if (r) { + sound->play(L"whizz.wav"); + rules[no] = r; + excludedRules[no] = NULL; + drawCell(no); + } + } else { + Rule *r = rules[no]; + if (r) { + sound->play(L"whizz.wav"); + rules[no] = NULL; + excludedRules[no] = r; + drawCell(no); + } + } + } + + return true; +} + + +void VertHints::toggleExcluded() +{ + showExcluded = !showExcluded; + draw(); +} + + +bool VertHints::onMouseMove(int x, int y) +{ + int no = getRuleNo(x, y); + + if (no != highlighted) { + int old = highlighted; + highlighted = no; + if (isActive(old)) + drawCell(old); + if (isActive(no)) + drawCell(no); + } + + return false; +} + + +int VertHints::getRuleNo(int x, int y) +{ + if (! isInRect(x, y, TILE_X, TILE_Y, (TILE_WIDTH + TILE_GAP) * TILE_NUM, + TILE_HEIGHT * 2)) + return -1; + + x = x - TILE_X; + y = y - TILE_Y; + + int no = x / (TILE_WIDTH + TILE_GAP); + if (no * (TILE_WIDTH + TILE_GAP) + TILE_WIDTH < x) + return -1; + + return no; +} + +bool VertHints::isActive(int ruleNo) +{ + if ((ruleNo < 0) || (ruleNo >= (int)rules.size())) + return false; + Rule *r = showExcluded ? excludedRules[ruleNo] : rules[ruleNo]; + return r != NULL; +} + + +void VertHints::save(std::ostream &stream) +{ + int cnt = numbersArr.size(); + writeInt(stream, cnt); + for (int i = 0; i < cnt; i++) { + writeInt(stream, numbersArr[i]); + writeInt(stream, rules[i] ? 0 : 1); + } + writeInt(stream, showExcluded ? 1 : 0); +} + diff --git a/verthints.h b/verthints.h new file mode 100644 index 0000000..860657a --- /dev/null +++ b/verthints.h @@ -0,0 +1,41 @@ +#ifndef __VERTHINTS_H__ +#define __VERTHINTS_H__ + + +#include +#include "iconset.h" +#include "puzgen.h" +#include "widgets.h" + + + +class VertHints: public Widget +{ + private: + IconSet &iconSet; + typedef std::vector RulesArr; + RulesArr rules; + RulesArr excludedRules; + std::vector numbersArr; + bool showExcluded; + int highlighted; + + public: + VertHints(IconSet &is, Rules &rules); + VertHints(IconSet &is, Rules &rules, std::istream &stream); + + public: + virtual void draw(); + void drawCell(int col, bool addToUpdate=true); + virtual bool onMouseButtonDown(int button, int x, int y); + void toggleExcluded(); + int getRuleNo(int x, int y); + virtual bool onMouseMove(int x, int y); + bool isActive(int ruleNo); + void save(std::ostream &stream); + void reset(Rules &rules); +}; + + +#endif + diff --git a/visitor.h b/visitor.h new file mode 100644 index 0000000..35cb7d8 --- /dev/null +++ b/visitor.h @@ -0,0 +1,18 @@ +#ifndef __VISITOR_H__ +#define __VISITOR_H__ + + +/// Abstract visitor +template +class Visitor +{ + public: + virtual ~Visitor() { }; + + /// Called at every visit + virtual void onVisit(T &t) = 0; +}; + + +#endif + diff --git a/widgets.cpp b/widgets.cpp new file mode 100644 index 0000000..7730ee7 --- /dev/null +++ b/widgets.cpp @@ -0,0 +1,1013 @@ +#include "widgets.h" +#include "main.h" +#include "utils.h" +#include "sound.h" + + +////////////////////////////////////////////////////////////////// +// +// Button +// +////////////////////////////////////////////////////////////////// + + +Button::Button(int x, int y, const std::wstring &name, Command *cmd, + bool transparent) +{ + image = loadImage(name, transparent); + highlighted = adjustBrightness(image, 1.5, transparent); + + left = x; + top = y; + width = image->w; + height = image->h; + + mouseInside = false; + command = cmd; +} + + +Button::Button(int x, int y, int w, int h, Font *font, + int fR, int fG, int fB, int hR, int hG, int hB, + const std::wstring &text, Command *cmd) +{ + left = x; + top = y; + width = w; + height = h; + + SDL_Surface *s = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, + 24, 0x00FF0000, 0x0000FF00, 0x000000FF, 0/*0xFF000000*/); + SDL_Rect src = { x, y, width, height }; + SDL_Rect dst = { 0, 0, width, height }; + SDL_BlitSurface(screen.getSurface(), &src, s, &dst); + + int tW, tH; + font->getSize(text, tW, tH); + font->draw(s, (width - tW) / 2, (height - tH) / 2, fR, fG, fB, true, text); + image = SDL_DisplayFormat(s); + SDL_BlitSurface(screen.getSurface(), &src, s, &dst); + font->draw(s, (width - tW) / 2, (height - tH) / 2, hR, hG, hB, true, text); + highlighted = SDL_DisplayFormat(s); + SDL_FreeSurface(s); + + mouseInside = false; + command = cmd; +} + + +Button::Button(int x, int y, int w, int h, Font *font, + int r, int g, int b, const std::wstring &bg, + const std::wstring &text, bool bevel, Command *cmd) +{ + left = x; + top = y; + width = w; + height = h; + + SDL_Surface *s = screen.getSurface(); + image = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, + s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, + s->format->Bmask, s->format->Amask); + + SDL_Surface *tile = loadImage(bg, true); + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { 0, 0, tile->w, tile->h }; + for (int j = 0; j < height; j += tile->h) + for (int i = 0; i < width; i += tile->w) { + dst.x = i; + dst.y = j; + SDL_BlitSurface(tile, &src, image, &dst); + } + SDL_FreeSurface(tile); + + if (bevel) { + SDL_LockSurface(image); + drawBevel(image, 0, 0, width, height, false, 1); + drawBevel(image, 1, 1, width - 2, height - 2, true, 1); + SDL_UnlockSurface(image); + } + + int tW, tH; + font->getSize(text, tW, tH); + font->draw(image, (width - tW) / 2, (height - tH) / 2, r, g, b, true, text); + + highlighted = adjustBrightness(image, 1.5, false); + SDL_SetColorKey(image, SDL_SRCCOLORKEY, getCornerPixel(image)); + SDL_SetColorKey(highlighted, SDL_SRCCOLORKEY, getCornerPixel(highlighted)); + + mouseInside = false; + command = cmd; +} + + + +Button::Button(int x, int y, int w, int h, Font *font, + int r, int g, int b, const std::wstring &bg, + const std::wstring &text, Command *cmd) +{ + left = x; + top = y; + width = w; + height = h; + + SDL_Surface *s = screen.getSurface(); + image = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, + s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, + s->format->Bmask, s->format->Amask); + + SDL_Surface *tile = loadImage(bg); + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { 0, 0, tile->w, tile->h }; + for (int j = 0; j < height; j += tile->h) + for (int i = 0; i < width; i += tile->w) { + dst.x = i; + dst.y = j; + SDL_BlitSurface(tile, &src, image, &dst); + } + SDL_FreeSurface(tile); + + SDL_LockSurface(image); + drawBevel(image, 0, 0, width, height, false, 1); + drawBevel(image, 1, 1, width - 2, height - 2, true, 1); + SDL_UnlockSurface(image); + + int tW, tH; + font->getSize(text, tW, tH); + font->draw(image, (width - tW) / 2, (height - tH) / 2, r, g, b, true, text); + + highlighted = adjustBrightness(image, 1.5, false); + + mouseInside = false; + command = cmd; +} + + +Button::~Button() +{ + SDL_FreeSurface(image); + SDL_FreeSurface(highlighted); +} + + +void Button::draw() +{ + if (mouseInside) + screen.draw(left, top, highlighted); + else + screen.draw(left, top, image); + screen.addRegionToUpdate(left, top, width, height); +} + + +void Button::getBounds(int &l, int &t, int &w, int &h) +{ + l = left; + t = top; + w = width; + h = height; +} + + +bool Button::onMouseButtonDown(int button, int x, int y) +{ + if (isInRect(x, y, left, top, width, height)) { + sound->play(L"click.wav"); + if (command) + command->doAction(); + return true; + } else + return false; +} + + +bool Button::onMouseMove(int x, int y) +{ + bool in = isInRect(x, y, left, top, width, height); + if (in != mouseInside) { + mouseInside = in; + draw(); + } + return false; +} + + +////////////////////////////////////////////////////////////////// +// +// KeyAccel +// +////////////////////////////////////////////////////////////////// + + +KeyAccel::KeyAccel(SDLKey sym, Command *cmd) +{ + command = cmd; + key = sym; +} + + +bool KeyAccel::onKeyDown(SDLKey k, unsigned char ch) +{ + if (key == k) { + if (command) + command->doAction(); + return true; + } else + return false; +} + + +////////////////////////////////////////////////////////////////// +// +// Area +// +////////////////////////////////////////////////////////////////// + + +Area::Area() +{ + timer = NULL; +} + +Area::~Area() +{ + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) { + Widget *w = *i; + if (w && w->destroyByArea() && (! notManagedWidgets.count(w))) + delete w; + } +} + +void Area::add(Widget *widget, bool managed) +{ + widgets.push_back(widget); + if (! managed) + notManagedWidgets.insert(widget); + widget->setParent(this); +} + +void Area::remove(Widget *widget) +{ + widgets.remove(widget); + notManagedWidgets.insert(widget); +} + +void Area::handleEvent(const SDL_Event &event) +{ + switch (event.type) { + case SDL_MOUSEBUTTONDOWN: + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + if ((*i)->onMouseButtonDown(event.button.button, + event.button.x, event.button.y)) + return; + break; + + case SDL_MOUSEBUTTONUP: + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + if ((*i)->onMouseButtonUp(event.button.button, + event.button.x, event.button.y)) + return; + break; + + case SDL_MOUSEMOTION: + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + if ((*i)->onMouseMove(event.motion.x, event.motion.y)) + return; + break; + + case SDL_VIDEOEXPOSE: + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + (*i)->draw(); + break; + + case SDL_KEYDOWN: + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + if ((*i)->onKeyDown(event.key.keysym.sym, + (unsigned char)event.key.keysym.unicode)) + return; + break; + + case SDL_QUIT: + exit(0); + } +} + +void Area::run() +{ + terminate = false; + SDL_Event event; + + Uint32 lastTimer = 0; + draw(); + screen.showMouse(); + + bool runTimer = timer ? true : false; + bool dispetchEvent; + while (! terminate) { + dispetchEvent = true; + if (! timer) { + SDL_WaitEvent(&event); + } else { + Uint32 now = SDL_GetTicks(); + if (now - lastTimer > time) { + lastTimer = now; + runTimer = true; + } + if (! SDL_PollEvent(&event)) { + if (! runTimer) { + SDL_Delay(20); + continue; + } else + dispetchEvent = false; + } + } + screen.hideMouse(); + if (runTimer) { + if (timer) + timer->onTimer(); + runTimer = false; + } + if (dispetchEvent) + handleEvent(event); + if (! terminate) { + screen.showMouse(); + screen.flush(); + } + } +} + +void Area::finishEventLoop() +{ + terminate = true; +} + + +void Area::draw() +{ + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + (*i)->draw(); +} + + +void Area::setTimer(Uint32 interval, TimerHandler *t) +{ + time = interval; + timer = t; +} + + +void Area::updateMouse() +{ + int x, y; + SDL_GetMouseState(&x, &y); + + for (WidgetsList::iterator i = widgets.begin(); i != widgets.end(); i++) + if ((*i)->onMouseMove(x, y)) + return; +} + + + +////////////////////////////////////////////////////////////////// +// +// AnyKeyAccel +// +////////////////////////////////////////////////////////////////// + + + +AnyKeyAccel::AnyKeyAccel() +{ + command = NULL; +} + +AnyKeyAccel::AnyKeyAccel(Command *cmd) +{ + command = cmd; +} + +AnyKeyAccel::~AnyKeyAccel() +{ +} + +bool AnyKeyAccel::onKeyDown(SDLKey key, unsigned char ch) +{ + if (((key >= SDLK_NUMLOCK) && (key <= SDLK_COMPOSE)) || + (key == SDLK_TAB) || (key == SDLK_UNKNOWN)) + return false; + + if (command) + command->doAction(); + else + area->finishEventLoop(); + return true; +} + +bool AnyKeyAccel::onMouseButtonDown(int button, int x, int y) +{ + if (command) + command->doAction(); + else + area->finishEventLoop(); + return true; +} + + + +////////////////////////////////////////////////////////////////// +// +// Window +// +////////////////////////////////////////////////////////////////// + + + +Window::Window(int x, int y, int w, int h, const std::wstring &bg, + bool frameWidth, bool raised) +{ + left = x; + top = y; + width = w; + height = h; + + SDL_Surface *s = screen.getSurface(); + SDL_Surface *win = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, + s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, + s->format->Bmask, s->format->Amask); + + SDL_Surface *tile = loadImage(bg); + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { 0, 0, tile->w, tile->h }; + for (int j = 0; j < height; j += tile->h) + for (int i = 0; i < width; i += tile->w) { + dst.x = i; + dst.y = j; + SDL_BlitSurface(tile, &src, win, &dst); + } + SDL_FreeSurface(tile); + + SDL_LockSurface(win); + double k = 2.6; + double f = 0.1; + for (int i = 0; i < frameWidth; i++) { + double ltK, rbK; + if (raised) { + ltK = k; rbK = f; + } else { + ltK = f; rbK = k; + } + for (int j = i; j < height - i - 1; j++) + adjustBrightness(win, i, j, ltK); + for (int j = i; j < width - i; j++) + adjustBrightness(win, j, i, ltK); + for (int j = i+1; j < height - i; j++) + adjustBrightness(win, width - i - 1, j, rbK); + for (int j = i; j < width - i - 1; j++) + adjustBrightness(win, j, height - i - 1, rbK); + k -= 0.2; + f += 0.1; + } + SDL_UnlockSurface(win); + + background = SDL_DisplayFormat(win); + SDL_FreeSurface(win); +} + + +Window::~Window() +{ + SDL_FreeSurface(background); +} + + +void Window::draw() +{ + screen.draw(left, top, background); + screen.addRegionToUpdate(left, top, width, height); +} + + + +////////////////////////////////////////////////////////////////// +// +// Label +// +////////////////////////////////////////////////////////////////// + + + +Label::Label(Font *f, int x, int y, int r, int g, int b, std::wstring s, + bool sh): text(s) +{ + font = f; + left = x; + top = y; + red = r; + green = g; + blue = b; + hAlign = ALIGN_LEFT; + vAlign = ALIGN_TOP; + shadow = sh; +} + + +Label::Label(Font *f, int x, int y, int w, int h, HorAlign hA, VerAlign vA, + int r, int g, int b, const std::wstring &s): + text(s) +{ + font = f; + left = x; + top = y; + red = r; + green = g; + blue = b; + hAlign = hA; + vAlign = vA; + width = w; + height = h; + shadow = true; +} + + +void Label::draw() +{ + int w, h, x, y; + font->getSize(text, w, h); + + switch (hAlign) { + case ALIGN_RIGHT: x = left + width - w; break; + case ALIGN_CENTER: x = left + (width - w) / 2; break; + default: x = left; + } + + switch (vAlign) { + case ALIGN_BOTTOM: y = top + height - h; break; + case ALIGN_MIDDLE: y = top + (height - h) / 2; break; + default: y = top; + } + + font->draw(x, y, red,green,blue, shadow, text); + screen.addRegionToUpdate(x, y, w, h); +} + + + +////////////////////////////////////////////////////////////////// +// +// InputField +// +////////////////////////////////////////////////////////////////// + + +InputField::InputField(int x, int y, int w, int h, const std::wstring &background, + std::wstring &s, int maxLen, int r, int g, int b, Font *f): + Window(x, y, w, h, background, 1, false), text(s) +{ + maxLength = maxLen; + red = r; + green = g; + blue = b; + font = f; + moveCursor(text.length()); + SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + SDL_EnableUNICODE(1); +} + +InputField::~InputField() +{ + SDL_EnableKeyRepeat(0, 0); +} + +void InputField::draw() +{ + Window::draw(); + + SDL_Rect rect = { left+1, top+1, width-2, height-2 }; + SDL_SetClipRect(screen.getSurface(), &rect); + + font->draw(left+1, top+1, red,green,blue, true, text); + + if (cursorVisible) { + int pos = 0; + if (cursorPos > 0) + pos += font->getWidth(text.substr(0, cursorPos)); + for (int i = 2; i < height-2; i++) { + screen.setPixel(left + pos, top + i, red, green, blue); + screen.setPixel(left + pos + 1, top + i, red, green, blue); + } + } + + SDL_SetClipRect(screen.getSurface(), NULL); +} + +void InputField::setParent(Area *a) +{ + Window::setParent(a); + area->setTimer(100, this); +} + + +void InputField::onTimer() +{ + Uint32 now = SDL_GetTicks(); + if (now - lastCursor > 1000) { + cursorVisible = ! cursorVisible; + lastCursor = now; + draw(); + } +} + + +bool InputField::onKeyDown(SDLKey key, unsigned char translatedChar) +{ + switch (key) { + case SDLK_BACKSPACE: + if (cursorPos > 0) { + text.erase(cursorPos - 1, 1); + moveCursor(cursorPos - 1); + } else + moveCursor(cursorPos); + draw(); + return true; + + case SDLK_LEFT: + if (cursorPos > 0) + moveCursor(cursorPos - 1); + else + moveCursor(cursorPos); + draw(); + return true; + + case SDLK_RIGHT: + if (cursorPos < (int)text.length()) + moveCursor(cursorPos + 1); + else + moveCursor(cursorPos); + draw(); + return true; + + case SDLK_HOME: + moveCursor(0); + draw(); + return true; + + case SDLK_END: + moveCursor(text.length()); + draw(); + return true; + + case SDLK_DELETE: + if (cursorPos < (int)text.length()) + text.erase(cursorPos, 1); + moveCursor(cursorPos); + draw(); + return true; + + default: ; + } + + if (translatedChar > 31) + onCharTyped(translatedChar); + return false; +} + +bool InputField::onKeyUp(SDLKey key) +{ + return false; +} + +void InputField::onCharTyped(unsigned char ch) +{ + if ((int)text.length() < maxLength) { + wchar_t buf[2]; + buf[0] = ch; + buf[1] = 0; + text.insert(cursorPos, buf); + moveCursor(cursorPos + 1); + } else + moveCursor(cursorPos); + draw(); +} + +void InputField::moveCursor(int pos) +{ + lastCursor = SDL_GetTicks(); + cursorVisible = true; + cursorPos = pos; +} + + +////////////////////////////////////////////////////////////////// +// +// Checkbox +// +////////////////////////////////////////////////////////////////// + + + +Checkbox::Checkbox(int x, int y, int w, int h, Font *font, + int r, int g, int b, const std::wstring &bg, + bool &chk): checked(chk) +{ + left = x; + top = y; + width = w; + height = h; + checked = chk; + + SDL_Surface *s = screen.getSurface(); + image = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, + s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, + s->format->Bmask, s->format->Amask); + + SDL_Surface *tile = loadImage(bg); + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { 0, 0, tile->w, tile->h }; + for (int j = 0; j < height; j += tile->h) + for (int i = 0; i < width; i += tile->w) { + dst.x = i; + dst.y = j; + SDL_BlitSurface(tile, &src, image, &dst); + } + SDL_FreeSurface(tile); + + SDL_LockSurface(image); + drawBevel(image, 0, 0, width, height, false, 1); + drawBevel(image, 1, 1, width - 2, height - 2, true, 1); + SDL_UnlockSurface(image); + + highlighted = adjustBrightness(image, 1.5, false); + + checkedImage = SDL_DisplayFormat(image); + int tW, tH; + font->getSize(L"X", tW, tH); + tH += 2; + tW += 2; + font->draw(checkedImage, (width - tW) / 2, (height - tH) / 2, r, g, b, + true, L"X"); + checkedHighlighted = adjustBrightness(checkedImage, 1.5, false); + + mouseInside = false; +} + + +Checkbox::~Checkbox() +{ + SDL_FreeSurface(image); + SDL_FreeSurface(highlighted); + SDL_FreeSurface(checkedImage); + SDL_FreeSurface(checkedHighlighted); +} + + +void Checkbox::draw() +{ + if (checked) { + if (mouseInside) + screen.draw(left, top, checkedHighlighted); + else + screen.draw(left, top, checkedImage); + } else { + if (mouseInside) + screen.draw(left, top, highlighted); + else + screen.draw(left, top, image); + } + screen.addRegionToUpdate(left, top, width, height); +} + + +void Checkbox::getBounds(int &l, int &t, int &w, int &h) +{ + l = left; + t = top; + w = width; + h = height; +} + + +bool Checkbox::onMouseButtonDown(int button, int x, int y) +{ + if (isInRect(x, y, left, top, width, height)) { + sound->play(L"click.wav"); + checked = ! checked; + draw(); + return true; + } else + return false; +} + + +bool Checkbox::onMouseMove(int x, int y) +{ + bool in = isInRect(x, y, left, top, width, height); + if (in != mouseInside) { + mouseInside = in; + draw(); + } + return false; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// Picture +// +////////////////////////////////////////////////////////////////////////////// + +Picture::Picture(int x, int y, const std::wstring &name, bool transparent) +{ + image = loadImage(name, transparent); + left = x; + top = y; + width = image->w; + height = image->h; + managed = true; +} + +Picture::Picture(int x, int y, SDL_Surface *img) +{ + image = img; + left = x; + top = y; + width = image->w; + height = image->h; + managed = false; +} + +Picture::~Picture() +{ + if (managed) + SDL_FreeSurface(image); +} + +void Picture::draw() +{ + screen.draw(left, top, image); + screen.addRegionToUpdate(left, top, width, height); +} + +void Picture::moveX(const int newX) +{ + left = newX; +} + +void Picture::getBounds(int &l, int &t, int &w, int &h) +{ + l = left; + t = top; + w = width; + h = height; +} + + +////////////////////////////////////////////////////////////////// +// +// Slider +// +////////////////////////////////////////////////////////////////// + +Slider::Slider(int x, int y, int w, int h, float &v): value(v) +{ + left = x; + top = y; + width = w; + height = h; + background = NULL; + createSlider(height); + highlight = false; + dragging = false; +} + +Slider::~Slider() +{ + if (background) + SDL_FreeSurface(background); + if (slider) + SDL_FreeSurface(slider); + if (activeSlider) + SDL_FreeSurface(activeSlider); +} + +void Slider::draw() +{ + if (! background) + createBackground(); + screen.draw(left, top, background); + screen.addRegionToUpdate(left, top, width, height); + int posX = valueToX(value); + SDL_Surface *s = highlight ? activeSlider : slider; + screen.draw(left + posX, top, s); +} + +void Slider::createBackground() +{ + background = screen.createSubimage(left, top, width, height); + int y = height / 2; + SDL_LockSurface(background); + drawBevel(background, 0, y - 2, width, 4, false, 1); + SDL_UnlockSurface(background); +} + +void Slider::createSlider(int size) +{ + SDL_Surface *s = screen.getSurface(); + SDL_Surface *image = SDL_CreateRGBSurface(SDL_SWSURFACE, size, size, + s->format->BitsPerPixel, s->format->Rmask, s->format->Gmask, + s->format->Bmask, s->format->Amask); + + SDL_Surface *tile = loadImage(L"blue.bmp"); + SDL_Rect src = { 0, 0, tile->w, tile->h }; + SDL_Rect dst = { 0, 0, tile->w, tile->h }; + for (int j = 0; j < size; j += tile->h) + for (int i = 0; i < size; i += tile->w) { + dst.x = i; + dst.y = j; + SDL_BlitSurface(tile, &src, image, &dst); + } + SDL_FreeSurface(tile); + + SDL_LockSurface(image); + drawBevel(image, 0, 0, size, size, false, 1); + drawBevel(image, 1, 1, size - 2, size - 2, true, 1); + SDL_UnlockSurface(image); + + activeSlider = adjustBrightness(image, 1.5, false); + slider = SDL_DisplayFormat(image); + + SDL_FreeSurface(image); +} + + +bool Slider::onMouseButtonDown(int button, int x, int y) +{ + bool in = isInRect(x, y, left, top, width, height); + if (in) { + int sliderX = valueToX(value); + bool hl = isInRect(x, y, left + sliderX, top, height, height); + if (hl) { + dragging = true; + dragOffsetX = x - left - sliderX; + } + } + return in; +} + +bool Slider::onMouseButtonUp(int button, int x, int y) +{ + if (dragging) { + dragging = false; + return true; + } else + return false; +} + + +int Slider::valueToX(float value) +{ + if (value < 0) + value = 0.0f; + if (value > 1) + value = 1.0f; + return (int)(((float)(width - height)) * value); +} + + +float Slider::xToValue(int pos) +{ + if (0 > pos) + pos = 0; + if (width - height < pos) + pos = width - height; + return (float)pos / (float)(width - height); +} + + + +bool Slider::onMouseMove(int x, int y) +{ + if (dragging) { + float val = xToValue(x - left - dragOffsetX); + if (val != value) { + value = val; + draw(); + } + return true; + } + + bool in = isInRect(x, y, left, top, width, height); + if (in) { + int sliderX = valueToX(value); + bool hl = isInRect(x, y, left + sliderX, top, height, height); + if (hl != highlight) { + highlight = hl; + draw(); + } + } else + if (highlight) { + highlight = false; + draw(); + } + return in; +} + diff --git a/widgets.h b/widgets.h new file mode 100644 index 0000000..0bd7753 --- /dev/null +++ b/widgets.h @@ -0,0 +1,322 @@ +#ifndef __WIDGETS_H__ +#define __WIDGETS_H__ + +#include +#include +#include +#include +#include "font.h" + + +class Command +{ + public: + virtual ~Command() { }; + virtual void doAction() = 0; +}; + + +class Area; + + +class Widget +{ + protected: + Area *area; + + public: + virtual ~Widget() { }; + + public: + virtual bool onMouseButtonDown(int button, int x, int y) { return false; }; + virtual bool onMouseButtonUp(int button, int x, int y) { return false; }; + virtual bool onMouseMove(int x, int y) { return false; }; + virtual void draw() { }; + virtual void setParent(Area *a) { area = a; }; + virtual bool onKeyDown(SDLKey key, unsigned char ch) { return false; }; + virtual bool destroyByArea() { return true; }; +}; + + +class Button: public Widget +{ + protected: + int left, top, width, height; + SDL_Surface *image, *highlighted; + bool mouseInside; + Command *command; + + public: + Button(int x, int y, const std::wstring &name, Command *cmd=NULL, + bool transparent=true); + Button(int x, int y, int width, int height, Font *font, + int fR, int fG, int fB, int hR, int hG, int hB, + const std::wstring &text, Command *cmd=NULL); + Button(int x, int y, int width, int height, Font *font, + int r, int g, int b, const std::wstring &background, + const std::wstring &text, Command *cmd=NULL); + Button(int x, int y, int width, int height, Font *font, + int r, int g, int b, const std::wstring &background, + const std::wstring &text, bool bevel, Command *cmd=NULL); + virtual ~Button(); + + public: + virtual void draw(); + void getBounds(int &left, int &top, int &width, int &height); + int getLeft() const { return left; }; + int getTop() const { return top; }; + int getWidth() const { return width; }; + int getHeight() const { return height; }; + virtual bool onMouseButtonDown(int button, int x, int y); + virtual bool onMouseMove(int x, int y); + void moveTo(int x, int y) { left = x; top = y; }; +}; + + + +class KeyAccel: public Widget +{ + protected: + SDLKey key; + Command *command; + + public: + KeyAccel(SDLKey key, Command *command); + virtual bool onKeyDown(SDLKey key, unsigned char ch); +}; + + +class TimerHandler +{ + public: + virtual ~TimerHandler() { }; + virtual void onTimer() = 0; +}; + + +class Area: public Widget +{ + private: + typedef std::list WidgetsList; + WidgetsList widgets; + std::set notManagedWidgets; + bool terminate; + Uint32 time; + TimerHandler *timer; + + public: + Area(); + virtual ~Area(); + + public: + void add(Widget *widget, bool manage=true); + void remove(Widget *widget); + void handleEvent(const SDL_Event &event); + void run(); + void finishEventLoop(); + virtual void draw(); + void setTimer(Uint32 interval, TimerHandler *handler); + void updateMouse(); + virtual bool destroyByArea() { return false; }; +}; + + +class ExitCommand: public Command +{ + private: + Area &area; + + public: + ExitCommand(Area &a): area(a) { } + + virtual void doAction() { + area.finishEventLoop(); + }; +}; + + +class AnyKeyAccel: public Widget +{ + protected: + Command *command; + + public: + AnyKeyAccel(); // use exit command by default + AnyKeyAccel(Command *command); + virtual ~AnyKeyAccel(); + + public: + virtual bool onKeyDown(SDLKey key, unsigned char ch); + virtual bool onMouseButtonDown(int button, int x, int y); +}; + + +class Window: public Widget +{ + protected: + int left, top, width, height; + SDL_Surface *background; + + public: + Window(int x, int y, int w, int h, const std::wstring &background, + bool frameWidth=4, bool raised=true); + virtual ~Window(); + + public: + virtual void draw(); +}; + + +class Label: public Widget +{ + public: + typedef enum { + ALIGN_LEFT, + ALIGN_CENTER, + ALIGN_RIGHT + } HorAlign; + + typedef enum { + ALIGN_TOP, + ALIGN_MIDDLE, + ALIGN_BOTTOM + } VerAlign; + + protected: + Font *font; + std::wstring text; + int left, top, width, height; + int red, green, blue; + HorAlign hAlign; + VerAlign vAlign; + bool shadow; + + public: + Label(Font *font, int x, int y, int r, int g, int b, + std::wstring text, bool shadow=true); + Label(Font *font, int x, int y, int width, int height, + HorAlign hAlign, VerAlign vAlign, int r, int g, int b, + const std::wstring &text); + + public: + virtual void draw(); +}; + + +class InputField: public Window, public TimerHandler +{ + private: + std::wstring &text; + int maxLength; + int cursorPos; + int red, green, blue; + Font *font; + Uint32 lastCursor; + bool cursorVisible; + char lastChar; + Uint32 lastKeyUpdate; + + public: + InputField(int x, int y, int w, int h, const std::wstring &background, + std::wstring &name, int maxLength, int r, int g, int b, Font *font); + ~InputField(); + + public: + virtual void draw(); + virtual void setParent(Area *a); + virtual void onTimer(); + virtual bool onKeyDown(SDLKey key, unsigned char ch); + virtual bool onKeyUp(SDLKey key); + virtual void onCharTyped(unsigned char ch); + + private: + void moveCursor(int pos); +}; + + +class Checkbox: public Widget +{ + protected: + int left, top, width, height; + SDL_Surface *image, *highlighted; + SDL_Surface *checkedImage, *checkedHighlighted; + bool &checked; + bool mouseInside; + + public: + Checkbox(int x, int y, int width, int height, Font *font, + int r, int g, int b, const std::wstring &background, + bool &checked); + virtual ~Checkbox(); + + public: + virtual void draw(); + void getBounds(int &left, int &top, int &width, int &height); + int getLeft() const { return left; }; + int getTop() const { return top; }; + int getWidth() const { return width; }; + int getHeight() const { return height; }; + virtual bool onMouseButtonDown(int button, int x, int y); + virtual bool onMouseMove(int x, int y); + void moveTo(int x, int y) { left = x; top = y; }; +}; + +class Picture: public Widget +{ + protected: + int left; + int top; + int width; + int height; + SDL_Surface *image; + bool managed; + + public: + Picture(int x, int y, const std::wstring &name, bool transparent=true); + Picture(int x, int y, SDL_Surface *image); + virtual ~Picture(); + + public: + virtual void draw(); + void moveX(const int newX); + void getBounds(int &l, int &t, int &w, int &h); + int getLeft() const { return left; }; + int getTop() const { return top; }; + int getWidth() const { return width; }; + int getHeight() const { return height; }; + +}; + + +class Slider: public Widget +{ + private: + int left, top, width, height; + float &value; + SDL_Surface *background; + SDL_Surface *slider; + SDL_Surface *activeSlider; + bool highlight; + bool dragging; + int dragOffsetX; + + public: + Slider(int x, int y, int width, int height, float &value); + virtual ~Slider(); + + public: + virtual void draw(); + virtual bool onMouseButtonDown(int button, int x, int y); + virtual bool onMouseButtonUp(int button, int x, int y); + virtual bool onMouseMove(int x, int y); + + private: + void createBackground(); + void createSlider(int size); + int valueToX(float value); + float xToValue(int pos); +}; + + +#endif +