From 1fbc400e9208f47bb8e9bac25ec4a21fdf3cc0e8 Mon Sep 17 00:00:00 2001 From: Chadlia Jerad Date: Sat, 6 Jan 2024 00:05:06 +0100 Subject: [PATCH] Add TagOfWar demo --- experiments/C/src/TugOfWar/Interface/app.py | 239 ++++++++++++++++++ .../C/src/TugOfWar/Interface/app_helper.py | 63 +++++ .../C/src/TugOfWar/Interface/assets/lf.png | Bin 0 -> 3429 bytes .../C/src/TugOfWar/Interface/request.sh | 7 + .../C/src/TugOfWar/Interface/requirements.txt | 7 + experiments/C/src/TugOfWar/Interface/start.sh | 18 ++ experiments/C/src/TugOfWar/Player.lf | 17 ++ experiments/C/src/TugOfWar/README.md | 17 ++ experiments/C/src/TugOfWar/TugOfWar.lf | 21 ++ experiments/C/src/TugOfWar/TugOfWarGame.lf | 111 ++++++++ 10 files changed, 500 insertions(+) create mode 100755 experiments/C/src/TugOfWar/Interface/app.py create mode 100755 experiments/C/src/TugOfWar/Interface/app_helper.py create mode 100755 experiments/C/src/TugOfWar/Interface/assets/lf.png create mode 100755 experiments/C/src/TugOfWar/Interface/request.sh create mode 100755 experiments/C/src/TugOfWar/Interface/requirements.txt create mode 100755 experiments/C/src/TugOfWar/Interface/start.sh create mode 100755 experiments/C/src/TugOfWar/Player.lf create mode 100644 experiments/C/src/TugOfWar/README.md create mode 100755 experiments/C/src/TugOfWar/TugOfWar.lf create mode 100755 experiments/C/src/TugOfWar/TugOfWarGame.lf diff --git a/experiments/C/src/TugOfWar/Interface/app.py b/experiments/C/src/TugOfWar/Interface/app.py new file mode 100755 index 00000000..88adec5e --- /dev/null +++ b/experiments/C/src/TugOfWar/Interface/app.py @@ -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) diff --git a/experiments/C/src/TugOfWar/Interface/app_helper.py b/experiments/C/src/TugOfWar/Interface/app_helper.py new file mode 100755 index 00000000..7f14b53b --- /dev/null +++ b/experiments/C/src/TugOfWar/Interface/app_helper.py @@ -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 diff --git a/experiments/C/src/TugOfWar/Interface/assets/lf.png b/experiments/C/src/TugOfWar/Interface/assets/lf.png new file mode 100755 index 0000000000000000000000000000000000000000..5c6bd98d5a2172a2db718d96996dee428d260bf3 GIT binary patch literal 3429 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 Hu0mjfbI6Sj literal 0 HcmV?d00001 diff --git a/experiments/C/src/TugOfWar/Interface/request.sh b/experiments/C/src/TugOfWar/Interface/request.sh new file mode 100755 index 00000000..d48afe7d --- /dev/null +++ b/experiments/C/src/TugOfWar/Interface/request.sh @@ -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 + } +]' diff --git a/experiments/C/src/TugOfWar/Interface/requirements.txt b/experiments/C/src/TugOfWar/Interface/requirements.txt new file mode 100755 index 00000000..70e7fae5 --- /dev/null +++ b/experiments/C/src/TugOfWar/Interface/requirements.txt @@ -0,0 +1,7 @@ +flask==2.2.0 +dash==2.11.1 +Werkzeug==2.2.0 +pandas +plotly_express +dash_bootstrap_components +dash_daq \ No newline at end of file diff --git a/experiments/C/src/TugOfWar/Interface/start.sh b/experiments/C/src/TugOfWar/Interface/start.sh new file mode 100755 index 00000000..16760419 --- /dev/null +++ b/experiments/C/src/TugOfWar/Interface/start.sh @@ -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 \ No newline at end of file diff --git a/experiments/C/src/TugOfWar/Player.lf b/experiments/C/src/TugOfWar/Player.lf new file mode 100755 index 00000000..10b3ad1e --- /dev/null +++ b/experiments/C/src/TugOfWar/Player.lf @@ -0,0 +1,17 @@ +target C + +preamble {= + #include +=} + +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); + =} +} diff --git a/experiments/C/src/TugOfWar/README.md b/experiments/C/src/TugOfWar/README.md new file mode 100644 index 00000000..31b8a28d --- /dev/null +++ b/experiments/C/src/TugOfWar/README.md @@ -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 runnig the script `start.sh` under `C/src/TugOfWar/Interface`. The script will create a virtual environment and install the requirements listed in `requirements.txt`, if not there already. It then lauches Falsk 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. + diff --git a/experiments/C/src/TugOfWar/TugOfWar.lf b/experiments/C/src/TugOfWar/TugOfWar.lf new file mode 100755 index 00000000..1a774860 --- /dev/null +++ b/experiments/C/src/TugOfWar/TugOfWar.lf @@ -0,0 +1,21 @@ +target C { + keepalive: true +} + +import Player from "Player.lf" +import TugOfWarGame from "TugOfWarGame.lf" + +preamble {= + #include + #include +=} + +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 +} diff --git a/experiments/C/src/TugOfWar/TugOfWarGame.lf b/experiments/C/src/TugOfWar/TugOfWarGame.lf new file mode 100755 index 00000000..3871000f --- /dev/null +++ b/experiments/C/src/TugOfWar/TugOfWarGame.lf @@ -0,0 +1,111 @@ +target C + +preamble {= + #include + #include +=} + +reactor TugOfWarGame { + input[4] force: int // Each player will exert a force + + timer t(0, 1 s) // Timer to compute the total status and send the display request + + state agent_force: int[4] = {0, 0, 0, 0} // States + state rope_mark: int = 20 + state updated: bool = false + + reaction(startup) {= + // Construct the data to send + char curl_cmd[1024]; + sprintf(curl_cmd, + "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\": [0, 0, 0, 0], \ + \"score\": \"Advantage: None\", \ + \"rope_mark\": 20 \ + }\ + ]' \ + " + ); + int status = system(curl_cmd); + if (status == 0) { + lf_print("Updates successfully sent."); + } else { + lf_print("Unable to send update."); + } + =} + + reaction(force) {= + int sum = 0; + for (int i = 0; i < 4; i++) { + if (force[i]->is_present) { + self->agent_force[i] = force[i]->value; + self->updated = true; + } + } + =} + + reaction(t) {= + if (self->updated) { + // Compute the new rope mark position + self->rope_mark = self->rope_mark - self->agent_force[0] + - self->agent_force[1] + + self->agent_force[2] + + self->agent_force[3]; + + // Derive the new score + char score[25]; + if (self->rope_mark <= 5) { + sprintf(score, "Winner: Team A"); + } else if (self->rope_mark >= 35) { + sprintf(score, "Winner: Team B"); + } else if (self->rope_mark == 20) { + sprintf(score, "Advantage: None"); + } else if (self->rope_mark < 20) { + sprintf(score, "Advantage: Team A"); + } else { + sprintf(score, "Advantage: Team B"); + } + + + // Construct the data to send + char curl_cmd[1024]; + + sprintf(curl_cmd, + "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\": [%d, %d, %d, %d], \ + \"score\": \"%s\", \ + \"rope_mark\": %d \ + }\ + ]' \ + ", + self->agent_force[0], + self->agent_force[1], + self->agent_force[2], + self->agent_force[3], + score, + self->rope_mark + ); + + int status = system(curl_cmd); + if (status == 0) { + lf_print("Updates successfully sent."); + } else { + lf_print("Unable to send update."); + } + + self->updated = false; + + // Reset all forces + for (int i = 0; i < 4; i++) { + self->agent_force[i] = 0; + } + + // If one of team won, stop the game + if (self->rope_mark<= 5 || self->rope_mark >= 35) { + lf_request_stop(); + } + } + =} +}