- 做者:陳大魚頭
- github: KRISACHAN
不知道你們小時候有沒有玩過一款遊戲叫『井字棋』的。css
它長這樣:html
(我贏了,快誇我 ~o(´^`)o)git
上面的就是本次文章的最終結果,一個用純CSS實現的AI井字棋遊戲,Mmmm,雖然看起來有點蠢。。。github
地址在此:web
遊戲的規則比較簡單,就是在一個九宮格(聽說十六宮格,二十五宮格也行~反正是格子就行),只要你下的棋能連成一條直線,就算贏。瀏覽器
因此此次魚頭就來教你們怎樣才能在這個遊戲中獲勝。微信
額,不對,大霧呀~工具
是怎樣經過純CSS來實現上面這個遊戲~佈局
經過開頭的GIF圖,咱們能夠看到其實這個遊戲是有先手選擇的。
咱們能夠選擇是玩家先下,仍是電腦先下。
那麼若是經過單純的HTML標籤 + CSS屬性,該如何完成呢?
首先咱們轉換下思路,先手選擇不是「我方」跟「電腦方」的選擇,而是「選擇我」以及「不選擇我」之間兩種狀態的切換,那麼基於這個原理,咱們就很快能夠聯想到<input type="checkbox"/>
有如下的效果:
但這裏還有一個問題,就是雖然咱們實現了雙向選擇的效果,可是開頭的GIF圖裏先手選擇是一個好看的switch,明顯<input type="checkbox"/>
沒法實現這個功能,那怎麼呢?
嗯,因此咱們仍是用JS模擬吧!
(吃瓜羣衆:說好的CSS呢?給我打)
對不起,咱們能夠用<label>
標籤來模擬。
<label>
標籤能夠經過for="#hash"
來跟<input id="#hash">
來進行關聯,因此咱們有如下效果:
源碼以下:
<style> .switch { display: inline-block; width: 48px; height: 24px; background: #c4d7d6; vertical-align: bottom; margin: 0 10px; border-radius: 16px; position: relative; cursor: pointer; } .switch::before { content: ''; position: absolute; display: block; width: 16px; height: 16px; top: 4px; left: 4px; background: #2e317c; border-radius: 100%; transition: all 0.25s; } #switch:checked ~ label[for='switch']::before { left: 28px; background: #863020; } </style>
checkbox: <input type="checkbox" id="switch" />
<label for="switch" class="switch"></label>
複製代碼
而後咱們再觀察圖1,能夠發現,當咱們選擇時,是能夠控制「電腦走」的按鈕的。
那麼這個又該怎麼實現呢?
CSS實現不了,咱們用JS吧。
(吃瓜羣衆:??????)
秋,秋,秋得嘛跌。CSS也能夠實現!
咱們看到上面的源碼中有**~
**這個選擇器。
這玩意叫作「兄弟選擇器」,能夠選擇同層級順序排後的兄弟節點,並且無論距離由多遠,老是心連心~。
例若有如下HTML結構:
<span>This is not red.</span>
<p>Here is a paragraph.</p>
<code>Here is some code.</code>
<span>And here is a span.</span>
複製代碼
如下CSS:
p ~ span {
color: red;
}
複製代碼
這樣同樣能夠選中<code>
後面的<span>
。
因此咱們有:
代碼以下:
<style> #computer { width: 100px; display: inline-block; background: #131824; color: #eef7f2; border-radius: 5px; margin-top: 10px; padding: 5px; box-sizing: border-box; cursor: pointer; transition: all 0.25s; } #switch ~ #computer { display: none; } #switch:checked ~ #computer { display: block; } </style>
checkbox: <input type="checkbox" id="switch" />
<label for="switch" class="switch"></label>
<div id="computer" class="computer">電腦走!</div>
複製代碼
咱們再回過頭來看圖1,選擇先手的功能是以彈窗的形式出現的,就是爲了確保選擇先手以前不污染棋盤。因此這該怎麼作呢?
經過上面的DEMO,咱們發現有個:checked
選擇器,這個選擇器任何可選元素的選中狀態,例如<input type="radio">
,<input type="checkbox">
以及<option>
。
因此咱們有如下效果:
代碼以下:
<style> .switch { display: inline-block; width: 48px; height: 24px; background: #c4d7d6; vertical-align: bottom; margin: 0 10px; border-radius: 16px; position: relative; cursor: pointer; } .switch::before { content: ''; position: absolute; display: block; width: 16px; height: 16px; top: 4px; left: 4px; background: #2e317c; border-radius: 100%; transition: all 0.25s; } #switch:checked ~ label[for='switch']::before { left: 28px; background: #863020; } .btn { width: auto; display: inline-block; background: #131824; color: #eef7f2; border-radius: 5px; margin-top: 10px; padding: 5px; box-sizing: border-box; cursor: pointer; transition: all 0.25s; } #switch ~ #computer { display: none; } #switch:checked ~ #computer { display: inline-block; } #start:checked ~ .container { display: none; } </style>
<input type="radio" id="start" />
checkbox: <input type="checkbox" id="switch" />
<div class="container">
<br />
<label for="switch" class="switch"></label>
<br />
<br />
<label for="start" class="btn">皮皮蝦,咱們走</label>
</div>
<div id="computer" class="btn">電腦走!</div>
複製代碼
接下來咱們就是畫棋盤,其實棋盤是個比較常規的九宮格,能夠實現的方式有不少,不過此次魚頭要安利個gird佈局在線生成的網站:grid.malven.co/
圖一的DEMO佈局就是用這個工具生成的,很是方便~
好了,咱們棋盤已經畫好,那麼棋子呢?
嗯,能夠去文具店花15塊錢買一盒黑白棋,而後就能夠下了,好了,本文完結。
大霧啊~
有了棋盤咱們就應該畫棋子了,棋子該怎麼畫呢?
其實怎麼畫都沒關係,重要的是得保證每一個格子都能下兩方的棋子。
在咱們畫棋子以前咱們先談談<input />
的狀態管理。
做爲可替換元素的<input />
,可真是個神器,由於有它以及後續瀏覽器對它功能的不斷完善,因此也是變得愈來愈強大。
根據咱們以往的開發經驗以及上文的描述,咱們很容易就能聯繫到兩個存儲正負狀態的屬性<input type="radio">
和<input type="checkbox">
。
以上兩個不一樣屬性的<input />
都能存儲選擇狀態。
惟一不一樣的是<input type="radio">
選擇狀態自己是單向不可逆的,只有經過所關聯的<input type="radio">
才能夠進行切換。
而<input type="checkbox">
則是雙向可逆的,狀態改變只在當前標籤就能夠完成。效果以下:
那麼咱們回到井字棋來。
咱們棋盤的每一個格子會有三種狀態,一個是初始時,一個是我方落子,另外一個是電腦落子。
若是以數字來表示,則有:
狀態碼 | 含義 |
---|---|
00 | 無子 |
01 | 我方落子 |
10 | 電腦落子 |
結合上面的信息,咱們不難選出<input type="radio">
來畫棋子,因此咱們有:
因此思路就是每一個格子放兩個<input type="radio">
,經過選擇的一個標籤來肯定棋子內渲染的樣式。棋子樣式能夠隨本身美化,根據需求咱們來畫<label>
就行。
因此咱們棋盤的HTML就以下:
<form id="container" class="container">
<input type="radio" name="c-radio-0" id="c-radio-0-X" />
<input type="radio" name="c-radio-0" id="c-radio-0-O" />
......
<div id="c-board" class="c-center">
<div class="c-grid" id="c-grid-0">
<label for="c-radio-0-X"></label>
<div></div>
</div>
......
</div>
<div id="c-computer" class="c-btn">
電腦走!
<label for="c-radio-0-O"></label>
......
</div>
複製代碼
基本的棋盤佈局就這麼完成了,接下來就是下手規則的處理了。
那麼下面咱們就一步一步的解析落子程序。
首先咱們來康康工具人標籤:
<div class="c-grid" id="c-grid-0">
<label for="c-radio-0-X"></label>
<div></div>
</div>
複製代碼
經過上面咱們不難知道<label for="c-radio-0-X"></label>
就是落子標籤,那麼這個<div></div>
是幹啥的呢?
你可別看這個標籤都沒有,像個一無全部的舔狗同樣,可是須要用到它的時候,它能夠立刻變成一個很是有用的工具人。
這個標籤的做用就是用來承載落子的標記。
好比咱們定義己方標籤的id規則是input[id*='-編號-X']
,電腦方是input[id*='-編號-0']
,那麼咱們就能夠經過**~
**選擇器來肯定這個工具人渲染的樣式,例如:
input[id*='-0-X']:checked~#c-board #c-grid-0 div::before {
content: 'X';
background: var(--color1);
color: var(--color3);
}
input[id*='-0-O']:checked~#c-board #c-grid-0 div::before {
content: 'O';
background: var(--color2);
color: var(--color3);
}
複製代碼
來到這裏要格外提一點,每個格子的input[id]
都是O與X兩個的存在,而不是同一個的緣由就是爲了保證狀態不可逆,當checked以後就不讓它復原。
對,就是這樣。
咱們肯定了落子的渲染方式,接下來就是肯定如何落子了。
咱們知道,一個格子裏能夠渲染input[id*='-0-X']
以及input[id*='-0-O']
,咱們也能夠經過點擊來肯定渲染哪個,但是咱們如何肯定點擊的是哪一個呢?
咱們先來捋捋思路。
首先我方下棋,這沒什麼問題,就跟小X王學習機同樣,哪裏不懂點哪裏就能夠,so easy~
可是電腦方是由電腦控制,在本DEMO裏,須要經過點擊下方的「電腦走」按鈕,來讓它自動落子,因此最開始須要讓它隱藏起來。
#c-computer { display: none; }
複製代碼
還有就是我方落完子以後,這個按鈕須要出現,按了以後須要隱藏,因此咱們只須要交替讓它顯示就能夠,也就是這樣:
#c-computer,
input:checked~input:checked~#c-computer,
input:checked~input:checked~input:checked~input:checked~#c-computer,
input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~#c-computer,
input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~#c-computer {
display: none;
}
input:checked~#c-computer,
input:checked~input:checked~input:checked~#c-computer,
input:checked~input:checked~input:checked~input:checked~input:checked~#c-computer,
input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~#c-computer {
display: block;
}
複製代碼
這裏的意思就是我第一個:checked
的<input />
後面的按鈕要display: block
,再來一個則要display: none
起來,如此一個接着一個,一個接着一個,一個接着一個。。
我方落子位置能夠經過咱們主動點擊肯定,那麼電腦方呢?
畢竟是電腦,要是落子位置還要咱們肯定,那就尷大尬了。
首先咱們來看下電腦方相關的HTML結構。
<div id="c-computer" class="c-btn">
電腦走!
<label for="c-radio-0-O"></label>
<label for="c-radio-1-O"></label>
<label for="c-radio-2-O"></label>
<label for="c-radio-3-O"></label>
<label for="c-radio-4-O"></label>
<label for="c-radio-5-O"></label>
<label for="c-radio-6-O"></label>
<label for="c-radio-7-O"></label>
<label for="c-radio-8-O"></label>
</div>
複製代碼
經過上面,咱們能夠發現,當咱們點**「電腦走」**按鈕時,其實是點label[for$='-O']
。
可是label
的層級結構也是肯定的,那麼不就很容易跟label[for$='-X']
的位置衝突了嗎?
既然咱們這裏提到了**「層級」**,那麼咱們不難想到,能夠經過z-index
來肯定點擊是的是哪一個label
。
咱們看實操栗子。
因此咱們就能夠控制每次電腦落子的位置。
怎麼肯定呢?
咱們能夠根據**「玩家」**的落子位置來肯定。
好比玩家在**「0號位置」已經有個:checked
,那麼咱們就能夠按照咱們的想法來肯定「電腦」**的落子位置,以此類推。
例如這樣:
#c-radio-0-X:checked~#c-radio-4-X:checked~#c-radio-8-O:checked~#c-computer label[for='c-radio-2-O'],
...... {
z-index: 2;
}
#c-radio-0-O:not(:checked)~#c-radio-2-O:not(:checked)~#c-radio-4-X:checked~#c-radio-6-O:not(:checked)~#c-radio-8-O:not(:checked)~#c-computer label[for='c-radio-0-O'],
...... {
z-index: 2;
}
複製代碼
好了,終於到了咱們最後一個環節了,就是如何判斷輸贏。
這部分就是經過雙方落子位置來肯定。
衆所周知,咱們有如下幾種贏法:
以字母**「X」**表明贏的規則:
<!-- XXX OOO OOO XOO OXO OOX XOO OOX OOO XXX OOO XOO OXO OOX OXO OXO OOO OOO xxx XOO OXO OOX OOX XOO -->
複製代碼
應該沒有漏吧,就是以上幾種,因此咱們只須要判斷雙方的落子是否知足以上的規則便可,因此咱們有:
#c-radio-0-X:checked~#c-radio-1-X:checked~#c-radio-2-X:checked~#c-result #c-info::before,
#c-radio-3-X:checked~#c-radio-4-X:checked~#c-radio-5-X:checked~#c-result #c-info::before,
#c-radio-6-X:checked~#c-radio-7-X:checked~#c-radio-8-X:checked~#c-result #c-info::before,
#c-radio-0-X:checked~#c-radio-3-X:checked~#c-radio-6-X:checked~#c-result #c-info::before,
#c-radio-1-X:checked~#c-radio-4-X:checked~#c-radio-7-X:checked~#c-result #c-info::before,
#c-radio-2-X:checked~#c-radio-5-X:checked~#c-radio-8-X:checked~#c-result #c-info::before,
#c-radio-0-X:checked~#c-radio-4-X:checked~#c-radio-8-X:checked~#c-result #c-info::before,
#c-radio-2-X:checked~#c-radio-4-X:checked~#c-radio-6-X:checked~#c-result #c-info::before {
content: '恭喜你贏了~';
}
#c-radio-0-O:checked~#c-radio-1-O:checked~#c-radio-2-O:checked~#c-result #c-info::before,
#c-radio-3-O:checked~#c-radio-4-O:checked~#c-radio-5-O:checked~#c-result #c-info::before,
#c-radio-6-O:checked~#c-radio-7-O:checked~#c-radio-8-O:checked~#c-result #c-info::before,
#c-radio-0-O:checked~#c-radio-3-O:checked~#c-radio-6-O:checked~#c-result #c-info::before,
#c-radio-1-O:checked~#c-radio-4-O:checked~#c-radio-7-O:checked~#c-result #c-info::before,
#c-radio-2-O:checked~#c-radio-5-O:checked~#c-radio-8-O:checked~#c-result #c-info::before,
#c-radio-0-O:checked~#c-radio-4-O:checked~#c-radio-8-O:checked~#c-result #c-info::before,
#c-radio-2-O:checked~#c-radio-4-O:checked~#c-radio-6-O:checked~#c-result #c-info::before {
content: '惋惜你輸了~';
}
複製代碼
(吃瓜羣衆:「完美個頭,要是沒輸沒贏呢?」)
要是沒輸沒贏,沒輸沒贏,沒輸沒贏,該怎麼辦呢?沒辦法了,用JS吧。。。
對不起,我錯了,這個功能只須要給這個提示標籤一個默認文本便可。
固然咱們得寫個讓提示彈窗出現的邏輯。
input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~input:checked~#c-result,
...... {
display: block;
}
複製代碼
就是所有空格都:checked
以及幾個關鍵空格佔滿的時候,就讓它展現。
若是咱們想玩下一盤該怎麼辦?
刷新頁面啊!!!
(吃瓜羣衆:「就這?」)
固然不是就這啊,接下來要給你們介紹最後一個姿式:<input type="reset">
<input type="reset">
呈按鈕狀,能夠一鍵初始化表單內全部的<input />
,就像這樣
一鍵初始化,很是方便~
<input />
是一個很是有用且有趣的可替換標籤,業界中大部分的純CSS遊戲差很少都是用它來完成的,雖然不是特別實用,可是結合選擇器,是能夠幫助咱們在業務中解決不少問題的。
若是你喜歡探討技術,或者對本文有任何的意見或建議,很是歡迎加魚頭微信好友一塊兒探討,固然,魚頭也很是但願能跟你一塊兒聊生活,聊愛好,談天說地。 魚頭的微信號是:krisChans95 也能夠掃碼關注公衆號,訂閱更多精彩內容。