zhzabcd commited on
Commit
7092f63
·
verified ·
1 Parent(s): 80693a9

Upload 9 files

Browse files
Files changed (9) hide show
  1. .gitignore +2 -0
  2. Dockerfile +8 -0
  3. LICENSE +21 -0
  4. favicon.ico +0 -0
  5. index.html +286 -0
  6. qrcode.jpg +0 -0
  7. requirements.txt +1 -0
  8. run.sh +1 -0
  9. server.py +86 -0
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ .idea
2
+ __pycache__
Dockerfile ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ FROM python:3
2
+ MAINTAINER hullqin
3
+ EXPOSE 8000
4
+ COPY . /root/gobang
5
+ WORKDIR /root/gobang
6
+ RUN pip config set global.index-url https://mirrors.cloud.tencent.com/pypi/simple
7
+ RUN pip install -r requirements.txt
8
+ CMD daphne -b 0.0.0.0 -p 8000 server:application
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Hull
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
favicon.ico ADDED
index.html ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" style="height: 100%">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <link rel="icon" href="https://fe-1255520126.file.myqcloud.com/gobang/favicon.ico">
7
+ <title>五子棋</title>
8
+ </head>
9
+ <body style="height: 100%; margin: 0; background: bisque">
10
+ <style>
11
+ .message {-webkit-box-sizing: border-box;box-sizing: border-box;margin: 0;padding: 0;color: rgba(0,0,0,.85);font-size: 14px;font-variant: tabular-nums;line-height: 1.5715;list-style: none;-webkit-font-feature-settings: "tnum";font-feature-settings: "tnum";position: fixed;top: 8px;left: 0;z-index: 1010;width: 100%;pointer-events: none;text-align: center;}
12
+ .message > div {padding: 6px;}
13
+ .message > div > div {display: inline-block;padding: 12px 18px;background: #fff;border-radius: 2px;-webkit-box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05);box-shadow: 0 3px 6px -4px rgba(0,0,0,.12), 0 6px 16px 0 rgba(0,0,0,.08), 0 9px 28px 8px rgba(0,0,0,.05);pointer-events: all;}
14
+ </style>
15
+ <div id="message" class="message"></div>
16
+ <svg id="svg" viewBox="-76,-76,152,152" style="height: 100%; width: 100%" xmlns="http://www.w3.org/2000/svg">
17
+ <defs>
18
+ <radialGradient id="black">
19
+ <stop offset="0%" style="stop-color:#808080" />
20
+ <stop offset="100%" style="stop-color:#111" />
21
+ </radialGradient>
22
+ <radialGradient id="white">
23
+ <stop offset="0%" style="stop-color:#fff" />
24
+ <stop offset="100%" style="stop-color:#ddd" />
25
+ </radialGradient>
26
+ <rect id="mark" x="-1" y="-1" width="2" height="2"/>
27
+ <circle id="piece" r="4.2"/>
28
+ </defs>
29
+ <path stroke="brown" stroke-width="0.5" fill="none" d="m-70,-70h140v140h-140zm10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,0v-140m10,0v140m10,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140m0,-10h140m0,-10h-140"/>
30
+ <use xlink:href="#mark"/>
31
+ <use xlink:href="#mark" x="40" y="40"/>
32
+ <use xlink:href="#mark" x="40" y="-40"/>
33
+ <use xlink:href="#mark" x="-40" y="40"/>
34
+ <use xlink:href="#mark" x="-40" y="-40"/>
35
+ </svg>
36
+ <div style="position: fixed; top: 0; left: 0;">
37
+ <button id="offline" onclick="game.online ? setOffline() : changeRoom(window.prompt('请输入房间号,开启联机对战。第一进入房间者执黑,第二进入房间者执白,其他人可观战。'))">转为离线模式</button>
38
+ </div>
39
+ <script>
40
+ const svg = document.getElementById('svg');
41
+ const pieces = [];
42
+ for (let x = 0; x < 15; x++) {
43
+ pieces.push([]);
44
+ for (let y = 0; y < 15; y++) {
45
+ const _x = x;
46
+ const _y = y;
47
+ const piece = document.createElementNS('http://www.w3.org/2000/svg', 'use');
48
+ pieces[x].push(piece);
49
+ piece.setAttribute('x', (x * 10 - 70).toString());
50
+ piece.setAttribute('y', (y * 10 - 70).toString());
51
+ piece.setAttribute('fill-opacity', '0');
52
+ piece.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#piece');
53
+ piece.addEventListener('mouseenter', () => {
54
+ if (game.waiting || piece.getAttribute('fill-opacity') === '1') return;
55
+ piece.setAttribute('fill', game.black ? 'url(#black)' : 'url(#white)');
56
+ piece.setAttribute('fill-opacity', '0.5');
57
+ });
58
+ piece.addEventListener('mouseleave', () => {
59
+ if (game.waiting || piece.getAttribute('fill-opacity') === '1') return;
60
+ piece.setAttribute('fill-opacity', '0');
61
+ });
62
+ piece.addEventListener('click', () => {
63
+ if (game.waiting || piece.getAttribute('fill-opacity') === '1') return;
64
+ piece.setAttribute('fill', game.black ? 'url(#black)' : 'url(#white)');
65
+ piece.setAttribute('fill-opacity', '1');
66
+ dropPiece(_x, _y);
67
+ if (game.online) {
68
+ game.waiting = true;
69
+ ws.send(JSON.stringify({type: 'DropPiece', x: _x, y: _y}));
70
+ } else {
71
+ game.black = !game.black;
72
+ }
73
+ });
74
+ svg.appendChild(piece);
75
+ }
76
+ }
77
+ const mark = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
78
+ mark.setAttribute('fill', 'red');
79
+ mark.setAttribute('width', '2');
80
+ mark.setAttribute('height', '2');
81
+ mark.setAttribute('opacity', '0');
82
+ svg.appendChild(mark);
83
+ const dropPiece = (x, y) => {
84
+ mark.setAttribute('x', (x * 10 - 71).toString());
85
+ mark.setAttribute('y', (y * 10 - 71).toString());
86
+ mark.setAttribute('opacity', '0.7');
87
+ game.winner = checkWinner(x, y);
88
+ if (game.winner) {
89
+ game.waiting = true;
90
+ if (game.visiting || !game.online) {
91
+ Message(`游戏结束,${game.winner === 'black' ? '黑' : '白'}棋胜利!`);
92
+ } else {
93
+ if ((game.winner === 'black' && game.black) || (game.winner === 'white' && !game.black)) {
94
+ Message('你赢了!');
95
+ } else {
96
+ Message('你输了,下次加油!');
97
+ }
98
+ }
99
+ }
100
+ };
101
+ const checkWinner = (x, y) => {
102
+ const color = pieces[x][y].getAttribute('fill');
103
+ const potentialWinner = color === 'url(#black)' ? 'black' : 'white';
104
+ const list = [4, 3, 2, 1];
105
+ if (x >= 4) {
106
+ if (!list.some(i => pieces[x - i][y].getAttribute('fill-opacity') !== '1' || pieces[x - i][y].getAttribute('fill') !== color)) {
107
+ return potentialWinner;
108
+ }
109
+ if (y >= 4 && !list.some(i => pieces[x - i][y - i].getAttribute('fill-opacity') !== '1' || pieces[x - i][y - i].getAttribute('fill') !== color)) {
110
+ return potentialWinner;
111
+ }
112
+ if (y <= 10 && !list.some(i => pieces[x - i][y + i].getAttribute('fill-opacity') !== '1' || pieces[x - i][y + i].getAttribute('fill') !== color)) {
113
+ return potentialWinner;
114
+ }
115
+ }
116
+ if (x <= 10) {
117
+ if (!list.some(i => pieces[x + i][y].getAttribute('fill-opacity') !== '1' || pieces[x + i][y].getAttribute('fill') !== color)) {
118
+ return potentialWinner;
119
+ }
120
+ if (y >= 4 && !list.some(i => pieces[x + i][y - i].getAttribute('fill-opacity') !== '1' || pieces[x + i][y - i].getAttribute('fill') !== color)) {
121
+ return potentialWinner;
122
+ }
123
+ if (y <= 10 && !list.some(i => pieces[x + i][y + i].getAttribute('fill-opacity') !== '1' || pieces[x + i][y + i].getAttribute('fill') !== color)) {
124
+ return potentialWinner;
125
+ }
126
+ }
127
+ if (y >= 4) {
128
+ if (!list.some(i => pieces[x][y - i].getAttribute('fill-opacity') !== '1' || pieces[x][y - i].getAttribute('fill') !== color)) {
129
+ return potentialWinner;
130
+ }
131
+ }
132
+ if (y <= 10) {
133
+ if (!list.some(i => pieces[x][y + i].getAttribute('fill-opacity') !== '1' || pieces[x][y + i].getAttribute('fill') !== color)) {
134
+ return potentialWinner;
135
+ }
136
+ }
137
+ return null;
138
+ };
139
+ const messageDiv = document.getElementById('message');
140
+ const Message = (content) => {
141
+ const div = document.createElement('div');
142
+ div.innerHTML = `<div>${content}</div>`;
143
+ messageDiv.appendChild(div);
144
+ setTimeout(() => messageDiv.removeChild(div), 3000);
145
+ };
146
+ const user = {};
147
+ const initializeUser = () => {
148
+ const localUserId = window.localStorage.getItem('user.id');
149
+ if (!localUserId) {
150
+ const newUserId = Math.random().toString().substr(2,8);
151
+ window.localStorage.setItem('user.id', newUserId);
152
+ user.id = newUserId;
153
+ } else {
154
+ user.id = localUserId;
155
+ }
156
+ user.name = window.localStorage.getItem('user.name') || '';
157
+ };
158
+ initializeUser();
159
+ const game = {};
160
+ let ws = null;
161
+ const initializeGame = (room, preserve = false) => {
162
+ game.room = room;
163
+ game.online = game.room !== '';
164
+ if (game.online || !preserve) {
165
+ for (const row of pieces) {
166
+ for (const piece of row) {
167
+ piece.setAttribute('fill-opacity', '0');
168
+ }
169
+ }
170
+ }
171
+ if (!preserve) {
172
+ mark.setAttribute('opacity', '0');
173
+ }
174
+ if (game.online) {
175
+ document.getElementById('offline').innerText = '转为离线模式';
176
+ game.black = true;
177
+ game.waiting = true;
178
+ game.winner = null;
179
+ ws = new WebSocket(`ws://${window.location.host}`);
180
+ ws.onopen = () => {
181
+ ws.send(JSON.stringify({type: 'EnterRoom', id: user.id, name: user.name, room: game.room}));
182
+ };
183
+ ws.onerror = (_ => {
184
+ console.error('error');
185
+ });
186
+ ws.onclose = (event => {
187
+ if (event.code !== 1000) {
188
+ if (event.code === 4000) {
189
+ alert('您已在新的浏览器窗口中进入了该房间,本页面将转换为单机模式!');
190
+ } else {
191
+ alert('连接已断开,将转换为单机模式!');
192
+ }
193
+ setOffline();
194
+ }
195
+ });
196
+ ws.onmessage = (ev => {
197
+ const data = JSON.parse(ev.data);
198
+ const { type } = data;
199
+ if (type === 'InitializeRoomState') {
200
+ game.black = data.black;
201
+ game.visiting = data.visiting;
202
+ game.ready = data.ready;
203
+ const droppedPieces = data.pieces;
204
+ let black = true;
205
+ for (const [x, y] of droppedPieces) {
206
+ pieces[x][y].setAttribute('fill', black ? 'url(#black)' : 'url(#white)');
207
+ pieces[x][y].setAttribute('fill-opacity', '1');
208
+ black = !black;
209
+ }
210
+ if (droppedPieces.length) {
211
+ dropPiece(...droppedPieces[droppedPieces.length - 1]);
212
+ }
213
+ if (!game.winner) {
214
+ if (!game.visiting && data.ready) {
215
+ if (game.black === black) {
216
+ if (droppedPieces.length) {
217
+ if (!game.winner) Message('欢迎回来,该您下棋了!');
218
+ } else {
219
+ Message('欢迎进入,您执黑棋,下棋后即开局,无法更换黑白和对手。');
220
+ }
221
+ game.waiting = false;
222
+ } else {
223
+ Message(`欢迎进入,请等待对手下棋。`);
224
+ }
225
+ } else if (!droppedPieces.length && !data.ready) {
226
+ Message(`欢迎进入,等待对手中。`);
227
+ }
228
+ }
229
+ } else if (type === 'AddPlayer') {
230
+ if (!game.visiting && data.ready && game.black === true) {
231
+ game.ready = true;
232
+ Message('您执黑棋,下棋后即开局,无法更换黑白和对手。');
233
+ game.waiting = false;
234
+ }
235
+ } else if (type === 'DropPiece') {
236
+ const { x, y } = data;
237
+ const piece = pieces[x][y];
238
+ piece.setAttribute('fill', game.black ? 'url(#white)' : 'url(#black)');
239
+ piece.setAttribute('fill-opacity', '1');
240
+ dropPiece(x, y);
241
+ if (game.visiting) game.black = !game.black;
242
+ if (!game.visiting && !game.winner) {
243
+ game.waiting = false;
244
+ Message('请下棋!');
245
+ }
246
+ }
247
+ });
248
+ } else {
249
+ document.getElementById('offline').innerText = '开始联机对战';
250
+ if (!preserve) {
251
+ game.black = true;
252
+ game.waiting = false;
253
+ game.winner = null;
254
+ } else {
255
+ if (!game.winner) {
256
+ game.black = !game.ready || (game.black ? !game.waiting : game.waiting);
257
+ game.waiting = false;
258
+ }
259
+ }
260
+ if (ws) {
261
+ ws.close();
262
+ ws = null;
263
+ }
264
+ }
265
+ };
266
+ initializeGame(window.location.pathname.substr(1));
267
+ window.onpopstate = (event) => {
268
+ initializeGame(event.target.location.pathname.substr(1));
269
+ };
270
+ const setOffline = () => {
271
+ window.history.pushState({preserve: true}, null, '/');
272
+ initializeGame('', true);
273
+ Message('切换至离线模式,可自由模拟棋局,按浏览器返回键可回到房间。')
274
+ };
275
+ const changeRoom = (room) => {
276
+ room = room.replace(/ /g, '');
277
+ if (!room) {
278
+ alert('请输入房间号,空格会被忽略,房间号不能为空');
279
+ } else {
280
+ window.history.pushState(null, null, `/${room}`);
281
+ initializeGame(room);
282
+ }
283
+ };
284
+ </script>
285
+ </body>
286
+ </html>
qrcode.jpg ADDED
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ daphne==3.0.2
run.sh ADDED
@@ -0,0 +1 @@
 
 
1
+ daphne -b 0.0.0.0 -p 80 server:application
server.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ with open('index.html', 'rb') as fp:
4
+ html = fp.read()
5
+
6
+ house = {}
7
+
8
+
9
+ async def application(scope, receive, send):
10
+ if scope['type'] == 'websocket':
11
+ event = await receive()
12
+ if event['type'] != 'websocket.connect':
13
+ return
14
+ await send({'type': 'websocket.accept'})
15
+ event = await receive()
16
+ data = json.loads(event['text'])
17
+ if data['type'] != 'EnterRoom' or not data['id'] or not data['room']:
18
+ await send({'type': 'websocket.close', 'code': 403})
19
+ return
20
+ room_id = data['room']
21
+ user_id = data['id']
22
+ if room_id not in house:
23
+ house[room_id] = {
24
+ 'black': None,
25
+ 'white': None,
26
+ 'pieces': [],
27
+ 'sends': [],
28
+ 'users': [],
29
+ }
30
+ room = house[room_id]
31
+ old = False
32
+ if room['black'] == user_id or room['white'] == user_id:
33
+ old = True
34
+ if user_id in room['users']:
35
+ old_send = room['sends'][room['users'].index(user_id)]
36
+ room['sends'].remove(old_send)
37
+ room['users'].remove(user_id)
38
+ await old_send({'type': 'websocket.close', 'code': 4000})
39
+ else:
40
+ if room['black'] is None:
41
+ room['black'] = user_id
42
+ elif room['white'] is None:
43
+ room['white'] = user_id
44
+ visiting = room['black'] != user_id and room['white'] != user_id
45
+ room['sends'].append(send)
46
+ room['users'].append(user_id)
47
+ await send({'type': 'websocket.send', 'text': json.dumps({
48
+ 'type': 'InitializeRoomState',
49
+ 'pieces': room['pieces'],
50
+ 'visiting': visiting,
51
+ 'black': room['black'] == user_id if not visiting else bool(len(room['pieces']) % 2),
52
+ 'ready': bool(room['black'] and room['white']),
53
+ })})
54
+ if not old and (room['black'] == user_id or room['white'] == user_id):
55
+ for _send in room['sends']:
56
+ if _send == send:
57
+ continue
58
+ await _send({'type': 'websocket.send', 'text': json.dumps({
59
+ 'type': 'AddPlayer',
60
+ 'ready': bool(room['black'] and room['white']),
61
+ })})
62
+ while True:
63
+ event = await receive()
64
+ if event['type'] == 'websocket.disconnect':
65
+ if send in room['sends']:
66
+ room['sends'].remove(send)
67
+ room['users'].remove(user_id)
68
+ if len(room['pieces']) == 0 and len(room['sends']) == 0:
69
+ del house[room_id]
70
+ break
71
+ data = json.loads(event['text'])
72
+ if data['type'] == 'DropPiece':
73
+ room['pieces'].append((data['x'], data['y']))
74
+ for _send in room['sends']:
75
+ if _send == send:
76
+ continue
77
+ await _send({'type': 'websocket.send', 'text': json.dumps({
78
+ 'type': 'DropPiece',
79
+ 'x': data['x'],
80
+ 'y': data['y'],
81
+ })})
82
+ elif scope['type'] == 'http':
83
+ request = await receive()
84
+ if request['type'] == 'http.request':
85
+ await send({'type': 'http.response.start', 'status': 200})
86
+ await send({'type': 'http.response.body', 'body': html})