/* 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 }