<script lang="ts">
import { onMount } from 'svelte';
import { fade, blur, fly, slide, scale } from "svelte/transition";
import { flip } from "svelte/animate";
import { quadInOut } from "svelte/easing";
export let engine;
// import components
import RestartButton from './components/RestartButton.svelte';
import Controls from './components/Controls.svelte';
import GridBackground from './components/GridBackground.svelte';
import Analyzer from './components/Analyzer.svelte';
import type Tile from './components/tile.ts';
// Game variables
const start_board = '[[{"x":0,"y":0,"value":2,"merged":false,"id":1},{"x":1,"y":0,"value":0,"merged":false,"id":2},{"x":2,"y":0,"value":2,"merged":false,"id":3},{"x":3,"y":0,"value":0,"merged":false,"id":4},null,null],[{"x":0,"y":1,"value":0,"merged":false,"id":5},{"x":1,"y":1,"value":0,"merged":false,"id":6},{"x":2,"y":1,"value":0,"merged":false,"id":7},{"x":3,"y":1,"value":0,"merged":false,"id":8},null,null],[{"x":0,"y":2,"value":0,"merged":false,"id":9},{"x":1,"y":2,"value":0,"merged":false,"id":10},{"x":2,"y":2,"value":0,"merged":false,"id":11},{"x":3,"y":2,"value":0,"merged":false,"id":12},null,null],[{"x":0,"y":3,"value":0,"merged":false,"id":13},{"x":1,"y":3,"value":0,"merged":false,"id":14},{"x":2,"y":3,"value":0,"merged":false,"id":15},{"x":3,"y":3,"value":0,"merged":false,"id":16},null,null],[null,null,null,null,null,null],[null,null,null,null,null,null]]';
const start_board_3x3 = '[[{"x":0,"y":0,"value":2,"merged":false,"id":1},{"x":1,"y":0,"value":0,"merged":false,"id":2},{"x":2,"y":0,"value":2,"merged":false,"id":3},null,null,null],[{"x":0,"y":1,"value":0,"merged":false,"id":4},{"x":1,"y":1,"value":0,"merged":false,"id":5},{"x":2,"y":1,"value":0,"merged":false,"id":6},null,null,null],[{"x":0,"y":2,"value":0,"merged":false,"id":7},{"x":1,"y":2,"value":0,"merged":false,"id":8},{"x":2,"y":2,"value":0,"merged":false,"id":9},null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null],[null,null,null,null,null,null]]'
let score = 0;
let hiscore = 0;
$: if(score > hiscore){hiscore = score};
let width = 4;
let height = 4;
let width_max = 6;
let height_max = 6;
let board: Array<Array<Tile>> = JSON.parse(start_board);
function restart(size){
width = size;
height = size;
board = Array(width_max).fill(null).map(() => Array(height_max).fill(null));
for(let x = 0; x < width_max; x++){
for(let y = 0; y < height_max; y++){
board[x][y] = {x: x, y: y, value: 0, merged: false, id: x + y * width_max + 99};
}
}
board = JSON.parse( engine.add_random(JSON.stringify([width, height, board])) );
board = JSON.parse( engine.add_random(JSON.stringify([width, height, board])) );
score = 0;
}
let game_over = false;
function move(direction: number){
try{
let result = JSON.parse(engine.apply_move(JSON.stringify([width, height, board]), direction, true));
let newboard = result[0];
let possible = result[1];
let scoreGain = result[2];
switch(direction){
case 0: // Up
latestEvent = "up";
latestX = 0;
latestY =-1;
break;
case 1: // Right
latestEvent = "right";
latestX = 1;
latestY = 0;
break;
case 2: // Down
latestEvent = "down";
latestX = 0;
latestY = 1;
break;
case 3: // Left
latestEvent = "left";
latestX =-1;
latestY = 0;
break;
default:
break;
}
if(possible === true){
board = newboard;
score += scoreGain;
}
let nextMovesPossible = 0;
// Check if game is over
for(let dir = 0; dir < 4; dir++){
try{
let next_result = JSON.parse(engine.apply_move(JSON.stringify([width, height, board]), dir, true));
console.log(next_result);
if(next_result[1] == true){
nextMovesPossible += 1;
console.log("Move to " + dir + " possible!");
}
}
catch(e){
console.log("Error trying to check possible moves: ", e);
}
}
console.log(nextMovesPossible);
if(nextMovesPossible < 1){
game_over = true;
alert("Game Over!");
}
}
catch(e){
console.log("E", e);
}
}
let latestEvent = "";
let latestX = 0;
let latestY = 0;
function handleKeydown(event){
let key = event.key;
switch(key){
case "ArrowUp":
case "w":
move(0);
break;
case "ArrowDown":
case "s":
move(2);
break;
case "ArrowLeft":
case "a":
move(3);
break;
case "ArrowRight":
case "d":
move(1);
break;
default:
break;
}
}
let packName = "OispaHalla";
let packSubtitle = "Yhdistä opettajat ja saavuta Halla!";
let imagePackPath = "../img";
let usingCustomPack = false;
$: usingCustomPack = imagePackPath !== "../img";
let theme = 0;
const IMAGENAME_MAX = 15;
let imageNames = [];
$: imageNames = Array.from(Array(IMAGENAME_MAX).keys()).map(i => `${imagePackPath}/theme-${theme}/${2**(i+1)}.png`);
// $: if(theme != null){
// imageNames = [];
// for(let i = 1; i < IMAGENAME_MAX; i++){
// imageNames.push(`../img/theme-${theme}/${Math.pow(2, i)}.png`);
// }
// console.log("imageNames", imageNames);
// }
let devmode = true;
onMount(async () => {
if(window.location.href.includes("?pack=")){
try{
let pack = window.location.href.split("?pack=")[1];
imagePackPath = atob(pack);
fetch(imagePackPath + "/manifest.json").then(res => res.json()).then(res => {
packName = res.name;
packSubtitle = res.subtitle;
})
}
catch(e){
alert("Invalid pack! (Error in devconsole)");
console.log("Invalid pack!", e)
}
}
});
</script>
<svelte:head>
<title>Oispa Halla</title>
<link rel="manifest" href="./manifest.json">
<link rel="icon" type="image/png" href="./favicon.png"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="Hallabois">
<meta name="description" content="Oispa Halla: Yhdistä opettajat ja saavuta Halla!">
<meta name="keywords" content="Oispahalla, halla, ksyk, 2048, Lauri Halla, hallabois, oispa pelit">
<meta name="twitter:card" content="Yhdistä opettajat ja saavuta Halla!">
<meta name="theme-color" content="#000000"/>
<script>window.sa_event=window.sa_event||function(){var a=[].slice.call(arguments);window.sa_event.q?window.sa_event.q.push(a):window.sa_event.q=[a]};</script>
</svelte:head>
<svelte:window on:keydown={handleKeydown}/>
<main>
<noscript>
<div class="noscript">
<p>
<strong>JavaScript ei ole päällä tai selaimesi ei tue sitä.</strong>
<br>
Sivuston toiminta edellyttää JavaScriptin toimintaa.
</p>
</div>
</noscript>
<div class="container">
<div class="new-above-game">
<div class="above-game-left">
<a href="https://hallabois.github.io/invite/" target="_blank">
<h1 class="title">{packName}</h1>
</a>
<p class="game-intro">{packSubtitle}</p>
</div>
<div class="above-game-right">
<div class="HAC-container" title="HAC:n tila" style="display:none;">
<div class="HAC-status">...</div>
</div>
<div class="score-container">{score}</div>
<div class="best-container">{hiscore}</div>
<RestartButton callback={restart} />
</div>
</div>
<!-- <div class="heading">
<a href="https://discord.gg/7x25Jxrkvr" target="_blank">
<h1 class="title">Oispa Halla</h1>
</a>
<div class="scores-container">
<div class="HAC-container" title="HAC:n tila" style="display:none;">
<div class="HAC-status">...</div>
</div>
<div class="score-container">0</div>
<div class="best-container">0</div>
</div>
</div>
<div class="above-game">
<p class="game-intro">Yhdistä opettajat ja saavuta <strong>Halla!</strong></p>
<a class="restart-button">
<div class="uusi-jakso">Uusi Jakso</div>
<div class="size-selector">
<button><</button>
<button class="restart-3x3">3x3</button>
<button class="restart-4x4">4x4</button>
</div>
</a>
</div> -->
<Controls callback={move} />
<p>Latest move: {latestEvent}, X: {latestX}, Y: {latestY}</p>
<div class="game-container" style="--grid-size: {Math.min(width, height)};">
<div class="kurin-palautus-viesti"></div>
<div class="game-message">
<p class="tilanne"></p>
<p class="kurinpalautukset"></p>
<div class="lower">
<a class="keep-playing-button">Jatka pelaamista</a>
<a class="retry-button">Yritä uudelleen</a>
</div>
</div>
<GridBackground game_width={width_max} game_height={height_max} />
<div class="tile-container">
{#if board}
{#each board.flat().filter(t => t != null) as tile, key (tile.id)}
<div class="tile tile-position-{tile.x+1}-{tile.y+1} tile-{tile.value}" style="--x:{tile.x+1};--y:{tile.y+1};--value:{tile.value == 0 ? "" : Math.log2(tile.value)};" out:scale in:fly="{{ y: latestY*-20, x: latestX*-20, duration: 99, easing: quadInOut }}" animate:flip="{{duration: 99, easing: quadInOut}}">
<!-- {tile.value == 0 ? "" : tile.value} -->
<!-- {#if tile && tile.value && tile.value != 0} -->
<div class="tile-inner" style="--img-path:url({tile.value == 0 ? "" : imageNames[Math.log2(tile.value)-1]});"></div> <!-- Why do we need a logarithm? Good question. -->
<!-- {/if} -->
</div>
{/each} <!-- && board.flat().filter(t2 => t2 && t2.id == t.id).length < 2 -->
{/if}
</div>
</div>
<div class="underbar-container">
<div class="kurin-palautus-container">
<button class="kurin-palautus kurin-palautus-color">
<a class="parin-kulautus" title="Vai parin kulautus? Lahjot opettajia pois ruudulta, mutta menetät arvosanojasi! Voit lahjoa opettajia vain kolme kertaa ennen kun Halla saa kuulla tilanteesta.">KURINPALAUTUS</a>
</button>
</div>
<div class="button-container">
<div class="event-container">
<a class="event-button" title="Toggle Hallatunturin Joulu">
<img src="https://d33wubrfki0l68.cloudfront.net/96e5314f432b063a6e5f1078466585810b92d733/50db0/img/no_snow.svg" id="event-icon">
</a>
</div>
</div>
</div>
<div class="pwa-container" style="width: 100%;z-index: 500;display: flex;justify-content: center;margin-top: 30px;margin-bottom: -50px;">
<button class="pwa-add-button" style="display: none;border: none;margin: .5em;cursor: pointer;">Asenna sovelluksena</button>
</div>
<div class="disclaimer">
<p>
<strong>HUOMIO:</strong> Pelin lista opettajista on tehty täysin sattumanvaraisesti, eikä opettajia ole laitettu minkäänlaiseen paremmuusjärjestykseen. Rakastamme kaikkia opettajia sekä arvostamme kaikkien heidän työtänsä yhtä paljon ❤️.
</p>
<p>
Alkuperäisen projektin <a href="https://github.com/gabrielecirulli/2048" target="_blank">2048</a> on tehnyt <a href="http://gabrielecirulli.com" target="_blank">Gabriele Cirulli.</a>
</p>
<p>
<a href="https://simpleanalytics.com/oispahalla.com" target="_blank">Simpleanalytics</a>
</p>
</div>
{#if devmode}
<div class="dev">
<p>
<strong>DEV MODE</strong>
</p>
<label for="dev_theme">Theme:</label>
<input id="dev_theme" type="number" min="0" bind:value={theme} />
</div>
{/if}
<div class="preload-container">
{#each imageNames as imageName}
<img src="{usingCustomPack ? "" : "./build/"}{imageName}" alt="" />
{/each}
</div>
</div>
<!-- <Analyzer engine={engine} /> -->
</main>
<style lang="scss">
@import "style/helpers";
div.container {
width: var(--field-width);
margin: 0 auto;
padding: 40px 0;
}
h1.title {
font-size: 45px;
font-weight: bold;
margin: 0;
display: block;
float: left;
transition: 0.25s color;
}
.new-above-game{
display: -webkit-flex;
display: flex;
gap: 1em;
}
.above-game-left{
display: -webkit-flex;
display: flex;
flex-direction: column;
flex: 1;
}
.above-game-right{
display: -webkit-flex;
display: flex;
flex-direction: row;
gap: .5em;
flex: 1;
flex-wrap: wrap;
align-content: end;
}
.game-intro {
float: left;
line-height: 42px;
margin-bottom: 0;
font-size: smaller;
}
.scores-container {
float: right;
text-align: right;
margin-bottom: 20px;
min-width: 20px;
max-width: 50%;
}
.score-container, .best-container {
$height: 25px;
position: relative;
background: var(--game-container-background);
padding: 15px 20px;
font-size: $height - 5px;
height: $height;
line-height: $height + 22px;
font-weight: bold;
border-radius: 3px;
color: white;
text-align: center;
flex: 1; // mf's browsers don't support flex: content???
min-width: 50px;
&:after {
position: absolute;
width: 100%;
top: 10px;
left: 0;
text-transform: uppercase;
font-size: 13px;
line-height: 13px;
text-align: center;
color: var(--tile-color);
}
.score-addition {
position: absolute;
right: 30px;
color: red;
font-size: $height;
line-height: $height;
font-weight: bold;
color: rgba(var(--text-color), .9);
z-index: 100;
@include animation(move-up 600ms ease-in);
@include animation-fill-mode(both);
}
}
.score-container:after {
content: "Arvosana";
}
.best-container:after {
content: "Paras Halla";
}
.game-container{
--tile-size: calc(calc(var(--field-width) - calc(var(--grid-gap) * calc(var(--grid-size) + 1))) / var(--grid-size));
}
.game-container .kurin-palautus-viesti {
width: 94%;
position: absolute;
top: 25px;
display: -webkit-flex;
display: flex;
justify-content: center;
z-index: 1000;
pointer-events: none;
& img {
width: 300px;
@include animation(fade-in-out 4000ms ease-out);
@include animation-fill-mode(both);
}
}
.underbar-container {
display: -webkit-flex;
display: flex;
width: 100%;
> * {
margin-top: 15px;
}
.button-container {
> * {
display: -webkit-flex;
display: flex;
width: 50%;
img {
width: 25px;
}
}
}
}
.kurin-palautus-container {
justify-content: center;
flex: 1; // mf's browsers don't support flex: content???
display: -webkit-flex;
display: flex;
text-align: center;
margin-left: 25px;
min-height: 30px;
}
.kurin-palautus {
padding: 0 5px;
background-color: #c00;
cursor: pointer;
width: 25%;
border-radius: 3px;
transition: 1s background, 1s color;
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
border: var(--game-background) 1px solid;
&:hover {
border: var(--game-background-dark) 1px solid;
}
}
.HAC-container {
position: relative;
background: #bbada0;
padding: 15px 15px;
font-size: 25px;
height: 25px;
line-height: 47px;
font-weight: bold;
border-radius: 3px;
color: white;
text-align: center;
}
.HAC-container::after{
content: "HAC";
position: absolute;
width: 100%;
top: 10px;
left: 0;
text-transform: uppercase;
font-size: 13px;
line-height: 13px;
text-align: center;
color: #eee4da;
}
.HAC-status{
cursor: default;
}
.parin-kulautus {
text-decoration: none;
color: #eee;
font-size: 11px;
transition: 1s background, 1s color;
}
.disclaimer {
margin-top: 60px;
font-size: small;
text-align: center;
}
.disclaimer p {
margin-bottom: 20px;
}
.discord {
margin-top: 40px;
font-size: small;
text-align: center;
}
@import "style/game-field";
@include game-field;
$kiillot: (128: (-4px, #a8fa8a),
256: (-4px, #FFFB62),
512: (-3px, #ffa600),
1024: (-1px, #5000ff),
2048: (0px, #FF4646),
4096: (0px, #FF9B00),
8192: (0px, #00FF13),
16384: (1px, #FFC600),
32768: (2px, #0B00FF));
.tile .tile-inner{
background-size: cover;
}
.tile {
position: absolute;
-webkit-transition: 100ms ease-in-out;
-moz-transition: 100ms ease-in-out;
transition: 100ms ease-in-out;
transition-property: all;
-webkit-transition-property: -webkit-transform;
-moz-transition-property: -moz-transform;
transition-property: transform;
}
</style>