Counters and Stones

To­day's ar­ti­cle is a bit spe­cial for me: I'll ex­plain a so­lu­tion which I couldn't find for more than four years. The prob­lem is not very prac­ti­cal: it was one as­pect of CSS-only stuff that could be much eas­ier han­dled by JS, but I re­ally wanted to find a clean HTML&CSS-only so­lu­tion for. And I have found it. And in this ar­ti­cle, I'll ex­plain what was the prob­lem and how the so­lu­tion works.

But first, I need to say thanks to Una. I was brows­ing in­ter­nets and found out my­self look­ing at on one of her ar­ti­cles —Pure CSS Games with Counter-In­cre­ment. While I had read this ar­ti­cle a few times be­fore (I re­ally like CSS ex­per­i­ments like that as you can guess!), this time some­thing clicked. I re­mem­bered one of my older un­fin­ished ex­per­i­ments (I've men­tioned this ex­per­i­ment in my old light­ning talk “Don’t look into the source” at Fron­teers 2013 Jam Ses­sion.) which was a bit sim­i­lar to one of the games Una cre­ated, and how both our ex­am­ples didn't solve one hard as­pect. But look­ing at this ar­ti­cle now, when I just wrote the ar­ti­cle on Flex­i­ble Over­flow, I looked at the “Counter-In­cre­ment” part and the puz­zle in my head com­pleted.

I'll ex­plain the prob­lem and the so­lu­tion later, but now look at this, my now fin­ished ex­per­i­ment (This is a re­ally ba­sic im­ple­men­ta­tion of Gomoku —a game where two play­ers com­pete on who could purt five con­sec­u­tive stones first. Only the very ba­sic win­ning rules are im­ple­mented of course.) from the past:

Current turn:

Things you can notice:

  1. You can place “stones” by click­ing/​tap­ping on the line in­ter­sec­tions.
  2. When you place each new stone, the turn passes to an­other player.
  3. When a player places five stones in a row, they win.
  4. Every­thing there is done us­ing just HTML&CSS, no JS in­volved.

Un­til re­cently, it was known how to achieve most of those points in CSS: you can see it in Una's games, for ex­am­ple. Ex­cept (2018.01.10 up­date: Bence solved this prob­lem in­de­pen­dently in his “Con­nect 4” game, in a very sim­i­lar way, but with a few dif­fer­ences.) for the sec­ond point —pass­ing turns ended up to be the hard­est thing. When I did my demo for Fron­teers, I had to pass turns man­u­ally, by do­ing an ex­tra click af­ter putting a stone. Una han­dled this by al­low­ing 5 sec­onds for each player's turn, which is also far from ideal.

In this ar­ti­cle, I'll show you how this can be han­dled in a way you'd need to click only once to put a stone to pass a turn.

The ba­sic idea is that we need to have some kind of a logic switch, which we would use to change the states of our el­e­ments: turn them on and off. While this can be achieved by us­ing the :checked state (and in a lot of cases it is still much bet­ter than what I'll de­scribe there), for our case —when we'd need to change turns at the same time when we place stones on a field it wasn't enough. We'd need to have some­thing that would tog­gle the state with each con­sec­u­tive click on dif­fer­ent items. Here is what I came up with.

Logic Unit

The main idea for our main build­ing block is to use CSS coun­ters. They have this unique prop­erty for CSS: you can in­cre­ment them, decre­ment, and their val­ues would be avail­able all the way down the con­text they were de­clared at.

But here is a prob­lem: we can't use them any­where but the con­tent CSS prop­erty. So, for a long time, I thought that —yeah cool —we can have those coun­ters, but we couldn't use them for any­thing that deals with logic, only for vi­sual stuff, like rep­re­sent­ing the num­ber of things etc.

Un­til I looked at this:

0
42
100

Those are in­te­gers that CSS coun­ters are able to add to our HTML by us­ing con­tent prop­erty. Do you see it? Those num­bers have dif­fer­ent lengths. One sym­bol, two sym­bols, three sym­bols… That means that based on dif­fer­ent val­ues of a counter, it can ac­tu­ally af­fect the page's lay­out, as the gen­er­ated con­tent from them would have dif­fer­ent widths. And as I played a lot with stuff that de­pends on the el­e­ments' widths in my pre­vi­ous ar­ti­cle, when I no­ticed this I im­me­di­ately thought of how that as­pect of coun­ters can be used!

If we would take our ex­am­ple that has a flex­i­ble over­flow, then make it so it would have some fixed width, then we'd add the counter's con­tent in­side its “jump­ing down” part… Look:

🙀

You can see how when you click there, the checked in­put in­cre­ments our counter by 100000, and you can see how the flex­i­ble over­flow makes this counter to jump down (I re­moved over­flow:hid­den from the con­tainer for you to see it) and our hid­den con­tent ap­pears.

That is the ba­sic idea: we use the counter to in­flu­ence the dis­play of an el­e­ment, and when we use the in­put we didn't use any com­bi­na­tors like + or ~ to achieve it!

Now, you can see that it's kinda strange to in­cre­ment by 100000 just in or­der for a counter to fit into our box. That is re­ally easy to solve in a way we'd need to change one or­der of counter to achieve the same:

🙀 🙀 🙀

You can see how not only it works with just chang­ing from 0 to 10, but it also works with any width.

I achieved this by re­duc­ing the in­ner width of the el­e­ment con­tain­ing the counter in a way it could fit only one digit:

box-sizing: border-box;
padding-left: calc(100% - 1.5ch);

I'm us­ing the 1.5ch in­stead of 1ch there to en­sure there won't be any weird stuff hap­pen­ing on the edge val­ues: 1 digit would al­ways fit 1.5ch, and two dig­its would al­ways over­flow it.

You can re­mem­ber what ch stands for from the specs:

‘ch unit’ —equal to the used ad­vance mea­sure of the "0" (ZERO, U+0030) glyph found in the font used to ren­der it.

And that's ex­actly the unit we'd like to use there.

The code

Here is the source for this last ex­am­ple. HTML:

<input class="logic-unit" type="checkbox" id="LogicUnit"
/><label for="LogicUnit"> Click me!</label>
<span class="overflower">
  <span class="overflower-short">
    🙀 🙀 🙀
  </span>
  <span class="overflower-long">
  </span>
</span>

And CSS (with some un­needed vi­sual stuff omited):

.logic-unit:checked {
  counter-increment: logic-unit 10;
}

.overflower {
  display: flex;
  flex-wrap: wrap;
}

.overflower-short {
  overflow: hidden;
  flex-grow: 1;
  width: 0;
}

.overflower-long {
  flex-basis: 100%;
  box-sizing: border-box;
  padding-left: calc(100% - 1.5ch);
}

.overflower-long:before {
  content: counter(logic-unit);
}

And, Or, Not, Xor

The best thing with coun­ters: we can use mul­ti­ple coun­ters at the same time. That means, we can try to im­ple­ment some logic for mul­ti­ple counters!

AND/OR 🐈
AND 🦁🦁
AND DO NOT 😿
XOR 🦁🙀

We achieve this by set­ting dif­fer­ent start­ing val­ues for our coun­ters and by al­low­ing a dif­fer­ent amount of dig­its to fit it:

  • For AND/​OR we need to over­flow only on 3 dig­its. Any new digit would be enough.
  • For AND we need to over­flow only on 4 dig­its. We would need to have both coun­ters set.
  • For NOT we need to in­vert the logic and start from the longer num­ber, de­creas­ing it later.
  • Fi­nally, XOR is achieved by play­ing with the num­bers a bit so any counter would add a digit, but ad­di­tion of an­other one would re­duce it back.

All of this can be achieved for any num­ber of coun­ters, and the best part is that we don't need to know how those coun­ters would be set.

Board Game

All of this al­lows us to do a lot of in­ter­est­ing stuff. In our game these ba­sic logic units are used for:

  1. Pass­ing a turn from one player to an­other.
  2. Set­ting a “checked” state for each stone's la­bel.
  3. Dis­abling a stone of an­other color from be­ing placed in the al­ready filled-in space.

Passing a Turn

So, how is the pass­ing of a turn im­ple­mented? It's rather sim­ple: we need to tog­gle the state of our counter that tells which turn it is:

.board {
  counter-reset: White 0 Black 10;
}
.board > input:checked {
  counter-increment: White 10 Black -10;
}
.board > input[id^=White]:checked {
  counter-increment: White -10 Black 10;
}

Here we han­dle this for each color, so it would be eas­ier to use later:

.board > section > label > span:after {
  content: counter(Black);
}

.board > section > label[for^=White]:after {
  content: counter(White);
}

Our board has two lay­ers of la­bels: one set for white stones, and an­other —for black ones. They're po­si­tioned one over an­other, so what we would need to do next is to hide each other layer on each other's turn. And our over­flower mech­a­nism al­lows us to do it. Then we add vis­i­bil­ity: hid­den on all items of a layer, and re­store it us­ing vis­i­bil­ity: vis­i­ble only on the part that would be shown on each turn. All of this makes it so only the proper la­bels are click­able at each mo­ment, and with each click things change.

Checked state

This is a part that could be im­ple­mented in a dif­fer­ent way, for ex­am­ple (Or by us­ing 338 se­lec­tors for each in­put-la­bel pair. It would be ef­fec­tive, but not that in­ter­est­ing.), by us­ing the :checked along­side a lot of + com­bi­na­tors. But there are two prob­lems: it would be harder to han­dle all the stuff for la­bels, as they would need to be on the same level, and as I found out, Edge has a limit for a num­ber of com­bi­na­tors that can ap­pear in any given se­lec­tor —63. Edge won't like this se­lec­tor (There are 337 stars —cal­cu­lated as 13 columns × 13 rows × 2 − 1.):

:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+label

So, in or­der to be more browser-com­pat­i­ble, and to play more with coun­ters, I've im­ple­mented the checked state us­ing them. But how? Wouldn't we need to de­clare a counter for each in­put and then some­how con­nect them to labels?

Here is when in­line styles and CSS vari­ables (I re­ally like how you can use CSS vari­ables in in­line styles, and I'm work­ing on an ar­ti­cle to show­case just that, so keep tuned!) comes into play:

<input id="Black1" type="radio" style="--stone:B1;"/>

<label for="Black1" style="--stone:B1;"><span></span></label>

Those are HTML for an in­put and a cor­re­spond­ing la­bel. No­tice the in­line style that has the B1 iden­ti­fier. We now can use it in CSS, so, when we check an in­put, we need to set more counters:

.board > input:checked {
  counter-increment: White 10 Black -10 var(--stone) 10;
}
.board > input[id^=White]:checked {
  counter-increment: White -10 Black 10 var(--stone) 10;
}

Note how we can use a CSS vari­able to pass the iden­ti­fier of a counter that we want to in­cre­ment! That works, as well as the fol­low­ing call­ing of the counter in­side our label:

.board > section > label > span:after {
  content: counter(var(--stone));
}
.board > section > label:after {
  content: counter(Black) counter(var(--stone));
}

Here we use the logic unit for this counter in­side the la­bel (no­tice how use­ful is the in­her­i­tance of the CSS vari­ables: we can catch it on the in­ner span's pseudo-el­e­ment), and also as an OR for our la­bel, so we would see it both on the ap­pro­pri­ate player's turn, and when it is checked.

What is also in­ter­est­ing here is that we don't need to de­clare any of those coun­ters —counter-in­cre­ment is enough and by do­ing so we can cre­ate any num­ber of coun­ters right in your HTML (though, I can imag­ine there can be some lim­i­ta­tions).

Disabling Other Stones

The only thing left now is to dis­able the stone of an­other color that is placed at the same spot. With other meth­ods for pass­ing :checked state we could use other CSS prop­er­ties, like just us­ing z-in­dex to place our checked la­bel over any­thing else. But our counter logic al­lows us to only han­dle the width, and that is not what we can use for this purpose.

But we can use the log­i­cal NOT! So, each stone would know which other stone to disable:

<input id="Black1" type="radio" style="--stone:B1;--notstone:W1;"/>

And then we'd change the counter logic a bit:

.board > input {
  counter-increment: var(--stone) 10;
}
.board > input:checked {
  counter-increment: White 10 Black -10 var(--stone) 100 var(--notstone) -10;
}
.board > input[id^=White]:checked {
  counter-increment: White -10 Black 10 var(--stone) 100 var(--notstone) -10;
}

By do­ing this, when we check one stone, it would make it so an­other stone won't achieve its turn state and would al­ways be off!

In the fi­nal ex­am­ple of the game at the start of this ar­ti­cle you can see that we use the turn state in one other place: for dis­play­ing which player's turn is now —and that's a re­ally nice fea­ture of our method: once added, we can then later use those coun­ters as we'd seem fit.

Win Conditions & Other Stuff

Here are a few other fun things about our board game:

  1. We can use <but­ton type="re­set"> to, well, re­set the game state when we'd want, as it would re­set all our inputs.

  2. Our in­puts are in fact ra­dio in­puts. And you can no­tice the ab­sence of the name at­tribute: this is im­por­tant, as we don't want to have any groups of in­puts, and we don't want for any checked in­put to be able to be­come unchecked on the fol­low­ing click on its label.

  3. We have im­ple­mented a win con­di­tion: 5 con­sec­u­tive stones of any color.

This last win con­di­tion is a thing that I couldn't find a coun­ters so­lu­tion for. Well… there were some ideas, but all of them would have some se­ri­ous draw­backs. So I've used the win con­di­tion based on com­bi­na­tors. Here are the se­lec­tors that de­ter­mine which player would win:

.board > :not(:nth-child(13n-0)):not(:nth-child(13n-1)):not(:nth-child(13n-2)):not(:nth-child(13n-3))[id^=W]:checked + :checked + :checked + :checked + :checked ~ footer,
.board > :not(:nth-child(13n+1)):not(:nth-child(13n+2)):not(:nth-child(13n+3)):not(:nth-child(13n+4))[id^=W]:checked +*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+:checked ~ footer,
.board > [id^=W]:checked +*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+:checked ~ footer,
.board > :not(:nth-child(13n-0)):not(:nth-child(13n-1)):not(:nth-child(13n-2)):not(:nth-child(13n-3))[id^=W]:checked +*+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+:checked ~ footer {
  --endgame: grid;
  --endmessage: 'White won!';
}

.board > :not(:nth-child(13n-0)):not(:nth-child(13n-1)):not(:nth-child(13n-2)):not(:nth-child(13n-3)):checked + :checked + :checked + :checked + [id^=B]:checked ~ footer,
.board > :not(:nth-child(13n+1)):not(:nth-child(13n+2)):not(:nth-child(13n+3)):not(:nth-child(13n+4)):checked+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+ [id^=B]:checked ~ footer,
.board > :checked+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+ [id^=B]:checked ~ footer,
.board > :not(:nth-child(13n-0)):not(:nth-child(13n-1)):not(:nth-child(13n-2)):not(:nth-child(13n-3)):checked+*+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+:checked+*+*+*+*+*+*+*+*+*+*+*+*+*+ [id^=B]:checked ~ footer {
  --endgame: grid;
  --endmessage: 'Black won!';
}
  • In each se­lec­tor group the first one is de­ter­min­ing the con­di­tion for 5 stones in a hor­i­zon­tal row [⋯], the sec­ond one —for first di­ag­o­nal [⋰]︎, third —5 stones in a ver­ti­cal col­umn [ ⋮ ], and the last one —for an­other di­ag­o­nal [︎⋱].
  • Each se­lec­tor ex­cept for the ver­ti­cal one has an ex­tra con­di­tion that pre­vents the false pos­i­tives. Those are pos­si­ble, as our rows are not iso­lated and the stones go one af­ter an­other, so five stones in a row could be matched when some of them are in dif­fer­ent rows.
  • We can omit most of the ex­tra stuff, but in or­der to prop­erly de­ter­mine which stones are se­lected, we need to ei­ther check the color of the first or last one in a se­lec­tor.

Scaling and Edge bugs

This kind of se­lec­tors can be done for a board of any size and for any num­ber of con­sec­u­tive stones to win, how­ever, there is Edge. It won't al­low us to have se­lec­tors with 63 or more com­bi­na­tors (Com­bined, of any kind, you can see the re­duced case in this code­pen —the se­lec­tor which have 62 com­bi­na­tors is ap­plied in Edge, but the one con­tain­ing one ex­tra is not.). Look at this:

[id^=W]:not(:nth-child(15n-3)):not(:nth-child(15n-2)):not(:nth-child(15n-1)):not(:nth-child(15n)):checked + *+*+*+*+*+*+*+*+*+*+*+*+*+*+* + :checked + *+*+*+*+*+*+*+*+*+*+*+*+*+*+* + :checked + *+*+*+*+*+*+*+*+*+*+*+*+*+*+* + :checked + *+*+*+*+*+*+*+*+*+*+*+*+*+*+* + :checked ~ p

It is a se­lec­tor for one of the con­di­tions for the 15×15 grid, and that con­di­tion won't work in Edge. So, ba­si­cally, our board game still could be played there —the coun­ters con­di­tions would work —but we would need to de­ter­mine who won man­ually.

This is a case where I'd re­ally like to see a way in CSS to say “skip N con­sec­u­tive el­e­ments” or “skin N wrap­pers”, or nth- vari­ants of the + and > com­bi­na­tors. While this would help with an Edge prob­lem (which should be fixed any­way, in my opin­ion), I of­ten see cases like this or sim­i­lar, where we need to skip a pre­de­ter­mined num­ber of el­e­ments or wrap­pers, and right now the only tool we have is that clunky rows of stars. And it would be nice to have some­thing better.

Generated Boards

When I was play­ing with all of that, I cre­ated a Code­pen that gen­er­ates HTML for our boards based on the num­ber of columns and rows, so you can test dif­fer­ent lay­outs and see how, for ex­am­ple, 3x3 or 19x19 would work (or not —in Edge).

A few things to note:

  • The only CSS gen­er­ated (from Pug, so no need in CSS-pre­proces­sor) are those win con­di­tions.
  • Every­thing else is han­dled by CSS vari­ables.
  • I set the num­ber of columns and rows from pug as in­line CSS vari­ables, so you can just change those two num­bers and get the re­sult!
  • Some of the board lay­outs are untested, for ex­am­ple, the board is drawn us­ing CSS gra­di­ents, and not every­thing could work per­fectly on each board size.

Conclusion

It was very fun for me to play with this new­found way to ex­press logic in CSS. It is sad that there are a lot of draw­backs with it: that its only ap­pli­able for han­dling stuff that is based on el­e­ment's di­men­sions, and what we can't use tran­si­tions any­where. But yeah, com­plet­ing an ex­per­i­ment started in 2013 was re­ally nice.

And what would be also very nice is to have some­thing like that in CSS na­tively. Maybe Tab Atkins' ideas about Tog­gle States would be there some­day? We can only dream. Go and ping your spec au­thors and browser de­vel­op­ers, cre­ate your ex­am­ples of han­dling logic in CSS (I can see how these logic units could be used for form val­i­da­tion, for ex­am­ple), and let us move CSS for­ward, so we would have even more stuff to play with.