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 ACTIVELETTERS 20
#define MAXLETTERS   256
#define RMTIME       1.0
#define EXTIME       2.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 menuLetter;
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;
int glyphsMask = 0;
int mode;
double exitTm;

void clearBoard() {
  exitTm = 0;
  mode = 0;
  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));
  memset(&menuLetter, 0, sizeof(menuLetter));
  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 > menuLetter.glyphsCount) menuLetter = *letter;
    if (letter->glyphsCount > 0) ++letter;
  }
  lettersCount = letter - letters;
  cellWidth = ww;
  cellHeight = hh;
  assert(menuLetter.glyphsCount == 4);

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


void shuffleLetters(int glyps) {
  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);

    int gc = 0;
    Animation 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 = randomNumber(j, gc - 1);
      if (k != j) { Animation 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 = 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(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 = randomNumber(i, COUNT-1);
    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 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;
  }
}


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

  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();
  }


  if (mode == 0) {
    double kw = w/(cellWidth*6);
    double kh = h/(cellHeight*3);
    double k = kw < kh ? kw: kh;
    if (k > 1) k = 1;
    zoom(k);
    noStroke();
    translate(-cellWidth*2, -0.5*cellHeight);
    assert(letters[0].glyphsCount >= 4);

    int mc = (int)floor(mouseTransformedX()/cellWidth);
    int mr = (int)floor(mouseTransformedY()/cellHeight);
    if (!mr && mc >= 0 && mc < 4 && mouseWentDown("left")) glyphsMask ^= 1 << mc;

    for(int i = 0; i < 4; ++i) {
      double s = !mr && mc == i ? 1 : 0.9;
      double d = (1 - s)/2;
      double a = glyphsMask & (1 << i) ? 1.0 : 0.5;
      fill(colorByRGBA(1, 1, 1, a));
      rectTextured(menuLetter.glyphs[i], (i + d)*cellWidth, d*cellHeight, cellWidth*s, cellHeight*s);
    }

    if (glyphsMask) {
      noFill();
      stroke(colorByRGBA(0.0, 0.0, 0.0, 0.75));
      textAlign(HALIGN_CENTER, VALIGN_CENTER);
      textSize(cellHeight*0.5);
      text(2*cellWidth, 1.5*cellHeight, "press enter");
      if ( keyWentDown("any enter")
        || (mr == 1 && mc >= 0 && mc < 4 && mouseWentDown("left")) )
          generateBoard(glyphsMask, ACTIVELETTERS);
    }

    if (keyWentDown("escape")) windowStop();
  } else {
    double kw = w/(cellWidth*(COLS + 4));
    double kh = h/(cellHeight*(ROWS + 2));
    double k = kw < kh ? kw: kh;
    zoom(k);

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

    saveState();
    noFill();
    stroke(colorByRGBA(0.0, 0.0, 0.0, 0.75));
    textSize(cellWidth*0.5);
    textAlign(HALIGN_RIGHT, VALIGN_BOTTOM);
    text(-0.2*cellWidth, -0.2*cellHeight, "back");
    textAlign(HALIGN_LEFT, VALIGN_TOP);
    text((COLS + 0.2)*cellWidth, (ROWS + 0.2)*cellHeight, "hint");
    restoreState();

    double mx = mouseTransformedX();
    double my = mouseTransformedY();
    Pos hover = {};
    hover.r = (int)floor(my/cellHeight);
    hover.c = (int)floor(mx/cellWidth);

    if (hover.r < 0 && hover.c < 0 && mouseDown("left"))
      exitTm += dt/EXTIME; else exitTm = 0;
    if (keyWentDown("escape") || exitTm >= 1)
      clearBoard();

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

      if (hover.r == selected.r && hover.c == selected.c)
        selected.r = selected.c = -1;
      else
      if ( !(hover.r < 0 && hover.c < 0) // ignore hint or exit pressing
        && !(hover.r >= ROWS && hover.c >= COLS) )
          selected = hover;
    }

    Letter *hl = mouseDown("right") ? get(hover) : NULL;
    if (keyWentDown("h") || (!hl && mouseWentDown("right")))
      hint();
    if (hover.r >= ROWS && hover.c >= COLS) {
      hl = mouseDown("left") ? get(selected) : NULL;
      if (!hl && mouseWentDown("left")) 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 && hl && hl == cell->l               ) fill(colorByRGBA(0.9, 0.9, 0.9, 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);

    // draw exiting
    noFill();
    stroke(colorByRGBA(1, 0, 0, exitTm));
    double r = cellHeight/2;
    arc(mx - r, my - r, 2*r, 2*r, 0, 360*exitTm);
  }

  restoreState();
}


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