a5ec8db5f7
build / build (push) Failing after 5s
dependency (SDL2, SDL2_mixer, fluidsynth-lite, a MIDI soundfont) prebuilt, so compiling the engine needs no network access - just the image and the engine source. Add res/assets/extract_assets.sh to pull the game's data files out of a purchased GOG installer, and a Makefile that assembles a runnable dist/ from the two. Also add `make package`, which builds a redistributable tarball that omits the proprietary game assets (shipping res/GET_ASSETS.txt instead) plus license information for both the MIT tooling and the GPLv3 engine, and a Gitea Actions workflow that builds and publishes it to dl.ladkau.de on every push.
1941 lines
69 KiB
C
1941 lines
69 KiB
C
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
/*
|
|
* $Source: r:/prj/cit/src/RCS/mfdpanel.c $
|
|
* $Revision: 1.75 $
|
|
* $Author: dc $
|
|
* $Date: 1994/11/14 03:30:50 $
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#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
|
|
}
|