#include <img.h>
#include <snd.h>
#include <ctype.h>
#include <dirent.h>
#define COLS 16
#define ROWS 9
#define ACTIVELETTERS 20
#define MAXLETTERS 256
#define MAXSOUNDS 16
#define RMTIME 1.0
#define EXTIME 2.0
#define SNDRATE 44100
#define COUNT (ROWS*COLS)
typedef struct {
Image src;
XImage *x;
} ImageEx;
typedef struct {
ImageEx glyphs[4];
int glyphsCount;
Sound sounds[MAXSOUNDS];
int soundsCount;
ImageEx *g[4];
} Letter;
typedef struct {
int r, c;
} Pos;
typedef struct {
int len;
Pos p[4];
} Track;
typedef struct {
Letter *l;
ImageEx *g;
} Cell;
Letter menuLetter;
Letter letters[MAXLETTERS];
int lettersCount;
int cellWidth = 1;
int cellHeight = 1;
Cell board[ROWS][COLS];
Pos hover = { -1, -1 };
Pos selected = { -1, -1 };
Track tracks[COUNT];
int tracksCount;
int glyphsMask = 0;
int mode;
int showhint;
int back;
int ox, oy, cw, ch;
ImageEx bgtex;
ImageEx starttex;
ImageEx hinttex;
ImageEx backtex[3];
void imgExFree(ImageEx *img) {
if (img->x) XDestroyImage(img->x);
imgFree(&img->src);
*img = (ImageEx){};
}
void imgExDraw(ImageEx *img, int x, int y, int w, int h) {
if (!imgValid(img->src) || w <= 0 || h <= 0) return;
if (img->x && (img->x->width != w || img->x->height != h))
{ XDestroyImage(img->x); img->x = NULL; }
if (!img->x) {
Image tmp = imgResample(img->src, w, h);
imgDivA(tmp);
img->x = imgToX(&tmp);
}
if (img->x)
XPutImage(dpy, drw, gc, img->x, 0, 0, x, y, img->x->width, img->x->height);
}
int imgExLoadTga(ImageEx *img, const char *filename) {
imgExFree(img);
img->src = imgLoadTga(filename);
imgMultA(img->src);
return imgValid(img->src);
}
void clearBoard() {
mode = 0;
back = 0;
memset(tracks, 0, sizeof(tracks));
memset(board, 0, sizeof(board));
tracksCount = 0;
selected = (Pos){ -1, -1 };
}
void clearSounds() {
for(int i = 0; i < lettersCount; ++i)
for(int j = 0; j < letters[i].soundsCount; ++i)
sndFree(&letters[i].sounds[j]);
}
void clearLetters() {
clearBoard();
clearSounds();
for(int i = 0; i < lettersCount; ++i)
for(int j = 0; j < letters[i].glyphsCount; ++j)
imgExFree(&letters[i].glyphs[j]);
memset(letters, 0, sizeof(letters));
memset(&menuLetter, 0, sizeof(menuLetter));
lettersCount = 0;
cellWidth = cellHeight = 1;
}
int loadLetters(const char *filename, int rows, int cols) {
clearLetters();
Image img = imgLoadTga(filename);
if (!imgValid(img))
return 0;
int ww = img.w/cols/2;
int hh = img.h/rows/2;
if (!ww || !hh)
return LOGERR("loadLetters: image too small (%dx%d): %s", img.w, img.h, filename), imgFree(&img), 0;
imgMultA(img);
Letter *letter = letters;
for(int r = 0; r < rows; ++r)
for(int c = 0; c < cols; ++c)
{
for(int rr = 0; rr < 2; ++rr)
for(int cc = 0; cc < 2; ++cc) {
Image sub = imgSub(img, (c*2 + cc)*ww, (r*2 + rr)*hh, ww, hh);
if (!imgHasPicture(sub))
{ imgFree(&sub); continue; }
letter->glyphs[ letter->glyphsCount++ ].src = sub;
}
if (letter->glyphsCount > menuLetter.glyphsCount) menuLetter = *letter;
if (letter->glyphsCount > 0) ++letter;
}
lettersCount = letter - letters;
cellWidth = ww;
cellHeight = hh;
assert(menuLetter.glyphsCount == 4);
imgFree(&img);
return 1;
}
void loadSounds(const char *path) {
DIR *dir = opendir(path);
if (!dir) return;
size_t pl = strlen(path);
char buf[1024];
while(1) {
struct dirent *e = readdir(dir);
if (!e) break;
size_t nl = strlen(e->d_name);
if (nl < 4 || pl + nl + 1 >= sizeof(buf)) continue;
const char *ex = e->d_name + nl - 4;
if ( ex[0] != '.'
|| tolower(ex[1]) != 'r'
|| tolower(ex[2]) != 'a'
|| tolower(ex[3]) != 'w' ) continue;
int index = atoi(e->d_name);
if (index <= 0 || index > lettersCount) continue;
Letter *l = &letters[index-1];
if (l->soundsCount >= MAXSOUNDS) continue;
sprintf(buf, "%s/%s", path, e->d_name);
l->sounds[l->soundsCount] = sndLoadRaw(buf, SNDRATE);
//l->sounds[l->soundsCount] = sndGen(SNDRATE, 100, SNDRATE);
if (sndValid(l->sounds[l->soundsCount])) ++l->soundsCount;
}
closedir(dir);
}
void shuffleLetters(int glyps) {
for(int i = 0; i < lettersCount; ++i) {
int j = rand()%(lettersCount - i) + i;
if (j != i) { Letter l = letters[i]; letters[i] = letters[j]; letters[j] = l; }
Letter *l = &letters[i];
assert(l->glyphsCount > 0);
int gc = 0;
ImageEx *g[4] = {};
for(int j = 0; j < l->glyphsCount; ++j)
if (glyps & (1 << j)) g[gc++] = &l->glyphs[j];
if (!gc) g[gc++] = &l->glyphs[0];
for(int j = 0; j < gc; ++j) {
int k = rand()%(gc - j) + j;
if (k != j) { ImageEx *a = g[j]; g[j] = g[k]; g[k] = a; }
}
for(int j = 0; j < 4; ++j)
l->g[j] = g[j%gc];
for(int j = 0; j < 4; ++j) {
int k = rand()%(4 - j) + j;
if (k != j) { ImageEx *g = l->g[j]; l->g[j] = l->g[k]; l->g[k] = g; }
assert(l->g[j]);
}
}
}
void simpleBoard() {
clearBoard();
for(int i = 0; i < COUNT; ++i) {
Letter *l = &letters[i%lettersCount];
board[i/COLS][i%COLS].l = l;
board[i/COLS][i%COLS].g = &l->glyphs[1];
}
mode = 1;
}
void generateBoard(int glyphsMask, int maxLetters) {
if (maxLetters < 1) maxLetters = 1;
clearBoard();
shuffleLetters(glyphsMask);
int indices[COUNT];
for(int i = 0; i < COUNT; ++i) indices[i] = i;
int lc = lettersCount < maxLetters ? lettersCount : maxLetters;
for(int i = 0; i < COUNT; ++i) {
int j = rand()%(COUNT - i) + i;
int id = indices[j]; indices[j] = indices[i]; indices[i] = id;
Letter *l = &letters[ (id/4)%lc ];
board[i/COLS][i%COLS].l = l;
board[i/COLS][i%COLS].g = l->g[id%4];
}
mode = 1;
}
Letter* get(Pos p)
{ return p.r >= 0 && p.r < ROWS && p.c >= 0 && p.c < COLS ? board[p.r][p.c].l : NULL; }
int findSubTrack1(Track *t, Pos b) {
Pos p = t->p[t->len - 1];
//printf(" -- -- ft1: %d %d -> %d %d\n", p.r, p.c, b.r, b.c);
if (p.c == b.c && p.r < b.r) { for(++p.r; p.r != b.r; ++p.r) if (get(p)) return 0; } else
if (p.c == b.c && p.r > b.r) { for(--p.r; p.r != b.r; --p.r) if (get(p)) return 0; } else
if (p.r == b.r && p.c < b.c) { for(++p.c; p.c != b.c; ++p.c) if (get(p)) return 0; } else
if (p.r == b.r && p.c > b.c) { for(--p.c; p.c != b.c; --p.c) if (get(p)) return 0; } else return 0;
t->p[t->len++] = p;
return 1;
}
int findSubTrack2(Track *t, Pos b, int horz) {
Pos *p = &t->p[t->len];
*p = t->p[t->len - 1];
//printf(" -- ft2: %d %d -> %d %d %s\n", p->r, p->c, b.r, b.c, horz ? "horz" : "vert");
if (p->c == b.c || p->r == b.r) return findSubTrack1(t, b);
++t->len;
if (horz) {
if (p->c < b.c) { for(++p->c; p->c != b.c; ++p->c) if (get(*p)) break; }
else { for(--p->c; p->c != b.c; --p->c) if (get(*p)) break; }
} else {
if (p->r < b.r) { for(++p->r; p->r != b.r; ++p->r) if (get(*p)) break; }
else { for(--p->r; p->r != b.r; --p->r) if (get(*p)) break; }
}
if (!get(*p) && findSubTrack1(t, b)) return 1;
--t->len;
return 0;
}
int findSubTrack3(Track *t, Pos b) {
Pos p0 = t->p[t->len - 1];
Pos *p = &t->p[t->len];
if (p->c == b.c && p->r == b.r) return 0;
//printf("ft3: %d %d -> %d %d\n", p0.r, p0.c, b.r, b.c);
if (findSubTrack2(t, b, 0)) return 1;
++t->len;
*p = p0;
for(++p->c; p->c <= COLS; ++p->c)
if (get(*p)) break; else
if (findSubTrack2(t, b, 0)) return 1;
*p = p0;
for(--p->c; p->c >= -1; --p->c)
if (get(*p)) break; else
if (findSubTrack2(t, b, 0)) return 1;
*p = p0;
for(++p->r; p->r <= ROWS; ++p->r)
if (get(*p)) break; else
if (findSubTrack2(t, b, 1)) return 1;
*p = p0;
for(--p->r; p->r >= -1; --p->r)
if (get(*p)) break; else
if (findSubTrack2(t, b, 1)) return 1;
--t->len;
return 0;
}
Track findTrack(Pos a, Pos b) {
Track t = {};
t.p[t.len++] = a;
Letter *la = get(a);
Letter *lb = get(b);
return la && la == lb && (a.c != b.c || a.r != b.r) && findSubTrack3(&t, b) ? t : (Track){};
}
void hint() {
if (tracksCount) return;
for(int i = 0; i < COUNT; ++i) {
Pos a = { i/COLS, i%COLS };
for(int j = 0; j < COUNT; ++j) {
Pos b = { j/COLS, j%COLS };
Track t = findTrack(a, b);
if (t.len)
tracks[tracksCount++] = t;
}
}
LOGDBG("hint: tracks count: %d", tracksCount);
if (!tracksCount) return; // beep
int i = rand()%tracksCount;
if (i) tracks[0] = tracks[i];
tracksCount = 1;
}
int init() {
sndInit();
srand(time(NULL));
imgExLoadTga(&bgtex, "data/minas.tga");
imgExLoadTga(&starttex, "data/start.tga");
imgExLoadTga(&hinttex, "data/hint.tga");
imgExLoadTga(&backtex[0], "data/back.tga");
imgExLoadTga(&backtex[1], "data/back1.tga");
imgExLoadTga(&backtex[2], "data/back2.tga");
if (!loadLetters("data/letters.tga", 5, 8))
return LOGERR("init: cannot load letters"), 0;
loadSounds("data/sounds");
//simpleBoard();
return 1;
}
void deinit() {
clearLetters();
imgExFree(&bgtex);
imgExFree(&starttex);
imgExFree(&hinttex);
imgExFree(&backtex[0]);
imgExFree(&backtex[1]);
imgExFree(&backtex[2]);
sndDeinit();
}
void resize() { }
void mouseDown(int x, int y) {
int mc = (x - ox)/cw;
int mr = (y - oy)/ch;
if (x < ox) --mc;
if (y < oy) --mr;
if (mode == 0) {
if (!mr && mc >= 0 && mc < 4) {
glyphsMask ^= 1 << mc;
} else
if (mr == 1 && mc >= 1 && mc <= 2) {
generateBoard(glyphsMask, ACTIVELETTERS);
}
} else {
if (mc < 0 && mr < 0) {
if (++back >= 3) clearBoard();
return;
} else back = 0;
if (mc >= COLS && mr >= ROWS) {
if (get(selected)) showhint = 1;
else hint();
} else
if (mc >= 0 && mr >= 0 && mc < COLS && mr < COLS) {
hover.r = mr;
hover.c = mc;
Letter *l = get(hover);
if (l && l->soundsCount > 0)
sndPlay(l->sounds[rand() % l->soundsCount]);
if (!l || (mr == selected.r && mc == selected.c)) {
selected.r = selected.c = hover.r = hover.c = -1;
} else
if (l == get(selected)) {
Track t = findTrack(selected, hover);
if (t.len) {
Cell *ca = &board[selected.r][selected.c];
Cell *cb = &board[hover.r][hover.c];
ca->l = cb->l = NULL;
tracks[tracksCount++] = t;
} else {
selected = hover;
}
} else {
selected = hover;
}
} else {
selected.r = selected.c = hover.r = hover.c = -1;
}
}
}
void mouseUp() {
if (mode) {
showhint = 0;
tracksCount = 0;
for(int r = 0; r < ROWS; ++r)
for(int c = 0; c < COLS; ++c)
if (!board[r][c].l) board[r][c].g = NULL;
}
}
void draw() {
if (imgValid(bgtex.src)) {
if (winW*bgtex.src.h > winH*bgtex.src.w) {
int h = bgtex.src.h*winW/bgtex.src.w;
imgExDraw(&bgtex, 0, (winH - h)/2, winW, h);
} else {
int w = bgtex.src.w*winH/bgtex.src.h;
imgExDraw(&bgtex, (winW - w)/2, 0, w, winH);
}
}
if (mode == 0) {
if (winW*cellHeight*3 < winH*cellWidth*6) {
cw = winW/6;
ch = cellHeight*cw/cellWidth;
} else {
ch = winH/3;
cw = cellWidth*ch/cellHeight;
}
ox = (winW - cw*4)/2;
oy = (winH - ch)/2;
int lw = cw/6;
if (lw < 1) lw = 1;
XSetForeground(dpy, gc, 0x0000ff);
XSetLineAttributes(dpy, gc, lw, LineSolid, CapRound, JoinRound);
for(int i = 0; i < 4; ++i)
imgExDraw(&menuLetter.glyphs[i], ox + i*cw, oy, cw, ch);
if (glyphsMask)
imgExDraw(&starttex, ox + cw, oy + ch, 2*cw, ch);
for(int i = 0; i < 4; ++i)
if (glyphsMask & (1 << i))
XDrawRectangle(dpy, drw, gc, ox + i*cw, oy, cw, ch);
} else {
if (winW*cellHeight*(ROWS + 2) < winH*cellWidth*(COLS + 4)) {
cw = winW/(COLS + 4);
ch = cellHeight*cw/cellWidth;
} else {
ch = winH/(ROWS + 2);
cw = cellWidth*ch/cellHeight;
}
ox = (winW - cw*COLS)/2;
oy = (winH - ch*ROWS)/2;
int lw = cw/6;
if (lw < 1) lw = 1;
XSetForeground(dpy, gc, 0x0000ff);
XSetLineAttributes(dpy, gc, lw, LineSolid, CapRound, JoinRound);
// draw buttons
imgExDraw(&backtex[back], ox - 2*cw, oy - ch, 2*cw, ch);
imgExDraw(&hinttex, ox + COLS*cw, oy + ROWS*ch, 2*cw, ch);
// draw cells
Letter *hl = showhint ? get(selected) : NULL;
for(int r = 0; r < ROWS; ++r)
for(int c = 0; c < COLS; ++c) {
Cell *cell = &board[r][c];
if (!cell->g) continue;
imgExDraw(cell->g, ox + c*cw, oy + r*ch, cw, ch);
}
// draw selection
for(int r = 0; r < ROWS; ++r)
for(int c = 0; c < COLS; ++c) {
Cell *cell = &board[r][c];
if (!cell->g) continue;
if ( (r == hover.r && c == hover.c)
|| (r == selected.r && c == selected.c)
|| (hl && hl == cell->l) )
XDrawRectangle(dpy, drw, gc, ox + c*cw, oy + r*ch, cw, ch);
}
// draw tracks
XSetForeground(dpy, gc, 0xff0000);
for(int i = 0; i < tracksCount; ++i) {
Track *t = &tracks[i];
XPoint p[4];
for(int j = 0; j < t->len; ++j) {
p[j].x = ox + t->p[j].c*cw + cw/2;
p[j].y = oy + t->p[j].r*ch + ch/2;
}
XDrawLines(dpy, drw, gc, p, t->len, CoordModeOrigin);
}
}
}