You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
696 lines
15 KiB
696 lines
15 KiB
|
|
/*
|
|
|
|
SNAKE
|
|
|
|
by matty riek.
|
|
|
|
*/
|
|
|
|
global showMemoryUsage = false;
|
|
|
|
CURSOR(0,0);
|
|
|
|
global g_sx = 80;
|
|
global g_sy = 24;
|
|
global g_border =
|
|
|
|
"\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205"
|
|
"\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205"
|
|
"\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205\205";
|
|
|
|
global LocFromXY = function(a_x, a_y) { return a_y * g_sx + a_x; };
|
|
global XFromLoc = function(a_loc) { return a_loc % g_sx; };
|
|
global YFromLoc = function(a_loc) { return a_loc / g_sx; };
|
|
global background = CA.B_BLUE;
|
|
|
|
global fade0 = table( CA.F_RED|CA.F_BLUE|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_GREEN | CA.F_INTENSITY | CA.F_INTENSITY | background,
|
|
CA.F_BLUE | background,
|
|
CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_BLUE|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_BLUE | CA.F_INTENSITY | background,
|
|
CA.F_BLUE | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_BLUE|CA.F_GREEN | background,
|
|
CA.F_BLUE | background,
|
|
CA.F_BLUE | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_GREEN | CA.F_INTENSITY | background );
|
|
|
|
global fade1 = table( CA.F_RED|CA.F_BLUE|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_BLUE|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_BLUE|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_GREEN | CA.F_INTENSITY | background,
|
|
CA.F_RED | CA.F_INTENSITY | background,
|
|
CA.F_RED | background,
|
|
CA.F_RED | CA.F_INTENSITY | background,
|
|
CA.F_RED|CA.F_GREEN | CA.F_INTENSITY | background );
|
|
|
|
global fadeRed = table( CA.F_RED|CA.F_INTENSITY | background , CA.F_RED | background);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// CreateGemType
|
|
//
|
|
|
|
global CreateGemType = function(a_char, a_time, a_score, a_chance, a_length, a_cycle, a_pickup, a_mover)
|
|
{
|
|
gem = table(m_char = a_char, m_time = a_time * 1000, m_score = a_score, m_chance = a_chance, m_length = a_length, m_cycle = a_cycle, m_mover = a_mover);
|
|
|
|
gem.Pickup = function()
|
|
{
|
|
};
|
|
|
|
if(a_pickup)
|
|
{
|
|
gem.Pickup = a_pickup;
|
|
}
|
|
|
|
gem.i = 0;
|
|
gem.Draw = function(x, y)
|
|
{
|
|
.i = .i + 1;
|
|
if(.i >= tableCount(.m_cycle))
|
|
{
|
|
.i = 0;
|
|
}
|
|
CATTRIB(.m_cycle[.i]);
|
|
XYTEXT(x, y, .m_char);
|
|
CATTRIB(background | CA.F_GREEN | CA.F_INTENSITY);
|
|
};
|
|
return gem;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DrawScreen
|
|
//
|
|
global DrawScreen = function()
|
|
{
|
|
//
|
|
CATTRIB(background | CA.F_GREEN | CA.F_INTENSITY);
|
|
|
|
// clear screen
|
|
CLS();
|
|
|
|
// draw border
|
|
XYTEXT(1, 0, g_border);
|
|
XYTEXT(1, g_sy - 1, g_border);
|
|
|
|
ey = g_sy - 1;
|
|
for(i = 1; i < ey; i = i + 1)
|
|
{
|
|
XYTEXT(0, i, "\186");
|
|
XYTEXT(g_sx - 1, i, "\186");
|
|
}
|
|
XYTEXT(0, 0, "\201");
|
|
XYTEXT(g_sx - 1, 0, "\187");
|
|
XYTEXT(0, ey, "\200");
|
|
XYTEXT(g_sx - 1, ey, "\188");
|
|
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// EmitGems
|
|
//
|
|
EmitGems = function(a_game)
|
|
{
|
|
// add a gem
|
|
for(;;)
|
|
{
|
|
// choose a random sleep time
|
|
sleep( randfloat(
|
|
a_game.m_levels[a_game.m_level][1] / a_game.m_gameSpeed,
|
|
a_game.m_levels[a_game.m_level][2] / a_game.m_gameSpeed)
|
|
);
|
|
|
|
// pick a random location
|
|
x = randint(1, g_sx - 2);
|
|
y = randint(1, g_sy - 2);
|
|
loc = LocFromXY(x, y);
|
|
|
|
// test if the location is on a existing gem or snake
|
|
if(a_game.m_gems[loc])
|
|
{
|
|
continue;
|
|
}
|
|
onsnake = false;
|
|
foreach(snake in a_game.m_snakes)
|
|
{
|
|
if(snake.IsAt(loc))
|
|
{
|
|
onsnake = true;
|
|
break;
|
|
}
|
|
}
|
|
if(onsnake) { continue; }
|
|
|
|
// choose a gem
|
|
found = false;
|
|
while(!found)
|
|
{
|
|
foreach(gem in a_game.m_gemTypes)
|
|
{
|
|
if(randint(0, 100) < gem.m_chance)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add the gem
|
|
gemInstance = table(m_gem = gem, m_expire = sysTime() + (gem.m_time / a_game.m_gameSpeed));
|
|
a_game.m_gems[loc] = gemInstance;
|
|
if(gem.m_mover)
|
|
{
|
|
gemInstance.m_dirX = randint(-1, 2);
|
|
gemInstance.m_dirY = randint(-1, 2);
|
|
}
|
|
|
|
// draw the gem
|
|
gem.Draw(x, y);
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// ReclaimGems
|
|
//
|
|
ReclaimGems = function(a_game)
|
|
{
|
|
// add a gem
|
|
for(;;)
|
|
{
|
|
time = sysTime();
|
|
|
|
foreach(key and gem in a_game.m_gems)
|
|
{
|
|
if(time > gem.m_expire)
|
|
{
|
|
// remove from screen
|
|
XYTEXT(XFromLoc(key), YFromLoc(key), " ");
|
|
a_game.m_gems[key] = null;
|
|
b = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
yield();
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// DrawGems
|
|
//
|
|
DrawGems = function(a_game)
|
|
{
|
|
for(;;)
|
|
{
|
|
foreach(key and gem in a_game.m_gems)
|
|
{
|
|
x = XFromLoc(key);
|
|
y = YFromLoc(key);
|
|
gem.m_gem.Draw(x, y);
|
|
}
|
|
sleep(1 / 30.);
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Move mover gems
|
|
//
|
|
MoveMovers = function(a_game)
|
|
{
|
|
for(;;)
|
|
{
|
|
movers = table();
|
|
foreach(key and gem in a_game.m_gems)
|
|
{
|
|
if(gem.m_gem.m_mover)
|
|
{
|
|
x = XFromLoc(key);
|
|
y = YFromLoc(key);
|
|
|
|
XYTEXT(x, y, " ");
|
|
|
|
x = x + gem.m_dirX;
|
|
y = y + gem.m_dirY;
|
|
|
|
if(x <= 1 or x >= g_sx - 2) { gem.m_dirX = -gem.m_dirX; }
|
|
if(y <= 1 or y >= g_sy - 2) { gem.m_dirY = -gem.m_dirY; }
|
|
gem.m_newLoc = LocFromXY(x, y);
|
|
movers[key] = gem;
|
|
}
|
|
}
|
|
|
|
foreach(key and gem in movers)
|
|
{
|
|
a_game.m_gems[key] = null;
|
|
a_game.m_gems[gem.m_newLoc] = gem;
|
|
}
|
|
sleep(1 / 4.);
|
|
}
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Snake
|
|
//
|
|
Snake = function(a_id, a_name, a_score, a_scoreX, a_scoreY, a_headChar, a_tailChar, a_startX, a_startY, a_startDir, a_keys)
|
|
{
|
|
snake = table(
|
|
m_id = a_id,
|
|
m_grow = 2,
|
|
m_credits = 3,
|
|
m_startX = a_startX,
|
|
m_startY = a_startY,
|
|
m_startDir = a_startDir,
|
|
m_name = a_name,
|
|
m_score = a_score,
|
|
m_scoreX = a_scoreX,
|
|
m_scoreY = a_scoreY,
|
|
m_body = array(50),
|
|
m_length = 0,
|
|
m_dir = a_startDir, // 0 right, 1 up, 2 left, 3 down
|
|
m_head = table(
|
|
m_headChar = a_headChar,
|
|
m_tailChar = a_tailChar,
|
|
m_x = a_startX,
|
|
m_y = a_startY
|
|
),
|
|
m_keys = a_keys
|
|
);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Die
|
|
//
|
|
snake.Die = function() // return true if snake has no credits
|
|
{
|
|
// flash a death thinggy
|
|
|
|
i = 10;
|
|
while(i)
|
|
{
|
|
if(i & 1) {
|
|
XYTEXT(.m_scoreX, .m_scoreY, format(" *** FATALITY *** ", .m_name));
|
|
}
|
|
else {
|
|
XYTEXT(.m_scoreX, .m_scoreY, format(" ", .m_name));
|
|
}
|
|
sleep(0.2f);
|
|
i = i - 1;
|
|
}
|
|
|
|
.UpdateScore();
|
|
|
|
// clear the snake
|
|
for(i = 0; i < .m_length; i = i + 1)
|
|
{
|
|
loc = .m_body[i];
|
|
x = XFromLoc(loc);
|
|
y = YFromLoc(loc);
|
|
XYTEXT(x, y, " ");
|
|
}
|
|
XYTEXT(.m_head.m_x, .m_head.m_y, " ");
|
|
|
|
// update the credits
|
|
.m_credits = .m_credits - 1;
|
|
if(.m_credits)
|
|
{
|
|
.m_head.m_x = .m_startX;
|
|
.m_head.m_y = .m_startY;
|
|
.m_dir = .m_startDir;
|
|
.m_grow = 2;
|
|
.m_length = 0;
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// UpdateScore
|
|
//
|
|
snake.UpdateScore = function()
|
|
{
|
|
XYTEXT(.m_scoreX, .m_scoreY, format(" %d UP %s SCORE %d ", .m_credits, .m_name, .m_score));
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Draw
|
|
//
|
|
snake.Draw = function(a_oldX, a_oldY)
|
|
{
|
|
// clear the tail.
|
|
if(.m_length > 0 and .grow == null)
|
|
{
|
|
loc = .m_body[.m_length - 1];
|
|
y = YFromLoc(loc);
|
|
x = XFromLoc(loc);
|
|
XYTEXT(x, y, " ");
|
|
}
|
|
|
|
// grow the snake
|
|
if(.grow)
|
|
{
|
|
.m_length = .m_length + 1;
|
|
if(.m_length >= .m_body.Size())
|
|
{
|
|
.m_body.Resize(.m_body.Size() * 2);
|
|
}
|
|
.grow = false;
|
|
}
|
|
|
|
// move the body along.
|
|
for(i = .m_length - 1; i > 0; i = i - 1)
|
|
{
|
|
.m_body[i] = .m_body[i - 1];
|
|
}
|
|
|
|
// put the new tail piece in
|
|
if(.m_length)
|
|
{
|
|
.m_body[0] = LocFromXY(a_oldX, a_oldY);
|
|
XYTEXT(a_oldX, a_oldY, .m_head.m_tailChar);
|
|
}
|
|
else
|
|
{
|
|
XYTEXT(a_oldX, a_oldY, " ");
|
|
}
|
|
|
|
// draw the new head
|
|
XYTEXT(.m_head.m_x, .m_head.m_y, .m_head.m_headChar);
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// IsAt
|
|
//
|
|
snake.IsAt = function(a_loc)
|
|
{
|
|
for(i = 0; i < .m_length; i = i + 1)
|
|
{
|
|
if(a_loc == .m_body[i])
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
if(a_loc == LocFromXY(.m_head.m_x, .m_head.m_y))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update
|
|
//
|
|
snake.Update = function(a_game)
|
|
{
|
|
member UpdateScore;
|
|
|
|
UpdateScore();
|
|
for(;;)
|
|
{
|
|
wait = 1.0f / (a_game.m_snakeMoveRate * a_game.m_gameSpeed);
|
|
sleep(wait);
|
|
|
|
x = .m_head.m_x;
|
|
y = .m_head.m_y;
|
|
|
|
if(.m_dir == 0) { x = x + 1; }
|
|
else if(.m_dir == 1) { y = y - 1; }
|
|
else if(.m_dir == 2) { x = x - 1; }
|
|
else if(.m_dir == 3) { y = y + 1; }
|
|
|
|
// did we collect a gem?
|
|
loc = LocFromXY(x, y);
|
|
gem = a_game.m_gems[loc];
|
|
if(gem)
|
|
{
|
|
// update score
|
|
.m_score = .m_score + gem.m_gem.m_score;
|
|
.m_grow = gem.m_gem.m_length;
|
|
pickup = gem.m_gem.Pickup;
|
|
this:pickup();
|
|
UpdateScore();
|
|
|
|
// remove the gem
|
|
a_game.m_gems[loc] = null;
|
|
}
|
|
|
|
// did we run into a wall??
|
|
dead = false;
|
|
if(x <= 0 or x >= g_sx - 1 or y <= 0 or y >= g_sy - 1)
|
|
{
|
|
dead = true;
|
|
}
|
|
|
|
// did we run into ourselves?
|
|
if(!dead and .IsAt(loc))
|
|
{
|
|
dead = true;
|
|
}
|
|
|
|
// update position
|
|
oldX = .m_head.m_x;
|
|
oldY = .m_head.m_y;
|
|
.m_head.m_x = x;
|
|
.m_head.m_y = y;
|
|
|
|
// did we run into another snake
|
|
if(!dead)
|
|
{
|
|
foreach(snake in a_game.m_snakes)
|
|
{
|
|
if(snake != this and snake.IsAt(loc))
|
|
{
|
|
dead = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// did we die?
|
|
if(dead)
|
|
{
|
|
.m_head.m_x = oldX;
|
|
.m_head.m_y = oldY;
|
|
die = .Die();
|
|
.UpdateScore();
|
|
if(die)
|
|
{
|
|
a_game.m_snakes[.m_id] = null;
|
|
threadKill(.kht);
|
|
exit();
|
|
}
|
|
}
|
|
.Draw(oldX, oldY);
|
|
|
|
if(.m_grow)
|
|
{
|
|
.grow = true;
|
|
.m_grow = .m_grow - 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// KeyHandler
|
|
//
|
|
snake.KeyHandler = function(a_game)
|
|
{
|
|
wait = 1.0f / a_game.m_keyUpdateRate;
|
|
for(;;)
|
|
{
|
|
for(i = 0; i < 4; i = i + 1)
|
|
{
|
|
if(ISPRESSED(.m_keys[i]))
|
|
{
|
|
.m_dir = i;
|
|
break;
|
|
}
|
|
}
|
|
sleep(wait);
|
|
}
|
|
};
|
|
|
|
return snake;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Game
|
|
//
|
|
g_game = table(
|
|
|
|
// goodies
|
|
m_gems = table(),
|
|
|
|
m_levels = table(
|
|
|
|
// level is speed, lower spawn time, upper spawn time, next level score
|
|
table(1.0f, 10, 20, 500),
|
|
table(2.0f, 8, 18, 1500),
|
|
table(2.5f, 5, 16, 2700),
|
|
table(3.0f, 4, 13, 3800),
|
|
table(3.5f, 3, 10, 5000)
|
|
),
|
|
|
|
m_gemTypes = table(
|
|
|
|
CreateGemType("\4", 22, 50, 40, 3, fade0), // standard 1
|
|
CreateGemType("\5", 17, 100, 30, 4, fade0), // standard 2
|
|
CreateGemType("\6", 15, 200, 20, 5, fade1), // standard 3
|
|
CreateGemType("\3", 30, 20, 15, 0, fadeRed, function() { .m_credits = .m_credits + 1; }, true), // 1up
|
|
CreateGemType("\15", 45, 0, 15, 50, fade1) // bomb
|
|
),
|
|
|
|
// snakes
|
|
m_snakes = table(
|
|
Snake(0, "lefty", 0, 8, 23, "\1", "\176", 1, 12, 0, table('D', 'W', 'A', 'S')),
|
|
Snake(1, "righty", 0, 50, 23, "\2", "\177", 78, 12, 2, table('L', 'I', 'J', 'K'))
|
|
),
|
|
|
|
// game
|
|
m_level = 0,
|
|
m_over = false,
|
|
m_totalScore = 0,
|
|
|
|
// timing
|
|
m_speedup = 2.0f, // overall speed multiplier
|
|
m_gameSpeed = 1.0f, // current game speed
|
|
m_keyUpdateRate = 30.0f, // times per second
|
|
m_snakeMoveRate = 4.0f // times per second
|
|
);
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Main
|
|
//
|
|
ScoreMonitor = function()
|
|
{
|
|
.m_gameSpeed = .m_speedup * .m_levels[.m_level][0];
|
|
|
|
for(;;)
|
|
{
|
|
// update scores and levels.
|
|
.m_totalScore = 0;
|
|
foreach(snake in .m_snakes)
|
|
{
|
|
.m_totalScore = .m_totalScore + snake.m_score;
|
|
}
|
|
|
|
// are they at the next level
|
|
if(.m_totalScore >= .m_levels[.m_level][3])
|
|
{
|
|
// level up.... have we finished
|
|
.m_level = .m_level + 1;
|
|
if(.m_level >= tableCount(.m_levels))
|
|
{
|
|
// you win....
|
|
XYTEXT(0,0,"YOUWIN");
|
|
sleep(5);
|
|
.gameover = true;
|
|
}
|
|
else
|
|
{
|
|
// next level
|
|
.m_gameSpeed = .m_speedup * .m_levels[.m_level][0];
|
|
}
|
|
}
|
|
|
|
XYTEXT(8, 0, format("LEVEL %d, SCORE %d, NEXT LEVEL TARGET %d", .m_level, .m_totalScore, .m_levels[.m_level][3]));
|
|
if(showMemoryUsage)
|
|
{
|
|
XYTEXT(8, 1, format("mem %d Desired H: %d S: %d F: %d I: %d W: %d",
|
|
sysGetMemoryUsage(),
|
|
sysGetDesiredMemoryUsageHard(),
|
|
sysGetDesiredMemoryUsageSoft(),
|
|
sysGetStatsGCNumFullCollects(),
|
|
sysGetStatsGCNumIncCollects(),
|
|
sysGetStatsGCNumWarnings() ));
|
|
}
|
|
sleep(0.1);
|
|
}
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Title Screen
|
|
//
|
|
|
|
// todo
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Main
|
|
//
|
|
|
|
DrawScreen();
|
|
|
|
// start the snakes
|
|
foreach(snake in g_game.m_snakes)
|
|
{
|
|
snake.kht = snake:thread(snake.KeyHandler, g_game);
|
|
snake.ut = snake:thread(snake.Update, g_game);
|
|
}
|
|
|
|
// start the gems
|
|
egt = thread(EmitGems, g_game);
|
|
rgt = thread(ReclaimGems, g_game);
|
|
dgt = thread(DrawGems, g_game);
|
|
mmt = thread(MoveMovers, g_game);
|
|
smt = g_game:thread(ScoreMonitor);
|
|
|
|
for(;;)
|
|
{
|
|
// test for escape key
|
|
if(g_game.gameover or ISPRESSED(27))
|
|
{
|
|
// kill threads
|
|
threadKill(egt);
|
|
threadKill(rgt);
|
|
threadKill(dgt);
|
|
threadKill(smt);
|
|
threadKill(mmt);
|
|
|
|
foreach(snake in g_game.m_snakes)
|
|
{
|
|
threadKill(snake.kht);
|
|
threadKill(snake.ut);
|
|
}
|
|
exit();
|
|
}
|
|
yield();
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|