Kirjoittaja: Turatzuro
Kirjoitettu: 06.07.2005 – 04.07.2015
Tagit: pelinteko, koodi näytille, peli, vinkki
Shuffle on legendaarinen palikansiirtelypeli, jossa on ideana siirrellä vierekkäisiä palikoita niin, että saadaan aikaiseksi oikeanlainen kuva tai numerojärjestys. Tässä pätkässä taustakuvan voi luoda jpegistä, mutta tuen tekeminen muillekin formaateille kuten gif:lle ja png:lle, lienee melko helppoa.
Asennus:
Koodin toimintaan tarvitaan ensinnäkin tiedosto shuffle.php (saat päättää tiedostonnimen itse), joka sisältää alla olevan koodin. Lisäksi tarvitaan /use sekä /start-kansiot, joille kummillekin on annettava oikeudet 777 (tai vastaava). /use-kansion sisään pitää vielä luoda tiedostot jarjestys.txt sekä ennatys.txt, jotka myös tarvitsevat luku- ja kirjoitusoikeudet.
Taustakuvan luonti:
Ensin pitää valita sopivan kokoinen taustakuva, esimerkiksi esimerkissäni on 300x300. Seuraavaksi valitaan koodin alussa olevista muuttujista $conf['width'] ja $conf['height'] sopiva koko yhdelle ruudulle. Mikäli kuva on hieman isompi kuin ruutuja tulee, jätetään ylijäävä osa huomioimatta.
Tämän jälkeen upataan valitsemasi tiedosto /start-kansioon, ja annetaan kuvalle 777-oikeudet. Kun tämä on tehty, suoritetaan peliskripti _kerran_, jolloin se luo automaagisesti kaikki tarpeelliset tiedostot. Seuraavaksi kannattaa tarkistaa, että peliskripti todellakin poisti /start-kansiossa olevan kuvatiedoston, jottei pikkukuvia luoda joka ajolla uudelleen.
Nyt pelin pitäisi olla toimintakunnossa.
Toiminta:
Pikkukuvat luodaan satunnaisella nimellä, ja niiden oikea järjestys merkataan jarjestys.txt-tiedostoon. Kun peli alkaa, pelin tiedot tallennetaan sessiomuuttujiin talteen. Uutta peliä aloitettaessa kutsutaan session_destroy(), jolloin skripti luo automaagisesti uuden pelin.
$_SESSION['alue'], täällä on varsinainen senhetkinen pelialue
$_SESSION['jarjestys'], Alkuperäinen järjestys, jonka perusteella päätellään voitto
$_SESSION['siirrot'], siirtojen määrä (top10)
$_SESSION['riveja'], Kuinka monta riviä pelialue on korkea
$_SESSION['sarakkeita'], Kuinka monta saraketta pelialue on leveä
$_SESSION['voitto'], Mikäli pelaaja voittaa, tallennetaan varmistus siitä tänne (true/false)
Kun pelaaja voittaa, kysytään nimimerkkiä TOP10-listaan (mikäli tarpeeksi pisteitä), ja tuhotaan vanhat sessiotiedot. Tämän jälkeen pelaaja ohjataan TOP-listalle, josta voi taas aloittaa uuden pelin.
<?php session_start(); $conf['sdir'] = "start/"; // Taustakuva tänne $conf['udir'] = "use/"; // Tänne tulee kaikki resurssitiedostot $conf['jarjestys'] = "use/jarjestys.txt"; // Järjestys sullotaan tänne $conf['ennatys'] = "use/ennatys.txt"; // Ennätykset tänne $blokki['width'] = 100; $blokki['height'] = 100; $sdir_sis = glob($conf['sdir'].'*.jpg'); $spic = $sdir_sis[0]; $buffer = ""; $error = false; function randomname() { return md5(microtime().str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ1234567890abcdefghijklmnopqrstuvwxyzåäö')); } function kirjoita($s,$file) { $s = implode("\n",$s); $kahva = fopen($file,'w'); // Tiedoston avaus kirjoitusmoodiin & tyhjennys flock($kahva,LOCK_EX); // EXCLUSIVE-lukko. (Yksinoikeus) fwrite($kahva,$s); // Kirjoitetaan flock($kahva,LOCK_UN); // Poistetaan luko fclose($kahva); // Suljetaan tiedosto } function tyhjenna_ennatys($ents) { $kahva = fopen($ents,'w'); flock($kahva,LOCK_EX); ftruncate($kahva,0); flock($kahva,LOCK_UN); fclose($kahva); } function lue($file) { $kahva = fopen($file,'r'); flock($kahva,LOCK_SH); $raakadata = fread($kahva,filesize($file)); flock($kahva,LOCK_UN); fclose($kahva); $raakadata = explode("\n",$raakadata); return $raakadata; } function etsi_mahd_siirrot() { $mahdsiirrot = array(); $vasenreuna = floor($_SESSION['tyhjaruutu']/$_SESSION['sarakkeita'])*$_SESSION['sarakkeita']; $oikeareuna = floor($_SESSION['tyhjaruutu']/$_SESSION['sarakkeita'])*$_SESSION['sarakkeita']+$_SESSION['sarakkeita']-1; if($_SESSION['tyhjaruutu']-1 >= 0 && $_SESSION['tyhjaruutu'] != $vasenreuna) $mahdsiirrot[] = ($_SESSION['tyhjaruutu']-1); if($_SESSION['tyhjaruutu']+1 < $_SESSION['riveja']*$_SESSION['sarakkeita'] && $_SESSION['tyhjaruutu'] != $oikeareuna) $mahdsiirrot[] = ($_SESSION['tyhjaruutu']+1); if($_SESSION['tyhjaruutu']-$_SESSION['sarakkeita'] >= 0) $mahdsiirrot[] = ($_SESSION['tyhjaruutu']-$_SESSION['sarakkeita']); if($_SESSION['tyhjaruutu']+$_SESSION['sarakkeita'] < $_SESSION['sarakkeita']*$_SESSION['riveja']) $mahdsiirrot[] = ($_SESSION['tyhjaruutu']+$_SESSION['sarakkeita']); return $mahdsiirrot; } // Tässä toiminteet, joita voidaan suorittaa... if(!empty($_GET['uusipeli']) && isset($_GET['uusipeli'])) { session_destroy(); // Poistetaan session tiedot header('Location: '.$_SERVER['PHP_SELF']); } elseif(!empty($_POST['nimimerkki']) && strlen($_POST['nimimerkki']) > 0 && $_SESSION['voitto'] == true) { $topdata = lue($conf['ennatys']); // Luetaan ensin top10-tiedosto $kahva = fopen($conf['ennatys'],'w'); // Avataan, lukitetaan & tyhjennetään kirjoitusta varten. flock($kahva,LOCK_EX); // Poistetaan nimimerkistä html yms. $pelaaja = stripslashes(htmlspecialchars(str_replace(array("\n","\r","\t","|"),'',$_POST['nimimerkki']))); $raakalista = array(); $filuun = array(); $paikkaloydetty = false; // Sullotaan top-lista oikeassa järjestyksessä arrayyn foreach($topdata as $rivi) { $pilkottu = explode("|",$rivi); if(intval($pilkottu[0]) > $_SESSION['siirtoja'] && !$paikkaloydetty) { $raakalista[] = array($_SESSION['siirtoja'],$pelaaja); $paikkaloydetty = true; } $raakalista[] = $pilkottu; } // Pistetään array tekstikasaksi, erotellaan sarakkeet. Kirjoitetaan vain 10 ensimmäistä tulosta. for($i=0;$i<10;$i++) { if(isset($raakalista[$i])) { $filuun[] = implode("|",$raakalista[$i]); } } // Kirjoitetaan array tiedostoon $filuun = implode("\n",$filuun); fwrite($kahva,$filuun); flock($kahva,LOCK_UN); fclose($kahva); session_destroy(); // Tuhotaan session tiedot, nollataan peli header('Location: '.$_SERVER['PHP_SELF'].'?nayta=Top10'); // Refresh, jotta selain ei valita... Jos selain ei tue headeria, mennään kuitenkin etiäpäim. } if(!empty($spic)) { // Jos skriptalle annetaan uusi kuva taustaksi, luodaan pikkukuvat uudestaan $ainfo = getimagesize($spic); $sarakkeet = floor($ainfo[0]/$blokki['width']); // Montako saraketta luodaan $rivit = floor($ainfo[1]/$blokki['height']); // Montako riviä luodaan if($ainfo['mime'] == 'image/jpeg') { // Varmistetaan, että kuva on varmasti Jpeg foreach(glob($conf['udir']."*.jpg") as $poista) // Poistetaan vanhat tiedostot alta turhaa tilaa viemästä unlink($poista); $akuva = imagecreatefromjpeg($spic); $jarjestys = array(); $uusinimi = 'valkoinen.jpg'; $uusi = imagecreate($blokki['width'],$blokki['height']); // Luodaan uusi kuvapohja $white = imagecolorallocate($uusi, 255, 255, 255); // Taustaväriksi valkoinen imagejpeg($uusi,$conf['udir'].$uusinimi,100); // tallennetaan valkoinen kuva nimellä valkoinen.jpg imagedestroy($uusi); // Tuhotaan muistista $jarjestys[] = '0|'.$uusinimi; // Laitetaan for($a=0; $a < $rivit; $a++) { for($b=0; $b < $sarakkeet; $b++) { if($a==0 && $b==0) continue; $uusinimi = randomname().'.jpg'; $uusi = imagecreatetruecolor($blokki['width'],$blokki['height']); // Kopioidaan oikean kokoinen pala alkuperäisestä kuvasta, ja tallennetaan se satunnaisella nimellä hakemistoon talteen imagecopy($uusi, $akuva, 0, 0, intval($b*$blokki['width']), intval($a*$blokki['height']), $blokki['width'], $blokki['height']); imagejpeg($uusi,$conf['udir'].$uusinimi,100); imagedestroy($uusi); $jarjestys[] = $a.'|'.$uusinimi; } } imagejpeg($akuva,$conf['udir'].'alkup.jpg',100); // Tallennetaan alkuperäinen kuva parhaalla laadulla uuteen paikkaan imagedestroy($akuva); // Tuhotaan kuva muistia varaamasta kirjoita($jarjestys,$conf['jarjestys']); // Kirjoitetaan kuvien oikea järjestys tiedostoon tyhjenna_ennatys($conf['ennatys']); unlink($spic) or $error = 'POISTA KUVATIEDOSTO KANSIOSTA START! MUUTEN PELI STRESSAA PALVELINTA JOKA LATAUSKERRALLA TEKEMÄLLÄ PIKKUKUVAT UUDELLEEN!!!'; } else { die('Taustakuva ei ole JPEG.'); } } switch($_GET['nayta']) { case 'Top10': $buffer .= ' <form method="get" action="'.$_SERVER['PHP_SELF'].'"> <input type="submit" value="Takaisin peliin" name="nayta" /> </form> <table cellspacing="4" cellpadding="1" class="toptable"> <tr> <td align="left"> Pelaaja </td><td align="right"> Siirtoja </td> </tr>'; // Tarkistetaan pääsikö pelaaja toplistaan $toplista = lue($conf['ennatys']); foreach($toplista as $pelaaja) { $buffer .= " <tr>"; $pelaaja = explode("|",rtrim($pelaaja)); $buffer .= ' <td align="left">'.$pelaaja[1].'</td><td align="right">'.$pelaaja[0].'</td>'; $buffer .= " </tr>"; } $buffer .= ' </table>'; break; case 'voitto'; if($_SESSION['voitto'] == true) { $buffer .= '<h2> Onneksi Olkoon! </h2> <p>Ratkaisit pelin '.$_SESSION['siirtoja'].' siirrolla'; // Tarkistetaan pääsikö pelaaja top10-listaan $topkymppiin = false; $topdata = lue($conf['ennatys']); if(count($topdata) == 10) { foreach($topdata as $rivi) { $rivi = explode("|",$rivi); if($rivi[0] > $_SESSION['siirtoja']) { $topkymppiin = true; break; } } } else { $topkymppiin = true; } if($topkymppiin) { $buffer .= ' ja pääsit top-10 listaan.</p> <p> Kirjoita alle nimesi ja paina Lisää-nappia. <form action="'.$_SERVER['PHP_SELF'].'?nayta=Top10" method="post"> <input type="text" name="nimimerkki" size="30" maxlength="30" /> <input type="submit" value="Lisää" /> </form> '; } else { $buffer .= ' mutta et kuitenkaan päässyt top-10 listaan.</p> <p> Jatka top-10-listaan painamalla nappia. <form action="'.$_SERVER['PHP_SELF'].'" method="get"> <input type="submit" value="Top10" name="nayta" /> </form> '; session_destroy(); // Koska sessiotietoja ei tarvita enää top10-listaan lisäämisessä, poistetaan ne. Peli nollaantuu. } $buffer .= '</p> '; } else { $buffer .= 'Jahas, sitä yritetään uunottaa??'; } break; default: // Nyt päästään itse peliosioon. Ensin luodaan sessiomuuttujaan "kuva" pelialueesta, jos sitä ei ole if(!isset($_SESSION['alue'])) { $jarjestys = lue($conf['jarjestys']); // Luetaan järjestystiedosto $rivimaara = count($jarjestys); $rivi = explode('|',$jarjestys[$rivimaara-1]); $_SESSION['riveja'] = $rivi[0]+1; $_SESSION['sarakkeita'] = $rivimaara / ($_SESSION['riveja']); $_SESSION['ruutuja'] = $_SESSION['riveja']*$_SESSION['sarakkeita']; $_SESSION['siirtoja'] = 0; // Tänne tallennetaan siitojen määrä vahdollista top-10 varten $_SESSION['voitto'] = false; // Tällä tarkistetaan, että pelaaja on oikeasti voittanut... $_SESSION['alue'] = array(); // Tähän taulukkoon sotkettu pelialue $_SESSION['jarjestys'] = array(); // Tähän taulukkoon oikea järjestys $_SESSION['tyhjaruutu'] = 0; // Puhdistetaan törky pois, pistetään kamaa taulukkoihin for($i=0; $i<count($jarjestys); $i++) { $rivi = explode('|',rtrim($jarjestys[$i])); $_SESSION['jarjestys'][] = $rivi[1]; $_SESSION['alue'][] = $rivi[1]; } // Järjestellään palaset sekalaiseen järjestykseen for($i=0;$i<($_SESSION['ruutuja']*2);$i++) { $vaihtoehdot = array_flip(etsi_mahd_siirrot()); unset($vaihtoehdot[$edellinen]); // Poistetaan edellisen siirron lähtöruutu, ettei jäädä kieppumaan... $edellinen = $_SESSION['tyhjaruutu']; // Määritetään $edellinen uudestaan $siirrakohtaan = array_rand($vaihtoehdot); // Valitaan satunnaunen siirto $temp = $_SESSION['alue'][$siirrakohtaan]; $_SESSION['alue'][$siirrakohtaan] = 'valkoinen.jpg'; // Siirretään valkoista blokkia $_SESSION['alue'][$_SESSION['tyhjaruutu']] = $temp; // Siirretään klikattua blokkia $_SESSION['tyhjaruutu'] = $siirrakohtaan; // Muutetaan valkoisen blokin senhetkinen paikkatieto } header('Location: '.$_SERVER['PHP_SELF']); // Päivitetään sivu, ettei selaimet valita ... } // Etsitään kaikki mahdolliset siirrot $mahdsiirrot = etsi_mahd_siirrot($_SESSION['tyhjaruutu']); // Mikäli palasta halutaan siirtää, tarkistetaan että siirto on mahdollinen ja suoritetaan se if(isset($_GET['siirra']) && intval($_GET['siirra']) < $_SESSION['riveja']*$_SESSION['sarakkeita'] && intval($_GET['siirra']) >= 0) { if(!in_array(intval($_GET['siirra']),$mahdsiirrot)) { $error = "Et voi liikuttaa tähän ruutuun"; } else { $siirrakohtaan = intval($_GET['siirra']); $temp = $_SESSION['alue'][$siirrakohtaan]; $_SESSION['alue'][$siirrakohtaan] = 'valkoinen.jpg'; // Siirretään valkoista blokkia $_SESSION['alue'][$_SESSION['tyhjaruutu']] = $temp; // Siirretään klikattua blokkia $_SESSION['siirtoja']++; // Lisätään yksi siirto siirtoklopaaliin $_SESSION['tyhjaruutu'] = $siirrakohtaan; // Muutetaan valkoisen blokin senhetkinen paikkatieto header('Location: '.$_SERVER['PHP_SELF']); // Refresh } } // Tarkistetaan onko kuvio valmis $tarkistusarvot = array(); for($x=0; $x<count($_SESSION['alue']); $x++) { if($_SESSION['alue'][$x] == $_SESSION['jarjestys'][$x]) $tarkistusarvot[] = true; else $tarkistusarvot[] = false; } // Jos pelialue ja alkup. alue täsmäävät, voitto... if(!in_array(false,$tarkistusarvot)) { $_SESSION['voitto'] = true; $error = "Voitit!!! <br /> <a href=\"".$_SERVER['PHP_SELF']."?nayta=voitto\">Eteenpäin</a>"; header("Location: ".$_SERVER['PHP_SELF']."?nayta=voitto"); } $buffer .= ' <form method="get" action="'.$_SERVER['PHP_SELF'].'"> <input type="submit" value="Uusi peli!" name="uusipeli" /> <input type="submit" value="Top10" name="nayta" /> </form> <p> Valkoinen ruutu kuuluu aina vasempaan yläreunaan. </p> <table cellspacing="10" cellpadding="0"> <tr> <td> '; $i=0; for($a=0; $a < $_SESSION['riveja']; $a++) { // Luodaan rivit for($b=0; $b < $_SESSION['sarakkeita']; $b++) { // Luodaan sarakkeet $fkuva = $conf['udir'].$_SESSION['alue'][$i]; // Mikäli ruutuun voi siirtää, luodaan linkki kuvan ympärille if(in_array($i,$mahdsiirrot)) $buffer .= '<a href="'.$_SERVER['PHP_SELF'].'?siirra='.$i.'">'; $buffer .= '<img src="'.$fkuva.'" class="img" alt="palanen" width="'.$blokki['width'].'" height="'.$blokki['height'].'" />'; if(in_array($i,$mahdsiirrot)) $buffer .= '</a>'; $buffer .= "\n"; $i++; } $buffer .= "<br />\n"; } $buffer .= ' </td> <td> <img src="'.$conf['udir'].'alkup.jpg" alt="Alkup. kuvio"> </td> </tr> </table> <p>Siirtoja: '.$_SESSION['siirtoja'].'</p> </body> </html>'; break; } print '<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fi" lang="fi"> <head> <title>Shuffle v1.0 by Turatzuro</title> <style type="text/css"> .toptable { border: 1px solid black; } a:hover { background-color: white; -moz-opacity: 0.3; opacity: 0.3; } .img { padding-right: 1px; border: 0px; margin-bottom: 0px; padding-bottom: 0px; } </style> </head> <body> '; print $buffer; if($error !== false) print '<p style="color:red; font-weight: bold;">'.$error.'</p>'."\n"; // Mikäli virhe, tulostetaan print ' </body> </html>'; ?>
Wuhuu! Toimiikin jopa. Selkeetä koodia. ^^
Tuossa on se pieni bugi, että kaikki pelit eivät taida olla ratkeavia. Varmasti ratkeavan pelin saisi, jos sekoituksen hoitaisi pelaamalla tietyn määrän satunnaisia siirtoja pelin sääntöjen mukaan aloittaen järjestetystä pelistä.
Täh? Tietääkseni kaikki pelit ratkeavat jos siirrät oikein ne palikat.
Jos neliöiden järjestys arvotaan, puolet peleistä on ratkeamattomia. Mietitään vaikka yksinkertaistetusti ruudukkoa, jonka sivunpituus on kaksi. Seuraavassa vasemmalla on tavoitetilanne, oikealla taas aloitustilanne. Vaikka neliöitä kuinka yrittäisi liikutella, järjestys ei asetu oikeaksi.
_ A _ A B C C B
Aiheeseen liittyvää:
http://mathworld.wolfram.com/15Puzzle.html
No huh, olette muuten aivan oikeassa. Mietin kyllä asiaa, mutta ajattelin että palikoita voisi siirrellä sopivasti niin, että saa oikea kuvion aikaiseksi. Pitääpä tehdä parempi sotkentatsydeemi, ei tosin pitäisi olla hankalaa.
Noniin, nyt pitäisi toimia oikein.
liian vaikee en pääse läpi tosta
jännä ja vaikee peli
tuo top10 näyttää että Bill keitsi on päässy pelin läpi siirtämättä kertaakaan :D:D onkohan jääny pelin alusta joku tarkistus pois vai onko läppä?
Se on lähinnä tekijän outo läppä :P
.. 30 siirtoa!
Mulla ei toiminut :(
Katsokaa vaikka ---> http://koti.mbnet.fi/mansku91/salahakemisto/
En minäkään saanut toimimaan http://www.saunalahti.fi/~atk.data/Puzzle/index.
Peli toimii, mutta top-listaan tallennus ei onnistu.
Warning: fread(): Length parameter must be greater than 0.
Korjaus onnistuu sillä kun kirjoittaa ennatykset.txt - tiedostoon vaikkapa
1|Bill Gates
auttakaa joku, ei nyt ihan liity yleisesti tähän aiheeseen mutta miten chmodataan (vai mitensenyt oli) jotain 666 ja 667 juttuja??? siis onnistuuks se ihan notepadilla, ja kuinka, ja miten kirjotan sen. kertokaa ihan niinku kunnolla. :)
Ftp ohjelmalla
PHP_SELF tuollaisenaan mahdollistaa XSS-haavoittuvuuden, joten se pitää käsitellä oikein. PHP_SELF on myös käytännössä usein tarpeeton; seuraavassa on muutama esimerkki sen korvaamisesta eri tilanteissa:
<?php header("Location: ./"); # Ohjaus nykyiseen hakemistoon (index.php tms.). ?> <?php header("Location: ".basename(__FILE__)); # Ohjaus tähän tiedostoon. ?> <?php header("Location: ?"); # Ohjaus, jossa ?-osa tyhjenee. ?> <form action=""></form> <!-- Lomake, joka lähtee nykyiseen osoitteeseen. --> <form action="?"></form> <!-- Lomake, jossa ?-osa tyhjenee. --> <a href="?foo=bar"></a> <!-- Linkki, jossa vain ?-osa vaihtuu. -->
Metabolix kirjoitti:
PHP_SELF tuollaisenaan mahdollistaa XSS-haavoittuvuuden, joten se pitää käsitellä oikein. PHP_SELF on myös käytännössä usein tarpeeton; seuraavassa on muutama esimerkki sen korvaamisesta eri tilanteissa: – –
Jos puhutaan taas yleisellä tasolla, niin kyllä järkevin tapa on aina käyttää juuri sitä samaa osoitetta, mihin pyyntö on tehty. (Kun tarkoitus ohjata samaan osoitteeseen.) Ei ole tarvetta mankeloida osoitetta. Tietoturva-aukko tulee aina huolimattomasta html-koodin generoinnista, ei siitä että www-osoitteessa (syötteessä) olisi jotain vikaa.