Jak stworzyłem czystą grę logiczną CSS PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.

Jak stworzyłem czystą grę logiczną CSS?

Niedawno odkryłem radość tworzenia gier tylko w CSS. To zawsze fascynujące, jak HTML i CSS są w stanie poradzić sobie z logiką całej gry online, więc musiałem spróbować! Takie gry zwykle opierają się na starym hackowaniu pola wyboru, w którym łączymy zaznaczony/niezaznaczony stan danych wejściowych HTML z :checked pseudoklasa w CSS. Dzięki tej jednej kombinacji możemy zdziałać wiele magii!

W rzeczywistości postawiłem sobie wyzwanie zbudowania całej gry bez Checkboxa. Nie byłem pewien, czy to będzie możliwe, ale na pewno jest i pokażę ci jak.

Oprócz gry logicznej, którą będziemy studiować w tym artykule, zrobiłem zbiór czystych gier CSS, większość z nich bez Checkbox Hack. (Są również dostępne na CodePen.)

Chcesz zagrać zanim zaczniemy?

Osobiście wolę grać w grę w trybie pełnoekranowym, ale możesz w nią zagrać poniżej lub otwórz to tutaj.

Fajnie prawda? Wiem, że nie jest to najlepsza gra logiczna, jaką kiedykolwiek widziałeś™, ale nie jest też wcale taka zła jak na coś, co używa tylko CSS i kilku linijek HTML. Możesz łatwo dostosować rozmiar siatki, zmienić liczbę komórek, aby kontrolować poziom trudności i użyć dowolnego obrazu!

Wspólnie przerobimy to demo, a na koniec dodamy trochę dodatkowego blasku, aby uzyskać trochę kopa.

Funkcja przeciągnij i upuść

Chociaż struktura układanki jest dość prosta w CSS Grid, możliwość przeciągania i upuszczania elementów układanki jest nieco trudniejsza. Musiałem polegać na kombinacji przejść, efektów najechania i selektorów rodzeństwa, aby to zrobić.

Jeśli najedziesz kursorem na puste pole w tym demo, obraz przesunie się do niego i pozostanie tam, nawet jeśli wyjdziesz z pola. Sztuczka polega na tym, aby dodać duży czas trwania przejścia i opóźnienie — tak duże, że zdjęcie zajmuje dużo czasu, aby powrócić do swojej początkowej pozycji.

img {
  transform: translate(200%);
  transition: 999s 999s; /* very slow move on mouseout */
}
.box:hover img {
  transform: translate(0);
  transition: 0s; /* instant move on hover */
}

Określanie tylko transition-delay wystarczy, ale użycie dużych wartości zarówno opóźnienia, jak i czasu trwania zmniejsza szansę, że gracz kiedykolwiek zobaczy cofnięcie się obrazu. Jeśli poczekasz na 999s + 999s — czyli około 30 minut — wtedy zobaczysz, jak obraz się porusza. Ale nie zrobisz tego, prawda? To znaczy, nikt nie będzie trwał tak długo między turami, chyba że odejdzie z gry. Uważam to za dobrą sztuczkę do przełączania się między dwoma stanami.

Czy zauważyłeś, że najechanie na obraz również powoduje zmiany? Dzieje się tak dlatego, że obraz jest częścią elementu pudełka, co nie jest dla nas dobre. Możemy to naprawić, dodając pointer-events: none do obrazu, ale nie będziemy mogli go później przeciągnąć.

Oznacza to, że musimy wprowadzić do środka kolejny element .box:

To dodatkowe div (używamy klasy .a) zajmie ten sam obszar co obraz (dzięki CSS Grid i grid-area: 1 / 1) i będzie elementem wyzwalającym efekt najechania. I tu w grę wchodzi selektor rodzeństwa:

.a {
  grid-area: 1 / 1;
}
img {
  grid-area: 1 / 1;
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

Unosząc się na .a element przesuwa obraz, a ponieważ zajmuje on całą przestrzeń wewnątrz pudełka, to tak, jakbyśmy zamiast tego najeżdżali kursorem na pudełko! Unoszenie obrazu nie stanowi już problemu!

Przeciągnijmy i upuśćmy nasz obraz w polu i zobaczmy wynik:

Widziałeś to? Najpierw chwytasz obraz i przenosisz go do pudełka, nic nadzwyczajnego. Ale po zwolnieniu obrazu uruchamiasz efekt najechania, który przesuwa obraz, a następnie symulujemy funkcję przeciągania i upuszczania. Jeśli puścisz myszkę poza pudełko, nic się nie dzieje.

Hmm, twoja symulacja nie jest idealna, ponieważ możemy też najechać kursorem na pudełko i uzyskać ten sam efekt.

To prawda, a my to naprawimy. Musimy wyłączyć efekt najechania i zezwolić na to tylko wtedy, gdy uwolnimy obraz wewnątrz pudełka. Będziemy bawić się wymiarem naszego .a element, aby tak się stało.

Teraz najechanie na pudełko nic nie daje. Ale jeśli zaczniesz przeciągać obraz, .a pojawia się element, a po zwolnieniu wewnątrz pudełka możemy wywołać efekt najechania i przesunąć obraz.

Przeanalizujmy kod:

.a {
  width: 0%;
  transition: 0s .2s; /* add a small delay to make sure we catch the hover effect */
}
.box:active .a { /* on :active increase the width */
  width: 100%;
  transition: 0s; /* instant change */
}
img {
  transform: translate(200%);
  transition: 999s 999s;
}
.a:hover + img {
  transform: translate(0);
  transition: 0s;
}

Kliknięcie na obraz odpala :active pseudoklasa, która sprawia, że .a element o pełnej szerokości (początkowo jest równy 0). Stan aktywny pozostanie aktywny dopóki nie wypuścimy obrazu. Jeśli uwolnimy obraz wewnątrz pudełka, .a element wraca do width: 0, ale wywołamy efekt najechania, zanim to nastąpi, a obrazek znajdzie się w pudełku! Jeśli wypuścisz go poza pudełko, nic się nie dzieje.

Jest trochę dziwactwa: kliknięcie pustego pola również przesuwa obraz i przerywa naszą funkcję. W tej chwili, :active jest powiązany z .box element, więc kliknięcie go lub któregokolwiek z jego elementów podrzędnych aktywuje go; i robiąc to, w końcu pokazujemy .a elementu i wywołanie efektu najechania.

Możemy to naprawić, bawiąc się pointer-events. Pozwala nam to wyłączyć jakąkolwiek interakcję z .box zachowując interakcje z elementami podrzędnymi.

.box {
  pointer-events: none;
}
.box * {
  pointer-events: initial;
}

Teraz nasza funkcja przeciągnij i upuść jest idealna. O ile nie możesz znaleźć sposobu na zhakowanie, jedynym sposobem na przeniesienie obrazu jest przeciągnięcie go i upuszczenie go w pudełku.

Budowanie siatki puzzli

Ułożenie puzzli będzie łatwe w porównaniu do tego, co właśnie zrobiliśmy dla funkcji przeciągania i upuszczania. Będziemy polegać na siatce CSS i sztuczkach w tle, aby stworzyć układankę.

Oto nasza tabela, napisana w języku Pug dla wygody:

- let n = 4; /* number of columns/rows */
- let image = "https://picsum.photos/id/1015/800/800";

g(style=`--i:url(${image})`)
  - for(let i = 0; i < n*n; i++)
    z
      a
      b(draggable="true") 

Kod może wyglądać dziwnie, ale kompiluje się w zwykły HTML:

<g style="--i: url(https://picsum.photos/id/1015/800/800)">
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
 <z>
   <a></a>
   <b draggable="true"></b>
 </z>
  <!-- etc. -->
</g>

Założę się, że zastanawiasz się, co się dzieje z tymi tagami. Żaden z tych elementów nie ma specjalnego znaczenia — po prostu uważam, że kod jest znacznie łatwiejszy do napisania za pomocą <z> niż kilka <div class="z"> lub cokolwiek.

Oto jak je zmapowałem:

  • <g> to nasz pojemnik z siatką, który zawiera N*N <z> elementy.
  • <z> reprezentuje nasze elementy siatki. Odgrywa rolę .box element, który widzieliśmy w poprzedniej sekcji.
  • <a> wyzwala efekt najechania.
  • <b> reprezentuje część naszego wizerunku. Stosujemy draggable atrybut, ponieważ domyślnie nie można go przeciągać.

W porządku, zarejestrujmy nasz kontener siatki na <g>. To jest w Sass zamiast w CSS:

$n : 4; /* number of columns/rows */

g {
  --s: 300px; /* size of the puzzle */

  display: grid;
  max-width: var(--s);
  border: 1px solid;
  margin: auto;
  grid-template-columns: repeat($n, 1fr);
}

Właściwie zamierzamy uczynić nasze dzieci z siatki — te <z> elementy — również siatki i mają oba <a> i <b> w tym samym obszarze siatki:

z {
  aspect-ratio: 1;
  display: grid;
  outline: 1px dashed;
}
a {
  grid-area: 1/1;
}
b {
  grid-area: 1/1;
}

Jak widać, nic nadzwyczajnego — stworzyliśmy siatkę o określonym rozmiarze. Reszta CSS, której potrzebujemy, dotyczy funkcji przeciągania i upuszczania, która wymaga od nas losowego umieszczania elementów wokół planszy. Zwrócę się po to do Sassa, ponownie dla wygody przeglądania i stylizowania wszystkich puzzli za pomocą funkcji:

b {
  background: var(--i) 0/var(--s) var(--s);
}

@for $i from 1 to ($n * $n + 1) {
  $r: (random(180));
  $x: (($i - 1)%$n);
  $y: floor(($i - 0.001) / $n);
  z:nth-of-type(#{$i}) b{
    background-position: ($x / ($n - 1)) * 100% ($y / ($n - 1)) * 100%;
    transform: 
      translate((($n - 1) / 2 - $x) * 100%, (($n - 1)/2 - $y) * 100%) 
      rotate($r * 1deg) 
      translate((random(100)*1% + ($n - 1) * 100%)) 
      rotate((random(20) - 10 - $r) * 1deg)
   }
}

Być może zauważyłeś, że używam Sass random() funkcjonować. W ten sposób otrzymujemy losowe pozycje puzzli. Pamiętaj, że będziemy wyłączyć tej pozycji, gdy najedziesz kursorem na <a> element po przeciągnięciu i upuszczeniu odpowiadającego mu elementu <b> element wewnątrz komórki siatki.

z a:hover ~ b {
  transform: translate(0);
  transition: 0s;
}

W tej samej pętli definiuję również konfigurację tła dla każdego elementu układanki. Wszystkie z nich będą logicznie współdzielić ten sam obraz co tło, a jego rozmiar powinien być równy rozmiarowi całej siatki (zdefiniowanej za pomocą --s zmienny). Korzystanie z tego samego background-image i trochę matematyki, aktualizujemy background-position pokazać tylko fragment obrazu.

Otóż ​​to! Nasza gra logiczna oparta tylko na CSS jest skończona technicznie!

Ale zawsze możemy zrobić lepiej, prawda? pokazałem ci jak zrobić siatkę kształtów puzzli w innym artykule. Weźmy ten sam pomysł i zastosujmy go tutaj, dobrze?

Kształty puzzli

Oto nasza nowa gra logiczna. Ta sama funkcjonalność, ale z bardziej realistycznymi kształtami!

Oto ilustracja kształtów na siatce:

Jak stworzyłem czystą grę logiczną CSS?

Jeśli przyjrzysz się uważnie, zauważysz, że mamy dziewięć różnych kształtów puzzli: cztery rogiThe cztery krawędzie, jeden do wszystkiego innego.

Siatka puzzli, którą stworzyłem w innym artykule, o którym wspomniałem, jest nieco prostsza:

Możemy użyć tej samej techniki, która łączy maski CSS i gradienty, aby tworzyć różne kształty. Jeśli nie jesteś zaznajomiony z mask i spadki, bardzo polecam sprawdzenie ten uproszczony przypadek aby lepiej zrozumieć technikę przed przejściem do następnej części.

Po pierwsze, musimy użyć określonych selektorów, aby kierować reklamy na każdą grupę elementów o tym samym kształcie. Mamy dziewięć grup, więc użyjemy ośmiu selektorów oraz selektora domyślnego, który wybiera je wszystkie.

z  /* 0 */

z:first-child  /* 1 */

z:nth-child(-n + 4):not(:first-child) /* 2 */

z:nth-child(5) /* 3 */

z:nth-child(5n + 1):not(:first-child):not(:nth-last-child(5)) /* 4 */

z:nth-last-child(5)  /* 5 */

z:nth-child(5n):not(:nth-child(5)):not(:last-child) /* 6 */

z:last-child /* 7 */

z:nth-last-child(-n + 4):not(:last-child) /* 8 */

Oto rysunek, który pokazuje, jak to mapuje się do naszej siatki:

Jak stworzyłem czystą grę logiczną CSS PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.
Jak stworzyłem czystą grę logiczną CSS?

Teraz zajmijmy się kształtami. Skoncentrujmy się na nauce tylko jednego lub dwóch kształtów, ponieważ wszystkie wykorzystują tę samą technikę — w ten sposób będziesz mieć trochę pracy domowej do nauki!

W przypadku puzzli na środku siatki, 0:

mask: 
  radial-gradient(var(--r) at calc(50% - var(--r) / 2) 0, #0000 98%, #000) var(--r)  
    0 / 100% var(--r) no-repeat,
  radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% - var(--r) / 2), #0000 98%, #000) 
    var(--r) 50% / 100% calc(100% - 2 * var(--r)) no-repeat,
  radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
  radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);

Kod może wyglądać na złożony, ale skupmy się na jednym gradiencie na raz, aby zobaczyć, co się dzieje:

Dwa gradienty tworzą dwa okręgi (oznaczone na zielono i fioletowo w wersji demonstracyjnej), a dwa inne tworzą szczeliny, z którymi łączą się inne elementy (ten oznaczony na niebiesko wypełnia większość kształtu, a ten oznaczony na czerwono wypełnia górną część). Zmienna CSS, --r, ustawia promień okrągłych kształtów.

Jak stworzyłem czystą grę logiczną CSS PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.
Jak stworzyłem czystą grę logiczną CSS?

Kształt puzzli w środku (zaznaczony 0 na ilustracji) jest najtrudniejszy do wykonania, ponieważ wykorzystuje cztery gradienty i ma cztery krzywizny. Wszystkie inne kawałki żonglują mniejszą liczbą gradientów.

Na przykład elementy układanki wzdłuż górnej krawędzi układanki (oznaczone 2 na ilustracji) używa trzech gradientów zamiast czterech:

mask: 
  radial-gradient(var(--r) at calc(100% - var(--r)) calc(50% + var(--r) / 2), #0000 98%, #000) var(--r) calc(-1 * var(--r)) no-repeat,
  radial-gradient(var(--r) at var(--r) calc(50% - var(--r) / 2), #000 98%, #0000),
  radial-gradient(var(--r) at calc(50% + var(--r) / 2) calc(100% - var(--r)), #000 98%, #0000);

Usunęliśmy pierwszy (górny) gradient i dostosowaliśmy wartości drugiego gradientu tak, aby obejmował pozostałą przestrzeń. Nie zauważysz dużej różnicy w kodzie, jeśli porównasz dwa przykłady. Należy zauważyć, że możemy znaleźć różne konfiguracje tła, aby stworzyć ten sam kształt. Jeśli zaczniesz bawić się gradientami, na pewno wymyślisz coś innego niż to, co zrobiłem. Możesz nawet napisać coś bardziej zwięzłego — jeśli tak, podziel się tym w komentarzach!

Oprócz tworzenia kształtów, zauważysz również, że zwiększam szerokość i/lub wysokość elementów, jak poniżej:

height: calc(100% + var(--r));
width: calc(100% + var(--r));

Aby się połączyć, elementy układanki muszą przepełnić swoją siatkę.

Jak stworzyłem czystą grę logiczną CSS PlatoBlockchain Data Intelligence. Wyszukiwanie pionowe. AI.
Jak stworzyłem czystą grę logiczną CSS?

Ostateczne demo

Oto pełne demo ponownie. Jeśli porównasz go z pierwszą wersją, zobaczysz tę samą strukturę kodu do tworzenia siatki i funkcji przeciągania i upuszczania, a także kod do tworzenia kształtów.

Możliwe ulepszenia

Artykuł kończy się tutaj, ale możemy nadal ulepszać naszą układankę o jeszcze więcej funkcji! A może zegar? A może jakieś gratulacje, gdy gracz skończy układankę?

Mogę rozważyć wszystkie te funkcje w przyszłej wersji, więc miej oko na moje repozytorium GitHub.

Zamykając

oraz CSS nie jest językiem programowania, Mówią. Ha!

Nie próbuję przez to wywołać #HotDrama. Mówię to, ponieważ zrobiliśmy kilka naprawdę skomplikowanych rzeczy logicznych i po drodze omówiliśmy wiele właściwości i technik CSS. Bawiliśmy się siatką CSS, przejściami, maskowaniem, gradientami, selektorami i właściwościami tła. Nie wspominając o kilku sztuczkach Sassa, których użyliśmy, aby nasz kod był łatwy do dostosowania.

Celem nie było zbudowanie gry, ale zbadanie CSS i odkrycie nowych właściwości i sztuczek, które można wykorzystać w innych projektach. Tworzenie gry online w CSS to wyzwanie, które zmusza Cię do szczegółowego poznania funkcji CSS i nauczenia się, jak z nich korzystać. Poza tym to po prostu świetna zabawa, gdy mamy coś do zabawy, gdy wszystko jest powiedziane i zrobione.

To, czy CSS jest językiem programowania, czy nie, nie zmienia faktu, że zawsze uczymy się, budując i tworząc innowacyjne rzeczy.

Znak czasu:

Więcej z Sztuczki CSS