スタイルシート ハングマン の説明
戻る


JavaScript を使わず,スタイルシートだけでハングマン ゲームを作ってみます.
全くのお遊びです.これが何か実用の役に立つようなことは多分無いでしょう.スタイルシートは本来,動作を記述するものではありませんので,かなり力ずくで作っています.
画面
(これはキャプチャ画像です.これで遊ぶことはできません.)
使い方と仕組みについて説明します.


使い方

「出題」をクリックすると問題の単語の文字数分の線が表示されます.
単語に含まれている文字を推測して,文字ボードの文字をクリックして選びます.
選んだ文字が単語に含まれていれば,その文字が該当の位置に表示されます.選んだ文字が単語に含まれていなければ,吊るされる人の絵の一部が書き加えられます.
6 回推測が外れたら負けです.1 ゲーム終了したら,再度「出題」をクリックすると次の問題が表示されます.


問題のデータの持ち方

問題の単語は 100 個用意しています.
以下のような構成の HTML で単語のデータを定義しています.
<DIV ID="questions">
  <DIV CLASS="word" STYLE="--q:00">
    <P CLASS="A"><P CLASS="B"><P CLASS="O"><P CLASS="V"><P CLASS="E">
  </DIV>
  <DIV CLASS="word" STYLE="--q:01">
    <P CLASS="A"><P CLASS="C"><P CLASS="H"><P CLASS="I"><P CLASS="E"><P CLASS="V"><P CLASS="E">
  </DIV>
  <DIV CLASS="word" STYLE="--q:02">
    <P CLASS="A"><P CLASS="G"><P CLASS="O">
  </DIV>
    ・
    ・
    ・
</DIV>
単語の各文字を P タグで表して,単語の文字数分 P タグを並べます.
‘A’〜‘Z’の各文字を表すクラスを定義して,対応するクラスを各 P タグに設定します.クラス名はその文字と同じにしています.つまり,‘A’の文字を表すクラスならクラス名は“A”です.

たとえば,“ABOVE”という単語ならこのようなタグで表します.
<P CLASS="A"><P CLASS="B"><P CLASS="O"><P CLASS="V"><P CLASS="E">
なお, P タグは終了タグを省略できるので,なるべく HTML が短くなるように終了タグは省略しています.

ある文字が単語に含まれているかどうかは :has() 擬似クラスで判定します.
たとえば,このようなスタイルを設定すると,単語の子要素に“A”というクラス名の要素が存在するとき,つまり単語に‘A’が含まれるとき,カスタム プロパティ --w-a の値が 1 になります.(初期値は 0 に設定しておきます.)
.word:has(.A) {
  --w-a:1;
}
同様にして,‘A’,‘B’,‘C’, … に対して --w-a--w-b--w-c, … というカスタム プロパティの値を設定します.


選んだ文字の処理

文字‘A’〜‘Z’に対応して 26 個のラジオ ボタンを使います.文字ボードの‘A’〜‘Z’はそのラジオ ボタンのラベル(LABEL 要素)です.
文字をクリックすると対応するラジオ ボタンがチェックされます.その :checked 状態で選んだ文字を判定します.
たとえば,このようなスタイルを設定すると,‘A’の文字を選んだときカスタム プロパティ --g-a の値が 1 になります.(初期値は 0 に設定しておきます.)
#gb-a:checked ‾ #base #questions {
  --g-a:1;
}
gb-a は‘A’のラジオ ボタン要素の id です.
同様にして,‘A’,‘B’,‘C’, … に対して --g-a--g-b--g-c, … というカスタム プロパティの値を設定します.

ラジオ ボタンはフォームの中に入れて,フォームをリセットすると未チェック状態に戻るようにします.

上述の --w-a 等と --g-a 等の値で,選んだ文字の当たり/外れを判定します.
たとえば,‘A’の文字についてはこのようになります.
--w-a が 1 で --g-a が 0 → 単語に‘A’が含まれているが,まだ当たっていない
--w-a が 0 で --g-a が 1 → 単語に‘A’が含まれていないが‘A’を選んだので外れた

‘A’の文字の P 要素で --g-a が 1 → 当たった
「単語に‘A’が含まれていて,当たった」という条件は「--w-a が 1 で --g-a が 1」になりますが,当たったときの処理は各文字のプロパティの設定で行っているので「--g-a が 1」の条件で充分です.(単語に文字が含まれていなければ,そもそもプロパティを設定する対象がありません.)

これらの判定結果によって,当たった文字を表示したり外れたときの絵を書いたりします.外れた選択の個数が 6 になったらゲーム終了の処理を行います.
また,単語に含まれている文字がすべて当てられたら(判定の仕方としては,当たっていない文字が無い状態になったとき)ゲーム終了の処理を行います.


問題の選び方

問題の単語をランダムに選ぶためにアニメーションの機能を使います.
100 個の単語に 0 〜 99 の番号を付け,番号を表すカスタム プロパティの値をアニメーションで変化させます.その番号に対応する単語を問題にします.
ひとつの単語を出題している間,番号が変わらないようにするため,後述の「問題の番号の保持」の方法で問題の番号を保持しています.
新しい問題を開始したとき,つまり,まだ文字をひとつも選んでいない状態のときはアニメーションを停止します.
文字をひとつ選んだら,単語の番号を保持し,アニメーションを再開します.つまり,問題を解答している間に裏で,次に出題する単語の番号を変化させています.

保持した番号にしたがって各単語を表す要素(クラスが word の DIV 要素)の Z オーダーを操作して,保持した番号に対応する単語を前面にし,それ以外の単語は背景の背面にして隠します.
当たった文字を表示したり外れたときの絵を書いたりする処理は,実際にはすべての単語に対して行われています.問題以外の単語は背景に隠れているため見えないだけです.

プログラム(?)を起動した直後を除き,「出題」はフォームのリセット ボタンのラベルになっています.「出題」をクリックするとフォームがリセットされて,文字を選ぶラジオ ボタンはすべて未チェックの状態に戻ります.問題を選ぶアニメーションは停止します.

プログラムを起動した直後の処理のために,もうひとつラジオ ボタンを使います.プログラムを起動した直後では,「出題」はそのラジオ ボタンのラベルになっています.
そのラジオ ボタンがチェックされるまではアニメーションを動かしておいて,最初の問題をランダム化します.その間は問題は表示しないようにしておきます.
ラジオ ボタンがチェックされたらアニメーションを停止し,問題を表示します.


問題の番号の保持

問題を選ぶアニメーションを実行している間も出題中の問題の番号を保持しておくために transition の機能を流用します.
transition では,プロパティの設定値が変更されてから実際に値が変わり始めるまでのディレイを設定できます.問題の番号を保存するカスタム プロパティに,このようにディレイを設定します.
transition:--sel-q 0s 10000s;
そうすると,設定値(アニメーションで選んだ値)が変わっても,ディレイで指定した時間が経過するまでは元の値が保持されます.ディレイに充分大きな値を指定することで設定した値を保持します.
新しい問題を出題するときに transition を無効にして,値が変わるようにします.

カスタム プロパティに transition を適用するために,後述のように @ルールの @property でプロパティをアニメーション可能に設定しています.


カスタム プロパティの定義

このプログラムでは,いくつかのカスタム プロパティを @ルールの @property で定義しています.
@property の定義は,たとえばこのように書きます.
@property --a {
  syntax:'<integer>';
  inherits:true;
  initial-value:0;
}
上述したように,問題の単語を選択する処理でアニメーション(animation,transition)を使っている他,絵を描く処理でカスタム プロパティのアニメーションを使っています.カスタム プロパティに計算値によるアニメーションや transition を使うためには,プロパティのデータ型を定義する必要があります.
@propertysyntax<integer><angle> などを指定することで,そのプロパティを計算値によるアニメーション可能にしています.


このおもちゃは Opera 91 以上用に作っていますが,Google Chrome 107 で動くことが確認できています.Google Chrome の他のバージョンでの動作は未確認です.


戻る