Hur det Romerska Riket Gjorde Ren CSS Ansluta 4 Möjliga

0
16

Experiment är ett roligt ursäkt för att lära sig de senaste trick, tänka på nya idéer och för att driva dina gränser. “Ren CSS” demos har varit en sak för ett tag, men nya möjligheter som öppnas upp som webbläsare och CSS själv utvecklas. CSS och HTML förbehandling också hjälpt scenen framåt. Ibland förbehandling används för att hårdkoda varje möjligt scenario, till exempel långa strängar av :kontrolleras och angränsande syskon väljare.

I denna artikel kommer jag gå igenom de centrala idéerna i en Ren CSS Ansluta 4 spel som jag byggt. Jag försökte undvika att hårdkoda så mycket som jag kunde i mitt experiment och arbetade utan förbehandling för att fokusera på att hålla den resulterande koden kort. Du kan se all kod och spelet här:

Se Pennan Ren CSS Connect 4 av Bence Szabó (@finnhvman) på CodePen.

Grundläggande begrepp

Jag tror att det finns några begrepp som anses väsentliga i “ren CSS” genre. Typiskt formulär används för hantering av staten och fånga användarens åtgärder. Jag var jätteglad när jag hittade människor använder <button type=”reset”> reset eller starta ett nytt spel. Allt du behöver göra är att slå in dina delar i ett <form> – taggen och lägga till knappen. I min mening är detta ett mycket renare lösning än att behöva uppdatera sidan.

Mitt första steg var att skapa ett formulär element sedan kasta en massa ingångar till det för slots och lägg på reset-knappen. Här är en mycket grundläggande demonstration av <button type=”reset”> i aktion:

Se Pennan Ren HTML-Formulär Återställ av Bence Szabó (@finnhvman) på CodePen.

Jag ville ha fina visuella för denna demo för att ge en fullständig upplevelse. I stället för att dra i en extern bild för styrelsen eller skivor, jag använde en radial gradient(). En bra resurs som jag använder ofta är Lea Verou s CSS3 Mönster Galleri. Det är en samling av mönster av gradienter, och de är redigerbara också! Jag använde currentcolor, som kom ganska praktiskt för skivan mönster. Jag har lagt till en rubrik och återanvändas min Ren CSS Rippel-Knappen.

Vid denna punkt layout och skiva designen var redan finalen, bara att spelet fungerade inte alls

Att släppa skivor till styrelsen

Nästa jag gjort det möjligt för användare att ta sina svängar släppa skivor på Connect 4 styrelsen. I Connect 4, spelare (en röd och en gul) släppa skivor i kolumner i alternerande varv. Det finns 7 kolumner och 6 rader (42 slots). Varje slot kan vara tom eller upptas av en röd eller gul skiva. Så, en slot kan ha ett av tre lägen (tom, röd eller gul). Skivor sjunkit i samma kolumn är staplade på varandra.

Jag började med att placera två kryssrutor för varje kortplats. När de är både avmarkerad slot anses vara tom, och när en av dem är markerad, motsvarande spelare har sin skiva i det.

Eventuella tillstånd för att ha dem båda kollade bör undvikas genom att dölja dem när någon av dem är markerad. Dessa kryssrutor är omedelbara syskon, så när den första av ett par är markerad kan du dölja både med hjälp av :kollade pseudo-klass och den angränsande syskon combinator (+). Vad händer om den andra kontrolleras? Du kan dölja andra, men hur påverkar den första? Tja, det finns ingen tidigare syskon väljare, det är bara inte hur CSS-selektorer arbete. Jag var tvungen att förkasta denna idé.

Faktiskt, en kryssruta kan ha ett av tre lägen av sig själv, det kan vara i obestämd staten. Problemet är att du inte kan sätta det i obestämd stat med enbart HTML. Även om du kan, nästa klicka på kryssrutan för alltid skulle göra det förvandlas till kontrollerade staten. Att tvinga andra spelare att dubbelklicka på när de gör sin flytta, är opålitliga och oacceptabelt.

Jag var fast på MDN doc :obestämt och märkte att radio-ingångarna har också obestämt tillstånd. Knappar med samma namn i det här läget när de är alla omarkerade. Wow, det är en verklig ursprungliga skick! Vad som är riktigt bra är att kontrollera den senare syskon har också effekt på det tidigare! Så jag fyllde styrelsen med 42 par av radio-ingångar.

I efterhand, smart beställning och användning av etiketter med antingen kryssrutor eller alternativknappar skulle ha gjort susen, men jag anser inte att etiketter för att vara ett alternativ för att hålla koden enklare och kortare.

Jag ville ha stora områden för samverkan för att ha trevligt UX, så jag tänkte att det är rimligt att låta spelarna göra ett drag genom att klicka på en kolumn. Jag staplade kontroller av samma kolumn på varandra genom att lägga till absolut och relativ positionering till lämpliga delar. På detta sätt endast den lägsta tomma platsen kan väljas inom en kolumn. Jag noggrant ställa in tiden för övergången av skivan faller per rad och deras timing funktion är att tillnärma en kvadratisk kurva för att efterlikna realistiska fritt fall. Så långt pusselbitarna kom väl ihop, även om animationen nedan visar tydligt att endast de röda spelare kan göra sina drag.

Även om alla kontroller är det, bara röd-skivor som kan läggas på bordet

Klickbara områden av radio-ingångar visualiseras med färgade men semi-transparent rektangel. De gula och röda ingångar är staplade över varandra sex gånger(=sex rader per kolumn, lämnar den röda ingången på den nedersta raden på toppen av stacken. Blandningen av rött och gult skapar den orange färg som kan ses i styrelsen sedan start. Den mindre tomma slots finns i en kolumn, mindre intensiv denna orange färg får sedan radio ingångar visas inte när de inte :obestämd. På grund av den röda ingång alltid att vara just över den gula ingången i varje slot, bara de röda spelarna har möjlighet att göra drag.

Spårning visar

Jag hade bara en svag aning om och en hel del hopp om att jag på något sätt kan lösa byta varv mellan de två spelarna med de allmänna syskon väljare. Konceptet var att låta den röda spelare turas om att när antalet kontrollerade ingångar var jämnt (0, 2, 4, etc.) och låt den gula spelare turas om att när det var udda. Snart insåg jag att den allmänna syskon väljaren inte (och bör inte!) arbeta på det sätt jag ville.

Sedan en mycket självklara valet var att experimentera med den n: te-selektorer. Dock lockar det var att använda den jämna och udda sökord, sprang jag in i en återvändsgränd. :Nth-child-väljare “som räknas” barn inom en förälder, oavsett typ, klass, pseudo-klass, vad som helst. I :n: te-av-type selector “som räknas som” barn av en typ inom en förälder, oavsett klass eller pseudo-klass. Så problemet är att de inte kan räkna baserat på :markerad staten.

Väl CSS räknare räknas också, så varför inte ge dem ett försök? En vanlig användning av räknare är att antalet rubriker (även i flera nivåer) i ett dokument. De styrs av CSS-regler, kan vara godtyckligt reset på någon punkt och sina öka (eller minska!) värden kan vara ett heltal. Den räknare visas räknaren () – funktionen i innehållet egendom.

Det enklaste sättet var att ställa upp en räknare och räkna :kollade ingångar i Connect 4 rutnät. Det finns bara två problem med detta tillvägagångssätt. Den första är att du inte kan utföra aritmetik på en räknare för att upptäcka om det är jämnt eller udda. Den andra är att man inte kan tillämpa CSS-regler till delar baserat på mätarställningar.

Jag lyckades övervinna den första frågan, genom att göra det binär räknare. Värde av räknare är lika med noll i början. När den röda spelaren kontrollerar sina radio-knappen disk ökas med ett. När den gula spelaren kontrollerar sina radio-knappen räknas räknaren ned med en, och så vidare. Därför mätarställningar kommer att vara antingen noll eller ett, jämnt eller udda.

Lösa andra problem krävs mycket mer kreativitet (läs: hacka). Som nämnts räknare kan visas, men bara i ::före och ::after pseudo-element. Det är en no-brainer, men hur kan de påverka andra delar? Åtminstone disk värde kan ändra bredden på pseudo-element. Olika siffror har olika bredder. Tecken 1 är oftast tunnare än 0, men det är något som är väldigt svårt att kontrollera. Om antalet tecken förändring snarare än karaktären i sig den resulterande bredd förändring är mer kontrollerbar. Det är inte ovanligt att använda Romerska siffror med CSS-räknare. Ett och två representerade i Romerska siffror är samma karaktär en gång och två gånger och så är deras bredd i pixlar.

Min idé var att fästa knappar i en spelare (gul) till vänster och montera radio-knappar för andra spelare (röd) till höger om deras gemensamma överordnade behållare. Ursprungligen, den röda knappar är överlagrad på den gula knappen, då bredden förändring av behållaren skulle orsaka röda knappar för att “gå bort” och avslöja den gula knappen. En liknande verkliga konceptet är skjutbara fönster med två rutor, en ruta är fast (gula knappen), den andra är slidable (röda knappar) över den andra. Skillnaden är att i spelet endast hälften av fönster är synliga.

Så långt, så bra, men jag var fortfarande inte nöjd med font-size (och andra fonten) indirekt styr bredden. Jag trodde letter-spacing skulle passa fint här, eftersom det bara ökar i storlek i en dimension. Oväntat, inte ens en bokstav har teckenavstånd (som tolkas efter bokstaven), och två bokstäver göra teckenavstånd två gånger. Förutsägbar bredder är avgörande för att göra detta tillförlitlig. Bredden noll tecken tillsammans med enkel och dubbel teckenavstånd skulle fungera, men det är farligt att ställa in font-size till noll. Definiera stora letter-spacing (mellanrum (i bildpunkter) och små (1px) font-size gjort det nästan konsekvent i alla webbläsare, ja jag pratar om sub-pixels.

Jag behövde behållare bredd för att växla mellan ursprunglig storlek (=w) och minst det dubbla för den ursprungliga storlek (>=2w) för att fullt ut kunna dölja och visa på den gula knappen. Låt oss säga att v är utförda bredd av ‘jag’ – tecken (lägre romerska representation, varierar i olika webbläsare), och c som är utförda bredd (konstant) av us letter-spacing (mellanrum). Jag behövde v + c = w för att vara sant, men kunde det inte vara, eftersom c och w är heltal men v är icke-heltal. Jag slutade upp med min-width och max-width egenskaper för att begränsa den möjliga bredd värden, så jag ändrade också möjligt motverka värden till ” jag ” och ” iii ” för att se till att texten bredder underflow och bräddavlopp begränsningar. I ekvationer denna såg ut som v + c < w, 3v + 3c > 2w, och v << c, vilket ger 2/3w < c < w. Slutsatsen är att den letter-spacing har för att vara något mindre än den ursprungliga bredd.

Jag har varit resonemang så långt som om pseudo-element visar räknaren värde moderbolaget i radio-knappar, det är det inte. Dock märkte jag att bredden av pseudo-element att ändra bredden på sin överordnade elementet, och i detta fall den förälder som behållare av radio-knappar.

Om du funderar inte kunde detta lösas med arabiska siffror? Du har rätt, omväxlande värde mellan något som ‘1’ och ‘111’ skulle också fungera. Ändå, Romerska siffror gav mig idén i första hand, och de var också en bra ursäkt för clickbaity avdelning så jag behöll dem.

Spelarna ta alternerande varv börjar med den röda spelaren

Tillämpar tekniken diskuteras gör den överordnade behållare av radio dubbla ingångar i bredd när en röd inmatning kontrolleras och gör det ursprungliga bredd när en gul ingång är markerad. I den ursprungliga bredden behållare röda ingångar finns över de gula, men i dubbel bredd behållare, den röda ingångar är flyttat.

Att känna igen mönster

I verkliga livet, Connect 4 styrelsen inte berätta för dig om du har vunnit eller förlorat, men att ge korrekt återkoppling är en viktig del av en bra användarupplevelse i någon programvara. Nästa mål är att identifiera om en spelare har vunnit spelet. För att vinna spelet måste spelaren få fyra av sina skivor i en kolumn, rad eller en diagonal linje. Detta är en mycket enkel uppgift att lösa i många programmeringsspråk, men i ren CSS världen, detta är en enorm utmaning. Bryta ner det till underaktiviteter är ett sätt att närma sig detta på ett systematiskt sätt.

Jag använde en flex behållare som förälder av radio-knappar och skivor. En gul radio-knappen, en röd knapp och en div-för skivan tillhör en slot. En sådan slot upprepas 42 gånger och ordnade i kolumner som wrap. Följaktligen slots i en kolumn bredvid, vilket gör erkänner fyra i en kolumn den enklaste delen med hjälp av intilliggande väljare:

<div class=”grid”>
<input type=”radio” name=”slot11″>
<input type=”radio” name=”slot11″>
<div class=”disc”></div>
<input type=”radio” name=”slot12″>
<input type=”radio” name=”slot12″>
<div class=”disc”></div>

<input type=”radio” name=”slot16″>
<input type=”radio” name=”slot16″>
<div class=”disc”></div>

<input type=”radio” name=”slot21″>
<input type=”radio” name=”slot21″>
<div class=”disc”></div>

</div>
/* Röd fyra i en kolumn väljaren */
input:checkat + .disc + in + in:checkat + .disc + in + in:checkat + .disc + in + in:kollade ~ .resultatet

/* Gul fyra i en kolumn väljaren */
input:checkat + in + .disc + input:checkat + in + .disc + input:checkat + in + .disc + input:kollade ~ .resultatet

Detta är en enkel men ful lösning. Det finns 11 typ och klass-selektorer kedjas ihop per spelare för att täcka de fall av fyra i en kolumn. Lägga till en div med klassen .resultatet efter de delar av slots gör det möjligt att villkorligt visa resultatet meddelande. Det finns också ett problem med felaktigt upptäcka fyra i en kolumn där kolumnen är inslagna, men låt oss bara lägga frågan åt sidan.

En liknande metod för att detektera fyra i rad verkligen skulle vara en fruktansvärd idé. Det skulle vara 56 väljare kedjas ihop per spelare (om jag gjorde math höger), för att inte nämna att de skulle ha en liknande brist av falsklarm. Detta är en situation där :nth-child(An+B [S]) eller kolumnen kombinatorer kommer att komma till hands i framtiden.

För bättre semantik man kunde lägga till ett nytt div för varje kolumn och ordna slot element i dem. Denna ändring skulle också eliminera risken för falsklarm som nämns ovan. Sedan upptäcka fyra i rad kunde gå ut: markera en kolumn där den första röda radio ingång är markerad, och välj den anslutning syskon kolumn där den första röda radio inmatning kontrolleras, och så vidare två gånger. Detta låter mycket besvärligt och skulle behöva en “förälder” väljaren.

Du väljer den förälder som inte är möjlig, men att välja barnet är. Hur skulle upptäcka fyra i rad gå med tillgängliga kombinatorer och väljare? Markera en kolumn och väljer sedan sitt första röda radio input om markerad, och välj den intilliggande kolumn och väljer sedan sitt första röda radio input om markerad, och så vidare två gånger. Det låter fortfarande besvärligt, men ändå möjligt. Tricket är inte bara i CSS-men också i HTML, nästa kolumn måste vara syskon på radio knappar i föregående kolumn skapa en nästlad struktur.

<div class=”grid kolumnen”>
<input type=”radio” name=”slot11″>
<input type=”radio” name=”slot11″>
<div class=”disc”></div>

<input type=”radio” name=”slot16″>
<input type=”radio” name=”slot16″>
<div class=”disc”></div>

<div class=”kolumn”>
<input type=”radio” name=”slot21″>
<input type=”radio” name=”slot21″>
<div class=”disc”></div>

<input type=”radio” name=”slot26″>
<input type=”radio” name=”slot26″>
<div class=”disc”></div>

<div class=”kolumn”>

</div>
</div>
</div>
/* Röd fyra i rad väljaren */
input:n: te-av-typ(2):kollade ~ .kolumn > indata:n: te-av-typ(2):kollade ~ .kolumn > indata:n: te-av-typ(2):kollade ~ .kolumn > indata:n: te-av-typ(2):kollade ~ .kolumn::efter,
input:n: te-av-typ(4):kollade ~ .kolumn > indata:n: te-av-typ(4):kollade ~ .kolumn > indata:n: te-av-typ(4):kollade ~ .kolumn > indata:n: te-av-typ(4):kollade ~ .kolumn::efter,

input:n: te-av-typ(12):kollade ~ .kolumn > indata:n: te-av-typ(12):kollade ~ .kolumn > indata:n: te-av-typ(12):kollade ~ .kolumn > indata:n: te-av-typ(12):kollade ~ .kolumn::efter

Väl semantik är förstörd och dessa väljare är bara för de röd-spelare (en annan runda går för de gul-spelare), å andra sidan det fungerar. En liten fördel är att det inte blir någon falskt upptäcks kolumner eller rader. Visa mekanismen för resultatet var också tvungen att ändras, med hjälp av ::after pseudo-element av alla matchande kolumnen är en konsekvent lösning när ordentlig styling tillämpas. Som en följd av detta, en falsk åttonde kolumnen har att läggas till efter sista plats.

Som kan ses i kod snutten ovan, specifika positioner inom en kolumn är anpassade för att identifiera fyra i rad. Samma teknik kan användas för att detektera fyra i en diagonal genom att justera dessa positioner. Observera att diagonaler kan finns i två riktningar.

input:n: te-av-typ(2):kollade ~ .kolumn > indata:n: te-av-typ(4):kollade ~ .kolumn > indata:n: te-av-typ(6):kollade ~ .kolumn > indata:n: te-av-typ(8):kollade ~ .kolumn::efter,
input:n: te-av-typ(4):kollade ~ .kolumn > indata:n: te-av-typ(6):kollade ~ .kolumn > indata:n: te-av-typ(8):kollade ~ .kolumn > indata:n: te-av-typ(10):kollade ~ .kolumn::efter,

input:n: te-av-typ(12):kollade ~ .kolumn > indata:n: te-av-typ(10):kollade ~ .kolumn > indata:n: te-av-typ(8):kollade ~ .kolumn > indata:n: te-av-typ(6):kollade ~ .kolumn::efter

Antalet väljare har ökat avsevärt under det sista loppet, och detta är definitivt en plats där CSS förbehandling kan minska längden på den förklaringen. Ändå, jag tror demo är måttligt kort. Det bör vara någonstans runt mitten på skalan från att hårdkoda en väljare för varje möjlig vinnande mönster med hjälp av 4 magiska väljare (kolumn, rad, två diagonaler).

Ett meddelande visas när en spelare vinner

Täppa till de luckor

Någon programvara har kant fall och de måste hanteras. De möjliga utfallen av en Connect 4 spelet är inte bara röda, eller gula spelare vinna, men varken vinnande spelare att fylla styrelsen känd som draw. Tekniskt sett är detta mål inte bryta spelet eller producera något fel, vad som saknas är den feedback till spelarna.

Målet är att upptäcka när det finns 42 :kollade radio-knappar på brädet. Detta innebär också att ingen av dem är i :obestämt tillstånd. Som kräver ett urval att göras för varje grupp med alternativknappar. Radio-knapparna är ogiltig, när de är :obestämd, annars är giltig. Så jag har lagt till det obligatoriska attributet för varje ingång, sedan används :gäller pseudo-klass på formuläret för att upptäcka dra.

Oavgjort resultat-meddelande visas när brädet är fullt

Täcker rita resultatet infört en bugg. I mycket sällsynta fall av gula spelare att vinna den sista sväng, både vinna och dra meddelandena visas. Detta beror på att upptäcka och visa metod för att dessa resultat är ortogonala. Jag arbetade runt problemet genom att se till att vinna meddelandet har en vit bakgrund och är över drar meddelande. Jag var också tvungen att fördröja blekna i övergången av rita budskap, så skulle det inte får blandas med win meddelande övergången.

Den gula vinner budskapet är över oavgjort resultat förhindra att det ska visas

Medan en hel del av radio-knappar gömde sig bakom varandra med absolut positionering, alla dessa i obestämd staten kan fortfarande nås av tabb genom kontroller. Detta gör det möjligt för spelare att släppa sina skivor till godtyckliga platser. Ett sätt att hantera detta är att helt enkelt förbjuda tangentbord interaktioner av attributet tabindex: att ange det som -1 betyder att det inte bör vara nåbar via sekventiell navigering med tangentbordet. Jag var tvungen att öka varje radio input med detta attribut för att eliminera detta kryphål.

<input type=”radio” name=”slot11″ tabindex=”-1″ krävs> >
<input type=”radio” name=”slot11″ tabindex=”-1″ krävs> >
<div class=”disc”></div>

Begränsningar

Den mest påtagliga nackdelen är att styrelsen inte är lyhörd och det kanske är fel på små viewport på grund av otillförlitliga lösning för spårning visar. Jag vågade inte ta risken av refactoring till en responsiv lösning, på grund av genomförandet känns det mycket säkrare med hårdkodade dimensioner.

En annan fråga är den klibbiga sväva på touch-enheter. Att lägga till några interaktion media frågor till rätt ställen är det enklaste sättet att bota detta, även om det skulle eliminera free fall-animation.

Man skulle kunna tro att :obestämd pseudo-klassen är redan ett brett stöd, och det är det. Problemet är att det är bara delvis stöd i vissa webbläsare. Observera Not 1 i huruvida tabell: MS IE och Edge inte stödja det på radio-knappar. Om du vill visa demo i dessa webbläsare kommer markören förvandlas till en inte accepteras markör på bordet, det är en oavsiktlig men något graceful degradation.

Alla webbläsare har inte stöd :obestämt på radio-knappar

Slutsats

Tack för att göra det till det sista avsnittet! Låt oss se på några siffror:

  • 140 HTML-element
  • 350 (rimlig) rader CSS
  • 0 JavaScript
  • 0 externa resurser

Sammantaget är jag nöjd med resultatet och responsen var stor. Jag lärde mig en hel del att göra denna demo och jag hoppas att jag kan dela mycket att skriva denna artikel!