OsTris
De DigiWiki.
Première étape: construction de la grille.
- Rezzez un cube
- Donnez-lui la taille X=0.050, Y=0.050, Z=0.050 (important!).
- Renommez-le en l'appelant Cube.
- Copiez Cube, et donnez un nom à la copie. Appelez-là SLTRis, par exemple.
- Prenez Cube pour l'intégrer à votre inventaire.
- Copier Cube depuis votre inventaire dans celui de SLTRis.
- Faites glisser Cube vers SLTRis depuis votre inventaire en maintenant la touche.
- CTRL enfoncée, puis quand le cube SLTRis apparait encadré de rouge, relâchez le bouton.
- A présent, utilisez l'onglet Contenu de l'objet SLTRis pour vérifier si Cube s'y trouve.
- Cliquez sur "Nouveau Script" pour créer un script.
- Copier/Coller le code ci-dessous dans ce nouveau script.
// Position en cours de création dans la grille (X de 0 à 9, Y de 0 à 19) integer X; integer Z; // Position et taille du cube d'origine vector Pos; vector Scale; // Rezze un cube RezCube() { llRezObject("Cube", Pos + <Scale.x * X, 0, Scale.z * Z>, ZERO_VECTOR, ZERO_ROTATION, 0); } default { touch_start(integer total_number) { // Demande la permission de toucher aux liaisons llRequestPermissions(llGetOwner(), PERMISSION_CHANGE_LINKS); } // Des que la permission est accordée run_time_permissions(integer perm) { if (perm && PERMISSION_CHANGE_LINKS) { state build; } } } state build { state_entry() { // Recupere position et taille du cube d'origine Pos = llGetPos(); Scale = llGetScale(); X = 0; Z = 0; // Deplace-le a sa position definitive : ce sera le prim 1 llSetPos(Pos + <Scale.x * 9, 0, Scale.z * 19>); RezCube(); } // Cet evenement se declenche quand un cube a été rezzé object_rez(key id) { // Le nouvel objet rezzé est lié à celui-ci, mais celui-ci reste le père. Le nouvel objet rezzé prendra donc le numéro 2. Les objets déja attachés suivront : 3, 4, 5... llCreateLink(id, TRUE); ++X; // Il faut bien s'arrêter un jour ! if (X == 9 && Z == 19) state fin; if (X > 9) { X = 0; ++Z; } // Et on rezze le cube suivant... RezCube(); } } state fin { state_entry() { llOwnerSay("C'est fini !"); } }
Voilà... A présent, touchez SLTRis, accordez-lui la permission qu'il demande, puis patientez deux à trois bonnes minutes : SLTRis est en train de construire tout seul la grille de 200 cubes, alignés en 20 lignes de 10 colonnes. Le temps pour vous de lire la suite...
Deuxième étape: le moteur de jeu (mode démo).
- Créer un nouveau script.
- Copier/coller dans le script le code ci-dessous.
// Temporisation en seconde pour la chute d'une piece float INTERVALTIMERSTART = 1.5; float INTERVALTIMERDEMO = 0.5; // Largeur et hauteur du terrain de jeu integer PLAYGROUNDWIDTH = 10; integer PLAYGROUNDHEIGHT = 20; // Coordonnées relatives des carres occupes par les pieces par rapport au centre de rotation. // Si 0,0 est specifie en premier, la piece ne doit pas tourner (le "O" par exemple") list PIECES = [ <1.0, 0.0, 0.0>, 0, -1, 0, 0, 0, 1, 0, 2, // "I" Rouge <1.0, 1.0, 0.0>, 0, -1, 0, 0, 0, 1, -1, 1, // "J" Jaune <1.0, 0.0, 1.0>, 0, -1, 0, 0, 0, 1, 1, 1, // "L" Magenta <0.0, 0.0, 1.0>, 0, 0, 0, 1, 1, 0, 1, 1, // "O" Bleu <- Celle-ci ne tourne pas ! <0.0, 1.0, 0.0>, -1, -1, -1, 0, 0, 0, 0, 1, // "S" Vert <0.5, 0.5, 1.0>, -1, 0, 1, 0, 0, 0, 0, 1, // "T" Brun <0.5, 1.0, 1.0>, -1, -1, 0, -1, 0, 0, 1, 0 // "Z" Cyan ]; // Pieces integer gPieceType; list gPieceParams; vector gPieceColor; // Caractéristiques de la piece en cours integer gPieceX; integer gPieceY; integer gPieceAngle; list gPieceCoordinates; // Anciennes caractéristiques de la piece en cours integer gPieceOldX; integer gPieceOldY; integer gPieceOldAngle; list gPieceOldCoordinates; // Grille string gPlayGroundContent; string gEmptyRow; // Prisme plat integer FIRSTCUBELINKNUMBER = 1; DisplayCubes(list coordinates) { integer i; integer Count = llGetListLength(coordinates); for (i = 0; i < Count; i += 2) DisplayCube(llList2Integer(coordinates, i), llList2Integer(coordinates, i + 1), gPieceColor); } // Efface les cubes qui composent une piece EraseCubes(list coordinates) { integer i; integer Count = llGetListLength(coordinates); for (i = 0; i < Count; i += 2) EraseCube(llList2Integer(coordinates, i), llList2Integer(coordinates, i + 1)); } EraseCube(integer x, integer y) { integer Idx = y * PLAYGROUNDWIDTH + FIRSTCUBELINKNUMBER + x; llSetLinkColor(Idx, <1, 1, 1>, ALL_SIDES); llSetLinkAlpha(Idx, .25, ALL_SIDES); } DisplayCube(integer x, integer y, vector color) { integer Idx = y * PLAYGROUNDWIDTH + FIRSTCUBELINKNUMBER + x; llSetLinkColor(Idx, color, ALL_SIDES); llSetLinkAlpha(Idx, 1, ALL_SIDES); } // Initialise le jeu InitGame() { // Construit une ligne vide gEmptyRow = ""; integer Idx; for (Idx = 0; Idx < PLAYGROUNDWIDTH; Idx += 1) gEmptyRow += "0"; } // Initialise la grille InitPlay() { // Construit le playground gPlayGroundContent = ""; integer Idx; for (Idx = 0; Idx < PLAYGROUNDHEIGHT; Idx += 1) gPlayGroundContent += gEmptyRow; RefreshPlayGround(0); } // Envoie un message sur le debug channel Debug(list msg) { llSay(DEBUG_CHANNEL, llList2CSV(msg)); } // Affiche la piece en cours en effacant la position precedente // On ne touche pas aux cellules qui "restent" DisplayMovedPiece() { list OldXYList = gPieceOldCoordinates; list NewXYList = gPieceCoordinates; integer Count = llGetListLength(OldXYList); integer i = 0; do { integer Idx = llListFindList(NewXYList, llList2List(OldXYList, i, i+1)); if (Idx % 2) { i += 2; } else { OldXYList = llDeleteSubList(OldXYList, i, i+1); NewXYList = llDeleteSubList(NewXYList, Idx, Idx+1); Count -= 2; } } while(i < Count); // On efface seulement les cubes qui disparaissent... EraseCubes(OldXYList); // Pour n'afficher que les nouveaux DisplayCubes(NewXYList); } // Rotation SetRotatedPieceParams() { gPieceCoordinates = []; integer i; for (i = 0; i < 8; i += 2) { integer OffsetX = llList2Integer(gPieceParams, i); integer OffsetY = llList2Integer(gPieceParams, i + 1); // Neutralise la rotation pour les pieces symétriques comme "O" qui ne tournent pas. // Ce genre de pieces est repéré par les premieres coordonnees toujours à 0,0 if (i == 0) if (OffsetX == 0 && OffsetY == 0) gPieceAngle = 0; if (gPieceAngle == 0) gPieceCoordinates += [gPieceX + OffsetX, gPieceY + OffsetY]; else if (gPieceAngle == 1) gPieceCoordinates += [gPieceX - OffsetY, gPieceY + OffsetX]; else if (gPieceAngle == 2) gPieceCoordinates += [gPieceX - OffsetX, gPieceY - OffsetY]; else gPieceCoordinates += [gPieceX + OffsetY, gPieceY - OffsetX]; } } // retourne TRUE si la la piece peut occuper la position suivante integer IsPositionFree() { integer i; for (i = 0; i < 8; i += 2) { integer X = llList2Integer(gPieceCoordinates, i); integer Y = llList2Integer(gPieceCoordinates, i + 1); if (X < 0 || Y < 0 || X >= PLAYGROUNDWIDTH || Y >= PLAYGROUNDHEIGHT) return FALSE; integer Idx = X + Y * PLAYGROUNDWIDTH; if (llGetSubString(gPlayGroundContent, Idx, Idx) != "0") return FALSE; } return TRUE; } // Valide la nouvelle position StoreOldPosition() { gPieceOldX = gPieceX; gPieceOldY = gPieceY; gPieceOldAngle = gPieceAngle; gPieceOldCoordinates = gPieceCoordinates; } // Invalide la nouvelle position RetrieveOldPosition() { gPieceX = gPieceOldX; gPieceY = gPieceOldY; gPieceAngle = gPieceOldAngle; gPieceCoordinates = gPieceOldCoordinates; } // Fige la piece dans sa position actuelle : elle ne bougera plus FreezePiece() { string Piece = (string) (gPieceType + 1); integer i; for (i = 0; i < 8; i += 2) { integer Idx = llList2Integer(gPieceCoordinates, i) + llList2Integer(gPieceCoordinates, i + 1) * PLAYGROUNDWIDTH; gPlayGroundContent = llDeleteSubString(llInsertString(gPlayGroundContent, Idx, Piece), Idx + 1, Idx + 1); } } // Redessine la grille à partir de la ligne rowStart RefreshPlayGround(integer rowStart) { integer X; integer Y; for (X = 0; X < PLAYGROUNDWIDTH; X += 1) for (Y = rowStart; Y < PLAYGROUNDHEIGHT; Y += 1) { integer Idx = X + Y * PLAYGROUNDWIDTH; integer CodeCube = (integer) llGetSubString(gPlayGroundContent, Idx, Idx); if (CodeCube == 0) EraseCube(X,Y); else DisplayCube(X,Y, llList2Vector(PIECES, (CodeCube - 1) * 9)); } } // Retourne un nombre aleatoire entre min et max integer Random(integer min, integer max) { return (integer) llFrand((float) max + 1.0 - (float) min) + min; } // Affiche une nouvelle piece PlaceNewPiece() { gPieceType = Random(0, llGetListLength(PIECES) / 9 - 1); gPieceX = Random(4, PLAYGROUNDWIDTH - 4); gPieceY = 2; gPieceAngle = Random(0, 3); gPieceParams = llList2List(PIECES, gPieceType * 9 + 1, gPieceType * 9 + 8); gPieceColor = llList2Vector(PIECES, gPieceType * 9); SetRotatedPieceParams(); } // Apres le deplacement d'un joueur AfterPlayerMove() { SetRotatedPieceParams(); if (IsPositionFree()) { DisplayMovedPiece(); StoreOldPosition(); } else { RetrieveOldPosition(); } } integer gDemoMove; default { state_entry() { InitGame(); state demo; } } state demo { state_entry() { InitPlay(); PlaceNewPiece(); DisplayCubes(gPieceCoordinates); StoreOldPosition(); llSetTimerEvent(INTERVALTIMERDEMO); } timer() { gDemoMove = !gDemoMove; if (gDemoMove) { gPieceY += 1; SetRotatedPieceParams(); if (IsPositionFree()) { DisplayMovedPiece(); StoreOldPosition(); } else { RetrieveOldPosition(); FreezePiece(); PlaceNewPiece(); if (!IsPositionFree()) { llSetTimerEvent(0); InitPlay(); PlaceNewPiece(); llSetTimerEvent(INTERVALTIMERDEMO); } DisplayCubes(gPieceCoordinates); StoreOldPosition(); } } else { integer Move = Random(0, 4); if (Move == 0) gPieceX += 1; else if (Move == 1) gPieceX -= 1; else if (Move == 2) gPieceY += 1; else if (++gPieceAngle > 3) gPieceAngle = 0; AfterPlayerMove(); } } state_exit() { llSetTimerEvent(0); } }
Vous y voilà ! Aussitôt le script compilé et sauvegardé, regardez votre Tetris executer une démo. La grille est effacée, puis les pieces commencent à tomber, à tourner, puis à s'empiler jusqu'en haut. Une fois le haut de la grille atteint, la grille est de nouveau effacée et la démo recommence...
Deuxième étape: le moteur de jeu (mode jouable).
Voilà ci-dessous le script complet pour rendre ce jeu de Tétris jouable. Il vous suffit de remplacer le précédent par celui-ci:
// Temporisation en seconde pour la chute d'une piece float INTERVALTIMERSTART = 1.5; float INTERVALTIMERDEMO = 0.5; // Largeur et hauteur du terrain de jeu integer PLAYGROUNDWIDTH = 10; integer PLAYGROUNDHEIGHT = 20; // Coordonnées relatives des carres occupes par les pieces par rapport au centre de rotation. // Si 0,0 est specifie en premier, la piece ne doit pas tourner (le "O" par exemple") list PIECES = [ <1.0, 0.0, 0.0>, 0, -1, 0, 0, 0, 1, 0, 2, // "I" Rouge <1.0, 1.0, 0.0>, 0, -1, 0, 0, 0, 1, -1, 1, // "J" Jaune <1.0, 0.0, 1.0>, 0, -1, 0, 0, 0, 1, 1, 1, // "L" Magenta <0.0, 0.0, 1.0>, 0, 0, 0, 1, 1, 0, 1, 1, // "O" Bleu <- Celle-ci ne tourne pas ! <0.0, 1.0, 0.0>, -1, -1, -1, 0, 0, 0, 0, 1, // "S" Vert <0.5, 0.5, 1.0>, -1, 0, 1, 0, 0, 0, 0, 1, // "T" Brun <0.5, 1.0, 1.0>, -1, -1, 0, -1, 0, 0, 1, 0 // "Z" Cyan ]; // Pieces integer gPieceType; list gPieceParams; vector gPieceColor; // Caractéristiques de la piece en cours integer gPieceX; integer gPieceY; integer gPieceAngle; list gPieceCoordinates; // Anciennes caractéristiques de la piece en cours integer gPieceOldX; integer gPieceOldY; integer gPieceOldAngle; list gPieceOldCoordinates; // Grille string gPlayGroundContent; string gEmptyRow; // Prisme plat integer FIRSTCUBELINKNUMBER = 1; DisplayCubes(list coordinates) { integer i; integer Count = llGetListLength(coordinates); for (i = 0; i < Count; i += 2) DisplayCube(llList2Integer(coordinates, i), llList2Integer(coordinates, i + 1), gPieceColor); } // Efface les cubes qui composent une piece EraseCubes(list coordinates) { integer i; integer Count = llGetListLength(coordinates); for (i = 0; i < Count; i += 2) EraseCube(llList2Integer(coordinates, i), llList2Integer(coordinates, i + 1)); } EraseCube(integer x, integer y) { integer Idx = y * PLAYGROUNDWIDTH + FIRSTCUBELINKNUMBER + x; llSetLinkColor(Idx, <1, 1, 1>, ALL_SIDES); llSetLinkAlpha(Idx, .25, ALL_SIDES); } DisplayCube(integer x, integer y, vector color) { integer Idx = y * PLAYGROUNDWIDTH + FIRSTCUBELINKNUMBER + x; llSetLinkColor(Idx, color, ALL_SIDES); llSetLinkAlpha(Idx, 1, ALL_SIDES); } // Initialise le jeu InitGame() { // Construit une ligne vide gEmptyRow = ""; integer Idx; for (Idx = 0; Idx < PLAYGROUNDWIDTH; Idx += 1) gEmptyRow += "0"; } // Initialise la grille InitPlay() { // Construit le playground gPlayGroundContent = ""; integer Idx; for (Idx = 0; Idx < PLAYGROUNDHEIGHT; Idx += 1) gPlayGroundContent += gEmptyRow; RefreshPlayGround(0); } // Envoie un message sur le debug channel Debug(list msg) { llSay(DEBUG_CHANNEL, llList2CSV(msg)); } // Affiche la piece en cours en effacant la position precedente // On ne touche pas aux cellules qui "restent" DisplayMovedPiece() { list OldXYList = gPieceOldCoordinates; list NewXYList = gPieceCoordinates; integer Count = llGetListLength(OldXYList); integer i = 0; do { integer Idx = llListFindList(NewXYList, llList2List(OldXYList, i, i+1)); if (Idx % 2) { i += 2; } else { OldXYList = llDeleteSubList(OldXYList, i, i+1); NewXYList = llDeleteSubList(NewXYList, Idx, Idx+1); Count -= 2; } } while(i < Count); // On efface seulement les cubes qui disparaissent... EraseCubes(OldXYList); // Pour n'afficher que les nouveaux DisplayCubes(NewXYList); } // Rotation SetRotatedPieceParams() { gPieceCoordinates = []; integer i; for (i = 0; i < 8; i += 2) { integer OffsetX = llList2Integer(gPieceParams, i); integer OffsetY = llList2Integer(gPieceParams, i + 1); // Neutralise la rotation pour les pieces symétriques comme "O" qui ne tournent pas. // Ce genre de pieces est repéré par les premieres coordonnees toujours à 0,0 if (i == 0) if (OffsetX == 0 && OffsetY == 0) gPieceAngle = 0; if (gPieceAngle == 0) gPieceCoordinates += [gPieceX + OffsetX, gPieceY + OffsetY]; else if (gPieceAngle == 1) gPieceCoordinates += [gPieceX - OffsetY, gPieceY + OffsetX]; else if (gPieceAngle == 2) gPieceCoordinates += [gPieceX - OffsetX, gPieceY - OffsetY]; else gPieceCoordinates += [gPieceX + OffsetY, gPieceY - OffsetX]; } } // retourne TRUE si la la piece peut occuper la position suivante integer IsPositionFree() { integer i; for (i = 0; i < 8; i += 2) { integer X = llList2Integer(gPieceCoordinates, i); integer Y = llList2Integer(gPieceCoordinates, i + 1); if (X < 0 || Y < 0 || X >= PLAYGROUNDWIDTH || Y >= PLAYGROUNDHEIGHT) return FALSE; integer Idx = X + Y * PLAYGROUNDWIDTH; if (llGetSubString(gPlayGroundContent, Idx, Idx) != "0") return FALSE; } return TRUE; } // Valide la nouvelle position StoreOldPosition() { gPieceOldX = gPieceX; gPieceOldY = gPieceY; gPieceOldAngle = gPieceAngle; gPieceOldCoordinates = gPieceCoordinates; } // Invalide la nouvelle position RetrieveOldPosition() { gPieceX = gPieceOldX; gPieceY = gPieceOldY; gPieceAngle = gPieceOldAngle; gPieceCoordinates = gPieceOldCoordinates; } // Fige la piece dans sa position actuelle : elle ne bougera plus FreezePiece() { string Piece = (string) (gPieceType + 1); integer i; for (i = 0; i < 8; i += 2) { integer Idx = llList2Integer(gPieceCoordinates, i) + llList2Integer(gPieceCoordinates, i + 1) * PLAYGROUNDWIDTH; gPlayGroundContent = llDeleteSubString(llInsertString(gPlayGroundContent, Idx, Piece), Idx + 1, Idx + 1); } } // Redessine la grille à partir de la ligne rowStart RefreshPlayGround(integer rowStart) { integer X; integer Y; for (X = 0; X < PLAYGROUNDWIDTH; X += 1) for (Y = rowStart; Y < PLAYGROUNDHEIGHT; Y += 1) { integer Idx = X + Y * PLAYGROUNDWIDTH; integer CodeCube = (integer) llGetSubString(gPlayGroundContent, Idx, Idx); if (CodeCube == 0) EraseCube(X,Y); else DisplayCube(X,Y, llList2Vector(PIECES, (CodeCube - 1) * 9)); } } // Retourne un nombre aleatoire entre min et max integer Random(integer min, integer max) { return (integer) llFrand((float) max + 1.0 - (float) min) + min; } // Affiche une nouvelle piece PlaceNewPiece() { gPieceType = Random(0, llGetListLength(PIECES) / 9 - 1); gPieceX = Random(4, PLAYGROUNDWIDTH - 4); gPieceY = 2; gPieceAngle = Random(0, 3); gPieceParams = llList2List(PIECES, gPieceType * 9 + 1, gPieceType * 9 + 8); gPieceColor = llList2Vector(PIECES, gPieceType * 9); SetRotatedPieceParams(); } // Apres le deplacement d'un joueur AfterPlayerMove() { SetRotatedPieceParams(); if (IsPositionFree()) { DisplayMovedPiece(); StoreOldPosition(); } else { RetrieveOldPosition(); } } // Supprime les lignes completes integer ReArrangeBricks() { // Redétermine la toute premiere ligne non vide gRowStart = 0; while (IsEmptyRow(gRowStart) && gRowStart < PLAYGROUNDHEIGHT) gRowStart += 1; // Vide les lignes pleines integer DownLines; integer Row; for (Row = gRowStart; Row < PLAYGROUNDHEIGHT; Row += 1) if (IsFullRow(Row)) { // Suprime la ligne concernée integer Idx = Row * PLAYGROUNDWIDTH; gPlayGroundContent = gEmptyRow + llDeleteSubString(gPlayGroundContent, Idx, Idx + PLAYGROUNDWIDTH - 1); DownLines += 1; } // Il y'a eu des lignes en moins if (DownLines) { RefreshPlayGround(gRowStart); gRowStart += DownLines; } return DownLines; } // Renvoie TRUE si la ligne est vide integer IsEmptyRow(integer row) { integer Idx = row * PLAYGROUNDWIDTH; return (llGetSubString(gPlayGroundContent, Idx, Idx + PLAYGROUNDWIDTH - 1) == gEmptyRow); } // Renvoie TRUE si la ligne est pleine integer IsFullRow(integer row) { integer Idx = row * PLAYGROUNDWIDTH; return (llSubStringIndex(llGetSubString(gPlayGroundContent, Idx, Idx + PLAYGROUNDWIDTH - 1), "0") == -1); } integer gDemoMove; float gTimerEvent; integer gRowStart; default { state_entry() { InitGame(); state demo; } } state demo { state_entry() { InitPlay(); PlaceNewPiece(); DisplayCubes(gPieceCoordinates); StoreOldPosition(); llSetTimerEvent(INTERVALTIMERDEMO); } timer() { gDemoMove = !gDemoMove; if (gDemoMove) { gPieceY += 1; SetRotatedPieceParams(); if (IsPositionFree()) { DisplayMovedPiece(); StoreOldPosition(); } else { RetrieveOldPosition(); FreezePiece(); PlaceNewPiece(); if (!IsPositionFree()) { llSetTimerEvent(0); InitPlay(); PlaceNewPiece(); llSetTimerEvent(INTERVALTIMERDEMO); } DisplayCubes(gPieceCoordinates); StoreOldPosition(); } } else { integer Move = Random(0, 4); if (Move == 0) gPieceX += 1; else if (Move == 1) gPieceX -= 1; else if (Move == 2) gPieceY += 1; else if (++gPieceAngle > 3) gPieceAngle = 0; AfterPlayerMove(); } } touch_start(integer count) { llRequestPermissions(llDetectedKey(0), PERMISSION_TAKE_CONTROLS); } run_time_permissions(integer perms) { if (PERMISSION_TAKE_CONTROLS & perms) { llTakeControls(CONTROL_FWD | CONTROL_BACK | CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT, TRUE, FALSE); state playing; } } state_exit() { llSetTimerEvent(0); } } state playing { state_entry() { InitPlay(); PlaceNewPiece(); DisplayCubes(gPieceCoordinates); StoreOldPosition(); gTimerEvent = INTERVALTIMERSTART; llSetTimerEvent(gTimerEvent); } timer() { gPieceY += 1; SetRotatedPieceParams(); if (IsPositionFree()) { DisplayMovedPiece(); StoreOldPosition(); } else { RetrieveOldPosition(); FreezePiece(); ReArrangeBricks(); PlaceNewPiece(); if (!IsPositionFree()) { state demo; } else { DisplayCubes(gPieceCoordinates); StoreOldPosition(); } } } control(key id, integer level, integer edge) { integer start = level & edge; if (CONTROL_BACK & start) { gPieceY += 1; AfterPlayerMove(); } else if (CONTROL_FWD & start) { if (++gPieceAngle > 3) gPieceAngle = 0; AfterPlayerMove(); } else if (CONTROL_ROT_LEFT & start) { gPieceX -= 1; AfterPlayerMove(); } else if (CONTROL_ROT_RIGHT & start) { gPieceX += 1; AfterPlayerMove(); } } state_exit() { llSetTimerEvent(0); } }
Bon amusement!