From ff57e6b883f7faed68c2bbb6afc944006071aee1 Mon Sep 17 00:00:00 2001 From: Tait Hoyem Date: Sat, 8 Jan 2022 16:55:20 -0700 Subject: [PATCH] split up games into individual apps --- games/README.md | 4 + games/models.py | 1 + minesweeper/__init__.py | 0 {games => minesweeper}/admin.py | 0 minesweeper/apps.py | 6 + minesweeper/consumers.py | 212 ++++++++++++++++++ minesweeper/migrations/__init__.py | 0 {games => minesweeper}/tests.py | 0 minesweeper/urls.py | 6 + minesweeper/views.py | 17 ++ rps/__init__.py | 0 rps/admin.py | 3 + {games => rps}/apps.py | 0 {games => rps}/consumers.py | 2 +- {games => rps}/static/games/js/minesweeper.js | 0 {games => rps}/static/games/js/rps.js | 0 {games => rps}/templates/games/gamelog.html | 4 +- .../templates/games/minesweeper/board.html | 0 .../games/minesweeper/minesweeper.html | 2 +- .../games/minesweeper/mobile-buttons.html | 0 {games => rps}/templates/games/rps/rps.html | 0 .../templates/games/rps/rps_choose.html | 0 .../templates/games/rps/rps_join.html | 0 rps/tests.py | 3 + {games => rps}/urls.py | 0 {games => rps}/views.py | 2 +- 26 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 games/README.md create mode 100644 minesweeper/__init__.py rename {games => minesweeper}/admin.py (100%) create mode 100644 minesweeper/apps.py create mode 100644 minesweeper/consumers.py create mode 100644 minesweeper/migrations/__init__.py rename {games => minesweeper}/tests.py (100%) create mode 100644 minesweeper/urls.py create mode 100644 minesweeper/views.py create mode 100644 rps/__init__.py create mode 100644 rps/admin.py rename {games => rps}/apps.py (100%) rename {games => rps}/consumers.py (99%) rename {games => rps}/static/games/js/minesweeper.js (100%) rename {games => rps}/static/games/js/rps.js (100%) rename {games => rps}/templates/games/gamelog.html (92%) rename {games => rps}/templates/games/minesweeper/board.html (100%) rename {games => rps}/templates/games/minesweeper/minesweeper.html (94%) rename {games => rps}/templates/games/minesweeper/mobile-buttons.html (100%) rename {games => rps}/templates/games/rps/rps.html (100%) rename {games => rps}/templates/games/rps/rps_choose.html (100%) rename {games => rps}/templates/games/rps/rps_join.html (100%) create mode 100644 rps/tests.py rename {games => rps}/urls.py (100%) rename {games => rps}/views.py (93%) diff --git a/games/README.md b/games/README.md new file mode 100644 index 0000000..e15f459 --- /dev/null +++ b/games/README.md @@ -0,0 +1,4 @@ +# ABANDONED + +This app (the `/games` directory) is abandoned. +It needs to stick around for compatibility reasons, but otherwise do not use this. diff --git a/games/models.py b/games/models.py index bf6c321..69cc7df 100644 --- a/games/models.py +++ b/games/models.py @@ -25,6 +25,7 @@ class RPSMove(models.Model): user = models.ForeignKey(ActiveUser, on_delete=models.CASCADE) choice = models.CharField(max_length=8) +# Create your models here. class MinesweeperBoard(models.Model): class Status(models.IntegerChoices): IN_PROGRESS = 0 diff --git a/minesweeper/__init__.py b/minesweeper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/games/admin.py b/minesweeper/admin.py similarity index 100% rename from games/admin.py rename to minesweeper/admin.py diff --git a/minesweeper/apps.py b/minesweeper/apps.py new file mode 100644 index 0000000..4b289fb --- /dev/null +++ b/minesweeper/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class MinesweeperConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'minesweeper' diff --git a/minesweeper/consumers.py b/minesweeper/consumers.py new file mode 100644 index 0000000..6edd37d --- /dev/null +++ b/minesweeper/consumers.py @@ -0,0 +1,212 @@ +import json +from channels.generic.websocket import WebsocketConsumer +from asgiref.sync import async_to_sync +# TODO: rename +from games.models import MinesweeperBoard, MinesweeperCell +from common.models import LameUser +from django.db.models import Q +import random + +class MinesweeperConsumer(WebsocketConsumer): + def xy_to_pos(self, x, y): + return (y * self.board.width) + x + def pos_to_xy(self, pos): + return (pos % self.board.width, pos // self.board.height) + + def send_client(self, tpy, msg): + self.send(json.dumps({ + 'type': tpy, + 'payload': msg + })) + + def hit_bomb(self, sq): + self.send_client('change-board', [{ + 'x': sq.x, + 'y': sq.y, + 'flagged': sq.flagged, + 'bomb': sq.bomb, + 'bombs_next': sq.bombs_next + }]) + self.lose() + + def client_selected_square(self, pos): + x, y = self.pos_to_xy(pos) + reved = self.reveal(x, y, []) + print(reved) + if len(reved) == 0: + self.hit_bomb(self.board.cells.get(x=x, y=y)) + else: + self.send_client('change-board', reved) + self.save_revealed(reved) + + def check_win(self): + if self.has_won(): + self.win() + + def has_won(self): + shown = list(self.board.cells.filter(shown=True)) + not_bombs = list(self.board.cells.filter(bomb=False)) + return shown == not_bombs + + def win(self): + self.send_client('message', 'You win!') + self.board.status = self.board.Status.WON + self.board.save() + + def send_new_board(self): + self.send_client('new_board', '') + self.send_client('message', 'New board generated!') + + def flag_tile(self, pos): + x, y = self.pos_to_xy(pos) + cell = MinesweeperCell.objects.get(x=x, y=y, board=self.board) + if not cell.flagged: + cell.shown = False + cell.flagged = not cell.flagged + cell.save() + + def send_shown_flagged(self): + if (self.board.is_game_over()): + self.send_client('change-board', [ + x.as_full_dict() for x in self.board.cells.all() + ]) + else: + shown = [{'x': x.x, 'y': x.y, 'bombs_next': x.bombs_next} for x in list(self.board.cells.filter(shown=True))] + flagged = [{'x': x.x, 'y': x.y, 'flagged': True} for x in list(self.board.cells.filter(flagged=True))] + self.send_client('change-board', shown + flagged) + + def save_revealed(self, revealed_squares): + # edit database so user can come back later + for cell in revealed_squares: + dbcell = MinesweeperCell.objects.get(x=cell['x'], y=cell['y'], board=self.board) + dbcell.shown = True + dbcell.flagged = False + dbcell.save() + + def valid_pos(self, x, y): + return x >= 0 and x < self.board.width and y >= 0 and y < self.board.height + + def reveal(self, x, y, already_revealed): + if not self.valid_pos(x, y): + return already_revealed + # if already checked + for rev in already_revealed: + if rev['x'] == x and rev['y'] == y: + return already_revealed + + tile = self.board.cells.get(x=x, y=y) + # if is bomb + if tile.bomb: + return already_revealed + elif tile.bombs_next > 0: + already_revealed.append({ + 'x': x, + 'y': y, + 'bombs_next': self.cells[y][x] + }) + return already_revealed + # if bombs_next is 0 + already_revealed.append({ + 'x': x, + 'y': y, + 'bombs_next': 0 + }) + + for xd in range(-1, 2): + for yd in range(-1, 2): + nx = x+xd + ny = y+yd + # jump over middle square + if nx == x and ny == y: + continue + self.reveal(nx, ny, already_revealed) + return already_revealed + + def select_board_if_exists(self): + # if user has a board already available: use it instead + current_board = MinesweeperBoard.objects.filter(user=self.user) + if len(current_board) > 0: + self.board = current_board[0] + self.cells = [[0 for _ in range(self.board.width)] for _ in range(self.board.height)] + for cell in self.board.cells.all(): + self.cells[cell.y][cell.x] = '*' if cell.bomb else cell.bombs_next + return True + self.board_generator() + + # TODO: make more efficient + # TODO: make easier to read + # TODO: Move to seperate file + def board_generator(self): + self.board = MinesweeperBoard.objects.create(user=self.user) + self.cells = [] + for y in range(self.board.height): + self.cells.append([]) + for x in range(self.board.width): + self.cells[y].append(0) + placed_bombs = 0 + while placed_bombs < 15: + rand = random.randint(0, (self.board.width * self.board.height)-1) + + x = rand % 10 + y = rand // 10 + if self.cells[y][x] != '*': + self.cells[y][x] = '*' + placed_bombs += 1 + for y in range(self.board.height): + for x in range(self.board.width): + i = (y*self.board.width) + x + if self.cells[y][x] != '*': + # This finds the number of bombs within the 8 squares surrounding + check_matrix = [ + (-1, -1), + (-1, 0), + (-1, 1), + (0, -1), + (0, 1), + (1, -1), + (1, 0), + (1, 1) + ] + bombs_around = 0 + for p,q in check_matrix: + if self.valid_pos(p+y, q+x): + if self.cells[y+p][x+q] == '*': + bombs_around += 1 + self.cells[y][x] = bombs_around + m = MinesweeperCell.objects.create( + x=x, + y=y, + bombs_next=self.cells[y][x] if self.cells[y][x] != '*' else 0, + bomb=self.cells[y][x] == '*', + board=self.board, + shown=False + ) + def lose(self): + self.send_client('message', 'You hit a bomb!') + self.board.status = self.board.Status.LOST + # This will remove all flags; I believe this is the classic behaviour + self.board.cells.all().update(flagged=False) + self.board.save() + self.send_shown_flagged() + + def connect(self): + self.accept() + self.user = LameUser.objects.get(username=self.scope['user'].username) + self.select_board_if_exists() + self.send_shown_flagged() + + def disconnect(self, close_code): + pass + + def receive(self, text_data): + data = json.loads(text_data) + if data['type'] == 'generate': + self.board.delete() + self.send_new_board() + self.board_generator() + elif data['type'] == 'clicked' and not self.board.is_game_over(): + self.client_selected_square(data['button_id']) + self.check_win() + elif data['type'] == 'flagged' and not self.board.is_game_over(): + self.flag_tile(data['button_id']) + diff --git a/minesweeper/migrations/__init__.py b/minesweeper/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/games/tests.py b/minesweeper/tests.py similarity index 100% rename from games/tests.py rename to minesweeper/tests.py diff --git a/minesweeper/urls.py b/minesweeper/urls.py new file mode 100644 index 0000000..f928a47 --- /dev/null +++ b/minesweeper/urls.py @@ -0,0 +1,6 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('minesweeper/', views.minesweeper, name='minesweeper') +] diff --git a/minesweeper/views.py b/minesweeper/views.py new file mode 100644 index 0000000..9ce1d94 --- /dev/null +++ b/minesweeper/views.py @@ -0,0 +1,17 @@ +from django.shortcuts import ( + render, HttpResponse, redirect +) +from django.urls import reverse +# TODO: rename +from games.models import MinesweeperBoard + +def minesweeper(request): + board = MinesweeperBoard.objects.filter(user=request.user) + if len(board) > 0: + board = board[0] + else: + board = MinesweeperBoard.objects.create(user=request.user) + id_board = [[(y*board.width)+x for x in range(board.width)] for y in range(board.height)] + return render(request, 'games/minesweeper/minesweeper.html', { + 'board': id_board + }) diff --git a/rps/__init__.py b/rps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/rps/admin.py b/rps/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/rps/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/games/apps.py b/rps/apps.py similarity index 100% rename from games/apps.py rename to rps/apps.py diff --git a/games/consumers.py b/rps/consumers.py similarity index 99% rename from games/consumers.py rename to rps/consumers.py index 74567a5..5ecdd7e 100644 --- a/games/consumers.py +++ b/rps/consumers.py @@ -1,7 +1,7 @@ import json from channels.generic.websocket import WebsocketConsumer from asgiref.sync import async_to_sync -from .models import Room, ActiveUser, RPSMove, MinesweeperBoard, MinesweeperCell +from games.models import Room, ActiveUser, RPSMove from common.models import LameUser from django.db.models import Q import random diff --git a/games/static/games/js/minesweeper.js b/rps/static/games/js/minesweeper.js similarity index 100% rename from games/static/games/js/minesweeper.js rename to rps/static/games/js/minesweeper.js diff --git a/games/static/games/js/rps.js b/rps/static/games/js/rps.js similarity index 100% rename from games/static/games/js/rps.js rename to rps/static/games/js/rps.js diff --git a/games/templates/games/gamelog.html b/rps/templates/games/gamelog.html similarity index 92% rename from games/templates/games/gamelog.html rename to rps/templates/games/gamelog.html index 51caa98..df6e8ca 100644 --- a/games/templates/games/gamelog.html +++ b/rps/templates/games/gamelog.html @@ -11,7 +11,7 @@ Welcome!
\ No newline at end of file + diff --git a/games/templates/games/minesweeper/board.html b/rps/templates/games/minesweeper/board.html similarity index 100% rename from games/templates/games/minesweeper/board.html rename to rps/templates/games/minesweeper/board.html diff --git a/games/templates/games/minesweeper/minesweeper.html b/rps/templates/games/minesweeper/minesweeper.html similarity index 94% rename from games/templates/games/minesweeper/minesweeper.html rename to rps/templates/games/minesweeper/minesweeper.html index 3598bb8..09d0457 100644 --- a/games/templates/games/minesweeper/minesweeper.html +++ b/rps/templates/games/minesweeper/minesweeper.html @@ -6,4 +6,4 @@ {% include 'games/minesweeper/mobile-buttons.html' %} {% include 'games/gamelog.html' %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/games/templates/games/minesweeper/mobile-buttons.html b/rps/templates/games/minesweeper/mobile-buttons.html similarity index 100% rename from games/templates/games/minesweeper/mobile-buttons.html rename to rps/templates/games/minesweeper/mobile-buttons.html diff --git a/games/templates/games/rps/rps.html b/rps/templates/games/rps/rps.html similarity index 100% rename from games/templates/games/rps/rps.html rename to rps/templates/games/rps/rps.html diff --git a/games/templates/games/rps/rps_choose.html b/rps/templates/games/rps/rps_choose.html similarity index 100% rename from games/templates/games/rps/rps_choose.html rename to rps/templates/games/rps/rps_choose.html diff --git a/games/templates/games/rps/rps_join.html b/rps/templates/games/rps/rps_join.html similarity index 100% rename from games/templates/games/rps/rps_join.html rename to rps/templates/games/rps/rps_join.html diff --git a/rps/tests.py b/rps/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/rps/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/games/urls.py b/rps/urls.py similarity index 100% rename from games/urls.py rename to rps/urls.py diff --git a/games/views.py b/rps/views.py similarity index 93% rename from games/views.py rename to rps/views.py index 7191b4b..b5ea635 100644 --- a/games/views.py +++ b/rps/views.py @@ -2,7 +2,7 @@ from django.shortcuts import ( render, HttpResponse, redirect ) from django.urls import reverse -from .models import Room, ActiveUser, MinesweeperBoard +from games.models import Room, ActiveUser, MinesweeperBoard # Create your views here. def rps(request):