How to make four sub-games with pure CSS

Source: Internet
Author: User
Tags button type html form css preprocessor

Preface: Have you ever thought that using CSS alone can make a game? You can even duel with two people? This is a very interesting article, the author explained in detail the use of pure CSS to make four sub-line game ideas and the use of strange trick techniques to solve difficult problems. Because the case itself is more complex, and my level is limited, translation must have inappropriate places, look correct.

Original: How the Roman Empire made Pure CSS Connect 4 Possible

Translation: Nzbin

Experimentation is an interesting way to learn new tricks, think new ideas, and break through their limits. The "Pure CSS" demo was long overdue, but as browsers and CSS developed, new challenges emerged. CSS and HTML preprocessor also promote the development of pure CSS presentation. Sometimes, a preprocessor is used to hardcode every possible scenario, such as :checked a long string and an adjacent sibling selector.

In this article, I'll cover the key ideas of a four-sub-alignment game made with pure CSS. In my experiment, I tried to avoid hard coding, and I didn't use a preprocessor to focus on keeping the code simple. Here are all the code and demos for the game:

See the Pen Pure CSS Connect 4 by Bence Szabó (@finnhvman) on Codepen.

Basic concepts

I think there are some concepts that are essential in the "pure CSS" type. Typically, form elements are used to manage state and capture user actions. I was very excited when I found someone using <button type="reset"> reset or restarting a new game. Just wrap the element in the <form> label and add a button. In my opinion, this is a more convenient solution than refreshing the page.

The first step is to create the form element, and then create some input in the form that is used as a round hole (the slots), and then add the Reset button. Here are <button type="reset"> the basic demos to use:

See the Pen Pure HTML Form, Reset by Bence Szabó (@finnhvman) on Codepen.

To make the demo look good, I use it instead of radial-gradient() sticking a picture on the game board (the Board) or the disc (the discs). I often use the CSS3 pattern Library made by Lea Verou. It is a set of patterns made with gradients and is easy to edit. I used the CurrentColor, which is perfect for a disc pattern. I added a header and reused my own pure CSS ripple button.


Now, the layout and disc have been designed, but not yet played.

Put the disc on the game board.

Next, you need to let the user take turns to put the disc on the four-sub-alignment of the game board. In the four-sub-alignment game, the player (a red, a yellow) rotates the disc in the column of the panel. The game board has 7 columns of 6 rows (a total of 42 round holes). Each round hole can be empty or be occupied by a red or yellow disc. Therefore, a round hole can have three states (empty, red, or yellow). The disks that fall in the same column are stacked together.

First I placed two checkboxes for each round hole. When none of them is selected, the round hole is considered empty, and when one is selected, the corresponding player will put his disc in.

When any one of them is selected, it should be hidden to avoid a state where both are selected. These checkboxes are direct sibling classes, so if you select the first one, you can :checked hide two elements using pseudo-class and adjacent sibling selectors ( + ). But what if the second one is selected? You can hide the second one, but how do you affect the first one? Unfortunately did not choose the previous sibling selector, this is not the way CSS selectors work. I have to reject the idea.

In fact, a checkbox itself can have three states and can use indeterminate state. The problem is that using HTML alone cannot put it in an indeterminate state. Even if you can, when you tap the check box again, it will also be converted to the selected state. It is unrealistic to force a second player to double-click when moving the disc.

After I read the documentation on MDN, I :indeterminate found that radio input generic has a indeterminate status. The radio button with the same name is in this state when unchecked. Wow, this is a real initial state! What really works is that the next sibling element will have an effect on the former! So I placed 42 pairs of radio input on the game board.

In the past, using a label and matching a checkbox or radio in a reasonable order can solve the problem, but I don't think the label will make the code more concise.

To get a better user experience, I want the interactive area to be larger, so it's reasonable to have the player click on a column to move the disc. By adding the absolute and relative positions on the appropriate elements, I overlay the controls on the same column with each other. This allows you to select only the bottom round hole in each column. I carefully set the time for each row of the disk to fall, and their time function approximates a two-time curve, similar to the real freefall. So far, the parts of the game have been done, but it's clear that only red players can operate.


Although all the controls have been set, only the red disc can fall on the gamepad.

I used a color and translucent rectangle to visualize the clickable area of Radio input. Yellow and red input overlap 6 times (= 6 rows) on each column, placing the red input at the top of the bottom row. The red and yellow mixes form an orange-yellow color that can be seen on the game board. The fewer round holes available in each column, the less intense the orange is, because radio input is only :indeterminate displayed when the state is present. Because the red input always covers the yellow input on each round hole, only the red player can move.

Take turns game

I only have a vague idea that I can use a normal sibling selector to solve the problem of player rotation. The idea is to count the number of input selected, the red player moves for even (0, 2, 4, etc.), and the yellow player moves when it is odd. Soon I realized that the General Brotherhood selector could not (and should not!) ) Work the way I want.

Another way is to use the nth selector. Although I like 偶数 to use and 奇数 such keywords, but I still walked into a dead end. : Nth-child the child elements in the "statistics" parent class, including all types, classes, pseudo-classes, and so on. : Nth-of-type the selector "statistics" a subclass of a type in the parent class, excluding classes or pseudo-classes. So the problem is the inability to pass: checked state to count.

CSS counters can also be counted, so why not try it? A common use of counters is to number headings (or even multiple levels) in a document. They are controlled by CSS rules and can be reset at any time, their increments (or decrements!). ) value can be any integer. The Counter "counter ()" function is displayed in the Content property.

So the simplest way is to set the counter and then count the number of input in the four-sub-alignment game :checked . This approach is only two difficult. First, you cannot perform an arithmetic operation on a counter to detect whether it is an even or an odd number. Second, you cannot apply CSS rules on the element based on the value of the counter.

I used binary to solve the first problem. The initial value of the counter is set to 0. When the red Player selects the radio button, the counter adds 1. When the yellow player selects the radio button, the counter is reduced by 1, and so on. Therefore, the value of the counter is always 0 or 1, even or odd.

Solving a second problem requires more creativity (read:hack). As mentioned above, counters can only be displayed in ::before and ::after pseudo-elements. This is obvious, but how do they affect other elements? At least the counter value can change the width of the pseudo-element. The different numbers have different widths. Characters 1 are usually 0 thinner than slender, but this is difficult to control. If the number of characters is changed, not the character itself, then the resulting width change is controllable. It is not uncommon to use Roman numerals in CSS counters. The 1 and 2 represented by Roman numerals are the same as the characters 1 and 2, and their pixel widths are the same.

My idea is to connect a player (yellow) radio button to the left and connect another player (red) radio button to the right side of the shared parent container. Initially, the red button is overwritten with a yellow button, and the width of the container changes to cause the red button to "disappear" and the yellow button is displayed. It can be likened to a sliding window with two panes in the real world, one pane fixed (yellow button) and the other sliding (red button). The difference is that only half of the windows in the game are visible.

So far, not bad, but I'm not happy with the use font-size (and other font attributes) to control the width indirectly. The better way is letter-spacing to use it because it changes size only in one dimension. Unexpectedly, even if a letter has a letter spacing (rendered behind the letter), two letters have a two-letter spacing. The key to reliability is to ensure that the width is predictable. A character with a width of 0 plus a single-letter and two-letter spacing is possible, but font-size setting it to 0 is risky. In order to be compatible with all browsers, you can letter-spacing set the larger (in pixels) and font-size set it a little bit ( 1px ), yes, I'm talking about sub-pixels.

I need the width of the container to alternate between the initial size ( =w ) and at least twice times the size ( >=2w ) so that the yellow button can be completely hidden and displayed. The rendering width v c (constant) is assumed to be the ' I ' character's render width (lowercase roman letters are different in different browsers) letter-spacing . I need v + c = w to be true, but this is not possible, because c and w is an integer, not an v integer. Finally I used the min-width and max-width attributes to constrain the possible width values, so I also changed the possible counter values to ' I ' and ' III ' to make sure that the text was widening and overflow the constraint. Through equations,,,, v + c < w 3v + 3c > 2w v << c can be obtained 2/3w < c < w . The conclusion is that the "letter spacing" must be smaller than the initial width.

I've always thought that the value of pseudo-element display is the parent element of the radio button, but it's not. However, I notice that the width of the pseudo-element changes the width of its parent element, in this case the parent element is the container for the radio button.

If you're thinking, can't you solve it with Arabic numerals? You're right, the value of the counter alternating between ' 1 ' and ' 111 ' is also possible. Nonetheless, the Roman numerals were the first to give me a hint, they were also a good way to click on the titles of the taps, so I kept them.


Start with a red player and then take turns playing.

Applying the techniques discussed enables the parent container of radio input to double the width of the selected red input and the width of the selected yellow input to the original width. In the original width container, the red input is above the yellow input, and in the double-width container, the red input is moved away.

Recognition mode

In real life, a four-sub-alignment game doesn't tell you whether you won or lost, but providing the right feedback is part of a good user experience for any software. The next goal is to detect if the player has won the game. To win the game, the player must place four discs on a column, line, or diagonal. This is a very simple task in many programming languages, but in the pure CSS world, this is a huge challenge. Breaking it down into subtasks is a way to deal with this problem systematically.

I use a flex container as the parent class for radio buttons and discs. A yellow radio button, a red radio button, and a div that represents the disc and overlaps the round hole. Such a circular hole is repeated 42 times and arranged into multiple columns. Therefore, the round holes in the columns are contiguous, which makes it easiest to identify four of the columns using the adjacent selector:

<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>
/* Red four in a column selector */input:checked + .disc + input + input:checked + .disc + input + input:checked + .disc + input + input:checked ~ .outcome/* Yellow four in a column selector */input:checked + input + .disc + input:checked + input + .disc + input:checked + input + .disc + input:checked ~ .outcome

This is a simple but ugly solution. In order to detect a column of four sub-connected cases, each player has 11 types and class selection linked connected together. After the round hole element, add a class name to .outcome div display the output information. In one column of the listed package, there is a problem with the detection of the quad, but let's put the problem aside.

If a similar method is used to determine if there are four children in a row, it would be a horrible idea. Each player will have 56 selectors (if I am right), not to mention that they will have similar detection errors. In the Future,: Nth-child (an+b [of S]) or column combinators will come in handy.

For better semantics, you can add a new one to each column div and arrange the round hole elements in it. This modification will also eliminate the above-mentioned detection errors. Then, the detection of four children in a row can be connected in the following way: Select the first red radio input selected column, and then select the first red radio input is selected adjacent sibling column, repeat two times. This sounds troublesome and requires a "parent" selector.

It is not feasible to select a parent node, but it is possible to select a child node. How to detect the four sub-connections in a row with a selector and its combined method? Select a column, select its first selected red radio input, select the adjacent column, select its first selected red radio input, and so on, and then repeat two times. It still sounds a lot of trouble, but it's doable. The trick is not only in CSS, but also in HTML, the next column must be the sibling of the radio button that creates the nested structure in the previous column.

<div class="grid column">  <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="column">    <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="column">      ...    </div>  </div></div>
/* Red four in a row selectors */input:nth-of-type(2):checked ~ .column > input:nth-of-type(2):checked ~ .column > input:nth-of-type(2):checked ~ .column > input:nth-of-type(2):checked ~ .column::after,input:nth-of-type(4):checked ~ .column > input:nth-of-type(4):checked ~ .column > input:nth-of-type(4):checked ~ .column > input:nth-of-type(4):checked ~ .column::after,...input:nth-of-type(12):checked ~ .column > input:nth-of-type(12):checked ~ .column > input:nth-of-type(12):checked ~ .column > input:nth-of-type(12):checked ~ .column::after

Semantically confusing, these selectors apply only to red players (the yellow player has another round), but it does help. One advantage is that there are no columns or rows to detect errors. The display of the results must also be modified, and the pseudo-elements used by any matching column ::after should be consistent. Therefore, you must add a pseudo eighth column after the last position.

As shown in the code snippet above, the special positional relationship of a column detects the four children in a row are connected. The same technique can be used to detect the four sub-connections on the diagonal by adjusting these positions. Note that the diagonal can be in two directions.

input:nth-of-type(2):checked ~ .column > input:nth-of-type(4):checked ~ .column > input:nth-of-type(6):checked ~ .column > input:nth-of-type(8):checked ~ .column::after,input:nth-of-type(4):checked ~ .column > input:nth-of-type(6):checked ~ .column > input:nth-of-type(8):checked ~ .column > input:nth-of-type(10):checked ~ .column::after,...input:nth-of-type(12):checked ~ .column > input:nth-of-type(10):checked ~ .column > input:nth-of-type(8):checked ~ .column > input:nth-of-type(6):checked ~ .column::after

In the final code, the number of selectors is very large, and if you use a CSS preprocessor you can significantly reduce the length of the declaration. Nonetheless, I think the demo code is still relatively short. It should be somewhere in the middle, from hard-coded a selector to using 4 magical selectors (columns, rows, two diagonal lines).


When a player wins, a message is displayed.

Fix the vulnerability

Any software has edge situations that need to be addressed. The possible result of a four-child game is not only a red or yellow player winning, but a tie that fills the game board. Technically, this situation does not disrupt the game or produce any errors, and what is missing is feedback to the player.

Our goal is to detect 42 radio buttons on the Blackboard :checked . This also means that none of them are in a :indeterminate state. This requires that you make a selection for each radio button. The radio button is :indeterminate invalid, otherwise it is valid. Therefore, I added a property for each input required and then used pseudo-classes on the form :valid to detect a draw.


When the board is filled, a draw message is displayed.

A bug occurred while detecting the draw result. In rare cases there will be a case of the final victory of the yellow player, and the news of the win and the draw are displayed. This is because the detection and display methods of these results are orthogonal. I solved this problem by making sure that the winning message has a white background and is above the draw message. I must also defer the translation of the paint message so that it does not mix with the winning message transformation.


Huangfang Victory's information covered the draw result

Although many radio buttons are hidden behind each other by absolute positioning, all buttons that are in an indeterminate state can still be accessed through the tabindex of the control. This allows the player to place their discs in any round hole. One way to deal with this problem is to simply disallow tabindex keyboard interaction using attributes: Set it to -1 mean that it should not be accessed through continuous keyboard navigation. To solve this problem, you must add this property on each radio button.

<input type="radio" name="slot11" tabindex="-1" required><input type="radio" name="slot11" tabindex="-1" required><div class="disc"></div>...
Limit

The most substantial drawback is that the game board is not responsive and may fail on a small view window due to the unreliable solution of the rotation game. I dare not risk refactoring a responsive solution, hard coding looks more secure due to the nature of the implementation.

Another problem is touching the sticky hover on the device. Adding some media queries in the right place is the easiest way to solve this problem, but this will eliminate free-falling animations.

Some may think that the :indeterminate pseudo-class has been widely supported, and it is true. The problem is that it only gets partial support in some browsers. Note the comments in the compatibility table 1:ms IE and Edge do not support it on radio buttons. If you view the demo program in these browsers, your cursor will turn into not-allowed a cursor, which is unintentional but somewhat graceful to downgrade.


Not all browsers support the radio button: Indeterminate property.

Summarize

Thanks for reading to the last part! Let's look at some of the data for this game:

    • 140 HTML elements

    • 350 rows (reasonably) CSS

    • 0 lines of JavaScript

    • 0 external Resources

Overall, I am satisfied with the results and the feedback is very good. I did learn a lot by doing this demo, and I hope I can share more of these articles!

How to make four sub-games with pure CSS

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.