storycraft: port to ircbot v2
this silly storytelling game has been lightly tested, but it appears to not blow up miserably, so i'm going to commit it until i can spend more time testing it/caring about it
This commit is contained in:
parent
095221a7b2
commit
1193ce5b6a
@ -47,6 +47,7 @@ INSTALLED_APPS = (
|
||||
'pi',
|
||||
'races',
|
||||
'seen',
|
||||
'storycraft',
|
||||
'twitter',
|
||||
)
|
||||
|
||||
@ -156,6 +157,14 @@ IRCBOT_XMLRPC_PORT = 13132
|
||||
|
||||
# IRC module stuff
|
||||
|
||||
# storycraft
|
||||
|
||||
STORYCRAFT_MASTER_CHANNEL = '#dr.botzo'
|
||||
STORYCRAFT_CONCURRENT_GAMES = 10
|
||||
STORYCRAFT_DEFAULT_GAME_LENGTH = 20
|
||||
STORYCRAFT_DEFAULT_LINE_LENGTH = 140
|
||||
STORYCRAFT_DEFAULT_LINES_PER_TURN = 2
|
||||
|
||||
# twitter
|
||||
|
||||
TWITTER_CONSUMER_KEY = None
|
||||
|
0
dr_botzo/storycraft/__init__.py
Normal file
0
dr_botzo/storycraft/__init__.py
Normal file
8
dr_botzo/storycraft/admin.py
Normal file
8
dr_botzo/storycraft/admin.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from storycraft.models import StorycraftGame, StorycraftPlayer, StorycraftLine
|
||||
|
||||
|
||||
admin.site.register(StorycraftGame, admin.ModelAdmin)
|
||||
admin.site.register(StorycraftPlayer, admin.ModelAdmin)
|
||||
admin.site.register(StorycraftLine, admin.ModelAdmin)
|
568
dr_botzo/storycraft/ircplugin.py
Normal file
568
dr_botzo/storycraft/ircplugin.py
Normal file
@ -0,0 +1,568 @@
|
||||
"""Collaborative nonsense story writing."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from irc.client import NickMask
|
||||
|
||||
from ircbot.lib import Plugin
|
||||
from storycraft.models import StorycraftGame, StorycraftPlayer, StorycraftLine
|
||||
|
||||
|
||||
log = logging.getLogger('storycraft.ircplugin')
|
||||
|
||||
|
||||
class Storycraft(Plugin):
|
||||
|
||||
"""Play Storycraft, the game of varying authors adding to a story mostly devoid of context.
|
||||
|
||||
This is a game for people who like making up facts, insulting each other, telling
|
||||
insane stories, making in-jokes, and generally having a good time writing what can
|
||||
fairly only be described as pure nonsense. It does not assume that all players are
|
||||
available at any given time, so games may take a while, but the module allows for
|
||||
concurrency in the number of games running.
|
||||
"""
|
||||
|
||||
def start(self):
|
||||
"""Hook handler functions into the IRC library."""
|
||||
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!storycraft\s+status$',
|
||||
self.handle_status)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+new\s+game(\s+with\s+config\s+(.*)$|$)',
|
||||
self.handle_new_game)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+game\s+(\d+)\s+join$',
|
||||
self.handle_join_game)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+list\s+games\s+(open|in progress|completed|my games|waiting for me)$',
|
||||
self.handle_list_games)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+game\s+(\d+)\s+start$',
|
||||
self.handle_start_game)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+game\s+(\d+)\s+show\s+line$',
|
||||
self.handle_show_line)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+game\s+(\d+)\s+add\s+line\s+(.*)$',
|
||||
self.handle_add_line)
|
||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'],
|
||||
r'^!storycraft\s+game\s+(\d+)\s+status$',
|
||||
self.handle_game_status)
|
||||
|
||||
super(Storycraft, self).start()
|
||||
|
||||
def stop(self):
|
||||
"""Unhook handler functions into the IRC library."""
|
||||
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_status)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_new_game)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_join_game)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_list_games)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_start_game)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_show_line)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_add_line)
|
||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_game_status)
|
||||
|
||||
super(Storycraft, self).stop()
|
||||
|
||||
def handle_status(self, connection, event, match):
|
||||
"""Print information about the storycraft games, or one specific game."""
|
||||
|
||||
# do the general status of all games
|
||||
count_completed = self._get_completed_games().count()
|
||||
count_in_progress = self._get_in_progress_games().count()
|
||||
count_open = self._get_open_games().count()
|
||||
count_free = self._get_free_game_count()
|
||||
|
||||
return self.bot.reply(event, "Storycraft - {0:d} games completed, {1:d} in progress, {2:d} open. "
|
||||
"{3:d} slots free.".format(count_completed, count_in_progress, count_open, count_free))
|
||||
|
||||
def handle_game_status(self, connection, event, match):
|
||||
"""Print information about one specific game."""
|
||||
|
||||
game_id = int(match.group(1))
|
||||
|
||||
# do the status of one game
|
||||
game = self._get_game_details(game_id)
|
||||
if game:
|
||||
# get game string
|
||||
status_str = game.summary()
|
||||
|
||||
return self.bot.reply(event, status_str)
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} does not exist.".format(game_id))
|
||||
|
||||
def handle_new_game(self, connection, event, match):
|
||||
"""Add a game to the system, which users can join."""
|
||||
|
||||
nick = NickMask(event.source).nick
|
||||
|
||||
# ensure the system isn't at capacity
|
||||
count_free = self._get_free_game_count()
|
||||
if count_free > 0:
|
||||
# get the default settings
|
||||
master_channel = settings.STORYCRAFT_MASTER_CHANNEL
|
||||
game_length = settings.STORYCRAFT_DEFAULT_GAME_LENGTH
|
||||
line_length = settings.STORYCRAFT_DEFAULT_LINE_LENGTH
|
||||
lines_per_turn = settings.STORYCRAFT_DEFAULT_LINES_PER_TURN
|
||||
|
||||
options = match.group(2)
|
||||
if options:
|
||||
# override settings with provided options
|
||||
tuples = options.split(';')
|
||||
for option in tuples:
|
||||
optpair = option.split(',')
|
||||
|
||||
if len(optpair) == 2:
|
||||
if optpair[0] == 'game_length':
|
||||
game_length = int(optpair[1])
|
||||
elif optpair[0] == 'line_length':
|
||||
line_length = int(optpair[1])
|
||||
elif optpair[0] == 'lines_per_turn':
|
||||
lines_per_turn = int(optpair[1])
|
||||
|
||||
# add a new game
|
||||
game = self._add_new_game(game_length, line_length, lines_per_turn, nick, event.source)
|
||||
if game:
|
||||
# add the player to the game, too
|
||||
player = StorycraftPlayer.objects.create(nick=nick, nickmask=event.source, game=game)
|
||||
|
||||
# tell the control channel
|
||||
self.bot.privmsg(master_channel, "{0:s} created a game of storycraft - do '!storycraft game "
|
||||
"{1:d} join' to take part!".format(nick, game.pk))
|
||||
|
||||
log.debug("%s added a new game", nick)
|
||||
return self.bot.reply(event, "Game #{0:d} has been created. When everyone has joined, do "
|
||||
"'!storycraft game {0:d} start'".format(game.pk))
|
||||
else:
|
||||
return self.bot.reply(event, "Error creating game.")
|
||||
else:
|
||||
return self.bot.reply(event, "All slots are full.")
|
||||
|
||||
def handle_join_game(self, connection, event, match):
|
||||
"""Add a player to an open game."""
|
||||
|
||||
nick = NickMask(event.source).nick
|
||||
game_id = int(match.group(1))
|
||||
|
||||
# ensure the game exists
|
||||
game = self._get_game_details(game_id)
|
||||
if game:
|
||||
# check that the game hasn't started yet
|
||||
if game.status == StorycraftGame.STATUS_OPEN:
|
||||
# see if userhost is already in the game
|
||||
if not self._get_player_exists_in_game(game_id, event.source):
|
||||
# add the player
|
||||
player = StorycraftPlayer.objects.create(nick=nick, nickmask=event.source, game=game)
|
||||
if player:
|
||||
# output results
|
||||
master_channel = settings.STORYCRAFT_MASTER_CHANNEL
|
||||
|
||||
self.bot.privmsg(master_channel, "{0:s} joined storycraft #{1:d}!".format(nick, game_id))
|
||||
log.debug("%s joined game #%d", nick, game_id)
|
||||
return self.bot.reply(event, "{0:s}, welcome to the game.".format(nick))
|
||||
else:
|
||||
return self.bot.reply(event, "Error joining game.")
|
||||
else:
|
||||
return self.bot.reply(event, "You are already in game #{0:d}.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} is not open for new players.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply("Game #{0:d} does not exist.".format(game_id))
|
||||
|
||||
def handle_list_games(self, connection, event, match):
|
||||
"""Get the listing of either open or in progress games."""
|
||||
|
||||
nick = NickMask(event.source).nick
|
||||
category = match.group(1)
|
||||
|
||||
games = list()
|
||||
if category == 'open':
|
||||
games = self._get_open_games()
|
||||
elif category == 'in progress':
|
||||
games = self._get_in_progress_games()
|
||||
elif category == 'completed':
|
||||
games = self._get_completed_games()
|
||||
elif category == 'my games':
|
||||
games = self._get_active_games_with_player(nick)
|
||||
elif category == 'waiting for me':
|
||||
games = self._get_games_waiting_on_player(nick)
|
||||
|
||||
if len(games) > 5:
|
||||
# just list the game ids
|
||||
gameids = []
|
||||
for game in games:
|
||||
gameids.append(str(game.id))
|
||||
|
||||
return self.bot.reply(event, "Too many to list! ids: {0:s}".format(','.join(gameids)))
|
||||
else:
|
||||
# show game details, since there's not many
|
||||
gamestrs = []
|
||||
for game in games:
|
||||
gamestrs.append(game.summary())
|
||||
|
||||
return self.bot.reply(event, "\n".join(gamestrs))
|
||||
|
||||
def handle_start_game(self, connection, event, match):
|
||||
"""Start a game, closing the period to join and starting line trading."""
|
||||
|
||||
nick = NickMask(event.source).nick
|
||||
game_id = int(match.group(1))
|
||||
|
||||
# retrieve the game
|
||||
game = self._get_game_details(game_id)
|
||||
if game:
|
||||
# check that the user is the owner of the game
|
||||
if event.source == game.owner_nickmask:
|
||||
# check that the game is open
|
||||
if game.status == StorycraftGame.STATUS_OPEN:
|
||||
# start the game
|
||||
game.status = StorycraftGame.STATUS_IN_PROGRESS
|
||||
game.clean()
|
||||
game.save()
|
||||
|
||||
# determine the first person to send a line
|
||||
if self._pick_next_player(game_id):
|
||||
# indicate the status
|
||||
line = game.get_lines()[0]
|
||||
player = self._get_player_by_id(line.player_id)
|
||||
|
||||
master_channel = settings.STORYCRAFT_MASTER_CHANNEL
|
||||
|
||||
# tell the control channel
|
||||
self.bot.privmsg(master_channel, "{0:s} started storycraft #{1:d}! - first player "
|
||||
"is {2:s}, do '!storycraft game {1:d} show line' when the game is "
|
||||
"assigned to you.".format(nick, game_id, player.nick))
|
||||
log.debug("%s started game #%d", nick, game_id)
|
||||
|
||||
return self.bot.reply(event, "Game #{0:d} started.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Failed to assign game to first player.")
|
||||
else:
|
||||
return self.bot.reply("Game #{0:d} is not open.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "You are not the owner of #{0:d}.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} does not exist.".format(game_id))
|
||||
|
||||
def handle_show_line(self, connection, event, match):
|
||||
"""List the line to continue with, if queryer is the assignee."""
|
||||
|
||||
game_id = int(match.group(1))
|
||||
|
||||
# retrieve the game
|
||||
game = self._get_game_details(game_id)
|
||||
if game:
|
||||
# check that the game is in progress
|
||||
if game.status == StorycraftGame.STATUS_IN_PROGRESS:
|
||||
# get the most recent line
|
||||
lines = game.get_lines()
|
||||
if lines:
|
||||
line = lines[0]
|
||||
|
||||
# check that the line is a prompt for input
|
||||
if line.line == '':
|
||||
player = self._get_player_by_id(line.player_id)
|
||||
|
||||
# check that the caller is the person the line is assigned to
|
||||
if player.nickmask == event.source:
|
||||
# check previous line
|
||||
if len(lines) > 1:
|
||||
# get previous line and display it
|
||||
repline = lines[1]
|
||||
return self.bot.reply(event, "The current line is '{0:s}'. Continue the story "
|
||||
"with '!storycraft game {1:d} add line YOUR TEXT'."
|
||||
"".format(repline.line, game_id))
|
||||
else:
|
||||
# player is starting the story
|
||||
return self.bot.reply(event, "You are starting the story! Add the first line "
|
||||
"with '!storycraft game {0:d} add line YOUR TEXT'."
|
||||
"".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "You are not the assignee of the current game.")
|
||||
else:
|
||||
return self.bot.reply(event, "Game is in progress but the most recent line is not "
|
||||
"available - internal consistency error.")
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} has no lines - internal consistency error."
|
||||
"".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} has not been started.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} does not exist.".format(game_id))
|
||||
|
||||
def handle_add_line(self, connection, event, match):
|
||||
"""Add a line to an in progress game."""
|
||||
|
||||
nick = NickMask(event.source).nick
|
||||
game_id = int(match.group(1))
|
||||
input_line = match.group(2)
|
||||
|
||||
# retrieve the game
|
||||
game = self._get_game_details(game_id)
|
||||
if game:
|
||||
# check that the game is in progress
|
||||
if game.status == StorycraftGame.STATUS_IN_PROGRESS:
|
||||
# get the most recent line
|
||||
lines = game.get_lines()
|
||||
if lines:
|
||||
line = lines[0]
|
||||
|
||||
# check that the line is a prompt for input
|
||||
if line.line == '':
|
||||
player = self._get_player_by_id(line.player_id)
|
||||
if player.nickmask == event.source:
|
||||
# check the input line length
|
||||
if len(input_line) <= game.line_length:
|
||||
# add line
|
||||
line.line = input_line
|
||||
line.clean()
|
||||
line.save()
|
||||
|
||||
# determine the first person to send a line
|
||||
if self._pick_next_player(game_id):
|
||||
# indicate the status
|
||||
game = self._get_game_details(game_id)
|
||||
line = game.get_lines()[0]
|
||||
last_line = game.get_lines()[1]
|
||||
player = self._get_player_by_id(line.player_id)
|
||||
|
||||
return_msg = 'Line logged.'
|
||||
|
||||
# get progress, so user knows how much time is left
|
||||
progress_str = game.get_progress_string()
|
||||
|
||||
# message the new owner
|
||||
if player.nick == nick:
|
||||
# simpily notify them they're up again
|
||||
return_msg = 'Line logged. Please add another. ' + progress_str
|
||||
else:
|
||||
# notify the new owner, too
|
||||
self.bot.privmsg(player.nick, "You have a new line in storycraft "
|
||||
"#{0:d}: '{1:s}' {2:s}"
|
||||
"".format(game_id, last_line.line, progress_str))
|
||||
|
||||
master_channel = settings.STORYCRAFT_MASTER_CHANNEL
|
||||
|
||||
log.debug("%s added a line to #%d", nick, game_id)
|
||||
# log output
|
||||
if game.status == StorycraftGame.STATUS_IN_PROGRESS:
|
||||
self.bot.privmsg(master_channel, "{0:s} added a line to storycraft "
|
||||
"#{1:d}! - next player is {2:s}"
|
||||
"".format(nick, game_id, player.nick))
|
||||
return self.bot.reply(event, return_msg)
|
||||
else:
|
||||
self.bot.privmsg(master_channel, "{0:s} finished storycraft #{1:d}!"
|
||||
"".format(nick, game_id))
|
||||
return self.bot.reply(event, "Line logged (and game completed).")
|
||||
else:
|
||||
return self.bot.reply(event, "Failed to assign game to next player.")
|
||||
else:
|
||||
return self.bot.reply(event, "The maximum line length for this game is {0:d} "
|
||||
"characters. (You were over by {1:d}.)"
|
||||
"".format(game.line_length, len(input_line)-game.line_length))
|
||||
else:
|
||||
return self.bot.reply(event, "You are not the assignee of the current game.")
|
||||
else:
|
||||
return self.bot.reply(event, "Game is in progress but the most recent line is not "
|
||||
"available - internal consistency error.")
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} has no lines - internal consistency error."
|
||||
"".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} has not been started.".format(game_id))
|
||||
else:
|
||||
return self.bot.reply(event, "Game #{0:d} does not exist.".format(game_id))
|
||||
|
||||
@staticmethod
|
||||
def _get_game_details(game_id):
|
||||
"""Get the details of one game."""
|
||||
|
||||
try:
|
||||
game = StorycraftGame.objects.get(pk=game_id)
|
||||
return game
|
||||
except StorycraftGame.DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _add_new_game(game_length, line_length, lines_per_turn, nick, nickmask):
|
||||
"""Add a new game to the system."""
|
||||
|
||||
game = StorycraftGame.objects.create(game_length=game_length, line_length=line_length,
|
||||
lines_per_turn=lines_per_turn, owner_nick=nick,
|
||||
owner_nickmask=nickmask)
|
||||
return game
|
||||
|
||||
@staticmethod
|
||||
def _get_player_list_for_game(game_id):
|
||||
"""Get the list of players in one game."""
|
||||
|
||||
try:
|
||||
game = StorycraftGame.objects.get(pk=game_id)
|
||||
players = StorycraftPlayer.objects.filter(game=game)
|
||||
return players
|
||||
except StorycraftGame.DoesNotExist:
|
||||
return []
|
||||
|
||||
def _get_game_exists(self, game_id):
|
||||
"""Return the existence of a game."""
|
||||
|
||||
return self._get_game_details(game_id) is not None
|
||||
|
||||
def _pick_next_player(self, game_id):
|
||||
"""Based on the game's settings and state, set who will be replying next."""
|
||||
|
||||
game = self._get_game_details(game_id)
|
||||
if game:
|
||||
count_by_latest_player = 0
|
||||
latest_player_id = 0
|
||||
|
||||
# get the lines in this game, latest first
|
||||
lines = game.get_lines()
|
||||
|
||||
for line in lines:
|
||||
if latest_player_id == 0:
|
||||
latest_player_id = line.player_id
|
||||
|
||||
if latest_player_id == line.player_id:
|
||||
count_by_latest_player += 1
|
||||
else:
|
||||
break
|
||||
|
||||
# now we know how many times the most recent person has responded,
|
||||
# figure out what to do
|
||||
if count_by_latest_player == 0:
|
||||
# must be a new game, get a random player
|
||||
players = self._get_player_list_for_game(game.id)
|
||||
random_player = players[random.randint(1, len(players))-1]
|
||||
return self._assign_game_to_player(game_id, random_player.id)
|
||||
elif count_by_latest_player >= game.lines_per_turn and len(lines) < game.game_length:
|
||||
# player has reached the max, get a random different player to assign
|
||||
players = self._get_player_list_for_game(game.id)
|
||||
random_player_id = latest_player_id
|
||||
if len(players) > 1:
|
||||
while random_player_id == latest_player_id:
|
||||
random_player = players[random.randint(1, len(players))-1]
|
||||
random_player_id = random_player.id
|
||||
return self._assign_game_to_player(game_id, random_player_id)
|
||||
elif count_by_latest_player >= game.lines_per_turn and len(lines) >= game.game_length:
|
||||
# end of game
|
||||
game.status = StorycraftGame.STATUS_COMPLETED
|
||||
game.clean()
|
||||
game.save()
|
||||
return game
|
||||
elif count_by_latest_player < game.lines_per_turn:
|
||||
# player should continue getting lines
|
||||
return self._assign_game_to_player(game_id, latest_player_id)
|
||||
|
||||
@staticmethod
|
||||
def _assign_game_to_player(game_id, player_id):
|
||||
"""Assign the game to a player, prompting them for responses."""
|
||||
|
||||
try:
|
||||
game = StorycraftGame.objects.get(pk=game_id)
|
||||
except StorycraftGame.DoesNotExist:
|
||||
return None
|
||||
|
||||
try:
|
||||
player = StorycraftPlayer.objects.get(pk=player_id)
|
||||
except StorycraftPlayer.DoesNotExist:
|
||||
return None
|
||||
|
||||
line = StorycraftLine.objects.create(game=game, player=player)
|
||||
return line
|
||||
|
||||
@staticmethod
|
||||
def _get_player_by_id(player_id):
|
||||
"""Get the player details based on id."""
|
||||
|
||||
try:
|
||||
player = StorycraftPlayer.objects.get(pk=player_id)
|
||||
return player
|
||||
except StorycraftPlayer.DoesNotExist:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_player_by_userhost_in_game(game_id, nickmask):
|
||||
"""Get the player details if they exist in the given game."""
|
||||
|
||||
try:
|
||||
game = StorycraftGame.objects.get(pk=game_id)
|
||||
except StorycraftGame.DoesNotExist:
|
||||
return None
|
||||
|
||||
players = StorycraftPlayer.objects.filter(nickmask=nickmask, game=game)
|
||||
if len(players) == 1:
|
||||
return players[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
def _get_completed_games(self):
|
||||
"""Get the games with COMPLETED status."""
|
||||
|
||||
return self._get_games_of_type(StorycraftGame.STATUS_COMPLETED)
|
||||
|
||||
def _get_in_progress_games(self):
|
||||
"""Get the games with IN PROGRESS status."""
|
||||
|
||||
return self._get_games_of_type(StorycraftGame.STATUS_IN_PROGRESS)
|
||||
|
||||
def _get_open_games(self):
|
||||
"""Get the games with OPEN status."""
|
||||
|
||||
return self._get_games_of_type(StorycraftGame.STATUS_OPEN)
|
||||
|
||||
@staticmethod
|
||||
def _get_active_games_with_player(player_nick):
|
||||
"""Return the in progress/open games that include the player nick."""
|
||||
|
||||
players = StorycraftPlayer.objects.filter(nick=player_nick)
|
||||
game_ids = [x.game.pk for x in players]
|
||||
|
||||
return StorycraftGame.objects.filter(pk__in=game_ids).filter(status__in=[StorycraftGame.STATUS_OPEN,
|
||||
StorycraftGame.STATUS_IN_PROGRESS])
|
||||
|
||||
@staticmethod
|
||||
def _get_games_waiting_on_player(player_nick):
|
||||
"""Return the games where the player nick is the owner of a pending line.
|
||||
|
||||
Include the games that the player started and are open, since they're waiting
|
||||
on the person too.
|
||||
"""
|
||||
|
||||
try:
|
||||
player = StorycraftPlayer.objects.get(nick=player_nick)
|
||||
except StorycraftPlayer.DoesNotExist:
|
||||
return StorycraftGame.objects.none()
|
||||
|
||||
latest_lines = StorycraftLine.objects.filter(player=player).latest('time')
|
||||
return [x.game for x in latest_lines if x.line == '']
|
||||
|
||||
@staticmethod
|
||||
def _get_games_of_type(status):
|
||||
"""Return the games of the specified type."""
|
||||
|
||||
return StorycraftGame.objects.filter(status=status)
|
||||
|
||||
def _get_player_exists_in_game(self, game_id, nickmask):
|
||||
"""Return the existence of a player in a game."""
|
||||
|
||||
return self._get_player_by_userhost_in_game(game_id, nickmask) is not None
|
||||
|
||||
def _get_free_game_count(self):
|
||||
"""Get the number of slots available, given current in progess/open games."""
|
||||
|
||||
all_slots = settings.STORYCRAFT_CONCURRENT_GAMES
|
||||
count_in_progress = self._get_in_progress_games().count()
|
||||
count_open = self._get_open_games().count()
|
||||
return all_slots - count_in_progress - count_open
|
||||
|
||||
|
||||
plugin = Storycraft
|
50
dr_botzo/storycraft/migrations/0001_initial.py
Normal file
50
dr_botzo/storycraft/migrations/0001_initial.py
Normal file
@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='StorycraftGame',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('game_length', models.PositiveSmallIntegerField()),
|
||||
('line_length', models.PositiveSmallIntegerField()),
|
||||
('lines_per_turn', models.PositiveSmallIntegerField()),
|
||||
('status', models.CharField(max_length=16, choices=[('OPEN', 'OPEN'), ('IN PROGRESS', 'IN PROGRESS'), ('COMPLETED', 'COMPLETED')])),
|
||||
('owner_nick', models.CharField(max_length=64)),
|
||||
('owner_nickmask', models.CharField(max_length=200)),
|
||||
('start_time', models.DateTimeField()),
|
||||
('end_time', models.DateTimeField()),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StorycraftLine',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('line', models.TextField(default='')),
|
||||
('time', models.DateTimeField(auto_now_add=True)),
|
||||
('game', models.ForeignKey(to='storycraft.StorycraftGame')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StorycraftPlayer',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('nick', models.CharField(max_length=64)),
|
||||
('nickmask', models.CharField(max_length=200)),
|
||||
('game', models.ForeignKey(to='storycraft.StorycraftGame')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='storycraftline',
|
||||
name='player',
|
||||
field=models.ForeignKey(to='storycraft.StorycraftPlayer'),
|
||||
),
|
||||
]
|
24
dr_botzo/storycraft/migrations/0002_auto_20150619_2034.py
Normal file
24
dr_botzo/storycraft/migrations/0002_auto_20150619_2034.py
Normal file
@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('storycraft', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='storycraftgame',
|
||||
name='end_time',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='storycraftgame',
|
||||
name='start_time',
|
||||
field=models.DateTimeField(null=True),
|
||||
),
|
||||
]
|
19
dr_botzo/storycraft/migrations/0003_auto_20150619_2038.py
Normal file
19
dr_botzo/storycraft/migrations/0003_auto_20150619_2038.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('storycraft', '0002_auto_20150619_2034'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='storycraftgame',
|
||||
name='status',
|
||||
field=models.CharField(default='OPEN', max_length=16, choices=[('OPEN', 'OPEN'), ('IN PROGRESS', 'IN PROGRESS'), ('COMPLETED', 'COMPLETED')]),
|
||||
),
|
||||
]
|
29
dr_botzo/storycraft/migrations/0004_auto_20150619_2040.py
Normal file
29
dr_botzo/storycraft/migrations/0004_auto_20150619_2040.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('storycraft', '0003_auto_20150619_2038'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='storycraftline',
|
||||
name='game',
|
||||
field=models.ForeignKey(related_name='lines', to='storycraft.StorycraftGame'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='storycraftline',
|
||||
name='player',
|
||||
field=models.ForeignKey(related_name='lines', to='storycraft.StorycraftPlayer'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='storycraftplayer',
|
||||
name='game',
|
||||
field=models.ForeignKey(related_name='players', to='storycraft.StorycraftGame'),
|
||||
),
|
||||
]
|
@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import datetime
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('storycraft', '0004_auto_20150619_2040'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='storycraftgame',
|
||||
name='create_time',
|
||||
field=models.DateTimeField(default=datetime.datetime(2015, 6, 20, 1, 51, 18, 778824, tzinfo=utc), auto_now_add=True),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
0
dr_botzo/storycraft/migrations/__init__.py
Normal file
0
dr_botzo/storycraft/migrations/__init__.py
Normal file
114
dr_botzo/storycraft/models.py
Normal file
114
dr_botzo/storycraft/models.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""Track storycraft games."""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
log = logging.getLogger('storycraft.models')
|
||||
|
||||
|
||||
class StorycraftGame(models.Model):
|
||||
|
||||
"""Contain entire games of storycraft."""
|
||||
|
||||
STATUS_OPEN = 'OPEN'
|
||||
STATUS_IN_PROGRESS = 'IN PROGRESS'
|
||||
STATUS_COMPLETED = 'COMPLETED'
|
||||
|
||||
STATUS_CHOICES = (
|
||||
(STATUS_OPEN, "OPEN"),
|
||||
(STATUS_IN_PROGRESS, "IN PROGRESS"),
|
||||
(STATUS_COMPLETED, "COMPLETED"),
|
||||
)
|
||||
|
||||
game_length = models.PositiveSmallIntegerField()
|
||||
line_length = models.PositiveSmallIntegerField()
|
||||
lines_per_turn = models.PositiveSmallIntegerField()
|
||||
status = models.CharField(max_length=16, choices=STATUS_CHOICES, default=STATUS_OPEN)
|
||||
owner_nick = models.CharField(max_length=64)
|
||||
owner_nickmask = models.CharField(max_length=200)
|
||||
|
||||
create_time = models.DateTimeField(auto_now_add=True)
|
||||
start_time = models.DateTimeField(null=True)
|
||||
end_time = models.DateTimeField(null=True)
|
||||
|
||||
def __unicode__(self):
|
||||
"""String representation."""
|
||||
|
||||
return "Storycraft game {0:d}: {1:s}; {2:s}".format(self.pk, self.summary(), self.get_progress_string())
|
||||
|
||||
def get_progress_string(self):
|
||||
"""Get a terse summary of the game's progress."""
|
||||
|
||||
lines = self.get_lines()
|
||||
num_lines = len(lines)-1
|
||||
|
||||
if num_lines == self.game_length - 1:
|
||||
last_line = ' LAST LINE'
|
||||
else:
|
||||
last_line = ''
|
||||
|
||||
progress = "lines: {0:d}/{1:d}{2:s}".format(num_lines, self.game_length, last_line)
|
||||
return progress
|
||||
|
||||
def get_lines(self):
|
||||
"""Get the lines for the game."""
|
||||
|
||||
return self.lines.order_by('-time')
|
||||
|
||||
def summary(self):
|
||||
"""Display game info for a general summary."""
|
||||
|
||||
status_str = "#{0:d} - created on {1:s} by {2:s}, {3:s}".format(self.id,
|
||||
timezone.localtime(self.create_time).strftime('%Y-%m-%d %H:%M:%S %Z'),
|
||||
self.owner_nick, self.status)
|
||||
if self.status == StorycraftGame.STATUS_COMPLETED and self.end_time:
|
||||
status_str = status_str + ' ({0:s})'.format(self.end_time.strftime('%Y/%m/%d %H:%M:%S'))
|
||||
elif self.status == StorycraftGame.STATUS_IN_PROGRESS:
|
||||
lines = self.get_lines()
|
||||
player = self._get_player_by_id(lines[0].player_id)
|
||||
status_str = status_str + ' ({0:d} lines, next is {1:s})'.format(len(lines)-1, player.nick)
|
||||
|
||||
# get players in game
|
||||
player_names = []
|
||||
for player in self.players.all():
|
||||
player_names.append(player.nick)
|
||||
status_str = status_str + ', players: {0:s}'.format(', '.join(player_names))
|
||||
|
||||
status_str = status_str + "; [o:{0:d}[{1:d}],{2:d}]".format(self.game_length, self.line_length,
|
||||
self.lines_per_turn)
|
||||
|
||||
return status_str
|
||||
|
||||
|
||||
class StorycraftPlayer(models.Model):
|
||||
|
||||
"""Contain entire games of storycraft."""
|
||||
|
||||
game = models.ForeignKey('StorycraftGame', related_name='players')
|
||||
nick = models.CharField(max_length=64)
|
||||
nickmask = models.CharField(max_length=200)
|
||||
|
||||
def __unicode__(self):
|
||||
"""String representation."""
|
||||
|
||||
return "{0:s} in storycraft game {1:d}".format(self.nick, self.game.pk)
|
||||
|
||||
|
||||
class StorycraftLine(models.Model):
|
||||
|
||||
"""Handle requests to dispatchers and do something with them."""
|
||||
|
||||
game = models.ForeignKey('StorycraftGame', related_name='lines')
|
||||
player = models.ForeignKey('StorycraftPlayer', related_name='lines')
|
||||
line = models.TextField(default="")
|
||||
time = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __unicode__(self):
|
||||
"""String representation."""
|
||||
|
||||
return "line by {0:s} in storycraft game {1:d}".format(self.player.nick, self.game.pk)
|
Loading…
Reference in New Issue
Block a user