/*
Copyright (C) 2015-2018 Night Dive Studios, LLC.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
* $Source: r:/prj/cit/src/RCS/mfdpanel.c $
* $Revision: 1.75 $
* $Author: dc $
* $Date: 1994/11/14 03:30:50 $
*/
#include
#include
#include
#include "mfdpanel.h"
#include "mfdint.h"
#include "mfdext.h"
#include "mfddims.h"
#include "mfdgadg.h"
#include "mfdart.h"
#include "tools.h"
#include "cybstrng.h"
#include "gamescr.h"
#include "gamestrn.h"
#include "objbit.h"
#include "citres.h"
#include "objsim.h"
#include "objgame.h"
#include "colors.h"
#include "fullscrn.h"
#include "otrip.h"
#include "gr2ss.h"
#define WORKING_INC_MFDS
#define DRAW_GRID_PLUSSES
// ----------
// PROTOTYPES
// ----------
extern errtype accesspanel_trigger(ObjID id);
errtype simple_load_res_bitmap_cursor(LGCursor *c, grs_bitmap *bmp, Ref rid);
errtype load_res_bitmap(grs_bitmap *bmp, Ref rid, uchar alloc);
int wirepos_score(wirePosPuzzle *wppz);
void wirepos_setup_buttons(wirePosPuzzle *wppz);
uchar wirepos_3int_init(wirePosPuzzle *wppz, int a1, int a2, int a3);
void wirepos_3int_update(wirePosPuzzle *wppz);
int wirepos_iswire(wirePosPuzzle *wppz, int wim_code);
int wirepos_rescore_n_check(wirePosPuzzle *wppz);
uchar wirepos_moveto(wirePosPuzzle *wppz, int wim_code);
uchar mfd_accesspanel_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data);
int access_help_string(wirePosPuzzle *wppz);
void mfd_setup_wirepanel(uchar special, ObjID id);
uchar mfd_solve_wirepanel(void);
void mfd_setup_gridpanel(ObjID id);
uchar mfd_solve_gridpanel(void);
void mfd_gridpanel_set_winmove(uchar check);
gpz_state gridpanel_move(LGPoint node, gridFlowPuzzle *gfpz);
void gpz_set_grid_state(gridFlowPuzzle *gfpz, short row, short col, gpz_state val);
void gpz_uncharge_grid(gridFlowPuzzle *gfpz);
gpz_state gpz_charge_state(gpz_state s);
gpz_state gpz_uncharge_state(gpz_state s);
uchar gpz_is_charged(gpz_state s);
void gpz_toggle_state(gridFlowPuzzle *gfpz, short r, short c);
gpz_state bitarray_get(short siz, short off, uint *base);
void bitarray_set(short siz, short off, ushort val, uint *base);
int gpz_search_depth(gridFlowPuzzle *gfpz);
void gpz_add_gate(gridFlowPuzzle *gfpz, ObjID me);
void gpz_perimeter_to_grid(gridFlowPuzzle *gfpz, ushort per, short *r, short *c);
uchar gpz_state_charged(gridFlowPuzzle *gfpz, short r, short c);
uchar gpz_doneness(gridFlowPuzzle *gfpz);
uchar gpz_propogate_charge_n_check(gridFlowPuzzle *gfpz);
void gpz_setup_buttons(gridFlowPuzzle *gfpz);
uchar find_winning_move_from(gridFlowPuzzle *gfpz, gridFlowPuzzle *solved, LGPoint *move, int r0, int c0, int dep,
ulong timeout);
uchar find_winning_move(gridFlowPuzzle *gfpz, gridFlowPuzzle *solved, int depth, LGPoint *move, uchar breadth_first,
ulong timeout);
void wacky_int_line(short x1, short y1, short x2, short y2);
char *grid_help_string(gridFlowPuzzle *gfpz, char *buf, int siz);
void id_clut_init(uchar *clut);
// not so secret, is it?
// the fact that we claim to know the size of this is criminal.
extern uchar hideous_secret_game_storage[2052];
#define HSGS hideous_secret_game_storage
#define SHADOW_PANEL_SIG (*((ulong *)HSGS))
#define OUR_SIGNATURE (((ulong)'G' << 24) | ((ulong)'r' << 16) | ((ulong)'i' << 8) | ((ulong)'d'))
#define SIGN_US() (SHADOW_PANEL_SIG = OUR_SIGNATURE)
#define WE_ARE_SIGNED() ((SHADOW_PANEL_SIG) == OUR_SIGNATURE)
#define SHADOW_PANEL_ID (*((ObjID *)((&SHADOW_PANEL_SIG) + 1)))
#define SHADOW_PANEL_LEV (*((short *)((&SHADOW_PANEL_ID) + 1)))
#define SHADOW_PANEL_STOR ((uchar *)((&SHADOW_PANEL_LEV) + 1))
#define MFD_GRID_CLUT ((uchar *)((SHADOW_PANEL_STOR) + sizeof(player_struct.mfd_access_puzzles)))
#define grid_help_clut MFD_GRID_CLUT
#define MFDPANEL_MEMORY_BAG (MFD_GRID_CLUT + 256)
#define MFDPANEL_MEMORY_BAG_SIZE (sizeof(HSGS) - (MFDPANEL_MEMORY_BAG - HSGS))
int gpz_base_colors[] = {BLUE_BASE + 10, GREEN_BASE + 7, 0x24, GREEN_YELLOW_BASE + 9, GRAY_BASE + 5};
int gpz_dk_colors[] = {0x7E, 0x63, 0x27, 0x56, GRAY_BASE + 13};
LGCursor gridCursor;
grs_bitmap gridCursorbm;
#ifdef SVGA_SUPPORT
uchar
gridCursorBits[1016]; // This should be enough, maybe... note wacky computation scaling 9x9 to apropos for 1024x768
#else
uchar gridCursorBits[81]; // This should be enough
#endif
//#define score_spew(str,v) mprintf(str,v)
//#define score_spew2(str,v,o) mprintf(str,v,o)
#define score_spew(str, v)
#define score_spew2(str, v, o)
#define WIREPUZ_CODE 0
#define GRIDPUZ_CODE 1
int mfd_slot_primary(int slot) {
int mid;
for (mid = 0; mid < NUM_MFDS; mid++) {
if (player_struct.mfd_current_slots[mid] == slot)
return mid;
}
return -1;
}
#define MFD_MARGIN_WID 3
#define SWITCH_COLOR 2
// draws text and/or bitmap centered in current canvas,
// assumed to be an mfd.
static void draw_help_text(char *str, uchar wire, void *puzzle) {
short sw, sh, bw = 0, bh = 0, x, y;
uchar save_w = mfd_string_wrap, bmap = FALSE;
grs_bitmap foot;
uchar bcolor;
gr_set_font(ResLock(MFD_FONT));
gr_string_wrap(str, MFD_VIEW_WID - 1 - (2 * MFD_MARGIN_WID));
gr_string_size(str, &sw, &sh);
// special annointed string gets illustrative bitmap.
if (!wire && !(((gridFlowPuzzle *)puzzle)->gfLayout.have_won)) {
foot.bits = MFDPANEL_MEMORY_BAG;
bmap = (OK == load_res_bitmap(&foot, REF_IMG_GridHelpSwitch, FALSE));
bw = foot.w;
bh = foot.h;
}
x = (MFD_VIEW_WID - sw) / 2;
y = (MFD_VIEW_HGT - sh - bh) / 2;
mfd_string_wrap = FALSE;
mfd_draw_string(str, x, y, GREEN_YELLOW_BASE + 3, TRUE);
gr_font_string_unwrap(str);
mfd_string_wrap = save_w;
if (bmap) {
#ifdef PUZZ_DIFF_NOHELP
if (PUZZLE_DIFFICULTY < MAX_DIFFICULTY)
bcolor = gpz_base_colors[((gridFlowPuzzle *)puzzle)->control_alg];
else
bcolor = GPZ_NOALG_COLOR;
#else
bcolor = gpz_base_colors[((gridFlowPuzzle *)puzzle)->gfLayout.control_alg];
#endif
x = (MFD_VIEW_WID - bw) / 2;
y += sh;
grid_help_clut[SWITCH_COLOR] = bcolor;
ss_clut_ubitmap(&foot, x, y, grid_help_clut);
}
}
int wirepos_score(wirePosPuzzle *wppz) {
int i, sc = 0, j;
for (i = 0; i < wppz->wirecnt; i++) {
wirePTrg *wpt = &wppz->wires[i];
if (wppz->scorealg != 0) {
if (memcmp(&wpt->cur, &wpt->targ, sizeof(wirePos)) == 0) {
sc += P_CORRECT;
score_spew("crct %d...", i);
} else if ((wpt->cur.lpos == wpt->targ.lpos) || (wpt->cur.rpos == wpt->targ.rpos)) {
sc += P_POS_OK;
score_spew("pos %d...", i);
} else if (get_delta(wpt->cur) == get_delta(wpt->targ)) {
sc += P_DELTA_OK;
score_spew("dlta %d...", i);
} else
score_spew("fail %d...", i);
} else {
int bsc = 0;
for (j = 0; j < wppz->wirecnt; j++) {
wirePTrg *tpt = &wppz->wires[j];
if (memcmp(&wpt->cur, &tpt->targ, sizeof(wirePos)) == 0) {
bsc = P_CORRECT;
score_spew2("crct %d %d...", i, j);
} else if ((wpt->cur.lpos == wpt->targ.lpos) || (tpt->cur.rpos == wpt->targ.rpos)) {
if (D_POS_OK > bsc) {
bsc = D_POS_OK;
score_spew2("pos %d %d...", i, j);
}
} else if (get_delta(wpt->cur) == get_delta(tpt->targ)) {
if (D_DELTA_OK > bsc) {
bsc = D_DELTA_OK;
score_spew2("dlta %d %d...", i, j);
}
} else
score_spew2("fail %d %d...", i, j);
}
sc += bsc;
}
}
score_spew("sc %d\n", sc);
sc = (sc * wppz->scale) >> WP_SCALE_SHF;
if (player_struct.drug_status[CPTRIP(GENIUS_DRUG_TRIPLE)] > 0)
sc += sc >> 2;
if (sc > UCHAR_MAX)
sc = UCHAR_MAX;
return wppz->score = sc;
}
#define pegrand(peg, range) ((peg) ? ((range)-1) : (rand() % (range)))
#define randvar(wppz) pegrand((wppz)->have_won, ((wppz)->scale >> WP_SCALE_SHF) << 1)
#define wirepos_curscore(wpppppz) ((wpppppz)->score + randvar(wpppppz) - ((wpppppz)->scale >> WP_SCALE_SHF))
#ifndef GAMEONLY
#define wirepos_spew(wz, csc) \
mprintf("%d-%d, %d-%d, %d-%d, %d-%d, taps %2.2x %2.2x, wim %x tick %x sc %d cs %d\n", wz->wires[0].cur.lpos, \
wz->wires[0].cur.rpos, wz->wires[1].cur.lpos, wz->wires[1].cur.rpos, wz->wires[2].cur.lpos, \
wz->wires[2].cur.rpos, wz->wires[3].cur.lpos, wz->wires[3].cur.rpos, wz->left_tap, wz->right_tap, \
wz->wire_in_motion, wz->wim_tick, wz->score, csc)
#else
#define wirepos_spew(wz, csc)
#endif
void wirepos_setup_buttons(wirePosPuzzle *wppz) {
LGPoint bsize = {ACCESSP_BTN_WD, ACCESSP_BTN_HGT};
LGPoint bdims = {ACCESSP_BTN_COL, ACCESSP_BTN_ROW};
LGRect r = {{ACCESSP_BTN_X, ACCESSP_BTN_Y}, {ACCESSP_BTN_X + ACCESSP_FULL_WD, ACCESSP_BTN_Y + ACCESSP_FULL_HGT}};
bdims.y = wppz->pincnt;
r.lr.y = ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * wppz->pincnt);
MFDBttnArrayResize(&(mfd_funcs[MFD_ACCESSPANEL_FUNC].handlers[0]), &r, bdims, bsize);
}
// a1 is master data, a2 target pos, a3 current pos
uchar wirepos_3int_init(wirePosPuzzle *wppz, int a1, int a2, int a3) { // restore the puzzle, for now just init it
int i;
wppz->wirecnt = a1 & 0xf;
a1 >>= 4;
if (wppz->wirecnt == 0)
wppz->wirecnt = 4;
wppz->pincnt = a1 & 0xf;
a1 >>= 4;
if (wppz->pincnt == 0)
wppz->pincnt = 6;
wppz->tscore = a1 & 0xff;
a1 >>= 8;
if (wppz->tscore == 0)
wppz->tscore = 128;
wppz->scorealg = a1 & 0xf;
a1 >>= 4;
wppz->have_won = a1 & 0xf;
a1 >>= 4;
wppz->scale = (256 << WP_SCALE_SHF) / (P_CORRECT * wppz->wirecnt);
wppz->left_tap = wppz->right_tap = 0;
// on highest difficulty, always use colored wires, maybe add some
// spurious pins.
if (PUZZLE_DIFFICULTY == MAX_DIFFICULTY) {
int p = wppz->pincnt;
wppz->scorealg = 1;
wppz->pincnt = p + ((player_struct.panel_ref) % (6 - p + 1));
wppz->tscore = 255;
}
for (i = 0; i < wppz->wirecnt; i++) {
wppz->wires[i].cur.lpos = a3 & 0x7;
wppz->left_tap |= (1 << (a3 & 7));
a3 >>= 3;
wppz->wires[i].cur.rpos = a3 & 0x7;
wppz->right_tap |= (1 << (a3 & 7));
a3 >>= 3;
wppz->wires[i].targ.lpos = a2 & 0x7;
a2 >>= 3;
wppz->wires[i].targ.rpos = a2 & 0x7;
a2 >>= 3;
}
wirepos_score(wppz);
wppz->wires_moved = 1;
wppz->last_score = wppz->score;
wppz->wire_in_motion = 0xff;
wppz->wim_tick = wppz->wim_shown = 0;
access_primary_mfd = -1;
return TRUE;
}
#define WIN_MASK 0x0F00000
void wirepos_3int_update(wirePosPuzzle *wppz) {
int p2, p4 = 0, cshf = 0, i;
p2 = objFixtures[objs[wppz->our_id].specID].p2;
if (wppz->have_won)
p2 |= WIN_MASK;
else
p2 &= ~WIN_MASK;
for (i = 0; i < wppz->wirecnt; i++, cshf += 6) {
p4 |= (wppz->wires[i].cur.lpos << (cshf));
p4 |= (wppz->wires[i].cur.rpos << (cshf + 3));
}
objFixtures[objs[wppz->our_id].specID].p2 = p2;
objFixtures[objs[wppz->our_id].specID].p4 = p4;
}
int wirepos_iswire(wirePosPuzzle *wppz, int wim_code) {
int i, owire = wim_code & BTN_MASK;
if (wim_code & LR_MASK) {
for (i = 0; i < wppz->wirecnt; i++)
if (wppz->wires[i].cur.rpos == owire)
return i;
} else {
for (i = 0; i < wppz->wirecnt; i++)
if (wppz->wires[i].cur.lpos == owire)
return i;
}
return -1;
}
#define ALLOW_FLIP
uchar wirepos_moveto(wirePosPuzzle *wppz, int wim_code) {
int wim_tap, retv;
if (wim_code & LR_MASK)
wim_tap = wppz->right_tap;
else
wim_tap = wppz->left_tap;
if ((wppz->wire_in_motion != wim_code) && ((wppz->wire_in_motion & LR_MASK) == (wim_code & LR_MASK))
#ifndef ALLOW_FLIP
(((1 << (wim_code & BTN_MASK)) & wim_tap) == 0))
#endif
)
{
int owire = wppz->wire_in_motion & BTN_MASK, twire = wim_code & BTN_MASK, loc;
loc = wirepos_iswire(wppz, wppz->wire_in_motion);
if (loc == -1) {
retv = FALSE;
} else {
#ifdef ALLOW_FLIP
if (((1 << twire) & wim_tap) != 0) {
int oloc = wirepos_iswire(wppz, wim_code);
if (wim_code & LR_MASK) {
wppz->wires[loc].cur.rpos = twire;
wppz->wires[oloc].cur.rpos = owire;
} else {
wppz->wires[loc].cur.lpos = twire;
wppz->wires[oloc].cur.lpos = owire;
}
} else
#endif
{
wim_tap &= ~(1 << owire);
wim_tap |= (1 << twire);
if (wim_code & LR_MASK) {
wppz->right_tap = wim_tap;
wppz->wires[loc].cur.rpos = twire;
} else {
wppz->left_tap = wim_tap;
wppz->wires[loc].cur.lpos = twire;
}
}
}
retv = TRUE;
}
else
retv = FALSE;
wppz->wire_in_motion = NO_WIRE_IN_MOTION;
return retv;
}
int wirepos_rescore_n_check(wirePosPuzzle *wppz) { // score and check, if solved, open us
int score = wirepos_curscore(wppz);
if (score > 255)
score = 255;
if ((wppz->have_won == 0) && (score >= wppz->tscore)) {
accesspanel_trigger(player_struct.panel_ref);
wppz->have_won = 1;
wirepos_3int_update(wppz);
return -score;
}
return score;
}
uchar mfd_accesspanel_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
wirePosPuzzle *wppz = (wirePosPuzzle *)&player_struct.mfd_access_puzzles[0];
int wim_code;
if (mfd->id != access_primary_mfd)
return TRUE;
if ((ev->subtype & (MOUSE_LDOWN | UI_MOUSE_LDOUBLE)) == 0)
return TRUE;
if (wppz->have_won)
return TRUE;
wppz->wires_moved = 1;
wim_code = bttn.y + (bttn.x ? LR_MASK : 0);
// parse input, move appropriately
if (wppz->wire_in_motion != NO_WIRE_IN_MOTION) { // put it down
if (wirepos_moveto(wppz, wim_code)) {
wirepos_score(wppz);
}
} else // pick it up
{
if (wirepos_iswire(wppz, wim_code) != -1) {
wppz->wire_in_motion = wim_code;
}
}
// wirepos_spew(wppz,53);
mfd_notify_func(MFD_ACCESSPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
wirepos_3int_update(wppz);
return TRUE;
}
char mfd_type_accesspanel(ObjID id) {
int p2;
p2 = objFixtures[objs[id].specID].p2;
p2 = (p2 >> 28) & 0xF;
if (p2 == WIREPUZ_CODE)
return MFD_ACCESSPANEL_FUNC;
else {
return MFD_GRIDPANEL_FUNC;
}
}
char mfd_setup_accesspanel(uchar special, ObjID id) {
void mfd_setup_wirepanel(uchar special, ObjID id);
void mfd_setup_gridpanel(ObjID id);
char func;
func = mfd_type_accesspanel(id);
if (func == MFD_ACCESSPANEL_FUNC)
mfd_setup_wirepanel(special, id);
else {
mfd_setup_gridpanel(id);
SIGN_US();
SHADOW_PANEL_ID = id;
SHADOW_PANEL_LEV = player_struct.level;
memcpy(SHADOW_PANEL_STOR, player_struct.mfd_access_puzzles, sizeof(player_struct.mfd_access_puzzles));
}
return func;
}
uchar mfd_solve_accesspanel(ObjID id) {
int p2;
uchar retval;
p2 = objFixtures[objs[id].specID].p2;
p2 = (p2 >> 28) & 0xF;
if (p2 == WIREPUZ_CODE)
retval = mfd_solve_wirepanel();
else {
retval = mfd_solve_gridpanel();
// okay, grid panels trigger in the expose func, when they
// show you you've won, whereas wire puzzles trigger when they
// score. So, only in this case have we not triggered yet.
if (retval == EPICK_SOLVED)
accesspanel_trigger(player_struct.panel_ref);
}
return (retval);
}
// Does every thing but set the slot..
// Special is how much SHODAN has defeated us by
void mfd_setup_wirepanel(uchar special, ObjID id) {
int p2, p3, p4;
wirePosPuzzle *wppz = (wirePosPuzzle *)&player_struct.mfd_access_puzzles[0];
p2 = objFixtures[objs[id].specID].p2;
p3 = objFixtures[objs[id].specID].p3;
p4 = objFixtures[objs[id].specID].p4;
if (p3 == 0)
p3 = 0x1c342a50;
if (p4 == 0)
p4 = 0x0cb530a1;
wppz->special = special;
wppz->our_id = id;
wirepos_3int_init(wppz, p2, p3, p4);
mfd_notify_func(MFD_ACCESSPANEL_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);
}
uchar mfd_solve_wirepanel() {
wirePosPuzzle *wppz = (wirePosPuzzle *)&player_struct.mfd_access_puzzles[0];
int wire, swapper, targ, num_wires;
int score;
if (wppz->have_won) {
return EPICK_PRESOL;
}
//keep within array bounds
num_wires = wppz->wirecnt;
if (num_wires > MAX_P_WIRES) num_wires = MAX_P_WIRES;
for (wire = 0; wire < num_wires; wire++) {
#define MINIMAL_SOLUTION
#ifdef MINIMAL_SOLUTION
// move one wire to target, swapping it with a later wire if
// necessary (we should never need to swap with a previous
// wire, since they are already in their target positions,
// which should never share pins with our target position.
targ = wppz->wires[wire].targ.lpos;
for (swapper = wire + 1; swapper < num_wires; swapper++) {
if (wppz->wires[swapper].cur.lpos == targ) {
wppz->wires[swapper].cur.lpos = wppz->wires[wire].cur.lpos;
wppz->wires[wire].cur.lpos = targ;
}
}
if (wppz->wires[wire].cur.lpos != targ)
wppz->wires[wire].cur.lpos = targ;
targ = wppz->wires[wire].targ.rpos;
for (swapper = wire + 1; swapper < num_wires; swapper++) {
if (wppz->wires[swapper].cur.rpos == targ) {
wppz->wires[swapper].cur.rpos = wppz->wires[wire].cur.rpos;
wppz->wires[wire].cur.rpos = targ;
}
}
if (wppz->wires[wire].cur.rpos != targ)
wppz->wires[wire].cur.rpos = targ;
// rescore, and if target score has been achieved, we are done.
wirepos_score(wppz);
score = wirepos_rescore_n_check(wppz);
if (wppz->have_won) {
wppz->score = score > 0 ? score : -score;
wirepos_3int_update(wppz);
break;
}
#else
wppz->wires[wire].cur = wppz->wires[wire].targ;
#endif
}
#ifndef MINIMAL_SOLUTION
// rescore, and if target score has been achieved, we are done.
wirepos_score(wppz);
score = wirepos_rescore_n_check(wppz);
if (wppz->have_won) {
wppz->score = score > 0 ? score : -score;
wirepos_3int_update(wppz);
}
#endif
mfd_notify_func(MFD_ACCESSPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
// always return success: if we've gotten to this point without solving
// the puzzle, it should be because of randomness in rescore_n_check
return (EPICK_SOLVED);
// return(wppz->have_won?EPICK_SOLVED:EPICK_FAILED);
}
static uchar wire_base[MAX_P_WIRES] = {0x35, 0x22, 0x78, 0x4B, 0x5A, 0x41};
#define ACCESSP_WIRE_DULL 3
#define ACCESSP_BLINK_MASK 0x40
#define accessp_score_rect_f(f, ls, rs, off) \
f(ACCESSP_SCORE_X - off + ((ls) >> ACCESSP_SCORE_SHF), ACCESSP_SCORE_YT - off, \
ACCESSP_SCORE_X + off + ((rs) >> ACCESSP_SCORE_SHF), ACCESSP_SCORE_YB + off)
#define accessp_score_rect(ls, rs, off) \
ACCESSP_SCORE_X - off + ((ls) >> ACCESSP_SCORE_SHF), ACCESSP_SCORE_YT - off, \
ACCESSP_SCORE_X + off + ((rs) >> ACCESSP_SCORE_SHF), ACCESSP_SCORE_YB + off
#define accessp_score_cmp(sc1, sc2, cmp) ((sc1 >> ACCESSP_SCORE_SHF) cmp(sc2 >> ACCESSP_SCORE_SHF))
// if no mfd is on the given slot, return -1
// else, return the id of the lowest-numbered
// mfd with this slot.
errtype mfd_accesspanel_init(MFD_Func *f) {
int cnt = 0;
errtype err;
LGPoint bsize = {ACCESSP_BTN_WD, ACCESSP_BTN_HGT};
LGPoint bdims = {ACCESSP_BTN_COL, ACCESSP_BTN_ROW};
LGRect r = {{ACCESSP_BTN_X, ACCESSP_BTN_Y}, {ACCESSP_BTN_X + ACCESSP_FULL_WD, ACCESSP_BTN_Y + ACCESSP_FULL_HGT}};
err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_accesspanel_button_handler, NULL);
if (err != OK)
return err;
f->handler_count = cnt;
#ifdef WORKING_INC_MFDS
player_struct.mfd_func_status[MFD_ACCESSPANEL_FUNC] |= 1 << 4;
#endif
return OK;
}
uchar mfd_accesspanel_handler(MFD *m, uiEvent *e) {
#ifdef EPICK_ON_CURSOR_TRY
extern uchar try_use_epick(ObjID panel, ObjID cursor_obj);
if (ev->type != UI_EVENT_MOUSE || !(ev->subtype & (MOUSE_LDOWN | UI_MOUSE_LDOUBLE)))
return FALSE;
return (try_use_epick(player_struct.panel_ref, object_on_cursor));
#else
return (FALSE);
#endif
}
int access_help_string(wirePosPuzzle *wppz) {
if (wppz->have_won)
return (REF_STR_PanelSolved);
else
return (REF_STR_WirePuzzHelp + (wppz->wire_in_motion != NO_WIRE_IN_MOTION));
}
void mfd_accesspanel_expose(MFD *mfd, ubyte control) {
void mfd_clear_view(void);
wirePosPuzzle *wppz = (wirePosPuzzle *)&player_struct.mfd_access_puzzles[0];
uchar full = control & MFD_EXPOSE_FULL;
if (control & MFD_EXPOSE) // Time to draw stuff
{
int cscore;
uchar primary;
if ((full && access_primary_mfd < 0) || player_struct.mfd_current_slots[access_primary_mfd] != MFD_INFO_SLOT) {
full = TRUE;
access_primary_mfd = mfd->id;
}
primary = (mfd->id == access_primary_mfd);
if (primary) {
if ((cscore = wirepos_rescore_n_check(wppz)) < 0) {
mfd_notify_func(MFD_ACCESSPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
cscore = -cscore;
}
cscore = wirepos_curscore(wppz);
if (cscore < 0)
cscore = -cscore;
if (cscore > 255)
cscore = 255;
}
mfd_clear_rects();
gr_push_canvas(pmfd_canvas);
ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
if (!primary) {
// draw help string, if different from last string or
// if full expose.
int prev = last_access_help_string;
char buf[80];
last_access_help_string = access_help_string(wppz);
if (full || last_access_help_string != prev) {
mfd_clear_view();
get_string(last_access_help_string, buf, 80);
draw_help_text(buf, TRUE, wppz);
}
gr_pop_canvas();
mfd_update_rects(mfd);
return;
}
if (full) {
wirepos_setup_buttons(wppz);
mfd_clear_view(); // draw full score (should color based on score), every other line?
gr_set_fcolor(ACCESSP_SCORE_BOR);
accessp_score_rect_f(ss_box, 0, 255, 1);
gr_set_fcolor(ACCESSP_SCORE_COL);
accessp_score_rect_f(ss_rect, 0, cscore, 0);
gr_set_fcolor(ACCESSP_TARG_COL);
ss_vline(ACCESSP_SCORE_X + (wppz->tscore >> ACCESSP_SCORE_SHF) - 1, ACCESSP_SCORE_YT, ACCESSP_SCORE_YB - 1);
} else if (accessp_score_cmp(wppz->last_score, cscore, <)) { // draw more score delta
gr_set_fcolor(ACCESSP_SCORE_COL);
accessp_score_rect_f(ss_rect, wppz->last_score, cscore, 0);
mfd_add_rect(accessp_score_rect(wppz->last_score, cscore, 0));
if (accessp_score_cmp(wppz->tscore, wppz->last_score, >=) && accessp_score_cmp(cscore, wppz->tscore, >=)) {
gr_set_fcolor(ACCESSP_TARG_COL);
ss_vline(ACCESSP_SCORE_X + (wppz->tscore >> ACCESSP_SCORE_SHF) - 1, ACCESSP_SCORE_YT,
ACCESSP_SCORE_YB - 1);
}
} else if (accessp_score_cmp(wppz->last_score, cscore, >)) { // put back correct part of background
accessp_score_rect_f(ss_safe_set_cliprect, cscore, wppz->last_score, 0);
if (!full_game_3d)
ss_bitmap(&mfd_background, 0, 0);
// gr_bitmap(&mfd_background, 0, 0);
ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
mfd_add_rect(accessp_score_rect(cscore, wppz->last_score, 0));
if (accessp_score_cmp(cscore, wppz->tscore, <=) && accessp_score_cmp(wppz->tscore, wppz->last_score, <=)) {
gr_set_fcolor(ACCESSP_TARG_COL);
ss_vline(ACCESSP_SCORE_X + (wppz->tscore >> ACCESSP_SCORE_SHF) - 1, ACCESSP_SCORE_YT,
ACCESSP_SCORE_YB - 1);
}
}
wppz->last_score = cscore;
if (full || wppz->wires_moved) { // draw wires
int i;
int lry = ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * wppz->pincnt);
wppz->wires_moved = 0;
if (!full) {
ss_safe_set_cliprect(ACCESSP_BTN_X, ACCESSP_BTN_Y, ACCESSP_BTN_X + ACCESSP_FULL_WD, lry);
if (!full_game_3d)
ss_bitmap(&mfd_background, 0, 0);
// gr_bitmap(&mfd_background, 0, 0);
mfd_add_rect(ACCESSP_BTN_X, ACCESSP_BTN_Y, ACCESSP_BTN_X + ACCESSP_FULL_WD, lry);
ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
}
for (i = 0; i < wppz->pincnt; i++) {
gr_set_fcolor(ACCESSP_PIN_COL);
ss_box(ACCESSP_BTN_X + 1, ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * i), ACCESSP_BTN_X + ACCESSP_BTN_WD,
ACCESSP_BTN_Y - 1 + (ACCESSP_BTN_HGT * (i + 1)));
ss_box(ACCESSP_BTN_X + ACCESSP_FULL_WD - ACCESSP_BTN_WD, ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * i),
ACCESSP_BTN_X + ACCESSP_FULL_WD - 1, ACCESSP_BTN_Y - 1 + (ACCESSP_BTN_HGT * (i + 1)));
gr_set_fcolor(ACCESSP_PIN_COL + 6);
ss_box(ACCESSP_BTN_X, ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * i) - 1, ACCESSP_BTN_X + ACCESSP_BTN_WD + 1,
ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * (i + 1)));
ss_box(ACCESSP_BTN_X + ACCESSP_FULL_WD - ACCESSP_BTN_WD - 1, ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * i) - 1,
ACCESSP_BTN_X + ACCESSP_FULL_WD, ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * (i + 1)));
}
gr_set_fcolor(ACCESSP_CHIP_COL);
ss_box(ACCESSP_BTN_X + ACCESSP_BTN_WD, ACCESSP_BTN_Y, ACCESSP_BTN_X + ACCESSP_FULL_WD - ACCESSP_BTN_WD,
lry - 1);
gr_set_fcolor(ACCESSP_CHIP_COL + 7);
ss_box(ACCESSP_BTN_X + ACCESSP_BTN_WD + 1, ACCESSP_BTN_Y + 1,
ACCESSP_BTN_X + ACCESSP_FULL_WD - ACCESSP_BTN_WD - 1, lry - 1 - 1);
for (i = 0; i < wppz->wirecnt; i++) {
int lfti, rghi;
int basecol;
lfti = wppz->wires[i].cur.lpos;
rghi = wppz->wires[i].cur.rpos;
basecol = wire_base[wppz->scorealg == 0 ? (player_struct.panel_ref & 3) : i];
gr_set_fcolor(basecol);
ss_thick_int_line(ACCESSP_BTN_X + (ACCESSP_BTN_WD / 2),
ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * lfti) + (ACCESSP_BTN_HGT / 2) - 1,
ACCESSP_BTN_X + ACCESSP_FULL_WD - 1 - (ACCESSP_BTN_WD / 2),
ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * rghi) + (ACCESSP_BTN_HGT / 2) - 1);
gr_set_fcolor(basecol + 3);
ss_thick_int_line(ACCESSP_BTN_X + (ACCESSP_BTN_WD / 2),
ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * lfti) + (ACCESSP_BTN_HGT / 2),
ACCESSP_BTN_X + ACCESSP_FULL_WD - 1 - (ACCESSP_BTN_WD / 2),
ACCESSP_BTN_Y + (ACCESSP_BTN_HGT * rghi) + (ACCESSP_BTN_HGT / 2));
}
}
if ((wppz->wire_in_motion != NO_WIRE_IN_MOTION) &&
(((*tmd_ticks) & ACCESSP_BLINK_MASK) !=
wppz->wim_tick)) { // if already shown but not in motion, it is already redrawn, baby
int i = wppz->wire_in_motion & BTN_MASK;
wppz->wim_tick = (*tmd_ticks) & ACCESSP_BLINK_MASK;
if ((wppz->wim_tick == ACCESSP_BLINK_MASK) || (wppz->wire_in_motion == NO_WIRE_IN_MOTION)) {
gr_set_fcolor(0);
wppz->wim_shown = 0;
} else {
gr_set_fcolor(ACCESSP_PIN_COL - 2);
wppz->wim_shown = 1;
}
if (wppz->wire_in_motion & LR_MASK) {
ss_rect(ACCESSP_BTN_X + ACCESSP_FULL_WD - ACCESSP_BTN_WD + 1, ACCESSP_BTN_Y + 1 + (ACCESSP_BTN_HGT * i),
ACCESSP_BTN_X + ACCESSP_FULL_WD - 1 - 1, ACCESSP_BTN_Y - 1 - 1 + (ACCESSP_BTN_HGT * (i + 1)));
mfd_add_rect(ACCESSP_BTN_X + ACCESSP_FULL_WD - ACCESSP_BTN_WD + 1,
ACCESSP_BTN_Y + 1 + (ACCESSP_BTN_HGT * i), ACCESSP_BTN_X + ACCESSP_FULL_WD - 1 - 1,
ACCESSP_BTN_Y - 1 - 1 + (ACCESSP_BTN_HGT * (i + 1)));
} else {
ss_rect(ACCESSP_BTN_X + 1 + 1, ACCESSP_BTN_Y + 1 + (ACCESSP_BTN_HGT * i),
ACCESSP_BTN_X + ACCESSP_BTN_WD - 1, ACCESSP_BTN_Y - 1 - 1 + (ACCESSP_BTN_HGT * (i + 1)));
mfd_add_rect(ACCESSP_BTN_X + 1 + 1, ACCESSP_BTN_Y + 1 + (ACCESSP_BTN_HGT * i),
ACCESSP_BTN_X + ACCESSP_BTN_WD - 1, ACCESSP_BTN_Y - 1 - 1 + (ACCESSP_BTN_HGT * (i + 1)));
}
}
gr_pop_canvas();
// Now that we've popped the canvas, we can send the
// updated mfd to screen
mfd_update_rects(mfd);
#ifndef WORKING_INC_MFDS
mfd_notify_func(MFD_ACCESSPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
#endif
} else {
ObjID obj = panel_ref_unexpose(mfd->id, MFD_ACCESSPANEL_FUNC);
if (obj != OBJ_NULL) {
objs[obj].info.current_frame = 0;
}
return;
}
}
// returns the "charged" version of the given state, if any. For states
// which cannot be charged (empty and open) return the given state.
//
gpz_state gpz_charge_state(gpz_state s) {
if (s == GPZ_EMPTY)
return s;
return ((gpz_state)((int)s | 1));
}
// returns the "uncharged" version of the given state, if any. Note that
// empty and open are their own uncharged states.
gpz_state gpz_uncharge_state(gpz_state s) {
if (s == GPZ_OPEN)
return s;
return ((gpz_state)((int)s & ~1));
}
// returns TRUE iff s is a "charged" state.
uchar gpz_is_charged(gpz_state s) { return (s != GPZ_OPEN && (s & 1)); }
void gpz_toggle_state(gridFlowPuzzle *gfpz, short r, short c) {
gpz_state s;
s = gpz_get_grid_state(gfpz, r, c);
s = gpz_uncharge_state(s);
if (s == GPZ_OPEN)
gpz_set_grid_state(gfpz, r, c, GPZ_CLOSED);
else if (s == GPZ_CLOSED)
gpz_set_grid_state(gfpz, r, c, GPZ_OPEN);
}
// bitarray_get()
// Treating "base" as an array of elements of given size "siz", get
// element number "off". Works for bizzare sizes like 3 bits, packing.
// elements within ints and across int boundaries.
//
gpz_state bitarray_get(short siz, short off, uint *base) {
short loc, shft, ans, over;
loc = (siz * off) / (sizeof(uint) * 8);
shft = (siz * off) % (sizeof(uint) * 8);
// get from appropriate offset
ans = base[loc] >> shft;
// did we cross a word boundary?
over = siz - ((sizeof(uint) * 8) - shft);
if (over > 0) {
ans = ans & ((1 << (siz - over)) - 1);
ans |= base[loc + 1] << (siz - over);
}
// mask down to siz bits
ans = ans & ((1 << siz) - 1);
return (gpz_state)ans;
}
// bitarray_set()
// Treating "base" as an array of elements of given size "siz", set
// element number "off" to specified value "val." Works for bizzare sizes
// like 3 bits, packing elements within an int and across int boundaries.
//
void bitarray_set(short siz, short off, ushort val, uint *base) {
short loc, shft, over;
loc = (siz * off) / (sizeof(uint) * 8);
shft = (siz * off) % (sizeof(uint) * 8);
// restrict to valid range
val &= (1 << siz) - 1;
// clear destination bits
base[loc] &= ~(((1 << siz) - 1) << shft);
// set new bits
base[loc] |= val << shft;
// did we cross a word boundary?
over = siz - ((sizeof(uint) * 8) - shft);
if (over > 0) {
base[loc + 1] &= ~((1 << over) - 1);
base[loc + 1] |= val >> (siz - over);
}
}
errtype mfd_gridpanel_init(MFD_Func *f) {
uchar mfd_gridpanel_button_handler(MFD * mfd, LGPoint bttn, uiEvent * ev, void *data);
int cnt = 0;
errtype err;
LGPoint bsize = {GRIDP_BTN_WD, GRIDP_BTN_HGT};
LGPoint bdims = {GRIDP_BTN_COL, GRIDP_BTN_ROW};
LGRect r = {{GRIDP_BTN_X, GRIDP_BTN_Y}, {GRIDP_BTN_X + GRIDP_FULL_WD, GRIDP_BTN_Y + GRIDP_FULL_HGT}};
err = MFDBttnArrayInit(&f->handlers[cnt++], &r, bdims, bsize, mfd_gridpanel_button_handler, NULL);
if (err != OK)
return err;
f->handler_count = cnt;
gridCursorbm.bits = gridCursorBits;
return OK;
}
#define GPZ_GOOD_SEARCH_DEPTH 4
int gpz_search_depth(gridFlowPuzzle *gfpz) {
if (gfpz->gfLayout.control_alg == GPZ_SIMPLE)
return (gfpz->gfLayout.rows * gfpz->gfLayout.cols);
else
return (GPZ_GOOD_SEARCH_DEPTH);
}
//
// When I lie next to you
// I shiver and shake.
// Tell me you love me,
// I dream I'm awake.
//
// attempts to add a gate to a grid puzzle in such a way as to keep
// it solvable. Finds candidates for this new gate and tries them
// out with the same solver used by in-game "logic probes". If it can
// solve the puzzle with the new gate, it accepts the new gate.
//
// Times out in this attempt after 1 second.
//
void gpz_add_gate(gridFlowPuzzle *gfpz, ObjID me) {
int r, c, count, rr, cc, n;
gpz_state adj, tmp;
ulong timeout;
ushort seed = (ushort)me;
// if already opened panel once, don't bother.
// note that this isn't really correct, because there exists
// a panel that you might click on without opening it, because
// it has a comparator. However, we secretly know that the
// puzzle in this case is not one which can get a new gate
// anyway, so this happens to work out. Beware.
if (objs[me].info.inst_flags & OLH_INST_FLAG)
return;
timeout = *tmd_ticks + (CIT_CYCLE);
gpz_uncharge_grid(gfpz);
// find a node which could possibly be charged, and has
// at least three adjacent nodes which could possibly be charged.
// try changing it to a gate and seeing if you can solve the
// puzzle.
for (rr = 0; rr < gfpz->gfLayout.rows; rr++) {
for (cc = 0; cc < gfpz->gfLayout.cols; cc++) {
// note that tight_loop is getting called in the time-consuming
// part of this, find_winning_move, so hopefully we don't have
// to call it here.
// change our origin from 0,0 so we don't always put
// gates in the upper left.
n = rr + cc * gfpz->gfLayout.rows;
n = (n + seed) % (gfpz->gfLayout.rows * gfpz->gfLayout.cols);
r = n % gfpz->gfLayout.rows;
c = n / gfpz->gfLayout.rows;
if (gpz_get_grid_state(gfpz, r, c) == GPZ_EMPTY)
continue;
// count adjacent chargeable nodes.
{
count = 0;
adj = gpz_get_grid_state(gfpz, r - 1, c);
count += (adj != GPZ_EMPTY);
adj = gpz_get_grid_state(gfpz, r + 1, c);
count += (adj != GPZ_EMPTY);
adj = gpz_get_grid_state(gfpz, r, c - 1);
count += (adj != GPZ_EMPTY);
adj = gpz_get_grid_state(gfpz, r, c + 1);
count += (adj != GPZ_EMPTY);
}
if (count >= 3) {
tmp = gpz_get_grid_state(gfpz, r, c);
gpz_set_grid_state(gfpz, r, c, GPZ_GATE);
if (find_winning_move(gfpz, NULL, gpz_search_depth(gfpz), NULL, FALSE, timeout)) {
// it's solvable. Keep the change.
return;
} else if (*tmd_ticks > timeout) {
gpz_set_grid_state(gfpz, r, c, tmp);
return;
}
gpz_set_grid_state(gfpz, r, c, tmp);
}
}
}
return;
}
uchar mfd_solve_gridpanel() {
gridFlowPuzzle *gfpz = (gridFlowPuzzle *)&player_struct.mfd_access_puzzles[0];
gridFlowPuzzle solved;
int search_depth;
uchar found, shadow;
ObjID id = gfpz->gfLayout.our_id;
uchar temp[sizeof(player_struct.mfd_access_puzzles)];
// for slow machines; don't give probe more than a couple of
// seconds to solve a panel. This time limit is set to 0 to
// mean no time limit.
long logic_probe_timeout = *tmd_ticks + EPICK_TIMEOUT;
if (gfpz->gfLayout.have_won)
return EPICK_PRESOL;
shadow = (WE_ARE_SIGNED() && SHADOW_PANEL_ID == id && SHADOW_PANEL_LEV == player_struct.level &&
!(objFixtures[objs[id].specID].p2 & 0x10000));
if (shadow) {
memcpy(temp, player_struct.mfd_access_puzzles, sizeof(player_struct.mfd_access_puzzles));
memcpy(player_struct.mfd_access_puzzles, SHADOW_PANEL_STOR, sizeof(player_struct.mfd_access_puzzles));
}
search_depth = gpz_search_depth(gfpz);
found = find_winning_move(gfpz, &solved, search_depth, NULL, FALSE, logic_probe_timeout);
if (found && solved.gfLayout.have_won) {
*gfpz = solved;
gpz_4int_update(gfpz);
mfd_notify_func(MFD_GRIDPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
} else if (shadow)
memcpy(player_struct.mfd_access_puzzles, temp, sizeof(player_struct.mfd_access_puzzles));
return (gfpz->gfLayout.have_won ? EPICK_SOLVED : EPICK_FAILED);
}
// gets state of grid node at coordinates row,col. If row or col is off
// the grid, then GPZ_EMPTY is returned.
//
#define GPZ_PERIM_TYPEMASK 0x10
#define GPZ_PERIM_SIDEMASK 0x08
gpz_state gpz_get_grid_state(gridFlowPuzzle *gfpz, short row, short col) {
void gpz_perimeter_to_grid(gridFlowPuzzle * gfpz, ushort per, short *r, short *c);
short r, c, *q;
if (row < 0 || col < 0 || row >= (gfpz->gfLayout.rows) || col >= (gfpz->gfLayout.cols)) {
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.src, &r, &c);
if (gfpz->gfLayout.src & GPZ_PERIM_TYPEMASK)
q = &c;
else
q = &r;
*q += (gfpz->gfLayout.src & GPZ_PERIM_SIDEMASK) ? 1 : -1;
if (row == r && c == col)
return GPZ_CLOSED_CHARGED;
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.dest, &r, &c);
if (gpz_is_charged(gpz_get_grid_state(gfpz, r, c))) {
if (gfpz->gfLayout.dest & GPZ_PERIM_TYPEMASK)
q = &c;
else
q = &r;
*q += (gfpz->gfLayout.dest & GPZ_PERIM_SIDEMASK) ? 1 : -1;
if (row == r && c == col)
return GPZ_CLOSED_CHARGED;
}
return (GPZ_EMPTY);
}
return (bitarray_get(GRIDP_STATE_BITS, col + row * (gfpz->gfLayout.cols), gfpz->states));
}
// sets state of grid node at coordinates row, col. No effect if row or
// col is off the grid.
void gpz_set_grid_state(gridFlowPuzzle *gfpz, short row, short col, gpz_state val) {
if (row < 0 || col < 0 || row >= (gfpz->gfLayout.rows) || col >= (gfpz->gfLayout.cols))
return;
bitarray_set(GRIDP_STATE_BITS, col + row * (gfpz->gfLayout.cols), val, gfpz->states);
}
void gpz_uncharge_grid(gridFlowPuzzle *gfpz) {
int r, c;
gpz_state s;
for (r = 0; r < gfpz->gfLayout.rows; r++) {
for (c = 0; c < gfpz->gfLayout.cols; c++) {
s = gpz_get_grid_state(gfpz, r, c);
gpz_set_grid_state(gfpz, r, c, gpz_uncharge_state(s));
}
}
}
void gpz_perimeter_to_grid(gridFlowPuzzle *gfpz, ushort per, short *r, short *c) {
ushort rr, cc, edge;
edge = (per >> 3) & 3;
per = per & 7;
rr = gfpz->gfLayout.rows;
cc = gfpz->gfLayout.cols;
switch (edge) {
case 0:
*r = 0;
*c = per;
break;
case 1:
*r = rr - 1;
*c = per;
break;
case 2:
*c = 0;
*r = per;
break;
case 3:
*c = cc - 1;
*r = per;
break;
}
return;
}
uchar gpz_state_charged(gridFlowPuzzle *gfpz, short r, short c) {
return (gpz_is_charged(gpz_get_grid_state(gfpz, r, c)));
}
#ifdef SHOW_DONENESS
// Find the shortest Manhatten distance from a charged node to the
// destination node. Converts this to a rating in the range 0 to
// 255 by normalizing to the greatest Manhatten distance from the
// destination of any node in the puzzle.
uchar gpz_doneness(gridFlowPuzzle *gfpz) {
int farthest, doneness, dist;
short r, c, dr, dc;
doneness = INT_MAX;
farthest = INT_MIN;
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.dest, &dr, &dc);
for (r = 0; r < gfpz->gfLayout.rows; r++) {
for (c = 0; c < gfpz->gfLayout.cols; c++) {
dist = abs(r - dr) + abs(c - dc);
if (dist > farthest)
farthest = dist;
if (gpz_state_charged(gfpz, r, c) && dist < doneness)
doneness = dist;
}
}
if (doneness == INT_MAX)
return 0;
return (UCHAR_MAX * (farthest - doneness) / farthest);
}
#endif
// recalculates "current" or "charge" flow through grid. Returns TRUE
// iff destination node is charged.
//
uchar gpz_propogate_charge_n_check(gridFlowPuzzle *gfpz) {
uchar flow;
short r, c, src_r, src_c;
gpz_state s;
extern errtype accesspanel_trigger(ObjID id);
// uncharge whole grid
gpz_uncharge_grid(gfpz);
// charge source node
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.src, &r, &c);
s = gpz_get_grid_state(gfpz, r, c);
if (s == GPZ_CLOSED || s == GPZ_FULL)
gpz_set_grid_state(gfpz, r, c, gpz_charge_state(gpz_get_grid_state(gfpz, r, c)));
// propogate charges until the propogatin's done. This must terminate
// 'cause we only set "flow" when we charge a node, and eventually we
// will run out of nodes to charge.
do {
flow = FALSE;
for (r = 0; r < gfpz->gfLayout.rows; r++) {
for (c = 0; c < gfpz->gfLayout.cols; c++) {
int sum;
s = gpz_get_grid_state(gfpz, r, c);
src_r = r;
src_c = c;
switch (s) {
case GPZ_CLOSED:
case GPZ_FULL:
case GPZ_GATE:
sum = (int)gpz_state_charged(gfpz, --src_r, src_c);
sum += (int)gpz_state_charged(gfpz, ++src_r, --src_c);
sum += (int)gpz_state_charged(gfpz, ++src_r, ++src_c);
sum += (int)gpz_state_charged(gfpz, --src_r, ++src_c);
if (s == GPZ_GATE) {
if (sum >= 2) {
gpz_set_grid_state(gfpz, r, c, gpz_charge_state(s));
flow = TRUE;
}
} else {
if (sum > 0) {
gpz_set_grid_state(gfpz, r, c, gpz_charge_state(s));
flow = TRUE;
}
}
break;
/*
if(gpz_state_charged(gfpz,--src_r,src_c)
|| gpz_state_charged(gfpz,++src_r,--src_c)
|| gpz_state_charged(gfpz,++src_r,++src_c)
|| gpz_state_charged(gfpz,--src_r,++src_c)) {
gpz_set_grid_state(gfpz,r,c,gpz_charge_state(s));
flow=TRUE;
}
break;
if(gpz_state_charged(gfpz,--src_r,src_c)
+ gpz_state_charged(gfpz,++src_r,--src_c)
+ gpz_state_charged(gfpz,++src_r,++src_c)
+ gpz_state_charged(gfpz,--src_r,++src_c) >= 2) {
gpz_set_grid_state(gfpz,r,c,gpz_charge_state(s));
flow=TRUE;
}
break;
*/
// default if state is already charged or cannot be charged.
default:
break;
}
}
}
} while (flow);
// do we win?
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.dest, &r, &c);
s = gpz_get_grid_state(gfpz, r, c);
return (gpz_is_charged(s));
}
uchar find_winning_move_from(gridFlowPuzzle *gfpz, gridFlowPuzzle *solved, LGPoint *move, int r0, int c0, int dep,
ulong timeout) {
gpz_state gridpanel_move(LGPoint node, gridFlowPuzzle * gfpz);
int r, c, real_c0 = c0;
gpz_state s, pass;
LGPoint try_it;
// this will never put more copies on the stack at a time than our total search depth
// (currently no more than 4) at 32 bytes per copy.
gridFlowPuzzle copy;
copy = *gfpz;
// try all moves, working to the right and then down. Since moves are
// commutative and reversible, we need only try moves in this one
// particular order. Try moving open circuits to closed circuits
// first as a heuristic. Also, don't bother with the second pass
// (changing closed circuits to open circuits) if we have the simple
// scoring algorithm.
for (pass = GPZ_OPEN; pass != GPZ_EMPTY;) {
c0 = real_c0;
for (r = r0; r < gfpz->gfLayout.rows && dep > 0; r++) {
for (c = c0; c < gfpz->gfLayout.cols && dep > 0; c++) {
tight_loop(FALSE);
if (timeout != 0 && *tmd_ticks > timeout) {
return (FALSE);
}
s = gpz_uncharge_state(gpz_get_grid_state(©, r, c));
if (s == pass) {
try_it.x = c;
try_it.y = r;
gridpanel_move(try_it, ©);
if (gpz_propogate_charge_n_check(©)) {
if (move)
*move = try_it;
if (solved) {
*solved = copy;
solved->gfLayout.have_won = TRUE;
}
return TRUE;
}
if (gfpz->gfLayout.control_alg == GPZ_SIMPLE) {
dep--;
} else if (dep > 1) {
int new_r0 = r, new_c0 = c + 1;
if (new_c0 >= gfpz->gfLayout.cols) {
new_c0 = 0;
new_r0++;
}
if (new_r0 < gfpz->gfLayout.rows &&
find_winning_move_from(©, solved, move, new_r0, new_c0, dep - 1, timeout)) {
return TRUE;
} else {
// undo this move and try another.
memcpy(copy.states, gfpz->states, sizeof(copy.states));
}
} else
memcpy(copy.states, gfpz->states, sizeof(copy.states));
}
}
c0 = 0;
}
if (pass == GPZ_OPEN && gfpz->gfLayout.control_alg != GPZ_SIMPLE) {
// solutions requiring steps of opening closed circuits
// are likely to be inferior. Do not search as deep.
if (dep == 1)
pass = GPZ_EMPTY;
else {
dep--;
pass = GPZ_CLOSED;
}
} else
pass = GPZ_EMPTY; // in other words, stop.
}
return FALSE;
}
// be very careful with depth here; search time can increase exponentially
// with depth, ya know.
//
uchar find_winning_move(gridFlowPuzzle *gfpz, gridFlowPuzzle *solved, int depth, LGPoint *move, uchar breadth_first,
ulong timeout) {
int d;
if (breadth_first) {
// brain-damaged breadth-first search by successively deep
// depth first searches.
for (d = 1; d <= depth; d++)
if (find_winning_move_from(gfpz, solved, move, 0, 0, d, timeout))
return TRUE;
return FALSE;
} else {
return (find_winning_move_from(gfpz, solved, move, 0, 0, depth, timeout));
}
}
void gpz_setup_buttons(gridFlowPuzzle *gfpz) {
LGPoint bsize = {GRIDP_BTN_WD, GRIDP_BTN_HGT};
LGPoint bdims = {GRIDP_BTN_COL, GRIDP_BTN_ROW};
LGRect rct = {{GRIDP_BTN_X, GRIDP_BTN_Y}, {GRIDP_BTN_X + GRIDP_FULL_WD, GRIDP_BTN_Y + GRIDP_FULL_HGT}};
bdims.x = gfpz->gfLayout.cols;
bdims.y = gfpz->gfLayout.rows;
rct.ul.x = (MFD_VIEW_WID - (GRIDP_BTN_WD * gfpz->gfLayout.cols)) / 2 + GRIDP_X_OFFSET;
rct.ul.y = (MFD_VIEW_HGT - (GRIDP_BTN_HGT * gfpz->gfLayout.rows)) / 2 + GRIDP_Y_OFFSET;
rct.lr.x = rct.ul.x + (GRIDP_BTN_WD * gfpz->gfLayout.cols);
rct.lr.y = rct.ul.y + (GRIDP_BTN_HGT * gfpz->gfLayout.rows);
MFDBttnArrayResize(&(mfd_funcs[MFD_GRIDPANEL_FUNC].handlers[0]), &rct, bdims, bsize);
}
void gpz_4int_init(gridFlowPuzzle *gfpz, uint p1, uint p2, uint p3, uint p4) {
uint state_init[4];
int r, c, rr, cc, soff, easified = 0;
gpz_state si;
// strip reserved bits from p2
p2 &= 0xFFFF;
if (!objs[p2].active || objs[p2].obclass != CLASS_TRAP) {
memset(state_init, 0, sizeof(state_init));
} else {
ObjTrap *other = &objTraps[objs[p2].specID];
state_init[3] = other->p1;
state_init[2] = other->p2;
state_init[1] = other->p3;
state_init[0] = other->p4;
}
gfpz->gfLayout.have_won = p3 & 0xF;
p3 = p3 >> 4;
gfpz->gfLayout.src = p3 & 0xFF;
p3 = p3 >> 8;
gfpz->gfLayout.dest = p3 & 0xFF;
p3 = p3 >> 8;
gfpz->gfLayout.cols = cc = p3 & 0xF;
p3 = p3 >> 4;
gfpz->gfLayout.rows = rr = p3 & 0xF;
p3 = p3 >> 4;
gfpz->gfLayout.control_alg = p3 & 0xF;
gfpz->gfLayout.winmove_f = 0;
mfd_gridpanel_set_winmove(FALSE);
#ifdef GRIDP_AUTO_SOLVE
gfpz->gfLayout.solve_me = 0;
#endif
soff = 0;
for (r = 0; r < rr; r++) {
for (c = 0; c < cc; c++) {
si = bitarray_get(GRIDP_STATE_BITS, soff++, state_init);
si = gpz_uncharge_state(si);
if (PUZZLE_DIFFICULTY <= 1) {
switch (si) {
case GPZ_GATE:
si = GPZ_FULL;
break;
case GPZ_EMPTY:
if (((player_struct.panel_ref + (easified++)) & 3) == 0)
si = GPZ_OPEN;
break;
default:
break;
}
}
gpz_set_grid_state(gfpz, r, c, si);
}
}
if (PUZZLE_DIFFICULTY == MAX_DIFFICULTY) {
gpz_add_gate(gfpz, player_struct.panel_ref);
}
grid_primary_mfd = -1;
}
#define GPZ_WIN_MASK 1
void gpz_4int_update(gridFlowPuzzle *gfpz) {
uint init_st[4], p2;
int nodecount, node;
gpz_state s;
nodecount = gfpz->gfLayout.rows * gfpz->gfLayout.cols;
memset(init_st, 0, sizeof(init_st));
for (node = 0; node < nodecount; node++) {
s = bitarray_get(GRIDP_STATE_BITS, node, gfpz->states);
s = gpz_uncharge_state(s);
bitarray_set(GRIDP_STATE_BITS, node, s, init_st);
}
p2 = objFixtures[objs[gfpz->gfLayout.our_id].specID].p2;
p2 = p2 & 0xFFFF;
if (!objs[p2].active || objs[p2].obclass != CLASS_TRAP) {
return;
} else {
ObjTrap *other = &objTraps[objs[p2].specID];
other->p1 = init_st[3];
other->p2 = init_st[2];
other->p3 = init_st[1];
other->p4 = init_st[0];
}
if (gfpz->gfLayout.have_won) {
uint p3;
p3 = objFixtures[objs[gfpz->gfLayout.our_id].specID].p3;
p3 = p3 | GPZ_WIN_MASK;
objFixtures[objs[gfpz->gfLayout.our_id].specID].p3 = p3;
}
}
// executes a move at the given node in the given grid puzzle.
// returns the state of the given node after the move.
//
gpz_state gridpanel_move(LGPoint node, gridFlowPuzzle *gfpz) {
short r, c, sum, dif;
gpz_state s;
s = gpz_get_grid_state(gfpz, node.y, node.x);
s = gpz_uncharge_state(s);
if (s == GPZ_OPEN || s == GPZ_CLOSED) {
switch ((gpz_control)gfpz->gfLayout.control_alg) {
case GPZ_SIMPLE:
gpz_toggle_state(gfpz, node.y, node.x);
break;
case GPZ_KING:
for (r = node.y - 1; r <= node.y + 1; r++) {
for (c = node.x - 1; c <= node.x + 1; c++) {
gpz_toggle_state(gfpz, r, c);
}
}
break;
case GPZ_QUEEN:
case GPZ_ROOK:
for (r = 0; r < gfpz->gfLayout.rows; r++) {
gpz_toggle_state(gfpz, r, node.x);
}
for (c = 0; c < gfpz->gfLayout.cols; c++) {
if (c == node.x)
continue;
gpz_toggle_state(gfpz, node.y, c);
}
if (gfpz->gfLayout.control_alg == GPZ_ROOK)
break; // queen falls through to bishop.
case GPZ_BISH:
sum = node.x + node.y;
dif = node.x - node.y;
for (r = 0; r < gfpz->gfLayout.rows; r++) {
gpz_toggle_state(gfpz, r, sum - r);
gpz_toggle_state(gfpz, r, dif + r);
}
// make sure with bishop & queen to toggle center only once.
gpz_set_grid_state(gfpz, node.y, node.x, s);
gpz_toggle_state(gfpz, node.y, node.x);
break;
}
}
s = gpz_get_grid_state(gfpz, node.y, node.x);
return (s);
}
void mfd_gridpanel_set_winmove(uchar check) {
gridFlowPuzzle *gfpz;
if (check && player_struct.mfd_all_slots[MFD_INFO_SLOT] != MFD_GRIDPANEL_FUNC)
return;
gfpz = (gridFlowPuzzle *)&player_struct.mfd_access_puzzles[0];
if (!gfpz->gfLayout.have_won && player_struct.drug_status[CPTRIP(GENIUS_DRUG_TRIPLE)] > 0) {
uchar gotone;
LGPoint winner;
short diff;
if (gfpz->gfLayout.control_alg == GPZ_SIMPLE)
diff = 99;
else
diff = (PUZZLE_DIFFICULTY == MAX_DIFFICULTY) ? 2 : 3;
gotone = find_winning_move(gfpz, NULL, diff, &winner, TRUE, *tmd_ticks + EPICK_TIMEOUT);
if (gotone) {
gfpz->gfLayout.winmove_c = winner.x;
gfpz->gfLayout.winmove_r = winner.y;
}
gfpz->gfLayout.winmove_f = gotone;
mfd_notify_func(MFD_GRIDPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
}
}
uchar mfd_gridpanel_button_handler(MFD *mfd, LGPoint bttn, uiEvent *ev, void *data) {
gridFlowPuzzle *gfpz = (gridFlowPuzzle *)&player_struct.mfd_access_puzzles[0];
gpz_state s;
if (mfd->id != grid_primary_mfd)
return TRUE;
if ((ev->subtype & (MOUSE_LDOWN | UI_MOUSE_LDOUBLE)) == 0)
return TRUE;
if (gfpz->gfLayout.have_won)
return TRUE;
#ifdef GRIDP_AUTO_SOLVE
if (!gfpz->gfLayout.solve_me)
gfpz->gfLayout.solve_me = TRUE;
#endif
gfpz->gfLayout.winmove_f = 0;
s = gridpanel_move(bttn, gfpz);
s = gpz_uncharge_state(s);
#ifdef GRIDP_AUTO_SOLVE
mfd_notify_func(MFD_GRIDPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
#endif
if (s == GPZ_OPEN || s == GPZ_CLOSED) {
gpz_4int_update(gfpz);
mfd_notify_func(MFD_GRIDPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
}
mfd_gridpanel_set_winmove(FALSE);
return TRUE;
}
// Does every thing but set the slot..
void mfd_setup_gridpanel(ObjID id) {
uint p1, p2, p3, p4;
gridFlowPuzzle *gfpz = (gridFlowPuzzle *)&player_struct.mfd_access_puzzles[0];
p1 = objFixtures[objs[id].specID].p1;
p2 = objFixtures[objs[id].specID].p2;
p3 = objFixtures[objs[id].specID].p3;
p4 = objFixtures[objs[id].specID].p4;
gfpz->gfLayout.our_id = id;
gpz_4int_init(gfpz, p1, p2, p3, p4);
mfd_notify_func(MFD_GRIDPANEL_FUNC, MFD_INFO_SLOT, TRUE, MFD_ACTIVE, TRUE);
}
uchar mfd_gridpanel_handler(MFD *m, uiEvent *ev) {
extern uchar mfd_gridpanel_handler(MFD *, uiEvent *);
uiCursorStack *cs;
gridFlowPuzzle *gfpz = (gridFlowPuzzle *)&player_struct.mfd_access_puzzles[0];
int rr = gfpz->gfLayout.rows;
int cc = gfpz->gfLayout.cols;
LGRect r;
r.ul.x = (MFD_VIEW_WID - (GRIDP_BTN_WD * cc)) / 2 + GRIDP_X_OFFSET;
r.ul.y = (MFD_VIEW_HGT - (GRIDP_BTN_HGT * rr)) / 2 + GRIDP_Y_OFFSET;
r.lr.y = r.ul.y + rr * GRIDP_BTN_HGT;
r.lr.x = r.ul.x + cc * GRIDP_BTN_WD;
RECT_MOVE(&r, m->rect.ul);
uiGetRegionCursorStack(&m->reg, &cs);
if (RECT_TEST_PT(&r, ev->pos)) {
uiPushCursorOnce(cs, &gridCursor);
} else {
uiPopCursorEvery(cs, &gridCursor);
}
#ifdef EPICK_ON_CURSOR_TRY
extern uchar try_use_epick(ObjID panel, ObjID cursor_obj);
if (ev->type != UI_EVENT_MOUSE || !(ev->subtype & (MOUSE_LDOWN | UI_MOUSE_LDOUBLE)))
return FALSE;
return (try_use_epick(player_struct.panel_ref, object_on_cursor));
#else
return (FALSE);
#endif
}
#define GPZ_CHARGE_COLOR (GREEN_YELLOW_BASE + 3)
#define GPZ_BASE_CHARGE_COLOR 0x3
#define GPZ_RANGE_CHARGE_COLOR 5
#define GPZ_CHARGE_DK_COLOR 0x56
#define GPZ_SOURCE_COLOR (GREEN_YELLOW_BASE + 1)
#define GPZ_BOX_COLOR (0xb0 + 9)
#define GPZ_BACK_COLOR (0xb0 + 15)
#define GPZ_WIN_COLOR (RED_BASE + 7)
#define GPZ_NOALG_COLOR 0xA4
void wacky_int_line(short x1, short y1, short x2, short y2) {
short delt;
if (x1 == x2) {
delt = (y2 < y1) ? -1 : 1;
for (; y1 != y2 + delt; y1 += delt) {
ss_set_pixel(GPZ_BASE_CHARGE_COLOR + (256 - x1 - y1) % GPZ_RANGE_CHARGE_COLOR, x1, y1);
}
} else {
delt = (x2 < x1) ? -1 : 1;
for (; x1 != x2 + delt; x1 += delt) {
ss_set_pixel(GPZ_BASE_CHARGE_COLOR + (256 - x1 - y1) % GPZ_RANGE_CHARGE_COLOR, x1, y1);
}
}
}
char *grid_help_string(gridFlowPuzzle *gfpz, char *buf, int siz) {
int l;
char *ret = buf;
if (gfpz->gfLayout.have_won)
get_string(REF_STR_PanelSolved, buf, siz);
else {
get_string(REF_STR_GridPuzzHelp, buf, siz);
l = strlen(buf);
buf += l;
siz -= l;
if (player_struct.drug_status[CPTRIP(GENIUS_DRUG_TRIPLE)] > 0)
get_string(REF_STR_GridPuzzSide0 + gfpz->gfLayout.control_alg, buf, siz);
else
get_string(REF_STR_GridPuzzSideMay, buf, siz);
}
return (ret);
}
// initializes color look-up table to identity function
void id_clut_init(uchar *clut) {
int i;
for (i = 0; i < 256; i++) {
clut[i] = i;
}
}
// I remember the feeling
// my hands in your hair
void mfd_gridpanel_expose(MFD *mfd, ubyte control) {
void mfd_clear_view(void);
int rr, cc, r, c, x, y, ulx, uly, lry;
int bcolor;
short sc, sr, p;
gpz_state s, nearstate;
gridFlowPuzzle *gfpz = (gridFlowPuzzle *)&player_struct.mfd_access_puzzles[0];
uchar full = control & MFD_EXPOSE_FULL;
#ifdef SVGA_SUPPORT
// Whatta hack!
if (convert_use_mode)
full = TRUE;
#endif
rr = gfpz->gfLayout.rows;
cc = gfpz->gfLayout.cols;
ulx = (MFD_VIEW_WID - (GRIDP_BTN_WD * cc)) / 2 + GRIDP_X_OFFSET;
uly = (MFD_VIEW_HGT - (GRIDP_BTN_HGT * rr)) / 2 + GRIDP_Y_OFFSET;
if (control == 0) {
uiCursorStack *cs;
uiGetRegionCursorStack(&mfd->reg, &cs);
uiPopCursorEvery(cs, &gridCursor);
}
if (control & MFD_EXPOSE) // Time to draw stuff
{
short dr, dc;
uchar win, primary, winblink = FALSE;
if (gfpz->gfLayout.winmove_f) {
mfd_notify_func(MFD_GRIDPANEL_FUNC, MFD_INFO_SLOT, FALSE, MFD_ACTIVE, FALSE);
}
if ((full && grid_primary_mfd < 0) || player_struct.mfd_current_slots[grid_primary_mfd] != MFD_INFO_SLOT) {
full = TRUE; // if we weren't full exposing before, we are now
grid_primary_mfd = mfd->id;
}
primary = (mfd->id == grid_primary_mfd);
if (primary) {
id_clut_init(grid_help_clut);
if (full)
gpz_setup_buttons(gfpz);
win = gpz_propogate_charge_n_check(gfpz);
if ((gfpz->gfLayout.have_won == 0) && win) {
accesspanel_trigger(player_struct.panel_ref);
gfpz->gfLayout.have_won = 1;
gpz_4int_update(gfpz);
}
}
mfd_clear_rects();
gr_push_canvas(pmfd_canvas);
ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
if (!primary) {
char buf[120];
grid_help_string(gfpz, buf, 120);
mfd_clear_view();
draw_help_text(buf, FALSE, gfpz);
gr_pop_canvas();
mfd_update_rects(mfd);
return;
}
#ifdef PUZZ_DIFF_NOHELP
// set color of wires to indicate control algorithm, unless we're
// on hardest difficulty.
if (PUZZLE_DIFFICULTY < MAX_DIFFICULTY)
bcolor = gpz_base_colors[gfpz->gfLayout.control_alg];
else
bcolor = GPZ_NOALG_COLOR;
#else
bcolor = gpz_base_colors[gfpz->gfLayout.control_alg];
#endif
if (gfpz->gfLayout.have_won)
full = TRUE;
lry = uly + rr * GRIDP_BTN_HGT;
if (full) {
load_res_bitmap_cursor(&gridCursor, &gridCursorbm, REF_IMG_RookSymbol + gfpz->gfLayout.control_alg, FALSE);
mfd_clear_view();
}
ss_safe_set_cliprect(ulx, uly, ulx + cc * GRIDP_BTN_WD, lry + 7);
draw_res_bm(REF_IMG_CircuitBack, ulx, uly);
ss_safe_set_cliprect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
gr_set_fcolor(GPZ_BOX_COLOR);
ss_box(ulx - 1, uly - 1, ulx + cc * GRIDP_BTN_WD + 1, lry + 1);
gr_set_fcolor(bcolor);
ss_box(ulx - 2, uly - 2, ulx + cc * GRIDP_BTN_WD + 2, lry + 2);
mfd_add_rect(ulx - 2, uly - 2, ulx + cc * GRIDP_BTN_WD + 2, lry + 2);
ss_box(ulx - 2, lry + 3, ulx + cc * GRIDP_BTN_WD + 2, lry + 7);
gr_set_fcolor(ACCESSP_SCORE_COL);
ss_rect(ulx - 1, lry + 4, ulx + 1 + gpz_doneness(gfpz) * cc * GRIDP_BTN_WD / UCHAR_MAX, lry + 6);
mfd_add_rect(ulx - 2, lry + 3, ulx + cc * GRIDP_BTN_WD + 2, lry + 7);
if (full) {
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.src, &sr, &sc);
p = (gfpz->gfLayout.src >> 3) & 3;
switch (p) {
case 0:
sr = -1;
break;
case 1:
sr = rr;
break;
case 2:
sc = -1;
break;
case 3:
sc = cc;
break;
}
x = sc * GRIDP_BTN_WD + ulx;
y = sr * GRIDP_BTN_HGT + uly;
gr_set_fcolor(GREEN_YELLOW_BASE + 2);
ss_box(x + 1, y + 1, x + GRIDP_BTN_WD - 1, y + GRIDP_BTN_HGT - 1);
gr_set_fcolor(5);
ss_box(x + 2, y + 2, x + GRIDP_BTN_WD - 2, y + GRIDP_BTN_HGT - 2);
gr_set_fcolor(7);
ss_rect(x + 3, y + 3, x + GRIDP_BTN_WD - 3, y + GRIDP_BTN_HGT - 3);
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.dest, &sr, &sc);
p = (gfpz->gfLayout.dest >> 3) & 3;
switch (p) {
case 0:
sr = -1;
break;
case 1:
sr = rr;
break;
case 2:
sc = -1;
break;
case 3:
sc = cc;
break;
}
x = sc * GRIDP_BTN_WD + ulx;
y = sr * GRIDP_BTN_HGT + uly;
gr_set_fcolor(bcolor);
ss_box(x + 1, y + 1, x + GRIDP_BTN_WD - 1, y + GRIDP_BTN_HGT - 1);
if (gfpz->gfLayout.have_won)
gr_set_fcolor(GPZ_CHARGE_COLOR);
else
gr_set_fcolor(GPZ_BACK_COLOR);
ss_rect(x + 2, y + 2, x + GRIDP_BTN_WD - 2, y + GRIDP_BTN_HGT - 2);
mfd_add_rect(0, 0, MFD_VIEW_WID, MFD_VIEW_HGT);
}
for (r = 0; r < rr; r++) {
for (c = 0; c < cc; c++) {
x = c * GRIDP_BTN_WD + ulx;
y = r * GRIDP_BTN_HGT + uly;
s = gpz_get_grid_state(gfpz, r, c);
winblink = (!gfpz->gfLayout.have_won && gfpz->gfLayout.winmove_f && gfpz->gfLayout.winmove_c == c &&
gfpz->gfLayout.winmove_r == r && (*tmd_ticks) & (1 << 7));
if (gpz_is_charged(s)) {
short sc, sr;
gpz_perimeter_to_grid(gfpz, gfpz->gfLayout.src, &sr, &sc);
if (sc == c && sr == r)
gr_set_fcolor(GPZ_SOURCE_COLOR);
else
gr_set_fcolor(GPZ_CHARGE_COLOR);
} else
gr_set_fcolor(bcolor);
if (winblink)
gr_set_fcolor(GPZ_WIN_COLOR);
switch (s) {
case GPZ_EMPTY:
break;
case GPZ_FULL_CHARGED:
case GPZ_FULL:
ss_rect(x, y, x + GRIDP_BTN_WD, y + GRIDP_BTN_HGT);
break;
case GPZ_OPEN:
ss_int_line(x + 1, y + 1, x + GRIDP_BTN_WD - 2, y + GRIDP_BTN_HGT - 2);
ss_int_line(x + GRIDP_BTN_WD - 2, y + 1, x + 1, y + GRIDP_BTN_HGT - 2);
if (s == GPZ_OPEN)
break;
case GPZ_CLOSED:
case GPZ_CLOSED_CHARGED:
gr_set_fcolor(winblink ? GPZ_WIN_COLOR : bcolor);
for (dr = -1; dr <= 1; dr++) {
for (dc = -1; dc <= 1; dc++) {
if (dr != 0 && dc != 0)
continue;
nearstate = gpz_get_grid_state(gfpz, r + dr, c + dc);
#ifndef DRAW_GRID_PLUSSES
if (nearstate == GPZ_EMPTY)
continue;
#endif
ss_int_line(x + (GRIDP_BTN_WD / 2), y + (GRIDP_BTN_HGT / 2),
x + (dc + 1) * (GRIDP_BTN_WD / 2), y + (dr + 1) * (GRIDP_BTN_HGT / 2));
}
}
gr_set_fcolor(winblink ? GPZ_WIN_COLOR : GPZ_CHARGE_COLOR);
for (dr = -1; dr <= 1; dr++) {
for (dc = -1; dc <= 1; dc++) {
if (dr != 0 && dc != 0)
continue;
nearstate = gpz_get_grid_state(gfpz, r + dr, c + dc);
if (gpz_is_charged(nearstate) || (gpz_is_charged(s) && nearstate == GPZ_GATE)) {
wacky_int_line(x + (GRIDP_BTN_WD / 2), y + (GRIDP_BTN_HGT / 2),
x + (dc + 1) * (GRIDP_BTN_WD / 2), y + (dr + 1) * (GRIDP_BTN_HGT / 2));
}
}
}
break;
#ifdef GPZ_GATES
case GPZ_GATE:
ss_box(x, y, x + GRIDP_BTN_WD, y + GRIDP_BTN_HGT);
gr_set_fcolor(gpz_dk_colors[gfpz->gfLayout.control_alg]);
ss_rect(x + 1, y + 1, x + GRIDP_BTN_WD - 1, y + GRIDP_BTN_HGT - 1);
break;
case GPZ_GATE_CHARGED:
ss_box(x, y, x + GRIDP_BTN_WD, y + GRIDP_BTN_HGT);
gr_set_fcolor(GPZ_CHARGE_DK_COLOR);
ss_rect(x + 1, y + 1, x + GRIDP_BTN_WD - 1, y + GRIDP_BTN_HGT - 1);
break;
#endif
}
}
}
gr_pop_canvas();
} else {
ObjID obj = panel_ref_unexpose(mfd->id, MFD_GRIDPANEL_FUNC);
if (obj != OBJ_NULL) {
objs[obj].info.current_frame = 0;
}
}
// Now that we've popped the canvas, we can send the
// updated mfd to screen
mfd_update_rects(mfd);
#ifdef GRIDP_AUTO_SOLVE
{
uiEvent ev;
LGPoint bttn;
if (!gfpz->gfLayout.have_won) {
bttn.x = rand() % cc;
bttn.y = rand() % rr;
ev.subtype = MOUSE_LDOWN;
mfd_gridpanel_button_handler(NULL, bttn, &ev, NULL);
}
}
#endif
}