// $Id$ // Minesweeper, the game // See readme.txt for instructions on installation, configuration, and use "use strict"; const title = "Synchronet Minesweeper"; const ini_section = "minesweeper"; const REVISION = "$Revision$".split(' ')[1]; const author = "Digital Man"; const header_height = 4; const winners_list = js.exec_dir + "winners.jsonl"; const losers_list = js.exec_dir + "losers.jsonl"; const help_file = js.exec_dir + "minesweeper.hlp"; const welcome_image = js.exec_dir + "welcome.bin"; const mine_image = js.exec_dir + "mine.bin"; const winner_image = js.exec_dir + "winner.bin"; const boom_image = js.exec_dir + "boom?.bin"; const loser_image = js.exec_dir + "loser.bin"; const max_difficulty = 5; const min_mine_density = 0.10; const mine_density_multiplier = 0.025; const char_flag = '\x01r\x9f'; const char_badflag = '\x01r\x01h!'; const char_unsure = '\x01r?'; const attr_empty = '\x01b'; //\x01h'; const char_empty = attr_empty + '\xFA'; const char_covered = attr_empty +'\xFE'; const char_mine = '\x01r\x01h\xEB'; const char_detonated_mine = '\x01r\x01h\x01i\*'; const attr_count = "\x01c"; const winner_subject = "Winner"; const selectors = ["()", "[]", "<>", "{}", "--", " "]; require("sbbsdefs.js", "K_NONE"); require("mouse_getkey.js", "mouse_getkey"); if(BG_HIGH === undefined) BG_HIGH = 0x400; var options=load({}, "modopts.js", ini_section); if(!options) options = {}; if(!options.timelimit) options.timelimit = 60; // minutes if(!options.timewarn) options.timewarn = 5; if(!options.winners) options.winners = 20; if(!options.selector) options.selector = 0; if(!options.highlight) options.highlight = true; if(!options.boom_delay) options.boom_delay = 1000; if(!options.image_delay) options.image_delay = 1500; if(!options.splash_delay) options.splash_delay = 500; if(!options.sub) options.sub = load({}, "syncdata.js").find(); var userprops = bbs.mods.userprops; if(!userprops) userprops = load(bbs.mods.userprops = {}, "userprops.js"); var json_lines = bbs.mods.json_lines; if(!json_lines) json_lines = load(bbs.mods.json_lines = {}, "json_lines.js"); var game = {}; var board = []; var selected = {x:0, y:0}; var gamewon = false; var gameover = false; var new_best = false; var win_rank = false; var view_details = false; var cell_width; // either 3 or 2 var selector = userprops.get(ini_section, "selector", options.selector); var highlight = userprops.get(ini_section, "highlight", options.highlight); var difficulty = userprops.get(ini_section, "difficulty", options.difficulty); var best = null; log(LOG_DEBUG, title + " options: " + JSON.stringify(options)); var ansiterm = bbs.mods.ansiterm_lib; if(!ansiterm) ansiterm = bbs.mods.ansiterm_lib = load({}, "ansiterm_lib.js"); function mouse_enable(enable) { if(console.term_supports(USER_ANSI)) { ansiterm.send('mouse', enable ? 'set' : 'clear', 'x10_compatible'); ansiterm.send('mouse', enable ? 'set' : 'clear', 'extended_coord'); } } function show_image(filename, fx, delay) { var dir = directory(filename); filename = dir[random(dir.length)]; var Graphic = load({}, "graphic.js"); var sauce_lib = load({}, "sauce_lib.js"); var sauce = sauce_lib.read(filename); if(delay === undefined) delay = options.image_delay; if(sauce && sauce.datatype == sauce_lib.defs.datatype.bin) { try { var graphic = new Graphic(sauce.cols, sauce.rows); graphic.load(filename); if(fx && graphic.revision >= 1.82) graphic.drawfx('center', 'center'); else graphic.draw('center', 'center'); sleep(delay); } catch(e) { log(LOG_DEBUG, e); } } } function countmines(x, y) { var count = 0; for(var yi = y - 1; yi <= y + 1; yi++) for(var xi = x - 1; xi <= x + 1; xi++) if((yi != y || xi != x) && mined(xi, yi)) count++; return count; } function place_mines() { var mined = new Array(game.height * game.width); for(var i = 0; i < game.mines; i++) mined[i] = true; for(var i = 0; i < game.mines; i++) { var r; do { r = random(game.height * game.width); } while (r == (selected.y * game.width) + selected.x); var tmp = mined[i]; mined[i] = mined[r]; mined[r] = tmp; } for(var y = 0; y < game.height; y++) { for(var x = 0; x < game.width; x++) { if(mined[(y * game.width) + x]) board[y][x].mine = true; } } for(var y = 0; y < game.height; y++) { for(var x = 0; x < game.width; x++) { board[y][x].count = countmines(x, y); } } } function isgamewon() { var covered = 0; for(var y = 0; y < game.height; y++) { for(var x = 0; x < game.width; x++) { if(board[y][x].covered && !board[y][x].mine) covered++; } } if(covered === 0) { game.end = Date.now() / 1000; if(options.sub) { var msgbase = new MsgBase(options.sub); var hdr = { to: title, from: user.alias, subject: winner_subject }; game.name = user.alias; game.md5 = md5_calc(JSON.stringify(game)); game.name = undefined; var body = lfexpand(JSON.stringify(game, null, 1)); body += "\r\n--- " + js.exec_file + " " + REVISION + "\r\n"; if(!msgbase.save_msg(hdr, body)) alert("Error saving message to: " + options.sub); msgbase.close(); game.md5 = undefined; } var level = calc_difficulty(game); if(!best || !best[level] || calc_time(game) < calc_time(best[level])) { new_best = true; if(!best) best = {}; delete best[level]; best[level] = game; } game.name = user.alias; var result = json_lines.add(winners_list, game); if(result !== true) { alert(result); console.pause(); } gamewon = true; gameover = true; draw_board(false); show_image(winner_image, true, /* delay: */0); var start = Date.now(); var winners = get_winners(Math.ceil(level)); for(var i = 0; i < options.winners; i++) { if(winners[i].name == user.alias && winners[i].end == game.end) { win_rank = i + 1; break; } } var now = Date.now(); if(now - start < options.image_delay) sleep(options.image_delay - (now - start)); return true; } return false; } function lostgame(cause) { gameover = true; game.end = Date.now() / 1000; game.name = user.alias; game.cause = cause; json_lines.add(losers_list, game); draw_board(true); show_image(boom_image, false, options.boom_delay); show_image(loser_image, true); } function calc_difficulty(game) { const game_cells = game.height * game.width; const mine_density = game.mines / game_cells; const level = 1 + Math.ceil((mine_density - min_mine_density) / mine_density_multiplier); const target = target_height(level); const target_cells = target * target; const bias = (1 - (game_cells / target_cells)) * 1.5; return level - bias; } function calc_time(game) { return game.end - game.start; } function compare_won_game(g1, g2) { var diff = calc_difficulty(g2) - calc_difficulty(g1); if(diff) return diff; return calc_time(g1) - calc_time(g2); } function secondstr(t, frac) { if(frac) return format("%2u:%06.3f", Math.floor(t/60), t%60); return format("%2u:%02u", Math.floor(t/60), Math.floor(t%60)); } function get_winners(level) { var list = json_lines.get(winners_list); if(typeof list != 'object') list = []; if(options.sub) { var msgbase = new MsgBase(options.sub); if(msgbase.get_index !== undefined && msgbase.open()) { var to_crc = crc16_calc(title.toLowerCase()); var subj_crc = crc16_calc(winner_subject.toLowerCase()); var index = msgbase.get_index(); for(var i = 0; index && i < index.length; i++) { var idx = index[i]; if((idx.attr&MSG_DELETE) || idx.to != to_crc || idx.subject != subj_crc) continue; var hdr = msgbase.get_msg_header(true, idx.offset); if(!hdr) continue; if(!hdr.from_net_type || hdr.to != title || hdr.subject != winner_subject) continue; var body = msgbase.get_msg_body(hdr, false, false, false); if(!body) continue; body = body.split("\n===", 1)[0]; body = body.split("\n---", 1)[0]; var obj; try { obj = JSON.parse(strip_ctrl(body)); } catch(e) { log(LOG_INFO, title + " " + e + ": " + options.sub + " msg " + hdr.number); continue; } if(!obj.md5) // Ignore old test messages continue; obj.name = hdr.from; var md5 = obj.md5; obj.md5 = undefined; var calced = md5_calc(JSON.stringify(obj)); if(calced == md5) { obj.net_addr = hdr.from_net_addr; // Not included in MD5 sum list.push(obj); } else { log(LOG_INFO, title + " MD5 not " + calced + " in: " + options.sub + " msg " + hdr.number); } } msgbase.close(); } } if(level) list = list.filter(function (obj) { var difficulty = calc_difficulty(obj); return (difficulty <= level && difficulty > level - 1); }); list.sort(compare_won_game); return list; } function show_winners(level) { console.clear(); console.aborted = false; console.attributes = YELLOW|BG_BLUE|BG_HIGH; var str = " " + title + " Top " + options.winners; if(level) str += " Level " + level; str += " Winners "; console_center(str); console.attributes = LIGHTGRAY; var list = get_winners(level); if(!list.length) { alert("No " + (level ? ("level " + level + " ") : "") + "winners yet!"); return; } console.attributes = WHITE; console.print(format(" %-25s%-15s Lvl Time WxHxMines Date\r\n", "User", "")); var count = 0; var displayed = 0; var last_level = 0; for(var i = 0; i < list.length && displayed < options.winners && !console.aborted; i++) { var game = list[i]; var difficulty = calc_difficulty(game); if(Math.ceil(difficulty) != Math.ceil(last_level)) { last_level = difficulty; count = 0; } else { if(!level && difficulty > 1.0 && count >= options.winners / max_difficulty) continue; } if(displayed&1) console.attributes = LIGHTCYAN; else console.attributes = BG_CYAN; console.print(format("%3u %-25.25s%-15.15s %1.2f %s %3ux%2ux%-3u %s\x01>\r\n" ,count + 1 ,game.name ,game.net_addr ? ('@'+game.net_addr) : '' ,difficulty ,secondstr(calc_time(game), true) ,game.width ,game.height ,game.mines ,system.datestr(game.end) )); count++; displayed++; } console.attributes = LIGHTGRAY; } function compare_game(g1, g2) { return g2.start - g1.start; } function show_log() { console.clear(); console.attributes = YELLOW|BG_BLUE|BG_HIGH; console_center(" " + title + " Log "); console.attributes = LIGHTGRAY; var winners = json_lines.get(winners_list); if(typeof winners != 'object') winners = []; var losers = json_lines.get(losers_list); if(typeof losers != 'object') losers = []; var list = losers.concat(winners); if(!list.length) { alert("No winners or losers yet!"); return; } console.attributes = WHITE; console.print(format("Date %-25s Lvl Time WxHxMines Rev Result\r\n", "User", "")); list.sort(compare_game); for(var i = 0; i < list.length && !console.aborted; i++) { var game = list[i]; if(i&1) console.attributes = LIGHTCYAN; else console.attributes = BG_CYAN; console.print(format("%s %-25s %1.2f %s %3ux%2ux%-3u %3s %s\x01>\x01n\r\n" ,system.datestr(game.end) ,game.name ,calc_difficulty(game) ,secondstr(calc_time(game), true) ,game.width ,game.height ,game.mines ,game.rev ? game.rev : '' ,game.cause ? ("Lost: " + format("%.4s", game.cause)) : "Won" )); } console.attributes = LIGHTGRAY; } function show_best() { console.clear(LIGHTGRAY); console.attributes = YELLOW|BG_BLUE|BG_HIGH; console_center(" Your " + title + " Personal Best Wins "); console.attributes = LIGHTGRAY; var wins = []; for(var i in best) wins.push(best[i]); wins.reverse(); // Display newest first console.attributes = WHITE; console.print("Date Lvl Time WxHxMines Rev\r\n"); for(var i in wins) { var game = wins[i]; if(i&1) console.attributes = LIGHTCYAN; else console.attributes = BG_CYAN; console.print(format("%s %1.2f %s %3ux%2ux%-3u %s\x01>\x01n\r\n" ,system.datestr(game.end) ,calc_difficulty(game) ,secondstr(calc_time(game), true) ,game.width, game.height, game.mines ,game.rev ? game.rev : '')); } } function cell_val(x, y) { if(gameover && board[y][x].mine && (!view_details || board[y][x].detonated)) { if(board[y][x].detonated) return char_detonated_mine; return char_mine; } if(board[y][x].unsure) return char_unsure; if(board[y][x].flagged) { if(gameover && !board[y][x].mine) return char_badflag; return char_flag; } if((view_details || !gameover) && board[y][x].covered) return char_covered; if(board[y][x].count) return attr_count + board[y][x].count; return char_empty; } function highlighted(x, y) { if(selected.x == x && selected.y == y) return true; if(!highlight) return false; return (selected.x == x - 1 || selected.x == x || selected.x == x + 1) && (selected.y == y -1 || selected.y == y || selected.y == y + 1); } function draw_cell(x, y) { console.attributes = LIGHTGRAY; var val = cell_val(x, y); var left = " "; var right = " "; if(game.start && !gameover && !board[selected.y][selected.x].covered && board[selected.y][selected.x].count && highlighted(x, y)) console.attributes |= HIGH; if(selected.x == x && selected.y == y) { left = "\x01n\x01h" + selectors[selector%selectors.length][0]; right = "\x01n\x01h" + selectors[selector%selectors.length][1]; } console.print(left + val + right); } // Return total number of surrounding flags function countflagged(x, y) { var count = 0; for(var yi = y - 1; yi <= y + 1; yi++) for(var xi = x - 1; xi <= x + 1; xi++) if((yi != y || xi != x) && flagged(xi, yi)) count++; return count; } // Return total number of surrounding unflagged-covered cells function countunflagged(x, y) { var count = 0; for(var yi = y - 1; yi <= y + 1; yi++) for(var xi = x - 1; xi <= x + 1; xi++) if((yi != y || xi != x) && unflagged(xi, yi)) count++; return count; } function totalflags() { if(!game.start) return 0; var flags = 0; for(var y = 0; y < game.height; y++) { for(var x = 0; x < game.width; x++) { if(board[y][x].flagged) flags++; } } return flags; } function show_title() { console.attributes = YELLOW|BG_BLUE|BG_HIGH; console_center(title + " " + REVISION); } function draw_border() { const margin = Math.floor((console.screen_columns - (game.width * cell_width)) / 2); console.creturn(); console.attributes = LIGHTGRAY; console.cleartoeol(); console.attributes = BG_BLUE|BG_HIGH; console.right(margin - 1); console.print(' '); if(game.width * cell_width >= console.screen_columns - 3) { console.attributes = BG_BLUE; console.cleartoeol(); } else { console.right((game.width * cell_width) + !(cell_width&1)); console.print(' '); } console.creturn(); console.attributes = LIGHTGRAY; } // A non-destructive console.center() replacement function console_center(text) { console.right((console.screen_columns - console.strlen(text)) / 2); console.print(text); console.crlf(); } // global state variable used by draw_board() var cmds_shown; var top; function draw_board(full) { const margin = Math.floor((console.screen_columns - (game.width * cell_width)) / 2); top = Math.floor(Math.max(0, (console.screen_rows - (header_height + game.height)) - 1) / 2); console.line_counter = 0; console.home(); if(full) { console.down(top); console.right(margin - 1); console.attributes = BG_BLUE|BG_HIGH; console.print('\xDF'); var width = (game.width * cell_width) + 1 + !(cell_width&1); for(var x = 1; x < width; x++) console.print(' '); console.print('\xDF'); console.creturn(); show_title(); draw_border(); } else console.down(top + 1); if(gamewon) { console.attributes = YELLOW|BLINK; var blurb = "Winner! Cleared in"; if(win_rank) blurb = "Rank " + win_rank + " Winner in"; else if(new_best) blurb = "Personal Best Time"; console_center(blurb + " " + secondstr(calc_time(game), true).trim()); } else if(gameover && !view_details) { console.attributes = RED|HIGH|BLINK; console_center((calc_time(game) < options.timelimit * 60 ? "" : "Time-out: ") + "Game Over"); } else { var elapsed = 0; if(game.start) { if(gameover) elapsed = game.end - game.start; else elapsed = (Date.now() / 1000) - game.start; } var timeleft = Math.max(0, (options.timelimit * 60) - elapsed); console.attributes = LIGHTCYAN; console_center(format("%2d Mines Lvl %1.2f %s%s " , game.mines - totalflags(), calc_difficulty(game) , game.start && !gameover && (timeleft / 60) <= options.timewarn ? "\x01r\x01h\x01i" : "" , secondstr(timeleft) )); } var cmds = ""; if(gameover) { if(!gamewon) cmds += "\x01n\x01h\x01~D\x01nisplay "; } else { if(!board[selected.y][selected.x].covered) { if(can_chord(selected.x, selected.y)) cmds += "\x01h\x01k\x01~Dig \x01n\x01h\x01~C\x01nhord "; else cmds += "\x01h\x01k\x01~Dig Flag "; } else cmds += "\x01h\x01~D\x01nig \x01h\x01~F\x01nlag "; } cmds += "\x01n\x01h\x01~N\x01new \x01h\x01~Q\x01nuit"; if(full || cmds !== cmds_shown) { console.clear_hotspots(); draw_border(); console.attributes = LIGHTGRAY; console_center(cmds); cmds_shown = cmds; draw_border(); cmds = "\x01h\x01~W\x01ninners \x01h\x01~L\x01nog "; if(best) cmds += "\x01h\x01~B\x01nest "; cmds += "\x01h\x01~H\x01nelp" console_center(cmds); } else if(!console.term_supports(USER_ANSI)) { console.creturn(); console.down(2); } var redraw_selection = false; for(var y = 0; y < game.height; y++) { if(full) draw_border(); for(var x = 0; x < game.width; x++) { if(full || board[y][x].changed !== false) { if(console.term_supports(USER_ANSI)) console.gotoxy((x * cell_width) + margin + 1, header_height + y + top + 1); else { console.creturn(); console.right((x * cell_width) + margin); } draw_cell(x, y); if(cell_width < 3) redraw_selection = true; } board[y][x].changed = false; } if(y + 1 < game.height && (full || !console.term_supports(USER_ANSI))) console.down(); console.line_counter = 0; } var height = game.height; if(full) { if(game.height + header_height < console.screen_rows) { height++; console.down(); console.creturn(); console.right(margin - 1); console.attributes = BG_BLUE|BG_HIGH; console.print('\xDC'); for(var x = 0; x < (game.width * cell_width) + !(cell_width&1); x++) console.print(' '); console.print('\xDC'); } console.attributes = LIGHTGRAY; } if(redraw_selection) { // We need to draw/redraw the selected cell last in this case if(console.term_supports(USER_ANSI)) console.gotoxy(margin + (selected.x * cell_width) + 1, header_height + selected.y + top + 1); else { console.up(height - (selected.y + 1)); console.creturn(); console.right(margin + (selected.x * cell_width)); } draw_cell(selected.x, selected.y); console.left(2); } console.gotoxy(margin + (selected.x * cell_width) + 2, header_height + selected.y + top + 1); } function mined(x, y) { return board[y] && board[y][x] && board[y][x].mine; } function start_game() { place_mines(); game.start = Date.now() / 1000; } function uncover_cell(x, y) { if(!board[y] || !board[y][x]) return false; if(board[y][x].flagged) return false; board[y][x].covered = false; board[y][x].unsure = false; board[y][x].changed = true; if(!mined(x, y)) return false; board[y][x].detonated = true; return true; } function flagged(x, y) { return board[y] && board[y][x] && board[y][x].flagged; } function unflagged(x, y) { return board[y] && board[y][x] && board[y][x].covered && !board[y][x].flagged; } // Returns true if mined (game over) function uncover(x, y) { if(!game.start) start_game(); if(!board[y] || !board[y][x] || board[y][x].flagged || !board[y][x].covered) return; if(uncover_cell(x, y)) return true; if(board[y][x].count) return false; for(var yi = y - 1; yi <= y + 1; yi++) for(var xi = x - 1; xi <= x + 1; xi++) if((yi != y || xi != x) && !mined(xi, yi)) uncover(xi, yi); return false; } function can_chord(x, y) { return !board[y][x].covered && board[y][x].count && board[y][x].count == countflagged(x, y) && countunflagged(x, y); } // Return true if mine denotated function chord(x, y) { for(var yi = y - 1; yi <= y + 1; yi++) for(var xi = x - 1; xi <= x + 1; xi++) if((yi != y || xi != x) && uncover(xi, yi)) return true; return false; } function get_difficulty(all) { console.creturn(); console.cleartoeol(); draw_border(); console.attributes = WHITE; console.clear_hotspots(); var lvls = ""; for(var i = 1; i <= max_difficulty; i++) lvls += "\x01~" + i; if(all) { console.right((console.screen_columns - 20) / 2); console.print(format("Level (%s) [\x01~All]: ", lvls)); var key = console.getkeys("QA", max_difficulty); if(key == 'A') return 0; if(key == 'Q') return -1; return key; } console.right((console.screen_columns - 24) / 2); console.print(format("Difficulty Level (%s): ", lvls)); return console.getnum(max_difficulty); } function target_height(difficulty) { return 5 + (difficulty * 5); } function select_middle() { selected.x = Math.floor(game.width / 2) - !(game.width&1); selected.y = Math.floor(game.height / 2) - !(game.height&1); } function init_game(difficulty) { console.line_counter = 0; console.clear(LIGHTGRAY); gamewon = false; gameover = false; new_best = false; win_rank = false; view_details = false; game = { rev: REVISION }; game.height = target_height(difficulty); game.width = game.height; game.height = Math.min(game.height, console.screen_rows - header_height); game.width += game.width - game.height; if(game.width > 10 && (game.width * 3) + 2 > (console.screen_columns - 20)) cell_width = 2; else cell_width = 3; game.width = Math.min(game.width, Math.floor((console.screen_columns - 5) / cell_width)); game.mines = Math.floor((game.height * game.width) * (min_mine_density + ((difficulty - 1) * mine_density_multiplier))); log(LOG_INFO, title + " new level " + difficulty + " board WxHxM: " + format("%u x %u x %u", game.width, game.height, game.mines)); game.start = 0; // init board: board = []; for(var y = 0; y < game.height; y++) { board[y] = new Array(game.width); for(var x = 0; x < game.width; x++) { board[y][x] = { covered: true }; } } select_middle(); return difficulty; } function change(x, y) { if(y) { if(x) board[y - 1][x - 1].changed = true; board[y - 1][x].changed = true; if(board[y - 1][x + 1]) board[y - 1][x + 1].changed = true; } if(x) board[y][x - 1].changed = true; board[y][x].changed = true; if(board[y][x + 1]) board[y][x + 1].changed = true; if(board[y + 1]) { if(x) board[y + 1][x - 1].changed = true; board[y + 1][x].changed = true; if(board[y + 1][x + 1]) board[y + 1][x + 1].changed = true; } } function screen_to_board(mouse) { const margin = Math.floor((console.screen_columns - (game.width * cell_width)) / 2); top = Math.floor(Math.max(0, (console.screen_rows - (header_height + game.height)) - 1) / 2); var x = (mouse.x - margin + (cell_width - 2)) / cell_width; if (Math.floor(x) !== x) return false; mouse.x = x; mouse.y = (mouse.y - top - header_height); if (mouse.x < 1 || mouse.y < 1 || mouse.x > game.width || mouse.y > game.height) return false; return true; } function play() { console.clear(); var start = Date.now(); show_image(welcome_image, /* fx: */false, /* delay: */0); // Find "personal best" wins var winners = json_lines.get(winners_list); for(var i in winners) { var win = winners[i]; if(win.name !== user.alias) continue; var level = calc_difficulty(win); if(best && best[level] && calc_time(best[level]) < calc_time(win)) continue; if(!best) best = {}; delete best[level]; best[level] = win; } var now = Date.now(); if(now - start < options.splash_delay) sleep(options.splash_delay - (now - start)); show_image(mine_image, true); sleep(options.splash_delay); init_game(difficulty); draw_board(true); var full_redraw = false; mouse_enable(true); while(bbs.online) { if(!gameover && game.start && Date.now() - (game.start * 1000) >= options.timelimit * 60 * 1000) { lostgame("timeout"); draw_board(true); } var mk = mouse_getkey(K_NONE, 1000, true); var key = mk.key; if (mk.mouse !== null) { if ((!mk.mouse.press) || mk.mouse.motion || !screen_to_board(mk.mouse)) { key = null; } else { switch(mk.mouse.button) { case 0: key = 'D'; break; case 1: key = 'C'; break; case 2: key = 'F'; break; default: key = null; } } } if(key === '' || key === null) { if(game.start && !gameover) draw_board(false); // update the time display continue; } change(selected.x, selected.y); if (mk.mouse !== null) { selected.x = mk.mouse.x - 1; selected.y = mk.mouse.y - 1; } switch(key.toUpperCase()) { case KEY_HOME: if(!gameover) selected.x = 0; break; case KEY_END: if(!gameover) selected.x = game.width - 1; break; case KEY_PAGEUP: if(!gameover) selected.y = 0; break; case KEY_PAGEDN: if(!gameover) selected.y = game.height -1; break; case '7': if(!gameover && selected.y && selected.x) { selected.y--; selected.x--; } break; case '8': case KEY_UP: if(!gameover && selected.y) selected.y--; break; case '9': if(!gameover && selected.y && selected.x < game.width - 1) { selected.y--; selected.x++; } break; case '2': case KEY_DOWN: if(!gameover && selected.y < game.height - 1) selected.y++; break; case '1': if(!gameover && selected.y < game.height -1 && selected.x) { selected.y++; selected.x--; } break; case '3': if(!gameover && selected.x < game.width - 1&& selected.y < game.height - 1) { selected.x++; selected.y++; } break; case '4': case KEY_LEFT: if(!gameover && selected.x) selected.x--; break; case '5': if(!gameover) select_middle(); break; case '6': case KEY_RIGHT: if(!gameover && selected.x < game.width - 1) selected.x++; break; case 'D': // Dig (or Details) case ' ': if(gameover) { if(!gamewon) { view_details = !view_details; full_redraw = true; } } else { if(board[selected.y][selected.x].covered && !board[selected.y][selected.x].flagged) { if(uncover(selected.x, selected.y)) lostgame("mine"); else isgamewon(); full_redraw = gameover; } } break; case 'C': // Chord if(!gameover && can_chord(selected.x, selected.y)) { if(chord(selected.x, selected.y)) lostgame("mine"); else isgamewon(); full_redraw = gameover; } break; case 'F': // Flag if(!gameover && board[selected.y][selected.x].covered) { if(board[selected.y][selected.x].flagged) { board[selected.y][selected.x].unsure = true; board[selected.y][selected.x].flagged = false; } else if(board[selected.y][selected.x].unsure) { board[selected.y][selected.x].unsure = false; } else { board[selected.y][selected.x].flagged = true; } if(!game.start) start_game(); full_redraw = gameover; } break; case 'N': { console.home(); console.down(top + 1); full_redraw = true; if(game.start && !gameover) { console.cleartoeol(); draw_border(); console.attributes = LIGHTRED; console.right((console.screen_columns - 15) / 2); mouse_enable(false); console.clear_hotspots(); console.print("New Game (\x01~Y/\x01~N) ?"); var key = console.getkey(K_UPPER); mouse_enable(true); if(key != 'Y') break; } var new_difficulty = get_difficulty(); if(new_difficulty > 0) difficulty = init_game(new_difficulty); break; } case 'W': full_redraw = true; mouse_enable(false); console.home(); console.down(top + 1); var level = get_difficulty(true); if(level >= 0) { console.line_counter = 0; show_winners(level); console.pause(); console.clear(); console.aborted = false; } mouse_enable(true); break case 'T': mouse_enable(false); show_winners(); console.pause(); console.clear(); console.aborted = false; full_redraw = true; mouse_enable(true); break; case 'L': mouse_enable(false); console.line_counter = 0; show_log(); console.pause(); console.clear(); console.aborted = false; full_redraw = true; mouse_enable(true); break case 'B': if(!best) break; console.line_counter = 0; mouse_enable(false); show_best(); console.pause(); console.clear(); console.aborted = false; full_redraw = true; mouse_enable(true); break; case '?': case 'H': mouse_enable(false); console.line_counter = 0; console.clear(); console.printfile(help_file); console.pause(); console.clear(); console.aborted = false; full_redraw = true; mouse_enable(true); break; case '\t': highlight = !highlight; break; case CTRL_R: console.clear(); full_redraw = true; break; case CTRL_S: selector++; break; case 'Q': if(game.start && !gameover) { full_redraw = true; console.home(); console.down(top + 1); console.cleartoeol(); draw_border(); console.attributes = LIGHTRED; console.right((console.screen_columns - 16) / 2); mouse_enable(false); console.clear_hotspots(); console.print("Quit Game (\x01~Y/\x01~N) ?"); var key = console.getkey(K_UPPER); mouse_enable(true); if(key != 'Y') break; } return; } change(selected.x, selected.y); draw_board(full_redraw); full_redraw = false; } } try { // Parse cmd-line options here: var numval; for(var i = 0; i < argv.length; i++) { numval = parseInt(argv[i]); if(!isNaN(numval)) break; } if(argv.indexOf("nocls") < 0) js.on_exit("console.clear()"); js.on_exit("console.attributes = LIGHTGRAY"); if(argv.indexOf("winners") >= 0) { if(!isNaN(numval) && numval > 0) options.winners = numval; show_winners(); exit(); } js.on_exit("console.line_counter = 0"); js.on_exit("console.status = " + console.status); console.status |= CON_MOUSE_PASSTHRU; js.on_exit("console.ctrlkey_passthru = " + console.ctrlkey_passthru); console.ctrlkey_passthru = "KOPTUZ"; if(!isNaN(numval) && numval > 0 && numval < max_difficulty) difficulty = numval; if(!difficulty) difficulty = 1; play(); userprops.set(ini_section, "selector", selector%selectors.length); userprops.set(ini_section, "highlight", highlight); userprops.set(ini_section, "difficulty", difficulty); } catch(e) { var msg = file_getname(e.fileName) + " line " + e.lineNumber + ": " + e.message; console.crlf(); alert(msg); if(options.sub && user.alias != author) { var msgbase = new MsgBase(options.sub); var hdr = { to: author, from: user.alias, subject: title }; msg += "\r\n--- " + js.exec_file + " " + REVISION + "\r\n"; if(!msgbase.save_msg(hdr, msg)) alert("Error saving exception-message to: " + options.sub); msgbase.close(); } }