Skip to content

Commit

Permalink
Merge pull request #289 from domino14/on-demand-blanks
Browse files Browse the repository at this point in the history
[WIP] On-demand blank challenges
  • Loading branch information
domino14 authored Sep 14, 2018
2 parents 072aa81 + 3f832b0 commit e5cee57
Show file tree
Hide file tree
Showing 38 changed files with 1,893 additions and 676 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ coverage
__pycache__
.vscode
words
wordsdir
wordsdir
7 changes: 5 additions & 2 deletions djAerolith/base/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from base.utils import (generate_question_map_from_alphagrams,
generate_question_list_from_alphagrams,
question_list_from_probabilities)
from lib.macondo_interface import anagram_letters
from lib.macondo_interface import anagram_letters, MacondoError
from lib.response import response, StatusCode

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -329,5 +329,8 @@ def listmanager(request):
def word_lookup(request):
lexicon = request.GET.get('lexicon')
letters = request.GET.get('letters')
results = anagram_letters(lexicon, letters)
try:
results = anagram_letters(lexicon, letters)
except MacondoError as e:
return response(str(e), StatusCode.BAD_REQUEST)
return response(results)
2 changes: 1 addition & 1 deletion djAerolith/current_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CURRENT_VERSION = '1.1.2.0'
CURRENT_VERSION = '1.1.3.0'
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,36 @@
import React from 'react';
import PropTypes from 'prop-types';

import SearchRows from '../../../../../wordwalls/static/js/wordwalls/newtable/search_rows';
import SearchRows from '../../../../../wordwalls/static/js/wordwalls/newtable/search/rows';
import { SearchTypesEnum,
searchCriterionToAdd } from '../../../../../wordwalls/static/js/wordwalls/newtable/search_row';
searchCriterionToAdd,
SearchCriterion } from '../../../../../wordwalls/static/js/wordwalls/newtable/search/types';

import Select from '../../../../../wordwalls/static/js/wordwalls/forms/select';

const allowedSearchTypes = new Set([
SearchTypesEnum.PROBABILITY,
SearchTypesEnum.LENGTH,
SearchTypesEnum.TAGS,
SearchTypesEnum.POINTS,
SearchTypesEnum.NUM_ANAGRAMS,
SearchTypesEnum.NUM_VOWELS,
]);

class WordSearchForm extends React.Component {
constructor(props) {
super(props);
this.state = {
wordSearchCriteria: [{
searchType: SearchTypesEnum.LENGTH,
minValue: 7,
maxValue: 7,
}, {
searchType: SearchTypesEnum.PROBABILITY,
minValue: 1,
maxValue: 100,
}],
wordSearchCriteria: [
new SearchCriterion(SearchTypesEnum.LENGTH, {
minValue: 7,
maxValue: 7,
}),
new SearchCriterion(SearchTypesEnum.PROBABILITY, {
minValue: 1,
maxValue: 200,
}),
],
lexicon: 'America', // we will build a giant word wall
};

Expand Down Expand Up @@ -56,16 +67,7 @@ class WordSearchForm extends React.Component {

modifySearchParam(index, paramName, paramValue) {
const criteria = this.state.wordSearchCriteria;
const valueModifier = (val) => {
if (paramName === 'minValue' || paramName === 'maxValue') {
return parseInt(val, 10) || 0;
} else if (paramName === 'valueList') {
return val.trim();
}
return val;
};

criteria[index][paramName] = valueModifier(paramValue);
criteria[index].setOption(paramName, paramValue);
this.setState({
wordSearchCriteria: criteria,
});
Expand All @@ -74,11 +76,24 @@ class WordSearchForm extends React.Component {
modifySearchType(index, value) {
const criteria = this.state.wordSearchCriteria;
const searchType = parseInt(value, 10);

criteria[index].searchType = searchType;
// Reset the values.
if (searchType !== SearchTypesEnum.TAGS) {
criteria[index].minValue = SearchTypesEnum.properties[searchType].defaultMin;
criteria[index].maxValue = SearchTypesEnum.properties[searchType].defaultMax;
if ([SearchTypesEnum.LENGTH, SearchTypesEnum.NUM_ANAGRAMS, SearchTypesEnum.NUM_VOWELS,
SearchTypesEnum.POINTS, SearchTypesEnum.PROBABILITY].includes(searchType)) {
// Defaults to two options for this criteria - min/max
criteria[index].setOptions({
minValue: SearchTypesEnum.properties[searchType].defaultMin,
maxValue: SearchTypesEnum.properties[searchType].defaultMax,
});
} else if (searchType === SearchTypesEnum.FIXED_LENGTH) {
criteria[index].setOptions({
value: SearchTypesEnum.properties[searchType].default,
});
} else if (searchType === SearchTypesEnum.TAGS) {
criteria[index].setOptions({
valueList: '',
});
}
this.setState({
wordSearchCriteria: criteria,
Expand All @@ -91,9 +106,7 @@ class WordSearchForm extends React.Component {
* @return {Array.<Object>}
*/
searchCriteriaMapper() {
return this.state.wordSearchCriteria.map(criterion => Object.assign({}, criterion, {
searchType: SearchTypesEnum.properties[criterion.searchType].name,
}));
return this.state.wordSearchCriteria.map(criterion => criterion.toJSObj());
}

render() {
Expand Down Expand Up @@ -129,6 +142,7 @@ class WordSearchForm extends React.Component {
removeSearchRow={this.removeSearchRow}
modifySearchType={this.modifySearchType}
modifySearchParam={this.modifySearchParam}
allowedSearchTypes={allowedSearchTypes}
/>
</div>
</div>
Expand Down
10 changes: 9 additions & 1 deletion djAerolith/lib/word_db_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ def set_from_json(self, json_string):
"""
qs = json.loads(json_string)
self.set_from_list(qs)

def set_from_list(self, qs):
"""
Set Questions from a Python list, that looks like
[{'q': 'ABC', 'a': ['CAB']}, ... ]
"""
self.clear()
for q in qs:
question = Question()
Expand Down Expand Up @@ -192,7 +200,7 @@ def __repr__(self):

def __str__(self):
return '<Question: %s (%s)>' % (self.alphagram,
self.answers)
self.answers)


class WordDB:
Expand Down
25 changes: 20 additions & 5 deletions djAerolith/wordwalls/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ def wrap(request, *args, **kwargs):

parsed_req['questions_per_round'] = body.get('questionsPerRound', 50)
if (parsed_req['questions_per_round'] > 200 or
parsed_req['questions_per_round'] < 15):
parsed_req['questions_per_round'] < 10):
return bad_request(
'Questions per round must be between 15 and 200.')
'Questions per round must be between 10 and 200.')
parsed_req['search_criteria'] = body.get('searchCriteria', [])
parsed_req['list_option'] = body.get('listOption')
parsed_req['selectedList'] = body.get('selectedList')
parsed_req['multiplayer'] = body.get('multiplayer', False)

parsed_req['raw_questions'] = body.get('rawQuestions', False)
return f(request, parsed_req, *args, **kwargs)

return wrap
Expand Down Expand Up @@ -295,6 +295,23 @@ def load_saved_list(request, parsed_req_body):
return table_response(tablenum)


@login_required
@require_POST
@load_new_words
def load_raw_questions(request, parsed_req_body):
try:
tablenum = WordwallsGame().initialize_by_raw_questions(
parsed_req_body['lexicon'], request.user,
parsed_req_body['raw_questions'],
parsed_req_body['quiz_time_secs'],
parsed_req_body['questions_per_round'],
use_table=parsed_req_body['tablenum'],
multiplayer=parsed_req_body['multiplayer'])
except GameInitException as e:
return bad_request(str(e))
return table_response(tablenum)


@login_required
@require_GET
def default_lists(request):
Expand Down Expand Up @@ -379,8 +396,6 @@ def date_from_str(dt):

today = timezone.localtime(timezone.now()).date()
try:
# strptime has multithreading issues on Python 2 and this is
# an occasional error. XXX: Move to Python 3 already.
ch_date = strptime(dt, '%Y-%m-%d').date()
except (ValueError, TypeError):
ch_date = today
Expand Down
2 changes: 2 additions & 0 deletions djAerolith/wordwalls/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
new_search,
load_aerolith_list,
load_saved_list,
load_raw_questions,
)

urlpatterns = [
Expand All @@ -38,6 +39,7 @@
url(r'^new_search/$', new_search),
url(r'^load_aerolith_list/$', load_aerolith_list),
url(r'^load_saved_list/$', load_saved_list),
url(r'^load_raw_questions/$', load_raw_questions),

# url(r'^getNewSignature/$', 'wordwalls.views.get_new_signature',
# name='get_new_signature')
Expand Down
18 changes: 17 additions & 1 deletion djAerolith/wordwalls/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# To contact the author, please email delsolar at gmail dot com

import json
import datetime
import time
import copy
import logging
Expand All @@ -29,7 +30,8 @@
from gargoyle import gargoyle

from base.forms import SavedListForm
from lib.word_db_helper import WordDB, Questions, word_search, BadInput
from lib.word_db_helper import (WordDB, Questions, Question, word_search,
BadInput)
from lib.word_searches import temporary_list_name
from wordwalls.challenges import generate_dc_questions, toughies_challenge_date
from base.models import WordList
Expand Down Expand Up @@ -269,6 +271,20 @@ def initialize_by_search_params(self, user, search_description, time_secs,
questionsToPull=questions_per_round)
return wgm.pk # this is a table number id!

def initialize_by_raw_questions(self, lex, user, raw_questions, secs,
questions_per_round, use_table,
multiplayer):
dt = datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S')
qs = Questions()
qs.set_from_list(raw_questions)

wl = self.initialize_word_list(qs, lex, user)
wgm = self.create_or_update_game_instance(
user, lex, wl, use_table, multiplayer, timerSecs=secs,
temp_list_name='quiz on {}'.format(dt),
questionsToPull=questions_per_round)
return wgm.pk

def initialize_by_named_list(self, lex, user, named_list, secs,
questions_per_round=None, use_table=None,
multiplayer=None):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';

import ChallengeResults from './challenge_results';
import ChallengeResults from './newtable/challenges/challenge_results';
import ModalSkeleton from './modal_skeleton';

const ResultsModal = props => (
Expand Down
9 changes: 7 additions & 2 deletions djAerolith/wordwalls/static/js/wordwalls/forms/text_input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ const TextInput = (props) => {
);
};

TextInput.defaultProps = {
onKeyPress: () => {},
maxLength: 100,
};

TextInput.propTypes = {
colSize: PropTypes.number.isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
maxLength: PropTypes.number.isRequired,
maxLength: PropTypes.number,
onChange: PropTypes.func.isRequired,
onKeyPress: PropTypes.func.isRequired,
onKeyPress: PropTypes.func,
};

export default TextInput;
49 changes: 49 additions & 0 deletions djAerolith/wordwalls/static/js/wordwalls/generic_rpc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import WordwallsAPI from './wordwalls_api';

function uniqueId() {
return Math.random().toString(36).substring(2)
+ (new Date()).getTime().toString(36);
}

class GenericRPC extends WordwallsAPI {
/**
* Generate a JSON RPC data packet.
*/
constructor(rpcURL) {
super();
this.RPCURL = rpcURL || null;
}

setRPCURL(url) {
this.RPCURL = url;
}

fetchdata(method, params) {
return {
...this.fetchInit,
body: JSON.stringify({
id: uniqueId(),
jsonrpc: '2.0',
method,
params,
}),
};
}

async rpcwrap(method, params) {
if (!this.RPCURL) {
await Promise.reject(new Error('No RPC URL was set.'));
}
// eslint-disable-next-line compat/compat
const response = await fetch(this.RPCURL, this.fetchdata(method, params));
const data = await response.json();
if (response.ok && data.result) {
// Use the `result` key - since this is JSONRPC
return data.result;
}
// Otherwise, there's an error.
throw new Error(data.error.message);
}
}

export default GenericRPC;
Loading

0 comments on commit e5cee57

Please sign in to comment.