Je m'intéresse au Tetris depuis quelques jours. Ca serait intéressant d'en construire un avec de vraies primitives mais c'est un gros chantier que j'ai commencé mais dont j'ignore où il ira. En attendant je vous livre une version "web on prim". Je trouve qu'on n'utilise pas suffisamment cette fonctionnalité, pour une raison que j'ignore. Pour le tetris mettez ce script dans une primitive :
//----------------------------------------------------------------------------------
// Tetris web V1.0
//----------------------------------------------------------------------------------
// Source du Javascript : https://github.com/jakesgordon/javascript-tetris
//----------------------------------------------------------------------------------
//----------------------------------------------
// PARAMETRES
//----------------------------------------------
integer FACE = 4;
string VERSION = "1.00";
//----------------------------------------------
// VARIABLES
//----------------------------------------------
string myURL;
//----------------------------------------------
// FIN
//----------------------------------------------
browse(integer face, string url) {
llClearLinkMedia(LINK_THIS, face);
llSetLinkMedia(LINK_THIS, face, [
PRIM_MEDIA_CONTROLS, PRIM_MEDIA_CONTROLS_MINI,
PRIM_MEDIA_AUTO_PLAY, TRUE,
PRIM_MEDIA_AUTO_LOOP, TRUE,
PRIM_MEDIA_AUTO_SCALE, TRUE,
PRIM_MEDIA_CURRENT_URL, url,
PRIM_MEDIA_HOME_URL, url,
PRIM_MEDIA_HEIGHT_PIXELS, 512,
PRIM_MEDIA_WIDTH_PIXELS, 512]);
}
string getPage() {
return "<!DOCTYPE html>
<html>
<head>
<title>Javascript Tetris</title>
<style>
body { font-family: Helvetica, sans-serif; }
#tetris { margin: 1em auto; padding: 1em; border: 4px solid black; border-radius: 10px; background-color: #F8F8F8; }
#canvas { display: inline-block; vertical-align: top; background: url(http://www.scriptol.fr/design/paint/images/nuage-bleu.png); box-shadow: 10px 10px 10px #999; border: 2px solid #333; }
#menu { display: inline-block; vertical-align: top; position: relative; }
#menu p { margin: 0.5em 0; text-align: center; }
#menu p a { text-decoration: none; color: black; }
#upcoming { display: block; margin: 0 auto; background-color: #E0E0E0; }
#score { color: red; font-weight: bold; }
#rows { color: blue; font-weight: bold; }
@media screen and (min-width: 0px) and (min-height: 0px) { #tetris { font-size: 0.75em; width: 250px; } #menu { width: 100px; height: 200px; } #upcoming { width: 50px; height: 50px; } #canvas { width: 100px; height: 200px; } } /* 10px chunks */
@media screen and (min-width: 400px) and (min-height: 400px) { #tetris { font-size: 1.00em; width: 350px; } #menu { width: 150px; height: 300px; } #upcoming { width: 75px; height: 75px; } #canvas { width: 150px; height: 300px; } } /* 15px chunks */
@media screen and (min-width: 500px) and (min-height: 500px) { #tetris { font-size: 1.25em; width: 450px; } #menu { width: 200px; height: 400px; } #upcoming { width: 100px; height: 100px; } #canvas { width: 200px; height: 400px; } } /* 20px chunks */
@media screen and (min-width: 600px) and (min-height: 600px) { #tetris { font-size: 1.50em; width: 550px; } #menu { width: 250px; height: 500px; } #upcoming { width: 125px; height: 125px; } #canvas { width: 250px; height: 500px; } } /* 25px chunks */
@media screen and (min-width: 700px) and (min-height: 700px) { #tetris { font-size: 1.75em; width: 650px; } #menu { width: 300px; height: 600px; } #upcoming { width: 150px; height: 150px; } #canvas { width: 300px; height: 600px; } } /* 30px chunks */
@media screen and (min-width: 800px) and (min-height: 800px) { #tetris { font-size: 2.00em; width: 750px; } #menu { width: 350px; height: 700px; } #upcoming { width: 175px; height: 175px; } #canvas { width: 350px; height: 700px; } } /* 35px chunks */
@media screen and (min-width: 900px) and (min-height: 900px) { #tetris { font-size: 2.25em; width: 850px; } #menu { width: 400px; height: 800px; } #upcoming { width: 200px; height: 200px; } #canvas { width: 400px; height: 800px; } } /* 40px chunks */
</style>
</head>
<body>
<div id=\"tetris\">
<div id=\"menu\">
<p id=\"start\"><a href=\"javascript:play();\">Espace pour jouer</a></p>
<p><canvas id=\"upcoming\"></canvas></p>
<p>Score <span id=\"score\">00000</span></p>
<p>Lignes <span id=\"rows\">0</span></p>
</div>
<canvas id=\"canvas\">
Sorry, this example cannot be run because your browser does not support the <canvas> element
</canvas>
</div>
<script>
//-------------------------------------------------------------------------
// base helper methods
//-------------------------------------------------------------------------
function get(id) { return document.getElementById(id); };
function hide(id) { get(id).style.visibility = 'hidden'; };
function show(id) { get(id).style.visibility = null; };
function html(id, html) { get(id).innerHTML = html; };
function timestamp() { return new Date().getTime(); };
function random(min, max) { return (min + (Math.random() * (max - min))); };
function randomChoice(choices) { return choices[Math.round(random(0, choices.length-1))]; };
if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
window.setTimeout(callback, 1000 / 60);
}
}
//-------------------------------------------------------------------------
// game constants
//-------------------------------------------------------------------------
var KEY = { ESC: 27, SPACE: 32, LEFT: 37, UP: 38, RIGHT: 39, DOWN: 40 },
DIR = { UP: 0, RIGHT: 1, DOWN: 2, LEFT: 3, MIN: 0, MAX: 3 },
canvas = get('canvas'),
ctx = canvas.getContext('2d'),
ucanvas = get('upcoming'),
uctx = ucanvas.getContext('2d'),
speed = { start: 0.6, decrement: 0.005, min: 0.1 }, // how long before piece drops by 1 row (seconds)
nx = 10, // width of tetris court (in blocks)
ny = 20, // height of tetris court (in blocks)
nu = 5; // width/height of upcoming preview (in blocks)
//-------------------------------------------------------------------------
// game variables (initialized during reset)
//-------------------------------------------------------------------------
var dx, dy, // pixel size of a single tetris block
blocks, // 2 dimensional array (nx*ny) representing tetris court - either empty block or occupied by a 'piece'
actions, // queue of user actions (inputs)
playing, // true|false - game is in progress
dt, // time since starting this game
current, // the current piece
next, // the next piece
score, // the current score
vscore, // the currently displayed score (it catches up to score in small chunks - like a spinning slot machine)
rows, // number of completed rows in the current game
step; // how long before current piece drops by 1 row
//-------------------------------------------------------------------------
// tetris pieces
//
// blocks: each element represents a rotation of the piece (0, 90, 180, 270)
// each element is a 16 bit integer where the 16 bits represent
// a 4x4 set of blocks, e.g. j.blocks[0] = 0x44C0
//
// 0100 = 0x4 << 3 = 0x4000
// 0100 = 0x4 << 2 = 0x0400
// 1100 = 0xC << 1 = 0x00C0
// 0000 = 0x0 << 0 = 0x0000
// ------
// 0x44C0
//
//-------------------------------------------------------------------------
var i = { id: 'i', size: 4, blocks: [0x0F00, 0x2222, 0x00F0, 0x4444], color: 'cyan' };
var j = { id: 'j', size: 3, blocks: [0x44C0, 0x8E00, 0x6440, 0x0E20], color: 'blue' };
var l = { id: 'l', size: 3, blocks: [0x4460, 0x0E80, 0xC440, 0x2E00], color: 'orange' };
var o = { id: 'o', size: 2, blocks: [0xCC00, 0xCC00, 0xCC00, 0xCC00], color: 'yellow' };
var s = { id: 's', size: 3, blocks: [0x06C0, 0x8C40, 0x6C00, 0x4620], color: 'green' };
var t = { id: 't', size: 3, blocks: [0x0E40, 0x4C40, 0x4E00, 0x4640], color: 'purple' };
var z = { id: 'z', size: 3, blocks: [0x0C60, 0x4C80, 0xC600, 0x2640], color: 'red' };
//------------------------------------------------
// do the bit manipulation and iterate through each
// occupied block (x,y) for a given piece
//------------------------------------------------
function eachblock(type, x, y, dir, fn) {
var bit, result, row = 0, col = 0, blocks = type.blocks[dir];
for(bit = 0x8000 ; bit > 0 ; bit = bit >> 1) {
if (blocks & bit) {
fn(x + col, y + row);
}
if (++col === 4) {
col = 0;
++row;
}
}
};
//-----------------------------------------------------
// check if a piece can fit into a position in the grid
//-----------------------------------------------------
function occupied(type, x, y, dir) {
var result = false
eachblock(type, x, y, dir, function(x, y) {
if ((x < 0) || (x >= nx) || (y < 0) || (y >= ny) || getBlock(x,y))
result = true;
});
return result;
};
function unoccupied(type, x, y, dir) {
return !occupied(type, x, y, dir);
};
//-----------------------------------------
// start with 4 instances of each piece and
// pick randomly until the 'bag is empty'
//-----------------------------------------
var pieces = [];
function randomPiece() {
if (pieces.length == 0)
pieces = [i,i,i,i,j,j,j,j,l,l,l,l,o,o,o,o,s,s,s,s,t,t,t,t,z,z,z,z];
var type = pieces.splice(random(0, pieces.length-1), 1)[0];
return { type: type, dir: DIR.UP, x: Math.round(random(0, nx - type.size)), y: 0 };
};
//-------------------------------------------------------------------------
// GAME LOOP
//-------------------------------------------------------------------------
function run() {
addEvents(); // attach keydown and resize events
var last = now = timestamp();
function frame() {
now = timestamp();
update(Math.min(1, (now - last) / 1000.0)); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab
draw();
last = now;
requestAnimationFrame(frame, canvas);
}
resize(); // setup all our sizing information
reset(); // reset the per-game variables
frame(); // start the first frame
};
function addEvents() {
document.addEventListener('keydown', keydown, false);
window.addEventListener('resize', resize, false);
};
function resize(event) {
canvas.width = canvas.clientWidth; // set canvas logical size equal to its physical size
canvas.height = canvas.clientHeight; // (ditto)
ucanvas.width = ucanvas.clientWidth;
ucanvas.height = ucanvas.clientHeight;
dx = canvas.width / nx; // pixel size of a single tetris block
dy = canvas.height / ny; // (ditto)
invalidate();
invalidateNext();
};
function keydown(ev) {
var handled = false;
if (playing) {
switch(ev.keyCode) {
case KEY.LEFT: actions.push(DIR.LEFT); handled = true; break;
case KEY.RIGHT: actions.push(DIR.RIGHT); handled = true; break;
case KEY.UP: actions.push(DIR.UP); handled = true; break;
case KEY.DOWN: actions.push(DIR.DOWN); handled = true; break;
case KEY.ESC: lose(); handled = true; break;
}
}
else if (ev.keyCode == KEY.SPACE) {
play();
handled = true;
}
if (handled)
ev.preventDefault(); // prevent arrow keys from scrolling the page (supported in IE9+ and all other browsers)
};
//-------------------------------------------------------------------------
// GAME LOGIC
//-------------------------------------------------------------------------
function play() { hide('start'); reset(); playing = true; };
function lose() { show('start'); setVisualScore(); playing = false; };
function setVisualScore(n) { vscore = n || score; invalidateScore(); };
function setScore(n) { score = n; setVisualScore(n); };
function addScore(n) { score = score + n; };
function clearScore() { setScore(0); };
function clearRows() { setRows(0); };
function setRows(n) { rows = n; step = Math.max(speed.min, speed.start - (speed.decrement*rows)); invalidateRows(); };
function addRows(n) { setRows(rows + n); };
function getBlock(x,y) { return (blocks && blocks[x] ? blocks[x][y] : null); };
function setBlock(x,y,type) { blocks[x] = blocks[x] || []; blocks[x][y] = type; invalidate(); };
function clearBlocks() { blocks = []; invalidate(); }
function clearActions() { actions = []; };
function setCurrentPiece(piece) { current = piece || randomPiece(); invalidate(); };
function setNextPiece(piece) { next = piece || randomPiece(); invalidateNext(); };
function reset() {
dt = 0;
clearActions();
clearBlocks();
clearRows();
clearScore();
setCurrentPiece(next);
setNextPiece();
};
function update(idt) {
if (playing) {
if (vscore < score)
setVisualScore(vscore + 1);
handle(actions.shift());
dt = dt + idt;
if (dt > step) {
dt = dt - step;
drop();
}
}
};
function handle(action) {
switch(action) {
case DIR.LEFT: move(DIR.LEFT); break;
case DIR.RIGHT: move(DIR.RIGHT); break;
case DIR.UP: rotate(); break;
case DIR.DOWN: drop(); break;
}
};
function move(dir) {
var x = current.x, y = current.y;
switch(dir) {
case DIR.RIGHT: x = x + 1; break;
case DIR.LEFT: x = x - 1; break;
case DIR.DOWN: y = y + 1; break;
}
if (unoccupied(current.type, x, y, current.dir)) {
current.x = x;
current.y = y;
invalidate();
return true;
}
else {
return false;
}
};
function rotate(dir) {
var newdir = (current.dir == DIR.MAX ? DIR.MIN : current.dir + 1);
if (unoccupied(current.type, current.x, current.y, newdir)) {
current.dir = newdir;
invalidate();
}
};
function drop() {
if (!move(DIR.DOWN)) {
addScore(10);
dropPiece();
removeLines();
setCurrentPiece(next);
setNextPiece(randomPiece());
clearActions();
if (occupied(current.type, current.x, current.y, current.dir)) {
lose();
}
}
};
function dropPiece() {
eachblock(current.type, current.x, current.y, current.dir, function(x, y) {
setBlock(x, y, current.type);
});
};
function removeLines() {
var x, y, complete, n = 0;
for(y = ny ; y > 0 ; --y) {
complete = true;
for(x = 0 ; x < nx ; ++x) {
if (!getBlock(x, y))
complete = false;
}
if (complete) {
removeLine(y);
y = y + 1; // recheck same line
n++;
}
}
if (n > 0) {
addRows(n);
addScore(100*Math.pow(2,n-1)); // 1: 100, 2: 200, 3: 400, 4: 800
}
};
function removeLine(n) {
var x, y;
for(y = n ; y >= 0 ; --y) {
for(x = 0 ; x < nx ; ++x)
setBlock(x, y, (y == 0) ? null : getBlock(x, y-1));
}
};
//-------------------------------------------------------------------------
// RENDERING
//-------------------------------------------------------------------------
var invalid = {};
function invalidate() { invalid.court = true; }
function invalidateNext() { invalid.next = true; }
function invalidateScore() { invalid.score = true; }
function invalidateRows() { invalid.rows = true; }
function draw() {
ctx.save();
ctx.lineWidth = 1;
ctx.translate(0.5, 0.5); // for crisp 1px black lines
drawCourt();
drawNext();
drawScore();
drawRows();
ctx.restore();
};
function drawCourt() {
if (invalid.court) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (playing)
drawPiece(ctx, current.type, current.x, current.y, current.dir);
var x, y, block;
for(y = 0 ; y < ny ; y++) {
for (x = 0 ; x < nx ; x++) {
if (block = getBlock(x,y))
drawBlock(ctx, x, y, block.color);
}
}
ctx.strokeRect(0, 0, nx*dx - 1, ny*dy - 1); // court boundary
invalid.court = false;
}
};
function drawNext() {
if (invalid.next) {
var padding = (nu - next.type.size) / 2; // half-arsed attempt at centering next piece display
uctx.save();
uctx.translate(0.5, 0.5);
uctx.clearRect(0, 0, nu*dx, nu*dy);
drawPiece(uctx, next.type, padding, padding, next.dir);
uctx.strokeStyle = 'black';
uctx.strokeRect(0, 0, nu*dx - 1, nu*dy - 1);
uctx.restore();
invalid.next = false;
}
};
function drawScore() {
if (invalid.score) {
html('score', (\"00000\" + Math.floor(vscore)).slice(-5));
invalid.score = false;
}
};
function drawRows() {
if (invalid.rows) {
html('rows', rows);
invalid.rows = false;
}
};
function drawPiece(ctx, type, x, y, dir) {
eachblock(type, x, y, dir, function(x, y) {
drawBlock(ctx, x, y, type.color);
});
};
function drawBlock(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x*dx, y*dy, dx, dy);
ctx.strokeRect(x*dx, y*dy, dx, dy)
};
//-------------------------------------------------------------------------
// FINALLY, lets run the game
//-------------------------------------------------------------------------
run();
</script>
</body>
</html>";
}
default
{
changed(integer change) {
if (change & (CHANGED_REGION_START | CHANGED_REGION | CHANGED_TELEPORT))
llResetScript();
}
state_entry() {
llOwnerSay("Web Tetris " + VERSION);
llRequestURL();
}
on_rez(integer start_param) {
llResetScript();
}
http_request(key id, string method, string body) {
if (method == URL_REQUEST_GRANTED) {
myURL = body;
browse(FACE, body);
}
else if (method == "GET") {
string path_info = llGetHTTPHeader(id, "x-path-info");
// Appel url de base
if (path_info == "") {
// Réponse
llSetContentType(id, CONTENT_TYPE_HTML);
llHTTPResponse(id, 200, getPage());
}
}
}
}
Et cliquez dessus, au bout d'un moment (le temps que le serveur se crée et le programme se charge) vous devez obtenir le jeu :
un peu adapté pour SL dans un script.
Si ce n'était le temps de chargement il y aurait une piste intéressante pour élargir les possibilités de nos scripts en faisant directement appel à des librairies Javascript externes sans impacter la mémoire disponible...