Initial commit. Basic drawing of non-moving dancers implemented.
This commit is contained in:
commit
6fb6000bd4
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
www/js/*.js
|
3
external/README.md
vendored
Normal file
3
external/README.md
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
libfigure from
|
||||
https://github.com/contradb/contra/tree/master/app/javascript/libfigure
|
||||
is licensed under AGPLv3.0+
|
21
external/libfigure/after-figure.js
vendored
Normal file
21
external/libfigure/after-figure.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
// _ _____ _____ _____ ____ _____ ___ ____ _ _ ____ _____ _
|
||||
// / \ | ___|_ _| ____| _ \ | ___|_ _/ ___| | | | _ \| ____| (_)___
|
||||
// / _ \ | |_ | | | _| | |_) |____| |_ | | | _| | | | |_) | _| | / __|
|
||||
// / ___ \| _| | | | |___| _ <_____| _| | | |_| | |_| | _ <| |___ _ | \__ \
|
||||
// /_/ \_\_| |_| |_____|_| \_\ |_| |___\____|\___/|_| \_\_____(_)/ |___/
|
||||
// |__/
|
||||
|
||||
import { moves, parameters, defineRelatedMove2Way } from "./define-figure"
|
||||
import { param } from "./param"
|
||||
|
||||
// all moves with a balance are related to the move 'balance'
|
||||
moves().forEach(function(move) {
|
||||
parameters(move).forEach(function(the_param) {
|
||||
if (
|
||||
the_param.name === param("balance_true").name ||
|
||||
the_param.name === param("balance_false").name
|
||||
) {
|
||||
defineRelatedMove2Way(move, "balance")
|
||||
}
|
||||
})
|
||||
})
|
205
external/libfigure/chooser.js
vendored
Normal file
205
external/libfigure/chooser.js
vendored
Normal file
|
@ -0,0 +1,205 @@
|
|||
// ____ _ _ ___ ___ ____ _____ ____
|
||||
// / ___| | | |/ _ \ / _ \/ ___|| ____| _ \
|
||||
// | | | |_| | | | | | | \___ \| _| | |_) |
|
||||
// | |___| _ | |_| | |_| |___) | |___| _ <
|
||||
// \____|_| |_|\___/ \___/|____/|_____|_| \_\
|
||||
//
|
||||
//
|
||||
// Choosers are UI elements without semantic ties to other choosers,
|
||||
// who are given semantic ties by figures.
|
||||
// So a figure could have two chooser_booleans
|
||||
// or two chooser_dancers (e.g. GENTS roll away the NEIGHBORS)
|
||||
// Choosers are obtained by calling, for example, `chooser('chooser_boolean')`
|
||||
// You can read back a chooser's name property:
|
||||
// `chooser('chooser_boolean').name => 'chooser_boolean'`
|
||||
// Choosers can be compared with ===
|
||||
|
||||
var defined_choosers = {}
|
||||
|
||||
function defineChooser(name) {
|
||||
"string" == typeof name || throw_up("first argument isn't a string")
|
||||
"chooser_" == name.slice(0, 8) ||
|
||||
throw_up("first argument doesn't begin with 'chooser_'")
|
||||
defined_choosers[name] = { name: name }
|
||||
}
|
||||
|
||||
export const chooser = name => {
|
||||
const chooser = defined_choosers[name]
|
||||
if (chooser) {
|
||||
return chooser
|
||||
} else {
|
||||
throw_up(JSON.stringify(name) + " is not the name of a chooser")
|
||||
}
|
||||
}
|
||||
|
||||
defineChooser("chooser_boolean")
|
||||
defineChooser("chooser_beats")
|
||||
defineChooser("chooser_spin")
|
||||
defineChooser("chooser_left_right_spin")
|
||||
defineChooser("chooser_right_left_hand")
|
||||
defineChooser("chooser_right_left_shoulder")
|
||||
defineChooser("chooser_revolutions")
|
||||
defineChooser("chooser_places")
|
||||
defineChooser("chooser_text")
|
||||
defineChooser("chooser_star_grip")
|
||||
defineChooser("chooser_march_facing")
|
||||
defineChooser("chooser_slide")
|
||||
defineChooser("chooser_set_direction")
|
||||
defineChooser("chooser_set_direction_acrossish")
|
||||
defineChooser("chooser_set_direction_grid")
|
||||
defineChooser("chooser_set_direction_figure_8")
|
||||
defineChooser("chooser_gate_direction")
|
||||
defineChooser("chooser_slice_return")
|
||||
defineChooser("chooser_slice_increment")
|
||||
defineChooser("chooser_down_the_hall_ender")
|
||||
defineChooser("chooser_all_or_center_or_outsides")
|
||||
defineChooser("chooser_zig_zag_ender")
|
||||
defineChooser("chooser_go_back")
|
||||
defineChooser("chooser_give")
|
||||
defineChooser("chooser_half_or_full")
|
||||
defineChooser("chooser_swing_prefix")
|
||||
defineChooser("chooser_hey_length")
|
||||
|
||||
var _dancerMenuForChooser = {}
|
||||
|
||||
function defineDancerChooser(name, dancers) {
|
||||
defineChooser(name)
|
||||
_dancerMenuForChooser[name] = dancers
|
||||
}
|
||||
|
||||
export const dancerMenuForChooser = function(chooser) {
|
||||
// chooser object
|
||||
return _dancerMenuForChooser[chooser.name]
|
||||
}
|
||||
|
||||
export const dancerCategoryMenuForChooser = function(chooser) {
|
||||
return libfigureUniq(dancerMenuForChooser(chooser).map(dancersCategory))
|
||||
}
|
||||
|
||||
export const dancerChooserNames = function() {
|
||||
return Object.keys(_dancerMenuForChooser)
|
||||
}
|
||||
|
||||
var outOfSetDancers = [
|
||||
"shadows",
|
||||
"2nd shadows",
|
||||
"prev neighbors",
|
||||
"next neighbors",
|
||||
"3rd neighbors",
|
||||
"4th neighbors",
|
||||
]
|
||||
|
||||
defineDancerChooser(
|
||||
"chooser_dancers", // some collection of dancers
|
||||
[
|
||||
"everyone",
|
||||
"gentlespoon",
|
||||
"gentlespoons",
|
||||
"ladle",
|
||||
"ladles",
|
||||
"partners",
|
||||
"neighbors",
|
||||
"ones",
|
||||
"twos",
|
||||
"same roles",
|
||||
"first corners",
|
||||
"second corners",
|
||||
"first gentlespoon",
|
||||
"first ladle",
|
||||
"second gentlespoon",
|
||||
"second ladle",
|
||||
].concat(outOfSetDancers)
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pair", // 1 pair of dancers
|
||||
["gentlespoons", "ladles", "ones", "twos", "first corners", "second corners"]
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pair_or_everyone", // 1 pair or everyone
|
||||
["everyone"].concat(dancerMenuForChooser(chooser("chooser_pair")))
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pairc_or_everyone", // 1 pair or centers or everyone
|
||||
[
|
||||
"everyone",
|
||||
"gentlespoons",
|
||||
"ladles",
|
||||
"centers",
|
||||
"ones",
|
||||
"twos",
|
||||
// intentionally omitting 'first corners' and 'second corners', because 'centers' is clearer
|
||||
]
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pairz", // 1-2 pairs of dancers
|
||||
[
|
||||
"gentlespoons",
|
||||
"ladles",
|
||||
"partners",
|
||||
"neighbors",
|
||||
"ones",
|
||||
"twos",
|
||||
"same roles",
|
||||
"first corners",
|
||||
"second corners",
|
||||
].concat(outOfSetDancers)
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pairz_or_unspecified",
|
||||
[""].concat(dancerMenuForChooser(chooser("chooser_pairz")))
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pairs", // 2 pairs of dancers
|
||||
["partners", "neighbors", "same roles"].concat(outOfSetDancers)
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pairs_or_ones_or_twos",
|
||||
["partners", "neighbors", "same roles", "ones", "twos"].concat(
|
||||
outOfSetDancers
|
||||
)
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_pairs_or_everyone",
|
||||
["everyone"].concat(dancerMenuForChooser(chooser("chooser_pairs")))
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_dancer", // one dancer
|
||||
["first gentlespoon", "first ladle", "second gentlespoon", "second ladle"]
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_role", // ladles or gentlespoons
|
||||
["gentlespoons", "ladles"]
|
||||
)
|
||||
defineDancerChooser(
|
||||
"chooser_hetero", // partners or neighbors or shadows but not same-role
|
||||
["partners", "neighbors"].concat(outOfSetDancers)
|
||||
)
|
||||
|
||||
// nb: this hash is also accessed from ruby.
|
||||
var dancersCategoryHash = {
|
||||
// '1st shadows': 'shadows', // not sure if this needs to be included or not - for now: no
|
||||
"2nd shadows": "shadows",
|
||||
"prev neighbors": "neighbors",
|
||||
"next neighbors": "neighbors",
|
||||
// '2nd neighbors': 'neighbors', // not sure if this needs to be included or not - for now: no
|
||||
"3rd neighbors": "neighbors",
|
||||
"4th neighbors": "neighbors",
|
||||
}
|
||||
|
||||
function dancersCategory(chooser) {
|
||||
return dancersCategoryHash[chooser] || chooser
|
||||
}
|
||||
|
||||
export const dancers = function() {
|
||||
return dancerMenuForChooser(chooser("chooser_dancers"))
|
||||
}
|
||||
|
||||
var heyLengthMenu = (function() {
|
||||
var pairz = dancerMenuForChooser(chooser("chooser_pairz"))
|
||||
var acc = ["full", "half"]
|
||||
for (var i = 0; i < pairz.length; i++) {
|
||||
acc.push(pairz[i] + "%%1")
|
||||
acc.push(pairz[i] + "%%2")
|
||||
}
|
||||
return acc
|
||||
})()
|
46
external/libfigure/dance.js
vendored
Normal file
46
external/libfigure/dance.js
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
var invertPairHash = {
|
||||
ladles: "gentlespoons",
|
||||
gentlespoons: "ladles",
|
||||
ones: "twos",
|
||||
twos: "ones",
|
||||
"first corners": "second corners",
|
||||
"second corners": "first corners",
|
||||
"*": "*",
|
||||
}
|
||||
// If this names 2 dancers, this returns the names for the other 2 dancers
|
||||
// it's sketchy, because it assumes 4 dancers, so only use it in contra moves.
|
||||
// Formerly known as dancers complement.
|
||||
function invertPair(whostr, dialect) {
|
||||
if (!whostr) {
|
||||
return "others"
|
||||
} // undefined is ok
|
||||
var dancers = invertPairHash[whostr]
|
||||
if (dancers) {
|
||||
return dancerSubstitution(dancers, dialect)
|
||||
} else {
|
||||
throw_up("bogus parameter to invertPairHash: " + whostr)
|
||||
}
|
||||
}
|
||||
|
||||
function labelForBeats(beats) {
|
||||
if (beats % 16 == 0)
|
||||
switch (beats / 16) {
|
||||
case 0:
|
||||
return "A1"
|
||||
case 1:
|
||||
return "A2"
|
||||
case 2:
|
||||
return "B1"
|
||||
case 3:
|
||||
return "B2"
|
||||
case 4:
|
||||
return "C1"
|
||||
case 5:
|
||||
return "C2"
|
||||
case 6:
|
||||
return "D1"
|
||||
case 7:
|
||||
return "D2"
|
||||
}
|
||||
return ""
|
||||
}
|
583
external/libfigure/define-figure.js
vendored
Normal file
583
external/libfigure/define-figure.js
vendored
Normal file
|
@ -0,0 +1,583 @@
|
|||
// ____ _____ _____ ___ _ _ _____ _____ ___ ____ _ _ ____ _____
|
||||
// | _ \| ____| ___|_ _| \ | | ____| | ___|_ _/ ___| | | | _ \| ____|
|
||||
// | | | | _| | |_ | || \| | _| _____| |_ | | | _| | | | |_) | _|
|
||||
// | |_| | |___| _| | || |\ | |__|_____| _| | | |_| | |_| | _ <| |___
|
||||
// |____/|_____|_| |___|_| \_|_____| |_| |___\____|\___/|_| \_\_____|
|
||||
//
|
||||
// language construct for defining dance moves
|
||||
// and related support functions for dealing with figures
|
||||
|
||||
import { libfigureObjectCopy, throw_up } from "./util"
|
||||
|
||||
// always freshly allocated
|
||||
function newFigure(optional_progression) {
|
||||
var m = { move: "stand still", parameter_values: [8] }
|
||||
if (optional_progression) {
|
||||
m.progression = 1
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
function figureBeats(f) {
|
||||
var defaultBeats = 8
|
||||
if (!f.move) return defaultBeats
|
||||
var idx = find_parameter_index_by_name("beats", parameters(f.move))
|
||||
return idx < 0 ? defaultBeats : f.parameter_values[idx]
|
||||
}
|
||||
|
||||
function sumBeats(figures, optional_limit) {
|
||||
var acc = 0
|
||||
var n = Number.isInteger(optional_limit) ? optional_limit : figures.length
|
||||
for (var i = 0; i < n; i++) {
|
||||
acc += figureBeats(figures[i])
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
export const figureToHtml = (f, dialect) => {
|
||||
return figureFlatten(f, dialect, FLATTEN_FORMAT_HTML)
|
||||
}
|
||||
|
||||
function figureToUnsafeText(f, dialect) {
|
||||
return figureFlatten(f, dialect, FLATTEN_FORMAT_UNSAFE_TEXT)
|
||||
}
|
||||
|
||||
function figureToSafeText(f, dialect) {
|
||||
return figureFlatten(f, dialect, FLATTEN_FORMAT_SAFE_TEXT)
|
||||
}
|
||||
|
||||
function figureFlatten(f, dialect, flatten_format) {
|
||||
var fig_def = defined_events[f.move]
|
||||
if (fig_def) {
|
||||
var func = fig_def.props.words || figureGenericWords
|
||||
var main = func(alias(f), f.parameter_values, dialect)
|
||||
var note = f.note
|
||||
var pilcrow = f.progression ? "⁋" : false
|
||||
if (note && note.trim()) {
|
||||
var fancy_note = lingoLineWords(stringInDialect(note, dialect), dialect)
|
||||
return words(main, fancy_note, pilcrow).flatten(flatten_format)
|
||||
} else {
|
||||
return words(main, pilcrow).flatten(flatten_format)
|
||||
}
|
||||
} else if (f.move) {
|
||||
return "undefined figure '" + words(f.move).flatten(flatten_format) + "'!"
|
||||
} else {
|
||||
return "empty figure"
|
||||
}
|
||||
}
|
||||
|
||||
// Called if they don't specify a Words function in the figure definition:
|
||||
function figureGenericWords(move, parameter_values, dialect) {
|
||||
var ps = parameters(move)
|
||||
var pwords = parameter_words(move, parameter_values, dialect)
|
||||
var acc = []
|
||||
var subject_index = find_parameter_index_by_name("who", ps)
|
||||
var balance_index = find_parameter_index_by_name("bal", ps)
|
||||
var beats_index = find_parameter_index_by_name("beats", ps)
|
||||
if (subject_index >= 0) {
|
||||
acc.push(pwords[subject_index])
|
||||
}
|
||||
if (balance_index >= 0) {
|
||||
acc.push(pwords[balance_index])
|
||||
}
|
||||
acc.push(moveSubstitution(move, dialect))
|
||||
ps.length == parameter_values.length ||
|
||||
throw_up(
|
||||
"parameter type mismatch. " +
|
||||
ps.length +
|
||||
" formals and " +
|
||||
parameter_values.length +
|
||||
" values"
|
||||
)
|
||||
for (var i = 0; i < parameter_values.length; i++) {
|
||||
if (i != subject_index && i != balance_index && i != beats_index) {
|
||||
acc.push(pwords[i])
|
||||
}
|
||||
}
|
||||
return new Words(acc)
|
||||
}
|
||||
|
||||
function find_parameter_index_by_name(name, parameters) {
|
||||
var match_name_fn = function(p) {
|
||||
return p.name === name
|
||||
}
|
||||
return parameters.findIndex(match_name_fn, parameters)
|
||||
}
|
||||
|
||||
// ================
|
||||
|
||||
function parameter_strings(move, parameter_values, dialect) {
|
||||
return parameter_strings_or_words(move, parameter_values, dialect, false)
|
||||
}
|
||||
|
||||
function parameter_words(move, parameter_values, dialect) {
|
||||
return parameter_strings_or_words(move, parameter_values, dialect, true)
|
||||
}
|
||||
|
||||
function parameter_strings_or_words(move, parameter_values, dialect, words_ok) {
|
||||
var formal_parameters = parameters(move)
|
||||
var acc = []
|
||||
for (var i = 0; i < parameter_values.length; i++) {
|
||||
var pvi = parameter_values[i]
|
||||
var term
|
||||
if (pvi === undefined || pvi === null) {
|
||||
term = "____"
|
||||
} else if (formal_parameters[i].words && words_ok) {
|
||||
// caller wants special html-enabled return type, and we support it, e.g. Custom
|
||||
term = formal_parameters[i].words(pvi, move, dialect)
|
||||
} else if (formal_parameters[i].string) {
|
||||
term = formal_parameters[i].string(pvi, move, dialect)
|
||||
} else {
|
||||
term = String(pvi)
|
||||
}
|
||||
acc.push(parameterSubstitution(formal_parameters[i], term, dialect))
|
||||
}
|
||||
return acc
|
||||
}
|
||||
|
||||
// called when we don't know if the parameter is a dancer
|
||||
function parameterSubstitution(formal_parameter, actual_parameter, dialect) {
|
||||
var term = actual_parameter
|
||||
return (
|
||||
(formalParamIsDancers(formal_parameter) && dialect.dancers[term]) ||
|
||||
actual_parameter
|
||||
)
|
||||
}
|
||||
|
||||
// called when we do know the parameter is a dancer
|
||||
function dancerSubstitution(dancer_term, dialect) {
|
||||
return dialect.dancers[dancer_term] || dancer_term
|
||||
}
|
||||
|
||||
export const dancerMenuLabel = function(dancer_term, dialect) {
|
||||
if (dancer_term) {
|
||||
return dancerSubstitution(dancer_term, dialect)
|
||||
} else {
|
||||
return "unspecified"
|
||||
}
|
||||
}
|
||||
|
||||
function heyLengthSubstitution(hey_length, dialect) {
|
||||
var hey_arr = parseHeyLength(hey_length)
|
||||
var hey0 = hey_arr[0]
|
||||
if (hey0 === "full" || hey0 === "half") {
|
||||
return hey0
|
||||
} else {
|
||||
hey_arr[1] === 1 ||
|
||||
hey_arr[1] === 2 ||
|
||||
throw_up("parseHeyLength()s second value is not 1 or 2: " + hey_arr[1])
|
||||
var nth_time = hey_arr[1] === 2 ? " 2nd time" : ""
|
||||
return dancerSubstitution(hey0, dialect) + " meet" + nth_time
|
||||
}
|
||||
}
|
||||
|
||||
var moveSubstitutionPercentSRegexp = / *%S */g
|
||||
|
||||
function moveSubstitution(move_term, dialect) {
|
||||
var sub = moveSubstitutionWithEscape(move_term, dialect)
|
||||
return sub.replace(moveSubstitutionPercentSRegexp, " ").trim()
|
||||
}
|
||||
|
||||
function moveSubstitutionWithEscape(move_term, dialect) {
|
||||
return dialect.moves[move_term] || move_term
|
||||
}
|
||||
|
||||
// The basic applicaiton is a user substitution from 'form an ocean
|
||||
// wave' to 'form a short wave' and makes it possible to extract the phrases
|
||||
// 'a short wave' and 'short wave'.
|
||||
//
|
||||
// This takes a substitution that might be 'form a blahblah' and
|
||||
// returns either 'a blahblah' or 'blahblah', depending on the
|
||||
// optional add_article argument.
|
||||
//
|
||||
// Oh hey, the word 'form' and the word 'a' are both entered by the
|
||||
// user, and so are optional.
|
||||
// Check the specs for lots of examples.
|
||||
function moveSubstitutionWithoutForm(
|
||||
move_term,
|
||||
dialect,
|
||||
add_article,
|
||||
adjectives
|
||||
) {
|
||||
if (undefined === add_article) {
|
||||
add_article = false
|
||||
}
|
||||
if (undefined === adjectives) {
|
||||
adjectives = false
|
||||
}
|
||||
var subst = moveSubstitution(move_term, dialect)
|
||||
var match = subst.match(/(?:form )?(?:(an?) )?(.*)/i)
|
||||
var root = match[2]
|
||||
var adjectives_and_root = words(adjectives, root)
|
||||
if (add_article) {
|
||||
var article = /[aeiou]/.test(adjectives_and_root.peek()) ? "an" : "a"
|
||||
return words(article, adjectives, root)
|
||||
} else {
|
||||
return adjectives_and_root
|
||||
}
|
||||
}
|
||||
|
||||
// === Related Moves =============
|
||||
// Note that a lot of these are 'is composed of' relationships, and as such they
|
||||
// might be moved to another representation later.
|
||||
|
||||
var _relatedMoves = {}
|
||||
|
||||
function defineRelatedMove1Way(from, to) {
|
||||
_relatedMoves[from] = _relatedMoves[from] || []
|
||||
_relatedMoves[from].push(to)
|
||||
}
|
||||
|
||||
export const defineRelatedMove2Way = (from, to) => {
|
||||
defineRelatedMove1Way(from, to)
|
||||
defineRelatedMove1Way(to, from)
|
||||
}
|
||||
|
||||
function relatedMoves(move) {
|
||||
return _relatedMoves[move] || []
|
||||
}
|
||||
|
||||
var defined_events = {}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// defineFigure //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
export const defineFigure = (name, parameters, props) => {
|
||||
var props2 = libfigureObjectCopy(props || {})
|
||||
if (!props2.goodBeats) {
|
||||
var beats_index = find_parameter_index_by_name("beats", parameters)
|
||||
var beats_default = beats_index >= 0 && parameters[beats_index].value
|
||||
if (beats_default || beats_default === 0) {
|
||||
props2.goodBeats = goodBeatsEqualFn(beats_default)
|
||||
}
|
||||
}
|
||||
defined_events[name] = { name: name, parameters: parameters, props: props2 }
|
||||
}
|
||||
|
||||
export const defineFigureAlias = (
|
||||
alias_name,
|
||||
canonical_name,
|
||||
parameter_defaults
|
||||
) => {
|
||||
"string" == typeof alias_name || throw_up("first argument isn't a string")
|
||||
"string" == typeof canonical_name ||
|
||||
throw_up("second argument isn't a string")
|
||||
Array.isArray(parameter_defaults) ||
|
||||
throw_up("third argument isn't an array aliasing " + alias_name)
|
||||
var target =
|
||||
defined_events[canonical_name] ||
|
||||
throw_up(
|
||||
"undefined figure alias '" + alias_name + "' to '" + canonical_name + "'"
|
||||
)
|
||||
if (target.parameters.length !== parameter_defaults.length) {
|
||||
throw_up("wrong number of parameters to " + alias_name)
|
||||
}
|
||||
// defensively copy parameter_defaults[...]{...} into params
|
||||
var params = new Array(target.parameters.length)
|
||||
for (var i = 0; i < target.parameters.length; i++) {
|
||||
params[i] = parameter_defaults[i] || target.parameters[i]
|
||||
}
|
||||
defined_events[alias_name] = {
|
||||
name: canonical_name,
|
||||
parameters: params,
|
||||
alias_parameters: parameter_defaults,
|
||||
props: target.props,
|
||||
}
|
||||
}
|
||||
|
||||
export const deAliasMove = function(move) {
|
||||
return defined_events[move].name
|
||||
}
|
||||
|
||||
export const isAlias = function(move_string) {
|
||||
return defined_events[move_string].name !== move_string
|
||||
}
|
||||
|
||||
// you can also use 'figure.move' in js - this is a late addition because we need a function -dm 04-20-2018
|
||||
function move(figure) {
|
||||
return figure.move
|
||||
}
|
||||
|
||||
function alias(figure) {
|
||||
var fn = moveProp(figure.move, "alias", move)
|
||||
return fn(figure)
|
||||
}
|
||||
|
||||
// does not include itself
|
||||
function aliases(move) {
|
||||
// loop through defined_events, returning all keys where value.name == move
|
||||
var acc = []
|
||||
Object.keys(defined_events).forEach(function(key) {
|
||||
var value = defined_events[key]
|
||||
if (value.name == move && key != move) {
|
||||
acc.push(key)
|
||||
}
|
||||
})
|
||||
return acc
|
||||
}
|
||||
|
||||
export const aliasFilter = function(move_alias_string) {
|
||||
if (move_alias_string === deAliasMove(move_alias_string)) {
|
||||
throw_up(
|
||||
"aliasFilter(someDeAliasedMove) would produce weirdly overly specific filters if we weren't raising this error - it's only defined for move aliases"
|
||||
)
|
||||
}
|
||||
return aliasParameters(move_alias_string).map(function(param) {
|
||||
return param ? param.value : "*"
|
||||
})
|
||||
}
|
||||
|
||||
// List all the moves known to contradb.
|
||||
// See also: moveTermsAndSubstitutions
|
||||
export const moves = () => {
|
||||
return Object.keys(defined_events)
|
||||
}
|
||||
|
||||
function moveTermsAndSubstitutions(dialect) {
|
||||
if (!dialect) {
|
||||
throw_up("must specify dialect to moveTermsAndSubstitutions")
|
||||
}
|
||||
var terms = Object.keys(defined_events)
|
||||
var ms = terms.map(function(term) {
|
||||
return { term: term, substitution: moveSubstitution(term, dialect) }
|
||||
})
|
||||
ms = ms.sort(function(a, b) {
|
||||
var aa = a.substitution.toLowerCase()
|
||||
var bb = b.substitution.toLowerCase()
|
||||
if (aa < bb) {
|
||||
return -1
|
||||
} else if (aa > bb) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
return ms
|
||||
}
|
||||
|
||||
export const moveTermsAndSubstitutionsForSelectMenu = dialect => {
|
||||
if (!dialect) {
|
||||
throw_up("must specify dialect to moveTermsAndSubstitutionsForSelectMenu")
|
||||
}
|
||||
var mtas = moveTermsAndSubstitutions(dialect)
|
||||
var swing_index = mtas.findIndex(function(e) {
|
||||
return "swing" === e.term
|
||||
})
|
||||
if (swing_index >= 5) {
|
||||
mtas.unshift(mtas[swing_index]) // copy swing to front of the list
|
||||
}
|
||||
return mtas
|
||||
}
|
||||
|
||||
function isMove(string) {
|
||||
return !!defined_events[string]
|
||||
}
|
||||
|
||||
export const parameterLabel = (move, index) => {
|
||||
var fig_def = defined_events[move]
|
||||
var ps = parameters(move)
|
||||
return (
|
||||
(fig_def &&
|
||||
fig_def.props &&
|
||||
fig_def.props.labels &&
|
||||
fig_def.props.labels[index]) ||
|
||||
(ps[index] && ps[index].name)
|
||||
)
|
||||
}
|
||||
|
||||
var issued_parameter_warning = false
|
||||
|
||||
// TODO: complete renaming to formalParameters
|
||||
export const parameters = move => {
|
||||
var fig = defined_events[move]
|
||||
if (fig) {
|
||||
return fig.parameters
|
||||
}
|
||||
if (move && !issued_parameter_warning) {
|
||||
issued_parameter_warning = true
|
||||
throw_up("could not find a figure definition for '" + move + "'. ")
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
export const formalParameters = parameters
|
||||
|
||||
function aliasParameters(move) {
|
||||
var fig = defined_events[move]
|
||||
if (fig && fig.alias_parameters) {
|
||||
return fig.alias_parameters
|
||||
} else {
|
||||
throw_up(
|
||||
"call to aliasParameters('" +
|
||||
move +
|
||||
"') on a thing that doesn't seem to be an alias"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function moveProp(move_or_nil, property_name, default_value) {
|
||||
if (move_or_nil) {
|
||||
var fig_def = defined_events[move_or_nil]
|
||||
return (fig_def && fig_def.props[property_name]) || default_value
|
||||
} else {
|
||||
return default_value
|
||||
}
|
||||
}
|
||||
|
||||
export const stringInDialect = (str, dialect) => {
|
||||
if (textInDialect(dialect)) {
|
||||
return str
|
||||
} else {
|
||||
// Since this is called a lot, performance might be helped by memoizing dialectRegExp(dialect)
|
||||
return str.replace(dialectRegExp(dialect), function(
|
||||
_whole_match,
|
||||
left_whitespace,
|
||||
match
|
||||
) {
|
||||
// conceptually this is a pretty straightforward lookup in one hash or another, and usually that's all it takes...
|
||||
var substitution = dialect.moves[match] || dialect.dancers[match]
|
||||
if (substitution) {
|
||||
return stringInDialectHelper(
|
||||
match,
|
||||
substitution,
|
||||
left_whitespace,
|
||||
match
|
||||
)
|
||||
}
|
||||
// ...but sometimes they've uppercased their text, so we've gotta look again in the hash (in lower case)...
|
||||
var match_lower = match.toLowerCase()
|
||||
substitution = dialect.moves[match_lower] || dialect.dancers[match_lower]
|
||||
if (substitution) {
|
||||
return stringInDialectHelper(
|
||||
match_lower,
|
||||
substitution,
|
||||
left_whitespace,
|
||||
match
|
||||
)
|
||||
}
|
||||
// ...and sometimes the term itself is in mixed case, e.g. 'California twirl' or "Rory O'More"...
|
||||
var term
|
||||
for (term in dialect.moves) {
|
||||
if (term.toLowerCase() === match_lower) {
|
||||
return stringInDialectHelper(
|
||||
term,
|
||||
dialect.moves[term],
|
||||
left_whitespace,
|
||||
match
|
||||
)
|
||||
}
|
||||
}
|
||||
for (term in dialect.dancers) {
|
||||
if (term.toLowerCase() === match_lower) {
|
||||
return stringInDialectHelper(
|
||||
term,
|
||||
dialect.dancers[term],
|
||||
left_whitespace,
|
||||
match
|
||||
)
|
||||
}
|
||||
}
|
||||
/// ... I guess it's possible none of this worked, which is weird enough to explode.
|
||||
throw_up("failed to look up " + match)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// polish the case and remove %S in the final substitution
|
||||
function stringInDialectHelper(term, substitution, left_whitespace, match) {
|
||||
var s
|
||||
if (hasUpperCase(substitution)) {
|
||||
s = substitution
|
||||
} else if (moreCapitalizedThan(match, term)) {
|
||||
s = capitalize(substitution)
|
||||
} else {
|
||||
s = substitution
|
||||
}
|
||||
return left_whitespace + s.replace(/%S/g, "")
|
||||
}
|
||||
|
||||
function hasUpperCase(x) {
|
||||
return x !== x.toLowerCase()
|
||||
}
|
||||
|
||||
function moreCapitalizedThan(left, right) {
|
||||
return hasUpperCase(left) && !hasUpperCase(right)
|
||||
}
|
||||
|
||||
// Return a new string with the first lower case letter (if any) capitalized
|
||||
function capitalize(s) {
|
||||
// ugh, unicode is hard in JS
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
var c = s[i]
|
||||
var c_big = c.toUpperCase()
|
||||
if (c_big !== c) {
|
||||
var x = s.split("")
|
||||
x.splice(i, 1, c_big)
|
||||
return x.join("")
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
function dialectRegExp(dialect) {
|
||||
var move_strings = Object.keys(dialect.moves)
|
||||
var dance_strings = Object.keys(dialect.dancers)
|
||||
var term_strings = move_strings.concat(dance_strings).sort(longestFirstSortFn)
|
||||
var big_re_string_center = term_strings.map(regExpEscape).join("|")
|
||||
var big_re_string
|
||||
if (big_re_string_center) {
|
||||
big_re_string =
|
||||
"(\\s|" +
|
||||
PUNCTUATION_CHARSET_STRING +
|
||||
"|^)(" +
|
||||
big_re_string_center +
|
||||
")(?=\\s|" +
|
||||
PUNCTUATION_CHARSET_STRING +
|
||||
"|$)"
|
||||
} else {
|
||||
big_re_string = "^[]" // unmatchable regexp - https://stackoverflow.com/a/25315586/5158597
|
||||
}
|
||||
return new RegExp(big_re_string, "gi")
|
||||
}
|
||||
|
||||
////
|
||||
|
||||
function goodBeatsWithContext(figures, index) {
|
||||
var figure = figures[index]
|
||||
if (
|
||||
0 !==
|
||||
sumBeats(figures, index + 1) % moveProp(figure.move, "alignBeatsEnd", 1)
|
||||
) {
|
||||
return false
|
||||
} else {
|
||||
return goodBeats(figure)
|
||||
}
|
||||
}
|
||||
|
||||
function goodBeats(figure) {
|
||||
var fn = moveProp(figure.move, "goodBeats", defaultGoodBeats)
|
||||
return fn(figure)
|
||||
}
|
||||
|
||||
export const goodBeatsMinMaxFn = (min, max) => {
|
||||
return function(figure) {
|
||||
var beats = figureBeats(figure)
|
||||
return min <= beats && beats <= max
|
||||
}
|
||||
}
|
||||
|
||||
export const goodBeatsMinFn = min => {
|
||||
return function(figure) {
|
||||
var beats = figureBeats(figure)
|
||||
return min <= beats
|
||||
}
|
||||
}
|
||||
|
||||
export const goodBeatsEqualFn = beats => {
|
||||
return function(figure) {
|
||||
return beats === figureBeats(figure)
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultGoodBeats = goodBeatsEqualFn(8)
|
1927
external/libfigure/figure.js
vendored
Normal file
1927
external/libfigure/figure.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
46
external/libfigure/libfigure.js
vendored
Normal file
46
external/libfigure/libfigure.js
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
// repackage exports in a single file, so that LibFigure consumers
|
||||
// don't have to worry about what specific file a variable is defined
|
||||
// in.
|
||||
|
||||
import { defaultDialect } from "./util.js"
|
||||
import { param, wristGrips } from "./param.js"
|
||||
import { degreesToWords, anglesForMove } from "./move.js"
|
||||
import {
|
||||
chooser,
|
||||
dancerMenuForChooser,
|
||||
dancerCategoryMenuForChooser,
|
||||
dancerChooserNames,
|
||||
} from "./chooser.js"
|
||||
import {
|
||||
aliasFilter,
|
||||
isAlias,
|
||||
deAliasMove,
|
||||
moves,
|
||||
moveTermsAndSubstitutionsForSelectMenu,
|
||||
formalParameters,
|
||||
parameterLabel,
|
||||
dancerMenuLabel,
|
||||
} from "./define-figure.js"
|
||||
import {} from "./figure.js" // for side effect!
|
||||
import {} from "./after-figure.js" // for ... side effect?
|
||||
import {} from "./dance.js" // ?? for side effect??
|
||||
|
||||
export default {
|
||||
defaultDialect,
|
||||
param,
|
||||
wristGrips,
|
||||
degreesToWords,
|
||||
anglesForMove,
|
||||
chooser,
|
||||
dancerMenuForChooser,
|
||||
dancerCategoryMenuForChooser,
|
||||
dancerChooserNames,
|
||||
aliasFilter,
|
||||
deAliasMove,
|
||||
isAlias,
|
||||
moves,
|
||||
moveTermsAndSubstitutionsForSelectMenu,
|
||||
formalParameters,
|
||||
parameterLabel,
|
||||
dancerMenuLabel,
|
||||
}
|
101
external/libfigure/move.js
vendored
Normal file
101
external/libfigure/move.js
vendored
Normal file
|
@ -0,0 +1,101 @@
|
|||
// __ __ _____ _______
|
||||
// | \/ |/ _ \ \ / / ____|
|
||||
// | |\/| | | | \ \ / /| _|
|
||||
// | | | | |_| |\ V / | |___
|
||||
// |_| |_|\___/ \_/ |_____|
|
||||
//
|
||||
//
|
||||
// Properties of moves (strings).
|
||||
// Few dependencies to the rest of the system.
|
||||
|
||||
var moveCaresAboutRotationsHash = {
|
||||
"do si do": true,
|
||||
allemande: true,
|
||||
gyre: true,
|
||||
"allemande orbit": true,
|
||||
"star promenade": true,
|
||||
"mad robin": true,
|
||||
}
|
||||
// it now seems to me that this should be defined by defineFigure -dm 03-07-2017
|
||||
function moveCaresAboutRotations(move) {
|
||||
return moveCaresAboutRotationsHash[deAliasMove(move)]
|
||||
}
|
||||
|
||||
var moveCaresAboutPlacesHash = {
|
||||
circle: true,
|
||||
star: true,
|
||||
"facing star": true,
|
||||
"square through": true,
|
||||
"box circulate": true,
|
||||
}
|
||||
// it now seems to me that this should be defined by defineFigure -dm 03-07-2017
|
||||
function moveCaresAboutPlaces(move) {
|
||||
return moveCaresAboutPlacesHash[deAliasMove(move)]
|
||||
}
|
||||
|
||||
var degrees2rotations = {
|
||||
90: "¼",
|
||||
180: "½",
|
||||
270: "¾",
|
||||
360: "once",
|
||||
450: "1¼",
|
||||
540: "1½",
|
||||
630: "1¾",
|
||||
720: "twice",
|
||||
810: "2¼",
|
||||
900: "2½",
|
||||
"*": "*",
|
||||
}
|
||||
|
||||
var degrees2places = {
|
||||
90: "1 place",
|
||||
180: "2 places",
|
||||
270: "3 places",
|
||||
360: "4 places",
|
||||
450: "5 places",
|
||||
540: "6 places",
|
||||
630: "7 places",
|
||||
720: "8 places",
|
||||
810: "9 places",
|
||||
900: "10 places",
|
||||
"*": "* places",
|
||||
}
|
||||
|
||||
export const degreesToWords = function(degrees, optional_move) {
|
||||
if (optional_move) {
|
||||
if (moveCaresAboutRotations(optional_move) && degrees2rotations[degrees]) {
|
||||
return degrees2rotations[degrees]
|
||||
} else if (moveCaresAboutPlaces(optional_move) && degrees2places[degrees]) {
|
||||
return degrees2places[degrees]
|
||||
}
|
||||
}
|
||||
return degrees.toString() + " degrees"
|
||||
}
|
||||
|
||||
function degreesToRotations(degrees) {
|
||||
if (degrees) {
|
||||
return degrees2rotations[degrees] || degrees.toString() + " degrees"
|
||||
} else {
|
||||
return "?"
|
||||
}
|
||||
}
|
||||
|
||||
function degreesToPlaces(degrees) {
|
||||
if (degrees) {
|
||||
return degrees2places[degrees] || degrees.toString() + " degrees"
|
||||
} else {
|
||||
return "? places"
|
||||
}
|
||||
}
|
||||
|
||||
var anglesForMoveArr = [90, 180, 270, 360, 450, 540, 630, 720, 810, 900]
|
||||
|
||||
export const anglesForMove = function(move) {
|
||||
if (move === "square through") {
|
||||
return [180, 270, 360]
|
||||
} else if (move === "box circulate") {
|
||||
return [90, 180, 270, 360]
|
||||
} else {
|
||||
return anglesForMoveArr
|
||||
}
|
||||
}
|
793
external/libfigure/param.js
vendored
Normal file
793
external/libfigure/param.js
vendored
Normal file
|
@ -0,0 +1,793 @@
|
|||
// ____ _ ____ _ __ __
|
||||
// | _ \ / \ | _ \ / \ | \/ |
|
||||
// | |_) / _ \ | |_) | / _ \ | |\/| |
|
||||
// | __/ ___ \| _ < / ___ \| | | |
|
||||
// |_| /_/ \_\_| \_\/_/ \_\_| |_|
|
||||
//
|
||||
// Params have semantic value specific to each figure.
|
||||
// Params are returned by calls like "formalParameters('swing')".
|
||||
// Some param patterns have emerged. Patterns like:
|
||||
// figures have a subject telling who's acted on by the figure.
|
||||
//
|
||||
|
||||
import { throw_up } from "./util"
|
||||
import { chooser } from "./chooser"
|
||||
|
||||
const __params = {}
|
||||
|
||||
function defineParam(codeName, hash) {
|
||||
const h = (__params[codeName] = { ...hash })
|
||||
if (h.ui) {
|
||||
h.ui = chooser(h.ui)
|
||||
}
|
||||
}
|
||||
|
||||
export const param = codeName => {
|
||||
return (
|
||||
__params[codeName] || throw_up("attempt to find non param: " + codeName)
|
||||
)
|
||||
}
|
||||
|
||||
function stringParamBalance(value) {
|
||||
if (value === "*") {
|
||||
return "optional balance & "
|
||||
} else if (value) {
|
||||
return "balance & "
|
||||
} else return ""
|
||||
}
|
||||
|
||||
defineParam("balance_true", {
|
||||
name: "bal",
|
||||
value: true,
|
||||
ui: "chooser_boolean",
|
||||
string: stringParamBalance,
|
||||
})
|
||||
defineParam("balance_false", {
|
||||
name: "bal",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
string: stringParamBalance,
|
||||
})
|
||||
// if we ever add just an undefaulted 'balance', we probably should add it to the thing that adds
|
||||
// related moves in after-figure.js -dm 01-13-2019
|
||||
|
||||
defineParam("swing_prefix_none", {
|
||||
name: "prefix",
|
||||
value: "none",
|
||||
ui: "chooser_swing_prefix",
|
||||
})
|
||||
defineParam("swing_prefix_meltdown", {
|
||||
name: "prefix",
|
||||
value: "meltdown",
|
||||
ui: "chooser_swing_prefix",
|
||||
})
|
||||
|
||||
function stringParamHalfSashay(value) {
|
||||
if (value === "*") {
|
||||
return "maybe with a half sashay"
|
||||
} else if (value) {
|
||||
return "with a half sashay"
|
||||
} else return ""
|
||||
}
|
||||
// param_half_sashay_true = {name: "½sash", value: true, ui: 'chooser_boolean', string: stringParamHalfSashay} not used
|
||||
defineParam("half_sashay_false", {
|
||||
name: "½sash",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
string: stringParamHalfSashay,
|
||||
})
|
||||
|
||||
defineParam("subject_walk_in_true", {
|
||||
name: "in",
|
||||
value: true,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
defineParam("others_walk_out_false", {
|
||||
name: "out",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
defineParam("pass_through_true", {
|
||||
name: "pass thru",
|
||||
value: true,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
|
||||
// angular is picky about duped addresses of params, so we give them unique addresses.
|
||||
// if we move to another framework, these can all be compressed
|
||||
defineParam("ricochet1_false", {
|
||||
name: "rico",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
defineParam("ricochet2_false", {
|
||||
name: "rico",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
defineParam("ricochet3_false", {
|
||||
name: "rico",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
defineParam("ricochet4_false", {
|
||||
name: "rico",
|
||||
value: false,
|
||||
ui: "chooser_boolean",
|
||||
})
|
||||
|
||||
function stringParamBeatsNotN(n) {
|
||||
return function(value) {
|
||||
return value.toString()
|
||||
}
|
||||
}
|
||||
// do not use param_beats without a default number of beats - it causes dance validation explosions in the editor -dm 08-14-2018
|
||||
// defineParam('beats', {name: "beats", ui: 'chooser_beats', string: stringParamBeatsNotN(-100)});
|
||||
defineParam("beats_0", {
|
||||
name: "beats",
|
||||
value: 0,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(0),
|
||||
})
|
||||
defineParam("beats_2", {
|
||||
name: "beats",
|
||||
value: 2,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(2),
|
||||
})
|
||||
defineParam("beats_4", {
|
||||
name: "beats",
|
||||
value: 4,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(4),
|
||||
})
|
||||
defineParam("beats_6", {
|
||||
name: "beats",
|
||||
value: 6,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(6),
|
||||
})
|
||||
defineParam("beats_8", {
|
||||
name: "beats",
|
||||
value: 8,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(8),
|
||||
})
|
||||
defineParam("beats_12", {
|
||||
name: "beats",
|
||||
value: 12,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(12),
|
||||
})
|
||||
defineParam("beats_16", {
|
||||
name: "beats",
|
||||
value: 16,
|
||||
ui: "chooser_beats",
|
||||
string: stringParamBeatsNotN(16),
|
||||
})
|
||||
|
||||
function makeTurnStringParam(left, right, asterisk, null_) {
|
||||
return function(value) {
|
||||
if (value) {
|
||||
if ("*" === value) {
|
||||
return asterisk
|
||||
} else {
|
||||
return left
|
||||
}
|
||||
} else {
|
||||
if (null === value) {
|
||||
return null_
|
||||
} else {
|
||||
return right
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var stringParamClock = makeTurnStringParam(
|
||||
"clockwise",
|
||||
"counter-clockwise",
|
||||
"*",
|
||||
"____"
|
||||
)
|
||||
var stringParamLeftRight = makeTurnStringParam("left", "right", "*", "____")
|
||||
var stringParamShoulders = makeTurnStringParam(
|
||||
"right shoulders",
|
||||
"left shoulders",
|
||||
"* shoulders",
|
||||
"____"
|
||||
)
|
||||
var stringParamShouldersTerse = makeTurnStringParam(
|
||||
"rights",
|
||||
"lefts",
|
||||
"* shoulders",
|
||||
"____"
|
||||
)
|
||||
var stringParamHandStarHand = makeTurnStringParam(
|
||||
"right",
|
||||
"left",
|
||||
"* hand",
|
||||
"____"
|
||||
)
|
||||
var stringParamHand = makeTurnStringParam("right", "left", "*", "____")
|
||||
|
||||
// spin = clockwise | ccw | undefined
|
||||
defineParam("spin", {
|
||||
name: "turn",
|
||||
ui: "chooser_spin",
|
||||
string: stringParamClock,
|
||||
})
|
||||
defineParam("spin_clockwise", {
|
||||
name: "turn",
|
||||
value: true,
|
||||
ui: "chooser_spin",
|
||||
string: stringParamClock,
|
||||
})
|
||||
defineParam("spin_ccw", {
|
||||
name: "turn",
|
||||
value: false,
|
||||
ui: "chooser_spin",
|
||||
string: stringParamClock,
|
||||
})
|
||||
defineParam("spin_left", {
|
||||
name: "turn",
|
||||
value: true,
|
||||
ui: "chooser_left_right_spin",
|
||||
string: stringParamLeftRight,
|
||||
})
|
||||
defineParam("spin_right", {
|
||||
name: "turn",
|
||||
value: false,
|
||||
ui: "chooser_left_right_spin",
|
||||
string: stringParamLeftRight,
|
||||
})
|
||||
defineParam("xhand_spin", {
|
||||
name: "hand",
|
||||
ui: "chooser_right_left_hand",
|
||||
string: stringParamHandStarHand,
|
||||
})
|
||||
defineParam("right_hand_spin", {
|
||||
name: "hand",
|
||||
value: true,
|
||||
ui: "chooser_right_left_hand",
|
||||
string: stringParamHandStarHand,
|
||||
})
|
||||
defineParam("left_hand_spin", {
|
||||
name: "hand",
|
||||
value: false,
|
||||
ui: "chooser_right_left_hand",
|
||||
string: stringParamHandStarHand,
|
||||
})
|
||||
defineParam("xshoulders_spin", {
|
||||
name: "shoulder",
|
||||
ui: "chooser_right_left_shoulder",
|
||||
string: stringParamShoulders,
|
||||
})
|
||||
defineParam("right_shoulders_spin", {
|
||||
name: "shoulder",
|
||||
value: true,
|
||||
ui: "chooser_right_left_shoulder",
|
||||
string: stringParamShoulders,
|
||||
})
|
||||
defineParam("rights_spin", {
|
||||
name: "shoulder",
|
||||
value: true,
|
||||
ui: "chooser_right_left_shoulder",
|
||||
string: stringParamShouldersTerse,
|
||||
}) // shoulder
|
||||
defineParam("left_shoulders_spin", {
|
||||
name: "shoulder",
|
||||
value: false,
|
||||
ui: "chooser_right_left_shoulder",
|
||||
string: stringParamShoulders,
|
||||
})
|
||||
defineParam("by_xhand", {
|
||||
name: "c.hand",
|
||||
ui: "chooser_right_left_hand",
|
||||
string: stringParamHand,
|
||||
})
|
||||
defineParam("by_right_hand", {
|
||||
name: "hand",
|
||||
ui: "chooser_right_left_hand",
|
||||
string: stringParamHand,
|
||||
value: true,
|
||||
})
|
||||
|
||||
function stringParamDegrees(value, move) {
|
||||
// this second parameter should go away, it should be handled in figure.js,
|
||||
// because that's the file that knows about figures and moves.
|
||||
if (moveCaresAboutRotations(move)) {
|
||||
return degreesToRotations(value)
|
||||
} else if (moveCaresAboutPlaces(move)) {
|
||||
return degreesToPlaces(value)
|
||||
} else {
|
||||
console.log(
|
||||
"Warning: '" + move + "' doesn't care about either rotations or places"
|
||||
)
|
||||
return degreesToRotations(value)
|
||||
}
|
||||
}
|
||||
defineParam("revolutions", {
|
||||
name: "circling",
|
||||
ui: "chooser_revolutions",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("half_around", {
|
||||
name: "circling",
|
||||
value: 180,
|
||||
ui: "chooser_revolutions",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("once_around", {
|
||||
name: "circling",
|
||||
value: 360,
|
||||
ui: "chooser_revolutions",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("once_and_a_half", {
|
||||
name: "circling",
|
||||
value: 540,
|
||||
ui: "chooser_revolutions",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("places", {
|
||||
name: "places",
|
||||
ui: "chooser_places",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("two_places", {
|
||||
name: "places",
|
||||
value: 180,
|
||||
ui: "chooser_places",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("three_places", {
|
||||
name: "places",
|
||||
value: 270,
|
||||
ui: "chooser_places",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
defineParam("four_places", {
|
||||
name: "places",
|
||||
value: 360,
|
||||
ui: "chooser_places",
|
||||
string: stringParamDegrees,
|
||||
})
|
||||
|
||||
// always 'everyone' and never 'everybody'!
|
||||
defineParam("subject", {
|
||||
name: "who",
|
||||
value: "everyone",
|
||||
ui: "chooser_dancers",
|
||||
})
|
||||
defineParam("subject_pair", { name: "who", ui: "chooser_pair" }) // 1 pair of dancers
|
||||
defineParam("subject_pair_ladles", {
|
||||
name: "who",
|
||||
value: "ladles",
|
||||
ui: "chooser_pair",
|
||||
})
|
||||
defineParam("center_pair_ladles", {
|
||||
name: "center",
|
||||
value: "ladles",
|
||||
ui: "chooser_pair",
|
||||
})
|
||||
defineParam("subject_pair_gentlespoons", {
|
||||
name: "who",
|
||||
value: "gentlespoons",
|
||||
ui: "chooser_pair",
|
||||
})
|
||||
defineParam("subject_pair_ones", {
|
||||
name: "who",
|
||||
value: "ones",
|
||||
ui: "chooser_pair",
|
||||
})
|
||||
defineParam("subject_pair_or_everyone", {
|
||||
name: "who",
|
||||
value: "everyone",
|
||||
ui: "chooser_pair_or_everyone",
|
||||
})
|
||||
defineParam("subject_pairc_or_everyone", {
|
||||
name: "who",
|
||||
value: "everyone",
|
||||
ui: "chooser_pairc_or_everyone",
|
||||
}) // has `centers`
|
||||
defineParam("subject_pairz", { name: "who", ui: "chooser_pairz" }) // 1-2 pairs of dancers
|
||||
defineParam("subject_pairz_partners", {
|
||||
name: "who",
|
||||
value: "partners",
|
||||
ui: "chooser_pairz",
|
||||
})
|
||||
defineParam("subject_pairz_ladles", {
|
||||
name: "who",
|
||||
value: "ladles",
|
||||
ui: "chooser_pairz",
|
||||
})
|
||||
defineParam("subject2_pairz_or_unspecified", {
|
||||
name: "who2",
|
||||
value: "",
|
||||
ui: "chooser_pairz_or_unspecified",
|
||||
})
|
||||
defineParam("subject_pairs", { name: "who", ui: "chooser_pairs" }) // 2 pairs of dancers
|
||||
defineParam("sides_pairs_neighbors", {
|
||||
name: "sides",
|
||||
value: "neighbors",
|
||||
ui: "chooser_pairs",
|
||||
})
|
||||
defineParam("subject2_pairs", { name: "who2", ui: "chooser_pairs" })
|
||||
defineParam("subject_pairs_or_everyone", {
|
||||
name: "who",
|
||||
ui: "chooser_pairs_or_everyone",
|
||||
})
|
||||
defineParam("subject_pairs_partners", {
|
||||
name: "who",
|
||||
value: "partners",
|
||||
ui: "chooser_pairs",
|
||||
})
|
||||
defineParam("subject_dancer", { name: "who", ui: "chooser_dancer" })
|
||||
// param_subject_role = {name: "who", ui: 'chooser_role'}; // not used
|
||||
defineParam("subject_role_ladles", {
|
||||
name: "who",
|
||||
value: "ladles",
|
||||
ui: "chooser_role",
|
||||
})
|
||||
defineParam("subject_role_gentlespoons", {
|
||||
name: "who",
|
||||
value: "gentlespoons",
|
||||
ui: "chooser_role",
|
||||
})
|
||||
// param_subject_partners = {name: "who", value: "partners", ui: 'chooser_pairs'}; // not used
|
||||
// param_subject_hetero_partners = {name: "who", value: "partners", ui: 'chooser_hetero'}; // not used
|
||||
defineParam("object_hetero_partners", {
|
||||
name: "whom",
|
||||
value: "partners",
|
||||
ui: "chooser_hetero",
|
||||
})
|
||||
defineParam("object_dancer", { name: "whom", ui: "chooser_dancer" })
|
||||
defineParam("object_pairs", { name: "whom", ui: "chooser_pairs" })
|
||||
defineParam("object_pairs_or_ones_or_twos", {
|
||||
name: "whom",
|
||||
ui: "chooser_pairs_or_ones_or_twos",
|
||||
})
|
||||
defineParam("lead_dancer_l1", {
|
||||
name: "lead",
|
||||
value: "first ladle",
|
||||
ui: "chooser_dancer",
|
||||
})
|
||||
|
||||
function formalParamIsDancers(param) {
|
||||
// harder to maintain implementation:
|
||||
// return ['who', 'who2', 'whom', 'lead'].indexOf(param.name) >= 0;
|
||||
return !!dancerMenuForChooser(param.ui)
|
||||
}
|
||||
|
||||
function wordParamCustom(value, move_meh, dialect) {
|
||||
return lingoLineWords(stringInDialect(value, dialect), dialect)
|
||||
}
|
||||
|
||||
// so if you use param_custom_figure, you have to use parameter_words
|
||||
// instead of parameter_strings. (unless you use the default figure
|
||||
// printer, which takes care of it for you, c.f. contra corners)
|
||||
defineParam("custom_figure", {
|
||||
name: "custom",
|
||||
value: "",
|
||||
ui: "chooser_text",
|
||||
words: wordParamCustom,
|
||||
})
|
||||
|
||||
export const wristGrips = ["", "wrist grip", "hands across"]
|
||||
|
||||
function stringParamStarGrip(value) {
|
||||
return "*" === value ? "any grip" : value
|
||||
}
|
||||
|
||||
defineParam("star_grip", {
|
||||
name: "grip",
|
||||
value: wristGrips[0],
|
||||
ui: "chooser_star_grip",
|
||||
string: stringParamStarGrip,
|
||||
})
|
||||
|
||||
function stringParamMarchForward(value) {
|
||||
if (value) {
|
||||
return value === "forward" ? "" : value
|
||||
} else {
|
||||
return "facing ____"
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("march_facing", { name: "facing", ui: "chooser_march_facing" })
|
||||
defineParam("march_forward", {
|
||||
name: "facing",
|
||||
ui: "chooser_march_facing",
|
||||
value: "forward",
|
||||
string: stringParamMarchForward,
|
||||
})
|
||||
|
||||
function stringParamSlide(value) {
|
||||
if (value === "*") {
|
||||
return "*"
|
||||
} else if (value) {
|
||||
return "left"
|
||||
} else {
|
||||
return "right"
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("slide", {
|
||||
name: "slide",
|
||||
ui: "chooser_slide",
|
||||
string: stringParamSlide,
|
||||
})
|
||||
defineParam("slide_left", {
|
||||
name: "slide",
|
||||
value: true,
|
||||
ui: "chooser_slide",
|
||||
string: stringParamSlide,
|
||||
})
|
||||
defineParam("slide_right", {
|
||||
name: "slide",
|
||||
value: false,
|
||||
ui: "chooser_slide",
|
||||
string: stringParamSlide,
|
||||
})
|
||||
|
||||
function stringParamSetDirection(value) {
|
||||
if (value === "across") {
|
||||
return "across the set"
|
||||
} else if (value === "along") {
|
||||
return "along the set"
|
||||
} else if (
|
||||
["right diagonal", "left diagonal", "*", undefined].indexOf(value) >= 0
|
||||
) {
|
||||
return value
|
||||
} else {
|
||||
throw_up("unexpected set direction value: " + value)
|
||||
}
|
||||
}
|
||||
|
||||
function stringParamSetDirectionSilencingDefault(value_default) {
|
||||
return function(value) {
|
||||
return value === value_default ? "" : stringParamSetDirection(value)
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("set_direction", {
|
||||
name: "dir",
|
||||
ui: "chooser_set_direction",
|
||||
string: stringParamSetDirection,
|
||||
})
|
||||
defineParam("set_direction_along", {
|
||||
name: "dir",
|
||||
ui: "chooser_set_direction",
|
||||
value: "along",
|
||||
string: stringParamSetDirectionSilencingDefault("along"),
|
||||
})
|
||||
defineParam("set_direction_across", {
|
||||
name: "dir",
|
||||
ui: "chooser_set_direction",
|
||||
value: "across",
|
||||
string: stringParamSetDirectionSilencingDefault("across"),
|
||||
})
|
||||
defineParam("set_direction_grid", {
|
||||
name: "dir",
|
||||
ui: "chooser_set_direction_grid",
|
||||
string: stringParamSetDirectionSilencingDefault("nope"),
|
||||
})
|
||||
defineParam("set_direction_acrossish", {
|
||||
name: "dir",
|
||||
ui: "chooser_set_direction_acrossish",
|
||||
value: "across",
|
||||
string: stringParamSetDirectionSilencingDefault("across"),
|
||||
})
|
||||
defineParam("set_direction_figure_8", {
|
||||
name: "dir",
|
||||
ui: "chooser_set_direction_figure_8",
|
||||
value: "",
|
||||
}) // '', 'across', 'above', 'below'
|
||||
|
||||
function stringParamSliceReturn(value) {
|
||||
if ("straight" === value) {
|
||||
return "and straight back"
|
||||
} else if ("diagonal" === value) {
|
||||
return "and diagonal back"
|
||||
} else if ("none" === value) {
|
||||
return ""
|
||||
} else if ("*" === value) {
|
||||
return "and *"
|
||||
} else {
|
||||
throw_up("bad slice return value: " + value)
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("slice_return", {
|
||||
name: "slice return",
|
||||
ui: "chooser_slice_return",
|
||||
value: "straight",
|
||||
string: stringParamSliceReturn,
|
||||
})
|
||||
|
||||
function stringParamSliceIncrement(value) {
|
||||
if ("couple" === value) {
|
||||
return ""
|
||||
} else if ("dancer" === value) {
|
||||
return "one dancer"
|
||||
} else if ("*" === value) {
|
||||
return "one *"
|
||||
} else {
|
||||
throw_up("bad slice increment: " + value)
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("slice_increment", {
|
||||
name: "slice increment",
|
||||
ui: "chooser_slice_increment",
|
||||
value: "couple",
|
||||
string: stringParamSliceIncrement,
|
||||
})
|
||||
|
||||
defineParam("all_or_center_or_outsides", {
|
||||
name: "moving",
|
||||
ui: "chooser_all_or_center_or_outsides",
|
||||
value: "all",
|
||||
})
|
||||
|
||||
function stringParamDownTheHallEnder(value) {
|
||||
if ("" === value) {
|
||||
return ""
|
||||
} else if ("turn-couple" === value) {
|
||||
return "turn as a couple"
|
||||
} else if ("turn-alone" === value) {
|
||||
return "turn alone"
|
||||
} else if ("circle" === value) {
|
||||
return "bend into a ring"
|
||||
} else if ("cozy" === value) {
|
||||
return "form a cozy line"
|
||||
} else if ("cloverleaf" === value) {
|
||||
return "bend into a cloverleaf"
|
||||
} else if ("thread-needle" === value) {
|
||||
return "thread the needle"
|
||||
} else if ("right-high" === value) {
|
||||
return "right hand high, left hand low"
|
||||
} else if ("sliding-doors" === value) {
|
||||
return "slide doors"
|
||||
} else if ("*" === value) {
|
||||
return "end however"
|
||||
} else {
|
||||
throw_up("bad down the hall ender: " + value)
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("down_the_hall_ender", {
|
||||
name: "ender",
|
||||
ui: "chooser_down_the_hall_ender",
|
||||
value: "",
|
||||
string: stringParamDownTheHallEnder,
|
||||
})
|
||||
defineParam("down_the_hall_ender_circle", {
|
||||
name: "ender",
|
||||
ui: "chooser_down_the_hall_ender",
|
||||
value: "circle",
|
||||
string: stringParamDownTheHallEnder,
|
||||
})
|
||||
defineParam("down_the_hall_ender_turn_couples", {
|
||||
name: "ender",
|
||||
ui: "chooser_down_the_hall_ender",
|
||||
value: "turn-couple",
|
||||
string: stringParamDownTheHallEnder,
|
||||
})
|
||||
|
||||
function stringParamZigZagEnder(value) {
|
||||
if ("" === value) {
|
||||
return ""
|
||||
} else if ("ring" === value) {
|
||||
return "into a ring"
|
||||
} else if ("allemande" === value) {
|
||||
return "trailing two catching hands"
|
||||
} else if ("*" === value) {
|
||||
return "ending however"
|
||||
} else {
|
||||
throw_up("bad zig zag ender: " + value)
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("zig_zag_ender", {
|
||||
name: "ender",
|
||||
ui: "chooser_zig_zag_ender",
|
||||
value: "",
|
||||
string: stringParamZigZagEnder,
|
||||
})
|
||||
|
||||
defineParam("go_back", { name: "go", ui: "chooser_go_back", value: true })
|
||||
defineParam("give", { name: "give", ui: "chooser_give", value: true })
|
||||
|
||||
var _stringParamGateFace = {
|
||||
up: "up the set",
|
||||
down: "down the set",
|
||||
in: "into the set",
|
||||
out: "out of the set",
|
||||
"*": "any direction",
|
||||
}
|
||||
|
||||
function stringParamGateFace(value) {
|
||||
return _stringParamGateFace[value]
|
||||
}
|
||||
|
||||
defineParam("gate_face", {
|
||||
name: "face",
|
||||
ui: "chooser_gate_direction",
|
||||
string: stringParamGateFace,
|
||||
})
|
||||
|
||||
function stringParamHalfOrFullNotN(default_value) {
|
||||
return function(value) {
|
||||
if (default_value === value) {
|
||||
return ""
|
||||
} else if (0.5 === value) {
|
||||
return "half"
|
||||
} else if (1.0 === value) {
|
||||
return "full"
|
||||
} else if ("*" === value) {
|
||||
return "*"
|
||||
} else {
|
||||
throw_up(
|
||||
"bad half_or_full parameter value: " +
|
||||
value +
|
||||
" of type " +
|
||||
typeof value
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineParam("half_or_full", {
|
||||
name: "half",
|
||||
ui: "chooser_half_or_full",
|
||||
string: stringParamHalfOrFullNotN(-100.0),
|
||||
})
|
||||
defineParam("half_or_full_half", {
|
||||
name: "half",
|
||||
value: 0.5,
|
||||
ui: "chooser_half_or_full",
|
||||
string: stringParamHalfOrFullNotN(0.5),
|
||||
})
|
||||
defineParam("half_or_full_half_chatty_half", {
|
||||
name: "half",
|
||||
value: 0.5,
|
||||
ui: "chooser_half_or_full",
|
||||
string: stringParamHalfOrFullNotN(1.0),
|
||||
}) // hey is chatty about its halfness, but mum about fullness
|
||||
defineParam("half_or_full_half_chatty_max", {
|
||||
name: "half",
|
||||
value: 0.5,
|
||||
ui: "chooser_half_or_full",
|
||||
string: stringParamHalfOrFullNotN(-100),
|
||||
}) // poussette is chatty about both halfness and fullness
|
||||
defineParam("half_or_full_full", {
|
||||
name: "half",
|
||||
value: 1.0,
|
||||
ui: "chooser_half_or_full",
|
||||
string: stringParamHalfOrFullNotN(1.0),
|
||||
})
|
||||
|
||||
defineParam("hey_length_half", {
|
||||
name: "until",
|
||||
value: "half",
|
||||
ui: "chooser_hey_length",
|
||||
string: stringParamHeyLength,
|
||||
})
|
||||
|
||||
function stringParamHeyLength(value, move, dialect) {
|
||||
if (value === "full" || value === "half" || value === "*") {
|
||||
return value
|
||||
} else if (value === null) {
|
||||
return "____"
|
||||
} else if (value === "less than half") {
|
||||
return "until someone meets"
|
||||
} else if (value === "between half and full") {
|
||||
return "until someone meets the second time"
|
||||
} else {
|
||||
var pair = parseHeyLength(value)
|
||||
var dancer = pair[0]
|
||||
var meeting = pair[1] === 2 ? " meet the second time" : " meet"
|
||||
return "until " + dancerSubstitution(dancer, dialect) + meeting
|
||||
}
|
||||
}
|
25
external/libfigure/polyfill.js
vendored
Normal file
25
external/libfigure/polyfill.js
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
if (!Array.prototype.findIndex) {
|
||||
Array.prototype.findIndex = function(predicate) {
|
||||
"use strict"
|
||||
if (this == null) {
|
||||
throw new TypeError(
|
||||
"Array.prototype.findIndex called on null or undefined"
|
||||
)
|
||||
}
|
||||
if (typeof predicate !== "function") {
|
||||
throw new TypeError("predicate must be a function")
|
||||
}
|
||||
var list = Object(this)
|
||||
var length = list.length >>> 0
|
||||
var thisArg = arguments[1]
|
||||
var value
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
value = list[i]
|
||||
if (predicate.call(thisArg, value, i, list)) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
}
|
209
external/libfigure/util.js
vendored
Normal file
209
external/libfigure/util.js
vendored
Normal file
|
@ -0,0 +1,209 @@
|
|||
export const PUNCTUATION_CHARSET_STRING =
|
||||
"[\u2000-\u206F\u2E00-\u2E7F'!\"#$%&()*+,/:;<=>?@\\[\\]^_`{|}~\\.-]"
|
||||
|
||||
function set_if_unset(dict, key, value) {
|
||||
if (!(key in dict)) dict[key] = value
|
||||
}
|
||||
|
||||
// throw is a keyword and can't be in expressions, but function calls can be, so wrap throw.
|
||||
export const throw_up = str => {
|
||||
throw new Error(str)
|
||||
}
|
||||
|
||||
// a little weird that this takes a Words now, not a string
|
||||
function indefiniteArticleFor(w) {
|
||||
var str = peek(w)
|
||||
return /[aeiou]/.test(str) ? "an" : "a"
|
||||
}
|
||||
|
||||
// text_in_dialect: <bool> property can still be missing...
|
||||
export const defaultDialect = { moves: {}, dancers: {} }
|
||||
|
||||
export const testDialect = {
|
||||
moves: {
|
||||
gyre: "darcy",
|
||||
allemande: "almond",
|
||||
"see saw": "do si do left shoulder",
|
||||
"form an ocean wave": "form a short wavy line",
|
||||
"Rory O'More": "sliding doors",
|
||||
},
|
||||
dancers: {
|
||||
ladle: "raven",
|
||||
ladles: "ravens",
|
||||
gentlespoon: "lark",
|
||||
gentlespoons: "larks",
|
||||
"first ladle": "first raven",
|
||||
"second ladle": "second raven",
|
||||
"first gentlespoon": "first lark",
|
||||
"second gentlespoon": "second lark",
|
||||
},
|
||||
}
|
||||
|
||||
// ________________________________________________________________
|
||||
|
||||
function dialectIsOneToOne(dialect) {
|
||||
return isEmpty(dialectOverloadedSubstitutions(dialect))
|
||||
}
|
||||
|
||||
function dialectOverloadedSubstitutions(dialect) {
|
||||
var substitutions = {}
|
||||
var remember_as_itself = function(x) {
|
||||
substitutions[x] = (substitutions[x] || []).concat([x])
|
||||
}
|
||||
moves().forEach(remember_as_itself)
|
||||
dancers().forEach(remember_as_itself)
|
||||
;[dialect.moves, dialect.dancers].forEach(function(hash) {
|
||||
Object.keys(hash).forEach(function(term) {
|
||||
var substitution = hash[term]
|
||||
if (!substitutions[substitution]) {
|
||||
substitutions[substitution] = []
|
||||
}
|
||||
if (-1 === substitutions[substitution].indexOf(term)) {
|
||||
substitutions[substitution].push(term)
|
||||
}
|
||||
})
|
||||
})
|
||||
// delete substitutions that are 1-to-1
|
||||
for (var substitution in substitutions) {
|
||||
if (substitutions.hasOwnProperty(substitution)) {
|
||||
if (substitutions[substitution].length === 1)
|
||||
delete substitutions[substitution]
|
||||
}
|
||||
}
|
||||
return substitutions
|
||||
}
|
||||
|
||||
// ________________________________________________________________
|
||||
|
||||
export const longestFirstSortFn = function(a, b) {
|
||||
return b.length - a.length
|
||||
}
|
||||
|
||||
// ________________________________________________________________
|
||||
|
||||
function dialectForFigures(dialect, figures) {
|
||||
var new_dialect = copyDialect(dialect)
|
||||
if (figuresUseDancers(figures, "3rd neighbors")) {
|
||||
new_dialect.dancers["neighbors"] = "1st neighbors"
|
||||
new_dialect.dancers["next neighbors"] = "2nd neighbors"
|
||||
}
|
||||
if (figuresUseDancers(figures, "2nd shadows")) {
|
||||
new_dialect.dancers["shadows"] = "1st shadows"
|
||||
}
|
||||
return new_dialect
|
||||
}
|
||||
|
||||
function copyDialect(dialect) {
|
||||
return {
|
||||
dancers: libfigureObjectCopy(dialect.dancers),
|
||||
moves: libfigureObjectCopy(dialect.moves),
|
||||
text_in_dialect: !!dialect.text_in_dialect,
|
||||
}
|
||||
}
|
||||
|
||||
function textInDialect(dialect) {
|
||||
// see also ruby-side implementation
|
||||
return !!dialect.text_in_dialect
|
||||
}
|
||||
|
||||
// I just called this function 'copy', but then I got scared and changed it.
|
||||
export const libfigureObjectCopy = hash => {
|
||||
var o = {}
|
||||
Object.keys(hash).forEach(function(key) {
|
||||
o[key] = hash[key]
|
||||
})
|
||||
return o
|
||||
}
|
||||
|
||||
function libfigureUniq(array) {
|
||||
// suboptimal O(n^2)
|
||||
var output = []
|
||||
for (var i = 0; i < array.length; i++) {
|
||||
if (-1 === output.indexOf(array[i])) {
|
||||
output.push(array[i])
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
// // every element is ===
|
||||
// function array_equal(a1, a2) {
|
||||
// var l = a1.length;
|
||||
// if (l !== a2.length) {
|
||||
// return false;
|
||||
// }
|
||||
// for (var i=0; i<l; i++) {
|
||||
// if (a1[i] !== a2[i]) {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// used to determine if a dance uses the term 'shadow' or '3rd neighbor'
|
||||
function figuresUseDancers(figures, dancers_term) {
|
||||
for (var figi = 0; figi < figures.length; figi++) {
|
||||
var figure = figures[figi]
|
||||
var formals = parameters(figure.move)
|
||||
for (var i = 0; i < formals.length; i++) {
|
||||
var formal = formals[i]
|
||||
var actual = figure.parameter_values[i]
|
||||
if (formalParamIsDancers(formal) && dancers_term === actual) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// unpacks a hey length into a pair, [dancer, meeting_count] or ['full', meeting_count]
|
||||
function parseHeyLength(hey_length) {
|
||||
if (hey_length === "full" || hey_length === "between half and full") {
|
||||
return [hey_length, 2]
|
||||
} else if (hey_length === "half" || hey_length === "less than half") {
|
||||
return [hey_length, 1]
|
||||
} else if (hey_length === "*" || hey_length === null) {
|
||||
return [hey_length, hey_length] // kinda nonsense, but whatever
|
||||
} else {
|
||||
var match = /%%([12])$/.exec(hey_length)
|
||||
if (match) {
|
||||
var dancer = hey_length.slice(0, match.index)
|
||||
var meeting = match[1] === "2" ? 2 : 1
|
||||
return [dancer, meeting]
|
||||
}
|
||||
}
|
||||
throw_up("unparseable hey length - " + hey_length)
|
||||
}
|
||||
|
||||
function heyLengthMeetTimes(hey_length) {
|
||||
return parseHeyLength(hey_length)[1]
|
||||
}
|
||||
|
||||
function dancerIsPair(dancer) {
|
||||
return dancerMenuForChooser(chooser("chooser_pair")).indexOf(dancer) >= 0
|
||||
}
|
||||
|
||||
// see also the similar ruby-side function slugify_move
|
||||
function slugifyTerm(term) {
|
||||
return term
|
||||
.toLowerCase()
|
||||
.replace(/&/g, "and")
|
||||
.replace(/ /g, "-")
|
||||
.replace(/[^a-z0-9-]/g, "")
|
||||
}
|
||||
|
||||
// ________________________________________________________________
|
||||
|
||||
const regExpEscape_regexp = /[-\/\\^$*+?.()|[\]{}]/g
|
||||
export const regExpEscape = function(s) {
|
||||
return s.replace(regExpEscape_regexp, "\\$&")
|
||||
}
|
||||
// source https://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript/3561711#3561711
|
||||
|
||||
function isEmpty(hash) {
|
||||
// can't use: hash.length === 0
|
||||
for (var x in hash) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
308
external/libfigure/words.js
vendored
Normal file
308
external/libfigure/words.js
vendored
Normal file
|
@ -0,0 +1,308 @@
|
|||
import {
|
||||
longestFirstSortFn,
|
||||
PUNCTUATION_CHARSET_STRING,
|
||||
regExpEscape,
|
||||
} from "./util"
|
||||
import { dancers } from "./chooser"
|
||||
import { moves } from "./define-figure"
|
||||
|
||||
function Words(arr) {
|
||||
this.arr = arr
|
||||
}
|
||||
|
||||
export const words = function() {
|
||||
return new Words(Array.prototype.slice.call(arguments))
|
||||
}
|
||||
|
||||
Words.prototype.scrunched = function() {
|
||||
return false
|
||||
}
|
||||
|
||||
// ____ ScrunchedWords are Words that don't insert spaces
|
||||
// between arguments as agressively as regular words
|
||||
|
||||
function ScrunchedWords(arr) {
|
||||
Words.call(this, arr)
|
||||
}
|
||||
|
||||
ScrunchedWords.prototype = Object.create(Words.prototype)
|
||||
ScrunchedWords.prototype.constructor = ScrunchedWords
|
||||
|
||||
ScrunchedWords.prototype.scrunched = function() {
|
||||
return true
|
||||
}
|
||||
|
||||
// ____ ScrunchedWords end
|
||||
|
||||
var wants_no_space_before = [false, null, ",", ".", ";"]
|
||||
|
||||
export const FLATTEN_FORMAT_MARKDOWN = 1001
|
||||
export const FLATTEN_FORMAT_HTML = 1002
|
||||
export const FLATTEN_FORMAT_UNSAFE_TEXT = 1003
|
||||
export const FLATTEN_FORMAT_SAFE_TEXT = 1004
|
||||
|
||||
// returns *sanitized* html
|
||||
Words.prototype.toHtml = function() {
|
||||
return this.flatten(FLATTEN_FORMAT_HTML)
|
||||
}
|
||||
|
||||
// returns *unsanitized* string with Tags (see below) lobotimized into text.
|
||||
// May do whitespace dialation ala html, but at least it preserves newlines.
|
||||
Words.prototype.toUnsafeText = function() {
|
||||
return this.flatten(FLATTEN_FORMAT_UNSAFE_TEXT)
|
||||
}
|
||||
|
||||
// returns *sanitized* e.g. <b>pb&j</b> → <b>pb&a<b>
|
||||
// but is different from toHtml because any Tags (see below) are
|
||||
// flattened with no bracketing tags, they're all body.
|
||||
Words.prototype.toSafeText = function() {
|
||||
return this.flatten(FLATTEN_FORMAT_SAFE_TEXT)
|
||||
}
|
||||
|
||||
// returns *unsanitized* string with Tags (see below) rendered as html (not Markdown)
|
||||
// This preserves newlines, unlike toHtml.
|
||||
// It's assumed to be headed for a markdown parser that takes care of that.
|
||||
Words.prototype.toMarkdown = function() {
|
||||
return this.flatten(FLATTEN_FORMAT_MARKDOWN)
|
||||
}
|
||||
|
||||
Words.prototype.flatten = function(format) {
|
||||
var arr = this.arr
|
||||
var acc = []
|
||||
var space_before = false
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var wants_space_before =
|
||||
!this.scrunched() && -1 === wants_no_space_before.indexOf(peek(arr[i]))
|
||||
if (wants_space_before) {
|
||||
acc.push(" ")
|
||||
}
|
||||
acc.push(flattenWordNode(arr[i], format))
|
||||
}
|
||||
return trimButLeaveNewlines(acc.join(""))
|
||||
}
|
||||
|
||||
Words.prototype.peek = function() {
|
||||
for (var i = 0; i < this.arr.length; i++) {
|
||||
var p = peek(this.arr[i])
|
||||
if (p) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export const Tag = function(tag, attrs, body) {
|
||||
this.tag = tag
|
||||
this.attrs = attrs
|
||||
this.body = body
|
||||
}
|
||||
|
||||
export const tag = function(tag, body) {
|
||||
return new Tag(tag, {}, body)
|
||||
}
|
||||
|
||||
// function tag_attrs(tag, attrs, body) {
|
||||
// return new Tag(tag, attrs, body);
|
||||
// }
|
||||
|
||||
Tag.prototype.flatten = function(format) {
|
||||
if (
|
||||
format === FLATTEN_FORMAT_UNSAFE_TEXT ||
|
||||
format === FLATTEN_FORMAT_SAFE_TEXT
|
||||
) {
|
||||
return flattenWordNode(this.body, format)
|
||||
} else if (
|
||||
format === FLATTEN_FORMAT_MARKDOWN ||
|
||||
format === FLATTEN_FORMAT_HTML
|
||||
) {
|
||||
Object.keys(this.attrs).length === 0 ||
|
||||
throw_up("attrs not yet implemented, but used")
|
||||
return (
|
||||
"<" +
|
||||
this.tag +
|
||||
">" +
|
||||
flattenWordNode(this.body, format) +
|
||||
"</" +
|
||||
this.tag +
|
||||
">"
|
||||
)
|
||||
} else {
|
||||
throw_up("unexpected word flatten format :" + format.toString())
|
||||
}
|
||||
}
|
||||
|
||||
Tag.prototype.peek = function() {
|
||||
return peek(this.body)
|
||||
}
|
||||
|
||||
var sanitizationMap = {
|
||||
"<": "<",
|
||||
">": ">",
|
||||
"&": "&",
|
||||
"&": "&",
|
||||
}
|
||||
|
||||
function flattenWordNode(s, format) {
|
||||
if (s.flatten) {
|
||||
return s.flatten(format)
|
||||
} else if ("string" === typeof s) {
|
||||
if (format === FLATTEN_FORMAT_HTML || format === FLATTEN_FORMAT_SAFE_TEXT) {
|
||||
var replacer = function(match) {
|
||||
return (
|
||||
sanitizationMap[match] ||
|
||||
throw_up("Unexpected match during flatten sanitize")
|
||||
)
|
||||
}
|
||||
return s.replace(/&|&|<|>/g, replacer)
|
||||
} else if (
|
||||
format === FLATTEN_FORMAT_MARKDOWN ||
|
||||
format === FLATTEN_FORMAT_UNSAFE_TEXT
|
||||
) {
|
||||
return s
|
||||
} else {
|
||||
throw_up("unexpected flatten format: " + format.toString())
|
||||
}
|
||||
} else if (comma === s) {
|
||||
return ","
|
||||
} else if (false === s) {
|
||||
return ""
|
||||
} else {
|
||||
return "" + s
|
||||
}
|
||||
}
|
||||
|
||||
// returns first non-whitespace character
|
||||
export const peek = function(thing) {
|
||||
var m
|
||||
if (thing.peek) {
|
||||
return thing.peek()
|
||||
} else if (typeof thing === "string" && (m = thing.match(/[\S\n]/))) {
|
||||
return m[0]
|
||||
} else if (thing == comma) {
|
||||
return ","
|
||||
} else if (thing == false) {
|
||||
return null
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const trimButLeaveNewlines = function(s) {
|
||||
var start
|
||||
var end
|
||||
for (start = 0; start < s.length; start++) {
|
||||
if (s[start].match(/[\S\n]/)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for (end = s.length - 1; end >= start; end--) {
|
||||
if (s[end].match(/[\S\n]/)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s.slice(start, end + 1)
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
export const comma = Object.freeze([Object.freeze("comma")])
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
// clamp words in <s> or <u> - string is already in-dialect at the point when this proceess it
|
||||
export const lingoLineWords = function(string, dialect) {
|
||||
// lookbehind doesn't work in all versions of js, so we've got to use capture groups for word boundaries, sigh
|
||||
var underlines_and_strikes = underlinesAndStrikes(dialect)
|
||||
var all_lingo_lines = underlines_and_strikes.underlines
|
||||
.concat(underlines_and_strikes.strikes)
|
||||
.sort(longestFirstSortFn)
|
||||
var regex = new RegExp(
|
||||
"(\\s|" +
|
||||
PUNCTUATION_CHARSET_STRING +
|
||||
"|^)(" +
|
||||
all_lingo_lines.map(regExpEscape).join("|") +
|
||||
")(\\s|" +
|
||||
PUNCTUATION_CHARSET_STRING +
|
||||
"|$)",
|
||||
"ig"
|
||||
)
|
||||
var buffer = []
|
||||
var last_match_ended_at
|
||||
while (true) {
|
||||
last_match_ended_at = regex.lastIndex
|
||||
var match_info = regex.exec(string)
|
||||
if (!match_info) break
|
||||
buffer.push(string.slice(last_match_ended_at, match_info.index))
|
||||
var is_strike =
|
||||
underlines_and_strikes.strikes.indexOf(match_info[2].toLowerCase()) >= 0
|
||||
buffer.push(match_info[1]) // its whitespace, but it might be a newline
|
||||
buffer.push(tag(is_strike ? "s" : "u", match_info[2]))
|
||||
regex.lastIndex = regex.lastIndex - match_info[3].length // put back trailing whitespace
|
||||
}
|
||||
buffer.push(string.slice(last_match_ended_at))
|
||||
return new ScrunchedWords(buffer)
|
||||
}
|
||||
|
||||
var bogusTerms = [
|
||||
"men",
|
||||
"women",
|
||||
"man",
|
||||
"woman",
|
||||
"gentlemen",
|
||||
"gentleman",
|
||||
"gents",
|
||||
"gent",
|
||||
"ladies",
|
||||
"lady",
|
||||
"leads",
|
||||
"lead",
|
||||
"follows",
|
||||
"follow",
|
||||
"larks",
|
||||
"lark",
|
||||
"ravens",
|
||||
"raven",
|
||||
"sex",
|
||||
"gypsy",
|
||||
"yearn",
|
||||
"rory o'moore",
|
||||
"rollaway",
|
||||
"roll-away",
|
||||
"nn",
|
||||
"n ",
|
||||
"p ",
|
||||
"l ",
|
||||
"g ",
|
||||
"m ",
|
||||
"w ",
|
||||
"n.",
|
||||
"p.",
|
||||
"l.",
|
||||
"g.",
|
||||
"m.",
|
||||
"w.",
|
||||
"g1",
|
||||
"g2",
|
||||
"l1",
|
||||
"l2",
|
||||
]
|
||||
|
||||
var terms_for_uands
|
||||
// NB on return value: it is freshly allocated each time
|
||||
function underlinesAndStrikes(dialect) {
|
||||
if (!terms_for_uands) {
|
||||
terms_for_uands = moves().concat(dancers())
|
||||
}
|
||||
var underlines = terms_for_uands.map(function(term) {
|
||||
var substitution = dialect.dancers[term] || dialect.moves[term]
|
||||
return (substitution ? stripPercentS(substitution) : term).toLowerCase()
|
||||
})
|
||||
var strikes = terms_for_uands.concat(bogusTerms).filter(function(s) {
|
||||
return -1 === underlines.indexOf(s.toLowerCase())
|
||||
})
|
||||
return { underlines: underlines, strikes: strikes }
|
||||
}
|
||||
|
||||
function stripPercentS(str) {
|
||||
return str.replace(/%S/g, "").trim()
|
||||
}
|
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "es2015",
|
||||
"target": "es2015",
|
||||
"lib": [ "DOM", "es2022" ]
|
||||
}
|
||||
}
|
17
www/index.html
Normal file
17
www/index.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Prototype Contra Dance Renderer</title>
|
||||
<style>
|
||||
canvas {
|
||||
border: solid black 2px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
1
www/js/libfigure
Symbolic link
1
www/js/libfigure
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../external/libfigure
|
22
www/js/main.ts
Normal file
22
www/js/main.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import * as renderer from "./renderer.js";
|
||||
|
||||
const wrapperDiv = document.createElement('div');
|
||||
const canvas = document.createElement('canvas');
|
||||
wrapperDiv.appendChild(canvas);
|
||||
document.querySelector('body')?.appendChild(wrapperDiv);
|
||||
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
// TODO Redo this on resize?
|
||||
const w = canvas.offsetWidth;
|
||||
const h = canvas.offsetHeight;
|
||||
const s = window.devicePixelRatio;
|
||||
|
||||
canvas.width = w * s;
|
||||
canvas.height = h * s;
|
||||
|
||||
ctx.translate(canvas.width/2.0, canvas.height/2.0);
|
||||
const scale = 20;
|
||||
ctx.scale(-canvas.height/scale,-canvas.height/scale);
|
||||
const r = new renderer.Renderer(ctx);
|
||||
r.drawSets(renderer.improperLongLines, 3, 3);
|
223
www/js/renderer.ts
Normal file
223
www/js/renderer.ts
Normal file
|
@ -0,0 +1,223 @@
|
|||
import * as libfigure from "./libfigure/libfigure.js";
|
||||
|
||||
enum DanceRole {
|
||||
Lark = "Lark",
|
||||
Robin = "Robin",
|
||||
}
|
||||
|
||||
enum CoupleRole {
|
||||
Ones = "Ones", // aka "Actives"
|
||||
Twos = "Twos", // aka "Inactives"
|
||||
}
|
||||
|
||||
enum Rotation {
|
||||
Up = 0,
|
||||
Down = 180,
|
||||
Left = 90,
|
||||
Right = 270,
|
||||
}
|
||||
|
||||
export interface DancerIdentity {
|
||||
danceRole: DanceRole,
|
||||
coupleRole: CoupleRole, // TODO better name for this?
|
||||
}
|
||||
|
||||
interface ExtendedDancerIdentity {
|
||||
// Identity within own set.
|
||||
setIdentity: DancerIdentity,
|
||||
|
||||
// 0 = focused line, -1 = line to left, +1 = line to right, ...
|
||||
relativeLine: number,
|
||||
|
||||
// 0 = focused set, +1 = next set up, -1 = next set down.
|
||||
relativeSet: number,
|
||||
}
|
||||
|
||||
export interface Offset {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export const offsetZero : Offset = { x: 0, y: 0 };
|
||||
|
||||
// Distance from one side of the set to the other. Set so the left
|
||||
// of the set is -1 and the right of the set is +1.
|
||||
export const setWidth : number = 2;
|
||||
|
||||
// Distance from top of one hands four to its bottom.
|
||||
// Equivalently, the distance between a dancer and their neighbor
|
||||
// in improper position. Set so +1 is the top and -1 is the bottom.
|
||||
export const setHeight : number = 2;
|
||||
|
||||
// Distance between two hands fours in neutral position.
|
||||
const setSpacing : number = setHeight;
|
||||
|
||||
// y offset between any dancer and their previous/next neighbor
|
||||
// of the same role.
|
||||
const setDistance : number = setHeight + setSpacing;
|
||||
|
||||
const lineSpacing : number = setWidth;
|
||||
|
||||
// x offset between any dancer and their eqivalent in the line to
|
||||
// to left/right.
|
||||
const lineDistance : number = setWidth + lineSpacing;
|
||||
|
||||
const dancerWidth : number = 0.75; // Not sure about this value.
|
||||
const dancerHeight : number = 0.25; // Not sure about this value.
|
||||
const dancerHeightOffset : number = 0; //dancerHeight/4;
|
||||
|
||||
|
||||
export const leftShoulder : Offset = { x: -dancerWidth/2, y: -dancerHeightOffset };
|
||||
export const rightShoulder : Offset = { x: dancerWidth/2, y: -dancerHeightOffset };
|
||||
|
||||
export interface DancerSetPosition {
|
||||
// Position of the dancer relative to the center of their set.
|
||||
position: Offset;
|
||||
// Rotation in degrees where 0 is up the hall,
|
||||
// 90 is to the right, 180 is down the hall,
|
||||
// and 270 is to the left.
|
||||
rotation: number;
|
||||
leftArmEnd?: Offset;
|
||||
rightArmEnd?: Offset;
|
||||
}
|
||||
|
||||
function degreesToRadians(degrees: number) {
|
||||
return degrees * (Math.PI / 180);
|
||||
}
|
||||
|
||||
function hueForDancer(identity: DancerIdentity) : number {
|
||||
if (identity.coupleRole == CoupleRole.Ones) {
|
||||
if (identity.danceRole == DanceRole.Lark) {
|
||||
return 0; //red
|
||||
} else {
|
||||
return 39; //orange
|
||||
}
|
||||
} else {
|
||||
if (identity.danceRole == DanceRole.Lark) {
|
||||
return 240; //blue
|
||||
} else {
|
||||
return 180; //teal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function colorForDancer(identity: ExtendedDancerIdentity) : string {
|
||||
const hue = hueForDancer(identity.setIdentity);
|
||||
const sat = 100 - Math.abs(identity.relativeLine * 40);
|
||||
const lum = 50 + identity.relativeSet * 20;
|
||||
return `hsl(${hue}, ${sat}%, ${lum}%)`;
|
||||
}
|
||||
|
||||
export const improperNeutral = new Map<DancerIdentity, DancerSetPosition>([
|
||||
[{coupleRole: CoupleRole.Ones, danceRole: DanceRole.Lark}, {
|
||||
position: {x: +1, y: -1},
|
||||
rotation: Rotation.Right,
|
||||
}],
|
||||
[{coupleRole: CoupleRole.Ones, danceRole: DanceRole.Robin}, {
|
||||
position: {x: -1, y: -1},
|
||||
rotation: Rotation.Left,
|
||||
}],
|
||||
[{coupleRole: CoupleRole.Twos, danceRole: DanceRole.Lark}, {
|
||||
position: {x: -1, y: +1},
|
||||
rotation: Rotation.Left,
|
||||
}],
|
||||
[{coupleRole: CoupleRole.Twos, danceRole: DanceRole.Robin}, {
|
||||
position: {x: +1, y: +1},
|
||||
rotation: Rotation.Right,
|
||||
}],
|
||||
]);
|
||||
|
||||
export const improperLongLines = new Map<DancerIdentity, DancerSetPosition>([
|
||||
[{coupleRole: CoupleRole.Ones, danceRole: DanceRole.Lark}, {
|
||||
position: {x: +1, y: -1},
|
||||
rotation: Rotation.Right,
|
||||
leftArmEnd: {x: -1, y: 0},
|
||||
rightArmEnd: {x: +1, y: 0},
|
||||
}],
|
||||
[{coupleRole: CoupleRole.Ones, danceRole: DanceRole.Robin}, {
|
||||
position: {x: -1, y: -1},
|
||||
rotation: Rotation.Left,
|
||||
leftArmEnd: {x: -1, y: 0},
|
||||
rightArmEnd: {x: +1, y: 0},
|
||||
}],
|
||||
[{coupleRole: CoupleRole.Twos, danceRole: DanceRole.Lark}, {
|
||||
position: {x: -1, y: +1},
|
||||
rotation: Rotation.Left,
|
||||
leftArmEnd: {x: -1, y: 0},
|
||||
rightArmEnd: {x: +1, y: 0},
|
||||
}],
|
||||
[{coupleRole: CoupleRole.Twos, danceRole: DanceRole.Robin}, {
|
||||
position: {x: +1, y: +1},
|
||||
rotation: Rotation.Right,
|
||||
leftArmEnd: {x: -1, y: 0},
|
||||
rightArmEnd: {x: +1, y: 0},
|
||||
}],
|
||||
]);
|
||||
|
||||
export class Renderer {
|
||||
ctx: CanvasRenderingContext2D;
|
||||
constructor(ctx: CanvasRenderingContext2D) {
|
||||
this.ctx = ctx;
|
||||
}
|
||||
|
||||
drawDancerBody() {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
|
||||
this.ctx.lineTo(rightShoulder.x, rightShoulder.y);
|
||||
this.ctx.lineTo(0, dancerHeight-dancerHeightOffset);
|
||||
this.ctx.fill();
|
||||
|
||||
// Draw dot at origin to identify "center" point of dancer.
|
||||
const backupFillStyle = this.ctx.fillStyle;
|
||||
this.ctx.fillStyle = 'black';
|
||||
const pointSize = 0.05;
|
||||
this.ctx.fillRect(-pointSize/2, -pointSize/2, pointSize, pointSize);
|
||||
this.ctx.fillStyle = backupFillStyle;
|
||||
}
|
||||
|
||||
drawDancer(position: DancerSetPosition, identity?: ExtendedDancerIdentity) {
|
||||
this.ctx.save();
|
||||
|
||||
if (identity) {
|
||||
this.ctx.translate(identity.relativeLine * lineDistance,
|
||||
identity.relativeSet * setDistance);
|
||||
this.ctx.fillStyle = this.ctx.strokeStyle = colorForDancer(identity);
|
||||
}
|
||||
|
||||
this.ctx.translate(position.position.x, position.position.y);
|
||||
this.ctx.rotate(-degreesToRadians(position.rotation));
|
||||
|
||||
this.drawDancerBody();
|
||||
|
||||
// Draw arms.
|
||||
this.ctx.lineWidth = 0.03;
|
||||
if (position.leftArmEnd) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(leftShoulder.x, leftShoulder.y);
|
||||
this.ctx.lineTo(position.leftArmEnd.x, position.leftArmEnd.y);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
if (position.rightArmEnd) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(rightShoulder.x, rightShoulder.y);
|
||||
this.ctx.lineTo(position.rightArmEnd.x, position.rightArmEnd.y);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
this.ctx.restore();
|
||||
}
|
||||
|
||||
drawSet(positions: Map<DancerIdentity, DancerSetPosition>, relativeSet: number, relativeLine: number) {
|
||||
for (const entry of positions.entries()) {
|
||||
this.drawDancer(entry[1], { setIdentity: entry[0], relativeLine, relativeSet });
|
||||
}
|
||||
}
|
||||
|
||||
drawSets(positions: Map<DancerIdentity, DancerSetPosition>, extraSets: number, extraLines: number) {
|
||||
for (var relativeLine = -extraLines; relativeLine <= extraLines; relativeLine++) {
|
||||
for (var relativeSet = -extraSets; relativeSet <= extraSets; relativeSet++) {
|
||||
this.drawSet(positions, relativeSet, relativeLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user