Creating structure for the game events (#3)
* Adding the GameVent model and migration * Initializing the GameEvent endpoint * GameEvent listing endpoint * Adding CLICK_NAIVE event * Added signal for identify what is on the click location * Using row and col integer is better than metadata with jsonfield * Create event for the same position is not allowed * Better signals control * Adding a board progress to Game model * Identifying the win status * Hide generated board from client
This commit is contained in:
@@ -2,8 +2,8 @@ from rest_framework import status
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from game.models import Game
|
from game.models import Game, GameEvent, GameStatuses
|
||||||
from ..serializers import GameSerializer
|
from ..serializers import GameSerializer, GameEventSerializer
|
||||||
|
|
||||||
|
|
||||||
class GameResource(APIView):
|
class GameResource(APIView):
|
||||||
@@ -28,3 +28,40 @@ class GameSingleResource(APIView):
|
|||||||
|
|
||||||
serializer = GameSerializer(game)
|
serializer = GameSerializer(game)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
class GameEventResource(APIView):
|
||||||
|
def get(self, request, game_id):
|
||||||
|
""" Returns a list of all game events """
|
||||||
|
events = GameEvent.objects.filter(game_id=game_id)
|
||||||
|
serializer = GameEventSerializer(events, many=True)
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
def post(self, request, game_id):
|
||||||
|
""" Creates a new event """
|
||||||
|
|
||||||
|
try:
|
||||||
|
game = Game.objects.get(pk=game_id)
|
||||||
|
except Game.DoesNotExist:
|
||||||
|
return Response(status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
if game.status == GameStatuses.FINISHED:
|
||||||
|
return Response(
|
||||||
|
{"message": "Game is already finished"},
|
||||||
|
status=status.HTTP_412_PRECONDITION_FAILED,
|
||||||
|
)
|
||||||
|
|
||||||
|
row = request.data.get("row")
|
||||||
|
col = request.data.get("col")
|
||||||
|
game_event = GameEvent.objects.filter(game=game, row=row, col=col).first()
|
||||||
|
if game_event:
|
||||||
|
return Response(
|
||||||
|
{"message": "This event was already registered"},
|
||||||
|
status=status.HTTP_409_CONFLICT,
|
||||||
|
)
|
||||||
|
|
||||||
|
serializer = GameEventSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
serializer.save()
|
||||||
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .game import GameSerializer
|
from .game import GameSerializer, GameEventSerializer
|
||||||
|
|
||||||
|
|
||||||
__all__ = ["GameSerializer"]
|
__all__ = ["GameSerializer", "GameEventSerializer"]
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from game.models import Game
|
from game.models import Game, GameEvent
|
||||||
|
|
||||||
|
|
||||||
class GameSerializer(serializers.ModelSerializer):
|
class GameSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Game
|
model = Game
|
||||||
|
exclude = ["board"]
|
||||||
|
|
||||||
|
|
||||||
|
class GameEventSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = GameEvent
|
||||||
fields = "__all__"
|
fields = "__all__"
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .resources.main import MainResource
|
from .resources.main import MainResource
|
||||||
from .resources.game import GameResource, GameSingleResource
|
from .resources.game import GameResource, GameSingleResource, GameEventResource
|
||||||
|
|
||||||
|
|
||||||
app_name = "api"
|
app_name = "api"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("games/<game_id>/events", GameEventResource.as_view(), name="games_events"),
|
||||||
path("games/<game_id>", GameSingleResource.as_view(), name="games_single"),
|
path("games/<game_id>", GameSingleResource.as_view(), name="games_single"),
|
||||||
path("games", GameResource.as_view(), name="games"),
|
path("games", GameResource.as_view(), name="games"),
|
||||||
path("", MainResource.as_view(), name="main"),
|
path("", MainResource.as_view(), name="main"),
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = "game.apps.GameConfig"
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models import Game, GameEvent
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Game)
|
||||||
|
class GameAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
"id",
|
||||||
|
"created_at",
|
||||||
|
"modified_at",
|
||||||
|
"rows",
|
||||||
|
"cols",
|
||||||
|
"mines",
|
||||||
|
"win",
|
||||||
|
"status",
|
||||||
|
)
|
||||||
|
|
||||||
|
list_filter = (
|
||||||
|
"win",
|
||||||
|
"status",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(GameEvent)
|
||||||
|
class GameEventAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("id", "created_at", "game", "type", "row", "col")
|
||||||
|
|
||||||
|
list_filter = ("type",)
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ class GameConfig(AppConfig):
|
|||||||
name = "game"
|
name = "game"
|
||||||
verbose_name = "Game"
|
verbose_name = "Game"
|
||||||
verbose_name_plural = "Games"
|
verbose_name_plural = "Games"
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
import game.signals # noqa
|
||||||
|
|||||||
40
game/game.py
40
game/game.py
@@ -4,14 +4,23 @@ import random
|
|||||||
class Minesweeper:
|
class Minesweeper:
|
||||||
board = []
|
board = []
|
||||||
|
|
||||||
def __init__(self, rows=10, cols=10, mines=5):
|
def __init__(self, rows=10, cols=10, mines=5, board=None, board_progress=None):
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
self.cols = cols
|
self.cols = cols
|
||||||
self.mines = mines
|
self.mines = mines
|
||||||
|
|
||||||
|
if board is not None:
|
||||||
|
self.board = board
|
||||||
|
|
||||||
|
if board_progress is not None:
|
||||||
|
self.board_progress = board_progress
|
||||||
|
|
||||||
def create_board(self):
|
def create_board(self):
|
||||||
""" Creating the board cells with 0 as default value """
|
""" Creating the board cells with 0 as default value """
|
||||||
self.board = [[0 for col in range(self.cols)] for row in range(self.rows)]
|
self.board = [[0 for col in range(self.cols)] for row in range(self.rows)]
|
||||||
|
self.board_progress = [
|
||||||
|
["-" for col in range(self.cols)] for row in range(self.rows)
|
||||||
|
]
|
||||||
|
|
||||||
def put_mine(self):
|
def put_mine(self):
|
||||||
"""Put a single mine on the board.
|
"""Put a single mine on the board.
|
||||||
@@ -62,12 +71,26 @@ class Minesweeper:
|
|||||||
self.increment_safe_point(mine_position_row - 1, mine_position_col - 1)
|
self.increment_safe_point(mine_position_row - 1, mine_position_col - 1)
|
||||||
|
|
||||||
def is_mine(self, row, col):
|
def is_mine(self, row, col):
|
||||||
""" Checks whether the given location is a mine or not """
|
""" Checks whether the given location have a mine """
|
||||||
try:
|
try:
|
||||||
return self.board[row][col] == -1
|
return self.board[row][col] == -1
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def is_empty(self, row, col):
|
||||||
|
""" Checks whether the given location is empty """
|
||||||
|
try:
|
||||||
|
return self.board[row][col] == 0
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def is_point(self, row, col):
|
||||||
|
""" Checks whether the given location have pontuation """
|
||||||
|
try:
|
||||||
|
return self.board[row][col] > 0
|
||||||
|
except IndexError:
|
||||||
|
return False
|
||||||
|
|
||||||
def is_point_in_board(self, row, col):
|
def is_point_in_board(self, row, col):
|
||||||
""" Checks whether the location is inside board """
|
""" Checks whether the location is inside board """
|
||||||
if row in range(0, self.rows) and col in range(0, self.cols):
|
if row in range(0, self.rows) and col in range(0, self.cols):
|
||||||
@@ -87,3 +110,16 @@ class Minesweeper:
|
|||||||
|
|
||||||
# Increment the value of the position becaus is close to some mine
|
# Increment the value of the position becaus is close to some mine
|
||||||
self.board[row][col] += 1
|
self.board[row][col] += 1
|
||||||
|
|
||||||
|
def reveal(self, row, col):
|
||||||
|
self.board_progress[row][col] = self.board[row][col]
|
||||||
|
|
||||||
|
def win(self):
|
||||||
|
""" Identify if the player won the game """
|
||||||
|
unrevealed = 0
|
||||||
|
for row in self.board_progress:
|
||||||
|
for cell in row:
|
||||||
|
if cell == "-":
|
||||||
|
unrevealed += 1
|
||||||
|
if (unrevealed - self.mines) == 0:
|
||||||
|
return True
|
||||||
|
|||||||
73
game/migrations/0003_gameevent.py
Normal file
73
game/migrations/0003_gameevent.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2020-11-06 03:54
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_mysql.models
|
||||||
|
import game.models
|
||||||
|
import internal.utils
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("game", "0002_auto_20201106_0225"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="GameEvent",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"created_at",
|
||||||
|
models.DateTimeField(
|
||||||
|
auto_now_add=True, verbose_name="Creation date"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"type",
|
||||||
|
models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, "START_GAME"),
|
||||||
|
(1, "PAUSE"),
|
||||||
|
(2, "RESUME"),
|
||||||
|
(3, "CLICK_MINE"),
|
||||||
|
(4, "CLICK_POINT"),
|
||||||
|
(5, "CLICK_EMPTY"),
|
||||||
|
(6, "CLICK_FLAG"),
|
||||||
|
(7, "GAME_OVER"),
|
||||||
|
],
|
||||||
|
default=game.models.EventTypes["START_GAME"],
|
||||||
|
help_text="The game event",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"metadata",
|
||||||
|
django_mysql.models.JSONField(
|
||||||
|
default=internal.utils.empty_object,
|
||||||
|
help_text="Some usefull event metadata",
|
||||||
|
verbose_name="Event metadata",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"game",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE, to="game.game"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Game event",
|
||||||
|
"verbose_name_plural": "Game events",
|
||||||
|
"db_table": "game_events",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
41
game/migrations/0004_auto_20201106_0453.py
Normal file
41
game/migrations/0004_auto_20201106_0453.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2020-11-06 04:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import game.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("game", "0003_gameevent"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="gameevent",
|
||||||
|
options={
|
||||||
|
"ordering": ["created_at"],
|
||||||
|
"verbose_name": "Game event",
|
||||||
|
"verbose_name_plural": "Game events",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="gameevent",
|
||||||
|
name="type",
|
||||||
|
field=models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, "START_GAME"),
|
||||||
|
(1, "PAUSE"),
|
||||||
|
(2, "RESUME"),
|
||||||
|
(3, "CLICK_MINE"),
|
||||||
|
(4, "CLICK_POINT"),
|
||||||
|
(5, "CLICK_EMPTY"),
|
||||||
|
(6, "CLICK_FLAG"),
|
||||||
|
(7, "GAME_OVER"),
|
||||||
|
(8, "CLICK_NAIVE"),
|
||||||
|
],
|
||||||
|
default=game.models.EventTypes["START_GAME"],
|
||||||
|
help_text="The game event",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
51
game/migrations/0005_auto_20201106_2357.py
Normal file
51
game/migrations/0005_auto_20201106_2357.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2020-11-06 23:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import game.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("game", "0004_auto_20201106_0453"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(model_name="gameevent", name="metadata",),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="gameevent",
|
||||||
|
name="event_col",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Column on the board where the event occurred, if applicable",
|
||||||
|
null=True,
|
||||||
|
verbose_name="The column clicked",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="gameevent",
|
||||||
|
name="event_row",
|
||||||
|
field=models.PositiveIntegerField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
help_text="Row on the board where the event occurred, if applicable",
|
||||||
|
null=True,
|
||||||
|
verbose_name="The row clicked",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="game",
|
||||||
|
name="status",
|
||||||
|
field=models.IntegerField(
|
||||||
|
choices=[
|
||||||
|
(0, "NOT_PLAYED"),
|
||||||
|
(1, "PLAYING"),
|
||||||
|
(2, "PAUSED"),
|
||||||
|
(3, "FINISHED"),
|
||||||
|
],
|
||||||
|
default=game.models.GameStatuses["NOT_PLAYED"],
|
||||||
|
help_text="Actual game status",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
19
game/migrations/0006_auto_20201107_0010.py
Normal file
19
game/migrations/0006_auto_20201107_0010.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2020-11-07 00:10
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("game", "0005_auto_20201106_2357"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="gameevent", old_name="event_col", new_name="col",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="gameevent", old_name="event_row", new_name="row",
|
||||||
|
),
|
||||||
|
]
|
||||||
33
game/migrations/0007_auto_20201107_0130.py
Normal file
33
game/migrations/0007_auto_20201107_0130.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2020-11-07 01:30
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import django_mysql.models
|
||||||
|
import internal.utils
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("game", "0006_auto_20201107_0010"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="game",
|
||||||
|
name="board_progress",
|
||||||
|
field=django_mysql.models.JSONField(
|
||||||
|
default=internal.utils.empty_list,
|
||||||
|
help_text="This board is updated at each GameEvent recorded",
|
||||||
|
verbose_name="Progress board",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="game",
|
||||||
|
name="board",
|
||||||
|
field=django_mysql.models.JSONField(
|
||||||
|
default=internal.utils.empty_list,
|
||||||
|
help_text="The generated board game",
|
||||||
|
verbose_name="Generated board",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -6,18 +6,23 @@ from internal.utils import empty_list
|
|||||||
from .game import Minesweeper
|
from .game import Minesweeper
|
||||||
|
|
||||||
|
|
||||||
class GameStatuses(IntEnum):
|
class EnumChoicesBase(IntEnum):
|
||||||
""" Enum was used as choices of Game.status because explicit is better than implicit """
|
""" Enum was used as choices of Game.status because explicit is better than implicit """
|
||||||
|
|
||||||
NOT_PLAYED = 0
|
|
||||||
PLAYING = 1
|
|
||||||
FINISHED = 2
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def choices(cls):
|
def choices(cls):
|
||||||
return [(key.value, key.name) for key in cls]
|
return [(key.value, key.name) for key in cls]
|
||||||
|
|
||||||
|
|
||||||
|
class GameStatuses(EnumChoicesBase):
|
||||||
|
""" Statuses used by the player and system on game """
|
||||||
|
|
||||||
|
NOT_PLAYED = 0
|
||||||
|
PLAYING = 1
|
||||||
|
PAUSED = 2
|
||||||
|
FINISHED = 3
|
||||||
|
|
||||||
|
|
||||||
class Game(models.Model):
|
class Game(models.Model):
|
||||||
created_at = models.DateTimeField("Creation date", auto_now_add=True)
|
created_at = models.DateTimeField("Creation date", auto_now_add=True)
|
||||||
modified_at = models.DateTimeField("Last update", auto_now=True)
|
modified_at = models.DateTimeField("Last update", auto_now=True)
|
||||||
@@ -33,8 +38,14 @@ class Game(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
board = JSONField(
|
board = JSONField(
|
||||||
"Generated board", default=empty_list, help_text="Whe generated board game"
|
"Generated board", default=empty_list, help_text="The generated board game"
|
||||||
)
|
)
|
||||||
|
board_progress = JSONField(
|
||||||
|
"Progress board",
|
||||||
|
default=empty_list,
|
||||||
|
help_text="This board is updated at each GameEvent recorded",
|
||||||
|
)
|
||||||
|
|
||||||
win = models.BooleanField(
|
win = models.BooleanField(
|
||||||
"Win?",
|
"Win?",
|
||||||
default=None,
|
default=None,
|
||||||
@@ -56,6 +67,7 @@ class Game(models.Model):
|
|||||||
ms.create_board()
|
ms.create_board()
|
||||||
ms.put_mines()
|
ms.put_mines()
|
||||||
self.board = ms.board
|
self.board = ms.board
|
||||||
|
self.board_progress = ms.board_progress
|
||||||
|
|
||||||
super(Game, self).save(*args, **kwargs)
|
super(Game, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@@ -63,3 +75,49 @@ class Game(models.Model):
|
|||||||
verbose_name = "Game"
|
verbose_name = "Game"
|
||||||
verbose_name_plural = "Games"
|
verbose_name_plural = "Games"
|
||||||
db_table = "games"
|
db_table = "games"
|
||||||
|
|
||||||
|
|
||||||
|
class EventTypes(EnumChoicesBase):
|
||||||
|
""" Event types to generate a game timeline """
|
||||||
|
|
||||||
|
START_GAME = 0
|
||||||
|
PAUSE = 1
|
||||||
|
RESUME = 2
|
||||||
|
CLICK_MINE = 3
|
||||||
|
CLICK_POINT = 4
|
||||||
|
CLICK_EMPTY = 5
|
||||||
|
CLICK_FLAG = 6
|
||||||
|
GAME_OVER = 7
|
||||||
|
CLICK_NAIVE = 8
|
||||||
|
|
||||||
|
|
||||||
|
class GameEvent(models.Model):
|
||||||
|
created_at = models.DateTimeField("Creation date", auto_now_add=True)
|
||||||
|
game = models.ForeignKey("game.Game", on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
type = models.IntegerField(
|
||||||
|
choices=EventTypes.choices(),
|
||||||
|
default=EventTypes.START_GAME,
|
||||||
|
help_text="The game event",
|
||||||
|
)
|
||||||
|
|
||||||
|
row = models.PositiveIntegerField(
|
||||||
|
"The row clicked",
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Row on the board where the event occurred, if applicable",
|
||||||
|
)
|
||||||
|
col = models.PositiveIntegerField(
|
||||||
|
"The column clicked",
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
help_text="Column on the board where the event occurred, if applicable",
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["created_at"]
|
||||||
|
verbose_name = "Game event"
|
||||||
|
verbose_name_plural = "Game events"
|
||||||
|
db_table = "game_events"
|
||||||
|
|||||||
92
game/signals.py
Normal file
92
game/signals.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
from django.db.models.signals import post_save, pre_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from .models import Game, GameEvent, EventTypes, GameStatuses
|
||||||
|
from .game import Minesweeper
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Game)
|
||||||
|
def game_start(sender, signal, instance, **kwargs):
|
||||||
|
""" If the game was just created, insert the first event START_GAME """
|
||||||
|
if not instance.status == GameStatuses.NOT_PLAYED:
|
||||||
|
return
|
||||||
|
GameEvent.objects.get_or_create(game=instance, type=EventTypes.START_GAME)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(pre_save, sender=GameEvent)
|
||||||
|
def identify_click_event(sender, signal, instance, **kwargs):
|
||||||
|
""" Verify what is on the naive click: mine, point or empty """
|
||||||
|
if not instance.type == EventTypes.CLICK_NAIVE:
|
||||||
|
return
|
||||||
|
|
||||||
|
if instance.row is None and instance.col is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
ms = Minesweeper(
|
||||||
|
instance.game.rows,
|
||||||
|
instance.game.cols,
|
||||||
|
instance.game.mines,
|
||||||
|
instance.game.board,
|
||||||
|
)
|
||||||
|
|
||||||
|
if ms.is_mine(instance.row, instance.col):
|
||||||
|
instance.type = EventTypes.CLICK_MINE
|
||||||
|
|
||||||
|
elif ms.is_empty(instance.row, instance.col):
|
||||||
|
instance.type = EventTypes.CLICK_EMPTY
|
||||||
|
|
||||||
|
elif ms.is_point(instance.row, instance.col):
|
||||||
|
instance.type = EventTypes.CLICK_POINT
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=GameEvent)
|
||||||
|
def create_post_save_game_event(sender, signal, instance, **kwargs):
|
||||||
|
ms = Minesweeper(
|
||||||
|
instance.game.rows,
|
||||||
|
instance.game.cols,
|
||||||
|
instance.game.mines,
|
||||||
|
instance.game.board,
|
||||||
|
instance.game.board_progress,
|
||||||
|
)
|
||||||
|
game_changed = False
|
||||||
|
|
||||||
|
reveal_events = [
|
||||||
|
EventTypes.CLICK_POINT,
|
||||||
|
EventTypes.CLICK_EMPTY,
|
||||||
|
EventTypes.CLICK_MINE,
|
||||||
|
]
|
||||||
|
if instance.type in reveal_events:
|
||||||
|
ms.reveal(instance.row, instance.col)
|
||||||
|
instance.game.board_progress = ms.board_progress
|
||||||
|
game_changed = True
|
||||||
|
|
||||||
|
playing_events = [
|
||||||
|
EventTypes.START_GAME,
|
||||||
|
EventTypes.RESUME,
|
||||||
|
EventTypes.CLICK_POINT,
|
||||||
|
EventTypes.CLICK_EMPTY,
|
||||||
|
EventTypes.CLICK_FLAG,
|
||||||
|
]
|
||||||
|
|
||||||
|
if instance.type in playing_events:
|
||||||
|
instance.game.status = GameStatuses.PLAYING
|
||||||
|
game_changed = True
|
||||||
|
|
||||||
|
elif instance.type == EventTypes.PAUSE:
|
||||||
|
instance.game.status = GameStatuses.PAUSED
|
||||||
|
game_changed = True
|
||||||
|
|
||||||
|
elif instance.type == EventTypes.CLICK_MINE:
|
||||||
|
instance.game.status = GameStatuses.FINISHED
|
||||||
|
instance.game.win = False
|
||||||
|
game_changed = True
|
||||||
|
|
||||||
|
GameEvent(game=instance.game, type=EventTypes.GAME_OVER).save()
|
||||||
|
|
||||||
|
if ms.win() is True:
|
||||||
|
instance.game.status = GameStatuses.FINISHED
|
||||||
|
instance.game.win = True
|
||||||
|
game_changed = True
|
||||||
|
|
||||||
|
if game_changed is True:
|
||||||
|
instance.game.save()
|
||||||
Reference in New Issue
Block a user