diff --git a/frontend/static/js/api/MyProfile.js b/frontend/static/js/api/MyProfile.js index 549f116..ef2e694 100644 --- a/frontend/static/js/api/MyProfile.js +++ b/frontend/static/js/api/MyProfile.js @@ -18,7 +18,7 @@ class MyProfile extends Profile /** * @type {[Profile]} */ - this.friends = []; + this.friendList = []; /** * @type {[Profile]} */ @@ -46,7 +46,7 @@ class MyProfile extends Profile async getFriends() { const response = await this.client._get('/api/profiles/friends'); const data = await response.json(); - data.forEach(profileData => this.friends.push(new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar))); + data.forEach(profileData => this.friendList.push(new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar))); } async getIncomingFriendRequests() { const response = await this.client._get('/api/profiles/incoming_friend_requests'); @@ -62,6 +62,52 @@ class MyProfile extends Profile new Profile(this.client, profileData.username, profileData.user_id, profileData.avatar) )); } + + /** + * @param {Profile} profile + * @returns {Boolean} + */ + _isFriend(profile) { + for (const user of this.friendList) { + if (user.id === profile.id) + return true; + } + return false; + } + /** + * @param {Profile} profile + * @returns {Boolean} + */ + _isBlocked(profile) { + for (const user of this.blockedUsers) { + if (user.id === profile.id) + return true; + } + return false; + } + /** + * @param {Profile} profile + * @returns {Boolean} + */ + _hasIncomingRequestFrom(profile) { + for (const user of this.incomingFriendRequests) { + if (user.id === profile.id) + return true; + } + return false; + } + /** + * @param {Profile} profile + * @returns {Boolean} + */ + _hasOutgoingRequestTo(profile) { + for (const user of this.outgoingFriendRequests) { + if (user.id === profile.id) + return true; + } + return false; + } + /** * * @param {File} selectedFile diff --git a/frontend/static/js/api/Profile.js b/frontend/static/js/api/Profile.js index bd38d9f..feb9570 100644 --- a/frontend/static/js/api/Profile.js +++ b/frontend/static/js/api/Profile.js @@ -33,8 +33,10 @@ export class Profile extends AExchangeable /** * @type {Boolean} */ - this.isBlocked = false; - this.isFriend = false; + this.isFriend; + this.isBlocked; + this.hasIncomingRequest; + this.hasOutgoingRequest; } /** @@ -57,6 +59,13 @@ export class Profile extends AExchangeable this.username = response_data.username; this.avatar = response_data.avatar; + if (!this.client.me || this.client.me.id === this.id) + return; + + this.isFriend = this.client.me._isFriend(this); + this.isBlocked = this.client.me._isBlocked(this); + this.hasIncomingRequest = this.client.me._hasIncomingRequestFrom(this); + this.hasOutgoingRequest = this.client.me._hasOutgoingRequestTo(this); } /** diff --git a/frontend/static/js/api/Profiles.js b/frontend/static/js/api/Profiles.js index 7c0de7e..796abc7 100644 --- a/frontend/static/js/api/Profiles.js +++ b/frontend/static/js/api/Profiles.js @@ -54,40 +54,6 @@ class Profiles return null; return profile; } - - /** - * Block a user - * @param {Number} user_id - * @returns {Promise} - */ - async block(user_id) { - - // blocker & blocked - let response = await this.client._post("/api/profiles/block", { - users_id:[this.client.me.id, user_id], - }); - - let data = await response.json(); - return data; - - } - - /** - * Unblock a user - * @param {Number} user_id - * @returns {Promise} - */ - async deblock(user_id) { - - // blocker & blocked - let response = await this.client._delete("/api/profiles/block", { - users_id:[this.client.me.id, user_id], - }); - - let data = await response.json(); - return data; - - } } export {Profiles}; diff --git a/frontend/static/js/api/game/tictactoe/TicTacToeGame.js b/frontend/static/js/api/game/tictactoe/TicTacToeGame.js index 73b5a56..49d6d18 100644 --- a/frontend/static/js/api/game/tictactoe/TicTacToeGame.js +++ b/frontend/static/js/api/game/tictactoe/TicTacToeGame.js @@ -18,12 +18,12 @@ class TicTacToe this.canvas = canvas this.context = this.canvas.getContext("2d"); this.sign; + this.currentMorpion = 4; this.turn; } async init() { - console.log(this.game_id); await this.game.join(); this.canvas.addEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion)); } @@ -37,22 +37,46 @@ class TicTacToe async onReceive(messageData) { console.log(messageData) - if (messageData.detail == "x" || messageData.detail == "o") + switch (messageData.detail) { - this.sign = messageData.detail; - this.turn = messageData.detail == "x"; - } - else if (messageData.detail == "game_start") - this.game.started = true; - else if (messageData.targetMorpion && messageData.targetCase) - { - this.map[messageData.targetMorpion][messageData.targetCase] = (this.sign == "x") ? 1 : 0; - this.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x"); - if (this.checkWin() != -1) - printWin(); + case 'x': + case 'o': + this.sign = messageData.detail; + this.turn = messageData.detail == "x"; + if (this.turn) + this.setOutline(4, false); + break; + + case 'game_start': + this.game.started = true; + this.game.finished = false; + break; + + case 'game_move': + this.map[messageData.targetMorpion][messageData.targetCase] = (this.sign == "x") ? 1 : 0; + this.printSign(messageData.targetMorpion, messageData.targetCase, (this.sign == "x") ? "o" : "x"); + this.setOutline(this.currentMorpion, false); + break; + + case 'game_end': + this.game.finished = true; + this.canvas.removeEventListener("mousedown", (event, morpion = this) => this.onClick(event, morpion)); + this.printWin(messageData.winning_sign); + break; } } + printWin(winning_sign) + { + this.context.beginPath(); + this.context.fillStyle = "white"; + this.context.fillRect(this.width / 2 - 200, this.height - this.gap + 10, 400, 80); + this.context.closePath(); + this.context.beginPath(); + this.context.fillStyle = (winning_sign == "o") ? "red" : "green"; + this.context.fillText((winning_sign == "o") ? "Winner is : O" : "Winner is : X", this.width / 2 - 30, this.height - this.gap / 2, 140); + this.context.closePath(); + } checkWin() { for (let i = 0; i < 9; i++) @@ -85,6 +109,10 @@ class TicTacToe let y = event.offsetY; let targetMorpion, targetCase; + if (this.game.finished) + { + return; + } targetMorpion = morpion.findPlace(x, this) + morpion.findPlace(y, this) * 3; if (morpion.findPlace(x, this) < 0 || morpion.findPlace(y, this) < 0) return -1; @@ -92,8 +120,8 @@ class TicTacToe if (morpion.checkCase(targetMorpion, targetCase)) { + morpion.setOutline(this.currentMorpion, true); morpion.sendCase(targetMorpion, targetCase); - morpion.setOutline(); } else morpion.incorrectCase(); @@ -101,19 +129,20 @@ class TicTacToe checkCase(targetMorpion, targetCase) { - return (this.map[targetMorpion][targetCase] == -1 && this.turn == true); + return (this.map[targetMorpion][targetCase] == -1 && this.turn == true && targetMorpion == this.currentMorpion); } incorrectCase() { - console.log("bozo"); + } sendCase(targetMorpion, targetCase) { this.map[targetMorpion][targetCase] = (this.sign == "x") ? 0 : 1; + this.currentMorpion = targetCase; this.printSign(targetMorpion, targetCase, this.sign); - console.log(this.game.send, targetMorpion, targetCase) + console.log(targetMorpion, targetCase) this.game.send(JSON.stringify({"targetMorpion" : targetMorpion, "targetCase" : targetCase, "sign" : this.sign})); console.log(this.turn); this.turn = !this.turn; @@ -172,21 +201,25 @@ class TicTacToe return -1; } - setOutline() + setOutline(targetMorpion, clear) { - if (this.turn) + let targetX = (this.gap + targetMorpion % 3 * this.rectsize * 3); + let targetY = (this.gap + Math.floor(targetMorpion / 3) * this.rectsize * 3); + if (this.game.finished) + return; + if (!clear) { this.context.beginPath(); this.context.strokeStyle = (this.sign == "x") ? "green" : "red"; - this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25); + this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3) this.context.stroke(); this.context.closePath(); } else { this.context.beginPath(); - this.context.strokeStyle = "#1a1a1d"; - this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25); + this.context.strokeStyle = `rgb(230 230 230)`; + this.context.rect(targetX, targetY, this.rectsize * 3, this.rectsize * 3) this.context.stroke(); this.context.closePath(); } @@ -244,34 +277,6 @@ class TicTacToe } this.context.closePath(); } - - selectCase(x, y) - { - case_morpion = Math.floor(x / this.rectsize) + Math.floor((y / this.rectsize)) * 3 - case_square = Math.floor(x / Math.floor(this.rectsize / 3)) % this.rectsize + Math.floor(y / this.rectsize) % this.rectsize - // ask server if case_morpion == playing_case && case_square == empty - - } - - setOutline() - { - if (this.turn) - { - this.context.beginPath(); - this.context.strokeStyle = (this.sign == "x") ? "green" : "red"; - this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25); - this.context.stroke(); - this.context.closePath(); - } - else - { - this.context.beginPath(); - this.context.strokeStyle = "#1a1a1d"; - this.context.roundRect(0, 0, this.canvas.width, this.canvas.height, 25); - this.context.stroke(); - this.context.closePath(); - } - } } export { TicTacToe }; \ No newline at end of file diff --git a/frontend/static/js/sound/tictactoe/play-move.mp3 b/frontend/static/js/sound/tictactoe/play-move.mp3 new file mode 100644 index 0000000..1713db0 Binary files /dev/null and b/frontend/static/js/sound/tictactoe/play-move.mp3 differ diff --git a/frontend/static/js/views/ProfilePageView.js b/frontend/static/js/views/ProfilePageView.js index 930e206..8b27e81 100644 --- a/frontend/static/js/views/ProfilePageView.js +++ b/frontend/static/js/views/ProfilePageView.js @@ -8,7 +8,7 @@ export default class extends AbstractView { } setTitle() { - document.title = `${this.username} - Profile`; + document.title = this.titleKey; } async postInit() @@ -16,100 +16,32 @@ export default class extends AbstractView { if (!this.profile) return 404; - this.userId = this.profile.id; + const addFriendButton = document.getElementById('addFriendButton'), + removeFriendButton = document.getElementById('removeFriendButton'), + blockButton = document.getElementById('blockButton'), + unblockButton = document.getElementById('unblockButton'); - await this.blockButton(); - await this.friendButton(); + if (this.profile.hasIncomingRequest) { + addFriendButton.classList.remove('d-none'); + addFriendButton.innerHTML = 'Accept Request'; + } else if (this.profile.hasOutgoingRequest) { + removeFriendButton.classList.remove('d-none'); + removeFriendButton.classList.replace('btn-danger', 'btn-secondary'); + removeFriendButton.innerHTML = 'Cancel Request' + } else if (this.profile.isFriend) + removeFriendButton.classList.remove('d-none'); + else + addFriendButton.classList.remove('d-none'); - client.notice.rewrite_profile = async () => { - await this.profile.getFriend(); - await this.profile.getBlock(); - await this.friendButton(); - }; - } + if (this.profile.isBlocked) + unblockButton.classList.remove('d-none'); + else + blockButton.classList.remove('d-none'); - async blockButton() { - // Block option - if (await client.isAuthenticated() === false) - return; - - if (client.me.id != this.userId) { - let block = document.getElementById("block"); - if (block == undefined) { - block = document.createElement("p"); - // this.info.appendChild(block); - } - - block.id = "block"; - block.onclick = async () => { - if (!this.profile.isBlocked) - await client.profiles.block(this.userId); - else - await client.profiles.deblock(this.userId); - this.profile = await client.profiles.getProfile(this.username); - - this.blockButton(); - }; - if (this.profile.isBlocked) - block.textContent = lang.get('profileUnblock', 'Unblock'); - else - block.textContent = lang.get('profileBlock', 'Block'); - } - } - - async friendButton() { - if (await client.isAuthenticated() === false) - return; - - if (client.me.id != this.userId) { - let yes = document.getElementById("yes") || document.createElement("p"); - let no = document.getElementById("no") || document.createElement("p"); - let friend = document.getElementById("friend") || document.createElement("p"); - - if (client.notice.data.asker.includes(this.userId)) { - - if (friend) - friend.remove(); - - yes.id = "yes"; - yes.textContent = lang.get('profileAcceptRequest', 'Accept Friend'); - yes.onclick = async () => { - client.notice.accept_friend(this.userId); - }; - - no.id = "no"; - no.textContent = lang.get('profileDenyRequest', 'Decline Friend'); - no.onclick = async () => { - client.notice.refuse_friend(this.userId); - }; - - // this.info.appendChild(yes); - // this.info.appendChild(document.createTextNode(" ")); - // this.info.appendChild(no); - - } - else { - - if (yes && no) - yes.remove(); no.remove(); - - friend.id = "friend"; - friend.onclick = async () => { - if (this.profile.isFriend) - await client.notice.remove_friend(this.userId); - else - await client.notice.ask_friend(this.userId); - await client.profiles.getProfile(this.username); - this.friendButton(); - }; - if (this.profile.isFriend) - friend.textContent = lang.get('profileRemoveFriend', 'Remove Friend'); - else { - friend.textContent = lang.get('profileAddFriend', 'Ask Friend'); - } - // this.info.appendChild(friend); - } - } + addFriendButton.onclick = _ => this.addFriend(); + removeFriendButton.onclick = _ => this.removeFriend(); + unblockButton.onclick = _ => this.unblockUser(); + blockButton.onclick = _ => this.blockUser(); } async getHtml() { @@ -118,19 +50,94 @@ export default class extends AbstractView { if (!this.profile) return ''; - const logged = await client.isAuthenticated(); - return ` -
-

${this.username}

- - - -
-
- - -
- `; +
+
+

${this.username}

+ + + +
+
+ + + + +
+
+ `; + } + + async addFriend() { + const removeFriendButton = document.getElementById('removeFriendButton'); + + const response = await client._post(`/api/profiles/friends/${this.profile.id}`); + const body = await response.json(); + console.log(body); + + if (response.ok) { + removeFriendButton.classList.remove('d-none'); + document.getElementById('addFriendButton').classList.add('d-none'); + } + if (response.status === 200) { + removeFriendButton.innerHTML = 'Cancel Request'; + removeFriendButton.classList.replace('btn-danger', 'btn-secondary'); + client.me.outgoingFriendRequests.push(this.profile); + this.profile.hasOutgoingRequest = true; + } else if (response.status === 201) { + removeFriendButton.innerHTML = 'Remove Friend'; + removeFriendButton.classList.replace('btn-secondary', 'btn-danger'); + this.profile.friend = true; + this.profile.hasIncomingRequest = false; + client.me.incomingFriendRequests = client.me.incomingFriendRequests.filter(profile => profile.id !== this.profile.id); + client.me.friendList.push(this.profile); + } + } + + async removeFriend() { + const addFriendButton = document.getElementById('addFriendButton'); + + const response = await client._delete(`/api/profiles/friends/${this.profile.id}`); + const body = await response.json(); + console.log(body); + + if (response.ok) { + addFriendButton.innerHTML = 'Add Friend'; + addFriendButton.classList.remove('d-none'); + document.getElementById('removeFriendButton').classList.add('d-none'); + } + if (response.status === 200) { + this.profile.hasOutgoingRequest = false; + client.me.outgoingFriendRequests = client.me.outgoingFriendRequests.filter(profile => profile.id !== this.profile.id); + } else if (response.status === 201) { + this.profile.isFriend = false; + client.me.friendList = client.me.friendList.client.me.incomingFriendRequests = filter(friend => friend.id !== this.profile.id); + } + } + + async blockUser() { + const response = await client._post(`/api/profiles/block/${this.profile.id}`); + const body = await response.json(); + console.log(body); + + if (response.ok) { + document.getElementById('blockButton').classList.add('d-none'); + document.getElementById('unblockButton').classList.remove('d-none'); + client.me.blockedUsers.push(this.profile); + this.profile.isBlocked = true; + } + } + + async unblockUser() { + const response = await client._delete(`/api/profiles/block/${this.profile.id}`); + const body = await response.json(); + console.log(body); + + if (response.ok) { + document.getElementById('unblockButton').classList.add('d-none'); + document.getElementById('blockButton').classList.remove('d-none'); + client.me.blockedUsers = client.me.blockedUsers.filter(profile => profile.id !== this.profile.id); + this.profile.isBlocked = false; + } } } diff --git a/frontend/static/js/views/TicTacToeOnlineView.js b/frontend/static/js/views/TicTacToeOnlineView.js index 129e6a5..2379cd8 100644 --- a/frontend/static/js/views/TicTacToeOnlineView.js +++ b/frontend/static/js/views/TicTacToeOnlineView.js @@ -19,7 +19,6 @@ export class TicTacToeOnlineView extends AbstractView this.Morpion = new TicTacToe(this.height, this.width, 60, 60, document.getElementById("Morpion"), this.game_id); this.Morpion.DrawSuperMorpion(); await this.Morpion.init(); - this.Morpion.setOutline(); } async leavePage() diff --git a/games/consumers.py b/games/consumers.py index 4fd1094..825ffe5 100644 --- a/games/consumers.py +++ b/games/consumers.py @@ -45,6 +45,8 @@ class TicTacToeWebSocket(WebsocketConsumer): self.member.send(self.member.sign) if (self.game._everbody_is_here() and self.game.model.started == False): + if (self.game.time != -1): + self.game.broadcast("opponent_joined") self.game.broadcast("game_start") self.game.model.start() @@ -54,7 +56,12 @@ class TicTacToeWebSocket(WebsocketConsumer): if (data.get("targetMorpion") is not None and data.get("targetCase") is not None): if (self.game.add(data, self.member) == False): return - self.game.broadcast("", data, [self.member]) + if (data.get("catchup") is not None and self.game.model.finished == False and self.game.model.finished == True): + self.member.send("catchup", {"Morpion": self.game._map, "turn": self.game.turn}) + if (self.game.checkWin() != False): + print(self.game.checkWin()) + self.game.broadcast("game_end", {"winning_sign": self.member.sign}) + self.game.broadcast("game_move", data, [self.member]) pass def disconnect(self, event): diff --git a/games/objects/tictactoe/TicTacToeGame.py b/games/objects/tictactoe/TicTacToeGame.py index fe123e5..7befea5 100644 --- a/games/objects/tictactoe/TicTacToeGame.py +++ b/games/objects/tictactoe/TicTacToeGame.py @@ -18,6 +18,10 @@ class TicTacToeGame(AGame): self.players: list[TicTacToePlayer] = [TicTacToePlayer(player, None, self, ["x", "o"][i]) for i, player in enumerate(players)] + self.time = -1 + + self.turn = 'x' + self._map = [[-1 for _ in range(9)] for _ in range(9)] def _everbody_is_here(self): @@ -44,14 +48,15 @@ class TicTacToeGame(AGame): if (self.checkMove(newmove, player)): self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] = newmove.get("sign") - player.currentMorpion = int(newmove.get("targetMorpion")) + player.currentMorpion = int(newmove.get("targetCase")) + self.turn = newmove.get("sign") return True return False def checkMove(self, newmove, player): print(int(newmove.get("targetMorpion")), player.currentMorpion) - if (int(newmove.get("targetMorpion")) != player.currentMorpion): + if (int(newmove.get("targetMorpion")) != player.currentMorpion or newmove.get("sign") != self.turn): return False if (self._map[newmove.get("targetMorpion")][newmove.get("targetCase")] != -1): @@ -62,16 +67,16 @@ class TicTacToeGame(AGame): def checkWin(self): for tab in self._map: for i in range(3): - if tab[i] == tab[i + 3] == tab[i + 6]: + if tab[i] != -1 and tab[i] == tab[i + 3] and tab[i + 3] == tab[i + 6]: return tab[i] for i in range(0, 9, 3): - if tab[i] == tab[i + 1] == tab[i + 2]: + if tab[i] != -1 and tab[i] == tab[i + 1] and tab[i + 1] == tab[i + 2]: return tab[i] - if tab[0] == tab[4] == tab[8]: + if tab[0] != -1 and tab[0] == tab[4] and tab[4] == tab[8]: return tab[0] - if tab[6] == tab[4] == tab[2]: + if tab[6] != -1 and tab[6] == tab[4] and tab[4] == tab[2]: return tab[6] - return None + return False def _spectator_join(self, user_id: int, socket: WebsocketConsumer): diff --git a/profiles/views/friends.py b/profiles/views/friends.py index 00d76b3..125b061 100644 --- a/profiles/views/friends.py +++ b/profiles/views/friends.py @@ -50,16 +50,16 @@ class EditFriendView(APIView): user_profile = self.get_object() friend_profile = get_object_or_404(ProfileModel, pk=pk) - if not user_profile.is_friend(friend_profile): - return Response(_('You are not friend with this user.'), status.HTTP_400_BAD_REQUEST) - outgoing_request = user_profile.get_outgoing_friend_request_to(friend_profile) if outgoing_request: outgoing_request.delete() return Response(_('Friend request cancelled.')) + if not user_profile.is_friend(friend_profile): + return Response(_('You are not friend with this user.'), status.HTTP_400_BAD_REQUEST) + user_profile.delete_friend(friend_profile) - return Response(_('Friendship succssfully deleted.')) + return Response(_('Friendship succssfully deleted.'), status.HTTP_201_CREATED) class GetIncomingFriendRequestView(APIView):