Add the start of phase/state synchronization and transitions
This commit is contained in:
parent
774434b4ad
commit
74330b9af2
|
@ -1,6 +1,7 @@
|
|||
WIP credits:
|
||||
|
||||
* Banner image from https://benjaminhuen.blogspot.com/2010/12/spetch-dump.html
|
||||
* Icon from https://opengameart.org/content/700-rpg-icons
|
||||
* Icons from https://opengameart.org/content/700-rpg-icons
|
||||
* NordSudA font from https://fontlibrary.org/en/font/nord-sud
|
||||
* Theme modified from https://github.com/arulrajnet/attila
|
||||
* Coloured icons from https://opengameart.org/content/colored-ability-icons
|
||||
|
|
152
negotiation.py
152
negotiation.py
|
@ -70,7 +70,9 @@ def negotiation_create():
|
|||
# are being sent to it instead.
|
||||
return flask.redirect('/negotiations/{}'.format(n))
|
||||
nego = Negotiation(name = n, owner = user.id, taker_sway = 1,
|
||||
market_sway = 6, is_first_negotiation = 1)
|
||||
market_sway = 6, is_first_negotiation = True,
|
||||
manual_negotiation = True, current_phase = 'marketprep',
|
||||
)
|
||||
pony.orm.commit()
|
||||
return flask.redirect('/negotiations/{}'.format(n))
|
||||
|
||||
|
@ -91,6 +93,7 @@ def negotiation(negotiation):
|
|||
'room_owner': nego.owner,
|
||||
'room_owner_display_name': nego.owner.display_name if nego.owner.display_name else nego.owner.uid,
|
||||
'participant_count': len(get_room_participants(negotiation)),
|
||||
'phase': get_negotiation_phase(nego),
|
||||
}
|
||||
r.data = render_page('negotiation.html', c)
|
||||
return r
|
||||
|
@ -111,7 +114,147 @@ def handle_join_negotiation(json):
|
|||
room = json['room'])
|
||||
# Update the user's negotation state with what is stored on the server
|
||||
nego = get_negotiation(json['room'])
|
||||
return nego.to_dict()
|
||||
# Send the newly arrived use the computed phase information
|
||||
phase = get_negotiation_phase(nego);
|
||||
return {'negotiation': nego.to_dict(), 'phase': phase}
|
||||
|
||||
phases = {
|
||||
'marketprep': {
|
||||
'previous': None,
|
||||
'next': 'takerprep',
|
||||
'display_name': 'Market Prep',
|
||||
'description': 'Set up the negotiation\'s basic information in the UI, under "Negotiation Settings". When you\'re done, click on the "Next" button (right hand swip image)',
|
||||
'description_taker': 'The Market is setting up the negotiation, hang on.',
|
||||
'has_sub_phases': False,
|
||||
},
|
||||
'takerprep': {
|
||||
'previous': 'marketprep',
|
||||
'next': 'negotiatorselection',
|
||||
'display_name': 'Taker Prep',
|
||||
'description': 'Each taker may take one of the prep actions: x, y, z',
|
||||
'has_sub_phases': False,
|
||||
},
|
||||
'negotiatorselection': {
|
||||
'previous': 'takerprep',
|
||||
'next': 'firstimpression',
|
||||
'display_name': 'Negotiator Selection',
|
||||
'description': 'The crew selectons one member to be the negotiator. This character will perform all of the negotiation actions, but may be supported by the rest of the crew during scams.',
|
||||
'has_sub_phases': False,
|
||||
},
|
||||
'firstimpression': {
|
||||
'previous': 'negotiatorselection',
|
||||
'next': 'negotiation',
|
||||
'display_name': 'First Impression',
|
||||
'description': 'The taker crew\'s negotiator rolls a leadership check. The results of this check determine the number of rounds that a negotiation lasts, the taker\'s starting position on the sway tracker, if the negotiation length is known to the takers, and who will lead in each negotiation round.',
|
||||
'has_sub_phases': False,
|
||||
},
|
||||
'negotiation': {
|
||||
'previous': 'firstimpression',
|
||||
'next': 'wrapup',
|
||||
'display_name': 'Negotiation',
|
||||
'description': 'A round robin of: both negotiators playing tactics in order, checking if the negotiation should end, and a scam if not all the remaining takers have done one already.',
|
||||
'has_sub_phases': True,
|
||||
'sub_phases_repeat': True,
|
||||
'sub_phases': [
|
||||
'first_negotiator_tactic',
|
||||
'second_negotiatior_tactic',
|
||||
'check_negotiation_end',
|
||||
'scam',
|
||||
],
|
||||
},
|
||||
'wrapup': {
|
||||
'previous': 'negotiation',
|
||||
'next': 'done',
|
||||
'display_name': 'Wrap-Up',
|
||||
'description': 'Final details to close out the negotiation',
|
||||
'has_sub_phases': True,
|
||||
'sub_phases_repeat': False,
|
||||
'sub_phases': [
|
||||
'leadership',
|
||||
'accept_backout',
|
||||
'undercut',
|
||||
],
|
||||
},
|
||||
'done': {
|
||||
'previous': 'wrapup',
|
||||
'next': None,
|
||||
'display_name': 'Done',
|
||||
'description': 'The negotiation is concluded',
|
||||
'has_sub_phases': False,
|
||||
},
|
||||
}
|
||||
|
||||
def get_negotiation_phase(nego):
|
||||
r = dict();
|
||||
r['current_phase'] = nego.current_phase
|
||||
r['current_sub_phase'] = nego.current_sub_phase
|
||||
r['display_name'] = phases[r['current_phase']]['display_name'] if r['current_phase'] in phases else 'Unknown phase "{}"'.format(r['current_phase'])
|
||||
r['description'] = phases[r['current_phase']]['description'] if r['current_phase'] in phases else 'Unknown phase "{}"'.format(r['current_phase'])
|
||||
r['description_taker'] = phases[r['current_phase']]['description_taker'] if 'description_taker' in phases[r['current_phase']].keys() else r['description']
|
||||
# Can the takers see the round information?
|
||||
r['visible_rounds'] = True
|
||||
r['round_current'] = nego.negotiation_round
|
||||
try:
|
||||
r['round_max'] = max(floor(nego.first_impression_black / 2), 5)
|
||||
except:
|
||||
r['round_max'] = 'First impression not yet done'
|
||||
r['previous_phase'] = phases[nego.current_phase]['previous']
|
||||
r['next_phase'] = phases[nego.current_phase]['next']
|
||||
return r
|
||||
|
||||
@socketio.on('set next phase')
|
||||
@pony.orm.db_session
|
||||
def handle_set_next_phase(json):
|
||||
r = flask.Response()
|
||||
app.logger.info('Received set next phase request: {}'.format(json))
|
||||
uid, user = get_uid(r)
|
||||
nego = get_negotiation(json['room'])
|
||||
app.logger.info('Room owner is {}'.format(nego.owner.uid))
|
||||
if nego.owner.uid != uid:
|
||||
# Refuse the update from non-owners
|
||||
app.logger.warning('Refusing set next phase of {} from non-owner {}'.format(json['room'], uid))
|
||||
return False
|
||||
current_phase = nego.current_phase
|
||||
next_phase_key = phases[current_phase]['next'];
|
||||
if next_phase_key is None:
|
||||
# @TODO. Anything here?
|
||||
return False
|
||||
# @TODO Handle sub phases
|
||||
nego.current_phase = next_phase_key
|
||||
pony.orm.commit()
|
||||
data = {
|
||||
'phase': get_negotiation_phase(nego),
|
||||
'transitioned_from': current_phase,
|
||||
}
|
||||
flask_socketio.emit('negotiation state changed', {**data}, room = json['room'])
|
||||
return True
|
||||
|
||||
@socketio.on('set previous phase')
|
||||
@pony.orm.db_session
|
||||
def handle_set_prev_phase(json):
|
||||
r = flask.Response()
|
||||
app.logger.info('Received set previous phase request: {}'.format(json))
|
||||
uid, user = get_uid(r)
|
||||
nego = get_negotiation(json['room'])
|
||||
app.logger.info('Room owner is {}'.format(nego.owner.uid))
|
||||
if nego.owner.uid != uid:
|
||||
# Refuse the update from non-owners
|
||||
app.logger.warning('Refusing set previous phase of {} from non-owner {}'.format(json['room'], uid))
|
||||
return False
|
||||
current_phase = nego.current_phase
|
||||
next_phase_key = phases[current_phase]['previous'];
|
||||
if next_phase_key is None:
|
||||
# @TODO. Anything here?
|
||||
return False
|
||||
# @TODO Handle sub phases
|
||||
nego.current_phase = next_phase_key
|
||||
pony.orm.commit()
|
||||
data = {
|
||||
'phase': get_negotiation_phase(nego),
|
||||
'transitioned_from': current_phase,
|
||||
}
|
||||
flask_socketio.emit('negotiation state changed', {**data}, room = json['room'])
|
||||
return True
|
||||
|
||||
def get_room_participants(room):
|
||||
if '/' not in socketio.server.manager.get_namespaces():
|
||||
|
@ -253,6 +396,11 @@ if __name__ == '__main__':
|
|||
taker_crew_name = pony.orm.Optional(str)
|
||||
# Manual negotation
|
||||
manual_negotiation = pony.orm.Optional(bool)
|
||||
# marketprep, takerprep, negotiatorselection, firstimpression,
|
||||
# negotiation (first, second, checkendcondition, scam)
|
||||
# wrapup ( leadership, accept_refuse, undercut)
|
||||
current_phase = pony.orm.Optional(str)
|
||||
current_sub_phase = pony.orm.Optional(str)
|
||||
# Pre-negotiation details
|
||||
takers = pony.orm.Optional(int)
|
||||
lead_negotiator = pony.orm.Optional(str)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/* Retrieved from https://fontlibrary.org/face/nord-sud */
|
||||
@font-face {
|
||||
font-family: 'NordSudA';
|
||||
src: url('/static/font/NordSudA.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NordSudB';
|
||||
src: url('/static/font/NordSudB.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'NordSudC';
|
||||
src: url('/static/font/NordSudC.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
|
@ -973,8 +973,82 @@ img {
|
|||
max-width: 30%;
|
||||
}
|
||||
|
||||
.negotiation-sidebar p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.negotiation-sidebar h4 {
|
||||
margin-top: 0;
|
||||
font-family: 'NordSudA';
|
||||
color: #600000;
|
||||
text-shadow: 2px 4px #1d0202;
|
||||
margin-top: 0.15em;
|
||||
margin-bottom: 0.15em;
|
||||
}
|
||||
|
||||
.onoffswitch.yesno .onoffswitch-inner:before {
|
||||
content: "YES";
|
||||
}
|
||||
|
||||
.onoffswitch.yesno .onoffswitch-inner:after {
|
||||
content: "NO";
|
||||
}
|
||||
|
||||
.negotiation-sidebar .onoffswitch {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.negotiation-sidebar .negotiation-state-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.negotiation-sidebar h5 {
|
||||
margin: 0;
|
||||
font-family: 'NordSudA';
|
||||
color: #600000;
|
||||
text-shadow: 1px 2px #1d0202;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.fieldset .fieldset-button {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
font-size: 12px;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
.fieldset.fieldset-open .fieldset-button:after {
|
||||
content: "-Close-";
|
||||
}
|
||||
.fieldset.fieldset-closed .fieldset-button:after {
|
||||
content: "-Open-";
|
||||
}
|
||||
.fieldset.fieldset-closed div:first-of-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@-webkit-keyframes glow {
|
||||
to {
|
||||
border-color: #69c800;
|
||||
-webkit-box-shadow: 0 0 5px #69c800;
|
||||
-moz-box-shadow: 0 0 5px #69c800;
|
||||
box-shadow: 0 0 5px #69c800;
|
||||
}
|
||||
}
|
||||
|
||||
.market-settings p.phase-description {
|
||||
border: 1px solid transparent;
|
||||
-webkit-animation: glow 1.0s infinite alternate;
|
||||
-webkit-transition: border 1.0s linear, box-shadow 1.0s linear;
|
||||
-moz-transition: border 1.0s linear, box-shadow 1.0s linear;
|
||||
transition: border 1.0s linear, box-shadow 1.0s linear;
|
||||
}
|
||||
|
||||
.negotiation-panel {
|
||||
min-width: 70%;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.negotiation-panel h3 {
|
||||
|
@ -1974,6 +2048,9 @@ img {
|
|||
padding-left: 10px;
|
||||
background-color: #34A7C1; color: #FFFFFF;
|
||||
}
|
||||
.onoffswitch-label[disabled] .onoffswitch-inner:before {
|
||||
background-color: #2f3137;
|
||||
}
|
||||
.onoffswitch-inner:after {
|
||||
content: "OFF";
|
||||
padding-right: 10px;
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
|
@ -20,7 +20,7 @@
|
|||
<!-- Custom fonts -->
|
||||
<!-- link href='https://fonts.googleapis.com/css?family=Montserrat:400,300' rel='stylesheet' type='text/css' />
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet" type="text/css" /> -->
|
||||
<link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/nord-sud" type="text/css"/>
|
||||
<link rel="stylesheet" media="screen" href="/static/css/nordsud.css" type="text/css"/>
|
||||
{% endblock head %}
|
||||
</head>
|
||||
<body class="home-template " {% block bodyclasses %}{% endblock bodyclasses %}>
|
||||
|
|
|
@ -31,7 +31,8 @@
|
|||
socket.on('connect', function() {
|
||||
console.log("Trying to join room " + room);
|
||||
socket.emit('join negotiation', {"room": room}, function(e) {
|
||||
update_from_data(e);
|
||||
update_from_data(e['negotiation']);
|
||||
update_phase(e['phase']);
|
||||
});
|
||||
});
|
||||
window.addEventListener('beforeunload', function(e) {
|
||||
|
@ -66,6 +67,9 @@
|
|||
socket.on('negotiation updated', function(e) {
|
||||
update_from_data(e);
|
||||
});
|
||||
socket.on('negotiation state changed', function(e) {
|
||||
update_phase(e['phase'], e['transitioned_from']);
|
||||
});
|
||||
|
||||
function update_from_data(e) {
|
||||
console.log(e);
|
||||
|
@ -91,6 +95,43 @@
|
|||
$('#is-first-negotiation').prop('checked', e['is_first_negotiation'] == true)
|
||||
}
|
||||
}
|
||||
|
||||
function update_phase(p, transition_from = null) {
|
||||
console.log(p);
|
||||
$('.phase-display-name').html(p['display_name']);
|
||||
$('.phase-description').html(p['description']);
|
||||
$('.phase-description-taker').html(p['description_taker']);
|
||||
var was_disabled = $('#state-previous').attr('disabled');
|
||||
var previous_button = $('#state-previous');
|
||||
var next_button = $('#state-next');
|
||||
previous_button.attr('disabled', p['previous_phase'] == null);
|
||||
next_button.attr('disabled', p['next_phase'] == null);
|
||||
if (previous_button.attr('disabled')) {
|
||||
previous_button.attr('src', '/static/images/button-previous-disabled.png');
|
||||
}
|
||||
else {
|
||||
previous_button.attr('src', '/static/images/button-previous.png');
|
||||
}
|
||||
if (next_button.attr('disabled')) {
|
||||
next_button.attr('src', '/static/images/button-next-disabled.png');
|
||||
}
|
||||
else {
|
||||
next_button.attr('src', '/static/images/button-next.png');
|
||||
}
|
||||
}
|
||||
|
||||
function phase_on_next() {
|
||||
// Some client side validation when running in automatic should go here, before
|
||||
// the signal is sent to the server.
|
||||
console.log('next');
|
||||
socket.emit('set next phase', {"room": room});
|
||||
}
|
||||
|
||||
function phase_on_prev() {
|
||||
console.log('previous');
|
||||
socket.emit('set previous phase', {"room": room});
|
||||
}
|
||||
|
||||
function swayslot_on_dragstart(event) {
|
||||
var ev = event.originalEvent;
|
||||
ev.dataTransfer.setData("text/plain", ev.target.id);
|
||||
|
@ -152,9 +193,23 @@
|
|||
socket.emit('update negotiation', d);
|
||||
if (id == 'manual-negotiation') {
|
||||
$('#' + id).unbind('change').attr('disabled', true);
|
||||
$('#' + id).siblings('.onoffswitch-label').attr('disabled', true);
|
||||
}
|
||||
}
|
||||
|
||||
function toggle_fieldset(e) {
|
||||
console.log($(e.target));
|
||||
var parent = $(e.target).parents().parents('.fieldset').first();
|
||||
console.log(parent);
|
||||
if (parent.hasClass('fieldset-open')) {
|
||||
parent.removeClass('fieldset-open');
|
||||
parent.addClass('fieldset-closed');
|
||||
}
|
||||
else {
|
||||
parent.removeClass('fieldset-closed');
|
||||
parent.addClass('fieldset-open');
|
||||
}
|
||||
}
|
||||
// Drag/drog for the swaytracker
|
||||
var inputChanges = {};
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
@ -174,8 +229,19 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Disable the manual negotation toggle if it's already on.
|
||||
$('#state-next').on('click', phase_on_next);
|
||||
$('#state-previous').on('click', phase_on_prev);
|
||||
|
||||
// Disable the manual negotiation toggle if it's already on.
|
||||
$('#manual-negotiation[checked]').unbind('change').attr('disabled', true);
|
||||
$('#manual-negotiation[checked]').siblings('.onoffswitch-label').attr('disabled', true);
|
||||
|
||||
// Add fieldset bindings
|
||||
$('.fieldset .fieldset-button').on('click', function(e) {
|
||||
toggle_fieldset(e);
|
||||
});
|
||||
$('.fieldset.fieldset-open div').first().show();
|
||||
$('.fieldset:not(.fieldset-open) div').first().hide();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -7,7 +7,32 @@
|
|||
|
||||
{% if user == room_owner.uid %}
|
||||
<div class="market-settings">
|
||||
<div class="negotiation-state-wrapper">
|
||||
<input id="state-previous" type="image" width="32" height="32" alt="Go the previous state"
|
||||
{% if phase.current_phase == 'marketprep' %}
|
||||
disabled="true"
|
||||
src="/static/images/button-previous-disabled.png"
|
||||
{% else %}
|
||||
src="/static/images/button-previous.png"
|
||||
{% endif %}>
|
||||
<h5>
|
||||
<span class="phase-display-name">{{ phase.display_name }}</span>
|
||||
</h5>
|
||||
<br>
|
||||
<input id="state-next" type="image" width="32" height="32" alt="Next"
|
||||
{% if phase.current_phase == 'done' %}
|
||||
disabled="true"
|
||||
src="/static/images/button-next-disabled.png"
|
||||
{% else %}
|
||||
src="/static/images/button-next.png"
|
||||
{% endif %}>
|
||||
</div>
|
||||
<p class="phase-description">{{ phase.description }}</p>
|
||||
<form id="market-settings">
|
||||
<p class="phase-description-market"></p>
|
||||
<div class="fieldset {% if phase.current_phase == 'marketprep' %} fieldset-open{% else %} fieldset-closed{% endif %}">
|
||||
<h4>Negotiation Settings<span data-target=".negotiation-settings-wrapper" class="fieldset-button"></span></h4>
|
||||
<div class="negotiation-settings-wrapper">
|
||||
<span>Manual Negotiation</span>
|
||||
<div class="onoffswitch">
|
||||
<input id="manual-negotiation" type="checkbox" class="onoffswitch-checkbox" {% if negotiation.manual_negotiation %}checked{% endif %}>
|
||||
|
@ -16,9 +41,15 @@
|
|||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<label for="is-first-negotiation">First Negotiation?</label>
|
||||
<input id="is-first-negotiation" type="checkbox">
|
||||
<br><br>
|
||||
<span>Is First Negotiation?</span>
|
||||
<div class="onoffswitch yesno">
|
||||
<input id="is-first-negotiation" type="checkbox" class="onoffswitch-checkbox" {% if negotiation.is_first_negotiation %}checked{% endif %}>
|
||||
<label for="is-first-negotiation" class="onoffswitch-label">
|
||||
<span class="onoffswitch-inner"></span>
|
||||
<span class="onoffswitch-switch"></span>
|
||||
</label>
|
||||
</div>
|
||||
<br>
|
||||
<label for="client-name">Client Name</label>
|
||||
<input id="client-name" type="text" placeholder="Enter the client's name">
|
||||
|
@ -32,7 +63,9 @@
|
|||
<label for="takers">Numer of takers in the crew</label>
|
||||
<input id="takers" type="text" pattern="[0-9]+" size="1">
|
||||
<br>
|
||||
<label for="first-impression-black">First Impression (Black die value)</label>
|
||||
</div>
|
||||
</div>
|
||||
<label for="first-impression-black">First Impression (Black die value + Taker's leadership)</label>
|
||||
<input id="first-impression-black" type="text" pattern="[0-9]+" size="1">
|
||||
<br>
|
||||
<label for="first-impression-state">Level of success on the first impression</label>
|
||||
|
@ -44,6 +77,13 @@
|
|||
</select>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="negotiation-summary">
|
||||
<h4 class="phase-display-name">
|
||||
{{ phase.display_name }}
|
||||
</h4>
|
||||
<p class="phase-description-taker">{{ phase.description_taker }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="participants">
|
||||
|
|
Loading…
Reference in New Issue