Skip to content
Snippets Groups Projects
Commit 3a90bc53 authored by echicken's avatar echicken
Browse files

Added mouse support:

Hit a skill key, then click on a lemon to assign the skill.
(You can also click on nuke, help, pause, quit (I think) during play
and it will do the thing.)
Changed keyboard controls:
Now you must hit a skill key to select the current skill,
then put the cursor over a lemon and hit enter or space to assign
the skill.
The help screen still needs to be updated.
parent 4b179ebb
No related branches found
No related tags found
No related merge requests found
...@@ -42,4 +42,12 @@ const KEY_BASH = "A", ...@@ -42,4 +42,12 @@ const KEY_BASH = "A",
KEY_BOMB = "F", KEY_BOMB = "F",
KEY_BUILD = "V", KEY_BUILD = "V",
KEY_CLIMB = "C", KEY_CLIMB = "C",
KEY_DIG = "D"; KEY_DIG = "D";
\ No newline at end of file
const COLOUR = {};
COLOUR[KEY_BASH] = COLOUR_BASHER;
COLOUR[KEY_BLOCK] = COLOUR_BLOCKER;
COLOUR[KEY_BOMB] = COLOUR_BOMBER;
COLOUR[KEY_BUILD] = COLOUR_BUILDER;
COLOUR[KEY_CLIMB] = COLOUR_CLIMBER;
COLOUR[KEY_DIG] = COLOUR_DIGGER;
\ No newline at end of file
// The Game object tracks a user's progress and score across multiple levels // The Game object tracks a user's progress and score across multiple levels
var Game = function(host, port) { var Game = function (host, port) {
// We want the user to start by choosing an initial level, if applicable.
var gameState = GAME_STATE_CHOOSELEVEL; var gameState = GAME_STATE_CHOOSELEVEL;
// Some values to be tracked throughout this gaming session
var stats = { var stats = {
'level' : 0, // Dummy value level: 0, // Dummy value
'score' : 0, // Index into the 'levels' array (see below) score: 0, // Index into the 'levels' array (see below)
'turns' : 5 // How many turns to start the player off with turns: 5, // How many turns to start the player off with
}; };
// Just an initial value
var levelState = LEVEL_CONTINUE;
// This will always either be false or an instance of PopUp (lemons.js)
var popUp = false; var popUp = false;
/* This will always either be false (if not paused) or an array of stored
Timer events (if paused). */
var events = false; var events = false;
var levelChooser = false;
// Create a DBHelper object, which we'll use for a few things later var levelState = LEVEL_CONTINUE;
var dbHelper = new DBHelper(host, port); var dbHelper = new DBHelper(host, port);
// Load the levels from the DB (or local file)
var levels = dbHelper.getLevels(); var levels = dbHelper.getLevels();
// Get this user's highest-achieved level
var l = dbHelper.getHighestLevel(); var l = dbHelper.getHighestLevel();
if (l >= levels.length) l = levels.length - 1;
// This is unnecessary except in a very edge case
if(l >= levels.length)
l = levels.length - 1;
// This will be a LevelChooser if we're picking a level
var levelChooser = false;
// Make the paused/unpaused status of this Game publically accessible
this.paused = false; this.paused = false;
// A custom prompt that works with this game's input & state model var LevelChooser = function () {
var LevelChooser = function() {
var lcFrame = new Frame( var lcFrame = new Frame(
frame.x + Math.ceil((frame.width - 32) / 2), frame.x + Math.ceil((frame.width - 32) / 2),
...@@ -76,59 +53,28 @@ var Game = function(host, port) { ...@@ -76,59 +53,28 @@ var Game = function(host, port) {
var input = (l + 1) + ""; var input = (l + 1) + "";
inputFrame.putmsg(input); inputFrame.putmsg(input);
this.getcmd = function(userInput) { this.getcmd = function (userInput) {
if (userInput.key == "" || (userInput.key != "\x08" && userInput.key != "\r" && userInput.key != "\n" && isNaN(parseInt(userInput.key)))) {
// We're only looking for numbers, backspace, or enter
if( userInput == ""
||
( userInput != "\x08"
&&
userInput != "\r"
&&
userInput != "\n"
&&
isNaN(parseInt(userInput))
)
) {
return; return;
} }
if (userInput.key == "\x08" && input.length > 0) {
// Handle backspace
if(userInput == "\x08" && input.length > 0) {
input = input.substr(0, input.length - 1); input = input.substr(0, input.length - 1);
} else if (userInput.key == "\r" || userInput.key == "\n") {
// Handle enter
} else if(userInput == "\r" || userInput == "\n") {
var ret = parseInt(input); var ret = parseInt(input);
input = ""; input = "";
inputFrame.clear(); inputFrame.clear();
inputFrame.putmsg(input); inputFrame.putmsg(input);
if (input.length > 3 || isNaN(ret) || ret > (l + 1) || ret < 1) return;
// Don't return an invalid number return (ret - 1);
if(input.length > 3 || isNaN(ret) || ret > (l + 1) || ret < 1)
return;
else
return (ret - 1);
// Anything else that's gotten this far can be appended
} else { } else {
input += userInput.key;
input += userInput;
} }
// If we've gotten this far, the input box needs a refresh
inputFrame.clear(); inputFrame.clear();
inputFrame.putmsg(input); inputFrame.putmsg(input);
return; return;
} }
this.close = function() { this.close = function () {
lcFrame.delete(); lcFrame.delete();
} }
...@@ -136,183 +82,113 @@ var Game = function(host, port) { ...@@ -136,183 +82,113 @@ var Game = function(host, port) {
} }
var level; // We'll need this variable within some of the following methods var level;
// Handle user input passed to us by the parent script // Handle user input passed to us by the parent script
this.getcmd = function(userInput) { this.getcmd = function (userInput) {
if (gameState == GAME_STATE_CHOOSELEVEL) {
// If the user should be choosing a level ... if (l == 0) {
if(gameState == GAME_STATE_CHOOSELEVEL) {
// If this is a new user or they haven't beat level 0, start there
if(l == 0) {
level = new Level(levels[stats.level], stats.level); level = new Level(levels[stats.level], stats.level);
gameState = GAME_STATE_NORMAL; gameState = GAME_STATE_NORMAL;
} else if (!levelChooser) {
// Otherwise let them start at up to their highest level
} else if(!levelChooser) {
levelChooser = new LevelChooser(); levelChooser = new LevelChooser();
// And if a chooser is already open, use it
} else { } else {
var ll = levelChooser.getcmd(userInput); var ll = levelChooser.getcmd(userInput);
// The chooser should return a valid level number, or nothing if (typeof ll != "undefined") {
if(typeof ll != "undefined") {
levelChooser.close(); levelChooser.close();
levelChooser = false; levelChooser = false;
stats.level = ll; stats.level = ll;
level = new Level(levels[stats.level], stats.level); level = new Level(levels[stats.level], stats.level);
gameState = GAME_STATE_NORMAL; gameState = GAME_STATE_NORMAL;
return true; return true;
} }
} }
} else if (gameState == GAME_STATE_NORMAL && !level.getcmd(userInput)) {
/* If gameplay is in progress, pass user input to the Level object.
If Level.getcmd returns false, the user hit 'Q' to quit. */
} else if(gameState == GAME_STATE_NORMAL && !level.getcmd(userInput)) {
level.close(); level.close();
return false; return false;
} else if (gameState == GAME_STATE_POPUP && (userInput.key != "" || userInput.mouse !== null)) {
// If a pop-up is on the screen, remove it if the user hits a key
} else if(gameState == GAME_STATE_POPUP && userInput != "") {
gameState = GAME_STATE_NORMAL; gameState = GAME_STATE_NORMAL;
popUp.close(); popUp.close();
} }
// Game.getcmd will return true unless the user hit 'Q' during gameplay
return true; return true;
} }
/* This is called during the main loop when in gameplay state, and returns this.cycle = function () {
true unless the user runs out of turns or beats the game. */ if (gameState == GAME_STATE_CHOOSELEVEL) {
this.cycle = function() {
// If we're waiting for the player to choose a level
if(gameState == GAME_STATE_CHOOSELEVEL) {
return true; return true;
} else if (gameState == GAME_STATE_POPUP) {
// If we're waiting for them to dismiss a pop-up message
} else if(gameState == GAME_STATE_POPUP) {
return true; return true;
} else if (levelState == LEVEL_DEAD || levelState == LEVEL_TIME) {
// If they failed to rescue enough lemons, or ran out of time
} else if(levelState == LEVEL_DEAD || levelState == LEVEL_TIME) {
// Close the level, perchance to reopen it and start again
level.close(); level.close();
// If the user has run out of turns ... if (stats.turns < 1) {
if(stats.turns < 1) {
stats.score = stats.score + level.score; stats.score = stats.score + level.score;
return false; return false;
// If the user has turns left, let them try again
} else { } else {
level = new Level(levels[stats.level], stats.level); level = new Level(levels[stats.level], stats.level);
} }
} else if (levelState == LEVEL_NEXT) {
// If they beat the level, move on to the next one if it exists
} else if(levelState == LEVEL_NEXT) {
level.close(); level.close();
if(stats.level < levels.length - 1) { if (stats.level < levels.length - 1) {
stats.level++; stats.level++;
level = new Level(levels[stats.level], stats.level); level = new Level(levels[stats.level], stats.level);
} else { } else {
return false; return false;
} }
} }
// Call the Level.cycle housekeeping method, respond as necessary
levelState = level.cycle(); levelState = level.cycle();
switch(levelState) { switch (levelState) {
// The user failed to rescue enough lemons. Raise a pop-up.
case LEVEL_DEAD: case LEVEL_DEAD:
stats.turns--; stats.turns--;
popUp = new PopUp( popUp = new PopUp([
[ "What a sour outcome - less than 50% of your lemons survived!", "What a sour outcome - less than 50% of your lemons survived!",
"Score: " + stats.score, "Score: " + stats.score,
stats.turns + " turns remaining", stats.turns + " turns remaining",
"[ Press any key to continue ]" "[ Press any key to continue ]"
] ]);
);
gameState = GAME_STATE_POPUP; gameState = GAME_STATE_POPUP;
break; break;
// The user ran out of time. Raise a pop-up.
case LEVEL_TIME: case LEVEL_TIME:
stats.turns--; stats.turns--;
popUp = new PopUp( popUp = new PopUp([
[ "You ran out of time! Now the lemons are going to miss their party.", "You ran out of time! Now the lemons are going to miss their party.",
"Score: " + stats.score, "Score: " + stats.score,
stats.turns + " turns remaining", stats.turns + " turns remaining",
"[ Press any key to continue ]" "[ Press any key to continue ]"
] ]);
);
gameState = GAME_STATE_POPUP; gameState = GAME_STATE_POPUP;
break; break;
// The user beat the level. Raise a pop-up.
case LEVEL_NEXT: case LEVEL_NEXT:
stats.score += level.score; stats.score += level.score;
popUp = new PopUp( popUp = new PopUp([
[ "You saved some lemons. Savour this pointless victory!", "You saved some lemons. Savour this pointless victory!",
"Score: " + stats.score, "Score: " + stats.score,
"[ Press any key to continue ]" "[ Press any key to continue ]"
] ]);
);
gameState = GAME_STATE_POPUP; gameState = GAME_STATE_POPUP;
break; break;
// The level can continue cycling. Don't do shit.
case LEVEL_CONTINUE: case LEVEL_CONTINUE:
break; break;
default: default:
break; break;
} }
return true; // Everything's satisfactory in the neighbourhood return true;
} }
// Pause or resume the level this.pause = function () {
this.pause = function() { if (!events) {
/* If the level isn't paused, pause it and store the timed events
that it returns. */
if(!events) {
events = level.pause(); events = level.pause();
this.paused = true; this.paused = true;
/* If the level is paused, unpause it by passing in the array of
stored events. */
} else { } else {
level.pause(events); level.pause(events);
events = false; events = false;
this.paused = false; this.paused = false;
} }
} }
// If the user is done with this gaming session, save their score this.close = function () {
this.close = function() {
dbHelper.addScore(stats.score, stats.level); dbHelper.addScore(stats.score, stats.level);
dbHelper.close(); dbHelper.close();
} }
......
...@@ -5,8 +5,9 @@ load('tree.js'); ...@@ -5,8 +5,9 @@ load('tree.js');
load('event-timer.js'); load('event-timer.js');
load('json-client.js'); load('json-client.js');
load('sprite.js'); load('sprite.js');
require("mouse_getkey.js", "mouse_getkey");
var ansi = load({}, 'ansiterm_lib.js');
// Lemons modules
load(js.exec_dir + "defs.js"); load(js.exec_dir + "defs.js");
load(js.exec_dir + "game.js"); load(js.exec_dir + "game.js");
load(js.exec_dir + "level.js"); load(js.exec_dir + "level.js");
...@@ -14,45 +15,39 @@ load(js.exec_dir + "menu.js"); ...@@ -14,45 +15,39 @@ load(js.exec_dir + "menu.js");
load(js.exec_dir + "help.js"); load(js.exec_dir + "help.js");
load(js.exec_dir + "dbhelper.js"); load(js.exec_dir + "dbhelper.js");
var status, // A place to store bbs.sys_status until exit var state,
attributes; // A place to store console attributes until exit frame;
// The Lemons modules expect these globals: Frame.prototype.drawBorder = function (color) {
var state, // What we're currently doing
frame; // The parent frame
/* Draw a border of colour 'color' inside of a frame.
'color' can be a number or an array of colours. */
Frame.prototype.drawBorder = function(color) {
var theColor = color; var theColor = color;
if(Array.isArray(color)); if (Array.isArray(color)) var sectionLength = Math.round(this.width / color.length);
var sectionLength = Math.round(this.width / color.length);
this.pushxy(); this.pushxy();
for(var y = 1; y <= this.height; y++) { for (var y = 1; y <= this.height; y++) {
for(var x = 1; x <= this.width; x++) { for (var x = 1; x <= this.width; x++) {
if(x > 1 && x < this.width && y > 1 && y < this.height) if (x > 1 && x < this.width && y > 1 && y < this.height) continue;
continue;
var msg; var msg;
this.gotoxy(x, y); this.gotoxy(x, y);
if(y == 1 && x == 1) if (y == 1 && x == 1) {
msg = ascii(218); msg = ascii(218);
else if(y == 1 && x == this.width) } else if(y == 1 && x == this.width) {
msg = ascii(191); msg = ascii(191);
else if(y == this.height && x == 1) } else if(y == this.height && x == 1) {
msg = ascii(192); msg = ascii(192);
else if(y == this.height && x == this.width) } else if(y == this.height && x == this.width) {
msg = ascii(217); msg = ascii(217);
else if(x == 1 || x == this.width) } else if(x == 1 || x == this.width) {
msg = ascii(179); msg = ascii(179);
else } else {
msg = ascii(196); msg = ascii(196);
if(Array.isArray(color)) { }
if(x == 1) if (Array.isArray(color)) {
if (x == 1) {
theColor = color[0]; theColor = color[0];
else if(x % sectionLength == 0 && x < this.width) } else if (x % sectionLength == 0 && x < this.width) {
theColor = color[x / sectionLength]; theColor = color[x / sectionLength];
else if(x == this.width) } else if (x == this.width) {
theColor = color[color.length - 1]; theColor = color[color.length - 1];
}
} }
this.putmsg(msg, theColor); this.putmsg(msg, theColor);
} }
...@@ -60,16 +55,11 @@ Frame.prototype.drawBorder = function(color) { ...@@ -60,16 +55,11 @@ Frame.prototype.drawBorder = function(color) {
this.popxy(); this.popxy();
} }
/* Pop a message up near the centre of frame 'frame': function PopUp(message) {
var popUp = new PopUp(["Line 1", "Line 2" ...]);
console.getkey();
popUp.close(); */
var PopUp = function(message) {
var longestLine = 0; var longestLine = 0;
for(var m = 0; m < message.length; m++) { for (var m = 0; m < message.length; m++) {
if(message[m].length > longestLine) if (message[m].length > longestLine) longestLine = message[m].length;
longestLine = message[m].length;
} }
this.frame = new Frame( this.frame = new Frame(
...@@ -84,25 +74,29 @@ var PopUp = function(message) { ...@@ -84,25 +74,29 @@ var PopUp = function(message) {
this.frame.open(); this.frame.open();
this.frame.drawBorder([CYAN, LIGHTCYAN, WHITE]); this.frame.drawBorder([CYAN, LIGHTCYAN, WHITE]);
this.frame.gotoxy(1, 2); this.frame.gotoxy(1, 2);
for(var m = 0; m < message.length; m++) for (var m = 0; m < message.length; m++) {
this.frame.center(message[m] + "\r\n"); this.frame.center(message[m] + "\r\n");
}
this.close = function() { this.close = function () {
this.frame.delete(); this.frame.delete();
} }
} }
// Prepare the display, set the initial state function mouse_enable(enable) {
var init = function() { if (!console.term_supports(USER_ANSI)) return;
ansi.send('mouse', enable ? 'set' : 'clear', 'x10_compatible');
status = bbs.sys_status; // We'll restore this on cleanup ansi.send('mouse', enable ? 'set' : 'clear', 'extended_coord');
bbs.sys_status|=SS_MOFF; // Turn off node message delivery }
attributes = console.attributes; // We'll restore this on cleanup function init() {
console.clear(BG_BLACK|WHITE); // Wipe away any poops js.on_exit('frame.delete();');
js.on_exit('bbs.sys_status = ' + bbs.sys_status + ';');
// Set up the root frame js.on_exit('console.clear(' + console.attributes + ');');
js.on_exit('mouse_enable(false);'); // Should probably depend on whether it's already enabled
bbs.sys_status|=SS_MOFF; // Turn off node message delivery
console.clear(BG_BLACK|WHITE);
frame = new Frame( frame = new Frame(
Math.ceil((console.screen_columns - 79) / 2), Math.ceil((console.screen_columns - 79) / 2),
Math.ceil((console.screen_rows - 23) / 2), Math.ceil((console.screen_rows - 23) / 2),
...@@ -112,21 +106,11 @@ var init = function() { ...@@ -112,21 +106,11 @@ var init = function() {
); );
frame.open(); frame.open();
frame.checkbounds = false; frame.checkbounds = false;
// We start off at the menu screen
state = STATE_MENU; state = STATE_MENU;
mouse_enable(true);
}
// Return the display & system status to normal
var cleanUp = function() {
frame.delete();
bbs.sys_status = status;
console.clear(attributes);
} }
// Get input from the user, make things happen function main() {
var main = function() {
// These will always be either 'false' or an instance of a Lemons module // These will always be either 'false' or an instance of a Lemons module
var game = false, var game = false,
...@@ -134,130 +118,86 @@ var main = function() { ...@@ -134,130 +118,86 @@ var main = function() {
help = false, help = false,
scoreboard = false; scoreboard = false;
if(file_exists(js.exec_dir + "server.ini")) { if (file_exists(js.exec_dir + "server.ini")) {
var f = new File(js.exec_dir + "server.ini"); var f = new File(js.exec_dir + "server.ini");
f.open("r"); f.open("r");
var serverIni = f.iniGetObject(); var serverIni = f.iniGetObject();
f.close(); f.close();
} else { } else {
var serverIni = { 'host' : "127.0.0.1", 'port' : 10088 }; var serverIni = {
host: "127.0.0.1",
port: 10088
};
} }
while(!js.terminated) { while (!js.terminated) {
// Refresh the user's terminal and bury the (real) cursor if necessary if (frame.cycle()) console.gotoxy(console.screen_columns, console.screen_rows);
if(frame.cycle())
console.gotoxy(console.screen_columns, console.screen_rows);
/* This is the only place where we take input from the user. var userInput = mouse_getkey(K_NONE, 5, true);
Input is passed to the current (as per state) module from here. */ userInput.key = userInput.key.toUpperCase();
var userInput = console.inkey(K_UPPER, 5);
switch(state) {
switch (state) {
case STATE_MENU: case STATE_MENU:
// Load the menu if it isn't already showing if (!menu) menu = new Menu();
if(!menu) menu.getcmd(userInput.key);
menu = new Menu(); if (state != STATE_MENU) {
// Pass user input to the menu
menu.getcmd(userInput);
// If we've left the menu, close and falsify it
if(state != STATE_MENU) {
menu.close(); menu.close();
menu = false; menu = false;
} }
break; break;
case STATE_PLAY: case STATE_PLAY:
// Create a new Game if we're not already in one if (!game) game = new Game(serverIni.host, serverIni.port);
if(!game) if (!game.getcmd(userInput)) {
game = new Game(serverIni.host, serverIni.port);
// Pass user input to the game module
if(!game.getcmd(userInput)) {
// If Game.getcmd returns false, the user has hit 'Q'
game.close(); game.close();
game = false; game = false;
// Return to the menu on the next loop
state = STATE_MENU; state = STATE_MENU;
} else if(!game.cycle()) { } else if (!game.cycle()) {
/* If Game.cycle returns false, the user is out of turns
or has beat the last level. */
game.close(); game.close();
game = false; game = false;
// Return to the menu on the next loop
state = STATE_MENU; state = STATE_MENU;
} }
break; break;
case STATE_PAUSE: case STATE_PAUSE:
// If the game isn't paused, pause it if (!game.paused) game.pause();
if(!game.paused) if (game.paused && (userInput.key != "" || userInput.mouse !== null)) {
game.pause(); game.pause();
// If the user hit a key and the game is paused, unpause it
if(userInput != "" && game.paused) {
game.pause();
// Return to gameplay on the next loop
state = STATE_PLAY; state = STATE_PLAY;
} }
break; break;
case STATE_HELP: case STATE_HELP:
// If a game is in progress, pause it if (game instanceof Game && !game.paused) game.pause();
if(game instanceof Game && !game.paused) if (!help) help = new Help();
game.pause(); if (userInput.key != "" && game instanceof Game && game.paused) {
// If the help screen isn't showing, load it
if(!help)
help = new Help();
/* If a game is in progress and the user hit a key, unpause
the game, remove the help screen, and return to gameplay. */
if( userInput != ""
&&
game instanceof Game && game.paused
) {
help.close(); help.close();
help = false; help = false;
game.pause(); game.pause();
state = STATE_PLAY; state = STATE_PLAY;
/* If there's no game in progress, close the help screen and } else if (userInput.key != "") {
return to the menu. */
} else if(userInput != "") {
help.close(); help.close();
help = false; help = false;
state = STATE_MENU; state = STATE_MENU;
} }
break; break;
case STATE_SCORES: case STATE_SCORES:
// Bring up the scoreboard if it isn't showing already if (!scoreboard) {
if(!scoreboard) {
scoreboard = new DBHelper(serverIni.host, serverIni.port); scoreboard = new DBHelper(serverIni.host, serverIni.port);
scoreboard.showScores(); scoreboard.showScores();
// Remove the scoreboard when the user hits a key } else if (userInput.key != "") {
} else if(userInput != "") {
scoreboard.close(); scoreboard.close();
state = STATE_MENU; state = STATE_MENU;
scoreboard = false; scoreboard = false;
} }
break; break;
case STATE_EXIT: case STATE_EXIT:
// Break the main loop
return; return;
break; // I just can't not. :|
default: default:
break; break;
} }
} }
} }
// Prepare the things
init(); init();
// Do the things main();
main(); \ No newline at end of file
// Remove the things
cleanUp();
// We're done
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment