Skip to content

Commit

Permalink
Merge pull request #243 from domino14/build-mode
Browse files Browse the repository at this point in the history
Build mode
  • Loading branch information
domino14 authored Jun 29, 2017
2 parents be7022a + 5a8514b commit 59e16a8
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 31 deletions.
20 changes: 20 additions & 0 deletions djAerolith/base/migrations/0008_savedlist_category.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-06-28 16:35
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('base', '0007_maintenance'),
]

operations = [
migrations.AddField(
model_name='savedlist',
name='category',
field=models.CharField(choices=[(b'A', b'Anagram'), (b'B', b'Build'), (b'T', b'Through')], default=b'A', max_length=2),
),
]
24 changes: 20 additions & 4 deletions djAerolith/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,16 @@ def __unicode__(self):


class SavedList(models.Model):
CATEGORY_ANAGRAM = 'A' # Regular word walls
CATEGORY_BUILD = 'B' # Subwords
CATEGORY_THROUGH_TILES = 'T'

LIST_CATEGORIES = (
(CATEGORY_ANAGRAM, 'Anagram'),
(CATEGORY_BUILD, 'Build'),
(CATEGORY_THROUGH_TILES, 'Through')
)

lexicon = models.ForeignKey(Lexicon)
created = models.DateTimeField(auto_now_add=True)
lastSaved = models.DateTimeField(auto_now=True)
Expand All @@ -99,9 +109,12 @@ class SavedList(models.Model):
# that should set it back to False is a save.
is_temporary = models.BooleanField(default=False)
version = models.IntegerField(default=2)
category = models.CharField(choices=LIST_CATEGORIES, max_length=2,
default=CATEGORY_ANAGRAM)

def initialize_list(self, questions, lexicon, user, shuffle=False,
keep_old_name=False, save=True):
keep_old_name=False, save=True,
category=CATEGORY_ANAGRAM):
"""
Initialize a list with the passed in questions. Optionally saves
it to the database.
Expand Down Expand Up @@ -135,6 +148,7 @@ def initialize_list(self, questions, lexicon, user, shuffle=False,
self.missed = json.dumps([])
self.firstMissed = json.dumps([])
self.version = 2
self.category = category
if save:
self.user = user
self.save()
Expand All @@ -143,7 +157,7 @@ def restart_list(self, shuffle=False):
""" Restart this list; save it back to the database. """
self.initialize_list(json.loads(self.origQuestions),
self.lexicon, self.user, shuffle,
keep_old_name=True)
keep_old_name=True, category=self.category)

def set_to_first_missed(self):
""" Set this list to quiz on first missed questions; save. """
Expand Down Expand Up @@ -182,7 +196,8 @@ def to_python(self):
'firstMissed': json.loads(self.firstMissed),
'version': self.version,
'id': self.pk,
'temporary': self.is_temporary
'temporary': self.is_temporary,
'category': self.category,
}

def date_to_str(self, dt, human):
Expand Down Expand Up @@ -215,7 +230,8 @@ def to_python_reduced(self, last_saved_human=False):
'lastSaved': self.date_to_str(self.lastSaved, last_saved_human),
'lastSavedDT': self.date_to_str(self.lastSaved, False),
'id': self.pk,
'temporary': self.is_temporary
'temporary': self.is_temporary,
'category': self.category,
}

def __unicode__(self):
Expand Down
2 changes: 1 addition & 1 deletion djAerolith/current_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CURRENT_VERSION = '0.11.2.0'
CURRENT_VERSION = '0.11.3.0'
16 changes: 16 additions & 0 deletions djAerolith/lib/macondo_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ def gen_blank_challenges(length, lexicon_name, num_2_blanks, num_questions,
return resp['questions']


def gen_build_challenge(min_length, max_length, lexicon_name,
require_length_solution, min_solutions,
max_solutions):
logger.debug('in gen_build_challenge')
resp = make_rpc_call(
'AnagramService.BuildChallenge', {
'wordLength': max_length,
'minWordLength': min_length,
'lexicon': lexicon_name,
'requireLengthSolution': require_length_solution,
'minSolutions': min_solutions,
'maxSolutions': max_solutions
})
return resp['question'], resp['numAnswers']


def anagram_letters(lexicon_name, letters):
resp = make_rpc_call('AnagramService.Anagram', {
'lexicon': lexicon_name,
Expand Down
10 changes: 6 additions & 4 deletions djAerolith/lib/word_db_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,14 @@ def __unicode__(self):
class Questions(object):
def __init__(self):
self.questions = []
self.build_mode = False

def questions_array(self):
return self.questions

def set_build_mode(self):
self.build_mode = True

def append(self, question):
self.questions.append(question)

Expand All @@ -98,10 +102,8 @@ def to_json(self):
def set_from_json(self, json_string):
"""
Set Questions from a JSON string. Useful when loading from a
word list or challenge. We will be missing meta info as this
only loads words and alphagram strings.
See Question.to_python for format.
challenge. We will be missing meta info as this only loads
words and alphagram strings.
"""
qs = json.loads(json_string)
Expand Down
2 changes: 1 addition & 1 deletion djAerolith/templates/500.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

{% block content %}

There was a server error of some sort. Please click back and try again :)
There was a server error of some sort. Please click back and try again 😢😳

{% endblock %}

59 changes: 56 additions & 3 deletions djAerolith/wordwalls/challenges.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
DailyChallengeLeaderboard,
DailyChallengeLeaderboardEntry)
from lib.word_db_helper import WordDB, Question, Questions, Alphagram
from lib.macondo_interface import gen_blank_challenges, MacondoError
from lib.macondo_interface import (gen_blank_challenges, gen_build_challenge,
MacondoError)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -59,7 +60,7 @@ def generate_dc_questions(challenge_name, lex, challenge_date):
random.shuffle(alphagrams)
return db.get_questions(alphagrams), challenge_name.timeSecs
elif challenge_name.name == DailyChallengeName.BLANK_BINGOS:
questions = generate_blank_bingos_challenge(lex, challenge_date)
questions = generate_blank_bingos_challenge(lex)
questions.shuffle()
return questions, challenge_name.timeSecs
elif challenge_name.name == DailyChallengeName.BINGO_MARATHON:
Expand All @@ -78,6 +79,17 @@ def generate_dc_questions(challenge_name, lex, challenge_date):
# challenge_name.name)
# random.shuffle(questions)
# return questions, challenge_name.timeSecs

# Try to match to build challenges.
m = re.match(r'Word Builder \((?P<lmin>[0-9]+)-(?P<lmax>[0-9]+)\)',
challenge_name.name)
if m:
lmin = int(m.group('lmin'))
lmax = int(m.group('lmax'))
questions = generate_word_builder_challenge(lex, lmin, lmax)
return questions, challenge_name.timeSecs

# Nothing matched, we done here.
return None


Expand All @@ -91,7 +103,7 @@ def generate_dc_questions(challenge_name, lex, challenge_date):
# return pks[:50]


def generate_blank_bingos_challenge(lex, ch_date):
def generate_blank_bingos_challenge(lex):
"""
Contact blank challenges server and generate said challenges.
Expand All @@ -111,6 +123,47 @@ def generate_blank_bingos_challenge(lex, ch_date):
return bingos


def generate_word_builder_challenge(lex, lmin, lmax):
""" Contact builder server and generate builder challenges. """
# Require the lmax rack to have a word in it some percentage of the time.
min_sols_mu = 0
min_sols_sigma = 0
max_sols_mu = 0
max_sols_sigma = 0

if lmax == 6:
min_sols_mu, min_sols_sigma = 10, 2
max_sols_mu, max_sols_sigma = 30, 5
require_word = random.randint(1, 10) > 2 # 80%
elif lmax == 7:
min_sols_mu, min_sols_sigma = 15, 3
max_sols_mu, max_sols_sigma = 50, 6
require_word = random.randint(1, 10) > 5 # 50%
elif lmax == 8:
min_sols_mu, min_sols_sigma = 20, 4
max_sols_mu, max_sols_sigma = 75, 8
require_word = random.randint(1, 10) > 7 # 30%

min_sols = max(int(random.gauss(min_sols_mu, min_sols_sigma)), 1)
max_sols = min(int(random.gauss(max_sols_mu, max_sols_sigma)), 100)
min_sols, max_sols = min(min_sols, max_sols), max(min_sols, max_sols)
logger.debug('min_sols: %s, max_sols: %s, require_word: %s',
min_sols, max_sols, require_word)
q_struct = Questions()

try:
question, num_answers = gen_build_challenge(
lmin, lmax, lex.lexiconName, require_word, min_sols, max_sols)
except MacondoError:
logger.exception(u'[event=macondoerror]')
return q_struct
ret_question = Question(Alphagram(question['q']), [])
ret_question.set_answers_from_word_list(question['a'])
q_struct.append(ret_question)
q_struct.set_build_mode()
return q_struct


# XXX: This appears to be an expensive function; about 0.75 secs on my
# machine!
def gen_toughies_by_challenge(challenge_name, num, min_date, max_date, lex):
Expand Down
27 changes: 27 additions & 0 deletions djAerolith/wordwalls/fixtures/dcNames.json
Original file line number Diff line number Diff line change
Expand Up @@ -172,5 +172,32 @@
"timeSecs": 360,
"orderPriority": 2
}
},
{
"model": "wordwalls.DailyChallengeName",
"pk": 20,
"fields": {
"name": "Word Builder (3-6)",
"timeSecs": 210,
"orderPriority": 4
}
},
{
"model": "wordwalls.DailyChallengeName",
"pk": 21,
"fields": {
"name": "Word Builder (4-7)",
"timeSecs": 270,
"orderPriority": 4
}
},
{
"model": "wordwalls.DailyChallengeName",
"pk": 22,
"fields": {
"name": "Word Builder (5-8)",
"timeSecs": 300,
"orderPriority": 4
}
}
]
39 changes: 25 additions & 14 deletions djAerolith/wordwalls/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@
from base.forms import SavedListForm
from lib.word_db_helper import WordDB, Questions
from lib.word_searches import word_search
from wordwalls.challenges import generate_dc_questions
from wordwalls.challenges import generate_dc_questions, toughies_challenge_date
from base.models import WordList
from tablegame.models import GenericTableGameModel
from wordwalls.models import WordwallsGameModel
from wordwalls.challenges import toughies_challenge_date
from wordwalls.models import (DailyChallenge, DailyChallengeLeaderboard,
DailyChallengeLeaderboardEntry,
DailyChallengeMissedBingos, DailyChallengeName)
DailyChallengeMissedBingos, DailyChallengeName,
WordwallsGameModel)
logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -105,7 +104,8 @@ def create_or_update_game_instance(self, host, lex, word_list, use_table,

return wgm

def initialize_word_list(self, questions, lexicon, user):
def initialize_word_list(self, questions, lexicon, user,
category=WordList.CATEGORY_ANAGRAM):
"""
Initializes a word list with the given questions and
returns it.
Expand All @@ -116,7 +116,8 @@ def initialize_word_list(self, questions, lexicon, user):
"""
wl = WordList()
wl.initialize_list(questions.to_python(), lexicon, user, shuffle=True)
wl.initialize_list(questions.to_python(), lexicon, user, shuffle=True,
category=category)
return wl

def get_dc(self, ch_date, ch_lex, ch_name):
Expand Down Expand Up @@ -160,7 +161,10 @@ def initialize_daily_challenge(self, user, ch_lex, ch_name, ch_date,
raise GameInitException('Unable to create daily challenge {0}'.
format(ch_name))
qs, secs, dc = ret
wl = self.initialize_word_list(qs, ch_lex, user)
list_category = WordList.CATEGORY_ANAGRAM
if dc.category == DailyChallenge.CATEGORY_BUILD:
list_category = WordList.CATEGORY_BUILD
wl = self.initialize_word_list(qs, ch_lex, user, list_category)
temporary_list_name = '{0} {1} - {2}'.format(
ch_lex.lexiconName,
ch_name.name,
Expand Down Expand Up @@ -193,9 +197,12 @@ def get_or_create_dc(self, ch_date, ch_lex, ch_name):
if qs.size() == 0:
logger.error('Empty questions.')
return None
dc = DailyChallenge(date=ch_date, lexicon=ch_lex,
name=ch_name, seconds=secs,
alphagrams=qs.to_json())
ch_category = DailyChallenge.CATEGORY_ANAGRAM
if qs.build_mode:
ch_category = DailyChallenge.CATEGORY_BUILD
dc = DailyChallenge(date=ch_date, lexicon=ch_lex, name=ch_name,
seconds=secs, alphagrams=qs.to_json(),
category=ch_category)
try:
dc.save()
except IntegrityError:
Expand Down Expand Up @@ -342,9 +349,10 @@ def start_quiz(self, tablenum, user):
qs_set = set(qs)
if len(qs_set) != len(qs):
logger.error("Question set is not unique!!")
orig_questions = json.loads(word_list.origQuestions)

questions, answer_hash = self.load_questions(
qs, json.loads(word_list.origQuestions), word_list.lexicon)
qs, orig_questions, word_list.lexicon)
state['quizGoing'] = True # start quiz
state['quizStartTime'] = time.time()
state['answerHash'] = answer_hash
Expand All @@ -353,9 +361,12 @@ def start_quiz(self, tablenum, user):
wgm.currentGameState = json.dumps(state)
wgm.save()
word_list.save()
game_type = state['gameType']
if word_list.category == WordList.CATEGORY_BUILD:
game_type += '_build' # This is hell of ghetto.
ret = {'questions': questions,
'time': state['timerSecs'],
'gameType': state['gameType'],
'gameType': game_type,
'serverMsg': start_message}

return ret
Expand Down Expand Up @@ -507,7 +518,7 @@ def validate_can_save(self, tablenum, listname, wgm, state):
'been deleted. Please load or create a new list.')

def save(self, user, tablenum, listname):
logger.debug('user=%s, tablenum=%s, listname=%s, event=save',
logger.debug(u'user=%s, tablenum=%s, listname=%s, event=save',
user, tablenum, listname)
wgm = self.get_wgm(tablenum)
if not wgm:
Expand Down Expand Up @@ -598,7 +609,7 @@ def do_quiz_end_actions(self, state, tablenum, wgm):
logger.info("%d missed this round, %d missed total",
len(missed_indices), len(missed))
if state['gameType'] == 'challenge':
state['gameType'] = 'challengeOver'
state['gameType'] = 'regular'
self.create_challenge_leaderboard_entry(state, tablenum)

# check if we've gone thru the quiz once.
Expand Down
Loading

0 comments on commit 59e16a8

Please sign in to comment.