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(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|