Blob Blame History Raw

#include <math.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include "helianthus.h"

#define COLS        16
#define ROWS         9
#define MAXLETTERS 256
#define RMTIME       1.0

#define COUNT (ROWS*COLS)


typedef struct {
  Animation glyphs[4];
  int glyphsCount;
  Animation g[4];
} Letter;


typedef struct {
  int r, c;
} Pos;

typedef struct {
  int len;
  Pos p[4];
  double tm;
} Track;

typedef struct {
  Letter *l;
  Animation g;
  double tm;
} Cell;


Letter letters[MAXLETTERS];
int lettersCount;
int cellWidth = 1;
int cellHeight = 1;

Cell board[ROWS][COLS];
Pos selected = { -1, -1 };
Track tracks[COUNT];
int tracksCount;
Animation bgtex;

void clearBoard() {
  memset(tracks, 0, sizeof(tracks));
  memset(board, 0, sizeof(board));
  tracksCount = 0;
  selected = (Pos){ -1, -1 };
}

void clearLetters() {
  clearBoard();
  for(int i = 0; i < lettersCount; ++i)
    for(int j = 0; j < letters[i].glyphsCount; ++j)
      animationDestroy(letters[i].glyphs[j]);
  memset(letters, 0, sizeof(letters));
  lettersCount = 0;
  cellWidth = cellHeight = 1;
}

int loadLetters(const char *filename, int rows, int cols) {
  clearLetters();

  int w, h;
  unsigned char *pixels;
  if (!imageLoad(filename, &w, &h, &pixels))
    return FALSE;
  int stride = w*4;

  int ww = w/cols/2;
  int hh = h/rows/2;
  if (!ww || !hh)
    { free(pixels); return FALSE; }

  int bufstride = ww*4;
  int bufsize = hh*bufstride;
  unsigned char *buf = calloc(bufsize, 1);

  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) {
      unsigned char *p = pixels + (r*2 + rr)*hh*stride + (c*2 + cc)*bufstride;
      for(int rrr = 0; rrr < hh; ++rrr) memcpy(buf + rrr*bufstride, p + rrr*stride, bufstride);
      unsigned int visible = 0;
      for(unsigned char *b = buf + 3, *e = b + bufsize; b < e; b += 4) visible += *b;
      if (!visible) continue;
      letter->glyphs[ letter->glyphsCount++ ] = createAnimationFromImage(ww, hh, buf, TRUE);
    }
    if (letter->glyphsCount > 0) ++letter;
  }
  lettersCount = letter - letters;
  cellWidth = ww;
  cellHeight = hh;

  free(buf);
  free(pixels);
  return TRUE;
}


void shuffleLetters() {
  for(int i = 0; i < lettersCount; ++i) {
    int j = randomNumber(i, lettersCount - 1);
    if (j != i) { Letter l = letters[i]; letters[i] = letters[j]; letters[j] = l; }
    Letter *l = &letters[i];
    assert(l->glyphsCount > 0);

    for(int j = 1; j < l->glyphsCount; ++j) {
      int k = randomNumber(j, l->glyphsCount - 1);
      if (k != j) { Animation g = l->glyphs[j]; l->glyphs[j] = l->glyphs[k]; l->glyphs[k] = g; }
    }

    if (l->glyphsCount <= 1) {
      for(int j = 0; j < 4; ++j)
        l->g[j] = l->glyphs[0];
    } else {
      l->g[0] = l->glyphs[0];
      for(int j = 1; j < 4; ++j)
        l->g[j] = l->glyphs[(j-1)%(l->glyphsCount-1) + 1];
    }

    for(int j = 0; j < 4; ++j) {
      int k = randomNumber(j, 3);
      if (k != j) { Animation g = l->g[j]; l->g[j] = l->g[k]; l->g[k] = g; }
      assert(l->g[j]);
    }
  }
}


void generateBoard() {
  clearBoard();
  shuffleLetters();
  int indices[COUNT];
  for(int i = 0; i < COUNT; ++i) indices[i] = i;
  for(int i = 0; i < COUNT; ++i) {
    int j = randomNumber(i, COUNT-1);
    int id = indices[j]; indices[j] = indices[i]; indices[i] = id;
    Letter *l = &letters[ (id/4)%lettersCount ];
    board[i/COLS][i%COLS].l = l;
    board[i/COLS][i%COLS].g = l->g[id%4];
  }
}


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 FALSE; } else
  if (p.c == b.c && p.r > b.r) { for(--p.r; p.r != b.r; --p.r) if (get(p)) return FALSE; } else
  if (p.r == b.r && p.c < b.c) { for(++p.c; p.c != b.c; ++p.c) if (get(p)) return FALSE; } else
  if (p.r == b.r && p.c > b.c) { for(--p.c; p.c != b.c; --p.c) if (get(p)) return FALSE; } else return FALSE;
  t->p[t->len++] = p;
  return TRUE;
}


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 TRUE;
  --t->len;

  return FALSE;
}


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 FALSE;

  //printf("ft3: %d %d -> %d %d\n", p0.r, p0.c, b.r, b.c);
  if (findSubTrack2(t, b, FALSE)) return TRUE;

  ++t->len;

  *p = p0;
  for(++p->c; p->c <= COLS; ++p->c)
    if (get(*p)) break; else
      if (findSubTrack2(t, b, FALSE)) return TRUE;

  *p = p0;
  for(--p->c; p->c >= -1; --p->c)
    if (get(*p)) break; else
      if (findSubTrack2(t, b, FALSE)) return TRUE;

  *p = p0;
  for(++p->r; p->r <= ROWS; ++p->r)
    if (get(*p)) break; else
      if (findSubTrack2(t, b, TRUE)) return TRUE;

  *p = p0;
  for(--p->r; p->r >= -1; --p->r)
    if (get(*p)) break; else
      if (findSubTrack2(t, b, TRUE)) return TRUE;

  --t->len;
  return FALSE;
}


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) {
        t.tm = 1;
        tracks[tracksCount++] = t;
      }
    }
  }

  if (!tracksCount) return; // beep

  int i = randomNumber(0, tracksCount - 1);
  if (i) tracks[0] = tracks[i];
  tracksCount = 1;
}


void init() {
  bgtex = createAnimationEx("data/shisen/minas.png", TRUE, TRUE, TRUE);
  if (!loadLetters("data/shisen/letters-gen.png", 5, 8)) {
    printf("cannot load letters\n");
    windowStop();
    return;
  }
  generateBoard();
}


void draw() {
  double w = windowGetWidth();
  double h = windowGetHeight();
  double dt = windowGetFrameTime();

  double kw = w/(cellWidth*(COLS + 2));
  double kh = h/(cellHeight*(ROWS + 2));
  double k = kw < kh ? kw: kh;

  saveState();
  translate(w*0.5, h*0.5);

  {
    saveState();
    double aw = animationGetFrameOrigWidth(bgtex, 0);
    double ah = animationGetFrameOrigHeight(bgtex, 0);
    double kw = w/aw;
    double kh = h/ah;
    double k = kw > kh ? kw: kh;
    aw *= k;
    ah *= k;

    noStroke();
    rectTextured(bgtex, -0.5*aw, -0.5*ah, aw, ah);
    fill(colorByRGBA(1.0, 1.0, 1.0, 0.5));
    rect(-0.5*aw, -0.5*ah, aw, ah);
    restoreState();
  }


  zoom(k);
  translate(-0.5*COLS*cellWidth, -0.5*ROWS*cellHeight);
  strokeWidth(0.1*cellWidth);

  double mx = mouseTransformedX();
  double my = mouseTransformedY();
  Pos hover = {};
  hover.c = (int)floor(mx/cellWidth);
  hover.r = (int)floor(my/cellHeight);
  if (mouseWentDown("left")) {
    Track t = findTrack(selected, hover);
    if (t.len) {
      t.tm = 1;
      Cell *ca = &board[selected.r][selected.c];
      Cell *cb = &board[hover.r][hover.c];
      ca->l  = cb->l  = NULL;
      ca->tm = cb->tm = t.tm;
      tracks[tracksCount++] = t;
    }
    selected = hover;
  }

  if (keyWentDown("h")) hint();

  // draw letters
  for(int r = 0; r < ROWS; ++r)
  for(int c = 0; c < COLS; ++c) {
    Cell *cell = &board[r][c];
    if (!cell->g) continue;

    double a = 1;
    if (cell->tm) {
      cell->tm -= dt/RMTIME;
      if (cell->tm <= 0) {
        cell->g = NULL;
        cell->tm = 0;
        continue;
      }
      a = cell->tm;
    }

    saveState();
    noStroke();
    fill(colorByRGBA(1.0, 1.0, 1.0, a));
    if (a == 1 && c ==    hover.c && r ==    hover.r) fill(colorByRGBA(0.9, 0.9, 0.9, a));
    if (a == 1 && c == selected.c && r == selected.r) fill(colorByRGBA(0.8, 0.8, 0.8, a));
    rectTextured(cell->g, c*cellWidth, r*cellHeight, cellWidth, cellHeight);
    restoreState();
  }

  // draw tracks
  int j = 0;
  for(int i = 0; i < tracksCount; ++i) {
    Track *t = &tracks[i];
    t->tm -= dt/RMTIME;
    if (t->tm <= 0) { j = i + 1; continue; }
    saveState();
    stroke(colorByRGBA(1, 0, 0, t->tm));
    for(int k = 0; k < t->len; ++k)
      lineTo((t->p[k].c + 0.5)*cellWidth, (t->p[k].r + 0.5)*cellHeight);
    strokePath();
    restoreState();
  }
  tracksCount -= j;
  memmove(tracks, tracks + j, sizeof(*tracks)*tracksCount);

  restoreState();
}


int main() {
  windowSetResizable(TRUE);
  windowSetVariableFrameRate();
  windowSetInit(&init);
  windowSetDraw(&draw);
  windowRun();
}