Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TugOfWar demo #92

Merged
merged 6 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
239 changes: 239 additions & 0 deletions experiments/C/src/TugOfWar/Interface/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import pandas as pd
import dash
from dash import dcc, html, Input, Output
from dash.exceptions import PreventUpdate
import plotly.express as px
from flask import Flask, request
import json
import app_helper as ap
import dash_bootstrap_components as dbc
import dash_daq as daq


player_force = dict({
'players': ['Team_A_Player_0', 'Team_A_Player_1', 'Team_B_Player_0', 'Team_B_Player_1'],
'forces': [0, 0, 0, 0],
'score': 'Advantage: None',
'rope_mark': 20
})

print(player_force)

limit1 = 5
limit2 = 35
rope_range = 40


# Create Flask server
server = Flask(__name__)

# Create Dash app
app = dash.Dash(__name__, server=server,external_stylesheets=[dbc.themes.BOOTSTRAP])

df = ap.build_rope_dataframe(limit1, limit2, player_force['rope_mark'])

# Define layout
app.layout = html.Div([
dbc.Card(
dbc.CardBody([

# Row 1: title
dbc.Row([
dbc.Col([
html.Div(
dbc.CardImg(src='assets/lf.png',
style={'backgroundColor': '#013163', 'width': '40%', 'height': '40%'},
id='logo'
)
, style={'align': 'center'})
], align='center', width=2),
dbc.Col([
html.Div(ap.drawTextCard("TUG OF WAR"),
id='title'),
], width=10),
], style={'backgroundColor': '#013163'}, align='center'),
html.Br(),

### Row 2: Score
dbc.Row([
dbc.Col([
html.Div(
dbc.Card([
dbc.CardBody([
html.Div([
dcc.Input(id='score', value=player_force['score'], type="text", style={'fontSize': '20px'})
], style={'textAlign': 'center'})
])
])
),
], width=12),
], align='center'),
html.Br(),

### Row 3: Rope
dbc.Row([
dbc.Col([
html.Div(
dbc.Card(
dbc.CardBody([
dcc.Graph(
id='rope',
figure=px.scatter(df,
x="x",
y="y",
color="val",
symbol='val',
size='val',
range_x=[1,rope_range],
range_y=[0, 0]
).update_layout(
template='plotly_dark',
plot_bgcolor= 'rgba(0, 0, 0, 0)',
paper_bgcolor= 'rgba(0, 0, 0, 0)',
xaxis={'visible':False},
coloraxis={'showscale':False}
),
config={
'displayModeBar': False
}

)
])
),
)
], width=12),
], align='center'),
html.Br(),

### Row 4: Gauges
dbc.Row([
## Gauge 1
dbc.Col([
html.Div(
dbc.Card([
dbc.CardBody([
html.Div([
daq.Gauge(
value=player_force['forces'][0],
label=player_force['players'][0],
max=20,
min=0, id='gauge1'
)
], style={'color': 'dark', 'margin': '2', 'textAlign': 'center'})
])
])
),
], width=3),

## Gauge 2
dbc.Col([
html.Div(
dbc.Card([
dbc.CardBody([
html.Div([
daq.Gauge(
value=player_force['forces'][1],
label=player_force['players'][1],
max=20,
min=0, id='gauge2'
)
], style={'color': 'dark', 'margin': '2', 'textAlign': 'center'})
])
])
),
], width=3),

## Gauge 3
dbc.Col([
html.Div(
dbc.Card([
dbc.CardBody([
html.Div([
daq.Gauge(
value=player_force['forces'][2],
label=player_force['players'][2],
max=20,
min=0, id='gauge3'
)
], style={'color': 'dark', 'margin': '2', 'textAlign': 'center'})
])
])
),
], width=3),

## Gauge 4
dbc.Col([
html.Div(
dbc.Card([
dbc.CardBody([
html.Div([
daq.Gauge(
value=player_force['forces'][3],
label=player_force['players'][3],
max=20,
min=0, id='gauge4'
)
], style={'color': 'dark', 'margin': '2', 'textAlign': 'center'})
])
])
),
], width=3),
],align='center'),

])
), dcc.Interval(id='interval-component', interval=1000, n_intervals=0)
])

@app.callback([
Output('gauge1', 'value'),
Output('gauge2', 'value'),
Output('gauge3', 'value'),
Output('gauge4', 'value'),
Output('rope', 'figure'),
Output('score', 'value')
],
Input('interval-component', 'n_intervals')
)
def update_layout(n):
global player_force
global rope_range
global limit1
global limit2
df = ap.build_rope_dataframe(limit1, limit2, player_force['rope_mark'])
fig = px.scatter(df,
x="x",
y="y",
color="val",
symbol='val',
size='val',
range_x=[1,rope_range],
range_y=[0, 0]
).update_layout(
template='plotly_dark',
plot_bgcolor= 'rgba(0, 0, 0, 0)',
paper_bgcolor= 'rgba(0, 0, 0, 0)',
xaxis={'visible':False},
coloraxis={'showscale':False}
)
return player_force['forces'][0], \
player_force['forces'][1], \
player_force['forces'][2], \
player_force['forces'][3], \
fig, \
player_force['score']

@server.route('/update_force', methods=['POST'])
def update_force():
global player_force
try:
data = json.loads(request.data)
player_force = data[0]
print(player_force)
# player_force['players'] = data['players']
# player_force['rope_mark'] = int(data['rope_mark'])
return 'Force values updated successfully'
except Exception as e:
return str(e), 400

if __name__ == '__main__':
server.run(port=5004)
63 changes: 63 additions & 0 deletions experiments/C/src/TugOfWar/Interface/app_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from dash import dcc, html
import dash_bootstrap_components as dbc
import plotly.express as px
import dash_daq as daq
import pandas as pd
import plotly.graph_objects as go

def drawFigureCard(l1, l2, m):
df = build_rope_dataframe(l1, l2, m)
return dbc.Card(
dbc.CardBody([
dcc.Graph(
figure=px.scatter(df,
x="x",
y="y",
color="val",
symbol='val',
size='val',
range_x=[0,40],
range_y=[0, 0]
).update_layout(
template='plotly_dark',
plot_bgcolor= 'rgba(0, 0, 0, 0)',
paper_bgcolor= 'rgba(0, 0, 0, 0)',
xaxis={'visible':False},
coloraxis={'showscale':False}
),
config={
'displayModeBar': False
}
)
])
)

# Title field
def drawTextCard(tt):
return dbc.Card(
dbc.CardBody([
html.Div([
html.H1(tt, style={'color': 'white'}),
], style={'textAlign': 'center'})
], style={'backgroundColor': '#013163'})
)

# Build the dataframe for the heatmap
def build_rope_dataframe(limit1, limit2, mark):
# Create a DataFrame of zeros
# Create lists for 'x', 'y', and 'val'
x_values = list(range(1, 39))
y_values = [0]
val_values = [0] * len(x_values)
data = {'x': [], 'y': [], 'val': []}
for y in y_values:
data['x'].extend(x_values)
data['y'].extend([y] * len(x_values))
data['val'].extend(val_values)
df = pd.DataFrame(data)
# Render the limits
df.loc[(df['x'] == limit1) & (df['y'] == 0), 'val'] = 50
df.loc[(df['x'] == limit2) & (df['y'] == 0), 'val'] = 50
# # Render the mark
df.loc[(df['x'] == mark) & (df['y'] == 0), 'val'] = 100
return df
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions experiments/C/src/TugOfWar/Interface/request.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
curl -X POST http://127.0.0.1:5004/update_force -H "Content-Type: application/json" -d '[
{"players": ["Team_A_Player_0", "Team_A_Player_1", "Team_B_Player_0", "Team_B_Player_1"],
"forces": [12, 13, 11, 12],
"score": "ttt",
"rope_mark": 1
}
]'
8 changes: 8 additions & 0 deletions experiments/C/src/TugOfWar/Interface/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
flask==2.2.0
dash==2.11.1
Werkzeug==2.2.0
pandas
plotly_express
dash_bootstrap_components
dash_daq
setuptools
18 changes: 18 additions & 0 deletions experiments/C/src/TugOfWar/Interface/start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/bin/bash
if [ ! -f ".lf_env/bin/activate" ]
then
# Create a virtual environment
virtualenv .venv

# Activate the virtual environment
source .venv/bin/activate

# Install Flask
pip install -r requirements.txt
else
# Activate the virtual environment
source .venv/bin/activate
fi

# Start the Flask and Dash application
python3 app.py
17 changes: 17 additions & 0 deletions experiments/C/src/TugOfWar/Player.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
target C

preamble {=
#include <stdlib.h>
=}

reactor Player(max_force: int = 7) {
output out: int
timer t(0, 1 s)

reaction(t) -> out {=
int lower = 1;
int force = (rand() % (self->max_force - lower + 1)) + lower;
lf_print("Force = %d", force);
lf_set(out, force);
=}
}
17 changes: 17 additions & 0 deletions experiments/C/src/TugOfWar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Tug of War

Tug of War is a team-based game where two teams pull on opposite ends of a rope to bring the other team across a center marker. The demo involves two players per team, each characterized by a parameter. Players apply a randomly generated force within the interval 1 and a specified maximum force parameter. The sum of forces on each side moves the marker, and when it reaches one of the limits, the game ends.

## How it works

This example features a browser-based UI. The server is constructed using [Flask](https://flask.palletsprojects.com/en/3.0.x/), a Python web framework, and [Dash](https://dash.plotly.com/) components.

Every second, player forces are generated and their values are send to the server as
a post request. The UI's gauges are updated with these values, along with the position of the mark (yellow square). When the mark reaches one of the limits (pink diamond), the label on the top of the page is updated with the result.

## Steps:

1. Compile `TugOfWar.lf`.
2. Launch the UI by running the script `start.sh` in the `C/src/TugOfWar/Interface` directory. The script will create a virtual environment and install the requirements listed in `requirements.txt`, if not there already. It then lauches Flask server, accessible on: [http://127.0.0.1:5004](http://127.0.0.1:5004).
3. Run the launching script under `bin` and watch the game on the UI.

21 changes: 21 additions & 0 deletions experiments/C/src/TugOfWar/TugOfWar.lf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
target C {
keepalive: true
}

import Player from "Player.lf"
import TugOfWarGame from "TugOfWarGame.lf"

preamble {=
#include <string.h>
#include <unistd.h>
=}

federated reactor TugOfWar {
towg = new TugOfWarGame()
p1 = new Player(max_force=5)
p2 = new Player(max_force=6)
p3 = new Player(max_force=3)
p4 = new Player(max_force=9)

p1.out, p2.out, p3.out, p4.out -> towg.force
}
Loading
Loading