split up games into individual apps

master
Tait Hoyem 2 years ago
parent 0e09eebba1
commit ff57e6b883

@ -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.

@ -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

@ -0,0 +1,6 @@
from django.apps import AppConfig
class MinesweeperConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'minesweeper'

@ -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', '<b>You win!</b>')
self.board.status = self.board.Status.WON
self.board.save()
def send_new_board(self):
self.send_client('new_board', '')
self.send_client('message', '<i>New board generated!</i>')
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', '<b>You hit a bomb!</b>')
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'])

@ -0,0 +1,6 @@
from django.urls import path
from . import views
urlpatterns = [
path('minesweeper/', views.minesweeper, name='minesweeper')
]

@ -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
})

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

@ -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

@ -11,7 +11,7 @@
Welcome!<br>
</div>
<script>
const LOG_BOX = document.getElementById("logbox")
const LOG_BOX = document.getElementById("logbox");
const STATUS_BOX = document.getElementById("statusbox");
const write_message = (mess) => {
@ -25,4 +25,4 @@ const clear_statusbox = () => {
write_message('<i>Status Cleared</i>');
}
</script>
</script>

@ -6,4 +6,4 @@
{% include 'games/minesweeper/mobile-buttons.html' %}
{% include 'games/gamelog.html' %}
<script src="{% static 'games/js/minesweeper.js' %}"></script>
{% endblock %}
{% endblock %}

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

@ -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):
Loading…
Cancel
Save