1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+
4+ < head >
5+ < meta charset ="UTF-8 ">
6+ < meta http-equiv ="X-UA-Compatible " content ="IE=edge ">
7+ < meta name ="viewport " content ="width=device-width, initial-scale=1.0 ">
8+ < link rel ="stylesheet " href ="./assets/global.css ">
9+ < style >
10+ body {
11+ display : flex;
12+ align-items : center;
13+ justify-content : center;
14+ height : 100vh ;
15+ }
16+
17+ .reversi-board {
18+ width : 100vmin ;
19+ height : 100vmin ;
20+ background-color : # 059a14 ;
21+ padding : 20px ;
22+ position : relative;
23+ box-sizing : border-box;
24+ }
25+
26+ .reversi-board .grid {
27+ position : absolute;
28+ border : 1px solid # 000 ;
29+ box-sizing : border-box;
30+ display : flex;
31+ align-items : center;
32+ justify-content : center;
33+ }
34+
35+ .reversi-board .grid .allowSelect {
36+ position : absolute;
37+ left : 0 ;
38+ right : 0 ;
39+ bottom : 0 ;
40+ top : 0 ;
41+ border : 2px solid # f0f ;
42+ }
43+
44+ .reversi-board .piece {
45+ width : 90% ;
46+ height : 90% ;
47+ border-radius : 50% ;
48+ }
49+
50+ .reversi-board .piece .W {
51+ background : # fff ;
52+ }
53+
54+ .reversi-board .piece .B {
55+ background : # 000 ;
56+ }
57+
58+ .reversi-board .game-over {
59+ position : absolute;
60+ left : 0 ;
61+ top : 0 ;
62+ right : 0 ;
63+ bottom : 0 ;
64+ background-color : rgba (255 , 255 , 255 , .8 );
65+ display : flex;
66+ justify-content : center;
67+ align-items : center;
68+ flex-direction : column;
69+ }
70+
71+ .reversi-board .game-over .title {
72+ font-size : 40px ;
73+ font-weight : bold;
74+ color : # fdad19 ;
75+ }
76+
77+ .reversi-board .game-over .subtitle {
78+ font-size : 20px ;
79+ margin-top : 10px ;
80+ }
81+
82+ .reversi-board .game-over .maintitle {
83+ font-size : 22px ;
84+ margin-top : 20px ;
85+ }
86+ </ style >
87+ </ head >
88+
89+ < body >
90+ < div class ="reversi-board "> </ div >
91+ < script type ="module ">
92+ // String To V2
93+ function strToV2 ( str ) {
94+ let x = + str . split ( '-' ) ?. [ 0 ]
95+ let y = + str . split ( '-' ) ?. [ 1 ]
96+ return { x, y }
97+ }
98+ // V2 To String
99+ function v2ToStr ( x , y ) {
100+ return `${ x } -${ y } `
101+ }
102+
103+ let reversiBoardDOM = document . querySelector ( ".reversi-board" )
104+ let sideLength = ( reversiBoardDOM . clientWidth - 40 ) / 8 // 边长
105+ let operatePlayer = 'B' ; // 操作的颜色
106+ let lastPos = [ ] // 操作点位
107+ let reversiBoard = new Proxy ( { } , {
108+ get ( obj , prop ) {
109+ return obj [ prop ] ;
110+ } ,
111+ set ( obj , prop , val ) {
112+ let pos = prop // 位置
113+ let lastPiece = obj [ prop ] // 上次的棋子
114+ let { x, y } = strToV2 ( prop )
115+ let color = val
116+ let gridDOM = reversiBoardDOM . querySelector ( `.grid[data-x="${ x } "][data-y="${ y } "]` )
117+ obj [ prop ] = val // 落子
118+
119+ if ( ! lastPiece ) { // 为空落子
120+ // 旗子元素
121+ let pieceDOM = document . createElement ( 'div' )
122+ pieceDOM . classList . add ( 'piece' )
123+ pieceDOM . classList . add ( color )
124+ gridDOM . appendChild ( pieceDOM )
125+ }
126+ else {
127+ let pieceDOM = gridDOM . querySelector ( '.piece' )
128+ pieceDOM . classList . remove ( 'W' )
129+ pieceDOM . classList . remove ( 'B' )
130+ pieceDOM . classList . add ( color )
131+ }
132+
133+ return true
134+ }
135+ } )
136+ // 初始化棋盘
137+ for ( let y = 0 ; y < 8 ; y ++ ) {
138+ for ( let x = 0 ; x < 8 ; x ++ ) {
139+ let gridDOM = document . createElement ( "div" )
140+ gridDOM . classList . add ( 'grid' )
141+ gridDOM . style . top = sideLength * y + 20 + 'px'
142+ gridDOM . style . left = sideLength * x + 20 + 'px'
143+ gridDOM . style . width = sideLength + 'px' ;
144+ gridDOM . style . height = sideLength + 'px' ;
145+ gridDOM . setAttribute ( 'data-x' , x )
146+ gridDOM . setAttribute ( 'data-y' , y )
147+ gridDOM . addEventListener ( "click" , ( e ) => {
148+ if ( e . target . classList . contains ( "allowSelect" ) ) { // 允许选择
149+ let x = e . target . getAttribute ( 'data-x' )
150+ let y = e . target . getAttribute ( 'data-y' )
151+ let allowSelectPos = lastPos . filter ( op => op [ op . length - 1 ] == v2ToStr ( x , y ) )
152+ allowSelectPos . forEach ( ( arr ) => {
153+ arr . forEach ( key => {
154+ reversiBoard [ key ] = operatePlayer ;
155+ } )
156+ } )
157+ reversiBoardDOM . querySelectorAll ( '.allowSelect' ) . forEach ( dom => dom . classList . remove ( 'allowSelect' ) )
158+ operatePlayer = 'W' ;
159+ nextPlayerOperate ( )
160+ }
161+ } )
162+ reversiBoardDOM . appendChild ( gridDOM )
163+ }
164+ }
165+
166+
167+ // 初始化棋子
168+ reversiBoard [ '3-3' ] = 'W'
169+ reversiBoard [ '4-3' ] = 'B'
170+ reversiBoard [ '3-4' ] = 'B'
171+ reversiBoard [ '4-4' ] = 'W'
172+
173+
174+ // 加载可以操作的点位
175+ function nextPlayerOperate ( ) {
176+ showReversiBoard ( ) ;
177+ let pos = [ ]
178+ for ( const key in reversiBoard ) {
179+ if ( reversiBoard [ key ] === operatePlayer ) {
180+ let { x, y } = strToV2 ( key )
181+ pos . push ( ...find8Direction ( x , y ) )
182+ }
183+ }
184+
185+ if ( pos . length === 0 ) {
186+ if ( lastPos . length === 0 ) {
187+ console . log ( '游戏结束' ) ;
188+ showGameOver ( )
189+ return
190+ }
191+ }
192+ else if ( operatePlayer === "B" ) { // 玩家提供可操作的点位
193+ // 渲染到页面
194+ let allowSelect = pos . map ( a => a [ a . length - 1 ] )
195+ allowSelect . forEach ( ( key ) => {
196+ let { x, y } = strToV2 ( key )
197+ reversiBoardDOM . querySelector ( `.grid[data-x="${ x } "][data-y="${ y } "]` ) . classList . add ( 'allowSelect' )
198+ } )
199+ lastPos = pos ;
200+ return ;
201+ }
202+ else if ( operatePlayer === 'W' ) { // 机器人
203+ let allowSelect = pos . sort ( ( a , b ) => a . length - b . length ) ;
204+ let selected = allowSelect [ allowSelect . length - 1 ]
205+ let key = selected [ selected . length - 1 ]
206+ allowSelect . filter ( arr => arr [ arr . length - 1 ] === key ) . forEach ( arr => {
207+ arr . forEach ( key => {
208+ reversiBoard [ key ] = operatePlayer ;
209+ } )
210+ } )
211+ }
212+ operatePlayer = operatePlayer == "B" ? "W" : "B"
213+ lastPos = pos ;
214+ nextPlayerOperate ( ) ;
215+ }
216+
217+ // 查询一个方向可以操作点位
218+ function find1Direction ( sx , sy , dx , dy ) {
219+ let pos = [ ]
220+ let cx = sx + dx ;
221+ let cy = sy + dy ;
222+ while ( - 1 < cx && cx < 8 && - 1 < cy && cy < 8 ) {
223+ let key = v2ToStr ( cx , cy )
224+ if ( ! reversiBoard [ key ] ) return pos . length ? pos . concat ( key ) : [ ]
225+ else if ( reversiBoard [ key ] === operatePlayer ) return [ ]
226+ else pos . push ( key )
227+ cx += dx
228+ cy += dy
229+ }
230+ return [ ]
231+ }
232+
233+ // 查询八个方向可以操作点位
234+ function find8Direction ( cx , cy ) {
235+ let sx = cx ;
236+ let sy = cy ;
237+ let dt = find1Direction ( sx , sy , 0 , - 1 ) ;
238+ let db = find1Direction ( sx , sy , 0 , 1 ) ;
239+ let dl = find1Direction ( sx , sy , - 1 , 0 ) ;
240+ let dr = find1Direction ( sx , sy , 1 , 0 ) ;
241+ let dtl = find1Direction ( sx , sy , - 1 , - 1 ) ;
242+ let dtr = find1Direction ( sx , sy , 1 , - 1 ) ;
243+ let dbl = find1Direction ( sx , sy , - 1 , 1 ) ;
244+ let dbr = find1Direction ( sx , sy , 1 , 1 ) ;
245+ return [
246+ dt , db , dl , dr , dtl , dtr , dbl , dbr
247+ ] . filter ( a => a . length )
248+ }
249+
250+ // 展示游戏结束
251+ function showGameOver ( ) {
252+ let gameOverDOM = document . createElement ( "div" )
253+ let gameOverTitleDOM = document . createElement ( "div" )
254+ let gameOverSubTitleDOM = document . createElement ( "div" )
255+ let gameOverMainTitleDOM = document . createElement ( "div" )
256+
257+ let pieceList = Object . values ( reversiBoard )
258+ let blackCount = pieceList . filter ( p => p === "B" ) . length
259+ let whiteCount = pieceList . filter ( p => p === "W" ) . length
260+
261+ gameOverDOM . classList . add ( 'game-over' )
262+ gameOverTitleDOM . classList . add ( 'title' )
263+ gameOverSubTitleDOM . classList . add ( 'subtitle' )
264+ gameOverMainTitleDOM . classList . add ( 'maintitle' )
265+
266+ gameOverTitleDOM . textContent = '游戏结束'
267+ gameOverSubTitleDOM . textContent = `黑 ${ blackCount } vs 白 ${ whiteCount } `
268+ gameOverMainTitleDOM . textContent = `${ blackCount > whiteCount ? '真棒🎉,胜利啦~' : '失败啦,不要气馁哦~' } `
269+
270+ gameOverDOM . appendChild ( gameOverTitleDOM )
271+ gameOverDOM . appendChild ( gameOverSubTitleDOM )
272+ gameOverDOM . appendChild ( gameOverMainTitleDOM )
273+ reversiBoardDOM . appendChild ( gameOverDOM )
274+ }
275+
276+ // 展示棋盘图
277+ function showReversiBoard ( ) {
278+ let str = `` ;
279+ for ( let y = 0 ; y < 8 ; y ++ ) {
280+ for ( let x = 0 ; x < 8 ; x ++ ) {
281+ let key = v2ToStr ( x , y )
282+ str += reversiBoard [ key ] ? reversiBoard [ key ] == "W" ? '⚪' : '⚫' : '🈳'
283+ }
284+ str += '\n'
285+ }
286+ console . log ( str ) ;
287+ }
288+
289+ nextPlayerOperate ( )
290+ </ script >
291+
292+
293+
294+ </ body >
295+
296+ </ html >
0 commit comments