-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapp.py
285 lines (204 loc) · 7.61 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# import time
from random import choice
from flask import Flask, flash, redirect, render_template, request, session, url_for
from flask_bcrypt import Bcrypt
from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user
from flask_mysqldb import MySQL
from flask_session import Session
from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy
from ai import minimax
from config import Config
from forms import LoginForm, RegistrationForm
from game import Game, Game_AI
# creates the Flask instance. Passes the argument __name__ which is the name of the application's module. It needs to know this in order for flask to know where to look for resourses.
app = Flask(__name__)
app.config.from_object(Config)
# creates the SQLAlchemy object
db = SQLAlchemy(app)
app.config['SESSION_SQLALCHEMY'] = db
# creates the Session object
# Session instance is not used for direct access, flask.session is used
Session(app)
# creates the MySQL instance
mysql = MySQL(app)
# creates the Bcrypt object
bcrypt = Bcrypt(app)
# creates the LoginManager object
login_manager = LoginManager(app)
# adds Flask_SocketIO to the flask application
socketio = SocketIO(app)
# creates the initial database
db.create_all()
class User(UserMixin):
# pylint: disable=W0622
def __init__(self, id, firstname, surname, username, email, password):
self.id = id
self.firstname = firstname
self.surname = surname
self.username = username
self.email = email
self.password = password
@login_manager.user_loader
def load_user(user_id):
conn = mysql.connection
curs = conn.cursor()
curs.execute("""SELECT * FROM Users WHERE id = (%s);""",
[user_id])
user = curs.fetchone()
curs.close()
if user is None:
return None
else:
return User(*user[:-1])
"""
Routes
"""
@app.route('/')
def index():
"""
GET: Landing page: index.html
"""
return render_template("index.html")
@app.route('/play')
def play():
"""
GET: Play page: play.html
"""
return render_template('play.html')
@app.route('/game-pass-and-play')
def game_pass_and_play():
"""
GET: Create instance of a chess Game, store it in session and display game-pass-and-play page
"""
new_game = Game()
session['game'] = new_game
return render_template("game_pass_and_play.html")
@app.route('/game-ai')
def game_ai():
"""
GET: Create instance of a chess Game, store it in session and display game-ai page
"""
# get the depth query parameter
depth = request.args.get('depth')
if depth is None or not depth.isdigit or int(depth) not in [1, 2, 3]:
return redirect(url_for('play'))
new_game = Game_AI(int(depth))
session['game'] = new_game
return render_template("game_ai.html")
@app.route('/register', methods=['GET', 'POST'])
def register():
"""
GET: Create a register form and return register page
POST: Hash password and add user to database
"""
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm(mysql)
if form.validate_on_submit():
# hashes the password and converts it using string
hashed_password = bcrypt.generate_password_hash(
form.password.data).decode('utf-8')
# establishes a connection
conn = mysql.connection
curs = conn.cursor()
# inserts a row of data
curs.execute("""INSERT INTO Users (firstname, surname, username, email, password) VALUES (%s,%s,%s,%s,%s);""",
[form.firstname.data, form.surname.data, form.username.data.lower(), form.email.data.lower(), hashed_password])
# saves the changes
conn.commit()
# close connection
curs.close()
# flash sends a one-time alert
flash(
f'Account has been created! Welcome {form.firstname.data}!', 'success')
return redirect(url_for('login'))
return render_template('register.html', form=form)
@app.route('/login', methods=['GET', 'POST'])
def login():
"""
GET: Create a login form and return login page
POST: Check if user exists then login in user
"""
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
conn = mysql.connection
curs = conn.cursor()
curs.execute("""SELECT * FROM Users WHERE email = (%s);""",
[form.email.data])
user = curs.fetchone()
curs.close()
if user is not None and bcrypt.check_password_hash(user[5], form.password.data):
user = load_user(user[0])
login_user(user, remember=form.remember.data)
return redirect(url_for('index'))
else:
flash('Login Unsuccessful. Please check your email and password.', 'danger')
return render_template('login.html', form=form)
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
"""
SocketIO event handlers
"""
@socketio.on('socket_connect')
def connect():
"""Function that is run after a connection is established
"""
# print('connected')
@socketio.on('available_moves')
def available_moves(move=None):
"""Emits all the legal moves in dictionary format, position dictionary, and game information.
Args:
move (str): the starting coord, ending coord, and special moves type value joined together as a string. The coordiantes are in algebraic notation.
player_colour (tuple): player's colour as an integer.
"""
# print(move, player_colour)
game = session.get('game')
if move is None:
available_moves_dict = game.available_moves_dictionary()
else:
# executes move and returns available moves dictionary
available_moves_dict = game.next_move(move)
# print(game.chess)
current_turn = game.current_turn
winner = game.winner
checkmate = game.is_checkmate
draw = game.is_draw
information = {'current_turn': current_turn,
'winner': winner, 'checkmate': checkmate, 'draw': draw}
socketio.emit('available_moves_response', {
'available_moves': available_moves_dict, 'position': game.position_dictionary(), 'information': information}, room=request.sid)
@socketio.on('ai_moves')
def ai_moves(player_colour):
"""AI makes next move and then emits all the legal moves in dictionary format, position dictionary, and game information.
Args:
player_colour (int): player's side colour
"""
game = session.get('game')
# before = time.time()
ai_move = choice(minimax(game.chess, game.depth, player_colour))
# print(f"Time: {time.time() - before}")
ai_source = game.chess.coord_to_notation(ai_move[0])
ai_target = game.chess.coord_to_notation(ai_move[1][0])
ai_special_move = str(ai_move[1][1])
if ai_special_move == "None":
ai_special_move = ''
# make that move
game.next_move(ai_source + ai_target + ai_special_move)
# print(f"TURN: {game.current_turn}")
available_moves_dict = game.available_moves_dictionary()
current_turn = game.current_turn
winner = game.winner
checkmate = game.is_checkmate
draw = game.is_draw
information = {'current_turn': current_turn,
'winner': winner, 'checkmate': checkmate, 'draw': draw}
socketio.emit('available_moves_response', {
'available_moves': available_moves_dict, 'position': game.position_dictionary(), 'information': information}, room=request.sid)
# print('available_moves')
if __name__ == "__main__":
socketio.run(app, debug=True)