Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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

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